diff options
author | James Moger <james.moger@gitblit.com> | 2015-11-22 14:37:16 -0500 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2015-11-22 14:37:16 -0500 |
commit | ed552ba47c02779c270ffd62841d6d1048dade70 (patch) | |
tree | 45369badcb0e409fa08c04cd457eebc57eda3800 | |
parent | 27a099881969fb8628b1b19b7b7f76faf4e069ba (diff) | |
parent | e41e8f8c3bc9f5edab1d271464364f95620ece8c (diff) | |
download | gitblit-ed552ba47c02779c270ffd62841d6d1048dade70.tar.gz gitblit-ed552ba47c02779c270ffd62841d6d1048dade70.zip |
Merge branch 'develop'
263 files changed, 14517 insertions, 5869 deletions
@@ -5,59 +5,61 @@ <classpathentry kind="src" path="src/test/java" output="bin/test-classes" /> <classpathentry kind="src" path="src/test/bugtraq" output="bin/test-classes" /> <classpathentry kind="src" path="src/main/resources" /> - <classpathentry kind="lib" path="ext/dagger-1.1.0.jar" sourcepath="ext/src/dagger-1.1.0.jar" /> + <classpathentry kind="lib" path="ext/guice-4.0.jar" sourcepath="ext/src/guice-4.0.jar" /> <classpathentry kind="lib" path="ext/javax.inject-1.jar" sourcepath="ext/src/javax.inject-1.jar" /> - <classpathentry kind="lib" path="ext/dagger-compiler-1.1.0.jar" sourcepath="ext/src/dagger-compiler-1.1.0.jar" /> - <classpathentry kind="lib" path="ext/javawriter-2.1.1.jar" sourcepath="ext/src/javawriter-2.1.1.jar" /> + <classpathentry kind="lib" path="ext/aopalliance-1.0.jar" sourcepath="ext/src/aopalliance-1.0.jar" /> + <classpathentry kind="lib" path="ext/guava-18.0.jar" sourcepath="ext/src/guava-18.0.jar" /> + <classpathentry kind="lib" path="ext/guice-servlet-4.0-gb2.jar" sourcepath="ext/src/guice-servlet-4.0-gb2.jar" /> <classpathentry kind="lib" path="ext/annotations-12.0.jar" sourcepath="ext/src/annotations-12.0.jar" /> <classpathentry kind="lib" path="ext/log4j-1.2.17.jar" sourcepath="ext/src/log4j-1.2.17.jar" /> - <classpathentry kind="lib" path="ext/slf4j-api-1.7.5.jar" sourcepath="ext/src/slf4j-api-1.7.5.jar" /> - <classpathentry kind="lib" path="ext/slf4j-log4j12-1.7.5.jar" sourcepath="ext/src/slf4j-log4j12-1.7.5.jar" /> + <classpathentry kind="lib" path="ext/slf4j-api-1.7.12.jar" sourcepath="ext/src/slf4j-api-1.7.12.jar" /> + <classpathentry kind="lib" path="ext/slf4j-log4j12-1.7.12.jar" sourcepath="ext/src/slf4j-log4j12-1.7.12.jar" /> <classpathentry kind="lib" path="ext/javax.mail-1.5.1.jar" sourcepath="ext/src/javax.mail-1.5.1.jar" /> <classpathentry kind="lib" path="ext/javax.servlet-api-3.1.0.jar" sourcepath="ext/src/javax.servlet-api-3.1.0.jar" /> - <classpathentry kind="lib" path="ext/jetty-all-9.2.3.v20140905.jar" sourcepath="ext/src/jetty-all-9.2.3.v20140905.jar" /> - <classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21.jar" /> - <classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21.jar" /> - <classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21.jar" /> - <classpathentry kind="lib" path="ext/lucene-core-4.6.0.jar" sourcepath="ext/src/lucene-core-4.6.0.jar" /> - <classpathentry kind="lib" path="ext/lucene-analyzers-common-4.6.0.jar" sourcepath="ext/src/lucene-analyzers-common-4.6.0.jar" /> - <classpathentry kind="lib" path="ext/lucene-highlighter-4.6.0.jar" sourcepath="ext/src/lucene-highlighter-4.6.0.jar" /> - <classpathentry kind="lib" path="ext/lucene-memory-4.6.0.jar" sourcepath="ext/src/lucene-memory-4.6.0.jar" /> - <classpathentry kind="lib" path="ext/lucene-queries-4.6.0.jar" sourcepath="ext/src/lucene-queries-4.6.0.jar" /> - <classpathentry kind="lib" path="ext/lucene-queryparser-4.6.0.jar" sourcepath="ext/src/lucene-queryparser-4.6.0.jar" /> - <classpathentry kind="lib" path="ext/lucene-sandbox-4.6.0.jar" sourcepath="ext/src/lucene-sandbox-4.6.0.jar" /> + <classpathentry kind="lib" path="ext/jetty-all-9.2.13.v20150730.jar" sourcepath="ext/src/jetty-all-9.2.13.v20150730.jar" /> + <classpathentry kind="lib" path="ext/wicket-1.4.22.jar" sourcepath="ext/src/wicket-1.4.22.jar" /> + <classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.22.jar" sourcepath="ext/src/wicket-auth-roles-1.4.22.jar" /> + <classpathentry kind="lib" path="ext/wicket-extensions-1.4.22.jar" sourcepath="ext/src/wicket-extensions-1.4.22.jar" /> + <classpathentry kind="lib" path="ext/lucene-core-4.10.4.jar" sourcepath="ext/src/lucene-core-4.10.4.jar" /> + <classpathentry kind="lib" path="ext/lucene-analyzers-common-4.10.4.jar" sourcepath="ext/src/lucene-analyzers-common-4.10.4.jar" /> + <classpathentry kind="lib" path="ext/lucene-highlighter-4.10.4.jar" sourcepath="ext/src/lucene-highlighter-4.10.4.jar" /> + <classpathentry kind="lib" path="ext/lucene-memory-4.10.4.jar" sourcepath="ext/src/lucene-memory-4.10.4.jar" /> + <classpathentry kind="lib" path="ext/lucene-queries-4.10.4.jar" sourcepath="ext/src/lucene-queries-4.10.4.jar" /> + <classpathentry kind="lib" path="ext/lucene-queryparser-4.10.4.jar" sourcepath="ext/src/lucene-queryparser-4.10.4.jar" /> + <classpathentry kind="lib" path="ext/lucene-sandbox-4.10.4.jar" sourcepath="ext/src/lucene-sandbox-4.10.4.jar" /> <classpathentry kind="lib" path="ext/jakarta-regexp-1.4.jar" /> - <classpathentry kind="lib" path="ext/pegdown-1.4.2.jar" sourcepath="ext/src/pegdown-1.4.2.jar" /> - <classpathentry kind="lib" path="ext/parboiled-java-1.1.6.jar" sourcepath="ext/src/parboiled-java-1.1.6.jar" /> - <classpathentry kind="lib" path="ext/parboiled-core-1.1.6.jar" sourcepath="ext/src/parboiled-core-1.1.6.jar" /> - <classpathentry kind="lib" path="ext/asm-4.1.jar" sourcepath="ext/src/asm-4.1.jar" /> - <classpathentry kind="lib" path="ext/asm-tree-4.1.jar" sourcepath="ext/src/asm-tree-4.1.jar" /> - <classpathentry kind="lib" path="ext/asm-analysis-4.1.jar" sourcepath="ext/src/asm-analysis-4.1.jar" /> - <classpathentry kind="lib" path="ext/asm-util-4.1.jar" sourcepath="ext/src/asm-util-4.1.jar" /> + <classpathentry kind="lib" path="ext/pegdown-1.5.0.jar" sourcepath="ext/src/pegdown-1.5.0.jar" /> + <classpathentry kind="lib" path="ext/parboiled-java-1.1.7.jar" sourcepath="ext/src/parboiled-java-1.1.7.jar" /> + <classpathentry kind="lib" path="ext/parboiled-core-1.1.7.jar" sourcepath="ext/src/parboiled-core-1.1.7.jar" /> + <classpathentry kind="lib" path="ext/asm-5.0.3.jar" sourcepath="ext/src/asm-5.0.3.jar" /> + <classpathentry kind="lib" path="ext/asm-tree-5.0.3.jar" sourcepath="ext/src/asm-tree-5.0.3.jar" /> + <classpathentry kind="lib" path="ext/asm-analysis-5.0.3.jar" sourcepath="ext/src/asm-analysis-5.0.3.jar" /> + <classpathentry kind="lib" path="ext/asm-util-5.0.3.jar" sourcepath="ext/src/asm-util-5.0.3.jar" /> <classpathentry kind="lib" path="ext/wikitext-core-1.4.jar" sourcepath="ext/src/wikitext-core-1.4.jar" /> <classpathentry kind="lib" path="ext/twiki-core-1.4.jar" sourcepath="ext/src/twiki-core-1.4.jar" /> <classpathentry kind="lib" path="ext/textile-core-1.4.jar" sourcepath="ext/src/textile-core-1.4.jar" /> <classpathentry kind="lib" path="ext/tracwiki-core-1.4.jar" sourcepath="ext/src/tracwiki-core-1.4.jar" /> <classpathentry kind="lib" path="ext/mediawiki-core-1.4.jar" sourcepath="ext/src/mediawiki-core-1.4.jar" /> <classpathentry kind="lib" path="ext/confluence-core-1.4.jar" sourcepath="ext/src/confluence-core-1.4.jar" /> - <classpathentry kind="lib" path="ext/org.eclipse.jgit-3.5.1.201410131835-r.jar" sourcepath="ext/src/org.eclipse.jgit-3.5.1.201410131835-r.jar" /> - <classpathentry kind="lib" path="ext/jsch-0.1.50.jar" sourcepath="ext/src/jsch-0.1.50.jar" /> + <classpathentry kind="lib" path="ext/org.eclipse.jgit-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit-4.1.1.201511131810-r.jar" /> + <classpathentry kind="lib" path="ext/jsch-0.1.53.jar" sourcepath="ext/src/jsch-0.1.53.jar" /> <classpathentry kind="lib" path="ext/JavaEWAH-0.7.9.jar" sourcepath="ext/src/JavaEWAH-0.7.9.jar" /> - <classpathentry kind="lib" path="ext/httpclient-4.1.3.jar" sourcepath="ext/src/httpclient-4.1.3.jar" /> - <classpathentry kind="lib" path="ext/httpcore-4.1.4.jar" sourcepath="ext/src/httpcore-4.1.4.jar" /> - <classpathentry kind="lib" path="ext/commons-logging-1.1.1.jar" sourcepath="ext/src/commons-logging-1.1.1.jar" /> + <classpathentry kind="lib" path="ext/httpclient-4.3.6.jar" sourcepath="ext/src/httpclient-4.3.6.jar" /> + <classpathentry kind="lib" path="ext/httpcore-4.3.3.jar" sourcepath="ext/src/httpcore-4.3.3.jar" /> + <classpathentry kind="lib" path="ext/commons-logging-1.1.3.jar" sourcepath="ext/src/commons-logging-1.1.3.jar" /> <classpathentry kind="lib" path="ext/commons-codec-1.7.jar" sourcepath="ext/src/commons-codec-1.7.jar" /> - <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar" /> - <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.49.jar" sourcepath="ext/src/bcprov-jdk15on-1.49.jar" /> - <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.49.jar" sourcepath="ext/src/bcmail-jdk15on-1.49.jar" /> - <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.49.jar" sourcepath="ext/src/bcpkix-jdk15on-1.49.jar" /> - <classpathentry kind="lib" path="ext/sshd-core-0.12.0.jar" sourcepath="ext/src/sshd-core-0.12.0.jar" /> - <classpathentry kind="lib" path="ext/mina-core-2.0.7.jar" sourcepath="ext/src/mina-core-2.0.7.jar" /> + <classpathentry kind="lib" path="ext/org.eclipse.jdt.annotation-1.1.0.jar" /> + <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" /> + <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.52.jar" sourcepath="ext/src/bcprov-jdk15on-1.52.jar" /> + <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.52.jar" sourcepath="ext/src/bcmail-jdk15on-1.52.jar" /> + <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.52.jar" sourcepath="ext/src/bcpkix-jdk15on-1.52.jar" /> + <classpathentry kind="lib" path="ext/sshd-core-1.0.0.jar" sourcepath="ext/src/sshd-core-1.0.0.jar" /> + <classpathentry kind="lib" path="ext/mina-core-2.0.9.jar" sourcepath="ext/src/mina-core-2.0.9.jar" /> <classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/src/rome-0.9.jar" /> <classpathentry kind="lib" path="ext/jdom-1.0.jar" sourcepath="ext/src/jdom-1.0.jar" /> - <classpathentry kind="lib" path="ext/gson-1.7.2.jar" sourcepath="ext/src/gson-1.7.2.jar" /> - <classpathentry kind="lib" path="ext/groovy-all-1.8.8.jar" sourcepath="ext/src/groovy-all-1.8.8.jar" /> - <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0.jar" /> + <classpathentry kind="lib" path="ext/gson-2.3.1.jar" sourcepath="ext/src/gson-2.3.1.jar" /> + <classpathentry kind="lib" path="ext/groovy-all-2.4.4.jar" sourcepath="ext/src/groovy-all-2.4.4.jar" /> + <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.8.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.8.jar" /> <classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0.jar" /> <classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" /> <classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" /> @@ -66,16 +68,15 @@ <classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" /> <classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" /> <classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" /> - <classpathentry kind="lib" path="ext/freemarker-2.3.19.jar" sourcepath="ext/src/freemarker-2.3.19.jar" /> - <classpathentry kind="lib" path="ext/waffle-jna-1.5.jar" sourcepath="ext/src/waffle-jna-1.5.jar" /> - <classpathentry kind="lib" path="ext/platform-3.5.0.jar" sourcepath="ext/src/platform-3.5.0.jar" /> - <classpathentry kind="lib" path="ext/jna-3.5.0.jar" sourcepath="ext/src/jna-3.5.0.jar" /> - <classpathentry kind="lib" path="ext/guava-13.0.1.jar" sourcepath="ext/src/guava-13.0.1.jar" /> - <classpathentry kind="lib" path="ext/libpam4j-1.7.jar" sourcepath="ext/src/libpam4j-1.7.jar" /> - <classpathentry kind="lib" path="ext/args4j-2.0.26.jar" sourcepath="ext/src/args4j-2.0.26.jar" /> - <classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" /> + <classpathentry kind="lib" path="ext/freemarker-2.3.22.jar" sourcepath="ext/src/freemarker-2.3.22.jar" /> + <classpathentry kind="lib" path="ext/waffle-jna-1.7.3.jar" sourcepath="ext/src/waffle-jna-1.7.3.jar" /> + <classpathentry kind="lib" path="ext/jna-4.1.0.jar" sourcepath="ext/src/jna-4.1.0.jar" /> + <classpathentry kind="lib" path="ext/jna-platform-4.1.0.jar" sourcepath="ext/src/jna-platform-4.1.0.jar" /> + <classpathentry kind="lib" path="ext/libpam4j-1.8.jar" sourcepath="ext/src/libpam4j-1.8.jar" /> + <classpathentry kind="lib" path="ext/args4j-2.0.29.jar" sourcepath="ext/src/args4j-2.0.29.jar" /> + <classpathentry kind="lib" path="ext/jedis-2.6.2.jar" sourcepath="ext/src/jedis-2.6.2.jar" /> <classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" /> - <classpathentry kind="lib" path="ext/pf4j-0.8.0.jar" sourcepath="ext/src/pf4j-0.8.0.jar" /> + <classpathentry kind="lib" path="ext/pf4j-0.9.0.jar" sourcepath="ext/src/pf4j-0.9.0.jar" /> <classpathentry kind="lib" path="ext/tika-core-1.5.jar" sourcepath="ext/src/tika-core-1.5.jar" /> <classpathentry kind="lib" path="ext/jsoup-1.7.3.jar" sourcepath="ext/src/jsoup-1.7.3.jar" /> <classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" /> @@ -88,6 +89,9 @@ <classpathentry kind="lib" path="ext/json-20080701.jar" sourcepath="ext/src/json-20080701.jar" /> <classpathentry kind="lib" path="ext/selenium-api-2.28.0.jar" sourcepath="ext/src/selenium-api-2.28.0.jar" /> <classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" /> + <classpathentry kind="lib" path="ext/platform-3.4.0.jar" sourcepath="ext/src/platform-3.4.0.jar" /> + <classpathentry kind="lib" path="ext/mockito-core-1.10.19.jar" sourcepath="ext/src/mockito-core-1.10.19.jar" /> + <classpathentry kind="lib" path="ext/objenesis-2.1.jar" sourcepath="ext/src/objenesis-2.1.jar" /> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> <classpathentry kind="src" path="src/main/dagger"> <attributes> @@ -1,9 +1,11 @@ +tags /temp /lib /ext /build /site /git +/lucene /build.properties /federation.properties /mailtest.properties @@ -27,3 +29,4 @@ /**/.idea /**/init.lua /**/session +/nbproject/private @@ -358,3 +358,12 @@ pf4j Apache License 2.0 https://github.com/decebals/pf4j + +--------------------------------------------------------------------------- +google-guice +--------------------------------------------------------------------------- + google-guice, release under the + Apache License 2.0 + + https://code.google.com/p/google-guice +
\ No newline at end of file diff --git a/build.moxie b/build.moxie index 8dcb9752..e5f7c739 100644 --- a/build.moxie +++ b/build.moxie @@ -3,14 +3,14 @@ # # Specify minimum Moxie version required to build -requires: 0.9.3 +requires: 0.9.4 # Project Metadata name: Gitblit description: pure Java Git solution groupId: com.gitblit artifactId: gitblit -version: 1.6.3-SNAPSHOT +version: 1.7.0-SNAPSHOT inceptionYear: 2011 # Current stable release @@ -19,7 +19,7 @@ releaseDate: 2014-10-28 # Project urls url: 'http://gitblit.com' -issuesUrl: 'http://code.google.com/p/gitblit/issues/list' +issuesUrl: 'https://github.com/gitblit/gitblit' socialNetworkUrl: 'https://plus.google.com/114464678392593421684' forumUrl: 'http://groups.google.com/group/gitblit' mavenUrl: 'http://gitblit.github.io/gitblit-maven' @@ -58,7 +58,7 @@ mainclass: com.gitblit.GitBlitServer sourceDirectories: - compile 'src/main/java' - compile 'src/main/bugtraq' -- compile 'src/main/dagger' apt +- compile 'src/main/gen' apt - test 'src/test/java' - test 'src/test/bugtraq' # Moxie supports one site-scoped directory for mx:doc @@ -95,23 +95,27 @@ dependencyDirectory: ext registeredRepositories: - { id: eclipse, url: 'http://repo.eclipse.org/content/groups/releases' } - { id: eclipse-snapshots, url: 'http://repo.eclipse.org/content/groups/snapshots' } -- { id: atlassian-contrib, url: 'https://maven.atlassian.com/content/repositories/atlassian-3rdparty' } +- { id: gitblit, url: 'http://gitblit.github.io/gitblit-maven' } # Source all dependencies from the following repositories in the specified order -repositories: central, eclipse-snapshots, eclipse, atlassian-contrib +repositories: central, eclipse-snapshots, eclipse, gitblit # Convenience properties for dependencies properties: { - jetty.version : 9.2.9.v20150224 - wicket.version : 1.4.21 - lucene.version : 4.6.0 - jgit.version : 3.5.1.201410131835-r - groovy.version : 1.8.8 - bouncycastle.version : 1.49 + jetty.version : 9.2.13.v20150730 + slf4j.version : 1.7.12 + wicket.version : 1.4.22 + lucene.version : 4.10.4 + jgit.version : 4.1.1.201511131810-r + groovy.version : 2.4.4 + bouncycastle.version : 1.52 selenium.version : 2.28.0 wikitext.version : 1.4 - sshd.version: 0.12.0 - mina.version: 2.0.7 + sshd.version: 1.0.0 + mina.version: 2.0.9 + guice.version : 4.0 + # Gitblit maintains a fork of guice-servlet + guice-servlet.version : 4.0-gb2 } # Dependencies @@ -126,15 +130,14 @@ properties: { # dependencies: -# Dagger dependency injection library (annotation processor) -- compile 'com.squareup.dagger:dagger:1.1.0' :war apt -- compile 'com.squareup.dagger:dagger-compiler:1.1.0' :war optional apt -# Standard dependencies +- compile 'com.google.inject:guice:${guice.version}' :war :fedclient +- compile 'com.google.inject.extensions:guice-servlet:${guice-servlet.version}' :war +- compile 'com.google.guava:guava:18.0' :war :fedclient - compile 'com.intellij:annotations:12.0' :war -- compile 'log4j:log4j:1.2.17' :war :fedclient :authority -- compile 'org.slf4j:slf4j-api:1.7.5' :war :fedclient :authority -- compile 'org.slf4j:slf4j-log4j12:1.7.5' :war :fedclient :authority -- compile 'com.sun.mail:javax.mail:1.5.1' :war :authority +- compile 'log4j:log4j:1.2.17' :war :fedclient :manager +- compile 'org.slf4j:slf4j-api:${slf4j.version}' :war :fedclient :manager +- compile 'org.slf4j:slf4j-log4j12:${slf4j.version}' :war :fedclient :manager +- compile 'com.sun.mail:javax.mail:1.5.1' :war - compile 'javax.servlet:javax.servlet-api:3.1.0' :fedclient - compile 'org.eclipse.jetty.aggregate:jetty-all:${jetty.version}' @jar - compile 'org.apache.wicket:wicket:${wicket.version}' :war !org.mockito @@ -145,36 +148,36 @@ dependencies: - compile 'org.apache.lucene:lucene-highlighter:${lucene.version}' :war :fedclient - compile 'org.apache.lucene:lucene-memory:${lucene.version}' :war :fedclient - compile 'org.apache.lucene:lucene-queryparser:${lucene.version}' :war :fedclient -- compile 'org.pegdown:pegdown:1.4.2' :war +- compile 'org.pegdown:pegdown:1.5.0' :war - compile 'org.fusesource.wikitext:wikitext-core:${wikitext.version}' :war - compile 'org.fusesource.wikitext:twiki-core:${wikitext.version}' :war - compile 'org.fusesource.wikitext:textile-core:${wikitext.version}' :war - compile 'org.fusesource.wikitext:tracwiki-core:${wikitext.version}' :war - compile 'org.fusesource.wikitext:mediawiki-core:${wikitext.version}' :war - compile 'org.fusesource.wikitext:confluence-core:${wikitext.version}' :war -- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager :authority !junit -- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :manager :authority !junit -- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war :authority -- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war :authority -- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war :authority +- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager !junit +- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :manager !junit +- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war +- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war +- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war - compile 'org.apache.sshd:sshd-core:${sshd.version}' :war !org.easymock - compile 'org.apache.mina:mina-core:${mina.version}' :war !org.easymock - compile 'rome:rome:0.9' :war :manager :api -- compile 'com.google.code.gson:gson:1.7.2' :war :fedclient :manager :api +- compile 'com.google.code.gson:gson:2.3.1' :war :fedclient :manager :api - compile 'org.codehaus.groovy:groovy-all:${groovy.version}' :war -- compile 'com.unboundid:unboundid-ldapsdk:2.3.0' :war +- compile 'com.unboundid:unboundid-ldapsdk:2.3.8' :war - compile 'org.apache.ivy:ivy:2.2.0' :war - compile 'com.toedter:jcalendar:1.3.2' :authority - compile 'org.apache.commons:commons-compress:1.4.1' :war - compile 'commons-io:commons-io:2.2' :war - compile 'com.force.api:force-partner-api:24.0.0' :war -- compile 'org.freemarker:freemarker:2.3.19' :war -- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war -- compile 'org.kohsuke:libpam4j:1.7' :war -- compile 'args4j:args4j:2.0.26' :war :fedclient :authority +- compile 'org.freemarker:freemarker:2.3.22' :war +- compile 'com.github.dblock.waffle:waffle-jna:1.7.3' :war +- compile 'org.kohsuke:libpam4j:1.8' :war +- compile 'args4j:args4j:2.0.29' :war :fedclient - compile 'commons-codec:commons-codec:1.7' :war -- compile 'redis.clients:jedis:2.3.1' :war -- compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war +- compile 'redis.clients:jedis:2.6.2' :war +- compile 'ro.fortsoft.pf4j:pf4j:0.9.0' :war - compile 'org.apache.tika:tika-core:1.5' :war - compile 'org.jsoup:jsoup:1.7.3' :war - test 'junit' @@ -182,6 +185,7 @@ dependencies: - test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar - test 'org.seleniumhq.selenium:selenium-support:${selenium.version}' @jar - test 'org.seleniumhq.selenium:selenium-firefox-driver:${selenium.version}' +- test 'org.mockito:mockito-core:1.10.19' # Dependencies with the "build" scope are retrieved # and injected into the Ant runtime classpath - build 'jacoco' @@ -8,7 +8,7 @@ documentation @ http://gitblit.github.io/moxie
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
- <property name="moxie.version" value="0.9.3" />
+ <property name="moxie.version" value="0.9.4" />
<property name="moxie.url" value="http://gitblit.github.io/moxie/maven" />
<property name="moxie.jar" value="moxie-toolkit-${moxie.version}.jar" />
<property name="moxie.dir" value="${user.home}/.moxie" />
@@ -41,9 +41,10 @@ <mx:init verbose="no" mxroot="${moxie.dir}" />
<!-- Set Ant project properties -->
- <property name="distribution.zipfile" value="gitblit-${project.version}.zip" />
- <property name="distribution.tgzfile" value="gitblit-${project.version}.tar.gz" />
- <property name="distribution.warfile" value="gitblit-${project.version}.war" />
+ <property name="release.name" value="gitblit-${project.version}"/>
+ <property name="distribution.zipfile" value="${release.name}.zip" />
+ <property name="distribution.tgzfile" value="${release.name}.tar.gz" />
+ <property name="distribution.warfile" value="${release.name}.war" />
<property name="fedclient.zipfile" value="fedclient-${project.version}.zip" />
<property name="manager.zipfile" value="manager-${project.version}.zip" />
<property name="authority.zipfile" value="authority-${project.version}.zip" />
@@ -81,10 +82,9 @@ <fileset dir="${project.distrib.dir}/data" />
</copy>
- <!-- copy gitblit.properties to the source directory.
- this file is only used for parsing setting descriptions. -->
- <copy tofile="${project.src.dir}/reference.properties" overwrite="true"
- file="${project.distrib.dir}/data/gitblit.properties" />
+ <!-- copy defaults.properties to the source directory -->
+ <copy tofile="${project.src.dir}/defaults.properties" overwrite="true"
+ file="${project.distrib.dir}/data/defaults.properties" />
<!-- copy clientapps.json to the source directory.
this file is only used if a local file is not provided. -->
@@ -101,8 +101,8 @@ -->
<target name="compile" depends="setup" description="compiles Gitblit from source">
- <!-- Generate the Keys class from the properties file -->
- <mx:keys propertiesfile="${project.distrib.dir}/data/gitblit.properties"
+ <!-- Generate the Keys class from the defaults.properties file -->
+ <mx:keys propertiesfile="${project.distrib.dir}/data/defaults.properties"
outputclass="com.gitblit.Keys"
todir="${project.src.dir}" />
@@ -170,14 +170,17 @@ <echo>Building Gitblit GO ${project.version}</echo>
- <local name="go.dir" />
- <property name="go.dir" value="${project.outputDirectory}/go" />
+ <local name="go.dir"/>
+ <property name="go.dir" value="${project.outputDirectory}/go"/>
<delete dir="${go.dir}" />
+
+ <local name="go.release.dir" />
+ <property name="go.release.dir" value="${go.dir}/${release.name}" />
<local name="webinf" />
<property name="webinf" value="${project.compileOutputDirectory}/WEB-INF" />
- <prepareDataDirectory toDir="${go.dir}/data" />
+ <prepareDataDirectory toDir="${go.release.dir}/data" />
<!-- Copy the web.xml from the prototype web.xml -->
<copy todir="${webinf}" overwrite="true">
@@ -188,42 +191,43 @@ </copy>
<!-- Build jar -->
- <mx:jar destfile="${go.dir}/gitblit.jar" includeresources="true">
+ <mx:jar destfile="${go.release.dir}/gitblit.jar" includeresources="true">
<mainclass name="com.gitblit.GitBlitServer" />
<launcher paths="ext" />
</mx:jar>
<!-- Generate the docs for the GO build -->
- <generateDocs toDir="${go.dir}/docs" />
-
+ <generateDocs toDir="${go.release.dir}/docs" />
+
<!-- Create GO Windows Zip deployment -->
<mx:zip basedir="${go.dir}">
<!-- LICENSE and NOTICE -->
- <fileset dir="${basedir}" >
+ <zipfileset dir="${basedir}" prefix="${release.name}">
<include name="LICENSE" />
<include name="NOTICE" />
- </fileset>
+ </zipfileset>
<!-- Windows distrib files -->
- <zipfileset dir="${project.distrib.dir}/win" />
+ <zipfileset dir="${project.distrib.dir}/win" prefix="${release.name}"/>
<!-- Gitblit Authority data -->
- <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+ <zipfileset dir="${project.distrib.dir}/data/certs" prefix="${release.name}/data/certs" />
+
<!-- include all dependencies -->
- <dependencies prefix="ext" />
+ <dependencies prefix="${release.name}/ext" />
</mx:zip>
<!-- Create GO Linux/OSX tar.gz deployment -->
<mx:tar basedir="${go.dir}" longfile="gnu" compression="gzip">
<!-- LICENSE and NOTICE -->
- <fileset dir="${basedir}" >
+ <zipfileset dir="${basedir}" prefix="${release.name}">
<include name="LICENSE" />
<include name="NOTICE" />
- </fileset>
+ </zipfileset>
<!-- Linux/OSX distrib files -->
- <tarfileset dir="${project.distrib.dir}/linux" filemode="755" />
+ <tarfileset dir="${project.distrib.dir}/linux" filemode="755" prefix="${release.name}"/>
<!-- Gitblit Authority data -->
- <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+ <zipfileset dir="${project.distrib.dir}/data/certs" prefix="${release.name}/data/certs" />
<!-- include all dependencies -->
- <dependencies prefix="ext" />
+ <dependencies prefix="${release.name}/ext" />
</mx:tar>
</target>
@@ -257,7 +261,7 @@ </mx:webxml>
<!-- Gitblit jar -->
- <mx:jar destfile="${webinf}/lib/gitblit.jar" includeresources="false" />
+ <mx:jar destfile="${webinf}/lib/gitblit-${project.version}.jar" includeresources="false" />
<!-- Build the WAR file -->
<mx:zip basedir="${war.dir}" destfile="${project.targetDirectory}/${distribution.warfile}" compress="true" >
@@ -290,7 +294,7 @@ classes, exclude any classes in classpath jars -->
<mx:genjar tag="" includeresources="false" excludeClasspathJars="true"
destfile="${project.targetDirectory}/fedclient.jar"
- excludes="**/.class,**/*.java, **/Thumbs.db, **/*.mkd, com/gitblit/wicket/**">
+ excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
<mainclass name="com.gitblit.FederationClient" />
<class name="com.gitblit.Keys" />
<launcher paths="ext" />
@@ -330,7 +334,8 @@ <!-- generate jar by traversing the class hierarchy of the specified
classes, exclude any classes in classpath jars -->
<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
- destfile="${project.targetDirectory}/manager.jar">
+ destfile="${project.targetDirectory}/manager.jar"
+ excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
<resource file="${project.src.dir}/com/gitblit/client/splash.png" />
<resource file="${project.resources.dir}/gitblt-favicon.png" />
<resource file="${project.resources.dir}/gitweb-favicon.png" />
@@ -406,9 +411,10 @@ <!-- Build API Library jar -->
<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
- destfile="${project.targetDirectory}/gbapi-${project.version}.jar">
+ destfile="${project.targetDirectory}/gbapi-${project.version}.jar"
+ excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
+ <mainclass name="com.gitblit.client.GitblitClient" />
<class name="com.gitblit.Keys" />
- <class name="com.gitblit.client.GitblitClient" />
<class name="com.gitblit.models.FederationModel" />
<class name="com.gitblit.models.FederationProposal" />
<class name="com.gitblit.models.FederationSet" />
@@ -507,6 +513,7 @@ <page name="bugtraq" src="setup_bugtraq.mkd" />
<page name="mirrors" src="setup_mirrors.mkd" />
<page name="scaling" src="setup_scaling.mkd" />
+ <page name="fail2ban" src="setup_fail2ban.mkd" />
<divider />
<page name="Gitblit as a viewer" src="setup_viewer.mkd" />
</menu>
@@ -546,8 +553,6 @@ <page name="release history" out="releases.html">
<template src="releasehistory.ftl" data="${releaselog}" />
</page>
- <divider />
- <page name="roadmap" src="roadmap.mkd" />
</menu>
<menu name="downloads">
@@ -578,7 +583,6 @@ <link name="Github" src="${project.scmUrl}" />
<link name="Issues" src="${project.issuesUrl}" />
<link name="Discussion" src="${project.forumUrl}" />
- <link name="Google+" src="${project.socialNetworkUrl}" />
<link name="Twitter" src="https://twitter.com/gitblit" />
<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
<divider />
@@ -586,7 +590,6 @@ <link name="Gitblit SSH and Plugin Management asciicast" src="https://asciinema.org/a/9342" />
<link name="GitMinutes #29: James Moger on Gitblit" src="http://episodes.gitminutes.com/2014/05/gitminutes-29-james-moger-on-gitblit.html" />
<divider />
- <link name="+JamesMoger" src="https://plus.google.com/+JamesMoger" />
<link name="@JamesMoger" src="https://twitter.com/JamesMoger" />
</menu>
<divider />
@@ -594,7 +597,7 @@ <replace token="%GCURL%" value="${gc.url}" />
- <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+ <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" />
<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>" />
<regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/pull/$3'>pull request #$3</a>" />
@@ -890,12 +893,11 @@ <link name="Github" src="${project.scmUrl}" />
<link name="Issues" src="${project.issuesUrl}" />
<link name="Discussion" src="${project.forumUrl}" />
- <link name="Google+" src="${project.socialNetworkUrl}" />
<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
</menu>
</structure>
- <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+ <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" />
<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>" />
<regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="<a href='https://github.com/gitblit/gitblit/pull/$3'>pull request #$3</a>" />
@@ -935,6 +937,7 @@ <fileset dir="${project.distrib.dir}/data">
<include name="users.conf" />
<include name="projects.conf" />
+ <include name="defaults.properties" />
<include name="gitblit.properties" />
</fileset>
</copy>
diff --git a/gitblit.iml b/gitblit.iml index d6f84df7..93331b20 100644 --- a/gitblit.iml +++ b/gitblit.iml @@ -7,20 +7,20 @@ <content url="file://$MODULE_DIR$"> <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/main/bugtraq" isTestSource="false" /> - <sourceFolder url="file://$MODULE_DIR$/src/main/dagger" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/gen" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/test/bugtraq" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" /> </content> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module-library"> - <library name="dagger-1.1.0.jar"> + <library name="guice-4.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/dagger-1.1.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/guice-4.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/dagger-1.1.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/guice-4.0.jar!/" /> </SOURCES> </library> </orderEntry> @@ -36,24 +36,35 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="dagger-compiler-1.1.0.jar"> + <library name="aopalliance-1.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/dagger-compiler-1.1.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/aopalliance-1.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/dagger-compiler-1.1.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/aopalliance-1.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="javawriter-2.1.1.jar"> + <library name="guava-18.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/javawriter-2.1.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/guava-18.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/javawriter-2.1.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/guava-18.0.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library name="guice-servlet-4.0-gb2.jar"> + <CLASSES> + <root url="jar://$MODULE_DIR$/ext/guice-servlet-4.0-gb2.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/ext/src/guice-servlet-4.0-gb2.jar!/" /> </SOURCES> </library> </orderEntry> @@ -80,24 +91,24 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="slf4j-api-1.7.5.jar"> + <library name="slf4j-api-1.7.12.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/slf4j-api-1.7.5.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/slf4j-api-1.7.12.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.7.5.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.7.12.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="slf4j-log4j12-1.7.5.jar"> + <library name="slf4j-log4j12-1.7.12.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/slf4j-log4j12-1.7.5.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/slf4j-log4j12-1.7.12.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.7.5.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.7.12.jar!/" /> </SOURCES> </library> </orderEntry> @@ -124,123 +135,123 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="jetty-all-9.2.3.v20140905.jar"> + <library name="jetty-all-9.2.13.v20150730.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/jetty-all-9.2.3.v20140905.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/jetty-all-9.2.13.v20150730.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/jetty-all-9.2.3.v20140905.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/jetty-all-9.2.13.v20150730.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="wicket-1.4.21.jar"> + <library name="wicket-1.4.22.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/wicket-1.4.21.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/wicket-1.4.22.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.22.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="wicket-auth-roles-1.4.21.jar"> + <library name="wicket-auth-roles-1.4.22.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/wicket-auth-roles-1.4.21.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/wicket-auth-roles-1.4.22.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.22.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="wicket-extensions-1.4.21.jar"> + <library name="wicket-extensions-1.4.22.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/wicket-extensions-1.4.21.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/wicket-extensions-1.4.22.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.22.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-core-4.6.0.jar"> + <library name="lucene-core-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-core-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-core-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-core-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-core-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-analyzers-common-4.6.0.jar"> + <library name="lucene-analyzers-common-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-analyzers-common-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-analyzers-common-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-analyzers-common-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-analyzers-common-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-highlighter-4.6.0.jar"> + <library name="lucene-highlighter-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-highlighter-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-highlighter-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-memory-4.6.0.jar"> + <library name="lucene-memory-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-memory-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-memory-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-queries-4.6.0.jar"> + <library name="lucene-queries-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-queries-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-queries-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-queryparser-4.6.0.jar"> + <library name="lucene-queryparser-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-queryparser-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-queryparser-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-queryparser-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-queryparser-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="lucene-sandbox-4.6.0.jar"> + <library name="lucene-sandbox-4.10.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/lucene-sandbox-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/lucene-sandbox-4.10.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/lucene-sandbox-4.6.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/lucene-sandbox-4.10.4.jar!/" /> </SOURCES> </library> </orderEntry> @@ -254,79 +265,79 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="pegdown-1.4.2.jar"> + <library name="pegdown-1.5.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/pegdown-1.4.2.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/pegdown-1.5.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/pegdown-1.4.2.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/pegdown-1.5.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="parboiled-java-1.1.6.jar"> + <library name="parboiled-java-1.1.7.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/parboiled-java-1.1.6.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/parboiled-java-1.1.7.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/parboiled-java-1.1.6.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/parboiled-java-1.1.7.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="parboiled-core-1.1.6.jar"> + <library name="parboiled-core-1.1.7.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/parboiled-core-1.1.6.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/parboiled-core-1.1.7.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/parboiled-core-1.1.6.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/parboiled-core-1.1.7.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="asm-4.1.jar"> + <library name="asm-5.0.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/asm-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/asm-5.0.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/asm-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/asm-5.0.3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="asm-tree-4.1.jar"> + <library name="asm-tree-5.0.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/asm-tree-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/asm-tree-5.0.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/asm-tree-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/asm-tree-5.0.3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="asm-analysis-4.1.jar"> + <library name="asm-analysis-5.0.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/asm-analysis-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/asm-analysis-5.0.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/asm-analysis-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/asm-analysis-5.0.3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="asm-util-4.1.jar"> + <library name="asm-util-5.0.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/asm-util-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/asm-util-5.0.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/asm-util-4.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/asm-util-5.0.3.jar!/" /> </SOURCES> </library> </orderEntry> @@ -397,24 +408,24 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="org.eclipse.jgit-3.5.1.201410131835-r.jar"> + <library name="org.eclipse.jgit-4.1.1.201511131810-r.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-3.5.1.201410131835-r.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-4.1.1.201511131810-r.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-3.5.1.201410131835-r.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-4.1.1.201511131810-r.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="jsch-0.1.50.jar"> + <library name="jsch-0.1.53.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/jsch-0.1.50.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/jsch-0.1.53.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.50.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.53.jar!/" /> </SOURCES> </library> </orderEntry> @@ -430,35 +441,35 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="httpclient-4.1.3.jar"> + <library name="httpclient-4.3.6.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/httpclient-4.1.3.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/httpclient-4.3.6.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.1.3.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.3.6.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="httpcore-4.1.4.jar"> + <library name="httpcore-4.3.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/httpcore-4.1.4.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/httpcore-4.3.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.1.4.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.3.3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="commons-logging-1.1.1.jar"> + <library name="commons-logging-1.1.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.3.jar!/" /> </SOURCES> </library> </orderEntry> @@ -474,68 +485,77 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar"> + <library name="org.eclipse.jdt.annotation-1.1.0.jar"> + <CLASSES> + <root url="jar://$MODULE_DIR$/ext/org.eclipse.jdt.annotation-1.1.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library name="org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="bcprov-jdk15on-1.49.jar"> + <library name="bcprov-jdk15on-1.52.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.49.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.52.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.49.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.52.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="bcmail-jdk15on-1.49.jar"> + <library name="bcmail-jdk15on-1.52.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.49.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.52.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.49.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.52.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="bcpkix-jdk15on-1.49.jar"> + <library name="bcpkix-jdk15on-1.52.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.49.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.52.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.49.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.52.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="sshd-core-0.12.0.jar"> + <library name="sshd-core-1.0.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/sshd-core-0.12.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/sshd-core-1.0.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/sshd-core-0.12.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/sshd-core-1.0.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="mina-core-2.0.7.jar"> + <library name="mina-core-2.0.9.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/mina-core-2.0.7.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/mina-core-2.0.9.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/mina-core-2.0.7.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/mina-core-2.0.9.jar!/" /> </SOURCES> </library> </orderEntry> @@ -562,35 +582,35 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="gson-1.7.2.jar"> + <library name="gson-2.3.1.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/gson-1.7.2.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/gson-2.3.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/gson-2.3.1.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="groovy-all-1.8.8.jar"> + <library name="groovy-all-2.4.4.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/groovy-all-1.8.8.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/groovy-all-2.4.4.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/groovy-all-2.4.4.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="unboundid-ldapsdk-2.3.0.jar"> + <library name="unboundid-ldapsdk-2.3.8.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/unboundid-ldapsdk-2.3.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/unboundid-ldapsdk-2.3.8.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.8.jar!/" /> </SOURCES> </library> </orderEntry> @@ -681,90 +701,79 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="freemarker-2.3.19.jar"> - <CLASSES> - <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.19.jar!/" /> - </CLASSES> - <JAVADOC /> - <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.19.jar!/" /> - </SOURCES> - </library> - </orderEntry> - <orderEntry type="module-library"> - <library name="waffle-jna-1.5.jar"> + <library name="freemarker-2.3.22.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.5.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.22.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.5.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.22.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="platform-3.5.0.jar"> + <library name="waffle-jna-1.7.3.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/platform-3.5.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.7.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/platform-3.5.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.7.3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="jna-3.5.0.jar"> + <library name="jna-4.1.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/jna-3.5.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/jna-4.1.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/jna-3.5.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/jna-4.1.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="guava-13.0.1.jar"> + <library name="jna-platform-4.1.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/guava-13.0.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/jna-platform-4.1.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/guava-13.0.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/jna-platform-4.1.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="libpam4j-1.7.jar"> + <library name="libpam4j-1.8.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/libpam4j-1.7.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/libpam4j-1.8.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.7.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.8.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="args4j-2.0.26.jar"> + <library name="args4j-2.0.29.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/args4j-2.0.26.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/args4j-2.0.29.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/args4j-2.0.26.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/args4j-2.0.29.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> - <library name="jedis-2.3.1.jar"> + <library name="jedis-2.6.2.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/jedis-2.3.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/jedis-2.6.2.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/jedis-2.3.1.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/jedis-2.6.2.jar!/" /> </SOURCES> </library> </orderEntry> @@ -780,13 +789,13 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="pf4j-0.8.0.jar"> + <library name="pf4j-0.9.0.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/pf4j-0.8.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/pf4j-0.9.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.8.0.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.9.0.jar!/" /> </SOURCES> </library> </orderEntry> @@ -922,6 +931,39 @@ </SOURCES> </library> </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="platform-3.4.0.jar"> + <CLASSES> + <root url="jar://$MODULE_DIR$/ext/platform-3.4.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/ext/src/platform-3.4.0.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="mockito-core-1.10.19.jar"> + <CLASSES> + <root url="jar://$MODULE_DIR$/ext/mockito-core-1.10.19.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/ext/src/mockito-core-1.10.19.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="objenesis-2.1.jar"> + <CLASSES> + <root url="jar://$MODULE_DIR$/ext/objenesis-2.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/ext/src/objenesis-2.1.jar!/" /> + </SOURCES> + </library> + </orderEntry> <orderEntry type="inheritedJdk" /> </component> </module> diff --git a/releases.moxie b/releases.moxie index 22413846..7a3927d7 100644 --- a/releases.moxie +++ b/releases.moxie @@ -9,11 +9,104 @@ r27: { html: ~ text: ~ security: ~ - fixes: ~ - changes: ~ - additions: ~ - dependencyChanges: ~ - contributors: ~ + fixes: + - Fix exception when viewing a ticket with a patchset where the integration branch does not exist (issue-521, ticket-212) + - Fix exception when deleting a repository using the FileTicketService (issue-522, ticket-213) + - Do not inject team repository permissions as explicit user permissions when editing a user (issue-462, ticket-214) + - Whitelist the target link attribute in the XSS filter (ticket-216) + - Strip line breaks from pasted SSH keys (ticket-245) + - Fix project sorting (pr-287) + - Fix Lucene indexing of tags (pr-291) + - Prevent session fixation for external authentication (pr-908) + - Encode email subject as UTF-8 (pr-929) + - Do not automatically trim passwords (pr-932) + - Fix nested repository detection in raw servlet (pr-950) + changes: + - Replaced Dagger with Guice (ticket-80) + - Use release name as root directory in Gitblit GO artifacts (ticket-109) + - Split gitblit.properties into gitblit.properties & defaults.properties (ticket-110) + - Show team type in teams page (pr-217, ticket-168) + - Relocate the repository Delete button (ticket-225) + - Improve diff performance by gracefully limiting large diffs (pr-226) + - Add granular settings to disable display of git transport urls (pr-274) + - Use author date to be consistent with other tools (pr-919) + additions: + - Add GitHub Octicons (ticket-106) + - Support for chain-loading properties files (ticket-110) + - Add Priority & Severity fields for tickets (pr-220, ticket-157) + - Add Maintenance ticket type (pr-223, ticket-206) + - Add commitdiff option to ignore whitespace (ticket-233) + - Add configurable tab length for blob views (ticket-253) + - Implement image diffs (pr-229) + - Add support for configurable HTTP proxy host/port in PluginManager (pr-235) + - Implement collapsed empty folder navigation (pr-241) + - Implement hashing to detect usermodel changes and reduce users.conf file I/O (pr-246) + - Add support for Kerberos5/GSS authentication to SSH (pr-254) + - Allow extraction of additional user metadata in request headers when using external or container authentication (pr-255) + - Allow custom host & port specification for advertised SSH urls (pr-268) + - Improve logging for fail2ban usage (pr-296) + - Initial implementation of Git-LFS (pr-921) + - Add "all" repositories parameter to Search page (pr-935) + dependencyChanges: + - Guice 4.0 (ticket-80, ticket-219) + - SLF4j 1.7.12 + - gson 2.3.1 + - Freemarker 2.3.22 + - Lucene 4.10.0 (ticket-159) + - SSHD 1.0.0 + - JGit 4.1.1 + - Groovy 2.4.4 + - Wicket 1.4.22 + - BouncyCastle 1.52 + - Pegdown 1.5.0 + - Jetty 9.2.13 + settings: + - { name: web.displayUserPanel, defaultValue: 'true' } + - { name: web.tabLength, defaultValue: 4 } + - { name: web.avatarClass, defaultValue: '' } + - { name: web.showHttpServletUrls, defaultValue: 'true' } + - { name: web.showGitDaemonUrls, defaultValue: 'true' } + - { name: web.showSshDaemonUrls, defaultValue: 'true' } + - { name: web.advertiseAccessPermissionForOtherUrls, defaultValue: 'false' } + - { name: web.maxDiffLinesPerFile, defaultValue: '4000' } + - { name: web.maxDiffLines, defaultValue: '20000' } + - { name: ssh.advertisedHost, defaultValue: '' } + - { name: ssh.advertisedPort, defaultValue: '' } + - { name: git.sshWithKrb5, defaultValue: '' } + - { name: git.sshKrb5Keytab, defaultValue: '' } + - { name: git.sshKrb5ServicePrincipalName, defaultValue: '' } + - { name: git.sshKrb5StripDomain, defaultValue: 'true' } + - { name: filestore.storageFolder, defaultValue: '${baseFolder}/lfs' } + - { name: filestore.maxUploadSize, defaultValue: '-1' } + - { name: plugins.httpProxyHost, defaultValue: '' } + - { name: plugins.httpProxyPort, defaultValue: '' } + - { name: plugins.httpProxyAuthorization, defaultValue: '' } + - { name: realm.container.autoAccounts.displayName, defaultValue: '' } + - { name: realm.container.autoAccounts.emailAddress, defaultValue: '' } + - { name: realm.container.autoAccounts.locale, defaultValue: '' } + - { name: realm.container.autoAccounts.adminRole, defaultValue: '' } + + contributors: + - James Moger + - David Ostrovsky + - Alex Lewis + - Florian Zschocke + - Paul Martin + - razzard + - Alexander Zabluda + - Marcin Cieślak + - Rainer W + - Vitaliy Filippov + - willyann + - enrico204 + - mrjoel + - Fabrice Bacchella + - Milos Cubrilo + - Thomas Wolf + - Morten Bøgeskov + - Steven Oliver + - Dariusz Bywalec + - Jan Šmucr } # diff --git a/src/main/.gitignore b/src/main/.gitignore index 01c48ab2..0d046882 100644 --- a/src/main/.gitignore +++ b/src/main/.gitignore @@ -1 +1,2 @@ /dagger +/gen diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java index 7776e6fe..60b4ecc0 100644 --- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java +++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java @@ -214,7 +214,7 @@ public final class BugtraqConfig { } finally { rw.dispose(); - tw.release(); + tw.close(); } if (content == null) { diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties new file mode 100644 index 00000000..ce6267a5 --- /dev/null +++ b/src/main/distrib/data/defaults.properties @@ -0,0 +1,2046 @@ +# +# DEFAULTS.PROPERTIES +# +# The default Gitblit settings. +# + +# This settings file supports parameterization from the command-line for the +# following command-line parameters: +# +# --baseFolder ${baseFolder} SINCE 1.2.1 +# +# Settings that support ${baseFolder} parameter substitution are indicated with the +# BASEFOLDER attribute. If the --baseFolder argument is unspecified, ${baseFolder} +# and it's trailing / will be discarded from the setting value leaving a relative +# path that is equivalent to pre-1.2.1 releases. +# +# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified +# +# Git Servlet Settings +# + +# Base folder for repositories. +# This folder may contain bare and non-bare repositories but Gitblit will only +# allow you to push to bare repositories. +# Use forward slashes even on Windows!! +# e.g. c:/gitrepos +# +# SINCE 0.5.0 +# RESTART REQUIRED +# BASEFOLDER +git.repositoriesFolder = ${baseFolder}/git + +# Build the available repository list at startup and cache this list for reuse. +# This reduces disk io when presenting the repositories page, responding to rpcs, +# etc, but it means that Gitblit will not automatically identify repositories +# added or deleted by external tools. +# +# For this case you can use curl, wget, etc to issue an rpc request to clear the +# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE) +# +# SINCE 1.1.0 +git.cacheRepositoryList = true + +# Search the repositories folder subfolders for other repositories. +# Repositories MAY NOT be nested (i.e. one repository within another) +# but they may be grouped together in subfolders. +# e.g. c:/gitrepos/libraries/mylibrary.git +# c:/gitrepos/libraries/myotherlibrary.git +# +# SINCE 0.5.0 +git.searchRepositoriesSubfolders = true + +# Maximum number of folders to recurse into when searching for repositories. +# The default value, -1, disables depth limits. +# +# SINCE 1.1.0 +git.searchRecursionDepth = -1 + +# List of regex exclusion patterns to match against folders found in +# *git.repositoriesFolder*. +# Use forward slashes even on Windows!! +# e.g. test/jgit\.git +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.1.0 +git.searchExclusions = + +# List of regex url patterns for extracting a repository name when locating +# submodules. +# e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract +# *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git* +# If no matches are found then the submodule repository name is assumed to be +# whatever trails the last / character. (e.g. gitblit.git). +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.1.0 +git.submoduleUrlPatterns = .*?://github.com/(.*) + +# Specify the interface for Git Daemon to bind it's service. +# You may specify an ip or an empty value to bind to all interfaces. +# Specifying localhost will result in Gitblit ONLY listening to requests to +# localhost. +# +# SINCE 1.3.0 +# RESTART REQUIRED +git.daemonBindInterface = + +# port for serving the Git Daemon service. <= 0 disables this service. +# On Unix/Linux systems, ports < 1024 require root permissions. +# Recommended value: 9418 +# +# SINCE 1.3.0 +# RESTART REQUIRED +git.daemonPort = 9418 + +# The port for serving the SSH service. <= 0 disables this service. +# On Unix/Linux systems, ports < 1024 require root permissions. +# Recommended value: 29418 +# +# SINCE 1.5.0 +# RESTART REQUIRED +git.sshPort = 29418 + +# Specify the interface for the SSH daemon to bind its service. +# You may specify an ip or an empty value to bind to all interfaces. +# Specifying localhost will result in Gitblit ONLY listening to requests to +# localhost. +# +# SINCE 1.5.0 +# RESTART REQUIRED +git.sshBindInterface = + +# Manually specify the hostname to use in advertised SSH repository urls. +# This may be useful in complex forwarding setups. +# +# SINCE 1.7.0 +git.sshAdvertisedHost = + +# Manually specify the port to use in advertised SSH repository urls. +# This may be useful in complex forwarding setups. +# +# SINCE 1.7.0 +git.sshAdvertisedPort = + +# Specify the SSH key manager to use for retrieving, storing, and removing +# SSH keys. +# +# Valid key managers are: +# com.gitblit.transport.ssh.FileKeyManager +# +# SINCE 1.5.0 +git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager + +# Directory for storing user SSH keys when using the FileKeyManager. +# +# SINCE 1.5.0 +git.sshKeysFolder= ${baseFolder}/ssh + +# Use Kerberos5 (GSS) authentication +# +# SINCE 1.7.0 +git.sshWithKrb5 = false + +# The path to a Kerberos 5 keytab. +# +# SINCE 1.7.0 +git.sshKrb5Keytab = + +# The service principal name to be used for Kerberos5. +# The default is host/hostname. +# +# SINCE 1.7.0 +git.sshKrb5ServicePrincipalName = + +# Strip the domain suffix from a kerberos username. +# e.g. james@bigbox would be "james" +# +# SINCE 1.7.0 +git.sshKrb5StripDomain = true + +# SSH backend NIO2|MINA. +# +# The Apache Mina project recommends using the NIO2 backend. +# +# SINCE 1.5.0 +git.sshBackend = NIO2 + +# Number of threads used to parse a command line submitted by a client over SSH +# for execution, create the internal data structures used by that command, +# and schedule it for execution on another thread. +# +# SINCE 1.5.0 +git.sshCommandStartThreads = 2 + + +# Allow push/pull over http/https with JGit servlet. +# If you do NOT want to allow Git clients to clone/push to Gitblit set this +# to false. You might want to do this if you are only using ssh:// or git://. +# If you set this false, consider changing the *web.otherUrls* setting to +# indicate your clone/push urls. +# +# SINCE 0.5.0 +git.enableGitServlet = true + +# If you want to restrict all git servlet access to those with valid X509 client +# certificates then set this value to true. +# +# SINCE 1.2.0 +git.requiresClientCertificate = false + +# Enforce date checks on client certificates to ensure that they are not being +# used prematurely and that they have not expired. +# +# SINCE 1.2.0 +git.enforceCertificateValidity = true + +# List of OIDs to extract from a client certificate DN to map a certificate to +# an account username. +# +# e.g. git.certificateUsernameOIDs = CN +# e.g. git.certificateUsernameOIDs = FirstName LastName +# +# SPACE-DELIMITED +# SINCE 1.2.0 +git.certificateUsernameOIDs = CN + +# Only serve/display bare repositories. +# If there are non-bare repositories in git.repositoriesFolder and this setting +# is true, they will be excluded from the ui. +# +# SINCE 0.9.0 +git.onlyAccessBareRepositories = false + + +# Specify the list of acceptable transports for pushes. +# If this setting is empty, all transports are acceptable. +# +# Valid choices are: GIT HTTP HTTPS SSH +# +# SINCE 1.5.0 +# SPACE-DELIMITED +git.acceptedPushTransports = HTTP HTTPS SSH + +# Allow an authenticated user to create a destination repository on a push if +# the repository does not already exist. +# +# Administrator accounts can create a repository in any project. +# These repositories are created with the default access restriction and authorization +# control values. The pushing account is set as the owner. +# +# Non-administrator accounts with the CREATE role may create personal repositories. +# These repositories are created as VIEW restricted for NAMED users. +# The pushing account is set as the owner. +# +# SINCE 1.2.0 +git.allowCreateOnPush = true + +# Global setting to control anonymous pushes. +# +# This setting allows/rejects anonymous pushes at the level of the receive pack. +# This trumps all repository config settings. While anonymous pushes are convenient +# on your own box when you are a lone developer, they are not recommended for +# any multi-user installation where accountability is required. Since Gitblit +# tracks pushes and user accounts, allowing anonymous pushes compromises that +# information. +# +# SINCE 1.4.0 +git.allowAnonymousPushes = false + +# The default access restriction for new repositories. +# Valid values are NONE, PUSH, CLONE, VIEW +# NONE = anonymous view, clone, & push +# PUSH = anonymous view & clone and authenticated push +# CLONE = anonymous view, authenticated clone & push +# VIEW = authenticated view, clone, & push +# +# SINCE 1.0.0 +git.defaultAccessRestriction = PUSH + +# The default authorization control for new repositories. +# Valid values are AUTHENTICATED and NAMED +# AUTHENTICATED = any authenticated user is granted restricted access +# NAMED = only named users/teams are granted restricted access +# +# SINCE 1.1.0 +git.defaultAuthorizationControl = NAMED + +# The prefix for a users personal repository directory. +# +# Personal user repositories are created in this directory, named by the user name +# prefixed with the userRepositoryPrefix. For eaxmple, a user 'john' would have his +# personal repositories in the directory '~john'. +# +# Cannot be an empty string. Also, absolute paths are changed to relative paths by +# removing the first directory separator. +# +# It is not recommended to change this value AFTER your user's have created +# personal repositories because it will break all permissions, ownership, and +# repository push/pull operations. +# +# RESTART REQUIRED +# SINCE 1.4.0 +git.userRepositoryPrefix = ~ + +# The default incremental push tag prefix. Tag prefix applied to a repository +# that has automatic push tags enabled and does not specify a custom tag prefix. +# +# If incremental push tags are enabled, the tips of each branch in the push will +# be tagged with an increasing revision integer. +# +# e.g. refs/tags/r2345 or refs/tags/rev_2345 +# +# SINCE 1.3.0 +git.defaultIncrementalPushTagPrefix = r + +# Controls creating a repository as --shared on Unix servers. +# +# In an Unix environment where mixed access methods exist for shared repositories, +# the repository should be created with 'git init --shared' to make sure that +# it can be accessed e.g. via ssh (user git) and http (user www-data). +# +# Valid values are the values available for the '--shared' option. The the manual +# page for 'git init' for more information on shared repositories. +# +# SINCE 1.4.0 +git.createRepositoriesShared = false + +# Directory for gitignore templates used during repository creation. +# +# SINCE 1.6.0 +git.gitignoreFolder = ${baseFolder}/gitignore + +# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!) +# +# USE AT YOUR OWN RISK! +# +# If enabled, the garbage collection executor scans all repositories once a day +# at the hour of your choosing. The GC executor will take each repository "offline", +# one-at-a-time, to check if the repository satisfies it's GC trigger requirements. +# +# While the repository is offline it will be inaccessible from the web UI or from +# any of the other services (git, rpc, rss, etc). +# +# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block, +# especially on Windows systems, so if you are using other tools please coordinate +# their usage with your GC Executor schedule or do not use this feature. +# +# The GC algorithm complex and the JGit team advises caution when using their +# young implementation of GC. +# +# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics +# +# EXPERIMENTAL +# SINCE 1.2.0 +# RESTART REQUIRED +git.enableGarbageCollection = false + +# Hour of the day for the GC Executor to scan repositories. +# This value is in 24-hour time. +# +# SINCE 1.2.0 +git.garbageCollectionHour = 0 + +# The default minimum total filesize of loose objects to trigger early garbage +# collection. +# +# You may specify a custom threshold for a repository in the repository's settings. +# Common unit suffixes of k, m, or g are supported. +# +# SINCE 1.2.0 +git.defaultGarbageCollectionThreshold = 500k + +# The default period, in days, between GCs for a repository. If the total filesize +# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's +# custom threshold, this period will be short-circuited. +# +# e.g. if a repository collects 100KB of loose objects every day with a 500KB +# threshold and a period of 7 days, it will take 5 days for the loose objects to +# be collected, packed, and pruned. +# +# OR +# +# if a repository collects 10KB of loose objects every day with a 500KB threshold +# and a period of 7 days, it will take the full 7 days for the loose objects to be +# collected, packed, and pruned. +# +# You may specify a custom period for a repository in the repository's settings. +# +# The minimum value is 1 day since the GC Executor only runs once a day. +# +# SINCE 1.2.0 +git.defaultGarbageCollectionPeriod = 7 + +# Gitblit can automatically fetch ref updates for a properly configured mirror +# repository. +# +# Requirements: +# 1. you must manually clone the repository using native git +# git clone --mirror git://somewhere.com/myrepo.git +# 2. the "origin" remote must be the mirror source +# 3. the "origin" repository must be accessible without authentication OR the +# credentials must be embedded in the origin url (not recommended) +# +# Notes: +# 1. "origin" SSH urls are untested and not likely to work +# 2. mirrors cloned while Gitblit is running are likely to require clearing the +# gitblit cache (link on the repositories page of an administrator account) +# 3. Gitblit will automatically repair any invalid fetch refspecs with a "//" +# sequence. +# +# SINCE 1.4.0 +# RESTART REQUIRED +git.enableMirroring = false + +# Specify the period between update checks for mirrored repositories. +# The shortest period you may specify between mirror update checks is 5 mins. +# +# SINCE 1.4.0 +# RESTART REQUIRED +git.mirrorPeriod = 30 mins + +# Number of bytes of a pack file to load into memory in a single read operation. +# This is the "page size" of the JGit buffer cache, used for all pack access +# operations. All disk IO occurs as single window reads. Setting this too large +# may cause the process to load more data than is required; setting this too small +# may increase the frequency of read() system calls. +# +# Default on JGit is 8 KiB on all platforms. +# +# Common unit suffixes of k, m, or g are supported. +# Documentation courtesy of the Gerrit project. +# +# SINCE 1.0.0 +# RESTART REQUIRED +git.packedGitWindowSize = 8k + +# Maximum number of bytes to load and cache in memory from pack files. If JGit +# needs to access more than this many bytes it will unload less frequently used +# windows to reclaim memory space within the process. As this buffer must be shared +# with the rest of the JVM heap, it should be a fraction of the total memory available. +# +# The JGit team recommends setting this value larger than the size of your biggest +# repository. This ensures you can serve most requests from memory. +# +# Default on JGit is 10 MiB on all platforms. +# +# Common unit suffixes of k, m, or g are supported. +# Documentation courtesy of the Gerrit project. +# +# SINCE 1.0.0 +# RESTART REQUIRED +git.packedGitLimit = 10m + +# Maximum number of bytes to reserve for caching base objects that multiple deltafied +# objects reference. By storing the entire decompressed base object in a cache Git +# is able to avoid unpacking and decompressing frequently used base objects multiple times. +# +# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust +# this value. +# +# Common unit suffixes of k, m, or g are supported. +# Documentation courtesy of the Gerrit project. +# +# SINCE 1.0.0 +# RESTART REQUIRED +git.deltaBaseCacheLimit = 10m + +# Maximum number of pack files to have open at once. A pack file must be opened +# in order for any of its data to be available in a cached window. +# +# If you increase this to a larger setting you may need to also adjust the ulimit +# on file descriptors for the host JVM, as Gitblit needs additional file descriptors +# available for network sockets and other repository data manipulation. +# +# Default on JGit is 128 file descriptors on all platforms. +# Documentation courtesy of the Gerrit project. +# +# SINCE 1.0.0 +# RESTART REQUIRED +git.packedGitOpenFiles = 128 + +# When true, JGit will use mmap() rather than malloc()+read() to load data from +# pack files. The use of mmap can be problematic on some JVMs as the garbage +# collector must deduce that a memory mapped segment is no longer in use before +# a call to munmap() can be made by the JVM native code. +# +# In server applications (such as Gitblit) that need to access many pack files, +# setting this to true risks artificially running out of virtual address space, +# as the garbage collector cannot reclaim unused mapped spaces fast enough. +# +# Default on JGit is false. Although potentially slower, it yields much more +# predictable behavior. +# Documentation courtesy of the Gerrit project. +# +# SINCE 1.0.0 +# RESTART REQUIRED +git.packedGitMmap = false + +# Validate all received (pushed) objects are valid. +# +# SINCE 1.5.0 +git.checkReceivedObjects = true + +# Validate all referenced but not supplied objects are reachable. +# +# If enabled, Gitblit will verify that references to objects not contained +# within the received pack are already reachable through at least one other +# reference advertised to clients. +# +# This feature is useful when Gitblit doesn't trust the client to not provide a +# forged SHA-1 reference to an object, in an attempt to access parts of the DAG +# that they aren't allowed to see and which have been hidden from them via the +# configured AdvertiseRefsHook or RefFilter. +# +# Enabling this feature may imply at least some, if not all, of the same functionality +# performed by git.checkReceivedObjects. +# +# SINCE 1.5.0 +git.checkReferencedObjectsAreReachable = true + +# Set the maximum allowed Git object size. +# +# If an object is larger than the given size the pack-parsing will throw an exception +# aborting the receive-pack operation. The default value, 0, disables maximum +# object size checking. +# +# SINCE 1.5.0 +git.maxObjectSizeLimit = 0 + +# Set the maximum allowed pack size. +# +# A pack exceeding this size will be rejected. The default value, -1, disables +# maximum pack size checking. +# +# SINCE 1.5.0 +git.maxPackSizeLimit = -1 + +# Use the Gitblit patch receive pack for processing contributions and tickets. +# This allows the user to push a patch using the familiar Gerrit syntax: +# +# git push <remote> HEAD:refs/for/<targetBranch> +# +# NOTE: +# This requires git.enableGitServlet = true AND it requires an authenticated +# git transport connection (http/https) when pushing from a client. +# +# Valid services include: +# com.gitblit.tickets.FileTicketService +# com.gitblit.tickets.BranchTicketService +# com.gitblit.tickets.RedisTicketService +# +# SINCE 1.4.0 +# RESTART REQUIRED +tickets.service = + +# Globally enable or disable creation of new bug, enhancement, task, etc tickets +# for all repositories. +# +# If false, no tickets can be created through the ui for any repositories. +# If true, each repository can control if they allow new tickets to be created. +# +# NOTE: +# If a repository is accepting patchsets, new proposal tickets can be created +# regardless of this setting. +# +# SINCE 1.4.0 +tickets.acceptNewTickets = true + +# Globally enable or disable pushing patchsets to all repositories. +# +# If false, no patchsets will be accepted for any repositories. +# If true, each repository can control if they accept new patchsets. +# +# NOTE: +# If a repository is accepting patchsets, new proposal tickets can be created +# regardless of the acceptNewTickets setting. +# +# SINCE 1.4.0 +tickets.acceptNewPatchsets = true + +# Default setting to control patchset merge through the web ui. If true, patchsets +# must have an approval score to enable the merge button. This setting can be +# overriden per-repository. +# +# SINCE 1.4.0 +tickets.requireApproval = false + +# The case-insensitive regular expression used to identify and close tickets on +# push to the integration branch for commits that are NOT already referenced as +# a patchset tip. +# +# SINCE 1.5.0 +tickets.closeOnPushCommitMessageRegex = (?:fixes|closes)[\\s-]+#?(\\d+) + +# Specify the location of the Lucene Ticket index +# +# SINCE 1.4.0 +# RESTART REQUIRED +tickets.indexFolder = ${baseFolder}/tickets/lucene + +# Define the url for the Redis server. +# +# e.g. redis://localhost:6379 +# redis://:foobared@localhost:6379/2 +# +# SINCE 1.4.0 +# RESTART REQUIRED +tickets.redis.url = + +# The number of tickets to display on a page. +# +# SINCE 1.4.0 +tickets.perPage = 25 + +# The folder where plugins are loaded from. +# +# SINCE 1.5.0 +# RESTART REQUIRED +# BASEFOLDER +plugins.folder = ${baseFolder}/plugins + +# The registry of available plugins. +# +# SINCE 1.5.0 +plugins.registry = http://plugins.gitblit.com/plugins.json + +# The HTTP proxy host for plugin manager. +# +# SINCE 1.7.0 +plugins.httpProxyHost = + +# The HTTP proxy port for plugin manager. +# +# SINCE 1.7.0 +plugins.httpProxyPort = + +# The HTTP proxy authorization header for plugin manager. +# +# SINCE 1.7.0 +plugins.httpProxyAuthorization = + +# Number of threads used to handle miscellaneous tasks in the background. +# +# SINCE 1.6.0 +# RESTART REQUIRED +execution.defaultThreadPoolSize = 1 + +# +# Groovy Integration +# + +# Location of Groovy scripts to use for Pre and Post receive hooks. +# Use forward slashes even on Windows!! +# e.g. c:/groovy +# +# RESTART REQUIRED +# SINCE 0.8.0 +# BASEFOLDER +groovy.scriptsFolder = ${baseFolder}/groovy + +# Specify the directory Grape uses for downloading libraries. +# http://groovy.codehaus.org/Grape +# +# RESTART REQUIRED +# SINCE 1.0.0 +# BASEFOLDER +groovy.grapeFolder = ${baseFolder}/groovy/grape + +# Scripts to execute on Pre-Receive. +# +# These scripts execute after an incoming push has been parsed and validated +# but BEFORE the changes are applied to the repository. You might reject a +# push in this script based on the repository and branch the push is attempting +# to change. +# +# Script names are case-sensitive on case-sensitive file systems. You may omit +# the traditional ".groovy" from this list if your file extension is ".groovy" +# +# NOTE: +# These scripts are only executed when pushing to *Gitblit*, not to other Git +# tooling you may be using. Also note that these scripts are shared between +# repositories. These are NOT repository-specific scripts! Within the script +# you may customize the control-flow for a specific repository by checking the +# *repository* variable. +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 0.8.0 +groovy.preReceiveScripts = + +# Scripts to execute on Post-Receive. +# +# These scripts execute AFTER an incoming push has been applied to a repository. +# You might trigger a continuous-integration build here or send a notification. +# +# Script names are case-sensitive on case-sensitive file systems. You may omit +# the traditional ".groovy" from this list if your file extension is ".groovy" +# +# NOTE: +# These scripts are only executed when pushing to *Gitblit*, not to other Git +# tooling you may be using. Also note that these scripts are shared between +# repositories. These are NOT repository-specific scripts! Within the script +# you may customize the control-flow for a specific repository by checking the +# *repository* variable. +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 0.8.0 +groovy.postReceiveScripts = + +# Repository custom fields for Groovy Hook mechanism +# +# List of key=label pairs of custom fields to prompt for in the Edit Repository +# page. These keys are stored in the repository's git config file in the +# section [gitblit "customFields"]. Key names are alphanumeric only. These +# fields are intended to be used for the Groovy hook mechanism where a script +# can adjust it's execution based on the custom fields stored in the repository +# config. +# +# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another +# +# SPACE-DELIMITED +# SINCE 1.0.0 +groovy.customFields = + +# +# Fanout Settings +# + +# Fanout is a PubSub notification service that can be used by Sparkleshare +# to eliminate repository change polling. The fanout service runs in a separate +# thread on a separate port from the Gitblit http/https application. +# This service is provided so that Sparkleshare may be used with Gitblit in +# firewalled environments or where reliance on Sparkleshare's default notifications +# server (notifications.sparkleshare.org) is unwanted. +# +# This service maintains an open socket connection from the client to the +# Fanout PubSub service. This service may not work properly behind a proxy server. + +# Specify the interface for Fanout to bind it's service. +# You may specify an ip or an empty value to bind to all interfaces. +# Specifying localhost will result in Gitblit ONLY listening to requests to +# localhost. +# +# SINCE 1.2.1 +# RESTART REQUIRED +fanout.bindInterface = + +# port for serving the Fanout PubSub service. <= 0 disables this service. +# On Unix/Linux systems, ports < 1024 require root permissions. +# Recommended value: 17000 +# +# SINCE 1.2.1 +# RESTART REQUIRED +fanout.port = 0 + +# Use Fanout NIO service. If false, a multi-threaded socket service will be used. +# Be advised, the socket implementation spawns a thread per connection plus the +# connection acceptor thread. The NIO implementation is completely single-threaded. +# +# SINCE 1.2.1 +# RESTART REQUIRED +fanout.useNio = true + +# Concurrent connection limit. <= 0 disables concurrent connection throttling. +# If > 0, only the specified number of concurrent connections will be allowed +# and all other connections will be rejected. +# +# SINCE 1.2.1 +# RESTART REQUIRED +fanout.connectionLimit = 0 + +# +# Authentication Settings +# + +# Require authentication to see everything but the admin pages +# +# SINCE 0.5.0 +# RESTART REQUIRED +web.authenticateViewPages = false + +# If web.authenticateViewPages=true you may optionally require a client-side +# basic authentication prompt instead of the standard form-based login. +# +# SINCE 1.3.0 +web.enforceHttpBasicAuthentication = false + +# Require admin authentication for the admin functions and pages +# +# SINCE 0.5.0 +# RESTART REQUIRED +web.authenticateAdminPages = true + +# Allow Gitblit to store a cookie in the user's browser for automatic +# authentication. The cookie is generated by the user service. +# +# SINCE 0.5.0 +web.allowCookieAuthentication = true + +# Allow deletion of non-empty repositories. This is enforced for all delete vectors. +# +# SINCE 1.6.0 +web.allowDeletingNonEmptyRepositories = true + +# Setting to include personal repositories in the main repositories list. +# +# SINCE 1.6.0 +web.includePersonalRepositories = false + +# Config file for storing project metadata +# +# SINCE 1.2.0 +# BASEFOLDER +web.projectsFile = ${baseFolder}/projects.conf + +# Defines the tab length for all blob views +# +# SINCE 1.7.0 +web.tabLength = 4 + +# Either the full path to a user config file (users.conf) +# OR a fully qualified class name that implements the IUserService interface. +# +# Any custom user service implementation must have a public default constructor. +# +# SINCE 0.5.0 +# RESTART REQUIRED +# BASEFOLDER +realm.userService = ${baseFolder}/users.conf + +# Ordered list of external authentication providers which will be used if +# authentication against the local user service fails. +# +# Valid providers are: +# +# htpasswd +# ldap +# pam +# redmine +# salesforce +# windows + +# e.g. realm.authenticationProviders = htpasswd windows +# +# SINCE 1.4.0 +# RESTART REQUIRED +# SPACE-DELIMITED +realm.authenticationProviders = + +# How to store passwords. +# Valid values are plain, md5, or combined-md5. md5 is the hash of password. +# combined-md5 is the hash of username.toLowerCase()+password. +# Default is md5. +# +# SINCE 0.5.0 +realm.passwordStorage = md5 + +# Minimum valid length for a plain text password. +# Default value is 5. Absolute minimum is 4. +# +# SINCE 0.5.0 +realm.minPasswordLength = 5 + +# +# Gitblit Web Settings +# +# If blank Gitblit is displayed. +# +# SINCE 0.5.0 +web.siteName = + +# The canonical url of your Gitblit server to be used in repository url generation, +# RSS feeds, and all embedded links in email and plugin-based notifications. +# +# If you are running Gitblit on a non-standard http port (i.e. not 80 and not 443) +# then you must specify that port in this url otherwise your generated urls will be +# incorrect. +# +# The hostname of this url will be extracted for SSH and GIT protocol repository +# url generation. +# +# e.g. web.canonicalUrl = https://dev.gitblit.com +# web.canonicalUrl = https://dev.gitblit.com:8443 +# +# SINCE 1.4.0 +web.canonicalUrl = + +# You may specify a different logo image for the header but it must be 120x45px. +# If the specified file does not exist, the default Gitblit logo will be used. +# +# SINCE 1.3.0 +# BASEFOLDER +web.headerLogo = ${baseFolder}/logo.png + +# You may specify a different link URL for the logo image anchor. +# If blank the Gitblit main page URL is used. +# +# SINCE 1.3.0 +# BASEFOLDER +web.rootLink = + +# You may specify a custom header background CSS color. If unspecified, the +# default color will be used. +# +# e.g. web.headerBackgroundColor = #002060 +# +# SINCE 1.3.0 +web.headerBackgroundColor = + +# You may specify a custom header foreground CSS color. If unspecified, the +# default color will be used. +# +# e.g. web.headerForegroundColor = white +# +# SINCE 1.3.0 +web.headerForegroundColor = + +# You may specify a custom header foreground hover CSS color. If unspecified, the +# default color will be used. +# +# e.g. web.headerHoverColor = white +# +# SINCE 1.3.0 +web.headerHoverColor = + +# You may specify a custom header border CSS color. If unspecified, the default +# color will be used. +# +# e.g. web.headerBorderColor = #002060 +# +# SINCE 1.3.0 +web.headerBorderColor = + +# You may specify a custom header border CSS color. If unspecified, the default +# color will be used. +# +# e.g. web.headerBorderFocusColor = #ff9900 +# +# SINCE 1.3.0 +web.headerBorderFocusColor = + +# If *web.authenticateAdminPages*=true, users with "admin" role can create +# repositories, create users, and edit repository metadata. +# +# If *web.authenticateAdminPages*=false, any user can execute the aforementioned +# functions. +# +# SINCE 0.5.0 +web.allowAdministration = true + +# Setting to disable rendering the top-level navigation header which includes +# the login form, top-level links like dashboard, repositories, search, etc. +# This setting is only useful if you plan to embed Gitblit within another page +# or system. +# +# SINCE 1.4.0 +web.hideHeader = false + +# Allows rpc clients to list repositories and possibly manage or administer the +# Gitblit server, if the authenticated account has administrator permissions. +# See *web.enableRpcManagement* and *web.enableRpcAdministration*. +# +# SINCE 0.7.0 +web.enableRpcServlet = true + +# Allows rpc clients to manage repositories and users of the Gitblit instance, +# if the authenticated account has administrator permissions. +# Requires *web.enableRpcServlet=true*. +# +# SINCE 0.7.0 +web.enableRpcManagement = false + +# Allows rpc clients to control the server settings and monitor the health of this +# this Gitblit instance, if the authenticated account has administrator permissions. +# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*. +# +# SINCE 0.7.0 +web.enableRpcAdministration = false + +# Full path to a configurable robots.txt file. With this file you can control +# what parts of your Gitblit server respectable robots are allowed to traverse. +# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html +# +# SINCE 1.0.0 +# BASEFOLDER +web.robots.txt = ${baseFolder}/robots.txt + +# The number of minutes to cache a page in the browser since the last request. +# The default value is 0 minutes. A value <= 0 disables all page caching which +# is the default behavior for Gitblit <= 1.3.0. +# +# SINCE 1.3.1 +web.pageCacheExpires = 0 + +# If true, the web ui layout will respond and adapt to the browser's dimensions. +# if false, the web ui will use a 940px fixed-width layout. +# http://twitter.github.com/bootstrap/scaffolding.html#responsive +# +# SINCE 1.0.0 +web.useResponsiveLayout = true + +# Allow Gravatar images to be displayed in Gitblit pages. +# +# SINCE 0.8.0 +web.allowGravatar = true + +# Define which class will generate the avatar URL. +# +# SINCE 1.7.0 +web.avatarClass = com.gitblit.GravatarGenerator + +# Allow dynamic zip downloads. +# +# SINCE 0.5.0 +web.allowZipDownloads = true + +# If *web.allowZipDownloads=true* the following formats will be displayed for +# download compressed archive links: +# +# zip = standard .zip +# tar = standard tar format (preserves *nix permissions and symlinks) +# gz = gz-compressed tar +# xz = xz-compressed tar +# bzip2 = bzip2-compressed tar +# +# SPACE-DELIMITED +# SINCE 1.2.0 +web.compressedDownloads = zip gz + +# Allow optional Lucene integration. Lucene indexing is an opt-in feature. +# A repository may specify branches to index with Lucene instead of using Git +# commit traversal. There are scenarios where you may want to completely disable +# Lucene indexing despite a repository specifying indexed branches. One such +# scenario is on a resource-constrained federated Gitblit mirror. +# +# SINCE 0.9.0 +web.allowLuceneIndexing = true + +# Control the frequency of Lucene repository indexing. +# The default setting is to check for updated refs every 2 mins. +# +# SINCE 1.6.1 +web.luceneFrequency = 2 mins + +# Allows an authenticated user to create forks of a repository +# +# set this to false if you want to disable all fork controls on the web site +# +web.allowForking = true + +# Controls the length of shortened commit hash ids +# +# SINCE 1.2.0 +web.shortCommitIdLength = 6 + +# Use Clippy (Flash solution) to provide a copy-to-clipboard button. +# If false, a button with a more primitive JavaScript-based prompt box will +# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative. +# +# SINCE 0.8.0 +web.allowFlashCopyToClipboard = true + +# Default maximum number of commits that a repository may contribute to the +# activity page, regardless of the selected duration. This setting may be valuable +# for an extremely busy server. This value may also be configed per-repository +# in Edit Repository. 0 disables this throttle. +# +# SINCE 1.2.0 +web.maxActivityCommits = 0 + +# Default number of entries to include in RSS Syndication links +# +# SINCE 0.5.0 +web.syndicationEntries = 25 + +# Show the size of each repository on the repositories page. +# This requires recursive traversal of each repository folder. This may be +# non-performant on some operating systems and/or filesystems. +# +# SINCE 0.5.2 +web.showRepositorySizes = true + +# List of custom regex expressions that can be displayed in the Filters menu +# of the Repositories and Activity pages. Keep them very simple because you +# are likely to run into encoding issues if they are too complex. +# +# Use !!! to separate the filters +# +# SINCE 0.8.0 +web.customFilters = + +# Show federation registrations (without token) and the current pull status +# to non-administrator users. +# +# SINCE 0.6.0 +web.showFederationRegistrations = false + +# This is the message displayed when *web.authenticateViewPages=true*. +# This can point to a file with Markdown content. +# Specifying "gitblit" uses the internal login message. +# +# SINCE 0.7.0 +# BASEFOLDER +web.loginMessage = gitblit + +# This is the message displayed above the repositories table. +# This can point to a file with Markdown content. +# Specifying "gitblit" uses the internal welcome message. +# +# SINCE 0.5.0 +# BASEFOLDER +web.repositoriesMessage = gitblit + +# Ordered list of charsets/encodings to use when trying to display a blob. +# If empty, UTF-8 and ISO-8859-1 are used. The server's default charset +# is always appended to the encoding list. If all encodings fail to cleanly +# decode the blob content, UTF-8 will be used with the standard malformed +# input/unmappable character replacement strings. +# +# SPACE-DELIMITED +# SINCE 1.0.0 +web.blobEncodings = UTF-8 ISO-8859-1 + +# Manually set the default timezone to be used by Gitblit for display in the +# web ui. This value is independent of the JVM timezone. Specifying a blank +# value will default to the JVM timezone. +# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin +# +# SINCE 0.9.0 +# RESTART REQUIRED +web.timezone = + +# Use the client timezone when formatting dates. +# This uses AJAX to determine the browser's timezone and may require more +# server overhead because a Wicket session is created. All Gitblit pages +# attempt to be stateless, if possible. +# +# SINCE 0.5.0 +# RESTART REQUIRED +web.useClientTimezone = false + +# Time format +# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html> +# +# SINCE 0.8.0 +web.timeFormat = HH:mm + +# Short date format +# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html> +# +# SINCE 0.5.0 +web.datestampShortFormat = yyyy-MM-dd + +# Long date format +# +# SINCE 0.8.0 +web.datestampLongFormat = EEEE, MMMM d, yyyy + +# Long timestamp format +# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html> +# +# SINCE 0.5.0 +web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z + +# Mount URL parameters +# This setting controls if pretty or parameter URLs are used. +# i.e. +# if true: +# http://localhost/commit/myrepo/abcdef +# if false: +# http://localhost/commit/?r=myrepo&h=abcdef +# +# SINCE 0.5.0 +# RESTART REQUIRED +web.mountParameters = true + +# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding +# in URLs as a security precaution for proxies. This setting tells Gitblit +# to preemptively replace '/' with '*' or '!' for url string parameters. +# +# <https://issues.apache.org/jira/browse/WICKET-1303> +# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10> +# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your +# *CATALINA_OPTS* or to your JVM launch parameters +# +# SINCE 0.5.2 +web.forwardSlashCharacter = / + +# Show other URLs on the summary page for accessing your git repositories +# Use spaces to separate urls. +# +# {0} is the token for the repository name +# {1} is the token for the username +# +# The username is only practical if you have setup your other git serving +# solutions accounts to have the same username as the Gitblit account. +# +# e.g. +# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0} +# +# SPACE-DELIMITED +# SINCE 0.5.0 +web.otherUrls = + +# Should HTTP/HTTPS URLs be displayed if the git servlet is enabled? +# default: true +# +# SINCE 1.7.0 +web.showHttpServletUrls = true + +# Should git URLs be displayed if the git daemon is enabled? +# default: true +# +# SINCE 1.7.0 +web.showGitDaemonUrls = true + +# Should SSH URLs be displayed if the SSH daemon is enabled? +# default: true +# +# SINCE 1.7.0 +web.showSshDaemonUrls = true + +# Should effective permissions be advertised for access paths defined in web.otherUrls? +# If false, gitblit will indicate unknown permissions for the external link. If true, +# gitblit will indicate permissions as defined within gitblit (including limiting to clone +# permission is the transport type is not a valid push mechaism in git.acceptedPushTransports). +# +# Configure with caution: Note that gitblit has no way of knowing if further restrictions +# are imposed by an external forwarding agent, so this may cause user confusion due to +# more rights being advertised than are available through the URL. It will NOT grant +# additional rights, but may incorrectly offer actions that are unavailable externally. +# default: false +# +# SINCE 1.7.0 +web.advertiseAccessPermissionForOtherUrls = false + +# Should app-specific clone links be displayed for SourceTree, SparkleShare, etc? +# +# SINCE 1.3.0 +web.allowAppCloneLinks = true + +# Choose how to present the repositories list. +# grouped = group nested/subfolder repositories together (no sorting) +# flat = flat list of repositories (sorting allowed) +# +# SINCE 0.5.0 +web.repositoryListType = grouped + +# If using a grouped repository list and there are repositories at the +# root level of your repositories folder, you may specify the displayed +# group name with this setting. This value is only used for web presentation. +# +# SINCE 0.5.0 +web.repositoryRootGroupName = main + +# Display the repository swatch color next to the repository name link in the +# repositories list. +# +# SINCE 0.8.0 +web.repositoryListSwatches = true + +# Defines the default commit message renderer. This can be configured +# per-repository. +# +# Valid values are: plain, markdown +# +# SINCE 1.4.0 +web.commitMessageRenderer = plain + +# Control if email addresses are shown in web ui +# +# SINCE 0.5.0 +web.showEmailAddresses = true + +# Shows a combobox in the page links header with commit, committer, and author +# search selection. Default search is commit. +# +# SINCE 0.5.0 +web.showSearchTypeSelection = false + +# Controls display of activity graphs on the dashboard, activity, and summary +# pages. Charts are generated using Flotr2; an open source HTML5 library. +# +# SINCE 0.5.0 +web.generateActivityGraph = true + +# Displays the commits branch graph in the summary page and commits/log page. +# +# SINCE 1.4.0 +web.showBranchGraph = true + +# The default number of days to show on the activity page. +# Value must exceed 0 else default of 7 is used +# +# SINCE 0.8.0 +web.activityDuration = 7 + +# Choices for days of activity to display. +# +# SPACE-DELIMITED +# SINCE 1.3.0 +web.activityDurationChoices = 1 3 7 14 21 28 + +# Maximum number of days of activity that may be displayed on the activity page. +# +# SINCE 1.3.2 +web.activityDurationMaximum = 30 + +# The number of days of commits to cache in memory for the dashboard, activity, +# and project pages. A value of 0 will disable all caching and will parse commits +# in each repository per-request. If the value > 0 these pages will try to fulfill +# requests using the commit cache. If the request specifies a period which falls +# outside the commit cache window, then the cache will be ignored and the request +# will be fulfilled by brute-force parsing all relevant commits per-repository. +# +# Consider the values specified for *web.activityDurationChoices* when setting +# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately. +# +# SINCE 1.3.0 +# RESTART REQUIRED +web.activityCacheDays = 14 + +# Case-insensitive list of authors to exclude from metrics. Useful for +# eliminating bots. +# +# SPACE-DELIMITED +# SINCE 1.3.0 +web.metricAuthorExclusions = + +# The number of commits to display on the summary page +# Value must exceed 0 else default of 20 is used +# +# SINCE 0.5.0 +web.summaryCommitCount = 16 + +# The number of tags/branches to display on the summary page. +# -1 = all tags/branches +# 0 = hide tags/branches +# N = N tags/branches +# +# SINCE 0.5.0 +web.summaryRefsCount = 5 + +# Show a README file, if available, on the summary page. +# +# SINCE 1.4.0 +web.summaryShowReadme = false + +# The number of items to show on a page before showing the first, prev, next +# pagination links. A default of 50 is used for any invalid value. +# +# SINCE 0.5.0 +web.itemsPerPage = 50 + +# The number of reflog changes to display on the overview page +# Value must exceed 0 else default of 5 is used +# +# SINCE 1.3.0 +web.overviewReflogCount = 5 + +# The number of reflog changes to show on a reflog page before show the first, +# prev, next pagination links. A default of 10 is used for any invalid value. +# +# SINCE 1.3.0 +web.reflogChangesPerPage = 10 + +# Specify the names of documents in the root of your repository to be displayed +# in tabs on your repository docs page. If the name is not found in the root +# then no tab is added. The order specified is the order displayed. Do not +# specify a file extension as the aggregation of markup extensions + txt are used +# in the search algorithm. +# +# SPACE-DELIMITED +# SINCE 1.4.0 +web.documents = readme home index changelog contributing submitting_patches copying license notice authors + +# Registered file extensions to ignore during Lucene indexing +# +# SPACE-DELIMITED +# SINCE 0.9.0 +web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt pptx png so swf tar xcf xls xlsx zip + +# Registered extensions for google-code-prettify +# +# SPACE-DELIMITED +# SINCE 0.5.0 +web.prettyPrintExtensions = aea agc basic bat c cbm cl clj cmd cpp cs css dart el erl erlang frm fs go groovy h hpp hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl pm prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo + +# Registered extensions for markdown transformation +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 0.5.0 +web.markdownExtensions = md mkd markdown MD MKD + +# Registered extensions for mediawiki transformation +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.4.0 +web.mediawikiExtensions = mw mediawiki + +# Registered extensions for twiki transformation +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.4.0 +web.twikiExtensions = twiki + +# Registered extensions for textile transformation +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.4.0 +web.textileExtensions = textile + +# Registered extensions for confluence transformation +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.4.0 +web.confluenceExtensions = confluence + +# Registered extensions for tracwiki transformation +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 1.4.0 +web.tracwikiExtensions = tracwiki + +# Image extensions +# +# SPACE-DELIMITED +# SINCE 0.5.0 +web.imageExtensions = bmp ico gif jpg jpeg png svg + +# Registered extensions for binary blobs +# +# SPACE-DELIMITED +# SINCE 0.5.0 +web.binaryExtensions = 7z arc arj bin dll doc docx exe gz jar lib lzh odg odf odt pdf ppt pptx so tar xls xlsx zip + +# Aggressive heap management will run the garbage collector on every generated +# page. This slows down page generation a little but improves heap consumption. +# +# SINCE 0.5.0 +web.aggressiveHeapManagement = false + +# Run the webapp in debug mode +# +# SINCE 0.5.0 +# RESTART REQUIRED +web.debugMode = false + +# Allows to hide the user logon form or dropdown menu from the top pane +# if it's not needed. +# +# SINCE 1.7.0 +web.displayUserPanel = true + +# Force a default locale for all users, ignoring the browser's settings. +# An empty value allows Gitblit to use the translation preferred by the browser. +# +# Changing this value while the server is running will only affect new sessions. +# +# e.g. web.forceDefaultLocale = en +# +# SINCE 1.3.0 +web.forceDefaultLocale = + +# The following two settings serve to avoid browser overload when trying to +# render very large diffs. Both limits apply to commitdiffs, not to single-file +# diffs. + +# Maximum number of diff lines to display for a single file diff in a commitdiff. +# Defaults to 4000; can be adjusted in the range [500 .. 4000]. Smaller values +# set the limit to 500, larger values to 4000. The count includes context lines +# in the diff. +# +# If a file diff in a commitdiff produces more lines, the diff for that file is +# not shown in the commitdiff. +# +# SINCE 1.7.0 +web.maxDiffLinesPerFile = 4000 + +# Total maximum number of diff lines to show in a commitdiff. Defaults to 20000; +# can be adjusted in the range [1000 .. 20000]. Smaller values set the limit to +# 1000, larger values to 20000. The count includes context lines in diffs. +# +# If a commitdiff produces more lines, it is truncated after the first file +# that exceeds the limit. Diffs for subsequent files in the commit are not shown +# at all in the commitdiff. Omitted files are listed, though. +# +# SINCE 1.7.0 +web.maxDiffLines = 20000 + +# Enable/disable global regex substitutions (i.e. shared across repositories) +# +# SINCE 0.5.0 +# DEPRECATED 1.4.0 (migrate to bugtraq instead) +regex.global = true + +# Example global regex substitutions +# Use !!! to separate the search pattern and the replace pattern +# searchpattern!!!replacepattern +# SINCE 0.5.0 + +# regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://somehost/bug/$3">$3</a> +# SINCE 0.5.0 + +# Example Gerrit links +# regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: <a href="http://somehost/r/#q,$2,n,z">$2</a> +# regex.global.reviewedon = \\b(Reviewed-on:\\s*)([A-Za-z0-9:/\\.]*)\\b!!!Reviewed-on: <a href="$2">$2</a> + +# Example per-repository regex substitutions overrides global +# SINCE 0.5.0 +# regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://elsewhere/bug/$3">$3</a> + +# +# Mail Settings +# SINCE 0.6.0 +# +# Mail settings are used to notify administrators of received federation proposals +# + +# ip or hostname of smtp server +# +# SINCE 0.6.0 +mail.server = + +# port to use for smtp requests +# +# SINCE 0.6.0 +mail.port = 25 + +# debug the mail executor +# +# SINCE 0.6.0 +mail.debug = false + +# use SMTPs flag +mail.smtps = false + +# use STARTTLS flag +# +# SINCE 1.6.0 +mail.starttls = false + +# if your smtp server requires authentication, supply the credentials here +# +# SINCE 0.6.0 +mail.username = +# SINCE 0.6.0 +mail.password = + +# from address for generated emails +# +# SINCE 0.6.0 +mail.fromAddress = + +# List of email addresses for the Gitblit administrators +# +# SPACE-DELIMITED +# SINCE 0.6.0 +mail.adminAddresses = + +# List of email addresses for sending push email notifications. +# +# This key currently requires use of the sendemail.groovy hook script. +# If you set sendemail.groovy in *groovy.postReceiveScripts* then email +# notifications for all repositories (regardless of access restrictions!) +# will be sent to these addresses. +# +# SPACE-DELIMITED +# SINCE 0.8.0 +mail.mailingLists = + +# +# Federation Settings +# SINCE 0.6.0 +# +# A Gitblit federation is a way to backup one Gitblit instance to another. +# +# *git.enableGitServlet* must be true to use this feature. + +# Your federation name is used for federation status acknowledgments. If it is +# unset, and you elect to send a status acknowledgment, your Gitblit instance +# will be identified by its hostname, if available, else your internal ip address. +# The source Gitblit instance will also append your external IP address to your +# identification to differentiate multiple pulling systems behind a single proxy. +# +# SINCE 0.6.0 +federation.name = + +# Specify the passphrase of this Gitblit instance. +# +# An unspecified (empty) passphrase disables processing federation requests. +# +# This value can be anything you want: an integer, a sentence, an haiku, etc. +# Keep the value simple, though, to avoid Java properties file encoding issues. +# +# Changing your passphrase will break any registrations you have established with other +# Gitblit instances. +# +# CASE-SENSITIVE +# SINCE 0.6.0 +# RESTART REQUIRED *(only to enable or disable federation)* +federation.passphrase = + +# Control whether or not this Gitblit instance can receive federation proposals +# from another Gitblit instance. Registering a federated Gitblit is a manual +# process. Proposals help to simplify that process by allowing a remote Gitblit +# instance to send your Gitblit instance the federation pull data. +# +# SINCE 0.6.0 +federation.allowProposals = false + +# The destination folder for cached federation proposals. +# Use forward slashes even on Windows!! +# +# SINCE 0.6.0 +# BASEFOLDER +federation.proposalsFolder = ${baseFolder}/proposals + +# The default pull frequency if frequency is unspecified on a registration +# +# SINCE 0.6.0 +federation.defaultFrequency = 60 mins + +# Federation Sets are named groups of repositories. The Federation Sets are +# available for selection in the repository settings page. You can assign a +# repository to one or more sets and then distribute the token for the set. +# This allows you to grant federation pull access to a subset of your available +# repositories. Tokens for federation sets only grant repository pull access. +# +# SPACE-DELIMITED +# CASE-SENSITIVE +# SINCE 0.6.0 +federation.sets = + +# Federation pull registrations +# Registrations are read once, at startup. +# +# RESTART REQUIRED +# +# frequency: +# The shortest frequency allowed is every 5 minutes +# Decimal frequency values are cast to integers +# Frequency values may be specified in mins, hours, or days +# Values that can not be parsed or are unspecified default to *federation.defaultFrequency* +# +# folder: +# if unspecified, the folder is *git.repositoriesFolder* +# if specified, the folder is relative to *git.repositoriesFolder* +# +# bare: +# if true, each repository will be created as a *bare* repository and will not +# have a working directory. +# +# if false, each repository will be created as a normal repository suitable +# for local work. +# +# mirror: +# if true, each repository HEAD is reset to *origin/master* after each pull. +# The repository will be flagged *isFrozen* after the initial clone. +# +# if false, each repository HEAD will point to the FETCH_HEAD of the initial +# clone from the origin until pushed to or otherwise manipulated. +# +# mergeAccounts: +# if true, remote accounts and their permissions are merged into your +# users.properties file +# +# notifyOnError: +# if true and the mail configuration is properly set, administrators will be +# notified by email of pull failures +# +# include and exclude: +# Space-delimited list of repositories to include or exclude from pull +# may be * wildcard to include or exclude all +# may use fuzzy match (e.g. org.eclipse.*) + +# +# (Nearly) Perfect Mirror example +# + +#federation.example1.url = https://go.gitblit.com +#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4 +#federation.example1.frequency = 120 mins +#federation.example1.folder = +#federation.example1.bare = true +#federation.example1.mirror = true +#federation.example1.mergeAccounts = true + +# +# Advanced Realm Settings +# + +# Auto-creates user accounts based on the servlet container principal. This +# assumes that your Gitblit install is a protected resource and your container's +# authentication process intercepts all Gitblit requests. +# +# SINCE 1.3.0 +realm.container.autoCreateAccounts = false + +# A set of mapping used to map HTTP session attributes to user informations +# They are used if realm.container.autoCreateAccounts is set to true and +# the webapp container used can fill the session with user informations +# +# SINCE 1.7.0 +realm.container.autoAccounts.displayName = +realm.container.autoAccounts.emailAddress = +realm.container.autoAccounts.locale = + +# If the user's created by the webapp container is given this role, +# the user created will be a admin user. +# +# SINCE 1.7.0 +realm.container.autoAccounts.adminRole = + + +# Allow or prohibit Windows guest account logins +# +# SINCE 1.3.0 +realm.windows.allowGuests = false + +# Allow user accounts belonging to the BUILTIN\Administrators group to be +# Gitblit administrators. +# +# SINCE 1.4.0 +realm.windows.permitBuiltInAdministrators = true + +# The default domain for authentication. +# +# If specified, this domain will be used for authentication UNLESS the supplied +# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain) +# +# If unspecified, the username must be specified in UPN format (name@domain). +# +# if "." (dot) is specified, ONLY the local account database will be used. +# +# SINCE 1.3.0 +realm.windows.defaultDomain = + +# The PAM service name for authentication. +# default: system-auth +# +# SINCE 1.3.1 +realm.pam.serviceName = system-auth + +# The Apache htpasswd file that contains the users and passwords. +# default: ${baseFolder}/htpasswd +# +# RESTART REQUIRED +# BASEFOLDER +# SINCE 1.3.2 +realm.htpasswd.userfile = ${baseFolder}/htpasswd + +# Restrict the Salesforce user to members of this org. +# default: 0 (i.e. do not check the Org ID) +# +# SINCE 1.3.0 +realm.salesforce.orgId = 0 + +# URL of the LDAP server. +# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to +# send StartTLS command. +# +# SINCE 1.0.0 +realm.ldap.server = ldap://localhost + +# Login username for LDAP searches. +# If this value is unspecified, anonymous LDAP login will be used. +# +# e.g. mydomain\\username +# +# SINCE 1.0.0 +realm.ldap.username = cn=Directory Manager + +# Login password for LDAP searches. +# +# SINCE 1.0.0 +realm.ldap.password = password + +# Bind pattern for Authentication. +# Allow to directly authenticate an user without LDAP Searches. +# +# e.g. CN=${username},OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain +# +# SINCE 1.5.0 +realm.ldap.bindpattern = + + +# Delegate team membership control to LDAP. +# +# If true, team user memberships will be specified by LDAP groups. This will +# disable team selection in Edit User and user selection in Edit Team. +# +# If false, LDAP will only be used for authentication and Gitblit will maintain +# team memberships with the *realm.ldap.backingUserService*. +# +# SINCE 1.0.0 +realm.ldap.maintainTeams = false + +# Root node for all LDAP users +# +# This is the root node from which subtree user searches will begin. +# If blank, Gitblit will search ALL nodes. +# +# SINCE 1.0.0 +realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain + +# Filter criteria for LDAP users +# +# Query pattern to use when searching for a user account. This may be any valid +# LDAP query expression, including the standard (&) and (|) operators. +# +# Variables may be injected via the ${variableName} syntax. +# Recognized variables are: +# ${username} - The text entered as the user name +# +# SINCE 1.0.0 +realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username})) + +# Root node for all LDAP groups to be used as Gitblit Teams +# +# This is the root node from which subtree team searches will begin. +# If blank, Gitblit will search ALL nodes. +# +# SINCE 1.0.0 +realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain + +# Filter criteria for LDAP groups +# +# Query pattern to use when searching for a team. This may be any valid +# LDAP query expression, including the standard (&) and (|) operators. +# +# Variables may be injected via the ${variableName} syntax. +# Recognized variables are: +# ${username} - The text entered as the user name +# ${dn} - The Distinguished Name of the user logged in +# +# All attributes from the LDAP User record are available. For example, if a user +# has an attribute "fullName" set to "John", "(fn=${fullName})" will be +# translated to "(fn=John)". +# +# SINCE 1.0.0 +realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn})) + +# Filter criteria for empty LDAP groups +# +# Query pattern to use when searching for an empty team. This may be any valid +# LDAP query expression, including the standard (&) and (|) operators. +# +# default: (&(objectClass=group)(!(member=*))) +# SINCE 1.4.0 +realm.ldap.groupEmptyMemberPattern = (&(objectClass=group)(!(member=*))) + +# LDAP users or groups that should be given administrator privileges. +# +# Teams are specified with a leading '@' character. Groups with spaces in the +# name can be entered as "@team name". This setting only applies when using +# LDAP to maintain team memberships. +# +# e.g. realm.ldap.admins = john @git_admins "@git admins" +# +# SPACE-DELIMITED +# SINCE 1.0.0 +realm.ldap.admins = @Git_Admins + +# Attribute(s) on the USER record that indicate their display (or full) name. +# Leave blank for no mapping available in LDAP. +# +# This may be a single attribute, or a string of multiple attributes. Examples: +# displayName - Uses the attribute 'displayName' on the user record +# ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3 +# attributes together, with a '.' after personalTitle +# +# SINCE 1.0.0 +realm.ldap.displayName = displayName + +# Attribute(s) on the USER record that indicate their email address. +# Leave blank for no mapping available in LDAP. +# +# This may be a single attribute, or a string of multiple attributes. Examples: +# email - Uses the attribute 'email' on the user record +# ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes +# together with a '.' and '@' creating something like first.last@gitblit.com +# +# SINCE 1.0.0 +realm.ldap.email = email + +# Attribute on the USER record that indicate their username to be used in gitblit +# when synchronizing users from LDAP +# if blank, Gitblit will use uid +# For MS Active Directory this may be sAMAccountName +# +# SINCE 1.0.0 +realm.ldap.uid = uid + +# Defines whether to synchronize all LDAP users and teams into the user service +# +# Valid values: true, false +# If left blank, false is assumed +# +# SINCE 1.4.0 +realm.ldap.synchronize = false + +# Defines the period to be used when synchronizing users and teams from ldap. +# +# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS' + +# default: 5 MINUTES +# +# RESTART REQUIRED +# SINCE 1.4.0 +realm.ldap.syncPeriod = 5 MINUTES + +# Defines whether to delete non-existent LDAP users from the user service +# during synchronization. depends on realm.ldap.synchronize = true +# +# Valid values: true, false +# If left blank, true is assumed +# +# SINCE 1.4.0 +realm.ldap.removeDeletedUsers = true + +# URL of the Redmine. +# +# SINCE 1.2.0 +realm.redmine.url = http://example.com/redmine + +# +# Gitblit GO Server Settings +# The following settings only affect the integrated GO variant. +# + +# The temporary folder to decompress the embedded gitblit webapp. +# +# SINCE 0.5.0 +# RESTART REQUIRED +# BASEFOLDER +server.tempFolder = ${baseFolder}/temp + +# Specify the maximum number of concurrent http/https Jetty worker +# threads to allow. This setting does not affect other threaded +# daemons and components of Gitblit. +# +# SINCE 1.3.0 +# RESTART REQUIRED +server.threadPoolSize = 50 + +# Context path for the GO application. You might want to change the context +# path if running Gitblit behind a proxy layer such as mod_proxy. +# +# SINCE 0.7.0 +# RESTART REQUIRED +server.contextPath = / + +# Standard http port to serve. <= 0 disables this connector. +# On Unix/Linux systems, ports < 1024 require root permissions. +# Recommended value: 80 or 8080 +# +# SINCE 0.5.0 +# RESTART REQUIRED +server.httpPort = 0 + +# Secure/SSL https port to serve. <= 0 disables this connector. +# On Unix/Linux systems, ports < 1024 require root permissions. +# Recommended value: 443 or 8443 +# +# SINCE 0.5.0 +# RESTART REQUIRED +server.httpsPort = 8443 + +# Automatically redirect http requests to the secure https connector. +# +# This setting requires that you have configured server.httpPort and server.httpsPort. +# Unless you are on a private LAN where you trust all client connections, it is +# recommended to use https for all communications. +# +# SINCE 1.4.0 +# RESTART REQUIRED +server.redirectToHttpsPort = false + +# Specify the interface for Jetty to bind the standard connector. +# You may specify an ip or an empty value to bind to all interfaces. +# Specifying localhost will result in Gitblit ONLY listening to requests to +# localhost. +# +# SINCE 0.5.0 +# RESTART REQUIRED +server.httpBindInterface = + +# Specify the interface for Jetty to bind the secure connector. +# You may specify an ip or an empty value to bind to all interfaces. +# Specifying localhost will result in Gitblit ONLY listening to requests to +# localhost. +# +# SINCE 0.5.0 +# RESTART REQUIRED +server.httpsBindInterface = + +# Alias of certificate to use for https/SSL serving. If blank the first +# certificate found in the keystore will be used. +# +# SINCE 1.2.0 +# RESTART REQUIRED +server.certificateAlias = localhost + +# Password for SSL keystore. +# Keystore password and certificate password must match. +# This is provided for convenience, its probably more secure to set this value +# using the --storePassword command line parameter. +# +# If you are using the official JRE or JDK from Oracle you may not have the +# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM. Because +# of this, your store/key password can not exceed 7 characters. If you require +# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction +# Policy files from Oracle. +# +# http://www.oracle.com/technetwork/java/javase/downloads/index.html +# +# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited +# Strength encryption is available. +# +# SINCE 0.5.0 +# RESTART REQUIRED +server.storePassword = gitblit + +# If serving over https (recommended) you might consider requiring clients to +# authenticate with ssl certificates. If enabled, only https clients with the +# a valid client certificate will be able to access Gitblit. +# +# If disabled, client certificate authentication is optional and will be tried +# first before falling-back to form authentication or basic authentication. +# +# Requiring client certificates to access any of Gitblit may be too extreme, +# consider this carefully. +# +# SINCE 1.2.0 +# RESTART REQUIRED +server.requireClientCertificates = false + +# Port for shutdown monitor to listen on. +# +# SINCE 0.5.0 +# RESTART REQUIRED +server.shutdownPort = 8081 + +# +# Gitblit Filestore Settings +# +# The location to save the filestore blobs +# +# SINCE 1.7.0 +filestore.storageFolder = ${baseFolder}/lfs + +# Maximum allowable upload size +# The default value, -1, disables upload limits. +# Common unit suffixes of k, m, or g are supported. +# SINCE 1.7.0 +filestore.maxUploadSize = -1 diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index f53cc112..a4202e01 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -1,1891 +1,21 @@ # -# Gitblit Settings +# GITBLIT.PROPERTIES # - -# This settings file supports parameterization from the command-line for the -# following command-line parameters: -# -# --baseFolder ${baseFolder} SINCE 1.2.1 -# -# Settings that support ${baseFolder} parameter substitution are indicated with the -# BASEFOLDER attribute. If the --baseFolder argument is unspecified, ${baseFolder} -# and it's trailing / will be discarded from the setting value leaving a relative -# path that is equivalent to pre-1.2.1 releases. -# -# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified -# -# Git Servlet Settings -# - -# Base folder for repositories. -# This folder may contain bare and non-bare repositories but Gitblit will only -# allow you to push to bare repositories. -# Use forward slashes even on Windows!! -# e.g. c:/gitrepos -# -# SINCE 0.5.0 -# RESTART REQUIRED -# BASEFOLDER -git.repositoriesFolder = ${baseFolder}/git - -# Build the available repository list at startup and cache this list for reuse. -# This reduces disk io when presenting the repositories page, responding to rpcs, -# etc, but it means that Gitblit will not automatically identify repositories -# added or deleted by external tools. -# -# For this case you can use curl, wget, etc to issue an rpc request to clear the -# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE) -# -# SINCE 1.1.0 -git.cacheRepositoryList = true - -# Search the repositories folder subfolders for other repositories. -# Repositories MAY NOT be nested (i.e. one repository within another) -# but they may be grouped together in subfolders. -# e.g. c:/gitrepos/libraries/mylibrary.git -# c:/gitrepos/libraries/myotherlibrary.git -# -# SINCE 0.5.0 -git.searchRepositoriesSubfolders = true - -# Maximum number of folders to recurse into when searching for repositories. -# The default value, -1, disables depth limits. -# -# SINCE 1.1.0 -git.searchRecursionDepth = -1 - -# List of regex exclusion patterns to match against folders found in -# *git.repositoriesFolder*. -# Use forward slashes even on Windows!! -# e.g. test/jgit\.git -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.1.0 -git.searchExclusions = - -# List of regex url patterns for extracting a repository name when locating -# submodules. -# e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract -# *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git* -# If no matches are found then the submodule repository name is assumed to be -# whatever trails the last / character. (e.g. gitblit.git). -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.1.0 -git.submoduleUrlPatterns = .*?://github.com/(.*) - -# Specify the interface for Git Daemon to bind it's service. -# You may specify an ip or an empty value to bind to all interfaces. -# Specifying localhost will result in Gitblit ONLY listening to requests to -# localhost. -# -# SINCE 1.3.0 -# RESTART REQUIRED -git.daemonBindInterface = - -# port for serving the Git Daemon service. <= 0 disables this service. -# On Unix/Linux systems, ports < 1024 require root permissions. -# Recommended value: 9418 -# -# SINCE 1.3.0 -# RESTART REQUIRED -git.daemonPort = 9418 - -# The port for serving the SSH service. <= 0 disables this service. -# On Unix/Linux systems, ports < 1024 require root permissions. -# Recommended value: 29418 -# -# SINCE 1.5.0 -# RESTART REQUIRED -git.sshPort = 29418 - -# Specify the interface for the SSH daemon to bind its service. -# You may specify an ip or an empty value to bind to all interfaces. -# Specifying localhost will result in Gitblit ONLY listening to requests to -# localhost. -# -# SINCE 1.5.0 -# RESTART REQUIRED -git.sshBindInterface = - -# Specify the SSH key manager to use for retrieving, storing, and removing -# SSH keys. -# -# Valid key managers are: -# com.gitblit.transport.ssh.FileKeyManager -# -# SINCE 1.5.0 -git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager - -# Directory for storing user SSH keys when using the FileKeyManager. -# -# SINCE 1.5.0 -git.sshKeysFolder= ${baseFolder}/ssh - -# SSH backend NIO2|MINA. -# -# The Apache Mina project recommends using the NIO2 backend. -# -# SINCE 1.5.0 -git.sshBackend = NIO2 - -# Number of threads used to parse a command line submitted by a client over SSH -# for execution, create the internal data structures used by that command, -# and schedule it for execution on another thread. -# -# SINCE 1.5.0 -git.sshCommandStartThreads = 2 - - -# Allow push/pull over http/https with JGit servlet. -# If you do NOT want to allow Git clients to clone/push to Gitblit set this -# to false. You might want to do this if you are only using ssh:// or git://. -# If you set this false, consider changing the *web.otherUrls* setting to -# indicate your clone/push urls. -# -# SINCE 0.5.0 -git.enableGitServlet = true - -# If you want to restrict all git servlet access to those with valid X509 client -# certificates then set this value to true. -# -# SINCE 1.2.0 -git.requiresClientCertificate = false - -# Enforce date checks on client certificates to ensure that they are not being -# used prematurely and that they have not expired. -# -# SINCE 1.2.0 -git.enforceCertificateValidity = true - -# List of OIDs to extract from a client certificate DN to map a certificate to -# an account username. -# -# e.g. git.certificateUsernameOIDs = CN -# e.g. git.certificateUsernameOIDs = FirstName LastName -# -# SPACE-DELIMITED -# SINCE 1.2.0 -git.certificateUsernameOIDs = CN - -# Only serve/display bare repositories. -# If there are non-bare repositories in git.repositoriesFolder and this setting -# is true, they will be excluded from the ui. -# -# SINCE 0.9.0 -git.onlyAccessBareRepositories = false - - -# Specify the list of acceptable transports for pushes. -# If this setting is empty, all transports are acceptable. -# -# Valid choices are: GIT HTTP HTTPS SSH -# -# SINCE 1.5.0 -# SPACE-DELIMITED -git.acceptedPushTransports = HTTP HTTPS SSH - -# Allow an authenticated user to create a destination repository on a push if -# the repository does not already exist. -# -# Administrator accounts can create a repository in any project. -# These repositories are created with the default access restriction and authorization -# control values. The pushing account is set as the owner. -# -# Non-administrator accounts with the CREATE role may create personal repositories. -# These repositories are created as VIEW restricted for NAMED users. -# The pushing account is set as the owner. -# -# SINCE 1.2.0 -git.allowCreateOnPush = true - -# Global setting to control anonymous pushes. -# -# This setting allows/rejects anonymous pushes at the level of the receive pack. -# This trumps all repository config settings. While anonymous pushes are convenient -# on your own box when you are a lone developer, they are not recommended for -# any multi-user installation where accountability is required. Since Gitblit -# tracks pushes and user accounts, allowing anonymous pushes compromises that -# information. -# -# SINCE 1.4.0 -git.allowAnonymousPushes = false - -# The default access restriction for new repositories. -# Valid values are NONE, PUSH, CLONE, VIEW -# NONE = anonymous view, clone, & push -# PUSH = anonymous view & clone and authenticated push -# CLONE = anonymous view, authenticated clone & push -# VIEW = authenticated view, clone, & push -# -# SINCE 1.0.0 -git.defaultAccessRestriction = PUSH - -# The default authorization control for new repositories. -# Valid values are AUTHENTICATED and NAMED -# AUTHENTICATED = any authenticated user is granted restricted access -# NAMED = only named users/teams are granted restricted access -# -# SINCE 1.1.0 -git.defaultAuthorizationControl = NAMED - -# The prefix for a users personal repository directory. -# -# Personal user repositories are created in this directory, named by the user name -# prefixed with the userRepositoryPrefix. For eaxmple, a user 'john' would have his -# personal repositories in the directory '~john'. -# -# Cannot be an empty string. Also, absolute paths are changed to relative paths by -# removing the first directory separator. -# -# It is not recommended to change this value AFTER your user's have created -# personal repositories because it will break all permissions, ownership, and -# repository push/pull operations. -# -# RESTART REQUIRED -# SINCE 1.4.0 -git.userRepositoryPrefix = ~ - -# The default incremental push tag prefix. Tag prefix applied to a repository -# that has automatic push tags enabled and does not specify a custom tag prefix. -# -# If incremental push tags are enabled, the tips of each branch in the push will -# be tagged with an increasing revision integer. -# -# e.g. refs/tags/r2345 or refs/tags/rev_2345 -# -# SINCE 1.3.0 -git.defaultIncrementalPushTagPrefix = r - -# Controls creating a repository as --shared on Unix servers. -# -# In an Unix environment where mixed access methods exist for shared repositories, -# the repository should be created with 'git init --shared' to make sure that -# it can be accessed e.g. via ssh (user git) and http (user www-data). -# -# Valid values are the values available for the '--shared' option. The the manual -# page for 'git init' for more information on shared repositories. -# -# SINCE 1.4.0 -git.createRepositoriesShared = false - -# Directory for gitignore templates used during repository creation. -# -# SINCE 1.6.0 -git.gitignoreFolder = ${baseFolder}/gitignore - -# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!) -# -# USE AT YOUR OWN RISK! -# -# If enabled, the garbage collection executor scans all repositories once a day -# at the hour of your choosing. The GC executor will take each repository "offline", -# one-at-a-time, to check if the repository satisfies it's GC trigger requirements. -# -# While the repository is offline it will be inaccessible from the web UI or from -# any of the other services (git, rpc, rss, etc). -# -# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block, -# especially on Windows systems, so if you are using other tools please coordinate -# their usage with your GC Executor schedule or do not use this feature. -# -# The GC algorithm complex and the JGit team advises caution when using their -# young implementation of GC. -# -# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics -# -# EXPERIMENTAL -# SINCE 1.2.0 -# RESTART REQUIRED -git.enableGarbageCollection = false - -# Hour of the day for the GC Executor to scan repositories. -# This value is in 24-hour time. -# -# SINCE 1.2.0 -git.garbageCollectionHour = 0 - -# The default minimum total filesize of loose objects to trigger early garbage -# collection. -# -# You may specify a custom threshold for a repository in the repository's settings. -# Common unit suffixes of k, m, or g are supported. -# -# SINCE 1.2.0 -git.defaultGarbageCollectionThreshold = 500k - -# The default period, in days, between GCs for a repository. If the total filesize -# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's -# custom threshold, this period will be short-circuited. -# -# e.g. if a repository collects 100KB of loose objects every day with a 500KB -# threshold and a period of 7 days, it will take 5 days for the loose objects to -# be collected, packed, and pruned. -# -# OR -# -# if a repository collects 10KB of loose objects every day with a 500KB threshold -# and a period of 7 days, it will take the full 7 days for the loose objects to be -# collected, packed, and pruned. -# -# You may specify a custom period for a repository in the repository's settings. -# -# The minimum value is 1 day since the GC Executor only runs once a day. -# -# SINCE 1.2.0 -git.defaultGarbageCollectionPeriod = 7 - -# Gitblit can automatically fetch ref updates for a properly configured mirror -# repository. -# -# Requirements: -# 1. you must manually clone the repository using native git -# git clone --mirror git://somewhere.com/myrepo.git -# 2. the "origin" remote must be the mirror source -# 3. the "origin" repository must be accessible without authentication OR the -# credentials must be embedded in the origin url (not recommended) -# -# Notes: -# 1. "origin" SSH urls are untested and not likely to work -# 2. mirrors cloned while Gitblit is running are likely to require clearing the -# gitblit cache (link on the repositories page of an administrator account) -# 3. Gitblit will automatically repair any invalid fetch refspecs with a "//" -# sequence. -# -# SINCE 1.4.0 -# RESTART REQUIRED -git.enableMirroring = false - -# Specify the period between update checks for mirrored repositories. -# The shortest period you may specify between mirror update checks is 5 mins. -# -# SINCE 1.4.0 -# RESTART REQUIRED -git.mirrorPeriod = 30 mins - -# Number of bytes of a pack file to load into memory in a single read operation. -# This is the "page size" of the JGit buffer cache, used for all pack access -# operations. All disk IO occurs as single window reads. Setting this too large -# may cause the process to load more data than is required; setting this too small -# may increase the frequency of read() system calls. -# -# Default on JGit is 8 KiB on all platforms. -# -# Common unit suffixes of k, m, or g are supported. -# Documentation courtesy of the Gerrit project. -# -# SINCE 1.0.0 -# RESTART REQUIRED -git.packedGitWindowSize = 8k - -# Maximum number of bytes to load and cache in memory from pack files. If JGit -# needs to access more than this many bytes it will unload less frequently used -# windows to reclaim memory space within the process. As this buffer must be shared -# with the rest of the JVM heap, it should be a fraction of the total memory available. -# -# The JGit team recommends setting this value larger than the size of your biggest -# repository. This ensures you can serve most requests from memory. -# -# Default on JGit is 10 MiB on all platforms. -# -# Common unit suffixes of k, m, or g are supported. -# Documentation courtesy of the Gerrit project. -# -# SINCE 1.0.0 -# RESTART REQUIRED -git.packedGitLimit = 10m - -# Maximum number of bytes to reserve for caching base objects that multiple deltafied -# objects reference. By storing the entire decompressed base object in a cache Git -# is able to avoid unpacking and decompressing frequently used base objects multiple times. -# -# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust -# this value. -# -# Common unit suffixes of k, m, or g are supported. -# Documentation courtesy of the Gerrit project. -# -# SINCE 1.0.0 -# RESTART REQUIRED -git.deltaBaseCacheLimit = 10m - -# Maximum number of pack files to have open at once. A pack file must be opened -# in order for any of its data to be available in a cached window. -# -# If you increase this to a larger setting you may need to also adjust the ulimit -# on file descriptors for the host JVM, as Gitblit needs additional file descriptors -# available for network sockets and other repository data manipulation. -# -# Default on JGit is 128 file descriptors on all platforms. -# Documentation courtesy of the Gerrit project. -# -# SINCE 1.0.0 -# RESTART REQUIRED -git.packedGitOpenFiles = 128 - -# When true, JGit will use mmap() rather than malloc()+read() to load data from -# pack files. The use of mmap can be problematic on some JVMs as the garbage -# collector must deduce that a memory mapped segment is no longer in use before -# a call to munmap() can be made by the JVM native code. -# -# In server applications (such as Gitblit) that need to access many pack files, -# setting this to true risks artificially running out of virtual address space, -# as the garbage collector cannot reclaim unused mapped spaces fast enough. -# -# Default on JGit is false. Although potentially slower, it yields much more -# predictable behavior. -# Documentation courtesy of the Gerrit project. -# -# SINCE 1.0.0 -# RESTART REQUIRED -git.packedGitMmap = false - -# Validate all received (pushed) objects are valid. -# -# SINCE 1.5.0 -git.checkReceivedObjects = true - -# Validate all referenced but not supplied objects are reachable. -# -# If enabled, Gitblit will verify that references to objects not contained -# within the received pack are already reachable through at least one other -# reference advertised to clients. -# -# This feature is useful when Gitblit doesn't trust the client to not provide a -# forged SHA-1 reference to an object, in an attempt to access parts of the DAG -# that they aren't allowed to see and which have been hidden from them via the -# configured AdvertiseRefsHook or RefFilter. -# -# Enabling this feature may imply at least some, if not all, of the same functionality -# performed by git.checkReceivedObjects. -# -# SINCE 1.5.0 -git.checkReferencedObjectsAreReachable = true - -# Set the maximum allowed Git object size. -# -# If an object is larger than the given size the pack-parsing will throw an exception -# aborting the receive-pack operation. The default value, 0, disables maximum -# object size checking. -# -# SINCE 1.5.0 -git.maxObjectSizeLimit = 0 - -# Set the maximum allowed pack size. -# -# A pack exceeding this size will be rejected. The default value, -1, disables -# maximum pack size checking. -# -# SINCE 1.5.0 -git.maxPackSizeLimit = -1 - -# Use the Gitblit patch receive pack for processing contributions and tickets. -# This allows the user to push a patch using the familiar Gerrit syntax: -# -# git push <remote> HEAD:refs/for/<targetBranch> -# -# NOTE: -# This requires git.enableGitServlet = true AND it requires an authenticated -# git transport connection (http/https) when pushing from a client. -# -# Valid services include: -# com.gitblit.tickets.FileTicketService -# com.gitblit.tickets.BranchTicketService -# com.gitblit.tickets.RedisTicketService -# -# SINCE 1.4.0 -# RESTART REQUIRED -tickets.service = - -# Globally enable or disable creation of new bug, enhancement, task, etc tickets -# for all repositories. -# -# If false, no tickets can be created through the ui for any repositories. -# If true, each repository can control if they allow new tickets to be created. -# -# NOTE: -# If a repository is accepting patchsets, new proposal tickets can be created -# regardless of this setting. -# -# SINCE 1.4.0 -tickets.acceptNewTickets = true - -# Globally enable or disable pushing patchsets to all repositories. -# -# If false, no patchsets will be accepted for any repositories. -# If true, each repository can control if they accept new patchsets. -# -# NOTE: -# If a repository is accepting patchsets, new proposal tickets can be created -# regardless of the acceptNewTickets setting. -# -# SINCE 1.4.0 -tickets.acceptNewPatchsets = true - -# Default setting to control patchset merge through the web ui. If true, patchsets -# must have an approval score to enable the merge button. This setting can be -# overriden per-repository. -# -# SINCE 1.4.0 -tickets.requireApproval = false - -# The case-insensitive regular expression used to identify and close tickets on -# push to the integration branch for commits that are NOT already referenced as -# a patchset tip. -# -# SINCE 1.5.0 -tickets.closeOnPushCommitMessageRegex = (?:fixes|closes)[\\s-]+#?(\\d+) - -# Specify the location of the Lucene Ticket index -# -# SINCE 1.4.0 -# RESTART REQUIRED -tickets.indexFolder = ${baseFolder}/tickets/lucene - -# Define the url for the Redis server. -# -# e.g. redis://localhost:6379 -# redis://:foobared@localhost:6379/2 -# -# SINCE 1.4.0 -# RESTART REQUIRED -tickets.redis.url = - -# The number of tickets to display on a page. -# -# SINCE 1.4.0 -tickets.perPage = 25 - -# The folder where plugins are loaded from. -# -# SINCE 1.5.0 -# RESTART REQUIRED -# BASEFOLDER -plugins.folder = ${baseFolder}/plugins - -# The registry of available plugins. -# -# SINCE 1.5.0 -plugins.registry = http://plugins.gitblit.com/plugins.json - -# Number of threads used to handle miscellaneous tasks in the background. -# -# SINCE 1.6.0 -# RESTART REQUIRED -execution.defaultThreadPoolSize = 1 - -# -# Groovy Integration -# - -# Location of Groovy scripts to use for Pre and Post receive hooks. -# Use forward slashes even on Windows!! -# e.g. c:/groovy -# -# RESTART REQUIRED -# SINCE 0.8.0 -# BASEFOLDER -groovy.scriptsFolder = ${baseFolder}/groovy - -# Specify the directory Grape uses for downloading libraries. -# http://groovy.codehaus.org/Grape -# -# RESTART REQUIRED -# SINCE 1.0.0 -# BASEFOLDER -groovy.grapeFolder = ${baseFolder}/groovy/grape - -# Scripts to execute on Pre-Receive. -# -# These scripts execute after an incoming push has been parsed and validated -# but BEFORE the changes are applied to the repository. You might reject a -# push in this script based on the repository and branch the push is attempting -# to change. -# -# Script names are case-sensitive on case-sensitive file systems. You may omit -# the traditional ".groovy" from this list if your file extension is ".groovy" -# -# NOTE: -# These scripts are only executed when pushing to *Gitblit*, not to other Git -# tooling you may be using. Also note that these scripts are shared between -# repositories. These are NOT repository-specific scripts! Within the script -# you may customize the control-flow for a specific repository by checking the -# *repository* variable. -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 0.8.0 -groovy.preReceiveScripts = - -# Scripts to execute on Post-Receive. -# -# These scripts execute AFTER an incoming push has been applied to a repository. -# You might trigger a continuous-integration build here or send a notification. -# -# Script names are case-sensitive on case-sensitive file systems. You may omit -# the traditional ".groovy" from this list if your file extension is ".groovy" -# -# NOTE: -# These scripts are only executed when pushing to *Gitblit*, not to other Git -# tooling you may be using. Also note that these scripts are shared between -# repositories. These are NOT repository-specific scripts! Within the script -# you may customize the control-flow for a specific repository by checking the -# *repository* variable. -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 0.8.0 -groovy.postReceiveScripts = - -# Repository custom fields for Groovy Hook mechanism -# -# List of key=label pairs of custom fields to prompt for in the Edit Repository -# page. These keys are stored in the repository's git config file in the -# section [gitblit "customFields"]. Key names are alphanumeric only. These -# fields are intended to be used for the Groovy hook mechanism where a script -# can adjust it's execution based on the custom fields stored in the repository -# config. -# -# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another -# -# SPACE-DELIMITED -# SINCE 1.0.0 -groovy.customFields = - -# -# Fanout Settings -# - -# Fanout is a PubSub notification service that can be used by Sparkleshare -# to eliminate repository change polling. The fanout service runs in a separate -# thread on a separate port from the Gitblit http/https application. -# This service is provided so that Sparkleshare may be used with Gitblit in -# firewalled environments or where reliance on Sparkleshare's default notifications -# server (notifications.sparkleshare.org) is unwanted. -# -# This service maintains an open socket connection from the client to the -# Fanout PubSub service. This service may not work properly behind a proxy server. - -# Specify the interface for Fanout to bind it's service. -# You may specify an ip or an empty value to bind to all interfaces. -# Specifying localhost will result in Gitblit ONLY listening to requests to -# localhost. -# -# SINCE 1.2.1 -# RESTART REQUIRED -fanout.bindInterface = - -# port for serving the Fanout PubSub service. <= 0 disables this service. -# On Unix/Linux systems, ports < 1024 require root permissions. -# Recommended value: 17000 -# -# SINCE 1.2.1 -# RESTART REQUIRED -fanout.port = 0 - -# Use Fanout NIO service. If false, a multi-threaded socket service will be used. -# Be advised, the socket implementation spawns a thread per connection plus the -# connection acceptor thread. The NIO implementation is completely single-threaded. -# -# SINCE 1.2.1 -# RESTART REQUIRED -fanout.useNio = true - -# Concurrent connection limit. <= 0 disables concurrent connection throttling. -# If > 0, only the specified number of concurrent connections will be allowed -# and all other connections will be rejected. -# -# SINCE 1.2.1 -# RESTART REQUIRED -fanout.connectionLimit = 0 - -# -# Authentication Settings -# - -# Require authentication to see everything but the admin pages -# -# SINCE 0.5.0 -# RESTART REQUIRED -web.authenticateViewPages = false - -# If web.authenticateViewPages=true you may optionally require a client-side -# basic authentication prompt instead of the standard form-based login. -# -# SINCE 1.3.0 -web.enforceHttpBasicAuthentication = false - -# Require admin authentication for the admin functions and pages -# -# SINCE 0.5.0 -# RESTART REQUIRED -web.authenticateAdminPages = true - -# Allow Gitblit to store a cookie in the user's browser for automatic -# authentication. The cookie is generated by the user service. -# -# SINCE 0.5.0 -web.allowCookieAuthentication = true - -# Allow deletion of non-empty repositories. This is enforced for all delete vectors. -# -# SINCE 1.6.0 -web.allowDeletingNonEmptyRepositories = true - -# Setting to include personal repositories in the main repositories list. -# -# SINCE 1.6.0 -web.includePersonalRepositories = false - -# Config file for storing project metadata -# -# SINCE 1.2.0 -# BASEFOLDER -web.projectsFile = ${baseFolder}/projects.conf - -# Either the full path to a user config file (users.conf) -# OR a fully qualified class name that implements the IUserService interface. -# -# Any custom user service implementation must have a public default constructor. -# -# SINCE 0.5.0 -# RESTART REQUIRED -# BASEFOLDER -realm.userService = ${baseFolder}/users.conf - -# Ordered list of external authentication providers which will be used if -# authentication against the local user service fails. -# -# Valid providers are: -# -# htpasswd -# ldap -# pam -# redmine -# salesforce -# windows - -# e.g. realm.authenticationProviders = htpasswd windows -# -# SINCE 1.4.0 -# RESTART REQUIRED -# SPACE-DELIMITED -realm.authenticationProviders = - -# How to store passwords. -# Valid values are plain, md5, or combined-md5. md5 is the hash of password. -# combined-md5 is the hash of username.toLowerCase()+password. -# Default is md5. -# -# SINCE 0.5.0 -realm.passwordStorage = md5 - -# Minimum valid length for a plain text password. -# Default value is 5. Absolute minimum is 4. -# -# SINCE 0.5.0 -realm.minPasswordLength = 5 - -# -# Gitblit Web Settings -# -# If blank Gitblit is displayed. -# -# SINCE 0.5.0 -web.siteName = - -# The canonical url of your Gitblit server to be used in repository url generation, -# RSS feeds, and all embedded links in email and plugin-based notifications. -# -# If you are running Gitblit on a non-standard http port (i.e. not 80 and not 443) -# then you must specify that port in this url otherwise your generated urls will be -# incorrect. -# -# The hostname of this url will be extracted for SSH and GIT protocol repository -# url generation. -# -# e.g. web.canonicalUrl = https://dev.gitblit.com -# web.canonicalUrl = https://dev.gitblit.com:8443 -# -# SINCE 1.4.0 -web.canonicalUrl = - -# You may specify a different logo image for the header but it must be 120x45px. -# If the specified file does not exist, the default Gitblit logo will be used. -# -# SINCE 1.3.0 -# BASEFOLDER -web.headerLogo = ${baseFolder}/logo.png - -# You may specify a different link URL for the logo image anchor. -# If blank the Gitblit main page URL is used. -# -# SINCE 1.3.0 -# BASEFOLDER -web.rootLink = - -# You may specify a custom header background CSS color. If unspecified, the -# default color will be used. -# -# e.g. web.headerBackgroundColor = #002060 -# -# SINCE 1.3.0 -web.headerBackgroundColor = - -# You may specify a custom header foreground CSS color. If unspecified, the -# default color will be used. -# -# e.g. web.headerForegroundColor = white -# -# SINCE 1.3.0 -web.headerForegroundColor = - -# You may specify a custom header foreground hover CSS color. If unspecified, the -# default color will be used. -# -# e.g. web.headerHoverColor = white -# -# SINCE 1.3.0 -web.headerHoverColor = - -# You may specify a custom header border CSS color. If unspecified, the default -# color will be used. -# -# e.g. web.headerBorderColor = #002060 -# -# SINCE 1.3.0 -web.headerBorderColor = - -# You may specify a custom header border CSS color. If unspecified, the default -# color will be used. -# -# e.g. web.headerBorderFocusColor = #ff9900 -# -# SINCE 1.3.0 -web.headerBorderFocusColor = - -# If *web.authenticateAdminPages*=true, users with "admin" role can create -# repositories, create users, and edit repository metadata. -# -# If *web.authenticateAdminPages*=false, any user can execute the aforementioned -# functions. -# -# SINCE 0.5.0 -web.allowAdministration = true - -# Setting to disable rendering the top-level navigation header which includes -# the login form, top-level links like dashboard, repositories, search, etc. -# This setting is only useful if you plan to embed Gitblit within another page -# or system. -# -# SINCE 1.4.0 -web.hideHeader = false - -# Allows rpc clients to list repositories and possibly manage or administer the -# Gitblit server, if the authenticated account has administrator permissions. -# See *web.enableRpcManagement* and *web.enableRpcAdministration*. -# -# SINCE 0.7.0 -web.enableRpcServlet = true - -# Allows rpc clients to manage repositories and users of the Gitblit instance, -# if the authenticated account has administrator permissions. -# Requires *web.enableRpcServlet=true*. -# -# SINCE 0.7.0 -web.enableRpcManagement = false - -# Allows rpc clients to control the server settings and monitor the health of this -# this Gitblit instance, if the authenticated account has administrator permissions. -# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*. -# -# SINCE 0.7.0 -web.enableRpcAdministration = false - -# Full path to a configurable robots.txt file. With this file you can control -# what parts of your Gitblit server respectable robots are allowed to traverse. -# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html -# -# SINCE 1.0.0 -# BASEFOLDER -web.robots.txt = ${baseFolder}/robots.txt - -# The number of minutes to cache a page in the browser since the last request. -# The default value is 0 minutes. A value <= 0 disables all page caching which -# is the default behavior for Gitblit <= 1.3.0. -# -# SINCE 1.3.1 -web.pageCacheExpires = 0 - -# If true, the web ui layout will respond and adapt to the browser's dimensions. -# if false, the web ui will use a 940px fixed-width layout. -# http://twitter.github.com/bootstrap/scaffolding.html#responsive -# -# SINCE 1.0.0 -web.useResponsiveLayout = true - -# Allow Gravatar images to be displayed in Gitblit pages. -# -# SINCE 0.8.0 -web.allowGravatar = true - -# Allow dynamic zip downloads. -# -# SINCE 0.5.0 -web.allowZipDownloads = true - -# If *web.allowZipDownloads=true* the following formats will be displayed for -# download compressed archive links: -# -# zip = standard .zip -# tar = standard tar format (preserves *nix permissions and symlinks) -# gz = gz-compressed tar -# xz = xz-compressed tar -# bzip2 = bzip2-compressed tar -# -# SPACE-DELIMITED -# SINCE 1.2.0 -web.compressedDownloads = zip gz - -# Allow optional Lucene integration. Lucene indexing is an opt-in feature. -# A repository may specify branches to index with Lucene instead of using Git -# commit traversal. There are scenarios where you may want to completely disable -# Lucene indexing despite a repository specifying indexed branches. One such -# scenario is on a resource-constrained federated Gitblit mirror. -# -# SINCE 0.9.0 -web.allowLuceneIndexing = true - -# Control the frequency of Lucene repository indexing. -# The default setting is to check for updated refs every 2 mins. -# -# SINCE 1.6.1 -web.luceneFrequency = 2 mins - -# Allows an authenticated user to create forks of a repository -# -# set this to false if you want to disable all fork controls on the web site -# -web.allowForking = true - -# Controls the length of shortened commit hash ids -# -# SINCE 1.2.0 -web.shortCommitIdLength = 6 - -# Use Clippy (Flash solution) to provide a copy-to-clipboard button. -# If false, a button with a more primitive JavaScript-based prompt box will -# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative. -# -# SINCE 0.8.0 -web.allowFlashCopyToClipboard = true - -# Default maximum number of commits that a repository may contribute to the -# activity page, regardless of the selected duration. This setting may be valuable -# for an extremely busy server. This value may also be configed per-repository -# in Edit Repository. 0 disables this throttle. -# -# SINCE 1.2.0 -web.maxActivityCommits = 0 - -# Default number of entries to include in RSS Syndication links -# -# SINCE 0.5.0 -web.syndicationEntries = 25 - -# Show the size of each repository on the repositories page. -# This requires recursive traversal of each repository folder. This may be -# non-performant on some operating systems and/or filesystems. -# -# SINCE 0.5.2 -web.showRepositorySizes = true - -# List of custom regex expressions that can be displayed in the Filters menu -# of the Repositories and Activity pages. Keep them very simple because you -# are likely to run into encoding issues if they are too complex. -# -# Use !!! to separate the filters -# -# SINCE 0.8.0 -web.customFilters = - -# Show federation registrations (without token) and the current pull status -# to non-administrator users. -# -# SINCE 0.6.0 -web.showFederationRegistrations = false - -# This is the message displayed when *web.authenticateViewPages=true*. -# This can point to a file with Markdown content. -# Specifying "gitblit" uses the internal login message. -# -# SINCE 0.7.0 -# BASEFOLDER -web.loginMessage = gitblit - -# This is the message displayed above the repositories table. -# This can point to a file with Markdown content. -# Specifying "gitblit" uses the internal welcome message. -# -# SINCE 0.5.0 -# BASEFOLDER -web.repositoriesMessage = gitblit - -# Ordered list of charsets/encodings to use when trying to display a blob. -# If empty, UTF-8 and ISO-8859-1 are used. The server's default charset -# is always appended to the encoding list. If all encodings fail to cleanly -# decode the blob content, UTF-8 will be used with the standard malformed -# input/unmappable character replacement strings. -# -# SPACE-DELIMITED -# SINCE 1.0.0 -web.blobEncodings = UTF-8 ISO-8859-1 - -# Manually set the default timezone to be used by Gitblit for display in the -# web ui. This value is independent of the JVM timezone. Specifying a blank -# value will default to the JVM timezone. -# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin -# -# SINCE 0.9.0 -# RESTART REQUIRED -web.timezone = - -# Use the client timezone when formatting dates. -# This uses AJAX to determine the browser's timezone and may require more -# server overhead because a Wicket session is created. All Gitblit pages -# attempt to be stateless, if possible. -# -# SINCE 0.5.0 -# RESTART REQUIRED -web.useClientTimezone = false - -# Time format -# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html> -# -# SINCE 0.8.0 -web.timeFormat = HH:mm - -# Short date format -# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html> -# -# SINCE 0.5.0 -web.datestampShortFormat = yyyy-MM-dd - -# Long date format -# -# SINCE 0.8.0 -web.datestampLongFormat = EEEE, MMMM d, yyyy - -# Long timestamp format -# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html> -# -# SINCE 0.5.0 -web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z - -# Mount URL parameters -# This setting controls if pretty or parameter URLs are used. -# i.e. -# if true: -# http://localhost/commit/myrepo/abcdef -# if false: -# http://localhost/commit/?r=myrepo&h=abcdef -# -# SINCE 0.5.0 -# RESTART REQUIRED -web.mountParameters = true - -# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding -# in URLs as a security precaution for proxies. This setting tells Gitblit -# to preemptively replace '/' with '*' or '!' for url string parameters. -# -# <https://issues.apache.org/jira/browse/WICKET-1303> -# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10> -# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your -# *CATALINA_OPTS* or to your JVM launch parameters -# -# SINCE 0.5.2 -web.forwardSlashCharacter = / - -# Show other URLs on the summary page for accessing your git repositories -# Use spaces to separate urls. -# -# {0} is the token for the repository name -# {1} is the token for the username -# -# The username is only practical if you have setup your other git serving -# solutions accounts to have the same username as the Gitblit account. -# -# e.g. -# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0} -# -# SPACE-DELIMITED -# SINCE 0.5.0 -web.otherUrls = - -# Should app-specific clone links be displayed for SourceTree, SparkleShare, etc? -# -# SINCE 1.3.0 -web.allowAppCloneLinks = true - -# Choose how to present the repositories list. -# grouped = group nested/subfolder repositories together (no sorting) -# flat = flat list of repositories (sorting allowed) -# -# SINCE 0.5.0 -web.repositoryListType = grouped - -# If using a grouped repository list and there are repositories at the -# root level of your repositories folder, you may specify the displayed -# group name with this setting. This value is only used for web presentation. -# -# SINCE 0.5.0 -web.repositoryRootGroupName = main - -# Display the repository swatch color next to the repository name link in the -# repositories list. -# -# SINCE 0.8.0 -web.repositoryListSwatches = true - -# Defines the default commit message renderer. This can be configured -# per-repository. -# -# Valid values are: plain, markdown -# -# SINCE 1.4.0 -web.commitMessageRenderer = plain - -# Control if email addresses are shown in web ui -# -# SINCE 0.5.0 -web.showEmailAddresses = true - -# Shows a combobox in the page links header with commit, committer, and author -# search selection. Default search is commit. -# -# SINCE 0.5.0 -web.showSearchTypeSelection = false - -# Controls display of activity graphs on the dashboard, activity, and summary -# pages. Charting makes use of the external Google Charts API. -# -# SINCE 0.5.0 -web.generateActivityGraph = true - -# Displays the commits branch graph in the summary page and commits/log page. -# -# SINCE 1.4.0 -web.showBranchGraph = true - -# The default number of days to show on the activity page. -# Value must exceed 0 else default of 7 is used -# -# SINCE 0.8.0 -web.activityDuration = 7 - -# Choices for days of activity to display. +# Define your custom settings in this file and/or include settings defined in +# other properties files. # -# SPACE-DELIMITED -# SINCE 1.3.0 -web.activityDurationChoices = 1 3 7 14 21 28 -# Maximum number of days of activity that may be displayed on the activity page. +# Include Gitblit's 'defaults.properties' within your configuration. # -# SINCE 1.3.2 -web.activityDurationMaximum = 30 - -# The number of days of commits to cache in memory for the dashboard, activity, -# and project pages. A value of 0 will disable all caching and will parse commits -# in each repository per-request. If the value > 0 these pages will try to fulfill -# requests using the commit cache. If the request specifies a period which falls -# outside the commit cache window, then the cache will be ignored and the request -# will be fulfilled by brute-force parsing all relevant commits per-repository. -# -# Consider the values specified for *web.activityDurationChoices* when setting -# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately. -# -# SINCE 1.3.0 -# RESTART REQUIRED -web.activityCacheDays = 14 - -# Case-insensitive list of authors to exclude from metrics. Useful for -# eliminating bots. -# -# SPACE-DELIMITED -# SINCE 1.3.0 -web.metricAuthorExclusions = - -# The number of commits to display on the summary page -# Value must exceed 0 else default of 20 is used -# -# SINCE 0.5.0 -web.summaryCommitCount = 16 - -# The number of tags/branches to display on the summary page. -# -1 = all tags/branches -# 0 = hide tags/branches -# N = N tags/branches -# -# SINCE 0.5.0 -web.summaryRefsCount = 5 - -# Show a README file, if available, on the summary page. -# -# SINCE 1.4.0 -web.summaryShowReadme = false - -# The number of items to show on a page before showing the first, prev, next -# pagination links. A default of 50 is used for any invalid value. -# -# SINCE 0.5.0 -web.itemsPerPage = 50 - -# The number of reflog changes to display on the overview page -# Value must exceed 0 else default of 5 is used -# -# SINCE 1.3.0 -web.overviewReflogCount = 5 - -# The number of reflog changes to show on a reflog page before show the first, -# prev, next pagination links. A default of 10 is used for any invalid value. -# -# SINCE 1.3.0 -web.reflogChangesPerPage = 10 - -# Specify the names of documents in the root of your repository to be displayed -# in tabs on your repository docs page. If the name is not found in the root -# then no tab is added. The order specified is the order displayed. Do not -# specify a file extension as the aggregation of markup extensions + txt are used -# in the search algorithm. +# NOTE: Gitblit will not automatically reload "included" properties. Gitblit +# only watches the 'gitblit.properties' file for modifications. # -# SPACE-DELIMITED -# SINCE 1.4.0 -web.documents = readme home index changelog contributing submitting_patches copying license notice authors - -# Registered file extensions to ignore during Lucene indexing -# -# SPACE-DELIMITED -# SINCE 0.9.0 -web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt pptx png so swf tar xcf xls xlsx zip - -# Registered extensions for google-code-prettify -# -# SPACE-DELIMITED -# SINCE 0.5.0 -web.prettyPrintExtensions = aea agc basic c cbm cl clj cpp cs css dart el erl erlang frm fs go groovy h hpp hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl pm prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo - -# Registered extensions for markdown transformation -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 0.5.0 -web.markdownExtensions = md mkd markdown MD MKD - -# Registered extensions for mediawiki transformation -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.4.0 -web.mediawikiExtensions = mw mediawiki - -# Registered extensions for twiki transformation +# Paths may be relative to the ${baseFolder} or they may be absolute. # -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.4.0 -web.twikiExtensions = twiki +# COMMA-DELIMITED +# SINCE 1.7.0 +include = defaults.properties -# Registered extensions for textile transformation -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.4.0 -web.textileExtensions = textile - -# Registered extensions for confluence transformation -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.4.0 -web.confluenceExtensions = confluence - -# Registered extensions for tracwiki transformation -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 1.4.0 -web.tracwikiExtensions = tracwiki - -# Image extensions -# -# SPACE-DELIMITED -# SINCE 0.5.0 -web.imageExtensions = bmp jpg jpeg gif png ico - -# Registered extensions for binary blobs -# -# SPACE-DELIMITED -# SINCE 0.5.0 -web.binaryExtensions = 7z arc arj bin dll doc docx exe gz jar lib lzh odg odf odt pdf ppt pptx so tar xls xlsx zip - -# Aggressive heap management will run the garbage collector on every generated -# page. This slows down page generation a little but improves heap consumption. -# -# SINCE 0.5.0 -web.aggressiveHeapManagement = false - -# Run the webapp in debug mode -# -# SINCE 0.5.0 -# RESTART REQUIRED -web.debugMode = false - -# Force a default locale for all users, ignoring the browser's settings. -# An empty value allows Gitblit to use the translation preferred by the browser. -# -# Changing this value while the server is running will only affect new sessions. -# -# e.g. web.forceDefaultLocale = en -# -# SINCE 1.3.0 -web.forceDefaultLocale = - -# Enable/disable global regex substitutions (i.e. shared across repositories) -# -# SINCE 0.5.0 -# DEPRECATED 1.4.0 (migrate to bugtraq instead) -regex.global = true - -# Example global regex substitutions -# Use !!! to separate the search pattern and the replace pattern -# searchpattern!!!replacepattern -# SINCE 0.5.0 - -# regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://somehost/bug/$3">$3</a> -# SINCE 0.5.0 - -# Example Gerrit links -# regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: <a href="http://somehost/r/#q,$2,n,z">$2</a> -# regex.global.reviewedon = \\b(Reviewed-on:\\s*)([A-Za-z0-9:/\\.]*)\\b!!!Reviewed-on: <a href="$2">$2</a> - -# Example per-repository regex substitutions overrides global -# SINCE 0.5.0 -# regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://elsewhere/bug/$3">$3</a> - -# -# Mail Settings -# SINCE 0.6.0 -# -# Mail settings are used to notify administrators of received federation proposals -# - -# ip or hostname of smtp server -# -# SINCE 0.6.0 -mail.server = - -# port to use for smtp requests -# -# SINCE 0.6.0 -mail.port = 25 - -# debug the mail executor -# -# SINCE 0.6.0 -mail.debug = false - -# use SMTPs flag -mail.smtps = false - -# use STARTTLS flag -# -# SINCE 1.6.0 -mail.starttls = false - -# if your smtp server requires authentication, supply the credentials here -# -# SINCE 0.6.0 -mail.username = -# SINCE 0.6.0 -mail.password = - -# from address for generated emails -# -# SINCE 0.6.0 -mail.fromAddress = - -# List of email addresses for the Gitblit administrators -# -# SPACE-DELIMITED -# SINCE 0.6.0 -mail.adminAddresses = - -# List of email addresses for sending push email notifications. -# -# This key currently requires use of the sendemail.groovy hook script. -# If you set sendemail.groovy in *groovy.postReceiveScripts* then email -# notifications for all repositories (regardless of access restrictions!) -# will be sent to these addresses. # -# SPACE-DELIMITED -# SINCE 0.8.0 -mail.mailingLists = - -# -# Federation Settings -# SINCE 0.6.0 -# -# A Gitblit federation is a way to backup one Gitblit instance to another. -# -# *git.enableGitServlet* must be true to use this feature. - -# Your federation name is used for federation status acknowledgments. If it is -# unset, and you elect to send a status acknowledgment, your Gitblit instance -# will be identified by its hostname, if available, else your internal ip address. -# The source Gitblit instance will also append your external IP address to your -# identification to differentiate multiple pulling systems behind a single proxy. -# -# SINCE 0.6.0 -federation.name = - -# Specify the passphrase of this Gitblit instance. -# -# An unspecified (empty) passphrase disables processing federation requests. -# -# This value can be anything you want: an integer, a sentence, an haiku, etc. -# Keep the value simple, though, to avoid Java properties file encoding issues. -# -# Changing your passphrase will break any registrations you have established with other -# Gitblit instances. -# -# CASE-SENSITIVE -# SINCE 0.6.0 -# RESTART REQUIRED *(only to enable or disable federation)* -federation.passphrase = - -# Control whether or not this Gitblit instance can receive federation proposals -# from another Gitblit instance. Registering a federated Gitblit is a manual -# process. Proposals help to simplify that process by allowing a remote Gitblit -# instance to send your Gitblit instance the federation pull data. -# -# SINCE 0.6.0 -federation.allowProposals = false - -# The destination folder for cached federation proposals. -# Use forward slashes even on Windows!! -# -# SINCE 0.6.0 -# BASEFOLDER -federation.proposalsFolder = ${baseFolder}/proposals - -# The default pull frequency if frequency is unspecified on a registration -# -# SINCE 0.6.0 -federation.defaultFrequency = 60 mins - -# Federation Sets are named groups of repositories. The Federation Sets are -# available for selection in the repository settings page. You can assign a -# repository to one or more sets and then distribute the token for the set. -# This allows you to grant federation pull access to a subset of your available -# repositories. Tokens for federation sets only grant repository pull access. -# -# SPACE-DELIMITED -# CASE-SENSITIVE -# SINCE 0.6.0 -federation.sets = - -# Federation pull registrations -# Registrations are read once, at startup. -# -# RESTART REQUIRED -# -# frequency: -# The shortest frequency allowed is every 5 minutes -# Decimal frequency values are cast to integers -# Frequency values may be specified in mins, hours, or days -# Values that can not be parsed or are unspecified default to *federation.defaultFrequency* -# -# folder: -# if unspecified, the folder is *git.repositoriesFolder* -# if specified, the folder is relative to *git.repositoriesFolder* -# -# bare: -# if true, each repository will be created as a *bare* repository and will not -# have a working directory. -# -# if false, each repository will be created as a normal repository suitable -# for local work. -# -# mirror: -# if true, each repository HEAD is reset to *origin/master* after each pull. -# The repository will be flagged *isFrozen* after the initial clone. -# -# if false, each repository HEAD will point to the FETCH_HEAD of the initial -# clone from the origin until pushed to or otherwise manipulated. -# -# mergeAccounts: -# if true, remote accounts and their permissions are merged into your -# users.properties file -# -# notifyOnError: -# if true and the mail configuration is properly set, administrators will be -# notified by email of pull failures -# -# include and exclude: -# Space-delimited list of repositories to include or exclude from pull -# may be * wildcard to include or exclude all -# may use fuzzy match (e.g. org.eclipse.*) - -# -# (Nearly) Perfect Mirror example -# - -#federation.example1.url = https://go.gitblit.com -#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4 -#federation.example1.frequency = 120 mins -#federation.example1.folder = -#federation.example1.bare = true -#federation.example1.mirror = true -#federation.example1.mergeAccounts = true - -# -# Advanced Realm Settings -# - -# Auto-creates user accounts based on the servlet container principal. This -# assumes that your Gitblit install is a protected resource and your container's -# authentication process intercepts all Gitblit requests. -# -# SINCE 1.3.0 -realm.container.autoCreateAccounts = false - -# Allow or prohibit Windows guest account logins -# -# SINCE 1.3.0 -realm.windows.allowGuests = false - -# Allow user accounts belonging to the BUILTIN\Administrators group to be -# Gitblit administrators. -# -# SINCE 1.4.0 -realm.windows.permitBuiltInAdministrators = true - -# The default domain for authentication. -# -# If specified, this domain will be used for authentication UNLESS the supplied -# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain) -# -# If unspecified, the username must be specified in UPN format (name@domain). -# -# if "." (dot) is specified, ONLY the local account database will be used. -# -# SINCE 1.3.0 -realm.windows.defaultDomain = - -# The PAM service name for authentication. -# default: system-auth -# -# SINCE 1.3.1 -realm.pam.serviceName = system-auth - -# The Apache htpasswd file that contains the users and passwords. -# default: ${baseFolder}/htpasswd -# -# RESTART REQUIRED -# BASEFOLDER -# SINCE 1.3.2 -realm.htpasswd.userfile = ${baseFolder}/htpasswd - -# Restrict the Salesforce user to members of this org. -# default: 0 (i.e. do not check the Org ID) -# -# SINCE 1.3.0 -realm.salesforce.orgId = 0 - -# URL of the LDAP server. -# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to -# send StartTLS command. -# -# SINCE 1.0.0 -realm.ldap.server = ldap://localhost - -# Login username for LDAP searches. -# If this value is unspecified, anonymous LDAP login will be used. -# -# e.g. mydomain\\username -# -# SINCE 1.0.0 -realm.ldap.username = cn=Directory Manager - -# Login password for LDAP searches. -# -# SINCE 1.0.0 -realm.ldap.password = password - -# Bind pattern for Authentication. -# Allow to directly authenticate an user without LDAP Searches. -# -# e.g. CN=${username},OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain -# -# SINCE 1.5.0 -realm.ldap.bindpattern = - - -# Delegate team membership control to LDAP. -# -# If true, team user memberships will be specified by LDAP groups. This will -# disable team selection in Edit User and user selection in Edit Team. -# -# If false, LDAP will only be used for authentication and Gitblit will maintain -# team memberships with the *realm.ldap.backingUserService*. -# -# SINCE 1.0.0 -realm.ldap.maintainTeams = false - -# Root node for all LDAP users -# -# This is the root node from which subtree user searches will begin. -# If blank, Gitblit will search ALL nodes. -# -# SINCE 1.0.0 -realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain - -# Filter criteria for LDAP users -# -# Query pattern to use when searching for a user account. This may be any valid -# LDAP query expression, including the standard (&) and (|) operators. -# -# Variables may be injected via the ${variableName} syntax. -# Recognized variables are: -# ${username} - The text entered as the user name -# -# SINCE 1.0.0 -realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username})) - -# Root node for all LDAP groups to be used as Gitblit Teams -# -# This is the root node from which subtree team searches will begin. -# If blank, Gitblit will search ALL nodes. -# -# SINCE 1.0.0 -realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain - -# Filter criteria for LDAP groups -# -# Query pattern to use when searching for a team. This may be any valid -# LDAP query expression, including the standard (&) and (|) operators. -# -# Variables may be injected via the ${variableName} syntax. -# Recognized variables are: -# ${username} - The text entered as the user name -# ${dn} - The Distinguished Name of the user logged in -# -# All attributes from the LDAP User record are available. For example, if a user -# has an attribute "fullName" set to "John", "(fn=${fullName})" will be -# translated to "(fn=John)". -# -# SINCE 1.0.0 -realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn})) - -# Filter criteria for empty LDAP groups -# -# Query pattern to use when searching for an empty team. This may be any valid -# LDAP query expression, including the standard (&) and (|) operators. -# -# default: (&(objectClass=group)(!(member=*))) -# SINCE 1.4.0 -realm.ldap.groupEmptyMemberPattern = (&(objectClass=group)(!(member=*))) - -# LDAP users or groups that should be given administrator privileges. -# -# Teams are specified with a leading '@' character. Groups with spaces in the -# name can be entered as "@team name". This setting only applies when using -# LDAP to maintain team memberships. -# -# e.g. realm.ldap.admins = john @git_admins "@git admins" -# -# SPACE-DELIMITED -# SINCE 1.0.0 -realm.ldap.admins = @Git_Admins - -# Attribute(s) on the USER record that indicate their display (or full) name. -# Leave blank for no mapping available in LDAP. -# -# This may be a single attribute, or a string of multiple attributes. Examples: -# displayName - Uses the attribute 'displayName' on the user record -# ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3 -# attributes together, with a '.' after personalTitle -# -# SINCE 1.0.0 -realm.ldap.displayName = displayName - -# Attribute(s) on the USER record that indicate their email address. -# Leave blank for no mapping available in LDAP. -# -# This may be a single attribute, or a string of multiple attributes. Examples: -# email - Uses the attribute 'email' on the user record -# ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes -# together with a '.' and '@' creating something like first.last@gitblit.com -# -# SINCE 1.0.0 -realm.ldap.email = email - -# Attribute on the USER record that indicate their username to be used in gitblit -# when synchronizing users from LDAP -# if blank, Gitblit will use uid -# For MS Active Directory this may be sAMAccountName -# -# SINCE 1.0.0 -realm.ldap.uid = uid - -# Defines whether to synchronize all LDAP users and teams into the user service -# -# Valid values: true, false -# If left blank, false is assumed -# -# SINCE 1.4.0 -realm.ldap.synchronize = false - -# Defines the period to be used when synchronizing users and teams from ldap. -# -# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS' - -# default: 5 MINUTES -# -# RESTART REQUIRED -# SINCE 1.4.0 -realm.ldap.syncPeriod = 5 MINUTES - -# Defines whether to delete non-existent LDAP users from the user service -# during synchronization. depends on realm.ldap.synchronize = true -# -# Valid values: true, false -# If left blank, true is assumed -# -# SINCE 1.4.0 -realm.ldap.removeDeletedUsers = true - -# URL of the Redmine. -# -# SINCE 1.2.0 -realm.redmine.url = http://example.com/redmine - -# -# Gitblit GO Server Settings -# The following settings only affect the integrated GO variant. -# - -# The temporary folder to decompress the embedded gitblit webapp. -# -# SINCE 0.5.0 -# RESTART REQUIRED -# BASEFOLDER -server.tempFolder = ${baseFolder}/temp - -# Specify the maximum number of concurrent http/https Jetty worker -# threads to allow. This setting does not affect other threaded -# daemons and components of Gitblit. -# -# SINCE 1.3.0 -# RESTART REQUIRED -server.threadPoolSize = 50 - -# Context path for the GO application. You might want to change the context -# path if running Gitblit behind a proxy layer such as mod_proxy. -# -# SINCE 0.7.0 -# RESTART REQUIRED -server.contextPath = / - -# Standard http port to serve. <= 0 disables this connector. -# On Unix/Linux systems, ports < 1024 require root permissions. -# Recommended value: 80 or 8080 -# -# SINCE 0.5.0 -# RESTART REQUIRED -server.httpPort = 0 - -# Secure/SSL https port to serve. <= 0 disables this connector. -# On Unix/Linux systems, ports < 1024 require root permissions. -# Recommended value: 443 or 8443 -# -# SINCE 0.5.0 -# RESTART REQUIRED -server.httpsPort = 8443 - -# Automatically redirect http requests to the secure https connector. -# -# This setting requires that you have configured server.httpPort and server.httpsPort. -# Unless you are on a private LAN where you trust all client connections, it is -# recommended to use https for all communications. -# -# SINCE 1.4.0 -# RESTART REQUIRED -server.redirectToHttpsPort = false - -# Specify the interface for Jetty to bind the standard connector. -# You may specify an ip or an empty value to bind to all interfaces. -# Specifying localhost will result in Gitblit ONLY listening to requests to -# localhost. -# -# SINCE 0.5.0 -# RESTART REQUIRED -server.httpBindInterface = - -# Specify the interface for Jetty to bind the secure connector. -# You may specify an ip or an empty value to bind to all interfaces. -# Specifying localhost will result in Gitblit ONLY listening to requests to -# localhost. -# -# SINCE 0.5.0 -# RESTART REQUIRED -server.httpsBindInterface = - -# Alias of certificate to use for https/SSL serving. If blank the first -# certificate found in the keystore will be used. -# -# SINCE 1.2.0 -# RESTART REQUIRED -server.certificateAlias = localhost - -# Password for SSL keystore. -# Keystore password and certificate password must match. -# This is provided for convenience, its probably more secure to set this value -# using the --storePassword command line parameter. -# -# If you are using the official JRE or JDK from Oracle you may not have the -# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM. Because -# of this, your store/key password can not exceed 7 characters. If you require -# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction -# Policy files from Oracle. -# -# http://www.oracle.com/technetwork/java/javase/downloads/index.html -# -# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited -# Strength encryption is available. -# -# SINCE 0.5.0 -# RESTART REQUIRED -server.storePassword = gitblit - -# If serving over https (recommended) you might consider requiring clients to -# authenticate with ssl certificates. If enabled, only https clients with the -# a valid client certificate will be able to access Gitblit. -# -# If disabled, client certificate authentication is optional and will be tried -# first before falling-back to form authentication or basic authentication. -# -# Requiring client certificates to access any of Gitblit may be too extreme, -# consider this carefully. -# -# SINCE 1.2.0 -# RESTART REQUIRED -server.requireClientCertificates = false - -# Port for shutdown monitor to listen on. +# Define your overrides or custom settings below # -# SINCE 0.5.0 -# RESTART REQUIRED -server.shutdownPort = 8081 diff --git a/src/main/distrib/linux/install-service-fedora.sh b/src/main/distrib/linux/install-service-fedora.sh index 281fa409..4fb43c61 100755 --- a/src/main/distrib/linux/install-service-fedora.sh +++ b/src/main/distrib/linux/install-service-fedora.sh @@ -1,5 +1,5 @@ #!/bin/bash -x -# This script installes gitblit on a system running under systemd. +# This script installs Gitblit on a system running under systemd. # The script assumes the server is running as user giblit # First create a file with the default settings @@ -8,7 +8,6 @@ GITBLIT_PATH=/opt/gitblit GITBLIT_BASE_FOLDER=/opt/gitblit/data GITBLIT_HTTP_PORT=0 GITBLIT_HTTPS_PORT=8443 -GITBLIT_LOG=/var/log/gitblit.log EOF # Create a systemd service file cat > /tmp/gitblit.service << EOF @@ -30,7 +29,5 @@ WantedBy=multi-user.target EOF # Finally copy the files to the destination and register the systemd unit. -sudo su -c "cp /tmp/gitblit.defaults /etc/sysconfig/gitblit && cp /tmp/gitblit.service /usr/lib/systemd/system/" +sudo su -c "cp /tmp/gitblit.defaults /etc/sysconfig/gitblit && cp /tmp/gitblit.service /etc/systemd/system/" sudo su -c "systemctl daemon-reload && systemctl enable gitblit.service && systemctl start gitblit.service" -# Prepare the logfile -sudo su -c "touch /var/log/gitblit.log && chown gitblit:gitblit /var/log/gitblit.log" diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore index b978906e..92bb4afa 100644 --- a/src/main/java/.gitignore +++ b/src/main/java/.gitignore @@ -1 +1,2 @@ /clientapps.json +/defaults.properties diff --git a/src/main/java/WEB-INF/web.xml b/src/main/java/WEB-INF/web.xml index 13f612e4..db772153 100644 --- a/src/main/java/WEB-INF/web.xml +++ b/src/main/java/WEB-INF/web.xml @@ -1,361 +1,52 @@ -<?xml version="1.0" encoding="UTF-8"?>
-<web-app version="2.4"
- xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
-
- <!-- The base folder is used to specify the root location of your Gitblit data.
-
- ${baseFolder}/gitblit.properties
- ${baseFolder}/users.conf
- ${baseFolder}/projects.conf
- ${baseFolder}/robots.txt
- ${baseFolder}/git
- ${baseFolder}/groovy
- ${baseFolder}/groovy/grape
- ${baseFolder}/proposals
-
- By default, this location is WEB-INF/data. It is recommended to set this
- path to a location outside your webapps folder that is writable by your
- servlet container. Gitblit will copy the WEB-INF/data files to that
- location for you when it restarts. This approach makes upgrading simpler.
- All you have to do is set this parameter for the new release and then
- review the defaults for any new settings. Settings are always versioned
- with a SINCE x.y.z attribute and also noted in the release changelog.
- -->
- <env-entry>
- <description>The base folder is used to specify the root location of your Gitblit data.</description>
- <env-entry-name>baseFolder</env-entry-name>
- <env-entry-type>java.lang.String</env-entry-type>
- <env-entry-value>${contextFolder}/WEB-INF/data</env-entry-value>
- </env-entry>
-
- <!-- Gitblit Displayname -->
- <display-name>Gitblit - @gb.version@</display-name>
-
-
-<!-- Gitblit Context Listener --><!-- STRIP
- <listener>
- <listener-class>com.gitblit.servlet.GitblitContext</listener-class>
- </listener>STRIP -->
-
-
- <!-- Git Servlet
- <url-pattern> MUST match:
- * GitFilter
- * com.gitblit.Constants.GIT_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>GitServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.GitServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>GitServlet</servlet-name>
- <url-pattern>/git/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>GitServlet</servlet-name>
- <url-pattern>/r/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- SparkleShare Invite Servlet
- <url-pattern> MUST match:
- * com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>SparkleShareInviteServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.SparkleShareInviteServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>SparkleShareInviteServlet</servlet-name>
- <url-pattern>/sparkleshare/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Syndication Servlet
- <url-pattern> MUST match:
- * SyndicationFilter
- * com.gitblit.Constants.SYNDICATION_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>SyndicationServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.SyndicationServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>SyndicationServlet</servlet-name>
- <url-pattern>/feed/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Zip Servlet
- <url-pattern> MUST match:
- * ZipServlet
- * com.gitblit.Constants.ZIP_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>ZipServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.DownloadZipServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>ZipServlet</servlet-name>
- <url-pattern>/zip/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Federation Servlet
- <url-pattern> MUST match:
- * com.gitblit.Constants.FEDERATION_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>FederationServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.FederationServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>FederationServlet</servlet-name>
- <url-pattern>/federation/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Rpc Servlet
- <url-pattern> MUST match:
- * com.gitblit.Constants.RPC_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>RpcServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.RpcServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>RpcServlet</servlet-name>
- <url-pattern>/rpc/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Raw Servlet
- <url-pattern> MUST match:
- * RawFilter
- * com.gitblit.Constants.RAW_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>RawServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.RawServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>RawServlet</servlet-name>
- <url-pattern>/raw/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Pages Servlet
- <url-pattern> MUST match:
- * PagesFilter
- * com.gitblit.Constants.PAGES_PATH
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>PagesServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.PagesServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>PagesServlet</servlet-name>
- <url-pattern>/pages/*</url-pattern>
- </servlet-mapping>
-
-
- <!-- Logo Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>LogoServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.LogoServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>LogoServlet</servlet-name>
- <url-pattern>/logo.png</url-pattern>
- </servlet-mapping>
-
-
- <!-- PT Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>PtServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.PtServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>PtServlet</servlet-name>
- <url-pattern>/pt</url-pattern>
- </servlet-mapping>
-
-
- <!-- Branch Graph Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>BranchGraphServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.BranchGraphServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>BranchGraphServlet</servlet-name>
- <url-pattern>/graph/*</url-pattern>
- </servlet-mapping>
-
- <!-- Robots.txt Servlet
- <url-pattern> MUST match:
- * Wicket Filter ignorePaths parameter -->
- <servlet>
- <servlet-name>RobotsTxtServlet</servlet-name>
- <servlet-class>com.gitblit.servlet.RobotsTxtServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>RobotsTxtServlet</servlet-name>
- <url-pattern>/robots.txt</url-pattern>
- </servlet-mapping>
-
- <filter>
- <filter-name>ProxyFilter</filter-name>
- <filter-class>com.gitblit.servlet.ProxyFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>ProxyFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <!-- Git Access Restriction Filter
- <url-pattern> MUST match:
- * GitServlet
- * com.gitblit.Constants.GIT_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>GitFilter</filter-name>
- <filter-class>com.gitblit.servlet.GitFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>GitFilter</filter-name>
- <url-pattern>/git/*</url-pattern>
- </filter-mapping>
- <filter-mapping>
- <filter-name>GitFilter</filter-name>
- <url-pattern>/r/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Syndication Restriction Filter
- <url-pattern> MUST match:
- * SyndicationServlet
- * com.gitblit.Constants.SYNDICATION_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>SyndicationFilter</filter-name>
- <filter-class>com.gitblit.servlet.SyndicationFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>SyndicationFilter</filter-name>
- <url-pattern>/feed/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Download Zip Restriction Filter
- <url-pattern> MUST match:
- * DownloadZipServlet
- * com.gitblit.Constants.ZIP_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>ZipFilter</filter-name>
- <filter-class>com.gitblit.servlet.DownloadZipFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>ZipFilter</filter-name>
- <url-pattern>/zip/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Rpc Restriction Filter
- <url-pattern> MUST match:
- * RpcServlet
- * com.gitblit.Constants.RPC_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>RpcFilter</filter-name>
- <filter-class>com.gitblit.servlet.RpcFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>RpcFilter</filter-name>
- <url-pattern>/rpc/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Branch Restriction Filter
- <url-pattern> MUST match:
- * RawServlet
- * com.gitblit.Constants.BRANCH_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>RawFilter</filter-name>
- <filter-class>com.gitblit.servlet.RawFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>RawFilter</filter-name>
- <url-pattern>/raw/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Pages Restriction Filter
- <url-pattern> MUST match:
- * PagesServlet
- * com.gitblit.Constants.PAGES_PATH
- * Wicket Filter ignorePaths parameter -->
- <filter>
- <filter-name>PagesFilter</filter-name>
- <filter-class>com.gitblit.servlet.PagesFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>PagesFilter</filter-name>
- <url-pattern>/pages/*</url-pattern>
- </filter-mapping>
-
- <filter>
- <filter-name>EnforceAuthenticationFilter</filter-name>
- <filter-class>com.gitblit.servlet.EnforceAuthenticationFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>EnforceAuthenticationFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
-
- <!-- Wicket Filter -->
- <filter>
- <filter-name>wicketFilter</filter-name>
- <filter-class>
- com.gitblit.wicket.GitblitWicketFilter
- </filter-class>
- <init-param>
- <param-name>ignorePaths</param-name>
- <!-- Paths should match
- * SyndicationFilter <url-pattern>
- * SyndicationServlet <url-pattern>
- * com.gitblit.Constants.SYNDICATION_PATH
- * GitFilter <url-pattern>
- * GitServlet <url-pattern>
- * com.gitblit.Constants.GIT_PATH
- * SparkleshareInviteServlet <url-pattern>
- * com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
- * Zipfilter <url-pattern>
- * ZipServlet <url-pattern>
- * com.gitblit.Constants.ZIP_PATH
- * FederationServlet <url-pattern>
- * RpcFilter <url-pattern>
- * RpcServlet <url-pattern>
- * RawFilter <url-pattern>
- * RawServlet <url-pattern>
- * PagesFilter <url-pattern>
- * PagesServlet <url-pattern>
- * com.gitblit.Constants.PAGES_PATH -->
- <param-value>r/,git/,pt,feed/,zip/,federation/,rpc/,raw/,pages/,robots.txt,logo.png,graph/,sparkleshare/</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>wicketFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
+<?xml version="1.0" encoding="UTF-8"?> +<web-app version="2.4" + xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd"> + + <!-- The base folder is used to specify the root location of your Gitblit data. + + ${baseFolder}/gitblit.properties + ${baseFolder}/users.conf + ${baseFolder}/projects.conf + ${baseFolder}/robots.txt + ${baseFolder}/git + ${baseFolder}/groovy + ${baseFolder}/groovy/grape + ${baseFolder}/proposals + + By default, this location is WEB-INF/data. It is recommended to set this + path to a location outside your webapps folder that is writable by your + servlet container. Gitblit will copy the WEB-INF/data files to that + location for you when it restarts. This approach makes upgrading simpler. + All you have to do is set this parameter for the new release and then + review the defaults for any new settings. Settings are always versioned + with a SINCE x.y.z attribute and also noted in the release changelog. + --> + <env-entry> + <description>The base folder is used to specify the root location of your Gitblit data.</description> + <env-entry-name>baseFolder</env-entry-name> + <env-entry-type>java.lang.String</env-entry-type> + <env-entry-value>${contextFolder}/WEB-INF/data</env-entry-value> + </env-entry> + + <!-- Gitblit Displayname --> + <display-name>Gitblit - @gb.version@</display-name> + + <listener> + <listener-class>com.gitblit.servlet.GitblitContext</listener-class> + </listener> + + <filter> + <filter-name>guiceFilter</filter-name> + <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> + </filter> + + <filter-mapping> + <filter-name>guiceFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + + <session-config> + <tracking-mode>COOKIE</tracking-mode> + </session-config> </web-app>
\ No newline at end of file diff --git a/src/main/java/com/gitblit/AvatarGenerator.java b/src/main/java/com/gitblit/AvatarGenerator.java new file mode 100644 index 00000000..0415f925 --- /dev/null +++ b/src/main/java/com/gitblit/AvatarGenerator.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit; + +public interface AvatarGenerator { + + String getURL(String username, String emailaddress, boolean identicon, int width); + +} diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java index d7d6c14f..200ec8a6 100644 --- a/src/main/java/com/gitblit/ConfigUserService.java +++ b/src/main/java/com/gitblit/ConfigUserService.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
import com.gitblit.Constants.Transport;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.TeamModel;
@@ -734,22 +735,22 @@ public class ConfigUserService implements IUserService { // user roles
List<String> roles = new ArrayList<String>();
if (model.canAdmin) {
- roles.add(Constants.ADMIN_ROLE);
+ roles.add(Role.ADMIN.getRole());
}
if (model.canFork) {
- roles.add(Constants.FORK_ROLE);
+ roles.add(Role.FORK.getRole());
}
if (model.canCreate) {
- roles.add(Constants.CREATE_ROLE);
+ roles.add(Role.CREATE.getRole());
}
if (model.excludeFromFederation) {
- roles.add(Constants.NOT_FEDERATED_ROLE);
+ roles.add(Role.NOT_FEDERATED.getRole());
}
if (roles.size() == 0) {
// we do this to ensure that user record with no password
// is written. otherwise, StoredConfig optimizes that account
// away. :(
- roles.add(Constants.NO_ROLE);
+ roles.add(Role.NONE.getRole());
}
config.setStringList(USER, model.username, ROLE, roles);
@@ -778,18 +779,18 @@ public class ConfigUserService implements IUserService { // team roles
List<String> roles = new ArrayList<String>();
if (model.canAdmin) {
- roles.add(Constants.ADMIN_ROLE);
+ roles.add(Role.ADMIN.getRole());
}
if (model.canFork) {
- roles.add(Constants.FORK_ROLE);
+ roles.add(Role.FORK.getRole());
}
if (model.canCreate) {
- roles.add(Constants.CREATE_ROLE);
+ roles.add(Role.CREATE.getRole());
}
if (roles.size() == 0) {
// we do this to ensure that team record is written.
// Otherwise, StoredConfig might optimizes that record away.
- roles.add(Constants.NO_ROLE);
+ roles.add(Role.NONE.getRole());
}
config.setStringList(TEAM, model.name, ROLE, roles);
if (model.accountType != null) {
@@ -911,10 +912,10 @@ public class ConfigUserService implements IUserService { // user roles
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
USER, username, ROLE)));
- user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
- user.canFork = roles.contains(Constants.FORK_ROLE);
- user.canCreate = roles.contains(Constants.CREATE_ROLE);
- user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
+ user.canAdmin = roles.contains(Role.ADMIN.getRole());
+ user.canFork = roles.contains(Role.FORK.getRole());
+ user.canCreate = roles.contains(Role.CREATE.getRole());
+ user.excludeFromFederation = roles.contains(Role.NOT_FEDERATED.getRole());
// repository memberships
if (!user.canAdmin) {
@@ -947,9 +948,9 @@ public class ConfigUserService implements IUserService { TeamModel team = new TeamModel(teamname);
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
TEAM, teamname, ROLE)));
- team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
- team.canFork = roles.contains(Constants.FORK_ROLE);
- team.canCreate = roles.contains(Constants.CREATE_ROLE);
+ team.canAdmin = roles.contains(Role.ADMIN.getRole());
+ team.canFork = roles.contains(Role.FORK.getRole());
+ team.canCreate = roles.contains(Role.CREATE.getRole());
team.accountType = AccountType.fromString(config.getString(TEAM, teamname, ACCOUNTTYPE));
if (!team.canAdmin) {
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index fa8af25f..4aa8c0ca 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -36,14 +36,19 @@ public class Constants { public static final String FULL_NAME = "Gitblit - a pure Java Git solution";
+ @Deprecated
public static final String ADMIN_ROLE = "#admin";
+ @Deprecated
public static final String FORK_ROLE = "#fork";
+ @Deprecated
public static final String CREATE_ROLE = "#create";
+ @Deprecated
public static final String NOT_FEDERATED_ROLE = "#notfederated";
+ @Deprecated
public static final String NO_ROLE = "#none";
public static final String EXTERNAL_ACCOUNT = "#externalAccount";
@@ -55,6 +60,8 @@ public class Constants { public static final String R_PATH = "/r/";
public static final String GIT_PATH = "/git/";
+
+ public static final String REGEX_SHA256 = "[a-fA-F0-9]{64}";
public static final String ZIP_PATH = "/zip/";
@@ -70,6 +77,8 @@ public class Constants { public static final String RAW_PATH = "/raw/";
+ public static final String PT_PATH = "/pt";
+
public static final String BRANCH_GRAPH_PATH = "/graph/";
public static final String BORDER = "*****************************************************************";
@@ -130,7 +139,11 @@ public class Constants { public static final String DEVELOP = "develop";
- public static final String AUTHENTICATION_TYPE = "authentication-type";
+ public static final String ATTRIB_AUTHTYPE = NAME + ":authentication-type";
+
+ public static final String ATTRIB_AUTHUSER = NAME + ":authenticated-user";
+
+ public static final String R_LFS = "info/lfs/";
public static String getVersion() {
String v = Constants.class.getPackage().getImplementationVersion();
@@ -148,6 +161,17 @@ public class Constants { return getManifestValue("build-date", "PENDING");
}
+ public static String getASCIIArt() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" _____ _ _ _ _ _ _").append('\n');
+ sb.append(" | __ \\(_)| | | | | |(_)| |").append('\n');
+ sb.append(" | | \\/ _ | |_ | |__ | | _ | |_").append('\n');
+ sb.append(" | | __ | || __|| '_ \\ | || || __|").append(" ").append("http://gitblit.com").append('\n');
+ sb.append(" | |_\\ \\| || |_ | |_) || || || |_").append(" ").append("@gitblit").append('\n');
+ sb.append(" \\____/|_| \\__||_.__/ |_||_| \\__|").append(" ").append(Constants.getVersion()).append('\n');
+ return sb.toString();
+ }
+
private static String getManifestValue(String attrib, String defaultValue) {
Class<?> clazz = Constants.class;
String className = clazz.getSimpleName() + ".class";
@@ -167,6 +191,19 @@ public class Constants { return defaultValue;
}
+ public static enum Role {
+ NONE, ADMIN, CREATE, FORK, NOT_FEDERATED;
+
+ public String getRole() {
+ return "#" + name().replace("_", "").toLowerCase();
+ }
+
+ @Override
+ public String toString() {
+ return getRole();
+ }
+ }
+
/**
* Enumeration representing the four access restriction levels.
*/
diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java deleted file mode 100644 index dd7e1b2b..00000000 --- a/src/main/java/com/gitblit/DaggerModule.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import javax.inject.Singleton; - -import com.gitblit.manager.AuthenticationManager; -import com.gitblit.manager.FederationManager; -import com.gitblit.manager.IAuthenticationManager; -import com.gitblit.manager.IFederationManager; -import com.gitblit.manager.IGitblit; -import com.gitblit.manager.INotificationManager; -import com.gitblit.manager.IPluginManager; -import com.gitblit.manager.IProjectManager; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.IUserManager; -import com.gitblit.manager.NotificationManager; -import com.gitblit.manager.PluginManager; -import com.gitblit.manager.ProjectManager; -import com.gitblit.manager.RepositoryManager; -import com.gitblit.manager.RuntimeManager; -import com.gitblit.manager.UserManager; -import com.gitblit.transport.ssh.FileKeyManager; -import com.gitblit.transport.ssh.IPublicKeyManager; -import com.gitblit.transport.ssh.MemoryKeyManager; -import com.gitblit.transport.ssh.NullKeyManager; -import com.gitblit.utils.JSoupXssFilter; -import com.gitblit.utils.StringUtils; -import com.gitblit.utils.XssFilter; -import com.gitblit.wicket.GitBlitWebApp; - -import dagger.Module; -import dagger.Provides; - -/** - * DaggerModule references all injectable objects. - * - * @author James Moger - * - */ -@Module( - library = true, - injects = { - IStoredSettings.class, - XssFilter.class, - - // core managers - IRuntimeManager.class, - IPluginManager.class, - INotificationManager.class, - IUserManager.class, - IAuthenticationManager.class, - IPublicKeyManager.class, - IRepositoryManager.class, - IProjectManager.class, - IFederationManager.class, - - // the monolithic manager - IGitblit.class, - - // the Gitblit Wicket app - GitBlitWebApp.class - } -) -public class DaggerModule { - - @Provides @Singleton IStoredSettings provideSettings() { - return new FileSettings(); - } - - @Provides @Singleton XssFilter provideXssFilter() { - return new JSoupXssFilter(); - } - - @Provides @Singleton IRuntimeManager provideRuntimeManager(IStoredSettings settings, XssFilter xssFilter) { - return new RuntimeManager(settings, xssFilter); - } - - @Provides @Singleton IPluginManager providePluginManager(IRuntimeManager runtimeManager) { - return new PluginManager(runtimeManager); - } - - @Provides @Singleton INotificationManager provideNotificationManager(IStoredSettings settings) { - return new NotificationManager(settings); - } - - @Provides @Singleton IUserManager provideUserManager( - IRuntimeManager runtimeManager, - IPluginManager pluginManager) { - - return new UserManager(runtimeManager, pluginManager); - } - - @Provides @Singleton IAuthenticationManager provideAuthenticationManager( - IRuntimeManager runtimeManager, - IUserManager userManager) { - - return new AuthenticationManager( - runtimeManager, - userManager); - } - - @Provides @Singleton IPublicKeyManager providePublicKeyManager( - IStoredSettings settings, - IRuntimeManager runtimeManager) { - - String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName()); - if (StringUtils.isEmpty(clazz)) { - clazz = FileKeyManager.class.getName(); - } - if (FileKeyManager.class.getName().equals(clazz)) { - return new FileKeyManager(runtimeManager); - } else if (NullKeyManager.class.getName().equals(clazz)) { - return new NullKeyManager(); - } else if (MemoryKeyManager.class.getName().equals(clazz)) { - return new MemoryKeyManager(); - } else { - try { - Class<?> mgrClass = Class.forName(clazz); - return (IPublicKeyManager) mgrClass.newInstance(); - } catch (Exception e) { - - } - return null; - } - } - - @Provides @Singleton IRepositoryManager provideRepositoryManager( - IRuntimeManager runtimeManager, - IPluginManager pluginManager, - IUserManager userManager) { - - return new RepositoryManager( - runtimeManager, - pluginManager, - userManager); - } - - @Provides @Singleton IProjectManager provideProjectManager( - IRuntimeManager runtimeManager, - IUserManager userManager, - IRepositoryManager repositoryManager) { - - return new ProjectManager( - runtimeManager, - userManager, - repositoryManager); - } - - @Provides @Singleton IFederationManager provideFederationManager( - IRuntimeManager runtimeManager, - INotificationManager notificationManager, - IRepositoryManager repositoryManager) { - - return new FederationManager( - runtimeManager, - notificationManager, - repositoryManager); - } - - @Provides @Singleton IGitblit provideGitblit( - IRuntimeManager runtimeManager, - IPluginManager pluginManager, - INotificationManager notificationManager, - IUserManager userManager, - IAuthenticationManager authenticationManager, - IPublicKeyManager publicKeyManager, - IRepositoryManager repositoryManager, - IProjectManager projectManager, - IFederationManager federationManager) { - - return new GitBlit( - runtimeManager, - pluginManager, - notificationManager, - userManager, - authenticationManager, - publicKeyManager, - repositoryManager, - projectManager, - federationManager); - } - - @Provides @Singleton GitBlitWebApp provideWebApplication( - IRuntimeManager runtimeManager, - IPluginManager pluginManager, - INotificationManager notificationManager, - IUserManager userManager, - IAuthenticationManager authenticationManager, - IPublicKeyManager publicKeyManager, - IRepositoryManager repositoryManager, - IProjectManager projectManager, - IFederationManager federationManager, - IGitblit gitblit) { - - return new GitBlitWebApp( - runtimeManager, - pluginManager, - notificationManager, - userManager, - authenticationManager, - publicKeyManager, - repositoryManager, - projectManager, - federationManager, - gitblit); - } -}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java index 079355ef..64ff0172 100644 --- a/src/main/java/com/gitblit/FederationClient.java +++ b/src/main/java/com/gitblit/FederationClient.java @@ -1,192 +1,192 @@ -/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.kohsuke.args4j.CmdLineException;
-import org.kohsuke.args4j.CmdLineParser;
-import org.kohsuke.args4j.Option;
-
-import com.gitblit.manager.FederationManager;
-import com.gitblit.manager.GitblitManager;
-import com.gitblit.manager.IGitblit;
-import com.gitblit.manager.INotificationManager;
-import com.gitblit.manager.RepositoryManager;
-import com.gitblit.manager.RuntimeManager;
-import com.gitblit.manager.UserManager;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.Mailing;
-import com.gitblit.service.FederationPullService;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.XssFilter;
-import com.gitblit.utils.XssFilter.AllowXssFilter;
-
-/**
- * Command-line client to pull federated Gitblit repositories.
- *
- * @author James Moger
- *
- */
-public class FederationClient {
-
- public static void main(String[] args) {
- Params params = new Params();
- CmdLineParser parser = new CmdLineParser(params);
- try {
- parser.parseArgument(args);
- } catch (CmdLineException t) {
- usage(parser, t);
- }
-
- System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
-
- // command-line specified base folder
- File baseFolder = new File(System.getProperty("user.dir"));
- if (!StringUtils.isEmpty(params.baseFolder)) {
- baseFolder = new File(params.baseFolder);
- }
-
- File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
- FileSettings settings = new FileSettings(regFile.getAbsolutePath());
- List<FederationModel> registrations = new ArrayList<FederationModel>();
- if (StringUtils.isEmpty(params.url)) {
- registrations.addAll(FederationUtils.getFederationRegistrations(settings));
- } else {
- if (StringUtils.isEmpty(params.token)) {
- System.out.println("Must specify --token parameter!");
- System.exit(0);
- }
- FederationModel model = new FederationModel("Gitblit");
- model.url = params.url;
- model.token = params.token;
- model.mirror = params.mirror;
- model.bare = params.bare;
- model.folder = "";
- registrations.add(model);
- }
- if (registrations.size() == 0) {
- System.out.println("No Federation Registrations! Nothing to do.");
- System.exit(0);
- }
-
- // command-line specified repositories folder
- if (!StringUtils.isEmpty(params.repositoriesFolder)) {
- settings.overrideSetting(Keys.git.repositoriesFolder, new File(
- params.repositoriesFolder).getAbsolutePath());
- }
-
- // configure the Gitblit singleton for minimal, non-server operation
- XssFilter xssFilter = new AllowXssFilter();
- RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start();
- NoopNotificationManager notifications = new NoopNotificationManager().start();
- UserManager users = new UserManager(runtime, null).start();
- RepositoryManager repositories = new RepositoryManager(runtime, null, users).start();
- FederationManager federation = new FederationManager(runtime, notifications, repositories).start();
- IGitblit gitblit = new GitblitManager(runtime, null, notifications, users, null, null, repositories, null, federation);
-
- FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) {
- @Override
- public void reschedule(FederationModel registration) {
- // NOOP
- }
- };
- puller.run();
-
- System.out.println("Finished.");
- System.exit(0);
- }
-
- private static void usage(CmdLineParser parser, CmdLineException t) {
- System.out.println(Constants.getGitBlitVersion());
- System.out.println();
- if (t != null) {
- System.out.println(t.getMessage());
- System.out.println();
- }
-
- if (parser != null) {
- parser.printUsage(System.out);
- }
- System.exit(0);
- }
-
- /**
- * Parameters class for FederationClient.
- */
- private static class Params {
-
- @Option(name = "--registrations", usage = "Gitblit Federation Registrations File", metaVar = "FILE")
- public String registrationsFile = "${baseFolder}/federation.properties";
-
- @Option(name = "--url", usage = "URL of Gitblit instance to mirror from", metaVar = "URL")
- public String url;
-
- @Option(name = "--mirror", usage = "Mirror repositories")
- public boolean mirror;
-
- @Option(name = "--bare", usage = "Create bare repositories")
- public boolean bare;
-
- @Option(name = "--token", usage = "Federation Token", metaVar = "TOKEN")
- public String token;
-
- @Option(name = "--baseFolder", usage = "Base folder for received data", metaVar = "PATH")
- public String baseFolder;
-
- @Option(name = "--repositoriesFolder", usage = "Destination folder for cloned repositories", metaVar = "PATH")
- public String repositoriesFolder;
-
- }
-
- private static class NoopNotificationManager implements INotificationManager {
-
- @Override
- public NoopNotificationManager start() {
- return this;
- }
-
- @Override
- public NoopNotificationManager stop() {
- return this;
- }
-
- @Override
- public boolean isSendingMail() {
- return false;
- }
-
- @Override
- public void sendMailToAdministrators(String subject, String message) {
- }
-
- @Override
- public void sendMail(String subject, String message, Collection<String> toAddresses) {
- }
-
- @Override
- public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
- }
-
- @Override
- public void send(Mailing mailing) {
- }
- }
-}
+/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.Option; + +import com.gitblit.manager.FederationManager; +import com.gitblit.manager.GitblitManager; +import com.gitblit.manager.IGitblit; +import com.gitblit.manager.INotificationManager; +import com.gitblit.manager.RepositoryManager; +import com.gitblit.manager.RuntimeManager; +import com.gitblit.manager.UserManager; +import com.gitblit.models.FederationModel; +import com.gitblit.models.Mailing; +import com.gitblit.service.FederationPullService; +import com.gitblit.utils.FederationUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.XssFilter; +import com.gitblit.utils.XssFilter.AllowXssFilter; + +/** + * Command-line client to pull federated Gitblit repositories. + * + * @author James Moger + * + */ +public class FederationClient { + + public static void main(String[] args) { + Params params = new Params(); + CmdLineParser parser = new CmdLineParser(params); + try { + parser.parseArgument(args); + } catch (CmdLineException t) { + usage(parser, t); + } + + System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")"); + + // command-line specified base folder + File baseFolder = new File(System.getProperty("user.dir")); + if (!StringUtils.isEmpty(params.baseFolder)) { + baseFolder = new File(params.baseFolder); + } + + File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile); + FileSettings settings = new FileSettings(regFile.getAbsolutePath()); + List<FederationModel> registrations = new ArrayList<FederationModel>(); + if (StringUtils.isEmpty(params.url)) { + registrations.addAll(FederationUtils.getFederationRegistrations(settings)); + } else { + if (StringUtils.isEmpty(params.token)) { + System.out.println("Must specify --token parameter!"); + System.exit(0); + } + FederationModel model = new FederationModel("Gitblit"); + model.url = params.url; + model.token = params.token; + model.mirror = params.mirror; + model.bare = params.bare; + model.folder = ""; + registrations.add(model); + } + if (registrations.size() == 0) { + System.out.println("No Federation Registrations! Nothing to do."); + System.exit(0); + } + + // command-line specified repositories folder + if (!StringUtils.isEmpty(params.repositoriesFolder)) { + settings.overrideSetting(Keys.git.repositoriesFolder, new File( + params.repositoriesFolder).getAbsolutePath()); + } + + // configure the Gitblit singleton for minimal, non-server operation + XssFilter xssFilter = new AllowXssFilter(); + RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start(); + NoopNotificationManager notifications = new NoopNotificationManager().start(); + UserManager users = new UserManager(runtime, null).start(); + RepositoryManager repositories = new RepositoryManager(runtime, null, users).start(); + FederationManager federation = new FederationManager(runtime, notifications, repositories).start(); + IGitblit gitblit = new GitblitManager(null, null, runtime, null, notifications, users, null, repositories, null, federation, null); + + FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) { + @Override + public void reschedule(FederationModel registration) { + // NOOP + } + }; + puller.run(); + + System.out.println("Finished."); + System.exit(0); + } + + private static void usage(CmdLineParser parser, CmdLineException t) { + System.out.println(Constants.getGitBlitVersion()); + System.out.println(); + if (t != null) { + System.out.println(t.getMessage()); + System.out.println(); + } + + if (parser != null) { + parser.printUsage(System.out); + } + System.exit(0); + } + + /** + * Parameters class for FederationClient. + */ + private static class Params { + + @Option(name = "--registrations", usage = "Gitblit Federation Registrations File", metaVar = "FILE") + public String registrationsFile = "${baseFolder}/federation.properties"; + + @Option(name = "--url", usage = "URL of Gitblit instance to mirror from", metaVar = "URL") + public String url; + + @Option(name = "--mirror", usage = "Mirror repositories") + public boolean mirror; + + @Option(name = "--bare", usage = "Create bare repositories") + public boolean bare; + + @Option(name = "--token", usage = "Federation Token", metaVar = "TOKEN") + public String token; + + @Option(name = "--baseFolder", usage = "Base folder for received data", metaVar = "PATH") + public String baseFolder; + + @Option(name = "--repositoriesFolder", usage = "Destination folder for cloned repositories", metaVar = "PATH") + public String repositoriesFolder; + + } + + private static class NoopNotificationManager implements INotificationManager { + + @Override + public NoopNotificationManager start() { + return this; + } + + @Override + public NoopNotificationManager stop() { + return this; + } + + @Override + public boolean isSendingMail() { + return false; + } + + @Override + public void sendMailToAdministrators(String subject, String message) { + } + + @Override + public void sendMail(String subject, String message, Collection<String> toAddresses) { + } + + @Override + public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) { + } + + @Override + public void send(Mailing mailing) { + } + } +} diff --git a/src/main/java/com/gitblit/FileSettings.java b/src/main/java/com/gitblit/FileSettings.java index 21a20435..3caf966b 100644 --- a/src/main/java/com/gitblit/FileSettings.java +++ b/src/main/java/com/gitblit/FileSettings.java @@ -18,10 +18,13 @@ package com.gitblit; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.StringUtils;
/**
* Dynamically loads and reloads a properties file by keeping track of the last
@@ -77,10 +80,14 @@ public class FileSettings extends IStoredSettings { if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
FileInputStream is = null;
try {
+ logger.debug("loading {}", propertiesFile);
Properties props = new Properties();
is = new FileInputStream(propertiesFile);
props.load(is);
+ // ticket-110
+ props = readIncludes(props);
+
// load properties after we have successfully read file
properties.clear();
properties.putAll(props);
@@ -103,12 +110,68 @@ public class FileSettings extends IStoredSettings { return properties;
}
+ /**
+ * Recursively read "include" properties files.
+ *
+ * @param properties
+ * @return
+ * @throws IOException
+ */
+ private Properties readIncludes(Properties properties) throws IOException {
+
+ Properties baseProperties = new Properties();
+
+ String include = (String) properties.remove("include");
+ if (!StringUtils.isEmpty(include)) {
+
+ // allow for multiples
+ List<String> names = StringUtils.getStringsFromValue(include, ",");
+ for (String name : names) {
+
+ if (StringUtils.isEmpty(name)) {
+ continue;
+ }
+
+ // try co-located
+ File file = new File(propertiesFile.getParentFile(), name.trim());
+ if (!file.exists()) {
+ // try absolute path
+ file = new File(name.trim());
+ }
+
+ if (!file.exists()) {
+ logger.warn("failed to locate {}", file);
+ continue;
+ }
+
+ // load properties
+ logger.debug("loading {}", file);
+ try (FileInputStream iis = new FileInputStream(file)) {
+ baseProperties.load(iis);
+ }
+
+ // read nested includes
+ baseProperties = readIncludes(baseProperties);
+
+ }
+
+ }
+
+ // includes are "default" properties, they must be set first and the
+ // props which specified the "includes" must override
+ Properties merged = new Properties();
+ merged.putAll(baseProperties);
+ merged.putAll(properties);
+
+ return merged;
+ }
+
@Override
public boolean saveSettings() {
String content = FileUtils.readContent(propertiesFile, "\n");
for (String key : removals) {
String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)"
- + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+ + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
content = content.replaceAll(regex, "");
}
removals.clear();
@@ -128,7 +191,7 @@ public class FileSettings extends IStoredSettings { String content = FileUtils.readContent(propertiesFile, "\n");
for (Map.Entry<String, String> setting:settings.entrySet()) {
String regex = "(?m)^(" + regExEscape(setting.getKey()) + "\\s*+=\\s*+)"
- + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+ + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
String oldContent = content;
content = content.replaceAll(regex, setting.getKey() + " = " + setting.getValue());
if (content.equals(oldContent)) {
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index f9d9be9f..4e25d5c6 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -15,471 +15,58 @@ */ package com.gitblit; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javax.inject.Singleton; -import javax.servlet.http.HttpServletRequest; - -import com.gitblit.Constants.AccessPermission; -import com.gitblit.Constants.Transport; import com.gitblit.manager.GitblitManager; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; -import com.gitblit.manager.IGitblit; +import com.gitblit.manager.IFilestoreManager; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.IUserManager; -import com.gitblit.manager.ServicesManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.RepositoryUrl; -import com.gitblit.models.UserModel; -import com.gitblit.tickets.BranchTicketService; -import com.gitblit.tickets.FileTicketService; import com.gitblit.tickets.ITicketService; -import com.gitblit.tickets.NullTicketService; -import com.gitblit.tickets.RedisTicketService; import com.gitblit.transport.ssh.IPublicKeyManager; -import com.gitblit.utils.StringUtils; - -import dagger.Module; -import dagger.ObjectGraph; -import dagger.Provides; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; /** - * GitBlit is the aggregate manager for the Gitblit webapp. It provides all - * management functions and also manages some long-running services. + * GitBlit is the aggregate manager for the Gitblit webapp. The parent class provides all + * functionality. This class exists to not break existing Groovy push hooks. * * @author James Moger * */ +@Singleton +@Deprecated public class GitBlit extends GitblitManager { - private final ObjectGraph injector; - - private final ServicesManager servicesManager; - - private ITicketService ticketService; - + @Inject public GitBlit( + Provider<IPublicKeyManager> publicKeyManagerProvider, + Provider<ITicketService> ticketServiceProvider, IRuntimeManager runtimeManager, IPluginManager pluginManager, INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, - IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, - IFederationManager federationManager) { + IFederationManager federationManager, + IFilestoreManager filestoreManager) { - super(runtimeManager, + super( + publicKeyManagerProvider, + ticketServiceProvider, + runtimeManager, pluginManager, notificationManager, userManager, authenticationManager, - publicKeyManager, repositoryManager, projectManager, - federationManager); - - this.injector = ObjectGraph.create(getModules()); - - this.servicesManager = new ServicesManager(this); - } - - @Override - public GitBlit start() { - super.start(); - logger.info("Starting services manager..."); - servicesManager.start(); - configureTicketService(); - return this; - } - - @Override - public GitBlit stop() { - super.stop(); - servicesManager.stop(); - ticketService.stop(); - return this; - } - - @Override - public boolean isServingRepositories() { - return servicesManager.isServingRepositories(); - } - - @Override - public boolean isServingHTTP() { - return servicesManager.isServingHTTP(); - } - - @Override - public boolean isServingGIT() { - return servicesManager.isServingGIT(); - } - - @Override - public boolean isServingSSH() { - return servicesManager.isServingSSH(); - } - - protected Object [] getModules() { - return new Object [] { new GitBlitModule()}; - } - - protected boolean acceptPush(Transport byTransport) { - if (byTransport == null) { - logger.info("Unknown transport, push rejected!"); - return false; - } - - Set<Transport> transports = new HashSet<Transport>(); - for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) { - Transport transport = Transport.fromString(value); - if (transport == null) { - logger.info(String.format("Ignoring unknown registered transport %s", value)); - continue; - } - - transports.add(transport); - } - - if (transports.isEmpty()) { - // no transports are explicitly specified, all are acceptable - return true; - } - - // verify that the transport is permitted - return transports.contains(byTransport); - } - - /** - * Returns a list of repository URLs and the user access permission. - * - * @param request - * @param user - * @param repository - * @return a list of repository urls - */ - @Override - public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { - if (user == null) { - user = UserModel.ANONYMOUS; - } - String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); - - List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); - - // http/https url - if (settings.getBoolean(Keys.git.enableGitServlet, true)) { - AccessPermission permission = user.getRepositoryPermission(repository).permission; - if (permission.exceeds(AccessPermission.NONE)) { - Transport transport = Transport.fromString(request.getScheme()); - if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) { - // downgrade the repo permission for this transport - // because it is not an acceptable PUSH transport - permission = AccessPermission.CLONE; - } - list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission)); - } - } - - // ssh daemon url - String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository); - if (!StringUtils.isEmpty(sshDaemonUrl)) { - AccessPermission permission = user.getRepositoryPermission(repository).permission; - if (permission.exceeds(AccessPermission.NONE)) { - if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) { - // downgrade the repo permission for this transport - // because it is not an acceptable PUSH transport - permission = AccessPermission.CLONE; - } - - list.add(new RepositoryUrl(sshDaemonUrl, permission)); - } - } - - // git daemon url - String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository); - if (!StringUtils.isEmpty(gitDaemonUrl)) { - AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository); - if (permission.exceeds(AccessPermission.NONE)) { - if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) { - // downgrade the repo permission for this transport - // because it is not an acceptable PUSH transport - permission = AccessPermission.CLONE; - } - list.add(new RepositoryUrl(gitDaemonUrl, permission)); - } - } - - // add all other urls - // {0} = repository - // {1} = username - for (String url : settings.getStrings(Keys.web.otherUrls)) { - if (url.contains("{1}")) { - // external url requires username, only add url IF we have one - if (!StringUtils.isEmpty(username)) { - list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null)); - } - } else { - // external url does not require username - list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null)); - } - } - - // sort transports by highest permission and then by transport security - Collections.sort(list, new Comparator<RepositoryUrl>() { - - @Override - public int compare(RepositoryUrl o1, RepositoryUrl o2) { - if (!o1.isExternal() && o2.isExternal()) { - // prefer Gitblit over external - return -1; - } else if (o1.isExternal() && !o2.isExternal()) { - // prefer Gitblit over external - return 1; - } else if (o1.isExternal() && o2.isExternal()) { - // sort by Transport ordinal - return o1.transport.compareTo(o2.transport); - } else if (o1.permission.exceeds(o2.permission)) { - // prefer highest permission - return -1; - } else if (o2.permission.exceeds(o1.permission)) { - // prefer highest permission - return 1; - } - - // prefer more secure transports - return o1.transport.compareTo(o2.transport); - } - }); - - // consider the user's transport preference - RepositoryUrl preferredUrl = null; - Transport preferredTransport = user.getPreferences().getTransport(); - if (preferredTransport != null) { - Iterator<RepositoryUrl> itr = list.iterator(); - while (itr.hasNext()) { - RepositoryUrl url = itr.next(); - if (url.transport.equals(preferredTransport)) { - itr.remove(); - preferredUrl = url; - break; - } - } - } - if (preferredUrl != null) { - list.add(0, preferredUrl); - } - - return list; - } - - /** - * Detect renames and reindex as appropriate. - */ - @Override - public void updateRepositoryModel(String repositoryName, RepositoryModel repository, - boolean isCreate) throws GitBlitException { - RepositoryModel oldModel = null; - boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name); - if (isRename) { - oldModel = repositoryManager.getRepositoryModel(repositoryName); - } - - super.updateRepositoryModel(repositoryName, repository, isCreate); - - if (isRename && ticketService != null) { - ticketService.rename(oldModel, repository); - } - } - - /** - * Delete the user and all associated public ssh keys. - */ - @Override - public boolean deleteUser(String username) { - UserModel user = userManager.getUserModel(username); - return deleteUserModel(user); - } - - @Override - public boolean deleteUserModel(UserModel model) { - boolean success = userManager.deleteUserModel(model); - if (success) { - getPublicKeyManager().removeAllKeys(model.username); - } - return success; - } - - /** - * Delete the repository and all associated tickets. - */ - @Override - public boolean deleteRepository(String repositoryName) { - RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName); - return deleteRepositoryModel(repository); - } - - @Override - public boolean deleteRepositoryModel(RepositoryModel model) { - boolean success = repositoryManager.deleteRepositoryModel(model); - if (success && ticketService != null) { - ticketService.deleteAll(model); - } - return success; - } - - /** - * Returns the configured ticket service. - * - * @return a ticket service - */ - @Override - public ITicketService getTicketService() { - return ticketService; - } - - protected void configureTicketService() { - String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName()); - if (StringUtils.isEmpty(clazz)) { - clazz = NullTicketService.class.getName(); - } - try { - Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz); - ticketService = injector.get(serviceClass).start(); - if (ticketService instanceof NullTicketService) { - logger.warn("No ticket service configured."); - } else if (ticketService.isReady()) { - logger.info("{} is ready.", ticketService); - } else { - logger.warn("{} is disabled.", ticketService); - } - } catch (Exception e) { - logger.error("failed to create ticket service " + clazz, e); - ticketService = injector.get(NullTicketService.class).start(); - } - } - - /** - * A nested Dagger graph is used for constructor dependency injection of - * complex classes. - * - * @author James Moger - * - */ - @Module( - library = true, - injects = { - IStoredSettings.class, - - // core managers - IRuntimeManager.class, - IPluginManager.class, - INotificationManager.class, - IUserManager.class, - IAuthenticationManager.class, - IRepositoryManager.class, - IProjectManager.class, - IFederationManager.class, - - // the monolithic manager - IGitblit.class, - - // ticket services - NullTicketService.class, - FileTicketService.class, - BranchTicketService.class, - RedisTicketService.class - } - ) - class GitBlitModule { - - @Provides @Singleton IStoredSettings provideSettings() { - return settings; - } - - @Provides @Singleton IRuntimeManager provideRuntimeManager() { - return runtimeManager; - } - - @Provides @Singleton IPluginManager providePluginManager() { - return pluginManager; - } - - @Provides @Singleton INotificationManager provideNotificationManager() { - return notificationManager; - } - - @Provides @Singleton IUserManager provideUserManager() { - return userManager; - } - - @Provides @Singleton IAuthenticationManager provideAuthenticationManager() { - return authenticationManager; - } - - @Provides @Singleton IRepositoryManager provideRepositoryManager() { - return repositoryManager; - } - - @Provides @Singleton IProjectManager provideProjectManager() { - return projectManager; - } - - @Provides @Singleton IFederationManager provideFederationManager() { - return federationManager; - } - - @Provides @Singleton IGitblit provideGitblit() { - return GitBlit.this; - } - - @Provides @Singleton NullTicketService provideNullTicketService() { - return new NullTicketService( - runtimeManager, - pluginManager, - notificationManager, - userManager, - repositoryManager); - } - - @Provides @Singleton FileTicketService provideFileTicketService() { - return new FileTicketService( - runtimeManager, - pluginManager, - notificationManager, - userManager, - repositoryManager); - } - - @Provides @Singleton BranchTicketService provideBranchTicketService() { - return new BranchTicketService( - runtimeManager, - pluginManager, - notificationManager, - userManager, - repositoryManager); - } - - @Provides @Singleton RedisTicketService provideRedisTicketService() { - return new RedisTicketService( - runtimeManager, - pluginManager, - notificationManager, - userManager, - repositoryManager); - } + federationManager, + filestoreManager); } } diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java index c79b1720..d56d9c0c 100644 --- a/src/main/java/com/gitblit/GitBlitServer.java +++ b/src/main/java/com/gitblit/GitBlitServer.java @@ -217,22 +217,7 @@ public class GitBlitServer { } logger = LoggerFactory.getLogger(GitBlitServer.class); - logger.info(Constants.BORDER); - logger.info(" _____ _ _ _ _ _ _"); - logger.info(" | __ \\(_)| | | | | |(_)| |"); - logger.info(" | | \\/ _ | |_ | |__ | | _ | |_"); - logger.info(" | | __ | || __|| '_ \\ | || || __|"); - logger.info(" | |_\\ \\| || |_ | |_) || || || |_"); - logger.info(" \\____/|_| \\__||_.__/ |_||_| \\__|"); - int spacing = (Constants.BORDER.length() - Constants.getGitBlitVersion().length()) / 2; - StringBuilder sb = new StringBuilder(); - while (spacing > 0) { - spacing--; - sb.append(' '); - } - logger.info(sb.toString() + Constants.getGitBlitVersion()); - logger.info(""); - logger.info(Constants.BORDER); + logger.info("\n" + Constants.getASCIIArt()); System.setProperty("java.awt.headless", "true"); @@ -524,22 +509,25 @@ public class GitBlitServer { @Override public void run() { - logger.info("Shutdown Monitor listening on port " + socket.getLocalPort()); - Socket accept; - try { - accept = socket.accept(); - BufferedReader reader = new BufferedReader(new InputStreamReader( - accept.getInputStream())); - reader.readLine(); - logger.info(Constants.BORDER); - logger.info("Stopping " + Constants.NAME); - logger.info(Constants.BORDER); - server.stop(); - server.setStopAtShutdown(false); - accept.close(); - socket.close(); - } catch (Exception e) { - logger.warn("Failed to shutdown Jetty", e); + // Only run if the socket was able to be created (not already in use, failed to bind, etc.) + if (null != socket) { + logger.info("Shutdown Monitor listening on port " + socket.getLocalPort()); + Socket accept; + try { + accept = socket.accept(); + BufferedReader reader = new BufferedReader(new InputStreamReader( + accept.getInputStream())); + reader.readLine(); + logger.info(Constants.BORDER); + logger.info("Stopping " + Constants.NAME); + logger.info(Constants.BORDER); + server.stop(); + server.setStopAtShutdown(false); + accept.close(); + socket.close(); + } catch (Exception e) { + logger.warn("Failed to shutdown Jetty", e); + } } } } diff --git a/src/main/java/com/gitblit/GravatarGenerator.java b/src/main/java/com/gitblit/GravatarGenerator.java new file mode 100644 index 00000000..1ba02e55 --- /dev/null +++ b/src/main/java/com/gitblit/GravatarGenerator.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit; + +import com.gitblit.utils.ActivityUtils; +import com.google.inject.Singleton; + +@Singleton +public class GravatarGenerator implements AvatarGenerator { + + @Override + public String getURL(String username, String emailaddress, boolean identicon, int width) { + String email = emailaddress == null ? username : emailaddress; + if (identicon) { + return ActivityUtils.getGravatarIdenticonUrl(email, width); + } else { + return ActivityUtils.getGravatarThumbnailUrl(email, width); + } + } + +} diff --git a/src/main/java/com/gitblit/auth/AuthenticationProvider.java b/src/main/java/com/gitblit/auth/AuthenticationProvider.java index f6511bad..c56c1843 100644 --- a/src/main/java/com/gitblit/auth/AuthenticationProvider.java +++ b/src/main/java/com/gitblit/auth/AuthenticationProvider.java @@ -22,6 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.IStoredSettings; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.IUserManager; @@ -151,6 +152,24 @@ public abstract class AuthenticationProvider { */ public abstract boolean supportsTeamMembershipChanges(); + /** + * Returns true if the user's role can be changed. + * + * @param user + * @param role + * @return true if the user's role can be changed + */ + public abstract boolean supportsRoleChanges(UserModel user, Role role); + + /** + * Returns true if the team's role can be changed. + * + * @param user + * @param role + * @return true if the team's role can be changed + */ + public abstract boolean supportsRoleChanges(TeamModel team, Role role); + @Override public String toString() { return getServiceName() + " (" + getClass().getName() + ")"; @@ -212,5 +231,16 @@ public abstract class AuthenticationProvider { public boolean supportsTeamMembershipChanges() { return true; } + + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } + } } diff --git a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java index 5ffb6930..2cdabf6f 100644 --- a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java +++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java @@ -32,8 +32,10 @@ import org.apache.commons.codec.digest.Md5Crypt; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; @@ -124,6 +126,16 @@ public class HtpasswdAuthProvider extends UsernamePasswordAuthenticationProvider return true; } + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } + /** * Authenticate a user based on a username and password. * diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java index 5690073a..cc772e7b 100644 --- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java +++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java @@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; import com.gitblit.models.TeamModel; @@ -272,7 +273,6 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider { return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, "")); } - /** * If the LDAP server will maintain team memberships then LdapUserService * will not allow team membership changes. In this scenario all team @@ -286,6 +286,32 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider { return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false); } + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + if (Role.ADMIN == role) { + if (!supportsTeamMembershipChanges()) { + List<String> admins = settings.getStrings(Keys.realm.ldap.admins); + if (admins.contains(user.username)) { + return false; + } + } + } + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + if (Role.ADMIN == role) { + if (!supportsTeamMembershipChanges()) { + List<String> admins = settings.getStrings(Keys.realm.ldap.admins); + if (admins.contains("@" + team.name)) { + return false; + } + } + } + return true; + } + @Override public AccountType getAccountType() { return AccountType.LDAP; @@ -591,7 +617,8 @@ public class LdapAuthProvider extends UsernamePasswordAuthenticationProvider { if (ldapSyncService.isReady()) { long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds(); int delay = 1; - logger.info("Ldap sync service will update users and groups every {} minutes.", ldapSyncPeriod); + logger.info("Ldap sync service will update users and groups every {} minutes.", + TimeUnit.MILLISECONDS.toMinutes(ldapSyncPeriod)); scheduledExecutorService.scheduleAtFixedRate(ldapSyncService, delay, ldapSyncPeriod, TimeUnit.MILLISECONDS); } else { logger.info("Ldap sync service is disabled."); diff --git a/src/main/java/com/gitblit/auth/PAMAuthProvider.java b/src/main/java/com/gitblit/auth/PAMAuthProvider.java index 5d441b84..46f4dd6a 100644 --- a/src/main/java/com/gitblit/auth/PAMAuthProvider.java +++ b/src/main/java/com/gitblit/auth/PAMAuthProvider.java @@ -23,8 +23,10 @@ import org.jvnet.libpam.impl.CLibrary; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; /** @@ -77,6 +79,16 @@ public class PAMAuthProvider extends UsernamePasswordAuthenticationProvider { return true; } + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } + @Override public AccountType getAccountType() { return AccountType.PAM; diff --git a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java index ae4f28ed..27cece29 100644 --- a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java +++ b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java @@ -23,8 +23,10 @@ import org.apache.commons.io.IOUtils; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ConnectionUtils; import com.gitblit.utils.StringUtils; @@ -77,6 +79,16 @@ public class RedmineAuthProvider extends UsernamePasswordAuthenticationProvider return false; } + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } + @Override public AccountType getAccountType() { return AccountType.REDMINE; @@ -154,7 +166,7 @@ public class RedmineAuthProvider extends UsernamePasswordAuthenticationProvider url = url.concat("/"); } String apiUrl = url + "users/current.json"; - + HttpURLConnection http; if (username == null) { // apikey authentication diff --git a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java index e4273ff6..df033c27 100644 --- a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java +++ b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java @@ -2,8 +2,10 @@ package com.gitblit.auth; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.sforce.soap.partner.Connector; import com.sforce.soap.partner.GetUserInfoResult; @@ -119,4 +121,15 @@ public class SalesforceAuthProvider extends UsernamePasswordAuthenticationProvid public boolean supportsTeamMembershipChanges() { return true; } + + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } + } diff --git a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java index ac15b28f..aee51008 100644 --- a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java +++ b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java @@ -26,8 +26,10 @@ import waffle.windows.auth.impl.WindowsAuthProviderImpl; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; +import com.gitblit.Constants.Role; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; import com.sun.jna.platform.win32.Win32Exception; @@ -90,6 +92,16 @@ public class WindowsAuthProvider extends UsernamePasswordAuthenticationProvider return true; } + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return true; + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return true; + } + @Override public AccountType getAccountType() { return AccountType.WINDOWS; diff --git a/src/main/java/com/gitblit/dagger/DaggerContext.java b/src/main/java/com/gitblit/dagger/DaggerContext.java deleted file mode 100644 index 0e6a3fc4..00000000 --- a/src/main/java/com/gitblit/dagger/DaggerContext.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit.dagger; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dagger.ObjectGraph; - -/** - * Dagger servlet context listener is a context listener that uses Dagger to - * instantiate and inject servlets, filters, and anything else you might want. - * - * @author James Moger - * - */ -public abstract class DaggerContext implements ServletContextListener { - - public static final String INJECTOR_NAME = ObjectGraph.class.getName(); - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - protected abstract Object [] getModules(); - - protected abstract void destroyContext(ServletContext context); - - protected ObjectGraph getInjector(ServletContext context) { - Object o = context.getAttribute(INJECTOR_NAME); - if (o == null) { - logger.debug("instantiating Dagger modules"); - Object [] modules = getModules(); - logger.debug("getting Dagger injector"); - try { - o = ObjectGraph.create(modules); - logger.debug("setting Dagger injector into {} attribute", INJECTOR_NAME); - context.setAttribute(INJECTOR_NAME, o); - } catch (Throwable t) { - logger.error("an error occurred creating the Dagger injector", t); - } - } - return (ObjectGraph) o; - } - - @Override - public final void contextDestroyed(ServletContextEvent contextEvent) { - ServletContext context = contextEvent.getServletContext(); - context.removeAttribute(INJECTOR_NAME); - destroyContext(context); - } -} diff --git a/src/main/java/com/gitblit/dagger/DaggerFilter.java b/src/main/java/com/gitblit/dagger/DaggerFilter.java deleted file mode 100644 index 01c07a4a..00000000 --- a/src/main/java/com/gitblit/dagger/DaggerFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a servlet filter.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerFilter implements Filter {
-
- @Override
- public final void init(FilterConfig filterConfig) throws ServletException {
- ServletContext context = filterConfig.getServletContext();
- ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- inject(objectGraph, filterConfig);
- }
-
- protected abstract void inject(ObjectGraph dagger, FilterConfig filterConfig) throws ServletException;
-
- @Override
- public void destroy() {
- }
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerServlet.java b/src/main/java/com/gitblit/dagger/DaggerServlet.java deleted file mode 100644 index 88331a43..00000000 --- a/src/main/java/com/gitblit/dagger/DaggerServlet.java +++ /dev/null @@ -1,44 +0,0 @@ -/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a servlet.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerServlet extends HttpServlet {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- public final void init() throws ServletException {
- ServletContext context = getServletContext();
- ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- inject(objectGraph);
- }
-
- protected abstract void inject(ObjectGraph dagger);
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java b/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java deleted file mode 100644 index c2fd4d67..00000000 --- a/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java +++ /dev/null @@ -1,45 +0,0 @@ -/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.dagger;
-
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-import org.apache.wicket.protocol.http.WicketFilter;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a Wicket filter.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerWicketFilter extends WicketFilter {
-
- @Override
- public final void init(FilterConfig filterConfig) throws ServletException {
- ServletContext context = filterConfig.getServletContext();
- ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- inject(objectGraph);
- super.init(filterConfig);
- }
-
- protected abstract void inject(ObjectGraph dagger);
-}
diff --git a/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java b/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java index 5ef03af7..e23fca78 100644 --- a/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java +++ b/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java @@ -36,6 +36,24 @@ public abstract class RepositoryLifeCycleListener implements ExtensionPoint { public abstract void onCreation(RepositoryModel repository); /** + * Called after a repository has been forked. + * + * @param origin + * @param fork + * @since 1.7.0 + */ + public abstract void onFork(RepositoryModel origin, RepositoryModel fork); + + /** + * Called after a repository has been renamed. + * + * @param oldName + * @param repository + * @since 1.7.0 + */ + public abstract void onRename(String oldName, RepositoryModel repository); + + /** * Called after a repository has been deleted. * * @param repository diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java index 54ffb7ba..ef0b409b 100644 --- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java +++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java @@ -667,7 +667,7 @@ public class PatchsetReceivePack extends GitblitReceivePack { // identified the missing object earlier before we got control.
LOGGER.error("failed to get commit count", e);
} finally {
- walk.release();
+ walk.close();
}
sendError("");
@@ -1078,7 +1078,7 @@ public class PatchsetReceivePack extends GitblitReceivePack { LOGGER.error("failed to get commit count", e);
return 0;
} finally {
- walk.release();
+ walk.close();
}
return count;
}
diff --git a/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java b/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java new file mode 100644 index 00000000..a18e2996 --- /dev/null +++ b/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.guice; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.AvatarGenerator; +import com.gitblit.GravatarGenerator; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +/** + * Provides a lazily-instantiated AvatarGenerator configured from IStoredSettings. + * + * @author James Moger + * + */ +@Singleton +public class AvatarGeneratorProvider implements Provider<AvatarGenerator> { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final IRuntimeManager runtimeManager; + + private volatile AvatarGenerator avatarGenerator; + + @Inject + public AvatarGeneratorProvider(IRuntimeManager runtimeManager) { + this.runtimeManager = runtimeManager; + } + + @Override + public synchronized AvatarGenerator get() { + if (avatarGenerator != null) { + return avatarGenerator; + } + + IStoredSettings settings = runtimeManager.getSettings(); + String clazz = settings.getString(Keys.web.avatarClass, GravatarGenerator.class.getName()); + if (StringUtils.isEmpty(clazz)) { + clazz = GravatarGenerator.class.getName(); + } + try { + Class<? extends AvatarGenerator> generatorClass = (Class<? extends AvatarGenerator>) Class.forName(clazz); + avatarGenerator = runtimeManager.getInjector().getInstance(generatorClass); + } catch (Exception e) { + logger.error("failed to create avatar generator", e); + avatarGenerator = new GravatarGenerator(); + } + return avatarGenerator; + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/guice/CoreModule.java b/src/main/java/com/gitblit/guice/CoreModule.java new file mode 100644 index 00000000..e2d14399 --- /dev/null +++ b/src/main/java/com/gitblit/guice/CoreModule.java @@ -0,0 +1,85 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.guice; + +import com.gitblit.FileSettings; +import com.gitblit.GitBlit; +import com.gitblit.IStoredSettings; +import com.gitblit.manager.AuthenticationManager; +import com.gitblit.manager.FederationManager; +import com.gitblit.manager.FilestoreManager; +import com.gitblit.manager.IAuthenticationManager; +import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IFilestoreManager; +import com.gitblit.manager.IGitblit; +import com.gitblit.manager.INotificationManager; +import com.gitblit.manager.IPluginManager; +import com.gitblit.manager.IProjectManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.manager.NotificationManager; +import com.gitblit.manager.PluginManager; +import com.gitblit.manager.ProjectManager; +import com.gitblit.manager.RepositoryManager; +import com.gitblit.manager.RuntimeManager; +import com.gitblit.manager.ServicesManager; +import com.gitblit.manager.UserManager; +import com.gitblit.tickets.ITicketService; +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.utils.JSoupXssFilter; +import com.gitblit.utils.WorkQueue; +import com.gitblit.utils.XssFilter; +import com.google.inject.AbstractModule; + +/** + * CoreModule references all the core business objects. + * + * @author James Moger + * + */ +public class CoreModule extends AbstractModule { + + @Override + protected void configure() { + + bind(IStoredSettings.class).toInstance(new FileSettings()); + bind(XssFilter.class).to(JSoupXssFilter.class); + + // bind complex providers + bind(IPublicKeyManager.class).toProvider(IPublicKeyManagerProvider.class); + bind(ITicketService.class).toProvider(ITicketServiceProvider.class); + bind(WorkQueue.class).toProvider(WorkQueueProvider.class); + + // core managers + bind(IRuntimeManager.class).to(RuntimeManager.class); + bind(IPluginManager.class).to(PluginManager.class); + bind(INotificationManager.class).to(NotificationManager.class); + bind(IUserManager.class).to(UserManager.class); + bind(IAuthenticationManager.class).to(AuthenticationManager.class); + bind(IRepositoryManager.class).to(RepositoryManager.class); + bind(IProjectManager.class).to(ProjectManager.class); + bind(IFederationManager.class).to(FederationManager.class); + bind(IFilestoreManager.class).to(FilestoreManager.class); + + // the monolithic manager + bind(IGitblit.class).to(GitBlit.class); + + // manager for long-running daemons and services + bind(IServicesManager.class).to(ServicesManager.class); + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java b/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java new file mode 100644 index 00000000..8075aa94 --- /dev/null +++ b/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.guice; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.transport.ssh.FileKeyManager; +import com.gitblit.transport.ssh.IPublicKeyManager; +import com.gitblit.transport.ssh.NullKeyManager; +import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +/** + * Provides a lazily-instantiated IPublicKeyManager configured from IStoredSettings. + * + * @author James Moger + * + */ +@Singleton +public class IPublicKeyManagerProvider implements Provider<IPublicKeyManager> { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final IRuntimeManager runtimeManager; + + private volatile IPublicKeyManager manager; + + @Inject + public IPublicKeyManagerProvider(IRuntimeManager runtimeManager) { + this.runtimeManager = runtimeManager; + } + + @Override + public synchronized IPublicKeyManager get() { + if (manager != null) { + return manager; + } + + IStoredSettings settings = runtimeManager.getSettings(); + String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName()); + if (StringUtils.isEmpty(clazz)) { + clazz = FileKeyManager.class.getName(); + } + try { + Class<? extends IPublicKeyManager> mgrClass = (Class<? extends IPublicKeyManager>) Class.forName(clazz); + manager = runtimeManager.getInjector().getInstance(mgrClass); + } catch (Exception e) { + logger.error("failed to create public key manager", e); + manager = new NullKeyManager(); + } + return manager; + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/guice/ITicketServiceProvider.java b/src/main/java/com/gitblit/guice/ITicketServiceProvider.java new file mode 100644 index 00000000..fd39955d --- /dev/null +++ b/src/main/java/com/gitblit/guice/ITicketServiceProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.guice; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.tickets.ITicketService; +import com.gitblit.tickets.NullTicketService; +import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +/** + * Provides a lazily-instantiated ITicketService configured from IStoredSettings. + * + * @author James Moger + * + */ +@Singleton +public class ITicketServiceProvider implements Provider<ITicketService> { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final IRuntimeManager runtimeManager; + + private volatile ITicketService service; + + @Inject + public ITicketServiceProvider(IRuntimeManager runtimeManager) { + this.runtimeManager = runtimeManager; + } + + @Override + public synchronized ITicketService get() { + if (service != null) { + return service; + } + + IStoredSettings settings = runtimeManager.getSettings(); + String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName()); + if (StringUtils.isEmpty(clazz)) { + clazz = NullTicketService.class.getName(); + } + + try { + Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz); + service = runtimeManager.getInjector().getInstance(serviceClass); + } catch (Exception e) { + logger.error("failed to create ticket service", e); + service = runtimeManager.getInjector().getInstance(NullTicketService.class); + } + + return service; + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/guice/WebModule.java b/src/main/java/com/gitblit/guice/WebModule.java new file mode 100644 index 00000000..7c83e455 --- /dev/null +++ b/src/main/java/com/gitblit/guice/WebModule.java @@ -0,0 +1,126 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.guice; + +import java.util.HashMap; +import java.util.Map; + +import com.gitblit.AvatarGenerator; +import com.gitblit.Constants; +import com.gitblit.servlet.AccessDeniedServlet; +import com.gitblit.servlet.BranchGraphServlet; +import com.gitblit.servlet.DownloadZipFilter; +import com.gitblit.servlet.DownloadZipServlet; +import com.gitblit.servlet.EnforceAuthenticationFilter; +import com.gitblit.servlet.FederationServlet; +import com.gitblit.servlet.FilestoreServlet; +import com.gitblit.servlet.GitFilter; +import com.gitblit.servlet.GitServlet; +import com.gitblit.servlet.LogoServlet; +import com.gitblit.servlet.PagesFilter; +import com.gitblit.servlet.PagesServlet; +import com.gitblit.servlet.ProxyFilter; +import com.gitblit.servlet.PtServlet; +import com.gitblit.servlet.RawFilter; +import com.gitblit.servlet.RawServlet; +import com.gitblit.servlet.RobotsTxtServlet; +import com.gitblit.servlet.RpcFilter; +import com.gitblit.servlet.RpcServlet; +import com.gitblit.servlet.SparkleShareInviteServlet; +import com.gitblit.servlet.SyndicationFilter; +import com.gitblit.servlet.SyndicationServlet; +import com.gitblit.wicket.GitblitWicketFilter; +import com.google.common.base.Joiner; +import com.google.inject.servlet.ServletModule; + +/** + * Defines all the web servlets & filters. + * + * @author James Moger + * + */ +public class WebModule extends ServletModule { + + final static String ALL = "/*"; + + @Override + protected void configureServlets() { + + // bind web component providers + bind(AvatarGenerator.class).toProvider(AvatarGeneratorProvider.class); + + // servlets + serveRegex(FilestoreServlet.REGEX_PATH).with(FilestoreServlet.class); + serve(fuzzy(Constants.R_PATH), fuzzy(Constants.GIT_PATH)).with(GitServlet.class); + serve(fuzzy(Constants.RAW_PATH)).with(RawServlet.class); + serve(fuzzy(Constants.PAGES)).with(PagesServlet.class); + serve(fuzzy(Constants.RPC_PATH)).with(RpcServlet.class); + serve(fuzzy(Constants.ZIP_PATH)).with(DownloadZipServlet.class); + serve(fuzzy(Constants.SYNDICATION_PATH)).with(SyndicationServlet.class); + + + serve(fuzzy(Constants.FEDERATION_PATH)).with(FederationServlet.class); + serve(fuzzy(Constants.SPARKLESHARE_INVITE_PATH)).with(SparkleShareInviteServlet.class); + serve(fuzzy(Constants.BRANCH_GRAPH_PATH)).with(BranchGraphServlet.class); + serve(Constants.PT_PATH).with(PtServlet.class); + serve("/robots.txt").with(RobotsTxtServlet.class); + serve("/logo.png").with(LogoServlet.class); + + /* Prevent accidental access to 'resources' such as GitBlit java classes + * + * In the GO setup the JAR containing the application and the WAR injected + * into Jetty are the same file. However Jetty expects to serve the entire WAR + * contents, except the WEB-INF folder. Thus, all java binary classes in the + * JAR are served by default as is they were legitimate resources. + * + * The below servlet mappings prevent that behavior + */ + serve(fuzzy("/com/")).with(AccessDeniedServlet.class); + + // global filters + filter(ALL).through(ProxyFilter.class); + filter(ALL).through(EnforceAuthenticationFilter.class); + + // security filters + filter(fuzzy(Constants.R_PATH), fuzzy(Constants.GIT_PATH)).through(GitFilter.class); + filter(fuzzy(Constants.RAW_PATH)).through(RawFilter.class); + filter(fuzzy(Constants.PAGES)).through(PagesFilter.class); + filter(fuzzy(Constants.RPC_PATH)).through(RpcFilter.class); + filter(fuzzy(Constants.ZIP_PATH)).through(DownloadZipFilter.class); + filter(fuzzy(Constants.SYNDICATION_PATH)).through(SyndicationFilter.class); + + + // Wicket + String toIgnore = Joiner.on(",").join(Constants.R_PATH, Constants.GIT_PATH, Constants.RAW_PATH, + Constants.PAGES, Constants.RPC_PATH, Constants.ZIP_PATH, Constants.SYNDICATION_PATH, + Constants.FEDERATION_PATH, Constants.SPARKLESHARE_INVITE_PATH, Constants.BRANCH_GRAPH_PATH, + Constants.PT_PATH, "/robots.txt", "/logo.png"); + + Map<String, String> params = new HashMap<String, String>(); + params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, ALL); + params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); + filter(ALL).through(GitblitWicketFilter.class, params); + } + + private String fuzzy(String path) { + if (path.endsWith(ALL)) { + return path; + } else if (path.endsWith("/")) { + return path + "*"; + } + return path + ALL; + } +} diff --git a/src/main/java/com/gitblit/guice/WorkQueueProvider.java b/src/main/java/com/gitblit/guice/WorkQueueProvider.java new file mode 100644 index 00000000..cde27ea9 --- /dev/null +++ b/src/main/java/com/gitblit/guice/WorkQueueProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.guice; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.utils.IdGenerator; +import com.gitblit.utils.WorkQueue; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +/** + * Provides a lazily-instantiated WorkQueue configured from IStoredSettings. + * + * @author James Moger + * + */ +@Singleton +public class WorkQueueProvider implements Provider<WorkQueue> { + + private final IRuntimeManager runtimeManager; + + private volatile WorkQueue workQueue; + + @Inject + public WorkQueueProvider(IRuntimeManager runtimeManager) { + this.runtimeManager = runtimeManager; + } + + @Override + public synchronized WorkQueue get() { + if (workQueue != null) { + return workQueue; + } + + IStoredSettings settings = runtimeManager.getSettings(); + int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1); + IdGenerator idGenerator = new IdGenerator(); + workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize); + return workQueue; + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java index f98f7b64..7e0b07be 100644 --- a/src/main/java/com/gitblit/manager/AuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java @@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants; import com.gitblit.Constants.AccountType; import com.gitblit.Constants.AuthenticationType; +import com.gitblit.Constants.Role; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.auth.AuthenticationProvider; @@ -52,6 +53,8 @@ import com.gitblit.utils.Base64; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; import com.gitblit.utils.X509Utils.X509Metadata; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * The authentication manager handles user login & logout. @@ -59,6 +62,7 @@ import com.gitblit.utils.X509Utils.X509Metadata; * @author James Moger * */ +@Singleton public class AuthenticationManager implements IAuthenticationManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -75,6 +79,7 @@ public class AuthenticationManager implements IAuthenticationManager { private final Map<String, String> legacyRedirects; + @Inject public AuthenticationManager( IRuntimeManager runtimeManager, IUserManager userManager) { @@ -189,6 +194,14 @@ public class AuthenticationManager implements IAuthenticationManager { */ @Override public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { + + // Check if this request has already been authenticated, and trust that instead of re-processing + String reqAuthUser = (String) httpRequest.getAttribute(Constants.ATTRIB_AUTHUSER); + if (!StringUtils.isEmpty(reqAuthUser)) { + logger.warn("Called servlet authenticate when request is already authenticated."); + return userManager.getUserModel(reqAuthUser); + } + // try to authenticate by servlet container principal if (!requiresCertificate) { Principal principal = httpRequest.getUserPrincipal(); @@ -199,7 +212,7 @@ public class AuthenticationManager implements IAuthenticationManager { UserModel user = userManager.getUserModel(username); if (user != null) { // existing user - flagSession(httpRequest, AuthenticationType.CONTAINER); + flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username); logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return validateAuthentication(user, AuthenticationType.CONTAINER); @@ -210,8 +223,31 @@ public class AuthenticationManager implements IAuthenticationManager { user.displayName = username; user.password = Constants.EXTERNAL_ACCOUNT; user.accountType = AccountType.CONTAINER; + + // Try to extract user's informations for the session + // it uses "realm.container.autoAccounts.*" as the attribute name to look for + HttpSession session = httpRequest.getSession(); + String emailAddress = resolveAttribute(session, Keys.realm.container.autoAccounts.emailAddress); + if(emailAddress != null) { + user.emailAddress = emailAddress; + } + String displayName = resolveAttribute(session, Keys.realm.container.autoAccounts.displayName); + if(displayName != null) { + user.displayName = displayName; + } + String userLocale = resolveAttribute(session, Keys.realm.container.autoAccounts.locale); + if(userLocale != null) { + user.getPreferences().setLocale(userLocale); + } + String adminRole = settings.getString(Keys.realm.container.autoAccounts.adminRole, null); + if(adminRole != null && ! adminRole.isEmpty()) { + if(httpRequest.isUserInRole(adminRole)) { + user.canAdmin = true; + } + } + userManager.updateUserModel(user); - flagSession(httpRequest, AuthenticationType.CONTAINER); + flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username); logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return validateAuthentication(user, AuthenticationType.CONTAINER); @@ -232,7 +268,7 @@ public class AuthenticationManager implements IAuthenticationManager { UserModel user = userManager.getUserModel(model.username); X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); if (user != null) { - flagSession(httpRequest, AuthenticationType.CERTIFICATE); + flagRequest(httpRequest, AuthenticationType.CERTIFICATE, user.username); logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}", user.username, metadata.serialNumber, httpRequest.getRemoteAddr())); return validateAuthentication(user, AuthenticationType.CERTIFICATE); @@ -254,7 +290,7 @@ public class AuthenticationManager implements IAuthenticationManager { if (!StringUtils.isEmpty(cookie)) { user = userManager.getUserModel(cookie.toCharArray()); if (user != null) { - flagSession(httpRequest, AuthenticationType.COOKIE); + flagRequest(httpRequest, AuthenticationType.COOKIE, user.username); logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", user.username, httpRequest.getRemoteAddr())); return validateAuthentication(user, AuthenticationType.COOKIE); @@ -274,20 +310,42 @@ public class AuthenticationManager implements IAuthenticationManager { if (values.length == 2) { String username = values[0]; char[] password = values[1].toCharArray(); - user = authenticate(username, password); + user = authenticate(username, password, httpRequest.getRemoteAddr()); if (user != null) { - flagSession(httpRequest, AuthenticationType.CREDENTIALS); + flagRequest(httpRequest, AuthenticationType.CREDENTIALS, user.username); logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}", user.username, httpRequest.getRemoteAddr())); return validateAuthentication(user, AuthenticationType.CREDENTIALS); - } else { - logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", - username, httpRequest.getRemoteAddr())); } } } return null; } + + /** + * Extract given attribute from the session and return it's content + * it return null if attributeMapping is empty, or if the value is + * empty + * + * @param session The user session + * @param attributeMapping + * @return + */ + private String resolveAttribute(HttpSession session, String attributeMapping) { + String attributeName = settings.getString(attributeMapping, null); + if(StringUtils.isEmpty(attributeName)) { + return null; + } + Object attributeValue = session.getAttribute(attributeName); + if(attributeValue == null) { + return null; + } + String value = attributeValue.toString(); + if(value.isEmpty()) { + return null; + } + return value; + } /** * Authenticate a user based on a public key. @@ -321,6 +379,35 @@ public class AuthenticationManager implements IAuthenticationManager { /** + * Return the UserModel for already authenticated user. + * + * This implementation assumes that the authentication has already take place + * (e.g. SSHDaemon) and that this is a validation/verification of the user. + * + * @param username + * @return a user object or null + */ + @Override + public UserModel authenticate(String username) { + if (username != null) { + if (!StringUtils.isEmpty(username)) { + UserModel user = userManager.getUserModel(username); + if (user != null) { + // existing user + logger.debug(MessageFormat.format("{0} authenticated externally", user.username)); + return validateAuthentication(user, AuthenticationType.CONTAINER); + } + logger.warn(MessageFormat.format("Failed to find UserModel for {0} during external authentication", + username)); + } + } else { + logger.warn("Empty user passed to AuthenticationManager.authenticate!"); + } + return null; + } + + + /** * This method allows the authentication manager to reject authentication * attempts. It is called after the username/secret have been verified to * ensure that the authentication technique has been logged. @@ -341,8 +428,9 @@ public class AuthenticationManager implements IAuthenticationManager { return user; } - protected void flagSession(HttpServletRequest httpRequest, AuthenticationType authenticationType) { - httpRequest.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, authenticationType); + protected void flagRequest(HttpServletRequest httpRequest, AuthenticationType authenticationType, String authedUsername) { + httpRequest.setAttribute(Constants.ATTRIB_AUTHUSER, authedUsername); + httpRequest.setAttribute(Constants.ATTRIB_AUTHTYPE, authenticationType); } /** @@ -354,7 +442,7 @@ public class AuthenticationManager implements IAuthenticationManager { * @return a user object or null */ @Override - public UserModel authenticate(String username, char[] password) { + public UserModel authenticate(String username, char[] password, String remoteIP) { if (StringUtils.isEmpty(username)) { // can not authenticate empty username return null; @@ -371,22 +459,29 @@ public class AuthenticationManager implements IAuthenticationManager { // try local authentication if (user != null && user.isLocalAccount()) { - return authenticateLocal(user, password); - } - - // try registered external authentication providers - for (AuthenticationProvider provider : authenticationProviders) { - if (provider instanceof UsernamePasswordAuthenticationProvider) { - UserModel returnedUser = provider.authenticate(usernameDecoded, password); - if (returnedUser != null) { - // user authenticated - returnedUser.accountType = provider.getAccountType(); - return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); + UserModel returnedUser = authenticateLocal(user, password); + if (returnedUser != null) { + // user authenticated + return returnedUser; + } + } else { + // try registered external authentication providers + for (AuthenticationProvider provider : authenticationProviders) { + if (provider instanceof UsernamePasswordAuthenticationProvider) { + UserModel returnedUser = provider.authenticate(usernameDecoded, password); + if (returnedUser != null) { + // user authenticated + returnedUser.accountType = provider.getAccountType(); + return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); + } } } } // could not authenticate locally or with a provider + logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", username, + remoteIP != null ? remoteIP : "unknown")); + return null; } @@ -463,9 +558,15 @@ public class AuthenticationManager implements IAuthenticationManager { @Override public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) { if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { - HttpSession session = request.getSession(); - AuthenticationType authenticationType = (AuthenticationType) session.getAttribute(Constants.AUTHENTICATION_TYPE); - boolean standardLogin = authenticationType.isStandard(); + boolean standardLogin = true; + + if (null != request) { + // Pull the auth type from the request, it is set there if container managed + AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE); + + if (null != authenticationType) + standardLogin = authenticationType.isStandard(); + } if (standardLogin) { Cookie userCookie; @@ -576,6 +677,28 @@ public class AuthenticationManager implements IAuthenticationManager { return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges(); } + /** + * Returns true if the user's role can be changed. + * + * @param user + * @return true if the user's role can be changed + */ + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return (user != null && user.isLocalAccount()) || findProvider(user).supportsRoleChanges(user, role); + } + + /** + * Returns true if the team's role can be changed. + * + * @param user + * @return true if the team's role can be changed + */ + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return (team != null && team.isLocalTeam()) || findProvider(team).supportsRoleChanges(team, role); + } + protected AuthenticationProvider findProvider(UserModel user) { for (AuthenticationProvider provider : authenticationProviders) { if (provider.getAccountType().equals(user.accountType)) { diff --git a/src/main/java/com/gitblit/manager/FederationManager.java b/src/main/java/com/gitblit/manager/FederationManager.java index 95d38af1..f009c1c8 100644 --- a/src/main/java/com/gitblit/manager/FederationManager.java +++ b/src/main/java/com/gitblit/manager/FederationManager.java @@ -45,6 +45,8 @@ import com.gitblit.utils.Base64; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Federation manager controls all aspects of handling federation sets, tokens, @@ -53,6 +55,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ +@Singleton public class FederationManager implements IFederationManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -70,6 +73,7 @@ public class FederationManager implements IFederationManager { private final IRepositoryManager repositoryManager; + @Inject public FederationManager( IRuntimeManager runtimeManager, INotificationManager notificationManager, diff --git a/src/main/java/com/gitblit/manager/FilestoreManager.java b/src/main/java/com/gitblit/manager/FilestoreManager.java new file mode 100644 index 00000000..c15de5b4 --- /dev/null +++ b/src/main/java/com/gitblit/manager/FilestoreManager.java @@ -0,0 +1,441 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.bouncycastle.util.io.StreamOverflowException; +import org.eclipse.jetty.io.EofException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.models.FilestoreModel.Status; +import com.gitblit.models.FilestoreModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.JsonUtils.GmtDateTypeAdapter; +import com.google.gson.ExclusionStrategy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * FilestoreManager handles files uploaded via: + * + git-lfs + * + ticket attachment (TBD) + * + * Files are stored using their SHA256 hash (as per git-lfs) + * If the same file is uploaded through different repositories no additional space is used + * Access is controlled through the current repository permissions. + * + * TODO: Identify what and how the actual BLOBs should work with federation + * + * @author Paul Martin + * + */ +@Singleton +public class FilestoreManager implements IFilestoreManager { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final IRuntimeManager runtimeManager; + + private final IStoredSettings settings; + + public static final int UNDEFINED_SIZE = -1; + + private static final String METAFILE = "filestore.json"; + + private static final String METAFILE_TMP = "filestore.json.tmp"; + + protected static final Type METAFILE_TYPE = new TypeToken<Collection<FilestoreModel>>() {}.getType(); + + private Map<String, FilestoreModel > fileCache = new ConcurrentHashMap<String, FilestoreModel>(); + + + @Inject + FilestoreManager( + IRuntimeManager runtimeManager) { + this.runtimeManager = runtimeManager; + this.settings = runtimeManager.getSettings(); + } + + @Override + public IManager start() { + + // Try to load any existing metadata + File dir = getStorageFolder(); + dir.mkdirs(); + File metadata = new File(dir, METAFILE); + + if (metadata.exists()) { + Collection<FilestoreModel> items = null; + + Gson gson = gson(); + try (FileReader file = new FileReader(metadata)) { + items = gson.fromJson(file, METAFILE_TYPE); + file.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + + for(Iterator<FilestoreModel> itr = items.iterator(); itr.hasNext(); ) { + FilestoreModel model = itr.next(); + fileCache.put(model.oid, model); + } + + logger.info("Loaded {} items from filestore metadata file", fileCache.size()); + } + else + { + logger.info("No filestore metadata file found"); + } + + return this; + } + + @Override + public IManager stop() { + return this; + } + + + @Override + public boolean isValidOid(String oid) { + //NOTE: Assuming SHA256 support only as per git-lfs + return Pattern.matches("[a-fA-F0-9]{64}", oid); + } + + @Override + public FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo) { + + //Handle access control + if (!user.canPush(repo)) { + if (user == UserModel.ANONYMOUS) { + return Status.AuthenticationRequired; + } else { + return Status.Error_Unauthorized; + } + } + + //Handle object details + if (!isValidOid(oid)) { return Status.Error_Invalid_Oid; } + + if (fileCache.containsKey(oid)) { + FilestoreModel item = fileCache.get(oid); + + if (!item.isInErrorState() && (size != UNDEFINED_SIZE) && (item.getSize() != size)) { + return Status.Error_Size_Mismatch; + } + + item.addRepository(repo.name); + + if (item.isInErrorState()) { + item.reset(user, size); + } + } else { + + if (size < 0) {return Status.Error_Invalid_Size; } + if ((getMaxUploadSize() != UNDEFINED_SIZE) && (size > getMaxUploadSize())) { return Status.Error_Exceeds_Size_Limit; } + + FilestoreModel model = new FilestoreModel(oid, size, user, repo.name); + fileCache.put(oid, model); + saveFilestoreModel(model); + } + + return fileCache.get(oid).getStatus(); + } + + @Override + public FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn) { + + //Access control and object logic + Status state = addObject(oid, size, user, repo); + + if (state != Status.Upload_Pending) { + return state; + } + + FilestoreModel model = fileCache.get(oid); + + if (!model.actionUpload(user)) { + return Status.Upload_In_Progress; + } else { + long actualSize = 0; + File file = getStoragePath(oid); + + try { + file.getParentFile().mkdirs(); + file.createNewFile(); + + try (FileOutputStream streamOut = new FileOutputStream(file)) { + + actualSize = IOUtils.copyLarge(streamIn, streamOut); + + streamOut.flush(); + streamOut.close(); + + if (model.getSize() != actualSize) { + model.setStatus(Status.Error_Size_Mismatch, user); + + logger.warn(MessageFormat.format("Failed to upload blob {0} due to size mismatch, expected {1} got {2}", + oid, model.getSize(), actualSize)); + } else { + String actualOid = ""; + + try (FileInputStream fileForHash = new FileInputStream(file)) { + actualOid = DigestUtils.sha256Hex(fileForHash); + fileForHash.close(); + } + + if (oid.equalsIgnoreCase(actualOid)) { + model.setStatus(Status.Available, user); + } else { + model.setStatus(Status.Error_Hash_Mismatch, user); + + logger.warn(MessageFormat.format("Failed to upload blob {0} due to hash mismatch, got {1}", oid, actualOid)); + } + } + } + } catch (Exception e) { + + model.setStatus(Status.Error_Unknown, user); + logger.warn(MessageFormat.format("Failed to upload blob {0}", oid), e); + } finally { + saveFilestoreModel(model); + } + + if (model.isInErrorState()) { + file.delete(); + model.removeRepository(repo.name); + } + } + + return model.getStatus(); + } + + private FilestoreModel.Status canGetObject(String oid, UserModel user, RepositoryModel repo) { + + //Access Control + if (!user.canView(repo)) { + if (user == UserModel.ANONYMOUS) { + return Status.AuthenticationRequired; + } else { + return Status.Error_Unauthorized; + } + } + + //Object Logic + if (!isValidOid(oid)) { + return Status.Error_Invalid_Oid; + } + + if (!fileCache.containsKey(oid)) { + return Status.Unavailable; + } + + FilestoreModel item = fileCache.get(oid); + + if (item.getStatus() == Status.Available) { + return Status.Available; + } + + return Status.Unavailable; + } + + @Override + public FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo) { + + if (canGetObject(oid, user, repo) == Status.Available) { + return fileCache.get(oid); + } + + return null; + } + + @Override + public FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut) { + + //Access control and object logic + Status status = canGetObject(oid, user, repo); + + if (status != Status.Available) { + return status; + } + + FilestoreModel item = fileCache.get(oid); + + if (streamOut != null) { + try (FileInputStream streamIn = new FileInputStream(getStoragePath(oid))) { + + IOUtils.copyLarge(streamIn, streamOut); + + streamOut.flush(); + streamIn.close(); + } catch (EofException e) { + logger.error(MessageFormat.format("Client aborted connection for {0}", oid), e); + return Status.Error_Unexpected_Stream_End; + } catch (Exception e) { + logger.error(MessageFormat.format("Failed to download blob {0}", oid), e); + return Status.Error_Unknown; + } + } + + return item.getStatus(); + } + + @Override + public List<FilestoreModel> getAllObjects() { + return new ArrayList<FilestoreModel>(fileCache.values()); + } + + @Override + public File getStorageFolder() { + return runtimeManager.getFileOrFolder(Keys.filestore.storageFolder, "${baseFolder}/lfs"); + } + + @Override + public File getStoragePath(String oid) { + return new File(getStorageFolder(), oid.substring(0, 2).concat("/").concat(oid.substring(2))); + } + + @Override + public long getMaxUploadSize() { + return settings.getLong(Keys.filestore.maxUploadSize, -1); + } + + @Override + public long getFilestoreUsedByteCount() { + Iterator<FilestoreModel> iterator = fileCache.values().iterator(); + long total = 0; + + while (iterator.hasNext()) { + + FilestoreModel item = iterator.next(); + if (item.getStatus() == Status.Available) { + total += item.getSize(); + } + } + + return total; + } + + @Override + public long getFilestoreAvailableByteCount() { + + try { + return Files.getFileStore(getStorageFolder().toPath()).getUsableSpace(); + } catch (IOException e) { + logger.error(MessageFormat.format("Failed to retrive available space in Filestore {0}", e)); + } + + return UNDEFINED_SIZE; + }; + + private synchronized void saveFilestoreModel(FilestoreModel model) { + + File metaFile = new File(getStorageFolder(), METAFILE); + File metaFileTmp = new File(getStorageFolder(), METAFILE_TMP); + boolean isNewFile = false; + + try { + if (!metaFile.exists()) { + metaFile.getParentFile().mkdirs(); + metaFile.createNewFile(); + isNewFile = true; + } + FileUtils.copyFile(metaFile, metaFileTmp); + + } catch (IOException e) { + logger.error("Writing filestore model to file {0}, {1}", METAFILE, e); + } + + try (RandomAccessFile fs = new RandomAccessFile(metaFileTmp, "rw")) { + + if (isNewFile) { + fs.writeBytes("["); + } else { + fs.seek(fs.length() - 1); + fs.writeBytes(","); + } + + fs.writeBytes(gson().toJson(model)); + fs.writeBytes("]"); + + fs.close(); + + } catch (IOException e) { + logger.error("Writing filestore model to file {0}, {1}", METAFILE_TMP, e); + } + + try { + if (metaFileTmp.exists()) { + FileUtils.copyFile(metaFileTmp, metaFile); + + metaFileTmp.delete(); + } else { + logger.error("Writing filestore model to file {0}", METAFILE); + } + } + catch (IOException e) { + logger.error("Writing filestore model to file {0}, {1}", METAFILE, e); + } + } + + /* + * Intended for testing purposes only + */ + public void clearFilestoreCache() { + fileCache.clear(); + } + + private static Gson gson(ExclusionStrategy... strategies) { + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter()); + if (!ArrayUtils.isEmpty(strategies)) { + builder.setExclusionStrategies(strategies); + } + return builder.create(); + } + +} diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java index 88fa804e..4a385fc1 100644 --- a/src/main/java/com/gitblit/manager/GitblitManager.java +++ b/src/main/java/com/gitblit/manager/GitblitManager.java @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.lang.reflect.Type; import java.text.MessageFormat; import java.util.ArrayList; @@ -49,15 +50,16 @@ import ro.fortsoft.pf4j.Version; import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; -import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.FederationRequest; import com.gitblit.Constants.FederationToken; +import com.gitblit.Constants.Role; import com.gitblit.GitBlitException; import com.gitblit.IStoredSettings; -import com.gitblit.Keys; +import com.gitblit.extensions.RepositoryLifeCycleListener; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; +import com.gitblit.models.FilestoreModel; import com.gitblit.models.ForkModel; import com.gitblit.models.GitClientApplication; import com.gitblit.models.Mailing; @@ -68,7 +70,6 @@ import com.gitblit.models.PluginRegistry.PluginRelease; import com.gitblit.models.ProjectModel; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.RepositoryModel; -import com.gitblit.models.RepositoryUrl; import com.gitblit.models.SearchResult; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; @@ -79,7 +80,6 @@ import com.gitblit.tickets.ITicketService; import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.transport.ssh.SshKey; import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; @@ -88,6 +88,10 @@ import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import com.google.inject.Singleton; /** * GitblitManager is an aggregate interface delegate. It implements all the manager @@ -101,12 +105,17 @@ import com.google.gson.reflect.TypeToken; * @author James Moger * */ +@Singleton public class GitblitManager implements IGitblit { protected final Logger logger = LoggerFactory.getLogger(getClass()); protected final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>(); + protected final Provider<IPublicKeyManager> publicKeyManagerProvider; + + protected final Provider<ITicketService> ticketServiceProvider; + protected final IStoredSettings settings; protected final IRuntimeManager runtimeManager; @@ -119,24 +128,30 @@ public class GitblitManager implements IGitblit { protected final IAuthenticationManager authenticationManager; - protected final IPublicKeyManager publicKeyManager; - protected final IRepositoryManager repositoryManager; protected final IProjectManager projectManager; protected final IFederationManager federationManager; + protected final IFilestoreManager filestoreManager; + + @Inject public GitblitManager( + Provider<IPublicKeyManager> publicKeyManagerProvider, + Provider<ITicketService> ticketServiceProvider, IRuntimeManager runtimeManager, IPluginManager pluginManager, INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, - IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, - IFederationManager federationManager) { + IFederationManager federationManager, + IFilestoreManager filestoreManager) { + + this.publicKeyManagerProvider = publicKeyManagerProvider; + this.ticketServiceProvider = ticketServiceProvider; this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; @@ -144,10 +159,10 @@ public class GitblitManager implements IGitblit { this.notificationManager = notificationManager; this.userManager = userManager; this.authenticationManager = authenticationManager; - this.publicKeyManager = publicKeyManager; this.repositoryManager = repositoryManager; this.projectManager = projectManager; this.federationManager = federationManager; + this.filestoreManager = filestoreManager; } @Override @@ -268,6 +283,16 @@ public class GitblitManager implements IGitblit { // add this clone to the cached model repositoryManager.addToCachedRepositoryList(cloneModel); + + if (pluginManager != null) { + for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) { + try { + listener.onFork(repository, cloneModel); + } catch (Throwable t) { + logger.error(String.format("failed to call plugin onFork %s", repository.name), t); + } + } + } return cloneModel; } @@ -358,66 +383,6 @@ public class GitblitManager implements IGitblit { } /** - * Returns a list of repository URLs and the user access permission. - * - * @param request - * @param user - * @param repository - * @return a list of repository urls - */ - @Override - public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { - if (user == null) { - user = UserModel.ANONYMOUS; - } - String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); - - List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); - // http/https url - if (settings.getBoolean(Keys.git.enableGitServlet, true)) { - AccessPermission permission = user.getRepositoryPermission(repository).permission; - if (permission.exceeds(AccessPermission.NONE)) { - list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission)); - } - } - - // add all other urls - // {0} = repository - // {1} = username - for (String url : settings.getStrings(Keys.web.otherUrls)) { - if (url.contains("{1}")) { - // external url requires username, only add url IF we have one - if (!StringUtils.isEmpty(username)) { - list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null)); - } - } else { - // external url does not require username - list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null)); - } - } - return list; - } - - protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) { - String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null); - if (StringUtils.isEmpty(gitblitUrl)) { - gitblitUrl = HttpUtils.getGitblitURL(request); - } - StringBuilder sb = new StringBuilder(); - sb.append(gitblitUrl); - sb.append(Constants.R_PATH); - sb.append(repository.name); - - // inject username into repository url if authentication is required - if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE) - && !StringUtils.isEmpty(username)) { - sb.insert(sb.indexOf("://") + 3, username + "@"); - } - return sb.toString(); - } - - - /** * Returns the list of custom client applications to be used for the * repository url panel; * @@ -492,7 +457,7 @@ public class GitblitManager implements IGitblit { // Read bundled Gitblit properties to extract setting descriptions. // This copy is pristine and only used for populating the setting // models map. - InputStream is = GitblitManager.class.getResourceAsStream("/reference.properties"); + InputStream is = GitblitManager.class.getResourceAsStream("/defaults.properties"); BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is)); StringBuilder description = new StringBuilder(); SettingModel setting = new SettingModel(); @@ -537,24 +502,20 @@ public class GitblitManager implements IGitblit { } propertiesReader.close(); } catch (NullPointerException e) { - logger.error("Failed to find resource copy of gitblit.properties"); + logger.error("Failed to find classpath resource 'defaults.properties'"); } catch (IOException e) { - logger.error("Failed to load resource copy of gitblit.properties"); + logger.error("Failed to load classpath resource 'defaults.properties'"); } } - /** - * Throws an exception if trying to get a ticket service. - * - */ @Override public ITicketService getTicketService() { - throw new RuntimeException("This class does not have a ticket service!"); + return ticketServiceProvider.get(); } @Override public IPublicKeyManager getPublicKeyManager() { - return publicKeyManager; + return publicKeyManagerProvider.get(); } /* @@ -605,26 +566,6 @@ public class GitblitManager implements IGitblit { } @Override - public boolean isServingRepositories() { - return runtimeManager.isServingRepositories(); - } - - @Override - public boolean isServingHTTP() { - return runtimeManager.isServingHTTP(); - } - - @Override - public boolean isServingGIT() { - return runtimeManager.isServingGIT(); - } - - @Override - public boolean isServingSSH() { - return runtimeManager.isServingSSH(); - } - - @Override public TimeZone getTimezone() { return runtimeManager.getTimezone(); } @@ -665,6 +606,11 @@ public class GitblitManager implements IGitblit { } @Override + public Injector getInjector() { + return runtimeManager.getInjector(); + } + + @Override public XssFilter getXssFilter() { return runtimeManager.getXssFilter(); } @@ -703,8 +649,8 @@ public class GitblitManager implements IGitblit { */ @Override - public UserModel authenticate(String username, char[] password) { - return authenticationManager.authenticate(username, password); + public UserModel authenticate(String username, char[] password, String remoteIP) { + return authenticationManager.authenticate(username, password, remoteIP); } @Override @@ -722,6 +668,11 @@ public class GitblitManager implements IGitblit { } @Override + public UserModel authenticate(String username) { + return authenticationManager.authenticate(username); + } + + @Override public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { UserModel user = authenticationManager.authenticate(httpRequest, requiresCertificate); if (user == null) { @@ -782,6 +733,16 @@ public class GitblitManager implements IGitblit { return authenticationManager.supportsTeamMembershipChanges(team); } + @Override + public boolean supportsRoleChanges(UserModel user, Role role) { + return authenticationManager.supportsRoleChanges(user, role); + } + + @Override + public boolean supportsRoleChanges(TeamModel team, Role role) { + return authenticationManager.supportsRoleChanges(team, role); + } + /* * USER MANAGER */ @@ -806,11 +767,6 @@ public class GitblitManager implements IGitblit { } @Override - public boolean deleteUser(String username) { - return userManager.deleteUser(username); - } - - @Override public UserModel getUserModel(String username) { return userManager.getUserModel(username); } @@ -851,8 +807,22 @@ public class GitblitManager implements IGitblit { } @Override + public boolean deleteUser(String username) { + // delegate to deleteUserModel() to delete public ssh keys + UserModel user = userManager.getUserModel(username); + return deleteUserModel(user); + } + + /** + * Delete the user and all associated public ssh keys. + */ + @Override public boolean deleteUserModel(UserModel model) { - return userManager.deleteUserModel(model); + boolean success = userManager.deleteUserModel(model); + if (success) { + getPublicKeyManager().removeAllKeys(model.username); + } + return success; } @Override @@ -1053,10 +1023,23 @@ public class GitblitManager implements IGitblit { return repositoryManager.getRepositoryDefaultMetrics(model, repository); } + /** + * Detect renames and reindex as appropriate. + */ @Override public void updateRepositoryModel(String repositoryName, RepositoryModel repository, boolean isCreate) throws GitBlitException { + RepositoryModel oldModel = null; + boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name); + if (isRename) { + oldModel = repositoryManager.getRepositoryModel(repositoryName); + } + repositoryManager.updateRepositoryModel(repositoryName, repository, isCreate); + + if (isRename && ticketServiceProvider.get() != null) { + ticketServiceProvider.get().rename(oldModel, repository); + } } @Override @@ -1069,14 +1052,23 @@ public class GitblitManager implements IGitblit { return repositoryManager.canDelete(model); } + /** + * Delete the repository and all associated tickets. + */ @Override public boolean deleteRepositoryModel(RepositoryModel model) { - return repositoryManager.deleteRepositoryModel(model); + boolean success = repositoryManager.deleteRepositoryModel(model); + if (success && ticketServiceProvider.get() != null) { + ticketServiceProvider.get().deleteAll(model); + } + return success; } @Override public boolean deleteRepository(String repositoryName) { - return repositoryManager.deleteRepository(repositoryName); + // delegate to deleteRepositoryModel() to destroy indexed tickets + RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName); + return deleteRepositoryModel(repository); } @Override @@ -1253,6 +1245,70 @@ public class GitblitManager implements IGitblit { } /* + * FILE STORAGE MANAGER + */ + + @Override + public boolean isValidOid(String oid) { + return filestoreManager.isValidOid(oid); + } + + @Override + public FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo) { + return filestoreManager.addObject(oid, size, user, repo); + } + + @Override + public FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo) { + return filestoreManager.getObject(oid, user, repo); + }; + + @Override + public FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn ) { + return filestoreManager.uploadBlob(oid, size, user, repo, streamIn); + } + + @Override + public FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut ) { + return filestoreManager.downloadBlob(oid, user, repo, streamOut); + } + + @Override + public List<FilestoreModel> getAllObjects() { + return filestoreManager.getAllObjects(); + } + + @Override + public File getStorageFolder() { + return filestoreManager.getStorageFolder(); + } + + @Override + public File getStoragePath(String oid) { + return filestoreManager.getStoragePath(oid); + } + + @Override + public long getMaxUploadSize() { + return filestoreManager.getMaxUploadSize(); + }; + + @Override + public void clearFilestoreCache() { + filestoreManager.clearFilestoreCache(); + }; + + @Override + public long getFilestoreUsedByteCount() { + return filestoreManager.getFilestoreUsedByteCount(); + }; + + @Override + public long getFilestoreAvailableByteCount() { + return filestoreManager.getFilestoreAvailableByteCount(); + }; + + /* * PLUGIN MANAGER */ @@ -1355,4 +1411,5 @@ public class GitblitManager implements IGitblit { public PluginRelease lookupRelease(String pluginId, String version) { return pluginManager.lookupRelease(pluginId, version); } + } diff --git a/src/main/java/com/gitblit/manager/IAuthenticationManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java index 3600b325..5406a794 100644 --- a/src/main/java/com/gitblit/manager/IAuthenticationManager.java +++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java @@ -18,6 +18,7 @@ package com.gitblit.manager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.gitblit.Constants.Role; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.transport.ssh.SshKey; @@ -64,10 +65,21 @@ public interface IAuthenticationManager extends IManager { * @see IUserService.authenticate(String, char[]) * @param username * @param password + * @param remoteIP * @return a user object or null * @since 1.4.0 */ - UserModel authenticate(String username, char[] password); + UserModel authenticate(String username, char[] password, String remoteIP); + + /** + * Return the UserModel for already authenticated user. + * + * @see IUserService.authenticate(String, char[]) + * @param username + * @return a user object or null + * @since 1.7.0 + */ + UserModel authenticate(String username); /** * Returns the Gitlbit cookie in the request. @@ -161,4 +173,22 @@ public interface IAuthenticationManager extends IManager { */ boolean supportsTeamMembershipChanges(TeamModel team); + /** + * Returns true if the specified role can be changed. + * + * @param user + * @return true if the specified role can be changed + * @since 1.6.1 + */ + boolean supportsRoleChanges(UserModel user, Role role); + + /** + * Returns true if the specified role can be changed. + * + * @param team + * @return true if the specified role can be changed + * @since 1.6.1 + */ + boolean supportsRoleChanges(TeamModel team, Role role); + }
\ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/IFilestoreManager.java b/src/main/java/com/gitblit/manager/IFilestoreManager.java new file mode 100644 index 00000000..0720650c --- /dev/null +++ b/src/main/java/com/gitblit/manager/IFilestoreManager.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import com.gitblit.models.FilestoreModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; + + +public interface IFilestoreManager extends IManager { + + boolean isValidOid(String oid); + + FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo); + + FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo); + + FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn ); + + FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut ); + + List<FilestoreModel> getAllObjects(); + + File getStorageFolder(); + + File getStoragePath(String oid); + + long getMaxUploadSize(); + + void clearFilestoreCache(); + + long getFilestoreUsedByteCount(); + + long getFilestoreAvailableByteCount(); + +} diff --git a/src/main/java/com/gitblit/manager/IGitblit.java b/src/main/java/com/gitblit/manager/IGitblit.java index 50ec8b1f..489de62d 100644 --- a/src/main/java/com/gitblit/manager/IGitblit.java +++ b/src/main/java/com/gitblit/manager/IGitblit.java @@ -16,14 +16,10 @@ package com.gitblit.manager; import java.util.Collection; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; import com.gitblit.GitBlitException; import com.gitblit.models.GitClientApplication; import com.gitblit.models.RepositoryModel; -import com.gitblit.models.RepositoryUrl; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.tickets.ITicketService; @@ -37,18 +33,8 @@ public interface IGitblit extends IManager, IAuthenticationManager, IRepositoryManager, IProjectManager, - IFederationManager { - - /** - * Returns a list of repository URLs and the user access permission. - * - * @param request - * @param user - * @param repository - * @return a list of repository urls - * @since 1.4.0 - */ - List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository); + IFederationManager, + IFilestoreManager { /** * Creates a complete user object. diff --git a/src/main/java/com/gitblit/manager/IRuntimeManager.java b/src/main/java/com/gitblit/manager/IRuntimeManager.java index 132534c3..2203b7ff 100644 --- a/src/main/java/com/gitblit/manager/IRuntimeManager.java +++ b/src/main/java/com/gitblit/manager/IRuntimeManager.java @@ -25,9 +25,12 @@ import com.gitblit.IStoredSettings; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; import com.gitblit.utils.XssFilter; +import com.google.inject.Injector; public interface IRuntimeManager extends IManager { + Injector getInjector(); + void setBaseFolder(File folder); File getBaseFolder(); @@ -49,42 +52,6 @@ public interface IRuntimeManager extends IManager { Locale getLocale(); /** - * Determine if this Gitblit instance is actively serving git repositories - * or if it is merely a repository viewer. - * - * @return true if Gitblit is serving repositories - * @since 1.4.0 - */ - boolean isServingRepositories(); - - /** - * Determine if this Gitblit instance is actively serving git repositories - * over HTTP. - * - * @return true if Gitblit is serving repositories over HTTP - * @since 1.6.0 - */ - boolean isServingHTTP(); - - /** - * Determine if this Gitblit instance is actively serving git repositories - * over the GIT Daemon protocol. - * - * @return true if Gitblit is serving repositories over the GIT Daemon protocol - * @since 1.6.0 - */ - boolean isServingGIT(); - - /** - * Determine if this Gitblit instance is actively serving git repositories - * over the SSH protocol. - * - * @return true if Gitblit is serving repositories over the SSH protocol - * @since 1.6.0 - */ - boolean isServingSSH(); - - /** * Determine if this Gitblit instance is running in debug mode * * @return true if Gitblit is running in debug mode diff --git a/src/main/java/com/gitblit/manager/IServicesManager.java b/src/main/java/com/gitblit/manager/IServicesManager.java new file mode 100644 index 00000000..b3a973b3 --- /dev/null +++ b/src/main/java/com/gitblit/manager/IServicesManager.java @@ -0,0 +1,94 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.manager; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import com.gitblit.Constants.Transport; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.RepositoryUrl; +import com.gitblit.models.UserModel; + +public interface IServicesManager extends IManager { + + /** + * Determine if this Gitblit instance is actively serving git repositories + * or if it is merely a repository viewer. + * + * @return true if Gitblit is serving repositories + * @since 1.7.0 + */ + boolean isServingRepositories(); + + /** + * Determine if this Gitblit instance is actively serving git repositories + * over HTTP. + * + * @return true if Gitblit is serving repositories over HTTP + * @since 1.7.0 + */ + boolean isServingHTTP(); + + /** + * Determine if this Gitblit instance is actively serving git repositories + * over HTTP. + * + * @return true if Gitblit is serving repositories over HTTPS + * @since 1.7.0 + */ + boolean isServingHTTPS(); + + /** + * Determine if this Gitblit instance is actively serving git repositories + * over the GIT Daemon protocol. + * + * @return true if Gitblit is serving repositories over the GIT Daemon protocol + * @since 1.7.0 + */ + boolean isServingGIT(); + + /** + * Determine if this Gitblit instance is actively serving git repositories + * over the SSH protocol. + * + * @return true if Gitblit is serving repositories over the SSH protocol + * @since 1.7.0 + */ + boolean isServingSSH(); + + /** + * Returns a list of repository URLs and the user access permission. + * + * @param request + * @param user + * @param repository + * @return a list of repository urls + * @since 1.7.0 + */ + List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository); + + /** + * Returns true if the transport may be used for pushing. + * + * @param byTransport + * @return true if the transport can be used for pushes. + * @since 1.7.0 + */ + boolean acceptsPush(Transport byTransport); + +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/manager/NotificationManager.java b/src/main/java/com/gitblit/manager/NotificationManager.java index 69a611bb..4bbc2abf 100644 --- a/src/main/java/com/gitblit/manager/NotificationManager.java +++ b/src/main/java/com/gitblit/manager/NotificationManager.java @@ -29,6 +29,8 @@ import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.models.Mailing; import com.gitblit.service.MailService; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * The notification manager dispatches notifications. Currently, email is the @@ -38,6 +40,7 @@ import com.gitblit.service.MailService; * @author James Moger * */ +@Singleton public class NotificationManager implements INotificationManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -48,6 +51,7 @@ public class NotificationManager implements INotificationManager { private final MailService mailService; + @Inject public NotificationManager(IStoredSettings settings) { this.settings = settings; this.mailService = new MailService(settings); diff --git a/src/main/java/com/gitblit/manager/PluginManager.java b/src/main/java/com/gitblit/manager/PluginManager.java index 5e25caa1..b3936e5d 100644 --- a/src/main/java/com/gitblit/manager/PluginManager.java +++ b/src/main/java/com/gitblit/manager/PluginManager.java @@ -15,13 +15,15 @@ */ package com.gitblit.manager; -import java.io.BufferedInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; import java.net.URLConnection; @@ -37,8 +39,12 @@ import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ro.fortsoft.pf4j.DefaultPluginFactory; import ro.fortsoft.pf4j.DefaultPluginManager; +import ro.fortsoft.pf4j.ExtensionFactory; +import ro.fortsoft.pf4j.Plugin; import ro.fortsoft.pf4j.PluginClassLoader; +import ro.fortsoft.pf4j.PluginFactory; import ro.fortsoft.pf4j.PluginState; import ro.fortsoft.pf4j.PluginStateEvent; import ro.fortsoft.pf4j.PluginStateListener; @@ -56,8 +62,9 @@ import com.gitblit.utils.Base64; import com.gitblit.utils.FileUtils; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.StringUtils; -import com.google.common.io.Files; -import com.google.common.io.InputSupplier; +import com.google.common.io.ByteStreams; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * The plugin manager maintains the lifecycle of plugins. It is exposed as @@ -68,32 +75,23 @@ import com.google.common.io.InputSupplier; * @author James Moger * */ +@Singleton public class PluginManager implements IPluginManager, PluginStateListener { private final Logger logger = LoggerFactory.getLogger(getClass()); - private final DefaultPluginManager pf4j; - private final IRuntimeManager runtimeManager; + private DefaultPluginManager pf4j; + // timeout defaults of Maven 3.0.4 in seconds private int connectTimeout = 20; private int readTimeout = 12800; + @Inject public PluginManager(IRuntimeManager runtimeManager) { - File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); - dir.mkdirs(); this.runtimeManager = runtimeManager; - - this.pf4j = new DefaultPluginManager(dir); - - try { - Version systemVersion = Version.createVersion(Constants.getVersion()); - pf4j.setSystemVersion(systemVersion); - } catch (Exception e) { - logger.error(null, e); - } } @Override @@ -108,6 +106,28 @@ public class PluginManager implements IPluginManager, PluginStateListener { @Override public PluginManager start() { + File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins"); + dir.mkdirs(); + + pf4j = new DefaultPluginManager(dir) { + + @Override + protected PluginFactory createPluginFactory() { + return new GuicePluginFactory(); + } + + @Override + protected ExtensionFactory createExtensionFactory() { + return new GuiceExtensionFactory(); + } + }; + + try { + Version systemVersion = Version.createVersion(Constants.getVersion()); + pf4j.setSystemVersion(systemVersion); + } catch (Exception e) { + logger.error(null, e); + } pf4j.loadPlugins(); logger.debug("Starting plugins"); pf4j.startPlugins(); @@ -438,7 +458,7 @@ public class PluginManager implements IPluginManager, PluginStateListener { } - if (sha1File == null && md5File == null && verifyChecksum) { + if (sha1File == null && md5File == null) { throw new IOException("Missing SHA1 and MD5 checksums for " + url); } @@ -513,12 +533,9 @@ public class PluginManager implements IPluginManager, PluginStateListener { // try to get the server-specified last-modified date of this artifact long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis()); - Files.copy(new InputSupplier<InputStream>() { - @Override - public InputStream getInput() throws IOException { - return new BufferedInputStream(conn.getInputStream()); - } - }, tmpFile); + try (InputStream is = conn.getInputStream(); OutputStream os = new FileOutputStream(tmpFile);) { + ByteStreams.copy(is, os); + } File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath())); if (destFile.exists()) { @@ -567,10 +584,55 @@ public class PluginManager implements IPluginManager, PluginStateListener { } protected Proxy getProxy(URL url) { - return java.net.Proxy.NO_PROXY; + String proxyHost = runtimeManager.getSettings().getString(Keys.plugins.httpProxyHost, ""); + String proxyPort = runtimeManager.getSettings().getString(Keys.plugins.httpProxyPort, ""); + + if (!StringUtils.isEmpty(proxyHost) && !StringUtils.isEmpty(proxyPort)) { + return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort))); + } else { + return java.net.Proxy.NO_PROXY; + } } protected String getProxyAuthorization(URL url) { - return ""; + String proxyAuth = runtimeManager.getSettings().getString(Keys.plugins.httpProxyAuthorization, ""); + return proxyAuth; + } + + /** + * Instantiates a plugin using pf4j but injects member fields + * with Guice. + */ + private class GuicePluginFactory extends DefaultPluginFactory { + + @Override + public Plugin create(PluginWrapper pluginWrapper) { + // use pf4j to create the plugin + Plugin plugin = super.create(pluginWrapper); + + if (plugin != null) { + // allow Guice to inject member fields + runtimeManager.getInjector().injectMembers(plugin); + } + + return plugin; + } + } + + /** + * Instantiates an extension using Guice. + */ + private class GuiceExtensionFactory implements ExtensionFactory { + @Override + public Object create(Class<?> extensionClass) { + // instantiate && inject the extension + logger.debug("Create instance for extension '{}'", extensionClass.getName()); + try { + return runtimeManager.getInjector().getInstance(extensionClass); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return null; + } } } diff --git a/src/main/java/com/gitblit/manager/ProjectManager.java b/src/main/java/com/gitblit/manager/ProjectManager.java index 666f5210..ae46bdfc 100644 --- a/src/main/java/com/gitblit/manager/ProjectManager.java +++ b/src/main/java/com/gitblit/manager/ProjectManager.java @@ -41,6 +41,8 @@ import com.gitblit.utils.DeepCopier; import com.gitblit.utils.ModelUtils; import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Project manager handles project-related functions. @@ -48,6 +50,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ +@Singleton public class ProjectManager implements IProjectManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -68,6 +71,7 @@ public class ProjectManager implements IProjectManager { private FileBasedConfig projectConfigs; + @Inject public ProjectManager( IRuntimeManager runtimeManager, IUserManager userManager, diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java index 2db41323..e2e4de68 100644 --- a/src/main/java/com/gitblit/manager/RepositoryManager.java +++ b/src/main/java/com/gitblit/manager/RepositoryManager.java @@ -91,6 +91,8 @@ import com.gitblit.utils.ModelUtils; import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Repository manager creates, updates, deletes and caches git repositories. It @@ -99,6 +101,7 @@ import com.gitblit.utils.TimeUtils; * @author James Moger * */ +@Singleton public class RepositoryManager implements IRepositoryManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -121,7 +124,7 @@ public class RepositoryManager implements IRepositoryManager { private final IUserManager userManager; - private final File repositoriesFolder; + private File repositoriesFolder; private LuceneService luceneExecutor; @@ -129,6 +132,7 @@ public class RepositoryManager implements IRepositoryManager { private MirrorService mirrorExecutor; + @Inject public RepositoryManager( IRuntimeManager runtimeManager, IPluginManager pluginManager, @@ -138,11 +142,11 @@ public class RepositoryManager implements IRepositoryManager { this.runtimeManager = runtimeManager; this.pluginManager = pluginManager; this.userManager = userManager; - this.repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git"); } @Override public RepositoryManager start() { + repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git"); logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath()); // initialize utilities @@ -1109,9 +1113,16 @@ public class RepositoryManager implements IRepositoryManager { // find the root, cached String key = getRepositoryKey(repository); RepositoryModel model = repositoryListCache.get(key); + if (model == null) { + return null; + } + while (model.originRepository != null) { String originKey = getRepositoryKey(model.originRepository); model = repositoryListCache.get(originKey); + if (model == null) { + return null; + } } ForkModel root = getForkModelFromCache(model.name); return root; @@ -1343,7 +1354,7 @@ public class RepositoryManager implements IRepositoryManager { } /** - * Creates/updates the repository model keyed by reopsitoryName. Saves all + * Creates/updates the repository model keyed by repositoryName. Saves all * repository settings in .git/config. This method allows for renaming * repositories and will update user access permissions accordingly. * @@ -1371,6 +1382,7 @@ public class RepositoryManager implements IRepositoryManager { repository.name = repository.name.substring(projectPath.length() + 1); } } + boolean isRename = false; if (isCreate) { // ensure created repository name ends with .git if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { @@ -1387,7 +1399,8 @@ public class RepositoryManager implements IRepositoryManager { r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared); } else { // rename repository - if (!repositoryName.equalsIgnoreCase(repository.name)) { + isRename = !repositoryName.equalsIgnoreCase(repository.name); + if (isRename) { if (!repository.name.toLowerCase().endsWith( org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; @@ -1507,6 +1520,14 @@ public class RepositoryManager implements IRepositoryManager { logger.error(String.format("failed to call plugin onCreation %s", repositoryName), t); } } + } else if (isRename && pluginManager != null) { + for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) { + try { + listener.onRename(repositoryName, repository); + } catch (Throwable t) { + logger.error(String.format("failed to call plugin onRename %s", repositoryName), t); + } + } } } @@ -1967,21 +1988,19 @@ public class RepositoryManager implements IRepositoryManager { } protected void confirmWriteAccess() { - if (runtimeManager.isServingRepositories()) { - try { - if (!getRepositoriesFolder().exists()) { - getRepositoriesFolder().mkdirs(); - } - File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder()); - file.delete(); - } catch (Exception e) { - logger.error(""); - logger.error(Constants.BORDER2); - logger.error("Please check filesystem permissions!"); - logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e); - logger.error(Constants.BORDER2); - logger.error(""); + try { + if (!getRepositoriesFolder().exists()) { + getRepositoriesFolder().mkdirs(); } + File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder()); + file.delete(); + } catch (Exception e) { + logger.error(""); + logger.error(Constants.BORDER2); + logger.error("Please check filesystem permissions!"); + logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e); + logger.error(Constants.BORDER2); + logger.error(""); } } } diff --git a/src/main/java/com/gitblit/manager/RuntimeManager.java b/src/main/java/com/gitblit/manager/RuntimeManager.java index 219bf801..18d6b9c2 100644 --- a/src/main/java/com/gitblit/manager/RuntimeManager.java +++ b/src/main/java/com/gitblit/manager/RuntimeManager.java @@ -33,7 +33,11 @@ import com.gitblit.models.ServerStatus; import com.gitblit.models.SettingModel; import com.gitblit.utils.StringUtils; import com.gitblit.utils.XssFilter; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Singleton; +@Singleton public class RuntimeManager implements IRuntimeManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -50,6 +54,10 @@ public class RuntimeManager implements IRuntimeManager { private TimeZone timezone; + @Inject + private Injector injector; + + @Inject public RuntimeManager(IStoredSettings settings, XssFilter xssFilter) { this(settings, xssFilter, null); } @@ -79,6 +87,11 @@ public class RuntimeManager implements IRuntimeManager { } @Override + public Injector getInjector() { + return injector; + } + + @Override public File getBaseFolder() { return baseFolder; } @@ -116,52 +129,6 @@ public class RuntimeManager implements IRuntimeManager { } /** - * Determine if this Gitblit instance is actively serving git repositories - * or if it is merely a repository viewer. - * - * @return true if Gitblit is serving repositories - */ - @Override - public boolean isServingRepositories() { - return isServingHTTP() - || isServingGIT() - || isServingSSH(); - } - - /** - * Determine if this Gitblit instance is actively serving git repositories - * over the HTTP protocol. - * - * @return true if Gitblit is serving repositories over the HTTP protocol - */ - @Override - public boolean isServingHTTP() { - return settings.getBoolean(Keys.git.enableGitServlet, true); - } - - /** - * Determine if this Gitblit instance is actively serving git repositories - * over the Git Daemon protocol. - * - * @return true if Gitblit is serving repositories over the Git Daemon protocol - */ - @Override - public boolean isServingGIT() { - return settings.getInteger(Keys.git.daemonPort, 0) > 0; - } - - /** - * Determine if this Gitblit instance is actively serving git repositories - * over the SSH protocol. - * - * @return true if Gitblit is serving repositories over the SSH protocol - */ - @Override - public boolean isServingSSH() { - return settings.getInteger(Keys.git.sshPort, 0) > 0; - } - - /** * Returns the preferred timezone for the Gitblit instance. * * @return a timezone diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java index 437fd106..b993eb66 100644 --- a/src/main/java/com/gitblit/manager/ServicesManager.java +++ b/src/main/java/com/gitblit/manager/ServicesManager.java @@ -18,9 +18,15 @@ package com.gitblit.manager; import java.io.IOException; import java.net.URI; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -30,9 +36,11 @@ import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.gitblit.Constants; import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.FederationToken; +import com.gitblit.Constants.Transport; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.fanout.FanoutNioService; @@ -40,14 +48,18 @@ import com.gitblit.fanout.FanoutService; import com.gitblit.fanout.FanoutSocketService; import com.gitblit.models.FederationModel; import com.gitblit.models.RepositoryModel; +import com.gitblit.models.RepositoryUrl; import com.gitblit.models.UserModel; import com.gitblit.service.FederationPullService; import com.gitblit.transport.git.GitDaemon; import com.gitblit.transport.ssh.SshDaemon; -import com.gitblit.utils.IdGenerator; +import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; import com.gitblit.utils.WorkQueue; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; /** * Services manager manages long-running services/processes that either have no @@ -57,32 +69,35 @@ import com.gitblit.utils.WorkQueue; * @author James Moger * */ -public class ServicesManager implements IManager { +@Singleton +public class ServicesManager implements IServicesManager { private final Logger logger = LoggerFactory.getLogger(getClass()); private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5); + private final Provider<WorkQueue> workQueueProvider; + private final IStoredSettings settings; private final IGitblit gitblit; - private final IdGenerator idGenerator; - - private final WorkQueue workQueue; - private FanoutService fanoutService; private GitDaemon gitDaemon; private SshDaemon sshDaemon; - public ServicesManager(IGitblit gitblit) { - this.settings = gitblit.getSettings(); + @Inject + public ServicesManager( + Provider<WorkQueue> workQueueProvider, + IStoredSettings settings, + IGitblit gitblit) { + + this.workQueueProvider = workQueueProvider; + + this.settings = settings; this.gitblit = gitblit; - int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1); - this.idGenerator = new IdGenerator(); - this.workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize); } @Override @@ -107,24 +122,217 @@ public class ServicesManager implements IManager { if (sshDaemon != null) { sshDaemon.stop(); } - workQueue.stop(); + workQueueProvider.get().stop(); return this; } + protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) { + String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null); + if (StringUtils.isEmpty(gitblitUrl)) { + gitblitUrl = HttpUtils.getGitblitURL(request); + } + StringBuilder sb = new StringBuilder(); + sb.append(gitblitUrl); + sb.append(Constants.R_PATH); + sb.append(repository.name); + + // inject username into repository url if authentication is required + if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE) + && !StringUtils.isEmpty(username)) { + sb.insert(sb.indexOf("://") + 3, username + "@"); + } + return sb.toString(); + } + + /** + * Returns a list of repository URLs and the user access permission. + * + * @param request + * @param user + * @param repository + * @return a list of repository urls + */ + @Override + public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { + if (user == null) { + user = UserModel.ANONYMOUS; + } + String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); + + List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); + + // http/https url + if (settings.getBoolean(Keys.git.enableGitServlet, true) && + settings.getBoolean(Keys.web.showHttpServletUrls, true)) { + AccessPermission permission = user.getRepositoryPermission(repository).permission; + if (permission.exceeds(AccessPermission.NONE)) { + String repoUrl = getRepositoryUrl(request, username, repository); + Transport transport = Transport.fromUrl(repoUrl); + if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + list.add(new RepositoryUrl(repoUrl, permission)); + } + } + + // ssh daemon url + String sshDaemonUrl = getSshDaemonUrl(request, user, repository); + if (!StringUtils.isEmpty(sshDaemonUrl) && + settings.getBoolean(Keys.web.showSshDaemonUrls, true)) { + AccessPermission permission = user.getRepositoryPermission(repository).permission; + if (permission.exceeds(AccessPermission.NONE)) { + if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(Transport.SSH)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + + list.add(new RepositoryUrl(sshDaemonUrl, permission)); + } + } + + // git daemon url + String gitDaemonUrl = getGitDaemonUrl(request, user, repository); + if (!StringUtils.isEmpty(gitDaemonUrl) && + settings.getBoolean(Keys.web.showGitDaemonUrls, true)) { + AccessPermission permission = getGitDaemonAccessPermission(user, repository); + if (permission.exceeds(AccessPermission.NONE)) { + if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(Transport.GIT)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + list.add(new RepositoryUrl(gitDaemonUrl, permission)); + } + } + + // add all other urls + // {0} = repository + // {1} = username + boolean advertisePermsForOther = settings.getBoolean(Keys.web.advertiseAccessPermissionForOtherUrls, false); + for (String url : settings.getStrings(Keys.web.otherUrls)) { + String externalUrl = null; + + if (url.contains("{1}")) { + // external url requires username, only add url IF we have one + if (StringUtils.isEmpty(username)) { + continue; + } else { + externalUrl = MessageFormat.format(url, repository.name, username); + } + } else { + // external url does not require username, just do repo name formatting + externalUrl = MessageFormat.format(url, repository.name); + } + + AccessPermission permission = null; + if (advertisePermsForOther) { + permission = user.getRepositoryPermission(repository).permission; + if (permission.exceeds(AccessPermission.NONE)) { + Transport transport = Transport.fromUrl(externalUrl); + if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) { + // downgrade the repo permission for this transport + // because it is not an acceptable PUSH transport + permission = AccessPermission.CLONE; + } + } + } + list.add(new RepositoryUrl(externalUrl, permission)); + } + + // sort transports by highest permission and then by transport security + Collections.sort(list, new Comparator<RepositoryUrl>() { + + @Override + public int compare(RepositoryUrl o1, RepositoryUrl o2) { + if (o1.hasPermission() && !o2.hasPermission()) { + // prefer known permission items over unknown + return -1; + } else if (!o1.hasPermission() && o2.hasPermission()) { + // prefer known permission items over unknown + return 1; + } else if (!o1.hasPermission() && !o2.hasPermission()) { + // sort by Transport ordinal + return o1.transport.compareTo(o2.transport); + } else if (o1.permission.exceeds(o2.permission)) { + // prefer highest permission + return -1; + } else if (o2.permission.exceeds(o1.permission)) { + // prefer highest permission + return 1; + } + + // prefer more secure transports + return o1.transport.compareTo(o2.transport); + } + }); + + // consider the user's transport preference + RepositoryUrl preferredUrl = null; + Transport preferredTransport = user.getPreferences().getTransport(); + if (preferredTransport != null) { + Iterator<RepositoryUrl> itr = list.iterator(); + while (itr.hasNext()) { + RepositoryUrl url = itr.next(); + if (url.transport.equals(preferredTransport)) { + itr.remove(); + preferredUrl = url; + break; + } + } + } + if (preferredUrl != null) { + list.add(0, preferredUrl); + } + + return list; + } + + /* (non-Javadoc) + * @see com.gitblit.manager.IServicesManager#isServingRepositories() + */ + @Override public boolean isServingRepositories() { - return isServingHTTP() + return isServingHTTPS() + || isServingHTTP() || isServingGIT() || isServingSSH(); } + /* (non-Javadoc) + * @see com.gitblit.manager.IServicesManager#isServingHTTP() + */ + @Override public boolean isServingHTTP() { - return settings.getBoolean(Keys.git.enableGitServlet, true); + return settings.getBoolean(Keys.git.enableGitServlet, true) + && ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpPort, 0) > 0) + || !gitblit.getStatus().isGO); } + /* (non-Javadoc) + * @see com.gitblit.manager.IServicesManager#isServingHTTPS() + */ + @Override + public boolean isServingHTTPS() { + return settings.getBoolean(Keys.git.enableGitServlet, true) + && ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpsPort, 0) > 0) + || !gitblit.getStatus().isGO); + } + + /* (non-Javadoc) + * @see com.gitblit.manager.IServicesManager#isServingGIT() + */ + @Override public boolean isServingGIT() { return gitDaemon != null && gitDaemon.isRunning(); } + /* (non-Javadoc) + * @see com.gitblit.manager.IServicesManager#isServingSSH() + */ + @Override public boolean isServingSSH() { return sshDaemon != null && sshDaemon.isRunning(); } @@ -158,6 +366,33 @@ public class ServicesManager implements IManager { } } + @Override + public boolean acceptsPush(Transport byTransport) { + if (byTransport == null) { + logger.info("Unknown transport, push rejected!"); + return false; + } + + Set<Transport> transports = new HashSet<Transport>(); + for (String value : settings.getStrings(Keys.git.acceptedPushTransports)) { + Transport transport = Transport.fromString(value); + if (transport == null) { + logger.info(String.format("Ignoring unknown registered transport %s", value)); + continue; + } + + transports.add(transport); + } + + if (transports.isEmpty()) { + // no transports are explicitly specified, all are acceptable + return true; + } + + // verify that the transport is permitted + return transports.contains(byTransport); + } + protected void configureGitDaemon() { int port = settings.getInteger(Keys.git.daemonPort, 0); String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); @@ -179,7 +414,7 @@ public class ServicesManager implements IManager { String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost"); if (port > 0) { try { - sshDaemon = new SshDaemon(gitblit, workQueue); + sshDaemon = new SshDaemon(gitblit, workQueueProvider.get()); sshDaemon.start(); } catch (IOException e) { sshDaemon = null; @@ -285,7 +520,7 @@ public class ServicesManager implements IManager { */ protected String getHostname(HttpServletRequest request) { String hostname = request.getServerName(); - String canonicalUrl = gitblit.getSettings().getString(Keys.web.canonicalUrl, null); + String canonicalUrl = settings.getString(Keys.web.canonicalUrl, null); if (!StringUtils.isEmpty(canonicalUrl)) { try { URI uri = new URI(canonicalUrl); diff --git a/src/main/java/com/gitblit/manager/UserManager.java b/src/main/java/com/gitblit/manager/UserManager.java index 2b82ffb6..e88ac93c 100644 --- a/src/main/java/com/gitblit/manager/UserManager.java +++ b/src/main/java/com/gitblit/manager/UserManager.java @@ -36,6 +36,8 @@ import com.gitblit.extensions.UserTeamLifeCycleListener; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * The user manager manages persistence and retrieval of users and teams. @@ -43,6 +45,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ +@Singleton public class UserManager implements IUserManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -57,6 +60,7 @@ public class UserManager implements IUserManager { private IUserService userService; + @Inject public UserManager(IRuntimeManager runtimeManager, IPluginManager pluginManager) { this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; @@ -79,9 +83,9 @@ public class UserManager implements IUserManager { * @param userService */ public void setUserService(IUserService userService) { - logger.info(userService.toString()); this.userService = userService; this.userService.setup(runtimeManager); + logger.info(userService.toString()); } @Override @@ -111,10 +115,12 @@ public class UserManager implements IUserManager { // check to see if this "file" is a custom user service class Class<?> realmClass = Class.forName(realm); service = (IUserService) realmClass.newInstance(); - } catch (Throwable t) { + } catch (ClassNotFoundException t) { // typical file path configuration File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf"); service = createUserService(realmFile); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("failed to instantiate user service {}: {}", realm, e.getMessage()); } } setUserService(service); diff --git a/src/main/java/com/gitblit/models/FilestoreModel.java b/src/main/java/com/gitblit/models/FilestoreModel.java new file mode 100644 index 00000000..ff7b210e --- /dev/null +++ b/src/main/java/com/gitblit/models/FilestoreModel.java @@ -0,0 +1,159 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.models; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * A FilestoreModel represents a file stored outside a repository but referenced by the repository using a unique objectID + * + * @author Paul Martin + * + */ +public class FilestoreModel implements Serializable { + + private static final long serialVersionUID = 1L; + + public final String oid; + + private Long size; + private Status status; + + //Audit + private String stateChangedBy; + private Date stateChangedOn; + + //Access Control + private List<String> repositories; + + public FilestoreModel(String id, long expectedSize, UserModel user, String repo) { + oid = id; + size = expectedSize; + status = Status.Upload_Pending; + stateChangedBy = user.getName(); + stateChangedOn = new Date(); + repositories = new ArrayList<String>(); + repositories.add(repo); + } + + public synchronized long getSize() { + return size; + } + + public synchronized Status getStatus() { + return status; + } + + public synchronized String getChangedBy() { + return stateChangedBy; + } + + public synchronized Date getChangedOn() { + return stateChangedOn; + } + + public synchronized void setStatus(Status status, UserModel user) { + this.status = status; + stateChangedBy = user.getName(); + stateChangedOn = new Date(); + } + + public synchronized void reset(UserModel user, long size) { + status = Status.Upload_Pending; + stateChangedBy = user.getName(); + stateChangedOn = new Date(); + this.size = size; + } + + /* + * Handles possible race condition with concurrent connections + * @return true if action can proceed, false otherwise + */ + public synchronized boolean actionUpload(UserModel user) { + if (status == Status.Upload_Pending) { + status = Status.Upload_In_Progress; + stateChangedBy = user.getName(); + stateChangedOn = new Date(); + return true; + } + + return false; + } + + public synchronized boolean isInErrorState() { + return (this.status.value < 0); + } + + public synchronized void addRepository(String repo) { + if (!repositories.contains(repo)) { + repositories.add(repo); + } + } + + public synchronized void removeRepository(String repo) { + repositories.remove(repo); + } + + public static enum Status { + + Deleted(-30), + AuthenticationRequired(-20), + + Error_Unknown(-8), + Error_Unexpected_Stream_End(-7), + Error_Invalid_Oid(-6), + Error_Invalid_Size(-5), + Error_Hash_Mismatch(-4), + Error_Size_Mismatch(-3), + Error_Exceeds_Size_Limit(-2), + Error_Unauthorized(-1), + //Negative values provide additional information and may be treated as 0 when not required + Unavailable(0), + Upload_Pending(1), + Upload_In_Progress(2), + Available(3); + + final int value; + + Status(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return name().toLowerCase().replace('_', ' '); + } + + public static Status fromState(int state) { + for (Status s : values()) { + if (s.getValue() == state) { + return s; + } + } + throw new NoSuchElementException(String.valueOf(state)); + } + } + +} + diff --git a/src/main/java/com/gitblit/models/RefModel.java b/src/main/java/com/gitblit/models/RefModel.java index 02ba1302..d20c2dc6 100644 --- a/src/main/java/com/gitblit/models/RefModel.java +++ b/src/main/java/com/gitblit/models/RefModel.java @@ -58,12 +58,7 @@ public class RefModel implements Serializable, Comparable<RefModel> { }
} else if (referencedObject instanceof RevCommit) {
RevCommit commit = (RevCommit) referencedObject;
- PersonIdent committer = commit.getCommitterIdent();
- if (committer != null) {
- date = committer.getWhen();
- } else {
- date = JGitUtils.getCommitDate(commit);
- }
+ date = JGitUtils.getAuthorDate(commit);
}
}
return date;
diff --git a/src/main/java/com/gitblit/models/RepositoryUrl.java b/src/main/java/com/gitblit/models/RepositoryUrl.java index d155dbda..13f69175 100644 --- a/src/main/java/com/gitblit/models/RepositoryUrl.java +++ b/src/main/java/com/gitblit/models/RepositoryUrl.java @@ -41,8 +41,8 @@ public class RepositoryUrl implements Serializable { this.permission = permission;
}
- public boolean isExternal() {
- return permission == null;
+ public boolean hasPermission() {
+ return permission != null;
}
@Override
diff --git a/src/main/java/com/gitblit/models/TicketModel.java b/src/main/java/com/gitblit/models/TicketModel.java index a4880ead..fd0b09eb 100644 --- a/src/main/java/com/gitblit/models/TicketModel.java +++ b/src/main/java/com/gitblit/models/TicketModel.java @@ -91,6 +91,10 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { public Integer deletions; + public Priority priority; + + public Severity severity; + /** * Builds an effective ticket from the collection of changes. A change may * Add or Subtract information from a ticket, but the collection of changes @@ -141,6 +145,8 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { changes = new ArrayList<Change>(); status = Status.New; type = Type.defaultType; + priority = Priority.defaultPriority; + severity = Severity.defaultSeverity; } public boolean isOpen() { @@ -517,6 +523,12 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { case mergeSha: mergeSha = toString(value); break; + case priority: + priority = TicketModel.Priority.fromObject(value, priority); + break; + case severity: + severity = TicketModel.Severity.fromObject(value, severity); + break; default: // unknown break; @@ -1183,16 +1195,16 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { public static enum Field { title, body, responsible, type, status, milestone, mergeSha, mergeTo, - topic, labels, watchers, reviewers, voters, mentions; + topic, labels, watchers, reviewers, voters, mentions, priority, severity; } public static enum Type { - Enhancement, Task, Bug, Proposal, Question; + Enhancement, Task, Bug, Proposal, Question, Maintenance; public static Type defaultType = Task; public static Type [] choices() { - return new Type [] { Enhancement, Task, Bug, Question }; + return new Type [] { Enhancement, Task, Bug, Question, Maintenance }; } @Override @@ -1310,4 +1322,110 @@ public class TicketModel implements Serializable, Comparable<TicketModel> { return null; } } + + public static enum Priority { + Low(-1), Normal(0), High(1), Urgent(2); + + public static Priority defaultPriority = Normal; + + final int value; + + Priority(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Priority [] choices() { + return new Priority [] { Urgent, High, Normal, Low }; + } + + @Override + public String toString() { + return name().toLowerCase().replace('_', ' '); + } + + public static Priority fromObject(Object o, Priority defaultPriority) { + if (o instanceof Priority) { + // cast and return + return (Priority) o; + } else if (o instanceof String) { + // find by name + for (Priority priority : values()) { + String str = o.toString(); + if (priority.name().equalsIgnoreCase(str) + || priority.toString().equalsIgnoreCase(str)) { + return priority; + } + } + } else if (o instanceof Number) { + + switch (((Number) o).intValue()) { + case -1: return Priority.Low; + case 0: return Priority.Normal; + case 1: return Priority.High; + case 2: return Priority.Urgent; + default: return Priority.Normal; + } + } + + return defaultPriority; + } + } + + public static enum Severity { + Unrated(-1), Negligible(1), Minor(2), Serious(3), Critical(4), Catastrophic(5); + + public static Severity defaultSeverity = Unrated; + + final int value; + + Severity(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Severity [] choices() { + return new Severity [] { Unrated, Negligible, Minor, Serious, Critical, Catastrophic }; + } + + @Override + public String toString() { + return name().toLowerCase().replace('_', ' '); + } + + public static Severity fromObject(Object o, Severity defaultSeverity) { + if (o instanceof Severity) { + // cast and return + return (Severity) o; + } else if (o instanceof String) { + // find by name + for (Severity severity : values()) { + String str = o.toString(); + if (severity.name().equalsIgnoreCase(str) + || severity.toString().equalsIgnoreCase(str)) { + return severity; + } + } + } else if (o instanceof Number) { + + switch (((Number) o).intValue()) { + case -1: return Severity.Unrated; + case 1: return Severity.Negligible; + case 2: return Severity.Minor; + case 3: return Severity.Serious; + case 4: return Severity.Critical; + case 5: return Severity.Catastrophic; + default: return Severity.Unrated; + } + } + + return defaultSeverity; + } + } } diff --git a/src/main/java/com/gitblit/service/LuceneService.java b/src/main/java/com/gitblit/service/LuceneService.java index 482be5c6..097a39b2 100644 --- a/src/main/java/com/gitblit/service/LuceneService.java +++ b/src/main/java/com/gitblit/service/LuceneService.java @@ -105,7 +105,7 @@ import com.gitblit.utils.StringUtils; public class LuceneService implements Runnable {
- private static final int INDEX_VERSION = 5;
+ private static final int INDEX_VERSION = 6;
private static final String FIELD_OBJECT_TYPE = "type";
private static final String FIELD_PATH = "path";
@@ -125,7 +125,7 @@ public class LuceneService implements Runnable { private static final String CONF_ALIAS = "aliases";
private static final String CONF_BRANCH = "branches";
- private static final Version LUCENE_VERSION = Version.LUCENE_46;
+ private static final Version LUCENE_VERSION = Version.LUCENE_4_10_0;
private final Logger logger = LoggerFactory.getLogger(LuceneService.class);
@@ -437,7 +437,7 @@ public class LuceneService implements Runnable { // skip non-annotated tags
continue;
}
- if (!tags.containsKey(tag.getObjectId().getName())) {
+ if (!tags.containsKey(tag.getReferencedObjectId().getName())) {
tags.put(tag.getReferencedObjectId().getName(), new ArrayList<String>());
}
tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName);
@@ -615,7 +615,7 @@ public class LuceneService implements Runnable { }
// finished
- reader.release();
+ reader.close();
// commit all changes and reset the searcher
config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION);
@@ -1104,6 +1104,7 @@ public class LuceneService implements Runnable { content = "";
}
+ int tabLength = storedSettings.getInteger(Keys.web.tabLength, 4);
int fragmentLength = SearchObjectType.commit == result.type ? 512 : 150;
QueryScorer scorer = new QueryScorer(query, "content");
@@ -1126,7 +1127,7 @@ public class LuceneService implements Runnable { if (fragment.length() > fragmentLength) {
fragment = fragment.substring(0, fragmentLength) + "...";
}
- return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true) + "</pre>";
+ return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true, tabLength) + "</pre>";
}
// make sure we have unique fragments
diff --git a/src/main/java/com/gitblit/service/MailService.java b/src/main/java/com/gitblit/service/MailService.java index ae9727fc..ec3a84ca 100644 --- a/src/main/java/com/gitblit/service/MailService.java +++ b/src/main/java/com/gitblit/service/MailService.java @@ -37,6 +37,7 @@ import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -196,7 +197,8 @@ public class MailService implements Runnable { }
message.setSentDate(new Date());
- message.setSubject(mailing.subject);
+ // UTF-8 encode
+ message.setSubject(MimeUtility.encodeText(mailing.subject, "utf-8", "B"));
MimeBodyPart messagePart = new MimeBodyPart();
messagePart.setText(mailing.content, "utf-8");
diff --git a/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java b/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java new file mode 100644 index 00000000..ae0797e2 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 Jean-Baptiste Mayer + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.inject.Singleton; + +/** + * Access-denied Servlet. + * + * This servlet serves only 404 Not Found error replies. + * + * This servlet is used to override the container's default behavior to serve + * all contents of the application's WAR. We can selectively prevent access to + * a specific path simply by mapping this servlet onto specific paths. + * + * + * @author Jean-Baptiste Mayer + * + */ +@Singleton +public class AccessDeniedServlet extends HttpServlet { + /** + * + */ + private static final long serialVersionUID = -3239463647917811122L; + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, java.io.IOException { + processRequest(request, response); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + private void processRequest(HttpServletRequest request, + HttpServletResponse response) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + } +} diff --git a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java index 7f691196..bfbc0899 100644 --- a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java +++ b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java @@ -17,23 +17,23 @@ package com.gitblit.servlet; import java.io.IOException;
import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Iterator;
import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* The AccessRestrictionFilter is an AuthenticationFilter that confirms that the
* requested repository can be accessed by the anonymous or named user.
@@ -54,11 +54,15 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { protected IRepositoryManager repositoryManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ protected AccessRestrictionFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(authenticationManager);
+
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
}
/**
@@ -82,16 +86,17 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { *
* @return true if the filter allows repository creation
*/
- protected abstract boolean isCreationAllowed();
+ protected abstract boolean isCreationAllowed(String action);
/**
* Determine if the action may be executed on the repository.
*
* @param repository
* @param action
+ * @param method
* @return true if the action may be performed
*/
- protected abstract boolean isActionAllowed(RepositoryModel repository, String action);
+ protected abstract boolean isActionAllowed(RepositoryModel repository, String action, String method);
/**
* Determine if the repository requires authentication.
@@ -100,7 +105,7 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { * @param action
* @return true if authentication required
*/
- protected abstract boolean requiresAuthentication(RepositoryModel repository, String action);
+ protected abstract boolean requiresAuthentication(RepositoryModel repository, String action, String method);
/**
* Determine if the user can access the repository and perform the specified
@@ -124,7 +129,26 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { protected RepositoryModel createRepository(UserModel user, String repository, String action) {
return null;
}
-
+
+ /**
+ * Allows authentication header to be altered based on the action requested
+ * Default is WWW-Authenticate
+ * @param action
+ * @return authentication type header
+ */
+ protected String getAuthenticationHeader(String action) {
+ return "WWW-Authenticate";
+ }
+
+ /**
+ * Allows request headers to be used as part of filtering
+ * @param request
+ * @return true (default) if headers are valid, false otherwise
+ */
+ protected boolean hasValidRequestHeader(String action, HttpServletRequest request) {
+ return true;
+ }
+
/**
* doFilter does the actual work of preprocessing the request to ensure that
* the user may proceed.
@@ -161,13 +185,14 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { // Load the repository model
RepositoryModel model = repositoryManager.getRepositoryModel(repository);
if (model == null) {
- if (isCreationAllowed()) {
+ if (isCreationAllowed(urlRequestType)) {
if (user == null) {
// challenge client to provide credentials for creation. send 401.
if (runtimeManager.isDebugMode()) {
logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl));
}
- httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+
+ httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
} else {
@@ -186,7 +211,7 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { }
// Confirm that the action may be executed on the repository
- if (!isActionAllowed(model, urlRequestType)) {
+ if (!isActionAllowed(model, urlRequestType, httpRequest.getMethod())) {
logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
urlRequestType, model, HttpServletResponse.SC_FORBIDDEN));
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
@@ -208,13 +233,13 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { }
// BASIC authentication challenge and response processing
- if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType)) {
+ if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType, httpRequest.getMethod())) {
if (user == null) {
// challenge client to provide credentials. send 401.
if (runtimeManager.isDebugMode()) {
logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl));
}
- httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+ httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
} else {
@@ -246,4 +271,17 @@ public abstract class AccessRestrictionFilter extends AuthenticationFilter { // pass processing to the restricted servlet.
chain.doFilter(authenticatedRequest, httpResponse);
}
+
+ public static boolean hasContentInRequestHeader(HttpServletRequest request, String headerName, String content)
+ {
+ Iterator<String> headerItr = Collections.list(request.getHeaders(headerName)).iterator();
+
+ while (headerItr.hasNext()) {
+ if (headerItr.next().contains(content)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java index c21f8692..3093e635 100644 --- a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java +++ b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java @@ -1,184 +1,190 @@ -/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.servlet;
-
-import java.io.IOException;
-import java.security.Principal;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants;
-import com.gitblit.dagger.DaggerFilter;
-import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
-
-/**
- * The AuthenticationFilter is a servlet filter that preprocesses requests that
- * match its url pattern definition in the web.xml file.
- *
- * http://en.wikipedia.org/wiki/Basic_access_authentication
- *
- * @author James Moger
- *
- */
-public abstract class AuthenticationFilter extends DaggerFilter {
-
- protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
-
- protected static final String SESSION_SECURED = "com.gitblit.secured";
-
- protected transient Logger logger = LoggerFactory.getLogger(getClass());
-
- protected IAuthenticationManager authenticationManager;
-
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- this.authenticationManager = dagger.get(IAuthenticationManager.class);
- }
-
- /**
- * doFilter does the actual work of preprocessing the request to ensure that
- * the user may proceed.
- *
- * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
- * javax.servlet.ServletResponse, javax.servlet.FilterChain)
- */
- @Override
- public abstract void doFilter(final ServletRequest request, final ServletResponse response,
- final FilterChain chain) throws IOException, ServletException;
-
- /**
- * Allow the filter to require a client certificate to continue processing.
- *
- * @return true, if a client certificate is required
- */
- protected boolean requiresClientCertificate() {
- return false;
- }
-
- /**
- * Returns the full relative url of the request.
- *
- * @param httpRequest
- * @return url
- */
- protected String getFullUrl(HttpServletRequest httpRequest) {
- String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
- String url = httpRequest.getRequestURI().substring(servletUrl.length());
- String params = httpRequest.getQueryString();
- if (url.length() > 0 && url.charAt(0) == '/') {
- url = url.substring(1);
- }
- String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
- return fullUrl;
- }
-
- /**
- * Returns the user making the request, if the user has authenticated.
- *
- * @param httpRequest
- * @return user
- */
- protected UserModel getUser(HttpServletRequest httpRequest) {
- UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
- return user;
- }
-
- /**
- * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
- */
- protected void newSession(HttpServletRequest request, HttpServletResponse response) {
- HttpSession oldSession = request.getSession(false);
- if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
- synchronized (this) {
- Map<String, Object> attributes = new HashMap<String, Object>();
- Enumeration<String> e = oldSession.getAttributeNames();
- while (e.hasMoreElements()) {
- String name = e.nextElement();
- attributes.put(name, oldSession.getAttribute(name));
- oldSession.removeAttribute(name);
- }
- oldSession.invalidate();
-
- HttpSession newSession = request.getSession(true);
- newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
- for (Map.Entry<String, Object> entry : attributes.entrySet()) {
- newSession.setAttribute(entry.getKey(), entry.getValue());
- }
- }
- }
- }
-
- /**
- * Wraps a standard HttpServletRequest and overrides user principal methods.
- */
- public static class AuthenticatedRequest extends HttpServletRequestWrapper {
-
- private UserModel user;
-
- public AuthenticatedRequest(HttpServletRequest req) {
- super(req);
- user = DeepCopier.copy(UserModel.ANONYMOUS);
- }
-
- UserModel getUser() {
- return user;
- }
-
- void setUser(UserModel user) {
- this.user = user;
- }
-
- @Override
- public String getRemoteUser() {
- return user.username;
- }
-
- @Override
- public boolean isUserInRole(String role) {
- if (role.equals(Constants.ADMIN_ROLE)) {
- return user.canAdmin();
- }
- // Gitblit does not currently use actual roles in the traditional
- // servlet container sense. That is the reason this is marked
- // deprecated, but I may want to revisit this.
- return user.hasRepositoryPermission(role);
- }
-
- @Override
- public Principal getUserPrincipal() {
- return user;
- }
- }
-}
+/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.security.Principal; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.Constants.Role; +import com.gitblit.manager.IAuthenticationManager; +import com.gitblit.models.UserModel; +import com.gitblit.utils.DeepCopier; +import com.gitblit.utils.StringUtils; + +/** + * The AuthenticationFilter is a servlet filter that preprocesses requests that + * match its url pattern definition in the web.xml file. + * + * http://en.wikipedia.org/wiki/Basic_access_authentication + * + * @author James Moger + * + */ +public abstract class AuthenticationFilter implements Filter { + + protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\""; + + protected static final String SESSION_SECURED = "com.gitblit.secured"; + + protected transient Logger logger = LoggerFactory.getLogger(getClass()); + + protected IAuthenticationManager authenticationManager; + + protected AuthenticationFilter(IAuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + + /** + * doFilter does the actual work of preprocessing the request to ensure that + * the user may proceed. + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public abstract void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException; + + /** + * Allow the filter to require a client certificate to continue processing. + * + * @return true, if a client certificate is required + */ + protected boolean requiresClientCertificate() { + return false; + } + + /** + * Returns the full relative url of the request. + * + * @param httpRequest + * @return url + */ + protected String getFullUrl(HttpServletRequest httpRequest) { + String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath(); + String url = httpRequest.getRequestURI().substring(servletUrl.length()); + String params = httpRequest.getQueryString(); + if (url.length() > 0 && url.charAt(0) == '/') { + url = url.substring(1); + } + String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params)); + return fullUrl; + } + + /** + * Returns the user making the request, if the user has authenticated. + * + * @param httpRequest + * @return user + */ + protected UserModel getUser(HttpServletRequest httpRequest) { + UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate()); + return user; + } + + /** + * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication() + */ + protected void newSession(HttpServletRequest request, HttpServletResponse response) { + HttpSession oldSession = request.getSession(false); + if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) { + synchronized (this) { + Map<String, Object> attributes = new HashMap<String, Object>(); + Enumeration<String> e = oldSession.getAttributeNames(); + while (e.hasMoreElements()) { + String name = e.nextElement(); + attributes.put(name, oldSession.getAttribute(name)); + oldSession.removeAttribute(name); + } + oldSession.invalidate(); + + HttpSession newSession = request.getSession(true); + newSession.setAttribute(SESSION_SECURED, Boolean.TRUE); + for (Map.Entry<String, Object> entry : attributes.entrySet()) { + newSession.setAttribute(entry.getKey(), entry.getValue()); + } + } + } + } + + /** + * Wraps a standard HttpServletRequest and overrides user principal methods. + */ + public static class AuthenticatedRequest extends HttpServletRequestWrapper { + + private UserModel user; + + public AuthenticatedRequest(HttpServletRequest req) { + super(req); + user = DeepCopier.copy(UserModel.ANONYMOUS); + } + + UserModel getUser() { + return user; + } + + void setUser(UserModel user) { + this.user = user; + } + + @Override + public String getRemoteUser() { + return user.username; + } + + @Override + public boolean isUserInRole(String role) { + if (role.equals(Role.ADMIN.getRole())) { + return user.canAdmin(); + } + // Gitblit does not currently use actual roles in the traditional + // servlet container sense. That is the reason this is marked + // deprecated, but I may want to revisit this. + return user.hasRepositoryPermission(role); + } + + @Override + public Principal getUserPrincipal() { + return user; + } + } +} diff --git a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java index fa2152c6..85fbb745 100644 --- a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java +++ b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java @@ -36,7 +36,10 @@ import java.util.Set; import java.util.TreeSet;
import javax.imageio.ImageIO;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -55,20 +58,18 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* Handles requests for branch graphs
*
* @author James Moger
*
*/
-public class BranchGraphServlet extends DaggerServlet {
+@Singleton
+public class BranchGraphServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -87,20 +88,20 @@ public class BranchGraphServlet extends DaggerServlet { private IRepositoryManager repositoryManager;
- public BranchGraphServlet() {
- super();
+ @Inject
+ public BranchGraphServlet(
+ IStoredSettings settings,
+ IRepositoryManager repositoryManager) {
+
+ this.settings = settings;
+ this.repositoryManager = repositoryManager;
+
strokeCache = new Stroke[4];
for (int i = 1; i < strokeCache.length; i++) {
strokeCache[i] = new BasicStroke(i);
}
}
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- }
-
/**
* Returns an url to this servlet for the specified parameters.
*
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java index 42257a23..146f6d4f 100644 --- a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java +++ b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java @@ -15,7 +15,13 @@ */
package com.gitblit.servlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -27,8 +33,18 @@ import com.gitblit.models.UserModel; * @author James Moger
*
*/
+@Singleton
public class DownloadZipFilter extends AccessRestrictionFilter {
+ @Inject
+ public DownloadZipFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+ }
+
/**
* Extract the repository name from the url.
*
@@ -65,7 +81,7 @@ public class DownloadZipFilter extends AccessRestrictionFilter { * @return true if the filter allows repository creation
*/
@Override
- protected boolean isCreationAllowed() {
+ protected boolean isCreationAllowed(String action) {
return false;
}
@@ -74,10 +90,11 @@ public class DownloadZipFilter extends AccessRestrictionFilter { *
* @param repository
* @param action
+ * @param method
* @return true if the action may be performed
*/
@Override
- protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
return true;
}
@@ -86,10 +103,11 @@ public class DownloadZipFilter extends AccessRestrictionFilter { *
* @param repository
* @param action
+ * @param method
* @return true if authentication required
*/
@Override
- protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java index 6a64778c..07562561 100644 --- a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java +++ b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java @@ -20,7 +20,10 @@ import java.text.MessageFormat; import java.text.ParseException;
import java.util.Date;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.lib.Repository;
@@ -31,15 +34,12 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.utils.CompressionUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* Streams out a zip file from the specified repository for any tree path at any
* revision.
@@ -47,7 +47,8 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
-public class DownloadZipServlet extends DaggerServlet {
+@Singleton
+public class DownloadZipServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -76,10 +77,10 @@ public class DownloadZipServlet extends DaggerServlet { }
}
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ @Inject
+ public DownloadZipServlet(IStoredSettings settings, IRepositoryManager repositoryManager) {
+ this.settings = settings;
+ this.repositoryManager = repositoryManager;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java index c015021d..8a3f782a 100644 --- a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java +++ b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java @@ -18,6 +18,9 @@ import java.io.IOException; import java.text.MessageFormat; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; @@ -31,12 +34,9 @@ import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; -import com.gitblit.dagger.DaggerFilter; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.models.UserModel; -import dagger.ObjectGraph; - /** * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so. * It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if @@ -45,7 +45,8 @@ import dagger.ObjectGraph; * @author Laurens Vrijnsen * */ -public class EnforceAuthenticationFilter extends DaggerFilter { +@Singleton +public class EnforceAuthenticationFilter implements Filter { protected transient Logger logger = LoggerFactory.getLogger(getClass()); @@ -53,10 +54,21 @@ public class EnforceAuthenticationFilter extends DaggerFilter { private IAuthenticationManager authenticationManager; + @Inject + public EnforceAuthenticationFilter( + IStoredSettings settings, + IAuthenticationManager authenticationManager) { + + this.settings = settings; + this.authenticationManager = authenticationManager; + } + @Override - protected void inject(ObjectGraph dagger, FilterConfig filterConfig) { - this.settings = dagger.get(IStoredSettings.class); - this.authenticationManager = dagger.get(IAuthenticationManager.class); + public void init(FilterConfig config) { + } + + @Override + public void destroy() { } /* @@ -87,12 +99,4 @@ public class EnforceAuthenticationFilter extends DaggerFilter { chain.doFilter(request, response); } } - - - /* - * @see javax.servlet.Filter#destroy() - */ - @Override - public void destroy() { - } } diff --git a/src/main/java/com/gitblit/servlet/FederationServlet.java b/src/main/java/com/gitblit/servlet/FederationServlet.java index acbc0021..78709c9c 100644 --- a/src/main/java/com/gitblit/servlet/FederationServlet.java +++ b/src/main/java/com/gitblit/servlet/FederationServlet.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Map;
import java.util.Set;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Constants.FederationRequest;
@@ -43,14 +45,13 @@ import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
-import dagger.ObjectGraph;
-
/**
* Handles federation requests.
*
* @author James Moger
*
*/
+@Singleton
public class FederationServlet extends JsonServlet {
private static final long serialVersionUID = 1L;
@@ -63,12 +64,17 @@ public class FederationServlet extends JsonServlet { private IFederationManager federationManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.userManager = dagger.get(IUserManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.federationManager = dagger.get(IFederationManager.class);
+ @Inject
+ public FederationServlet(
+ IStoredSettings settings,
+ IUserManager userManager,
+ IRepositoryManager repositoryManager,
+ IFederationManager federationManager) {
+
+ this.settings = settings;
+ this.userManager = userManager;
+ this.repositoryManager = repositoryManager;
+ this.federationManager = federationManager;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/FilestoreServlet.java b/src/main/java/com/gitblit/servlet/FilestoreServlet.java new file mode 100644 index 00000000..19751483 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/FilestoreServlet.java @@ -0,0 +1,493 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.models.FilestoreModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.FilestoreModel.Status; +import com.gitblit.manager.FilestoreManager; +import com.gitblit.manager.IGitblit; +import com.gitblit.models.UserModel; +import com.gitblit.utils.JsonUtils; + + +/** + * Handles large file storage as per the Git LFS v1 Batch API + * + * Further details can be found at https://github.com/github/git-lfs + * + * @author Paul Martin + */ +@Singleton +public class FilestoreServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + public static final int PROTOCOL_VERSION = 1; + + public static final String GIT_LFS_META_MIME = "application/vnd.git-lfs+json"; + + public static final String REGEX_PATH = "^(.*?)/(r|git)/(.*?)/info/lfs/objects/(batch|" + Constants.REGEX_SHA256 + ")"; + public static final int REGEX_GROUP_BASE_URI = 1; + public static final int REGEX_GROUP_PREFIX = 2; + public static final int REGEX_GROUP_REPOSITORY = 3; + public static final int REGEX_GROUP_ENDPOINT = 4; + + protected final Logger logger; + + private static IGitblit gitblit; + + @Inject + public FilestoreServlet(IStoredSettings settings, IGitblit gitblit) { + + super(); + logger = LoggerFactory.getLogger(getClass()); + + FilestoreServlet.gitblit = gitblit; + } + + + /** + * Handles batch upload request (metadata) + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + @Override + protected void doPost(HttpServletRequest request, + HttpServletResponse response) throws ServletException ,IOException { + + UrlInfo info = getInfoFromRequest(request); + if (info == null) { + sendError(response, HttpServletResponse.SC_NOT_FOUND); + return; + } + + //Post is for batch operations so no oid should be defined + if (info.oid != null) { + sendError(response, HttpServletResponse.SC_BAD_REQUEST); + return; + } + + IGitLFS.Batch batch = deserialize(request, response, IGitLFS.Batch.class); + + if (batch == null) { + sendError(response, HttpServletResponse.SC_BAD_REQUEST); + return; + } + + UserModel user = getUserOrAnonymous(request); + + IGitLFS.BatchResponse batchResponse = new IGitLFS.BatchResponse(); + + if (batch.operation.equalsIgnoreCase("upload")) { + for (IGitLFS.Request item : batch.objects) { + + Status state = gitblit.addObject(item.oid, item.size, user, info.repository); + + batchResponse.objects.add(getResponseForUpload(info.baseUrl, item.oid, item.size, user.getName(), info.repository.name, state)); + } + } else if (batch.operation.equalsIgnoreCase("download")) { + for (IGitLFS.Request item : batch.objects) { + + Status state = gitblit.downloadBlob(item.oid, user, info.repository, null); + batchResponse.objects.add(getResponseForDownload(info.baseUrl, item.oid, item.size, user.getName(), info.repository.name, state)); + } + } else { + sendError(response, HttpServletResponse.SC_NOT_IMPLEMENTED); + return; + } + + response.setStatus(HttpServletResponse.SC_OK); + serialize(response, batchResponse); + } + + /** + * Handles the actual upload (BLOB) + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + @Override + protected void doPut(HttpServletRequest request, + HttpServletResponse response) throws ServletException ,IOException { + + UrlInfo info = getInfoFromRequest(request); + + if (info == null) { + sendError(response, HttpServletResponse.SC_NOT_FOUND); + return; + } + + //Put is a singular operation so must have oid + if (info.oid == null) { + sendError(response, HttpServletResponse.SC_BAD_REQUEST); + return; + } + + UserModel user = getUserOrAnonymous(request); + long size = FilestoreManager.UNDEFINED_SIZE; + + + + FilestoreModel.Status status = gitblit.uploadBlob(info.oid, size, user, info.repository, request.getInputStream()); + IGitLFS.Response responseObject = getResponseForUpload(info.baseUrl, info.oid, size, user.getName(), info.repository.name, status); + + logger.info(MessageFormat.format("FILESTORE-AUDIT {0}:{4} {1} {2}@{3}", + "PUT", info.oid, user.getName(), info.repository.name, status.toString() )); + + if (responseObject.error == null) { + response.setStatus(responseObject.successCode); + } else { + serialize(response, responseObject.error); + } + }; + + /** + * Handles a download + * Treated as hypermedia request if accept header contains Git-LFS MIME + * otherwise treated as a download of the blob + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException ,IOException { + + UrlInfo info = getInfoFromRequest(request); + + if (info == null || info.oid == null) { + sendError(response, HttpServletResponse.SC_NOT_FOUND); + return; + } + + UserModel user = getUserOrAnonymous(request); + + FilestoreModel model = gitblit.getObject(info.oid, user, info.repository); + long size = FilestoreManager.UNDEFINED_SIZE; + + boolean isMetaRequest = AccessRestrictionFilter.hasContentInRequestHeader(request, "Accept", GIT_LFS_META_MIME); + FilestoreModel.Status status = Status.Unavailable; + + if (model != null) { + size = model.getSize(); + status = model.getStatus(); + } + + if (!isMetaRequest) { + status = gitblit.downloadBlob(info.oid, user, info.repository, response.getOutputStream()); + + logger.info(MessageFormat.format("FILESTORE-AUDIT {0}:{4} {1} {2}@{3}", + "GET", info.oid, user.getName(), info.repository.name, status.toString() )); + } + + if (status == Status.Error_Unexpected_Stream_End) { + return; + } + + IGitLFS.Response responseObject = getResponseForDownload(info.baseUrl, + info.oid, size, user.getName(), info.repository.name, status); + + if (responseObject.error == null) { + response.setStatus(responseObject.successCode); + + if (isMetaRequest) { + serialize(response, responseObject); + } + } else { + response.setStatus(responseObject.error.code); + serialize(response, responseObject.error); + } + }; + + private void sendError(HttpServletResponse response, int code) throws IOException { + + String msg = ""; + + switch (code) + { + case HttpServletResponse.SC_NOT_FOUND: msg = "Not Found"; break; + case HttpServletResponse.SC_NOT_IMPLEMENTED: msg = "Not Implemented"; break; + case HttpServletResponse.SC_BAD_REQUEST: msg = "Malformed Git-LFS request"; break; + + default: msg = "Unknown Error"; + } + + response.setStatus(code); + serialize(response, new IGitLFS.ObjectError(code, msg)); + } + + @SuppressWarnings("incomplete-switch") + private IGitLFS.Response getResponseForUpload(String baseUrl, String oid, long size, String user, String repo, FilestoreModel.Status state) { + + switch (state) { + case AuthenticationRequired: + return new IGitLFS.Response(oid, size, 401, MessageFormat.format("Authentication required to write to repository {0}", repo)); + case Error_Unauthorized: + return new IGitLFS.Response(oid, size, 403, MessageFormat.format("User {0}, does not have write permissions to repository {1}", user, repo)); + case Error_Exceeds_Size_Limit: + return new IGitLFS.Response(oid, size, 509, MessageFormat.format("Object is larger than allowed limit of {1}", gitblit.getMaxUploadSize())); + case Error_Hash_Mismatch: + return new IGitLFS.Response(oid, size, 422, "Hash mismatch"); + case Error_Invalid_Oid: + return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid oid", oid)); + case Error_Invalid_Size: + return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid size", size)); + case Error_Size_Mismatch: + return new IGitLFS.Response(oid, size, 422, "Object size mismatch"); + case Deleted: + return new IGitLFS.Response(oid, size, 410, "Object was deleted : ".concat("TBD Reason") ); + case Upload_In_Progress: + return new IGitLFS.Response(oid, size, 503, "File currently being uploaded by another user"); + case Unavailable: + return new IGitLFS.Response(oid, size, 404, MessageFormat.format("Repository {0}, does not exist for user {1}", repo, user)); + case Upload_Pending: + return new IGitLFS.Response(oid, size, 202, "upload", getObjectUri(baseUrl, repo, oid) ); + case Available: + return new IGitLFS.Response(oid, size, 200, "upload", getObjectUri(baseUrl, repo, oid) ); + } + + return new IGitLFS.Response(oid, size, 500, "Unknown Error"); + } + + @SuppressWarnings("incomplete-switch") + private IGitLFS.Response getResponseForDownload(String baseUrl, String oid, long size, String user, String repo, FilestoreModel.Status state) { + + switch (state) { + case Error_Unauthorized: + return new IGitLFS.Response(oid, size, 403, MessageFormat.format("User {0}, does not have read permissions to repository {1}", user, repo)); + case Error_Invalid_Oid: + return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid oid", oid)); + case Error_Unknown: + return new IGitLFS.Response(oid, size, 500, "Unknown Error"); + case Deleted: + return new IGitLFS.Response(oid, size, 410, "Object was deleted : ".concat("TBD Reason") ); + case Available: + return new IGitLFS.Response(oid, size, 200, "download", getObjectUri(baseUrl, repo, oid) ); + } + + return new IGitLFS.Response(oid, size, 404, "Object not available"); + } + + + private String getObjectUri(String baseUrl, String repo, String oid) { + return baseUrl + "/" + repo + "/" + Constants.R_LFS + "objects/" + oid; + } + + + protected void serialize(HttpServletResponse response, Object o) throws IOException { + if (o != null) { + // Send JSON response + String json = JsonUtils.toJsonString(o); + response.setCharacterEncoding(Constants.ENCODING); + response.setContentType(GIT_LFS_META_MIME); + response.getWriter().append(json); + } + } + + protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, + Class<X> clazz) { + + String json = ""; + try { + + json = readJson(request, response); + + return JsonUtils.fromJsonString(json.toString(), clazz); + + } catch (Exception e) { + //Intentional silent fail + } + + return null; + } + + private String readJson(HttpServletRequest request, HttpServletResponse response) + throws IOException { + BufferedReader reader = request.getReader(); + StringBuilder json = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + json.append(line); + } + reader.close(); + + if (json.length() == 0) { + logger.error(MessageFormat.format("Failed to receive json data from {0}", + request.getRemoteAddr())); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return null; + } + return json.toString(); + } + + private UserModel getUserOrAnonymous(HttpServletRequest r) { + UserModel user = (UserModel) r.getUserPrincipal(); + if (user != null) { return user; } + return UserModel.ANONYMOUS; + } + + private static class UrlInfo { + public RepositoryModel repository; + public String oid; + public String baseUrl; + + public UrlInfo(RepositoryModel repo, String oid, String baseUrl) { + this.repository = repo; + this.oid = oid; + this.baseUrl = baseUrl; + } + } + + public static UrlInfo getInfoFromRequest(HttpServletRequest httpRequest) { + + String url = httpRequest.getRequestURL().toString(); + Pattern p = Pattern.compile(REGEX_PATH); + Matcher m = p.matcher(url); + + + if (m.find()) { + RepositoryModel repo = gitblit.getRepositoryModel(m.group(REGEX_GROUP_REPOSITORY)); + String baseUrl = m.group(REGEX_GROUP_BASE_URI) + "/" + m.group(REGEX_GROUP_PREFIX); + + if (m.group(REGEX_GROUP_ENDPOINT).equals("batch")) { + return new UrlInfo(repo, null, baseUrl); + } else { + return new UrlInfo(repo, m.group(REGEX_GROUP_ENDPOINT), baseUrl); + } + } + + return null; + } + + + public interface IGitLFS { + + @SuppressWarnings("serial") + public class Request implements Serializable + { + public String oid; + public long size; + } + + + @SuppressWarnings("serial") + public class Batch implements Serializable + { + public String operation; + public List<Request> objects; + } + + + @SuppressWarnings("serial") + public class Response implements Serializable + { + public String oid; + public long size; + public Map<String, HyperMediaLink> actions; + public ObjectError error; + public transient int successCode; + + public Response(String id, long itemSize, int errorCode, String errorText) { + oid = id; + size = itemSize; + actions = null; + successCode = 0; + error = new ObjectError(errorCode, errorText); + } + + public Response(String id, long itemSize, int actionCode, String action, String uri) { + oid = id; + size = itemSize; + error = null; + successCode = actionCode; + actions = new HashMap<String, HyperMediaLink>(); + actions.put(action, new HyperMediaLink(action, uri)); + } + + } + + @SuppressWarnings("serial") + public class BatchResponse implements Serializable { + public List<Response> objects; + + public BatchResponse() { + objects = new ArrayList<Response>(); + } + } + + + @SuppressWarnings("serial") + public class ObjectError implements Serializable + { + public String message; + public int code; + public String documentation_url; + public Integer request_id; + + public ObjectError(int errorCode, String errorText) { + code = errorCode; + message = errorText; + request_id = null; + } + } + + @SuppressWarnings("serial") + public class HyperMediaLink implements Serializable + { + public String href; + public transient String header; + //public Date expires_at; + + public HyperMediaLink(String action, String uri) { + header = action; + href = uri; + } + } + } + + + +} diff --git a/src/main/java/com/gitblit/servlet/GitFilter.java b/src/main/java/com/gitblit/servlet/GitFilter.java index bb3d3216..27408f02 100644 --- a/src/main/java/com/gitblit/servlet/GitFilter.java +++ b/src/main/java/com/gitblit/servlet/GitFilter.java @@ -17,7 +17,9 @@ package com.gitblit.servlet; import java.text.MessageFormat;
-import javax.servlet.FilterConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import javax.servlet.http.HttpServletRequest;
import com.gitblit.Constants.AccessRestrictionType;
@@ -25,13 +27,14 @@ import com.gitblit.Constants.AuthorizationControl; import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* The GitFilter is an AccessRestrictionFilter which ensures that Git client
* requests for push, clone, or view restricted repositories are authenticated
@@ -40,24 +43,34 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
+@Singleton
public class GitFilter extends AccessRestrictionFilter {
protected static final String gitReceivePack = "/git-receive-pack";
protected static final String gitUploadPack = "/git-upload-pack";
-
+
+ protected static final String gitLfs = "/info/lfs";
+
protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
- "/objects" };
+ "/objects", gitLfs };
private IStoredSettings settings;
private IFederationManager federationManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.settings = dagger.get(IStoredSettings.class);
- this.federationManager = dagger.get(IFederationManager.class);
+ @Inject
+ public GitFilter(
+ IStoredSettings settings,
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager,
+ IFederationManager federationManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+
+ this.settings = settings;
+ this.federationManager = federationManager;
}
/**
@@ -106,6 +119,8 @@ public class GitFilter extends AccessRestrictionFilter { return gitReceivePack;
} else if (suffix.contains("?service=git-upload-pack")) {
return gitUploadPack;
+ } else if (suffix.startsWith(gitLfs)) {
+ return gitLfs;
} else {
return gitUploadPack;
}
@@ -134,7 +149,13 @@ public class GitFilter extends AccessRestrictionFilter { * @return true if the server allows repository creation on-push
*/
@Override
- protected boolean isCreationAllowed() {
+ protected boolean isCreationAllowed(String action) {
+
+ //Repository must already exist before large files can be deposited
+ if (action.equals(gitLfs)) {
+ return false;
+ }
+
return settings.getBoolean(Keys.git.allowCreateOnPush, true);
}
@@ -146,9 +167,15 @@ public class GitFilter extends AccessRestrictionFilter { * @return true if the action may be performed
*/
@Override
- protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
// the log here has been moved into ReceiveHook to provide clients with
// error messages
+ if (gitLfs.equals(action)) {
+ if (!method.matches("GET|POST|PUT|HEAD")) {
+ return false;
+ }
+ }
+
return true;
}
@@ -162,16 +189,25 @@ public class GitFilter extends AccessRestrictionFilter { *
* @param repository
* @param action
+ * @param method
* @return true if authentication required
*/
@Override
- protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
if (gitUploadPack.equals(action)) {
// send to client
return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
} else if (gitReceivePack.equals(action)) {
// receive from client
return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+ } else if (gitLfs.equals(action)) {
+
+ if (method.matches("GET|HEAD")) {
+ return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
+ } else {
+ //NOTE: Treat POST as PUT as as without reading message type cannot determine
+ return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+ }
}
return false;
}
@@ -220,6 +256,12 @@ public class GitFilter extends AccessRestrictionFilter { @Override
protected RepositoryModel createRepository(UserModel user, String repository, String action) {
boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
+
+ if (action.equals(gitLfs)) {
+ //Repository must already exist for any filestore actions
+ return null;
+ }
+
if (isPush) {
if (user.canCreate(repository)) {
// user is pushing to a new repository
@@ -271,4 +313,40 @@ public class GitFilter extends AccessRestrictionFilter { // repository could not be created or action was not a push
return null;
}
+
+ /**
+ * Git lfs action uses an alternative authentication header,
+ *
+ * @param action
+ * @return
+ */
+ @Override
+ protected String getAuthenticationHeader(String action) {
+
+ if (action.equals(gitLfs)) {
+ return "LFS-Authenticate";
+ }
+
+ return super.getAuthenticationHeader(action);
+ }
+
+ /**
+ * Interrogates the request headers based on the action
+ * @param action
+ * @param request
+ * @return
+ */
+ @Override
+ protected boolean hasValidRequestHeader(String action,
+ HttpServletRequest request) {
+
+ if (action.equals(gitLfs) && request.getMethod().equals("POST")) {
+ if ( !hasContentInRequestHeader(request, "Accept", FilestoreServlet.GIT_LFS_META_MIME)
+ || !hasContentInRequestHeader(request, "Content-Type", FilestoreServlet.GIT_LFS_META_MIME)) {
+ return false;
+ }
+ }
+
+ return super.hasValidRequestHeader(action, request);
+ }
}
diff --git a/src/main/java/com/gitblit/servlet/GitServlet.java b/src/main/java/com/gitblit/servlet/GitServlet.java index 93fe31d0..941b4c5e 100644 --- a/src/main/java/com/gitblit/servlet/GitServlet.java +++ b/src/main/java/com/gitblit/servlet/GitServlet.java @@ -20,6 +20,8 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import java.io.IOException;
import java.util.Enumeration;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
@@ -33,14 +35,11 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.http.server.GitFilter;
-import com.gitblit.dagger.DaggerContext;
import com.gitblit.git.GitblitReceivePackFactory;
import com.gitblit.git.GitblitUploadPackFactory;
import com.gitblit.git.RepositoryResolver;
import com.gitblit.manager.IGitblit;
-import dagger.ObjectGraph;
-
/**
* The GitServlet provides http/https access to Git repositories.
* Access to this servlet is protected by the GitFilter.
@@ -48,24 +47,23 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
+@Singleton
public class GitServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final GitFilter gitFilter;
- public GitServlet() {
+ @Inject
+ public GitServlet(IGitblit gitblit) {
gitFilter = new GitFilter();
+ gitFilter.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(gitblit));
+ gitFilter.setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>(gitblit));
+ gitFilter.setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>(gitblit));
}
@Override
public void init(final ServletConfig config) throws ServletException {
- ServletContext context = config.getServletContext();
- ObjectGraph dagger = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
- IGitblit gitblit = dagger.get(IGitblit.class);
- gitFilter.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(gitblit));
- gitFilter.setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>(gitblit));
- gitFilter.setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>(gitblit));
gitFilter.init(new FilterConfig() {
@Override
diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java index e5c59bd0..fb8f6b9d 100644 --- a/src/main/java/com/gitblit/servlet/GitblitContext.java +++ b/src/main/java/com/gitblit/servlet/GitblitContext.java @@ -31,16 +31,20 @@ import javax.naming.NamingException; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.gitblit.Constants; -import com.gitblit.DaggerModule; import com.gitblit.FileSettings; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.WebXmlSettings; -import com.gitblit.dagger.DaggerContext; import com.gitblit.extensions.LifeCycleListener; +import com.gitblit.guice.CoreModule; +import com.gitblit.guice.WebModule; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IFilestoreManager; import com.gitblit.manager.IGitblit; import com.gitblit.manager.IManager; import com.gitblit.manager.INotificationManager; @@ -48,28 +52,34 @@ import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; import com.gitblit.manager.IUserManager; +import com.gitblit.tickets.ITicketService; import com.gitblit.transport.ssh.IPublicKeyManager; import com.gitblit.utils.ContainerUtils; import com.gitblit.utils.StringUtils; - -import dagger.ObjectGraph; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.servlet.GuiceServletContextListener; /** * This class is the main entry point for the entire webapp. It is a singleton * created manually by Gitblit GO or dynamically by the WAR/Express servlet - * container. This class instantiates and starts all managers. Servlets and - * filters are instantiated defined in web.xml and instantiated by the servlet - * container, but those servlets and filters use Dagger to manually inject their - * dependencies. + * container. This class instantiates and starts all managers. + * + * Servlets and filters are injected which allows Gitblit to be completely + * code-driven. * * @author James Moger * */ -public class GitblitContext extends DaggerContext { +public class GitblitContext extends GuiceServletContextListener { private static GitblitContext gitblit; + protected final Logger logger = LoggerFactory.getLogger(getClass()); + private final List<IManager> managers = new ArrayList<IManager>(); private final IStoredSettings goSettings; @@ -111,12 +121,16 @@ public class GitblitContext extends DaggerContext { return null; } + @Override + protected Injector getInjector() { + return Guice.createInjector(getModules()); + } + /** - * Returns Gitblit's Dagger injection modules. + * Returns Gitblit's Guice injection modules. */ - @Override - protected Object [] getModules() { - return new Object [] { new DaggerModule() }; + protected AbstractModule [] getModules() { + return new AbstractModule [] { new CoreModule(), new WebModule() }; } /** @@ -127,18 +141,20 @@ public class GitblitContext extends DaggerContext { */ @Override public final void contextInitialized(ServletContextEvent contextEvent) { + super.contextInitialized(contextEvent); + ServletContext context = contextEvent.getServletContext(); - configureContext(context); + startCore(context); } /** * Prepare runtime settings and start all manager instances. */ - protected void configureContext(ServletContext context) { - ObjectGraph injector = getInjector(context); + protected void startCore(ServletContext context) { + Injector injector = (Injector) context.getAttribute(Injector.class.getName()); // create the runtime settings object - IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); + IStoredSettings runtimeSettings = injector.getInstance(IStoredSettings.class); final File baseFolder; if (goSettings != null) { @@ -168,7 +184,7 @@ public class GitblitContext extends DaggerContext { // Manually configure IRuntimeManager logManager(IRuntimeManager.class); - IRuntimeManager runtime = injector.get(IRuntimeManager.class); + IRuntimeManager runtime = injector.getInstance(IRuntimeManager.class); runtime.setBaseFolder(baseFolder); runtime.getStatus().isGO = goSettings != null; runtime.getStatus().servletContainer = context.getServerInfo(); @@ -186,7 +202,10 @@ public class GitblitContext extends DaggerContext { startManager(injector, IRepositoryManager.class); startManager(injector, IProjectManager.class); startManager(injector, IFederationManager.class); + startManager(injector, ITicketService.class); startManager(injector, IGitblit.class); + startManager(injector, IServicesManager.class); + startManager(injector, IFilestoreManager.class); // start the plugin manager last so that plugins can depend on // deterministic access to all other managers in their start() methods @@ -196,7 +215,7 @@ public class GitblitContext extends DaggerContext { logger.info("All managers started."); logger.info(""); - IPluginManager pluginManager = injector.get(IPluginManager.class); + IPluginManager pluginManager = injector.getInstance(IPluginManager.class); for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) { try { listener.onStartup(); @@ -236,17 +255,21 @@ public class GitblitContext extends DaggerContext { return defaultBaseFolder; } - protected <X extends IManager> X loadManager(ObjectGraph injector, Class<X> clazz) { - X x = injector.get(clazz); + protected <X extends IManager> X loadManager(Injector injector, Class<X> clazz) { + X x = injector.getInstance(clazz); return x; } - protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) { + protected <X extends IManager> X startManager(Injector injector, Class<X> clazz) { X x = loadManager(injector, clazz); logManager(clazz); - x.start(); - managers.add(x); - return x; + return startManager(x); + } + + protected <X extends IManager> X startManager(X x) { + x.start(); + managers.add(x); + return x; } protected void logManager(Class<? extends IManager> clazz) { @@ -254,11 +277,17 @@ public class GitblitContext extends DaggerContext { logger.info("----[{}]----", clazz.getName()); } + @Override + public final void contextDestroyed(ServletContextEvent contextEvent) { + super.contextDestroyed(contextEvent); + ServletContext context = contextEvent.getServletContext(); + destroyContext(context); + } + /** * Gitblit is being shutdown either because the servlet container is * shutting down or because the servlet container is re-deploying Gitblit. */ - @Override protected void destroyContext(ServletContext context) { logger.info("Gitblit context destroyed by servlet container."); @@ -341,12 +370,10 @@ public class GitblitContext extends DaggerContext { baseFolder.mkdirs(); // try to extract the data folder resource to the baseFolder - File localSettings = new File(baseFolder, "gitblit.properties"); - if (!localSettings.exists()) { - extractResources(context, "/WEB-INF/data/", baseFolder); - } + extractResources(context, "/WEB-INF/data/", baseFolder); // delegate all config to baseFolder/gitblit.properties file + File localSettings = new File(baseFolder, "gitblit.properties"); FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); // merge the stored settings into the runtime settings diff --git a/src/main/java/com/gitblit/servlet/JsonServlet.java b/src/main/java/com/gitblit/servlet/JsonServlet.java index 4378c8a6..abc0f292 100644 --- a/src/main/java/com/gitblit/servlet/JsonServlet.java +++ b/src/main/java/com/gitblit/servlet/JsonServlet.java @@ -21,6 +21,7 @@ import java.lang.reflect.Type; import java.text.MessageFormat;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -28,7 +29,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.StringUtils;
@@ -38,7 +38,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger
*
*/
-public abstract class JsonServlet extends DaggerServlet {
+public abstract class JsonServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/com/gitblit/servlet/LogoServlet.java b/src/main/java/com/gitblit/servlet/LogoServlet.java index 96f34afd..d5d298b1 100644 --- a/src/main/java/com/gitblit/servlet/LogoServlet.java +++ b/src/main/java/com/gitblit/servlet/LogoServlet.java @@ -21,24 +21,25 @@ import java.io.IOException; import java.io.InputStream;
import java.io.OutputStream;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRuntimeManager;
-import dagger.ObjectGraph;
-
/**
* Handles requests for logo.png
*
* @author James Moger
*
*/
-public class LogoServlet extends DaggerServlet {
+@Singleton
+public class LogoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -46,9 +47,9 @@ public class LogoServlet extends DaggerServlet { private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public LogoServlet(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
}
@Override
diff --git a/src/main/java/com/gitblit/servlet/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java index e07d9b3b..1d6c3db9 100644 --- a/src/main/java/com/gitblit/servlet/PagesFilter.java +++ b/src/main/java/com/gitblit/servlet/PagesFilter.java @@ -15,6 +15,13 @@ */
package com.gitblit.servlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+
/**
* The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
@@ -23,7 +30,17 @@ package com.gitblit.servlet; * @author James Moger
*
*/
+
+@Singleton
public class PagesFilter extends RawFilter {
+ @Inject
+ public PagesFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+ }
}
diff --git a/src/main/java/com/gitblit/servlet/PagesServlet.java b/src/main/java/com/gitblit/servlet/PagesServlet.java index defd9e63..1473e966 100644 --- a/src/main/java/com/gitblit/servlet/PagesServlet.java +++ b/src/main/java/com/gitblit/servlet/PagesServlet.java @@ -1,103 +1,116 @@ -/*
- * Copyright 2012 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.servlet;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.utils.JGitUtils;
-
-/**
- * Serves the content of a gh-pages branch.
- *
- * @author James Moger
- *
- */
-public class PagesServlet extends RawServlet {
-
- private static final long serialVersionUID = 1L;
-
-
- /**
- * Returns an url to this servlet for the specified parameters.
- *
- * @param baseURL
- * @param repository
- * @param path
- * @return an url
- */
- public static String asLink(String baseURL, String repository, String path) {
- if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
- baseURL = baseURL.substring(0, baseURL.length() - 1);
- }
- return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
- }
-
- @Override
- protected String getBranch(String repository, HttpServletRequest request) {
- return "gh-pages";
- }
-
- @Override
- protected String getPath(String repository, String branch, HttpServletRequest request) {
- String pi = request.getPathInfo().substring(1);
- if (pi.equals(repository)) {
- return "";
- }
- String path = pi.substring(pi.indexOf(repository) + repository.length() + 1);
- if (path.endsWith("/")) {
- path = path.substring(0, path.length() - 1);
- }
- return path;
- }
-
- @Override
- protected boolean renderIndex() {
- return true;
- }
-
- @Override
- protected void setContentType(HttpServletResponse response, String contentType) {
- response.setContentType(contentType);;
- }
-
- @Override
- protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository,
- RevCommit commit, String requestedPath) throws IOException {
-
- response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
- response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
-
- return super.streamFromRepo(request, response, repository, commit, requestedPath);
- }
-
- @Override
- protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
- response.setDateHeader("Last-Modified", date.getTime());
- response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
-
- super.sendContent(response, date, is);
- }
-}
+/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; + +import com.gitblit.Constants; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.utils.JGitUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Serves the content of a gh-pages branch. + * + * @author James Moger + * + */ +@Singleton +public class PagesServlet extends RawServlet { + + private static final long serialVersionUID = 1L; + + + /** + * Returns an url to this servlet for the specified parameters. + * + * @param baseURL + * @param repository + * @param path + * @return an url + */ + public static String asLink(String baseURL, String repository, String path) { + if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { + baseURL = baseURL.substring(0, baseURL.length() - 1); + } + return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path)); + } + + @Inject + public PagesServlet( + IRuntimeManager runtimeManager, + IRepositoryManager repositoryManager) { + + super(runtimeManager, repositoryManager); + } + + @Override + protected String getBranch(String repository, HttpServletRequest request) { + return "gh-pages"; + } + + @Override + protected String getPath(String repository, String branch, HttpServletRequest request) { + String pi = request.getPathInfo().substring(1); + if (pi.equals(repository)) { + return ""; + } + String path = pi.substring(pi.indexOf(repository) + repository.length() + 1); + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + return path; + } + + @Override + protected boolean renderIndex() { + return true; + } + + @Override + protected void setContentType(HttpServletResponse response, String contentType) { + response.setContentType(contentType);; + } + + @Override + protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository, + RevCommit commit, String requestedPath) throws IOException { + + response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime()); + response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); + + return super.streamFromRepo(request, response, repository, commit, requestedPath); + } + + @Override + protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException { + response.setDateHeader("Last-Modified", date.getTime()); + response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); + + super.sendContent(response, date, is); + } +} diff --git a/src/main/java/com/gitblit/servlet/ProxyFilter.java b/src/main/java/com/gitblit/servlet/ProxyFilter.java index 46f59de9..d7f096ac 100644 --- a/src/main/java/com/gitblit/servlet/ProxyFilter.java +++ b/src/main/java/com/gitblit/servlet/ProxyFilter.java @@ -16,9 +16,13 @@ package com.gitblit.servlet; import java.io.IOException; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; @@ -27,13 +31,10 @@ import javax.servlet.ServletResponse; import ro.fortsoft.pf4j.PluginWrapper; -import com.gitblit.dagger.DaggerFilter; import com.gitblit.extensions.HttpRequestFilter; import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IRuntimeManager; -import dagger.ObjectGraph; - /** * A request filter than allows registered extension request filters to access * request data. The intended purpose is for server monitoring plugins. @@ -41,15 +42,29 @@ import dagger.ObjectGraph; * @author David Ostrovsky * @since 1.6.0 */ -public class ProxyFilter extends DaggerFilter { - private List<HttpRequestFilter> filters; +@Singleton +public class ProxyFilter implements Filter { + private final IRuntimeManager runtimeManager; + + private final IPluginManager pluginManager; + + private final List<HttpRequestFilter> filters; + + @Inject + public ProxyFilter( + IRuntimeManager runtimeManager, + IPluginManager pluginManager) { + + this.runtimeManager = runtimeManager; + this.pluginManager = pluginManager; + this.filters = new ArrayList<>(); + + } @Override - protected void inject(ObjectGraph dagger, FilterConfig filterConfig) throws ServletException { - IRuntimeManager runtimeManager = dagger.get(IRuntimeManager.class); - IPluginManager pluginManager = dagger.get(IPluginManager.class); + public void init(FilterConfig filterConfig) throws ServletException { - filters = pluginManager.getExtensions(HttpRequestFilter.class); + filters.addAll(pluginManager.getExtensions(HttpRequestFilter.class)); for (HttpRequestFilter f : filters) { // wrap the filter config for Gitblit settings retrieval PluginWrapper pluginWrapper = pluginManager.whichPlugin(f.getClass()); diff --git a/src/main/java/com/gitblit/servlet/PtServlet.java b/src/main/java/com/gitblit/servlet/PtServlet.java index f69b444d..5f577f85 100644 --- a/src/main/java/com/gitblit/servlet/PtServlet.java +++ b/src/main/java/com/gitblit/servlet/PtServlet.java @@ -22,7 +22,10 @@ import java.io.IOException; import java.io.InputStream;
import java.io.OutputStream;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -34,11 +37,8 @@ import org.apache.commons.compress.compressors.CompressorOutputStream; import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.eclipse.jgit.lib.FileMode;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRuntimeManager;
-import dagger.ObjectGraph;
-
/**
* Handles requests for the Barnum pt (patchset tool).
*
@@ -47,7 +47,8 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
-public class PtServlet extends DaggerServlet {
+@Singleton
+public class PtServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -55,9 +56,9 @@ public class PtServlet extends DaggerServlet { private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public PtServlet(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
}
@Override
diff --git a/src/main/java/com/gitblit/servlet/RawFilter.java b/src/main/java/com/gitblit/servlet/RawFilter.java index 34989c98..8913a197 100644 --- a/src/main/java/com/gitblit/servlet/RawFilter.java +++ b/src/main/java/com/gitblit/servlet/RawFilter.java @@ -15,9 +15,15 @@ */
package com.gitblit.servlet;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
import org.eclipse.jgit.lib.Repository;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -28,8 +34,18 @@ import com.gitblit.models.UserModel; * @author James Moger
*
*/
+@Singleton
public class RawFilter extends AccessRestrictionFilter {
+ @Inject
+ public RawFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ super(runtimeManager, authenticationManager, repositoryManager);
+ }
+
/**
* Extract the repository name from the url.
*
@@ -82,7 +98,7 @@ public class RawFilter extends AccessRestrictionFilter { * @return true if the filter allows repository creation
*/
@Override
- protected boolean isCreationAllowed() {
+ protected boolean isCreationAllowed(String action) {
return false;
}
@@ -91,10 +107,11 @@ public class RawFilter extends AccessRestrictionFilter { *
* @param repository
* @param action
+ * @param method
* @return true if the action may be performed
*/
@Override
- protected boolean isActionAllowed(RepositoryModel repository, String action) {
+ protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
return true;
}
@@ -103,10 +120,11 @@ public class RawFilter extends AccessRestrictionFilter { *
* @param repository
* @param action
+ * @param method
* @return true if authentication required
*/
@Override
- protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+ protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
diff --git a/src/main/java/com/gitblit/servlet/RawServlet.java b/src/main/java/com/gitblit/servlet/RawServlet.java index ca41d0ab..1d2724bc 100644 --- a/src/main/java/com/gitblit/servlet/RawServlet.java +++ b/src/main/java/com/gitblit/servlet/RawServlet.java @@ -24,12 +24,14 @@ import java.text.MessageFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -48,7 +50,6 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants; import com.gitblit.Keys; -import com.gitblit.dagger.DaggerServlet; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; import com.gitblit.models.PathModel; @@ -56,8 +57,8 @@ import com.gitblit.utils.ByteFormat; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; - -import dagger.ObjectGraph; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Serves the content of a branch. @@ -65,20 +66,24 @@ import dagger.ObjectGraph; * @author James Moger * */ -public class RawServlet extends DaggerServlet { +@Singleton +public class RawServlet extends HttpServlet { private static final long serialVersionUID = 1L; private transient Logger logger = LoggerFactory.getLogger(RawServlet.class); - private IRuntimeManager runtimeManager; + private final IRuntimeManager runtimeManager; - private IRepositoryManager repositoryManager; + private final IRepositoryManager repositoryManager; - @Override - protected void inject(ObjectGraph dagger) { - this.runtimeManager = dagger.get(IRuntimeManager.class); - this.repositoryManager = dagger.get(IRepositoryManager.class); + @Inject + public RawServlet( + IRuntimeManager runtimeManager, + IRepositoryManager repositoryManager) { + + this.runtimeManager = runtimeManager; + this.repositoryManager = repositoryManager; } /** @@ -224,15 +229,32 @@ public class RawServlet extends DaggerServlet { return; } + Map<String, String> quickContentTypes = new HashMap<>(); + quickContentTypes.put("html", "text/html"); + quickContentTypes.put("htm", "text/html"); + quickContentTypes.put("xml", "application/xml"); + quickContentTypes.put("json", "application/json"); List<PathModel> pathEntries = JGitUtils.getFilesInPath(r, requestedPath, commit); if (pathEntries.isEmpty()) { // requested a specific resource String file = StringUtils.getLastPathElement(requestedPath); try { - // query Tika for the content type - Tika tika = new Tika(); - String contentType = tika.detect(file); + + String ext = StringUtils.getFileExtension(file).toLowerCase(); + String contentType = quickContentTypes.get(ext); + + if (contentType == null) { + List<String> exts = runtimeManager.getSettings().getStrings(Keys.web.prettyPrintExtensions); + if (exts.contains(ext)) { + // extension is a registered text type for pretty printing + contentType = "text/plain"; + } else { + // query Tika for the content type + Tika tika = new Tika(); + contentType = tika.detect(file); + } + } if (contentType == null) { // ask the container for the content type @@ -244,7 +266,7 @@ public class RawServlet extends DaggerServlet { } } - if (isTextType(contentType)) { + if (isTextType(contentType) || isTextDataType(contentType)) { // load, interpret, and serve text content as UTF-8 String [] encodings = runtimeManager.getSettings().getStrings(Keys.web.blobEncodings).toArray(new String[0]); @@ -378,6 +400,13 @@ public class RawServlet extends DaggerServlet { return false; } + protected boolean isTextDataType(String contentType) { + if ("image/svg+xml".equals(contentType)) { + return true; + } + return false; + } + /** * Override all text types to be plain text. * @@ -439,7 +468,7 @@ public class RawServlet extends DaggerServlet { served = true; } } finally { - tw.release(); + tw.close(); rw.dispose(); } diff --git a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java index 9bd3b3c3..4e58d4de 100644 --- a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java +++ b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java @@ -18,32 +18,33 @@ package com.gitblit.servlet; import java.io.File;
import java.io.IOException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.utils.FileUtils;
-import dagger.ObjectGraph;
-
/**
* Handles requests for robots.txt
*
* @author James Moger
*
*/
-public class RobotsTxtServlet extends DaggerServlet {
+@Singleton
+public class RobotsTxtServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public RobotsTxtServlet(IRuntimeManager runtimeManager) {
+ this.runtimeManager = runtimeManager;
}
@Override
diff --git a/src/main/java/com/gitblit/servlet/RpcFilter.java b/src/main/java/com/gitblit/servlet/RpcFilter.java index 23bf956e..34474d55 100644 --- a/src/main/java/com/gitblit/servlet/RpcFilter.java +++ b/src/main/java/com/gitblit/servlet/RpcFilter.java @@ -18,8 +18,9 @@ package com.gitblit.servlet; import java.io.IOException;
import java.text.MessageFormat;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -29,11 +30,10 @@ import javax.servlet.http.HttpServletResponse; import com.gitblit.Constants.RpcRequest;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.models.UserModel;
-import dagger.ObjectGraph;
-
/**
* The RpcFilter is a servlet filter that secures the RpcServlet.
*
@@ -47,17 +47,23 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
+@Singleton
public class RpcFilter extends AuthenticationFilter {
private IStoredSettings settings;
private IRuntimeManager runtimeManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.settings = dagger.get(IStoredSettings.class);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
+ @Inject
+ public RpcFilter(
+ IStoredSettings settings,
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager) {
+
+ super(authenticationManager);
+
+ this.settings = settings;
+ this.runtimeManager = runtimeManager;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/RpcServlet.java b/src/main/java/com/gitblit/servlet/RpcServlet.java index b8cdfb04..9809a252 100644 --- a/src/main/java/com/gitblit/servlet/RpcServlet.java +++ b/src/main/java/com/gitblit/servlet/RpcServlet.java @@ -23,6 +23,8 @@ import java.util.HashMap; import java.util.List;
import java.util.Map;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -47,13 +49,12 @@ import com.gitblit.utils.JGitUtils; import com.gitblit.utils.RpcUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
* Handles remote procedure calls.
*
* @author James Moger
*/
+@Singleton
public class RpcServlet extends JsonServlet {
private static final long serialVersionUID = 1L;
@@ -64,10 +65,10 @@ public class RpcServlet extends JsonServlet { private IGitblit gitblit;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.gitblit = dagger.get(IGitblit.class);
+ @Inject
+ public RpcServlet(IStoredSettings settings, IGitblit gitblit) {
+ this.settings = settings;
+ this.gitblit = gitblit;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java index 150dd68a..e989ece3 100644 --- a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java +++ b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java @@ -20,20 +20,20 @@ import java.net.URL; import java.text.MessageFormat;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IUserManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* Handles requests for Sparkleshare Invites
@@ -41,7 +41,8 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
-public class SparkleShareInviteServlet extends DaggerServlet {
+@Singleton
+public class SparkleShareInviteServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -53,12 +54,17 @@ public class SparkleShareInviteServlet extends DaggerServlet { private IRepositoryManager repositoryManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.userManager = dagger.get(IUserManager.class);
- this.authenticationManager = dagger.get(IAuthenticationManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
+ @Inject
+ public SparkleShareInviteServlet(
+ IStoredSettings settings,
+ IUserManager userManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager) {
+
+ this.settings = settings;
+ this.userManager = userManager;
+ this.authenticationManager = authenticationManager;
+ this.repositoryManager = repositoryManager;
}
@Override
@@ -83,6 +89,8 @@ public class SparkleShareInviteServlet extends DaggerServlet { response.getWriter().append("SSH is not active on this server!");
return;
}
+ int sshDisplayPort = settings.getInteger(Keys.git.sshAdvertisedPort, sshPort);
+
// extract repo name from request
String repoUrl = request.getPathInfo().substring(1);
@@ -106,6 +114,10 @@ public class SparkleShareInviteServlet extends DaggerServlet { if (!StringUtils.isEmpty(url) && url.indexOf("localhost") == -1) {
host = new URL(url).getHost();
}
+ String sshDisplayHost = settings.getString(Keys.git.sshAdvertisedHost, "");
+ if(sshDisplayHost.isEmpty()) {
+ sshDisplayHost = host;
+ }
UserModel user;
if (StringUtils.isEmpty(username)) {
@@ -135,7 +147,7 @@ public class SparkleShareInviteServlet extends DaggerServlet { StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
sb.append("<sparkleshare><invite>\n");
- sb.append(MessageFormat.format("<address>ssh://{0}@{1}:{2,number,0}/</address>\n", user.username, host, sshPort));
+ sb.append(MessageFormat.format("<address>ssh://{0}@{1}:{2,number,0}/</address>\n", user.username, sshDisplayHost, sshDisplayPort));
sb.append(MessageFormat.format("<remote_path>/{0}</remote_path>\n", model.name));
int fanoutPort = settings.getInteger(Keys.fanout.port, 0);
if (fanoutPort > 0) {
diff --git a/src/main/java/com/gitblit/servlet/SyndicationFilter.java b/src/main/java/com/gitblit/servlet/SyndicationFilter.java index 78da47e9..49348d0d 100644 --- a/src/main/java/com/gitblit/servlet/SyndicationFilter.java +++ b/src/main/java/com/gitblit/servlet/SyndicationFilter.java @@ -18,8 +18,9 @@ package com.gitblit.servlet; import java.io.IOException;
import java.text.MessageFormat;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
@@ -34,8 +36,6 @@ import com.gitblit.models.ProjectModel; import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
-import dagger.ObjectGraph;
-
/**
* The SyndicationFilter is an AuthenticationFilter which ensures that feed
* requests for projects or view-restricted repositories have proper authentication
@@ -44,18 +44,24 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
+@Singleton
public class SyndicationFilter extends AuthenticationFilter {
private IRuntimeManager runtimeManager;
private IRepositoryManager repositoryManager;
private IProjectManager projectManager;
- @Override
- protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
- super.inject(dagger, filterConfig);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.projectManager = dagger.get(IProjectManager.class);
+ @Inject
+ public SyndicationFilter(
+ IRuntimeManager runtimeManager,
+ IAuthenticationManager authenticationManager,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager) {
+ super(authenticationManager);
+
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
}
/**
diff --git a/src/main/java/com/gitblit/servlet/SyndicationServlet.java b/src/main/java/com/gitblit/servlet/SyndicationServlet.java index e3c25967..39dbf2e5 100644 --- a/src/main/java/com/gitblit/servlet/SyndicationServlet.java +++ b/src/main/java/com/gitblit/servlet/SyndicationServlet.java @@ -22,6 +22,8 @@ import java.util.Collections; import java.util.List;
import java.util.Map;
+import javax.servlet.http.HttpServlet;
+
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -31,7 +33,6 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.models.FeedEntryModel;
@@ -45,8 +46,8 @@ import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.SyndicationUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
/**
* SyndicationServlet generates RSS 2.0 feeds and feed links.
@@ -56,7 +57,8 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
-public class SyndicationServlet extends DaggerServlet {
+@Singleton
+public class SyndicationServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@@ -68,11 +70,15 @@ public class SyndicationServlet extends DaggerServlet { private IProjectManager projectManager;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.projectManager = dagger.get(IProjectManager.class);
+ @Inject
+ public SyndicationServlet(
+ IStoredSettings settings,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager) {
+
+ this.settings = settings;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
}
/**
@@ -362,7 +368,7 @@ public class SyndicationServlet extends DaggerServlet { if (mountParameters) {
// mounted url
feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl,
- StringUtils.encodeURL(feedName));
+ StringUtils.encodeURL(feedName.replace('/', fsc)));
} else {
// parameterized url
feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl,
diff --git a/src/main/java/com/gitblit/tickets/BranchTicketService.java b/src/main/java/com/gitblit/tickets/BranchTicketService.java index 5a42c6a7..83966939 100644 --- a/src/main/java/com/gitblit/tickets/BranchTicketService.java +++ b/src/main/java/com/gitblit/tickets/BranchTicketService.java @@ -72,6 +72,8 @@ import com.gitblit.models.TicketModel.Change; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Implementation of a ticket service based on an orphan branch. All tickets @@ -81,6 +83,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ +@Singleton public class BranchTicketService extends ITicketService implements RefsChangedListener { public static final String BRANCH = "refs/meta/gitblit/tickets"; @@ -91,6 +94,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi private final Map<String, AtomicLong> lastAssignedId; + @Inject public BranchTicketService( IRuntimeManager runtimeManager, IPluginManager pluginManager, @@ -112,6 +116,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi @Override public BranchTicketService start() { + log.info("{} started", getClass().getSimpleName()); return this; } @@ -292,7 +297,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi log.error("failed to read " + file, e); } finally { if (rw != null) { - rw.release(); + rw.close(); } } return null; @@ -348,7 +353,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi } catch (IOException e) { log.error("", e); } finally { - inserter.release(); + inserter.close(); } } @@ -707,7 +712,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi } finally { // release the treewalk if (treeWalk != null) { - treeWalk.release(); + treeWalk.close(); } } } finally { @@ -806,7 +811,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi // finish the index builder.finish(); } finally { - inserter.release(); + inserter.close(); } return newIndex; } @@ -850,7 +855,7 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi } } finally { if (tw != null) { - tw.release(); + tw.close(); } } return list; @@ -908,10 +913,10 @@ public class BranchTicketService extends ITicketService implements RefsChangedLi rc)); } } finally { - revWalk.release(); + revWalk.close(); } } finally { - odi.release(); + odi.close(); } return success; } diff --git a/src/main/java/com/gitblit/tickets/FileTicketService.java b/src/main/java/com/gitblit/tickets/FileTicketService.java index b3d8838e..1e82f0de 100644 --- a/src/main/java/com/gitblit/tickets/FileTicketService.java +++ b/src/main/java/com/gitblit/tickets/FileTicketService.java @@ -42,6 +42,8 @@ import com.gitblit.models.TicketModel.Change; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FileUtils; import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Implementation of a ticket service based on a directory within the repository. @@ -51,6 +53,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ +@Singleton public class FileTicketService extends ITicketService { private static final String JOURNAL = "journal.json"; @@ -59,6 +62,7 @@ public class FileTicketService extends ITicketService { private final Map<String, AtomicLong> lastAssignedId; + @Inject public FileTicketService( IRuntimeManager runtimeManager, IPluginManager pluginManager, @@ -77,6 +81,7 @@ public class FileTicketService extends ITicketService { @Override public FileTicketService start() { + log.info("{} started", getClass().getSimpleName()); return this; } @@ -488,6 +493,10 @@ public class FileTicketService extends ITicketService { @Override protected boolean deleteAllImpl(RepositoryModel repository) { Repository db = repositoryManager.getRepository(repository.name); + if (db == null) { + // the tickets no longer exist because the db no longer exists + return true; + } try { File dir = new File(db.getDirectory(), TICKETS_PATH); return FileUtils.delete(dir); diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java index 4cf099ff..5e3e372a 100644 --- a/src/main/java/com/gitblit/tickets/ITicketService.java +++ b/src/main/java/com/gitblit/tickets/ITicketService.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.extensions.TicketHook; +import com.gitblit.manager.IManager; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IRepositoryManager; @@ -63,7 +64,7 @@ import com.google.common.cache.CacheBuilder; * @author James Moger * */ -public abstract class ITicketService { +public abstract class ITicketService implements IManager { public static final String SETTING_UPDATE_DIFFSTATS = "migration.updateDiffstats"; @@ -176,12 +177,14 @@ public abstract class ITicketService { * Start the service. * @since 1.4.0 */ + @Override public abstract ITicketService start(); /** * Stop the service. * @since 1.4.0 */ + @Override public final ITicketService stop() { indexer.close(); ticketsCache.invalidateAll(); diff --git a/src/main/java/com/gitblit/tickets/NullTicketService.java b/src/main/java/com/gitblit/tickets/NullTicketService.java index d410cdd0..3947b945 100644 --- a/src/main/java/com/gitblit/tickets/NullTicketService.java +++ b/src/main/java/com/gitblit/tickets/NullTicketService.java @@ -28,6 +28,8 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.TicketModel; import com.gitblit.models.TicketModel.Attachment; import com.gitblit.models.TicketModel.Change; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Implementation of a ticket service that rejects everything. @@ -35,8 +37,10 @@ import com.gitblit.models.TicketModel.Change; * @author James Moger * */ +@Singleton public class NullTicketService extends ITicketService { + @Inject public NullTicketService( IRuntimeManager runtimeManager, IPluginManager pluginManager, @@ -58,6 +62,7 @@ public class NullTicketService extends ITicketService { @Override public NullTicketService start() { + log.info("{} started", getClass().getSimpleName()); return this; } diff --git a/src/main/java/com/gitblit/tickets/QueryResult.java b/src/main/java/com/gitblit/tickets/QueryResult.java index 7a2b1abe..f8d6d123 100644 --- a/src/main/java/com/gitblit/tickets/QueryResult.java +++ b/src/main/java/com/gitblit/tickets/QueryResult.java @@ -24,6 +24,8 @@ import java.util.List; import com.gitblit.models.TicketModel.Patchset; import com.gitblit.models.TicketModel.Status; import com.gitblit.models.TicketModel.Type; +import com.gitblit.models.TicketModel.Priority; +import com.gitblit.models.TicketModel.Severity; import com.gitblit.utils.StringUtils; /** @@ -62,6 +64,8 @@ public class QueryResult implements Serializable { public int commentsCount; public int votesCount; public int approvalsCount; + public Priority priority; + public Severity severity; public int docId; public int totalResults; diff --git a/src/main/java/com/gitblit/tickets/RedisTicketService.java b/src/main/java/com/gitblit/tickets/RedisTicketService.java index d773b0bd..0f9ad174 100644 --- a/src/main/java/com/gitblit/tickets/RedisTicketService.java +++ b/src/main/java/com/gitblit/tickets/RedisTicketService.java @@ -43,6 +43,8 @@ import com.gitblit.models.TicketModel.Attachment; import com.gitblit.models.TicketModel.Change; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Implementation of a ticket service based on a Redis key-value store. All @@ -53,6 +55,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ +@Singleton public class RedisTicketService extends ITicketService { private final JedisPool pool; @@ -61,6 +64,7 @@ public class RedisTicketService extends ITicketService { journal, ticket, counter } + @Inject public RedisTicketService( IRuntimeManager runtimeManager, IPluginManager pluginManager, @@ -80,6 +84,10 @@ public class RedisTicketService extends ITicketService { @Override public RedisTicketService start() { + log.info("{} started", getClass().getSimpleName()); + if (!isReady()) { + log.warn("{} is not ready!", getClass().getSimpleName()); + } return this; } diff --git a/src/main/java/com/gitblit/tickets/TicketIndexer.java b/src/main/java/com/gitblit/tickets/TicketIndexer.java index 11ea3a73..e2d53af7 100644 --- a/src/main/java/com/gitblit/tickets/TicketIndexer.java +++ b/src/main/java/com/gitblit/tickets/TicketIndexer.java @@ -103,7 +103,10 @@ public class TicketIndexer { mergesha(Type.STRING), mergeto(Type.STRING), patchsets(Type.INT), - votes(Type.INT); + votes(Type.INT), + //NOTE: Indexing on the underlying value to allow flexibility on naming + priority(Type.INT), + severity(Type.INT); final Type fieldType; @@ -519,6 +522,8 @@ public class TicketIndexer { toDocField(doc, Lucene.watchedby, StringUtils.flattenStrings(ticket.getWatchers(), ";").toLowerCase()); toDocField(doc, Lucene.mentions, StringUtils.flattenStrings(ticket.getMentions(), ";").toLowerCase()); toDocField(doc, Lucene.votes, ticket.getVoters().size()); + toDocField(doc, Lucene.priority, ticket.priority.getValue()); + toDocField(doc, Lucene.severity, ticket.severity.getValue()); List<String> attachments = new ArrayList<String>(); for (Attachment attachment : ticket.getAttachments()) { @@ -600,6 +605,8 @@ public class TicketIndexer { result.participants = unpackStrings(doc, Lucene.participants); result.watchedby = unpackStrings(doc, Lucene.watchedby); result.mentions = unpackStrings(doc, Lucene.mentions); + result.priority = TicketModel.Priority.fromObject(unpackInt(doc, Lucene.priority), TicketModel.Priority.defaultPriority); + result.severity = TicketModel.Severity.fromObject(unpackInt(doc, Lucene.severity), TicketModel.Severity.defaultSeverity); if (!StringUtils.isEmpty(doc.get(Lucene.patchset.name()))) { // unpack most recent patchset diff --git a/src/main/java/com/gitblit/tickets/TicketNotifier.java b/src/main/java/com/gitblit/tickets/TicketNotifier.java index d6217b35..5979cf26 100644 --- a/src/main/java/com/gitblit/tickets/TicketNotifier.java +++ b/src/main/java/com/gitblit/tickets/TicketNotifier.java @@ -135,6 +135,7 @@ public class TicketNotifier { StringBuilder html = new StringBuilder(); html.append("<head>"); html.append(readStyle()); + html.append(readViewTicketAction(ticket)); html.append("</head>"); html.append("<body>"); html.append(MarkdownUtils.transformGFM(settings, markdown, ticket.repository)); @@ -613,6 +614,12 @@ public class TicketNotifier { return sb.toString(); } + protected String readViewTicketAction(TicketModel ticket) { + String action = readResource("viewTicket.html"); + action = action.replace("${url}", ticketService.getTicketUrl(ticket)); + return action; + } + protected String readResource(String resource) { StringBuilder sb = new StringBuilder(); InputStream is = null; diff --git a/src/main/java/com/gitblit/tickets/viewTicket.html b/src/main/java/com/gitblit/tickets/viewTicket.html new file mode 100644 index 00000000..54e091ca --- /dev/null +++ b/src/main/java/com/gitblit/tickets/viewTicket.html @@ -0,0 +1,12 @@ +<script type="application/ld+json"> +{ + "@context": "http://schema.org", + "@type": "EmailMessage", + "description": "View this Ticket in Gitblit", + "action": { + "@type": "ViewAction", + "url": "${url}", + "name": "View Ticket" + } +} +</script> diff --git a/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java b/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java index de661a4d..9bab3b8e 100644 --- a/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java +++ b/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java @@ -16,31 +16,22 @@ package com.gitblit.transport.ssh; import java.io.IOException; +import java.nio.file.FileSystem; -import org.apache.sshd.common.Session; import org.apache.sshd.common.file.FileSystemFactory; -import org.apache.sshd.common.file.FileSystemView; -import org.apache.sshd.common.file.SshFile; +import org.apache.sshd.common.session.Session; public class DisabledFilesystemFactory implements FileSystemFactory { - @Override - public FileSystemView createFileSystemView(Session session) throws IOException { - return new FileSystemView() { - @Override - public SshFile getFile(SshFile baseDir, String file) { - return null; - } - - @Override - public SshFile getFile(String file) { - return null; - } - - @Override - public FileSystemView getNormalizedView() { - return null; - } - }; - } + /** + * Create user specific file system. + * + * @param session The session created for the user + * @return The current {@link FileSystem} for the provided session + * @throws java.io.IOException when the filesystem can not be created + */ + @Override + public FileSystem createFileSystem(Session session) throws IOException { + return null; + } } diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java index a063dc7d..1a2cd682 100644 --- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java @@ -29,6 +29,7 @@ import com.gitblit.manager.IRuntimeManager; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.io.Files; +import com.google.inject.Inject; /** * Manages public keys on the filesystem. @@ -42,6 +43,7 @@ public class FileKeyManager extends IPublicKeyManager { protected final Map<File, Long> lastModifieds; + @Inject public FileKeyManager(IRuntimeManager runtimeManager) { this.runtimeManager = runtimeManager; this.lastModifieds = new ConcurrentHashMap<File, Long>(); diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java new file mode 100644 index 00000000..db0741e0 --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.gitblit.transport.ssh; + +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider; +import org.apache.sshd.common.util.SecurityUtils; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.PasswordFinder; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; + +/** + * This host key provider loads private keys from the specified files. + * + * Note that this class has a direct dependency on BouncyCastle and won't work + * unless it has been correctly registered as a security provider. + * + * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class FileKeyPairProvider extends AbstractKeyPairProvider { + + private String[] files; + private PasswordFinder passwordFinder; + + public FileKeyPairProvider() { + } + + public FileKeyPairProvider(String[] files) { + this.files = files; + } + + public FileKeyPairProvider(String[] files, PasswordFinder passwordFinder) { + this.files = files; + this.passwordFinder = passwordFinder; + } + + public String[] getFiles() { + return files; + } + + public void setFiles(String[] files) { + this.files = files; + } + + public PasswordFinder getPasswordFinder() { + return passwordFinder; + } + + public void setPasswordFinder(PasswordFinder passwordFinder) { + this.passwordFinder = passwordFinder; + } + + public Iterable<KeyPair> loadKeys() { + if (!SecurityUtils.isBouncyCastleRegistered()) { + throw new IllegalStateException("BouncyCastle must be registered as a JCE provider"); + } + return new Iterable<KeyPair>() { + @Override + public Iterator<KeyPair> iterator() { + return new Iterator<KeyPair>() { + private final Iterator<String> iterator = Arrays.asList(files).iterator(); + private KeyPair nextKeyPair; + private boolean nextKeyPairSet = false; + @Override + public boolean hasNext() { + return nextKeyPairSet || setNextObject(); + } + @Override + public KeyPair next() { + if (!nextKeyPairSet) { + if (!setNextObject()) { + throw new NoSuchElementException(); + } + } + nextKeyPairSet = false; + return nextKeyPair; + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + private boolean setNextObject() { + while (iterator.hasNext()) { + String file = iterator.next(); + nextKeyPair = doLoadKey(file); + if (nextKeyPair != null) { + nextKeyPairSet = true; + return true; + } + } + return false; + } + + }; + } + }; + } + + protected KeyPair doLoadKey(String file) { + try { + PEMParser r = new PEMParser(new InputStreamReader(new FileInputStream(file))); + try { + Object o = r.readObject(); + + JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); + pemConverter.setProvider("BC"); + if (passwordFinder != null && o instanceof PEMEncryptedKeyPair) { + JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder(); + PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(passwordFinder.getPassword()); + o = pemConverter.getKeyPair(((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor)); + } + + if (o instanceof PEMKeyPair) { + o = pemConverter.getKeyPair((PEMKeyPair)o); + return (KeyPair) o; + } else if (o instanceof KeyPair) { + return (KeyPair) o; + } + } finally { + r.close(); + } + } catch (Exception e) { + log.warn("Unable to read key " + file, e); + } + return null; + } + +} diff --git a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java index 357b34a2..bf783786 100644 --- a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.google.inject.Inject; + /** * Memory public key manager. * @@ -30,6 +32,7 @@ public class MemoryKeyManager extends IPublicKeyManager { final Map<String, List<SshKey>> keys; + @Inject public MemoryKeyManager() { keys = new HashMap<String, List<SshKey>>(); } diff --git a/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java b/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java index 4bd75d59..29f7750d 100644 --- a/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java +++ b/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java @@ -15,13 +15,14 @@ */ package com.gitblit.transport.ssh; -import org.apache.sshd.common.ForwardingFilter; -import org.apache.sshd.common.Session; import org.apache.sshd.common.SshdSocketAddress; +import org.apache.sshd.common.session.Session; +import org.apache.sshd.server.forward.ForwardingFilter; public class NonForwardingFilter implements ForwardingFilter { + @Override - public boolean canConnect(SshdSocketAddress address, Session session) { + public boolean canConnect(Type type, SshdSocketAddress address, Session session) { return false; } diff --git a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java index 0761d842..fcd3e197 100644 --- a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java +++ b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java @@ -17,6 +17,8 @@ package com.gitblit.transport.ssh; import java.util.List; +import com.google.inject.Inject; + /** * Rejects all public key management requests. * @@ -25,6 +27,7 @@ import java.util.List; */ public class NullKeyManager extends IPublicKeyManager { + @Inject public NullKeyManager() { } diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java index 6bcc0390..5a94c9a3 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java @@ -25,12 +25,12 @@ import java.security.KeyPairGenerator; import java.text.MessageFormat; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.sshd.SshServer; import org.apache.sshd.common.io.IoServiceFactoryFactory; import org.apache.sshd.common.io.mina.MinaServiceFactoryFactory; import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory; -import org.apache.sshd.common.keyprovider.FileKeyPairProvider; import org.apache.sshd.common.util.SecurityUtils; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.auth.CachingPublicKeyAuthenticator; import org.bouncycastle.openssl.PEMWriter; import org.eclipse.jgit.internal.JGitText; import org.slf4j.Logger; @@ -98,8 +98,8 @@ public class SshDaemon { hostKeyPairProvider.setFiles(new String [] { rsaKeyStore.getPath(), dsaKeyStore.getPath(), dsaKeyStore.getPath() }); // Client public key authenticator - CachingPublicKeyAuthenticator keyAuthenticator = - new CachingPublicKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit); + SshKeyAuthenticator keyAuthenticator = + new SshKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit); // Configure the preferred SSHD backend String sshBackendStr = settings.getString(Keys.git.sshBackend, @@ -125,8 +125,11 @@ public class SshDaemon { sshd.setPort(addr.getPort()); sshd.setHost(addr.getHostName()); sshd.setKeyPairProvider(hostKeyPairProvider); - sshd.setPublickeyAuthenticator(keyAuthenticator); + sshd.setPublickeyAuthenticator(new CachingPublicKeyAuthenticator(keyAuthenticator)); sshd.setPasswordAuthenticator(new UsernamePasswordAuthenticator(gitblit)); + if (settings.getBoolean(Keys.git.sshWithKrb5, false)) { + sshd.setGSSAuthenticator(new SshKrbAuthenticator(settings, gitblit)); + } sshd.setSessionFactory(new SshServerSessionFactory()); sshd.setFileSystemFactory(new DisabledFilesystemFactory()); sshd.setTcpipForwardingFilter(new NonForwardingFilter()); @@ -143,14 +146,22 @@ public class SshDaemon { } public String formatUrl(String gituser, String servername, String repository) { - if (sshd.getPort() == DEFAULT_PORT) { + IStoredSettings settings = gitblit.getSettings(); + + int port = sshd.getPort(); + int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port); + String displayServername = settings.getString(Keys.git.sshAdvertisedHost, ""); + if(displayServername.isEmpty()) { + displayServername = servername; + } + if (displayPort == DEFAULT_PORT) { // standard port - return MessageFormat.format("ssh://{0}@{1}/{2}", gituser, servername, + return MessageFormat.format("ssh://{0}@{1}/{2}", gituser, displayServername, repository); } else { // non-standard port return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/{3}", - gituser, servername, sshd.getPort(), repository); + gituser, displayServername, displayPort, repository); } } @@ -192,7 +203,7 @@ public class SshDaemon { try { ((SshCommandFactory) sshd.getCommandFactory()).stop(); sshd.stop(); - } catch (InterruptedException e) { + } catch (IOException e) { log.error("SSH Daemon stop interrupted", e); } } diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java b/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java index a5d4c3dd..af25251b 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java +++ b/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java @@ -17,7 +17,7 @@ package com.gitblit.transport.ssh; import java.net.SocketAddress; -import org.apache.sshd.common.Session.AttributeKey; +import org.apache.sshd.common.session.Session.AttributeKey; import com.gitblit.models.UserModel; diff --git a/src/main/java/com/gitblit/transport/ssh/SshKey.java b/src/main/java/com/gitblit/transport/ssh/SshKey.java index 9c99d1a5..9fd1005a 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshKey.java +++ b/src/main/java/com/gitblit/transport/ssh/SshKey.java @@ -22,7 +22,8 @@ import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.sshd.common.SshException; -import org.apache.sshd.common.util.Buffer; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; import org.eclipse.jgit.lib.Constants; import com.gitblit.Constants.AccessPermission; @@ -72,7 +73,7 @@ public class SshKey implements Serializable { } final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1])); try { - publicKey = new Buffer(bin).getRawPublicKey(); + publicKey = new ByteArrayBuffer(bin).getRawPublicKey(); } catch (SshException e) { throw new RuntimeException(e); } @@ -145,7 +146,7 @@ public class SshKey implements Serializable { public String getRawData() { if (rawData == null && publicKey != null) { // build the raw data manually from the public key - Buffer buf = new Buffer(); + Buffer buf = new ByteArrayBuffer(); // 1: identify the algorithm buf.putRawPublicKey(publicKey); diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java index e804a0da..dc9d8a4e 100644 --- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java +++ b/src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java @@ -16,15 +16,10 @@ package com.gitblit.transport.ssh; import java.security.PublicKey; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.apache.sshd.common.Session; -import org.apache.sshd.common.SessionListener; -import org.apache.sshd.server.PublickeyAuthenticator; +import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; import org.apache.sshd.server.session.ServerSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +32,7 @@ import com.google.common.base.Preconditions; * Authenticates an SSH session against a public key. * */ -public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener { +public class SshKeyAuthenticator implements PublickeyAuthenticator { protected final Logger log = LoggerFactory.getLogger(getClass()); @@ -45,30 +40,13 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, Se protected final IAuthenticationManager authManager; - private final Map<ServerSession, Map<PublicKey, Boolean>> cache = new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>(); - - public CachingPublicKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) { + public SshKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) { this.keyManager = keyManager; this.authManager = authManager; } @Override - public boolean authenticate(String username, PublicKey key, ServerSession session) { - Map<PublicKey, Boolean> map = cache.get(session); - if (map == null) { - map = new HashMap<PublicKey, Boolean>(); - cache.put(session, map); - session.addListener(this); - } - if (map.containsKey(key)) { - return map.get(key); - } - boolean result = doAuthenticate(username, key, session); - map.put(key, result); - return result; - } - - private boolean doAuthenticate(String username, PublicKey suppliedKey, ServerSession session) { + public boolean authenticate(String username, PublicKey suppliedKey, ServerSession session) { SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); Preconditions.checkState(client.getUser() == null); username = username.toLowerCase(Locale.US); @@ -96,17 +74,4 @@ public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, Se log.warn("could not authenticate {} for SSH using the supplied public key", username); return false; } - - @Override - public void sessionCreated(Session session) { - } - - @Override - public void sessionEvent(Session sesssion, Event event) { - } - - @Override - public void sessionClosed(Session session) { - cache.remove(session); - } } diff --git a/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java new file mode 100644 index 00000000..b6d233cf --- /dev/null +++ b/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.transport.ssh; + +import java.util.Locale; + +import org.apache.sshd.server.auth.gss.GSSAuthenticator; +import org.apache.sshd.server.session.ServerSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IAuthenticationManager; +import com.gitblit.models.UserModel; + +public class SshKrbAuthenticator extends GSSAuthenticator { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + protected final IAuthenticationManager authManager; + protected final boolean stripDomain; + + + public SshKrbAuthenticator(IStoredSettings settings, IAuthenticationManager authManager) { + this.authManager = authManager; + + String keytabString = settings.getString(Keys.git.sshKrb5Keytab, ""); + if(! keytabString.isEmpty()) { + setKeytabFile(keytabString); + } + + String servicePrincipalName = settings.getString(Keys.git.sshKrb5ServicePrincipalName, ""); + if(! servicePrincipalName.isEmpty()) { + setServicePrincipalName(servicePrincipalName); + } + + this.stripDomain = settings.getBoolean(Keys.git.sshKrb5StripDomain, false); + } + + @Override + public boolean validateIdentity(ServerSession session, String identity) { + log.info("identify with kerberos {}", identity); + SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY); + if (client.getUser() != null) { + log.info("{} has already authenticated!", identity); + return true; + } + String username = identity.toLowerCase(Locale.US); + if (stripDomain) { + int p = username.indexOf('@'); + if (p > 0) { + username = username.substring(0, p); + } + } + UserModel user = authManager.authenticate(username); + if (user != null) { + client.setUser(user); + return true; + } + log.warn("could not authenticate {} for SSH", username); + return false; + } +} diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSession.java b/src/main/java/com/gitblit/transport/ssh/SshServerSession.java index d12a6be2..02504ec8 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshServerSession.java +++ b/src/main/java/com/gitblit/transport/ssh/SshServerSession.java @@ -19,10 +19,10 @@ import org.apache.sshd.common.future.CloseFuture; import org.apache.sshd.common.future.SshFutureListener; import org.apache.sshd.common.io.IoSession; import org.apache.sshd.server.ServerFactoryManager; -import org.apache.sshd.server.session.ServerSession; +import org.apache.sshd.server.session.ServerSessionImpl; // Expose addition of close session listeners -class SshServerSession extends ServerSession { +class SshServerSession extends ServerSessionImpl { SshServerSession(ServerFactoryManager server, IoSession ioSession) throws Exception { super(server, ioSession); diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java index 0c018f02..bc67cec0 100644 --- a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java +++ b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java @@ -67,6 +67,6 @@ public class SshServerSessionFactory extends SessionFactory { @Override protected AbstractSession doCreateSession(IoSession ioSession) throws Exception { - return new SshServerSession(server, ioSession); + return new SshServerSession(getServer(), ioSession); } } diff --git a/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java index c4e69dcd..e9e2d7e1 100644 --- a/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java +++ b/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java @@ -17,7 +17,7 @@ package com.gitblit.transport.ssh; import java.util.Locale; -import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.auth.password.PasswordAuthenticator; import org.apache.sshd.server.session.ServerSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,13 +51,13 @@ public class UsernamePasswordAuthenticator implements PasswordAuthenticator { } username = username.toLowerCase(Locale.US); - UserModel user = authManager.authenticate(username, password.toCharArray()); + UserModel user = authManager.authenticate(username, password.toCharArray(), null); if (user != null) { client.setUser(user); return true; } - log.warn("could not authenticate {} for SSH using the supplied password", username); + log.warn("could not authenticate {} ({}) for SSH using the supplied password", username, client.getRemoteAddress()); return false; } } diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java index 852756a7..ec6f7291 100644 --- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java +++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java @@ -200,13 +200,18 @@ public class WelcomeShell implements Factory<Command> { } private String formatUrl(String hostname, int port, String username) { - if (port == 22) { + int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port); + String displayHostname = settings.getString(Keys.git.sshAdvertisedHost, ""); + if(displayHostname.isEmpty()) { + displayHostname = hostname; + } + if (displayPort == 22) { // standard port - return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, hostname); + return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, displayHostname); } else { // non-standard port return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/REPOSITORY.git", - username, hostname, port); + username, displayHostname, displayPort); } } } diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java index f18b99bc..02764db6 100644 --- a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java +++ b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java @@ -27,6 +27,7 @@ import org.eclipse.jgit.util.SystemReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.manager.IGitblit; import com.gitblit.utils.StringUtils; @@ -73,15 +74,20 @@ public abstract class SshCommand extends BaseCommand { protected String getRepositoryUrl(String repository) { String username = getContext().getClient().getUsername(); - String hostname = getHostname(); - int port = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 0); - if (port == 22) { + IStoredSettings settings = getContext().getGitblit().getSettings(); + String displayHostname = settings.getString(Keys.git.sshAdvertisedHost, ""); + if(displayHostname.isEmpty()) { + displayHostname = getHostname(); + } + int port = settings.getInteger(Keys.git.sshPort, 0); + int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port); + if (displayPort == 22) { // standard port - return MessageFormat.format("{0}@{1}/{2}.git", username, hostname, repository); + return MessageFormat.format("{0}@{1}/{2}.git", username, displayHostname, repository); } else { // non-standard port return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/{3}", - username, hostname, port, repository); + username, displayHostname, displayPort, repository); } } diff --git a/src/main/java/com/gitblit/utils/ActivityUtils.java b/src/main/java/com/gitblit/utils/ActivityUtils.java index ba5599a1..ab1dad96 100644 --- a/src/main/java/com/gitblit/utils/ActivityUtils.java +++ b/src/main/java/com/gitblit/utils/ActivityUtils.java @@ -169,7 +169,7 @@ public class ActivityUtils { if (width <= 0) {
width = 50;
}
- String emailHash = StringUtils.getMD5(email);
+ String emailHash = StringUtils.getMD5(email.toLowerCase());
String url = MessageFormat.format(
"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
return url;
@@ -188,7 +188,7 @@ public class ActivityUtils { if (width <= 0) {
width = 50;
}
- String emailHash = StringUtils.getMD5(email);
+ String emailHash = StringUtils.getMD5(email.toLowerCase());
String url = MessageFormat.format(
"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=mm", emailHash, width);
return url;
diff --git a/src/main/java/com/gitblit/utils/CompressionUtils.java b/src/main/java/com/gitblit/utils/CompressionUtils.java index d4bfbb34..b06edd22 100644 --- a/src/main/java/com/gitblit/utils/CompressionUtils.java +++ b/src/main/java/com/gitblit/utils/CompressionUtils.java @@ -132,7 +132,7 @@ public class CompressionUtils { } catch (IOException e) {
error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
} finally {
- tw.release();
+ tw.close();
rw.dispose();
}
return success;
@@ -291,7 +291,7 @@ public class CompressionUtils { } catch (IOException e) {
error(e, repository, "{0} failed to {1} stream files from commit {2}", algorithm, commit.getName());
} finally {
- tw.release();
+ tw.close();
rw.dispose();
}
return success;
diff --git a/src/main/java/com/gitblit/utils/DiffUtils.java b/src/main/java/com/gitblit/utils/DiffUtils.java index dd2a7807..cdebec1b 100644 --- a/src/main/java/com/gitblit/utils/DiffUtils.java +++ b/src/main/java/com/gitblit/utils/DiffUtils.java @@ -52,6 +52,27 @@ public class DiffUtils { private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
/**
+ * Callback interface for binary diffs. All the getDiff methods here take an optional handler;
+ * if given and the {@link DiffOutputType} is {@link DiffOutputType#HTML HTML}, it is responsible
+ * for displaying a binary diff.
+ */
+ public interface BinaryDiffHandler {
+
+ /**
+ * Renders a binary diff. The result must be valid HTML, it will be inserted into an HTML table cell.
+ * May return {@code null} if the default behavior (which is typically just a textual note "Bnary
+ * files differ") is desired.
+ *
+ * @param diffEntry
+ * current diff entry
+ *
+ * @return the rendered diff as HTML, or {@code null} if the default is desired.
+ */
+ public String renderBinaryDiff(final DiffEntry diffEntry);
+
+ }
+
+ /**
* Enumeration for the diff output types.
*/
public static enum DiffOutputType {
@@ -68,6 +89,40 @@ public class DiffUtils { }
/**
+ * Enumeration for the diff comparator types.
+ */
+ public static enum DiffComparator {
+ SHOW_WHITESPACE(RawTextComparator.DEFAULT),
+ IGNORE_WHITESPACE(RawTextComparator.WS_IGNORE_ALL),
+ IGNORE_LEADING(RawTextComparator.WS_IGNORE_LEADING),
+ IGNORE_TRAILING(RawTextComparator.WS_IGNORE_TRAILING),
+ IGNORE_CHANGES(RawTextComparator.WS_IGNORE_CHANGE);
+
+ public final RawTextComparator textComparator;
+
+ DiffComparator(RawTextComparator textComparator) {
+ this.textComparator = textComparator;
+ }
+
+ public DiffComparator getOpposite() {
+ return this == SHOW_WHITESPACE ? IGNORE_WHITESPACE : SHOW_WHITESPACE;
+ }
+
+ public String getTranslationKey() {
+ return "gb." + name().toLowerCase();
+ }
+
+ public static DiffComparator forName(String name) {
+ for (DiffComparator type : values()) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
* Encapsulates the output of a diff.
*/
public static class DiffOutput implements Serializable {
@@ -172,12 +227,50 @@ public class DiffUtils { *
* @param repository
* @param commit
+ * @param comparator
+ * @param outputType
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
+ DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, null, commit, null, comparator, outputType, tabLength);
+ }
+
+ /**
+ * Returns the complete diff of the specified commit compared to its primary parent.
+ *
+ * @param repository
+ * @param commit
+ * @param comparator
* @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
* @return the diff
*/
public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
- DiffOutputType outputType) {
- return getDiff(repository, null, commit, null, outputType);
+ DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+ return getDiff(repository, null, commit, null, comparator, outputType, handler, tabLength);
+ }
+
+
+ /**
+ * Returns the diff for the specified file or folder from the specified
+ * commit compared to its primary parent.
+ *
+ * @param repository
+ * @param commit
+ * @param path
+ * @param comparator
+ * @param outputType
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
+ DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, null, commit, path, comparator, outputType, tabLength);
}
/**
@@ -187,12 +280,17 @@ public class DiffUtils { * @param repository
* @param commit
* @param path
+ * @param comparator
* @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
* @return the diff
*/
public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
- DiffOutputType outputType) {
- return getDiff(repository, null, commit, path, outputType);
+ DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+ return getDiff(repository, null, commit, path, comparator, outputType, handler, tabLength);
}
/**
@@ -201,12 +299,34 @@ public class DiffUtils { * @param repository
* @param baseCommit
* @param commit
+ * @param comparator
* @param outputType
+ * @param tabLength
+ *
* @return the diff
*/
public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
- DiffOutputType outputType) {
- return getDiff(repository, baseCommit, commit, null, outputType);
+ DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, baseCommit, commit, null, comparator, outputType, tabLength);
+ }
+
+ /**
+ * Returns the complete diff between the two specified commits.
+ *
+ * @param repository
+ * @param baseCommit
+ * @param commit
+ * @param comparator
+ * @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
+ DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+ return getDiff(repository, baseCommit, commit, null, comparator, outputType, handler, tabLength);
}
/**
@@ -221,27 +341,54 @@ public class DiffUtils { * if the path is specified, the diff is restricted to that file
* or folder. if unspecified, the diff is for the entire commit.
* @param outputType
+ * @param diffComparator
+ * @param tabLength
* @return the diff
*/
public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
- String path, DiffOutputType outputType) {
+ String path, DiffComparator diffComparator, DiffOutputType outputType, int tabLength) {
+ return getDiff(repository, baseCommit, commit, path, diffComparator, outputType, null, tabLength);
+ }
+
+ /**
+ * Returns the diff between two commits for the specified file.
+ *
+ * @param repository
+ * @param baseCommit
+ * if base commit is null the diff is to the primary parent of
+ * the commit.
+ * @param commit
+ * @param path
+ * if the path is specified, the diff is restricted to that file
+ * or folder. if unspecified, the diff is for the entire commit.
+ * @param comparator
+ * @param outputType
+ * @param handler
+ * to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+ * May be {@code null}, resulting in the default behavior.
+ * @param tabLength
+ * @return the diff
+ */
+ public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, String path,
+ DiffComparator comparator, DiffOutputType outputType, final BinaryDiffHandler handler, int tabLength) {
DiffStat stat = null;
String diff = null;
try {
- final ByteArrayOutputStream os = new ByteArrayOutputStream();
- RawTextComparator cmp = RawTextComparator.DEFAULT;
+ ByteArrayOutputStream os = null;
+
DiffFormatter df;
switch (outputType) {
case HTML:
- df = new GitBlitDiffFormatter(os, commit.getName());
+ df = new GitBlitDiffFormatter(commit.getName(), path, handler, tabLength);
break;
case PLAIN:
default:
+ os = new ByteArrayOutputStream();
df = new DiffFormatter(os);
break;
}
df.setRepository(repository);
- df.setDiffComparator(cmp);
+ df.setDiffComparator((comparator == null ? DiffComparator.SHOW_WHITESPACE : comparator).textComparator);
df.setDetectRenames(true);
RevTree commitTree = commit.getTree();
@@ -271,6 +418,7 @@ public class DiffUtils { } else {
df.format(diffEntries);
}
+ df.flush();
if (df instanceof GitBlitDiffFormatter) {
// workaround for complex private methods in DiffFormatter
diff = ((GitBlitDiffFormatter) df).getHtml();
@@ -278,7 +426,6 @@ public class DiffUtils { } else {
diff = os.toString();
}
- df.flush();
} catch (Throwable t) {
LOGGER.error("failed to generate commit diff!", t);
}
diff --git a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java index 47ff143a..86b7ca2e 100644 --- a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java +++ b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java @@ -1,236 +1,535 @@ -/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import static org.eclipse.jgit.lib.Constants.encode;
-import static org.eclipse.jgit.lib.Constants.encodeASCII;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.text.MessageFormat;
-
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.utils.DiffUtils.DiffStat;
-
-/**
- * Generates an html snippet of a diff in Gitblit's style, tracks changed paths,
- * and calculates diff stats.
- *
- * @author James Moger
- *
- */
-public class GitBlitDiffFormatter extends DiffFormatter {
-
- private final OutputStream os;
-
- private final DiffStat diffStat;
-
- private PathChangeModel currentPath;
-
- private int left, right;
-
- public GitBlitDiffFormatter(OutputStream os, String commitId) {
- super(os);
- this.os = os;
- this.diffStat = new DiffStat(commitId);
- }
-
- @Override
- public void format(DiffEntry ent) throws IOException {
- currentPath = diffStat.addPath(ent);
- super.format(ent);
- }
-
- /**
- * Output a hunk header
- *
- * @param aStartLine
- * within first source
- * @param aEndLine
- * within first source
- * @param bStartLine
- * within second source
- * @param bEndLine
- * within second source
- * @throws IOException
- */
- @Override
- protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
- throws IOException {
- os.write("<tr><th>..</th><th>..</th><td class='hunk_header'>".getBytes());
- os.write('@');
- os.write('@');
- writeRange('-', aStartLine + 1, aEndLine - aStartLine);
- writeRange('+', bStartLine + 1, bEndLine - bStartLine);
- os.write(' ');
- os.write('@');
- os.write('@');
- os.write("</td></tr>\n".getBytes());
- left = aStartLine + 1;
- right = bStartLine + 1;
- }
-
- protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
- os.write(' ');
- os.write(prefix);
- switch (cnt) {
- case 0:
- // If the range is empty, its beginning number must
- // be the
- // line just before the range, or 0 if the range is
- // at the
- // start of the file stream. Here, begin is always 1
- // based,
- // so an empty file would produce "0,0".
- //
- os.write(encodeASCII(begin - 1));
- os.write(',');
- os.write('0');
- break;
-
- case 1:
- // If the range is exactly one line, produce only
- // the number.
- //
- os.write(encodeASCII(begin));
- break;
-
- default:
- os.write(encodeASCII(begin));
- os.write(',');
- os.write(encodeASCII(cnt));
- break;
- }
- }
-
- @Override
- protected void writeLine(final char prefix, final RawText text, final int cur)
- throws IOException {
- // update entry diffstat
- currentPath.update(prefix);
-
- // output diff
- os.write("<tr>".getBytes());
- switch (prefix) {
- case '+':
- os.write(("<th></th><th>" + (right++) + "</th>").getBytes());
- os.write("<td><div class=\"diff add2\">".getBytes());
- break;
- case '-':
- os.write(("<th>" + (left++) + "</th><th></th>").getBytes());
- os.write("<td><div class=\"diff remove2\">".getBytes());
- break;
- default:
- os.write(("<th>" + (left++) + "</th><th>" + (right++) + "</th>").getBytes());
- os.write("<td>".getBytes());
- break;
- }
- os.write(prefix);
- String line = text.getString(cur);
- line = StringUtils.escapeForHtml(line, false);
- os.write(encode(line));
- switch (prefix) {
- case '+':
- case '-':
- os.write("</div>".getBytes());
- break;
- default:
- os.write("</td>".getBytes());
- }
- os.write("</tr>\n".getBytes());
- }
-
- /**
- * Workaround function for complex private methods in DiffFormatter. This
- * sets the html for the diff headers.
- *
- * @return
- */
- public String getHtml() {
- ByteArrayOutputStream bos = (ByteArrayOutputStream) os;
- String html = RawParseUtils.decode(bos.toByteArray());
- String[] lines = html.split("\n");
- StringBuilder sb = new StringBuilder();
- boolean inFile = false;
- String oldnull = "a/dev/null";
- for (String line : lines) {
- if (line.startsWith("index")) {
- // skip index lines
- } else if (line.startsWith("new file")) {
- // skip new file lines
- } else if (line.startsWith("\\ No newline")) {
- // skip no new line
- } else if (line.startsWith("---") || line.startsWith("+++")) {
- // skip --- +++ lines
- } else if (line.startsWith("diff")) {
- line = StringUtils.convertOctal(line);
- if (line.indexOf(oldnull) > -1) {
- // a is null, use b
- line = line.substring(("diff --git " + oldnull).length()).trim();
- // trim b/
- line = line.substring(2).trim();
- } else {
- // use a
- line = line.substring("diff --git ".length()).trim();
- line = line.substring(line.startsWith("\"a/") ? 3 : 2);
- line = line.substring(0, line.indexOf(" b/") > -1 ? line.indexOf(" b/") : line.indexOf("\"b/")).trim();
- }
-
- if (line.charAt(0) == '"') {
- line = line.substring(1);
- }
- if (line.charAt(line.length() - 1) == '"') {
- line = line.substring(0, line.length() - 1);
- }
- if (inFile) {
- sb.append("</tbody></table></div>\n");
- inFile = false;
- }
-
- sb.append(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"{0}\"><i class=\"icon-file\"></i> ", line)).append(line).append("</div></div>");
- sb.append("<div class=\"diff\">");
- sb.append("<table><tbody>");
- inFile = true;
- } else {
- boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit");
- if (gitLinkDiff) {
- sb.append("<tr><th></th><th></th>");
- if (line.charAt(0) == '+') {
- sb.append("<td><div class=\"diff add2\">");
- } else {
- sb.append("<td><div class=\"diff remove2\">");
- }
- }
- sb.append(line);
- if (gitLinkDiff) {
- sb.append("</div></td></tr>");
- }
- }
- }
- sb.append("</table></div>");
- return sb.toString();
- }
-
- public DiffStat getDiffStat() {
- return diffStat;
- }
-}
+/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.utils; + +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.Constants.encodeASCII; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.wicket.Application; +import org.apache.wicket.Localizer; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffEntry.ChangeType; +import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.util.RawParseUtils; + +import com.gitblit.models.PathModel.PathChangeModel; +import com.gitblit.utils.DiffUtils.BinaryDiffHandler; +import com.gitblit.utils.DiffUtils.DiffStat; +import com.gitblit.wicket.GitBlitWebApp; + +/** + * Generates an html snippet of a diff in Gitblit's style, tracks changed paths, and calculates diff stats. + * + * @author James Moger + * @author Tom <tw201207@gmail.com> + * + */ +public class GitBlitDiffFormatter extends DiffFormatter { + + /** Regex pattern identifying trailing whitespace. */ + private static final Pattern trailingWhitespace = Pattern.compile("(\\s+?)\r?\n?$"); + + /** + * gitblit.properties key for the per-file limit on the number of diff lines. + */ + private static final String DIFF_LIMIT_PER_FILE_KEY = "web.maxDiffLinesPerFile"; + + /** + * gitblit.properties key for the global limit on the number of diff lines in a commitdiff. + */ + private static final String GLOBAL_DIFF_LIMIT_KEY = "web.maxDiffLines"; + + /** + * Diffs with more lines are not shown in commitdiffs. (Similar to what GitHub does.) Can be reduced + * (but not increased) through gitblit.properties key {@link #DIFF_LIMIT_PER_FILE_KEY}. + */ + private static final int DIFF_LIMIT_PER_FILE = 4000; + + /** + * Global diff limit. Commitdiffs with more lines are truncated. Can be reduced (but not increased) + * through gitblit.properties key {@link #GLOBAL_DIFF_LIMIT_KEY}. + */ + private static final int GLOBAL_DIFF_LIMIT = 20000; + + private static final boolean CONVERT_TABS = true; + + private final DiffOutputStream os; + + private final DiffStat diffStat; + + private PathChangeModel currentPath; + + private int left, right; + + /** + * If a single file diff in a commitdiff produces more than this number of lines, we don't display + * the diff. First, it's too taxing on the browser: it'll spend an awful lot of time applying the + * CSS rules (despite my having optimized them). And second, no human can read a diff with thousands + * of lines and make sense of it. + * <p> + * Set to {@link #DIFF_LIMIT_PER_FILE} for commitdiffs, and to -1 (switches off the limit) for + * single-file diffs. + * </p> + */ + private final int maxDiffLinesPerFile; + + /** + * Global limit on the number of diff lines. Set to {@link #GLOBAL_DIFF_LIMIT} for commitdiffs, and + * to -1 (switched off the limit) for single-file diffs. + */ + private final int globalDiffLimit; + + /** Number of lines for the current file diff. Set to zero when a new DiffEntry is started. */ + private int nofLinesCurrent; + /** + * Position in the stream when we try to write the first line. Used to rewind when we detect that + * the diff is too large. + */ + private int startCurrent; + /** Flag set to true when we rewind. Reset to false when we start a new DiffEntry. */ + private boolean isOff; + /** The current diff entry. */ + private DiffEntry entry; + + // Global limit stuff. + + /** Total number of lines written before the current diff entry. */ + private int totalNofLinesPrevious; + /** Running total of the number of diff lines written. Updated until we exceed the global limit. */ + private int totalNofLinesCurrent; + /** Stream position to reset to if we decided to truncate the commitdiff. */ + private int truncateTo; + /** Whether we decided to truncate the commitdiff. */ + private boolean truncated; + /** If {@link #truncated}, contains all entries skipped. */ + private final List<DiffEntry> skipped = new ArrayList<DiffEntry>(); + + private int tabLength; + + /** + * A {@link ResettableByteArrayOutputStream} that intercept the "Binary files differ" message produced + * by the super implementation. Unfortunately the super implementation has far too many things private; + * otherwise we'd just have re-implemented {@link GitBlitDiffFormatter#format(DiffEntry) format(DiffEntry)} + * completely without ever calling the super implementation. + */ + private static class DiffOutputStream extends ResettableByteArrayOutputStream { + + private static final String BINARY_DIFFERENCE = "Binary files differ\n"; + + private GitBlitDiffFormatter formatter; + private BinaryDiffHandler binaryDiffHandler; + + public void setFormatter(GitBlitDiffFormatter formatter, BinaryDiffHandler handler) { + this.formatter = formatter; + this.binaryDiffHandler = handler; + } + + @Override + public void write(byte[] b, int offset, int length) { + if (binaryDiffHandler != null + && RawParseUtils.decode(Arrays.copyOfRange(b, offset, offset + length)).contains(BINARY_DIFFERENCE)) + { + String binaryDiff = binaryDiffHandler.renderBinaryDiff(formatter.entry); + if (binaryDiff != null) { + byte[] bb = ("<tr><td colspan='4' align='center'>" + binaryDiff + "</td></tr>").getBytes(StandardCharsets.UTF_8); + super.write(bb, 0, bb.length); + return; + } + } + super.write(b, offset, length); + } + + } + + public GitBlitDiffFormatter(String commitId, String path, BinaryDiffHandler handler, int tabLength) { + super(new DiffOutputStream()); + this.os = (DiffOutputStream) getOutputStream(); + this.os.setFormatter(this, handler); + this.diffStat = new DiffStat(commitId); + this.tabLength = tabLength; + // If we have a full commitdiff, install maxima to avoid generating a super-long diff listing that + // will only tax the browser too much. + maxDiffLinesPerFile = path != null ? -1 : getLimit(DIFF_LIMIT_PER_FILE_KEY, 500, DIFF_LIMIT_PER_FILE); + globalDiffLimit = path != null ? -1 : getLimit(GLOBAL_DIFF_LIMIT_KEY, 1000, GLOBAL_DIFF_LIMIT); + } + + /** + * Determines a limit to use for HTML diff output. + * + * @param key + * to use to read the value from the GitBlit settings, if available. + * @param minimum + * minimum value to enforce + * @param maximum + * maximum (and default) value to enforce + * @return the limit + */ + private int getLimit(String key, int minimum, int maximum) { + if (Application.exists()) { + Application application = Application.get(); + if (application instanceof GitBlitWebApp) { + GitBlitWebApp webApp = (GitBlitWebApp) application; + int configValue = webApp.settings().getInteger(key, maximum); + if (configValue < minimum) { + return minimum; + } else if (configValue < maximum) { + return configValue; + } + } + } + return maximum; + } + + /** + * Returns a localized message string, if there is a localization; otherwise the given default value. + * + * @param key + * message key for the message + * @param defaultValue + * to use if no localization for the message can be found + * @return the possibly localized message + */ + private String getMsg(String key, String defaultValue) { + if (Application.exists()) { + Localizer localizer = Application.get().getResourceSettings().getLocalizer(); + if (localizer != null) { + // Use getStringIgnoreSettings because we don't want exceptions here if the key is missing! + return localizer.getStringIgnoreSettings(key, null, null, defaultValue); + } + } + return defaultValue; + } + + @Override + public void format(DiffEntry ent) throws IOException { + currentPath = diffStat.addPath(ent); + nofLinesCurrent = 0; + isOff = false; + entry = ent; + if (!truncated) { + totalNofLinesPrevious = totalNofLinesCurrent; + if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) { + truncated = true; + isOff = true; + } + truncateTo = os.size(); + } else { + isOff = true; + } + if (truncated) { + skipped.add(ent); + } else { + // Produce a header here and now + String path; + String id; + if (ChangeType.DELETE.equals(ent.getChangeType())) { + path = ent.getOldPath(); + id = ent.getOldId().name(); + } else { + path = ent.getNewPath(); + id = ent.getNewId().name(); + } + StringBuilder sb = new StringBuilder(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"n{0}\"><i class=\"icon-file\"></i> ", id)); + sb.append(StringUtils.escapeForHtml(path, false)).append("</div></div>"); + sb.append("<div class=\"diff\"><table cellpadding='0'><tbody>\n"); + os.write(sb.toString().getBytes()); + } + // Keep formatting, but if off, don't produce anything anymore. We just keep on counting. + super.format(ent); + if (!truncated) { + // Close the table + os.write("</tbody></table></div>\n".getBytes()); + } + } + + @Override + public void flush() throws IOException { + if (truncated) { + os.resetTo(truncateTo); + } + super.flush(); + } + + /** + * Rewind and issue a message that the diff is too large. + */ + private void reset() { + if (!isOff) { + os.resetTo(startCurrent); + writeFullWidthLine(getMsg("gb.diffFileDiffTooLarge", "Diff too large")); + totalNofLinesCurrent = totalNofLinesPrevious; + isOff = true; + } + } + + /** + * Writes an initial table row containing information about added/removed/renamed/copied files. In case + * of a deletion, we also suppress generating the diff; it's not interesting. (All lines removed.) + */ + private void handleChange() { + // XXX Would be nice if we could generate blob links for the cases handled here. Alas, we lack the repo + // name, and cannot reliably determine it here. We could get the .git directory of a Repository, if we + // passed in the repo, and then take the name of the parent directory, but that'd fail for repos nested + // in GitBlit projects. And we don't know if the repo is inside a project or is a top-level repo. + // + // That's certainly solvable (just pass along more information), but would require a larger rewrite than + // I'm prepared to do now. + String message; + switch (entry.getChangeType()) { + case ADD: + message = getMsg("gb.diffNewFile", "New file"); + break; + case DELETE: + message = getMsg("gb.diffDeletedFile", "File was deleted"); + isOff = true; + break; + case RENAME: + message = MessageFormat.format(getMsg("gb.diffRenamedFile", "File was renamed from {0}"), entry.getOldPath()); + break; + case COPY: + message = MessageFormat.format(getMsg("gb.diffCopiedFile", "File was copied from {0}"), entry.getOldPath()); + break; + default: + return; + } + writeFullWidthLine(message); + } + + /** + * Output a hunk header + * + * @param aStartLine + * within first source + * @param aEndLine + * within first source + * @param bStartLine + * within second source + * @param bEndLine + * within second source + * @throws IOException + */ + @Override + protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException { + if (nofLinesCurrent++ == 0) { + handleChange(); + startCurrent = os.size(); + } + if (!isOff) { + totalNofLinesCurrent++; + if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) { + reset(); + } else { + os.write("<tr><th class='diff-line' data-lineno='..'></th><th class='diff-line' data-lineno='..'></th><th class='diff-state'></th><td class='hunk_header'>" + .getBytes()); + os.write('@'); + os.write('@'); + writeRange('-', aStartLine + 1, aEndLine - aStartLine); + writeRange('+', bStartLine + 1, bEndLine - bStartLine); + os.write(' '); + os.write('@'); + os.write('@'); + os.write("</td></tr>\n".getBytes()); + } + } + left = aStartLine + 1; + right = bStartLine + 1; + } + + protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException { + os.write(' '); + os.write(prefix); + switch (cnt) { + case 0: + // If the range is empty, its beginning number must be the + // line just before the range, or 0 if the range is at the + // start of the file stream. Here, begin is always 1 based, + // so an empty file would produce "0,0". + // + os.write(encodeASCII(begin - 1)); + os.write(','); + os.write('0'); + break; + + case 1: + // If the range is exactly one line, produce only the number. + // + os.write(encodeASCII(begin)); + break; + + default: + os.write(encodeASCII(begin)); + os.write(','); + os.write(encodeASCII(cnt)); + break; + } + } + + /** + * Writes a line spanning the full width of the code view, including the gutter. + * + * @param text + * to put on that line; will be HTML-escaped. + */ + private void writeFullWidthLine(String text) { + try { + os.write("<tr><td class='diff-cell' colspan='4'>".getBytes()); + os.write(StringUtils.escapeForHtml(text, false).getBytes()); + os.write("</td></tr>\n".getBytes()); + } catch (IOException ex) { + // Cannot happen with a ByteArrayOutputStream + } + } + + @Override + protected void writeLine(final char prefix, final RawText text, final int cur) throws IOException { + if (nofLinesCurrent++ == 0) { + handleChange(); + startCurrent = os.size(); + } + // update entry diffstat + currentPath.update(prefix); + if (isOff) { + return; + } + totalNofLinesCurrent++; + if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) { + reset(); + } else { + // output diff + os.write("<tr>".getBytes()); + switch (prefix) { + case '+': + os.write(("<th class='diff-line'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes()); + os.write("<th class='diff-state diff-state-add'></th>".getBytes()); + os.write("<td class='diff-cell add2'>".getBytes()); + break; + case '-': + os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line'></th>").getBytes()); + os.write("<th class='diff-state diff-state-sub'></th>".getBytes()); + os.write("<td class='diff-cell remove2'>".getBytes()); + break; + default: + os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes()); + os.write("<th class='diff-state'></th>".getBytes()); + os.write("<td class='diff-cell context2'>".getBytes()); + break; + } + os.write(encode(codeLineToHtml(prefix, text.getString(cur)))); + os.write("</td></tr>\n".getBytes()); + } + } + + /** + * Convert the given code line to HTML. + * + * @param prefix + * the diff prefix (+/-) indicating whether the line was added or removed. + * @param line + * the line to format as HTML + * @return the HTML-formatted line, safe for inserting as is into HTML. + */ + private String codeLineToHtml(final char prefix, final String line) { + if ((prefix == '+' || prefix == '-')) { + // Highlight trailing whitespace on deleted/added lines. + Matcher matcher = trailingWhitespace.matcher(line); + if (matcher.find()) { + StringBuilder result = new StringBuilder(StringUtils.escapeForHtml(line.substring(0, matcher.start()), CONVERT_TABS, tabLength)); + result.append("<span class='trailingws-").append(prefix == '+' ? "add" : "sub").append("'>"); + result.append(StringUtils.escapeForHtml(matcher.group(1), false)); + result.append("</span>"); + return result.toString(); + } + } + return StringUtils.escapeForHtml(line, CONVERT_TABS, tabLength); + } + + /** + * Workaround function for complex private methods in DiffFormatter. This sets the html for the diff headers. + * + * @return + */ + public String getHtml() { + String html = RawParseUtils.decode(os.toByteArray()); + String[] lines = html.split("\n"); + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + if (line.startsWith("index") || line.startsWith("similarity") + || line.startsWith("rename from ") || line.startsWith("rename to ")) { + // skip index lines + } else if (line.startsWith("new file") || line.startsWith("deleted file")) { + // skip new file lines + } else if (line.startsWith("\\ No newline")) { + // skip no new line + } else if (line.startsWith("---") || line.startsWith("+++")) { + // skip --- +++ lines + } else if (line.startsWith("diff")) { + // skip diff lines + } else { + boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit"); + if (gitLinkDiff) { + sb.append("<tr><th class='diff-line'></th><th class='diff-line'></th>"); + if (line.charAt(0) == '+') { + sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">"); + } else { + sb.append("<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">"); + } + line = StringUtils.escapeForHtml(line.substring(1), CONVERT_TABS, tabLength); + } + sb.append(line); + if (gitLinkDiff) { + sb.append("</td></tr>"); + } + sb.append('\n'); + } + } + if (truncated) { + sb.append(MessageFormat.format("<div class='header'><div class='diffHeader'>{0}</div></div>", + StringUtils.escapeForHtml(getMsg("gb.diffTruncated", "Diff truncated after the above file"), false))); + // List all files not shown. We can be sure we do have at least one path in skipped. + sb.append("<div class='diff'><table cellpadding='0'><tbody><tr><td class='diff-cell' colspan='4'>"); + String deletedSuffix = StringUtils.escapeForHtml(getMsg("gb.diffDeletedFileSkipped", "(deleted)"), false); + boolean first = true; + for (DiffEntry entry : skipped) { + if (!first) { + sb.append('\n'); + } + if (ChangeType.DELETE.equals(entry.getChangeType())) { + sb.append("<span id=\"n" + entry.getOldId().name() + "\">" + StringUtils.escapeForHtml(entry.getOldPath(), false) + ' ' + deletedSuffix + "</span>"); + } else { + sb.append("<span id=\"n" + entry.getNewId().name() + "\">" + StringUtils.escapeForHtml(entry.getNewPath(), false) + "</span>"); + } + first = false; + } + skipped.clear(); + sb.append("</td></tr></tbody></table></div>"); + } + return sb.toString(); + } + + public DiffStat getDiffStat() { + return diffStat; + } +} diff --git a/src/main/java/com/gitblit/utils/HtmlBuilder.java b/src/main/java/com/gitblit/utils/HtmlBuilder.java new file mode 100644 index 00000000..6208ea82 --- /dev/null +++ b/src/main/java/com/gitblit/utils/HtmlBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014 Tom <tw201207@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.utils; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.parser.Tag; + +/** + * Simple helper class to hide some common setup needed to use JSoup as an HTML generator. + * A {@link HtmlBuilder} has a root element that can be manipulated in all the usual JSoup + * ways (but not added to some {@link Document}); to generate HTML for that root element, + * the builder's {@link #toString()} method can be used. (Or one can invoke toString() + * directly on the root element.) By default, a HTML builder does not pretty-print the HTML. + * + * @author Tom <tw201207@gmail.com> + */ +public class HtmlBuilder { + + private final Document shell; + private final Element root; + + /** + * Creates a new HTML builder with a root element with the given {@code tagName}. + * + * @param tagName + * of the {@link Element} to set as the root element. + */ + public HtmlBuilder(String tagName) { + this(new Element(Tag.valueOf(tagName), "")); + } + + /** + * Creates a new HTML builder for the given element. + * + * @param element + * to set as the root element of this HTML builder. + */ + public HtmlBuilder(Element element) { + shell = Document.createShell(""); + shell.outputSettings().prettyPrint(false); + shell.body().appendChild(element); + root = element; + } + + /** @return the root element of this HTML builder */ + public Element getRoot() { + return root; + } + + /** @return the root element of this HTML builder */ + public Element root() { + return root; + } + + /** @return whether this HTML builder will pretty-print the HTML generated by {@link #toString()} */ + public boolean prettyPrint() { + return shell.outputSettings().prettyPrint(); + } + + /** + * Sets whether this HTML builder will produce pretty-printed HTML in its {@link #toString()} method. + * + * @param pretty + * whether to pretty-print + * @return the HTML builder itself + */ + public HtmlBuilder prettyPrint(boolean pretty) { + shell.outputSettings().prettyPrint(pretty); + return this; + } + + /** @return the HTML for the root element. */ + @Override + public String toString() { + return root.toString(); + } + + /** @return the {@link Document} used as generation shell. */ + protected Document getShell() { + return shell; + } +} diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java index da51ea98..c3d02073 100644 --- a/src/main/java/com/gitblit/utils/JGitUtils.java +++ b/src/main/java/com/gitblit/utils/JGitUtils.java @@ -90,6 +90,7 @@ import com.gitblit.models.PathModel; import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.SubmoduleModel;
+import com.google.common.base.Strings;
/**
* Collection of static methods for retrieving information from a repository.
@@ -690,7 +691,10 @@ public class JGitUtils { if (commit == null) {
return new Date(0);
}
- return commit.getAuthorIdent().getWhen();
+ if (commit.getAuthorIdent() != null) {
+ return commit.getAuthorIdent().getWhen();
+ }
+ return getCommitDate(commit);
}
/**
@@ -773,7 +777,7 @@ public class JGitUtils { }
} finally {
rw.dispose();
- tw.release();
+ tw.close();
}
return content;
}
@@ -884,7 +888,64 @@ public class JGitUtils { } catch (IOException e) {
error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
} finally {
- tw.release();
+ tw.close();
+ }
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * Returns the list of files in the specified folder at the specified
+ * commit. If the repository does not exist or is empty, an empty list is
+ * returned.
+ *
+ * This is modified version that implements path compression feature.
+ *
+ * @param repository
+ * @param path
+ * if unspecified, root folder is assumed.
+ * @param commit
+ * if null, HEAD is assumed.
+ * @return list of files in specified path
+ */
+ public static List<PathModel> getFilesInPath2(Repository repository, String path, RevCommit commit) {
+
+ List<PathModel> list = new ArrayList<PathModel>();
+ if (!hasCommits(repository)) {
+ return list;
+ }
+ if (commit == null) {
+ commit = getCommit(repository, null);
+ }
+ final TreeWalk tw = new TreeWalk(repository);
+ try {
+
+ tw.addTree(commit.getTree());
+ final boolean isPathEmpty = Strings.isNullOrEmpty(path);
+
+ if (!isPathEmpty) {
+ PathFilter f = PathFilter.create(path);
+ tw.setFilter(f);
+ }
+
+ tw.setRecursive(true);
+ List<String> paths = new ArrayList<>();
+
+ while (tw.next()) {
+ String child = isPathEmpty ? tw.getPathString()
+ : tw.getPathString().replaceFirst(String.format("%s/", path), "");
+ paths.add(child);
+ }
+
+ for(String p: PathUtils.compressPaths(paths)) {
+ String pathString = isPathEmpty ? p : String.format("%s/%s", path, p);
+ list.add(getPathModel(repository, pathString, path, commit));
+ }
+
+ } catch (IOException e) {
+ error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
+ } finally {
+ tw.close();
}
Collections.sort(list);
return list;
@@ -936,7 +997,7 @@ public class JGitUtils { .getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
ChangeType.ADD));
}
- tw.release();
+ tw.close();
} else {
RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
DiffStatFormatter df = new DiffStatFormatter(commit.getName());
@@ -991,7 +1052,7 @@ public class JGitUtils { RevCommit start = rw.parseCommit(startRange);
RevCommit end = rw.parseCommit(endRange);
list.addAll(getFilesInRange(repository, start, end));
- rw.release();
+ rw.close();
} catch (Throwable t) {
error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
}
@@ -1089,7 +1150,7 @@ public class JGitUtils { } catch (IOException e) {
error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
} finally {
- tw.release();
+ tw.close();
}
Collections.sort(list);
return list;
@@ -1124,6 +1185,46 @@ public class JGitUtils { }
/**
+ * Returns a path model by path string
+ *
+ * @param repo
+ * @param path
+ * @param filter
+ * @param commit
+ * @return a path model of the specified object
+ */
+ private static PathModel getPathModel(Repository repo, String path, String filter, RevCommit commit)
+ throws IOException {
+
+ long size = 0;
+ TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
+ String pathString = path;
+
+ if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+ size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
+ pathString = PathUtils.getLastPathComponent(pathString);
+
+ } else if (tw.isSubtree()) {
+
+ // do not display dirs that are behind in the path
+ if (!Strings.isNullOrEmpty(filter)) {
+ pathString = path.replaceFirst(filter + "/", "");
+ }
+
+ // remove the last slash from path in displayed link
+ if (pathString != null && pathString.charAt(pathString.length()-1) == '/') {
+ pathString = pathString.substring(0, pathString.length()-1);
+ }
+ }
+
+ return new PathModel(pathString, tw.getPathString(), size, tw.getFileMode(0).getBits(),
+ tw.getObjectId(0).getName(), commit.getName());
+
+
+ }
+
+
+ /**
* Returns a permissions representation of the mode bits.
*
* @param mode
@@ -1946,7 +2047,7 @@ public class JGitUtils { error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
} finally {
rw.dispose();
- tw.release();
+ tw.close();
}
return commitId;
}
@@ -2120,10 +2221,10 @@ public class JGitUtils { success = false;
}
} finally {
- revWalk.release();
+ revWalk.close();
}
} finally {
- odi.release();
+ odi.close();
}
} catch (Throwable t) {
error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
@@ -2194,212 +2295,222 @@ public class JGitUtils { }
return false;
}
- - /** - * Returns true if the commit identified by commitId is an ancestor or the - * the commit identified by tipId. - * - * @param repository - * @param commitId - * @param tipId - * @return true if there is the commit is an ancestor of the tip - */ - public static boolean isMergedInto(Repository repository, String commitId, String tipId) { - try { - return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId)); - } catch (Exception e) { - LOGGER.error("Failed to determine isMergedInto", e); - } - return false; - } - - /** - * Returns true if the commit identified by commitId is an ancestor or the - * the commit identified by tipId. - * - * @param repository - * @param commitId - * @param tipId - * @return true if there is the commit is an ancestor of the tip - */ - public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) { - // traverse the revlog looking for a commit chain between the endpoints - RevWalk rw = new RevWalk(repository); - try { - // must re-lookup RevCommits to workaround undocumented RevWalk bug - RevCommit tip = rw.lookupCommit(tipCommitId); - RevCommit commit = rw.lookupCommit(commitId); - return rw.isMergedInto(commit, tip); - } catch (Exception e) { - LOGGER.error("Failed to determine isMergedInto", e); - } finally { - rw.dispose(); - } - return false; - } - - /** - * Returns the merge base of two commits or null if there is no common - * ancestry. - * - * @param repository - * @param commitIdA - * @param commitIdB - * @return the commit id of the merge base or null if there is no common base - */ - public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) { - RevWalk rw = new RevWalk(repository); - try { - RevCommit a = rw.lookupCommit(commitIdA); - RevCommit b = rw.lookupCommit(commitIdB); - - rw.setRevFilter(RevFilter.MERGE_BASE); - rw.markStart(a); - rw.markStart(b); - RevCommit mergeBase = rw.next(); - if (mergeBase == null) { - return null; - } - return mergeBase.getName(); - } catch (Exception e) { - LOGGER.error("Failed to determine merge base", e); - } finally { - rw.dispose(); - } - return null; - } - - public static enum MergeStatus { - NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED; - } - - /** - * Determines if we can cleanly merge one branch into another. Returns true - * if we can merge without conflict, otherwise returns false. - * - * @param repository - * @param src - * @param toBranch - * @return true if we can merge without conflict - */ - public static MergeStatus canMerge(Repository repository, String src, String toBranch) { - RevWalk revWalk = null; - try { - revWalk = new RevWalk(repository); - RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch)); - RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src)); - if (revWalk.isMergedInto(srcTip, branchTip)) { - // already merged - return MergeStatus.ALREADY_MERGED; - } else if (revWalk.isMergedInto(branchTip, srcTip)) { - // fast-forward - return MergeStatus.MERGEABLE; - } - RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); - boolean canMerge = merger.merge(branchTip, srcTip); - if (canMerge) { - return MergeStatus.MERGEABLE; - } - } catch (IOException e) { - LOGGER.error("Failed to determine canMerge", e); +
+ /**
+ * Returns true if the commit identified by commitId is an ancestor or the
+ * the commit identified by tipId.
+ *
+ * @param repository
+ * @param commitId
+ * @param tipId
+ * @return true if there is the commit is an ancestor of the tip
+ */
+ public static boolean isMergedInto(Repository repository, String commitId, String tipId) {
+ try {
+ return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId));
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine isMergedInto", e);
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the commit identified by commitId is an ancestor or the
+ * the commit identified by tipId.
+ *
+ * @param repository
+ * @param commitId
+ * @param tipId
+ * @return true if there is the commit is an ancestor of the tip
+ */
+ public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) {
+ // traverse the revlog looking for a commit chain between the endpoints
+ RevWalk rw = new RevWalk(repository);
+ try {
+ // must re-lookup RevCommits to workaround undocumented RevWalk bug
+ RevCommit tip = rw.lookupCommit(tipCommitId);
+ RevCommit commit = rw.lookupCommit(commitId);
+ return rw.isMergedInto(commit, tip);
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine isMergedInto", e);
+ } finally {
+ rw.dispose();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the merge base of two commits or null if there is no common
+ * ancestry.
+ *
+ * @param repository
+ * @param commitIdA
+ * @param commitIdB
+ * @return the commit id of the merge base or null if there is no common base
+ */
+ public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) {
+ RevWalk rw = new RevWalk(repository);
+ try {
+ RevCommit a = rw.lookupCommit(commitIdA);
+ RevCommit b = rw.lookupCommit(commitIdB);
+
+ rw.setRevFilter(RevFilter.MERGE_BASE);
+ rw.markStart(a);
+ rw.markStart(b);
+ RevCommit mergeBase = rw.next();
+ if (mergeBase == null) {
+ return null;
+ }
+ return mergeBase.getName();
+ } catch (Exception e) {
+ LOGGER.error("Failed to determine merge base", e);
+ } finally {
+ rw.dispose();
+ }
+ return null;
+ }
+
+ public static enum MergeStatus {
+ MISSING_INTEGRATION_BRANCH, MISSING_SRC_BRANCH, NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED;
+ }
+
+ /**
+ * Determines if we can cleanly merge one branch into another. Returns true
+ * if we can merge without conflict, otherwise returns false.
+ *
+ * @param repository
+ * @param src
+ * @param toBranch
+ * @return true if we can merge without conflict
+ */
+ public static MergeStatus canMerge(Repository repository, String src, String toBranch) {
+ RevWalk revWalk = null;
+ try {
+ revWalk = new RevWalk(repository);
+ ObjectId branchId = repository.resolve(toBranch);
+ if (branchId == null) {
+ return MergeStatus.MISSING_INTEGRATION_BRANCH;
+ }
+ ObjectId srcId = repository.resolve(src);
+ if (srcId == null) {
+ return MergeStatus.MISSING_SRC_BRANCH;
+ }
+ RevCommit branchTip = revWalk.lookupCommit(branchId);
+ RevCommit srcTip = revWalk.lookupCommit(srcId);
+ if (revWalk.isMergedInto(srcTip, branchTip)) {
+ // already merged
+ return MergeStatus.ALREADY_MERGED;
+ } else if (revWalk.isMergedInto(branchTip, srcTip)) {
+ // fast-forward
+ return MergeStatus.MERGEABLE;
+ }
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+ boolean canMerge = merger.merge(branchTip, srcTip);
+ if (canMerge) {
+ return MergeStatus.MERGEABLE;
+ }
+ } catch (NullPointerException e) {
+ LOGGER.error("Failed to determine canMerge", e);
+ } catch (IOException e) {
+ LOGGER.error("Failed to determine canMerge", e);
} finally {
- if (revWalk != null) { - revWalk.release();
- } - } - return MergeStatus.NOT_MERGEABLE; - } - - - public static class MergeResult { - public final MergeStatus status; - public final String sha; - - MergeResult(MergeStatus status, String sha) { - this.status = status; - this.sha = sha; - } - } - - /** - * Tries to merge a commit into a branch. If there are conflicts, the merge - * will fail. - * - * @param repository - * @param src - * @param toBranch - * @param committer - * @param message - * @return the merge result - */ - public static MergeResult merge(Repository repository, String src, String toBranch, - PersonIdent committer, String message) { - - if (!toBranch.startsWith(Constants.R_REFS)) { - // branch ref doesn't start with ref, assume this is a branch head - toBranch = Constants.R_HEADS + toBranch; - } - - RevWalk revWalk = null; - try { - revWalk = new RevWalk(repository); - RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch)); - RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src)); - if (revWalk.isMergedInto(srcTip, branchTip)) { - // already merged - return new MergeResult(MergeStatus.ALREADY_MERGED, null); - } - RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true); - boolean merged = merger.merge(branchTip, srcTip); - if (merged) { - // create a merge commit and a reference to track the merge commit - ObjectId treeId = merger.getResultTreeId(); - ObjectInserter odi = repository.newObjectInserter(); - try { - // Create a commit object - CommitBuilder commitBuilder = new CommitBuilder(); - commitBuilder.setCommitter(committer); - commitBuilder.setAuthor(committer); - commitBuilder.setEncoding(Constants.CHARSET); - if (StringUtils.isEmpty(message)) { - message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName()); - } - commitBuilder.setMessage(message); - commitBuilder.setParentIds(branchTip.getId(), srcTip.getId()); - commitBuilder.setTreeId(treeId); - - // Insert the merge commit into the repository - ObjectId mergeCommitId = odi.insert(commitBuilder); - odi.flush(); - - // set the merge ref to the merge commit - RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId); - RefUpdate mergeRefUpdate = repository.updateRef(toBranch); - mergeRefUpdate.setNewObjectId(mergeCommitId); - mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false); - RefUpdate.Result rc = mergeRefUpdate.update(); - switch (rc) { - case FAST_FORWARD: - // successful, clean merge + if (revWalk != null) {
+ revWalk.close();
+ }
+ }
+ return MergeStatus.NOT_MERGEABLE;
+ }
+
+
+ public static class MergeResult {
+ public final MergeStatus status;
+ public final String sha;
+
+ MergeResult(MergeStatus status, String sha) {
+ this.status = status;
+ this.sha = sha;
+ }
+ }
+
+ /**
+ * Tries to merge a commit into a branch. If there are conflicts, the merge
+ * will fail.
+ *
+ * @param repository
+ * @param src
+ * @param toBranch
+ * @param committer
+ * @param message
+ * @return the merge result
+ */
+ public static MergeResult merge(Repository repository, String src, String toBranch,
+ PersonIdent committer, String message) {
+
+ if (!toBranch.startsWith(Constants.R_REFS)) {
+ // branch ref doesn't start with ref, assume this is a branch head
+ toBranch = Constants.R_HEADS + toBranch;
+ }
+
+ RevWalk revWalk = null;
+ try {
+ revWalk = new RevWalk(repository);
+ RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
+ RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
+ if (revWalk.isMergedInto(srcTip, branchTip)) {
+ // already merged
+ return new MergeResult(MergeStatus.ALREADY_MERGED, null);
+ }
+ RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+ boolean merged = merger.merge(branchTip, srcTip);
+ if (merged) {
+ // create a merge commit and a reference to track the merge commit
+ ObjectId treeId = merger.getResultTreeId();
+ ObjectInserter odi = repository.newObjectInserter();
+ try {
+ // Create a commit object
+ CommitBuilder commitBuilder = new CommitBuilder();
+ commitBuilder.setCommitter(committer);
+ commitBuilder.setAuthor(committer);
+ commitBuilder.setEncoding(Constants.CHARSET);
+ if (StringUtils.isEmpty(message)) {
+ message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());
+ }
+ commitBuilder.setMessage(message);
+ commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());
+ commitBuilder.setTreeId(treeId);
+
+ // Insert the merge commit into the repository
+ ObjectId mergeCommitId = odi.insert(commitBuilder);
+ odi.flush();
+
+ // set the merge ref to the merge commit
+ RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);
+ RefUpdate mergeRefUpdate = repository.updateRef(toBranch);
+ mergeRefUpdate.setNewObjectId(mergeCommitId);
+ mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);
+ RefUpdate.Result rc = mergeRefUpdate.update();
+ switch (rc) {
+ case FAST_FORWARD:
+ // successful, clean merge
break;
- default: - throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}", - rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory())); - } - - // return the merge commit id - return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName()); - } finally { - odi.release(); - } - } - } catch (IOException e) { - LOGGER.error("Failed to merge", e); + default:
+ throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",
+ rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));
+ }
+
+ // return the merge commit id
+ return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());
+ } finally {
+ odi.close();
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.error("Failed to merge", e);
} finally {
- if (revWalk != null) { - revWalk.release();
- } - } - return new MergeResult(MergeStatus.FAILED, null); - } + if (revWalk != null) {
+ revWalk.close();
+ }
+ }
+ return new MergeResult(MergeStatus.FAILED, null);
+ }
}
diff --git a/src/main/java/com/gitblit/utils/JSoupXssFilter.java b/src/main/java/com/gitblit/utils/JSoupXssFilter.java index 5ab7953a..aec22411 100644 --- a/src/main/java/com/gitblit/utils/JSoupXssFilter.java +++ b/src/main/java/com/gitblit/utils/JSoupXssFilter.java @@ -20,18 +20,23 @@ import org.jsoup.nodes.Document; import org.jsoup.safety.Cleaner; import org.jsoup.safety.Whitelist; +import com.google.inject.Inject; +import com.google.inject.Singleton; + /** * Implementation of an XSS filter based on JSoup. * * @author James Moger * */ +@Singleton public class JSoupXssFilter implements XssFilter { private final Cleaner none; private final Cleaner relaxed; + @Inject public JSoupXssFilter() { none = new Cleaner(Whitelist.none()); relaxed = new Cleaner(getRelaxedWhiteList()); diff --git a/src/main/java/com/gitblit/utils/JsonUtils.java b/src/main/java/com/gitblit/utils/JsonUtils.java index be7148cb..f389776b 100644 --- a/src/main/java/com/gitblit/utils/JsonUtils.java +++ b/src/main/java/com/gitblit/utils/JsonUtils.java @@ -46,6 +46,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
@@ -79,23 +80,29 @@ public class JsonUtils { /**
* Convert a json string to an object of the specified type.
- *
+ *
* @param json
* @param clazz
- * @return an object
+ * @return the deserialized object
+ * @throws JsonParseException
+ * @throws JsonSyntaxException
*/
- public static <X> X fromJsonString(String json, Class<X> clazz) {
+ public static <X> X fromJsonString(String json, Class<X> clazz) throws JsonParseException,
+ JsonSyntaxException {
return gson().fromJson(json, clazz);
}
/**
* Convert a json string to an object of the specified type.
- *
+ *
* @param json
- * @param clazz
- * @return an object
+ * @param type
+ * @return the deserialized object
+ * @throws JsonParseException
+ * @throws JsonSyntaxException
*/
- public static <X> X fromJsonString(String json, Type type) {
+ public static <X> X fromJsonString(String json, Type type) throws JsonParseException,
+ JsonSyntaxException {
return gson().fromJson(json, type);
}
diff --git a/src/main/java/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java index 2ebfdb26..e0c9dd4e 100644 --- a/src/main/java/com/gitblit/utils/MarkdownUtils.java +++ b/src/main/java/com/gitblit/utils/MarkdownUtils.java @@ -16,6 +16,7 @@ package com.gitblit.utils;
import static org.pegdown.Extensions.ALL;
+import static org.pegdown.Extensions.ANCHORLINKS;
import static org.pegdown.Extensions.SMARTYPANTS;
import java.io.IOException;
@@ -76,7 +77,7 @@ public class MarkdownUtils { */
public static String transformMarkdown(String markdown, LinkRenderer linkRenderer) {
try {
- PegDownProcessor pd = new PegDownProcessor(ALL & ~SMARTYPANTS);
+ PegDownProcessor pd = new PegDownProcessor(ALL & ~SMARTYPANTS & ~ANCHORLINKS);
RootNode astRoot = pd.parseMarkdown(markdown.toCharArray());
return new WorkaroundHtmlSerializer(linkRenderer == null ? new LinkRenderer() : linkRenderer).toHtml(astRoot);
} catch (ParsingTimeoutException e) {
diff --git a/src/main/java/com/gitblit/utils/MetricUtils.java b/src/main/java/com/gitblit/utils/MetricUtils.java index 47031021..62427e6d 100644 --- a/src/main/java/com/gitblit/utils/MetricUtils.java +++ b/src/main/java/com/gitblit/utils/MetricUtils.java @@ -136,7 +136,7 @@ public class MetricUtils { Iterable<RevCommit> revlog = revWalk;
for (RevCommit rev : revlog) {
- Date d = JGitUtils.getCommitDate(rev);
+ Date d = JGitUtils.getAuthorDate(rev);
String p = df.format(d);
if (!metricMap.containsKey(p)) {
metricMap.put(p, new Metric(p));
diff --git a/src/main/java/com/gitblit/utils/PathUtils.java b/src/main/java/com/gitblit/utils/PathUtils.java new file mode 100644 index 00000000..a3c7d8d6 --- /dev/null +++ b/src/main/java/com/gitblit/utils/PathUtils.java @@ -0,0 +1,92 @@ +package com.gitblit.utils; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; + +import java.util.*; + +/** + * Utils for handling path strings + * + */ +public class PathUtils { + + private PathUtils() {} + + /** + * Compress paths containing no files + * + * @param paths lines from `git ls-tree -r --name-only ${branch}` + * @return compressed paths + */ + public static List<String> compressPaths(final Iterable<String> paths) { + + ArrayList<String> pathList = new ArrayList<>(); + Map<String, List<String[]>> folderRoots = new LinkedHashMap<>(); + + for (String s: paths) { + String[] components = s.split("/"); + + // File in current directory + if (components.length == 1) { + pathList.add(components[0]); + + // Directory path + } else { + List<String[]> rootedPaths = folderRoots.get(components[0]); + if (rootedPaths == null) { + rootedPaths = new ArrayList<>(); + } + rootedPaths.add(components); + folderRoots.put(components[0], rootedPaths); + } + } + + for (String folder: folderRoots.keySet()) { + List<String[]> matchingPaths = folderRoots.get(folder); + + if (matchingPaths.size() == 1) { + pathList.add(toStringPath(matchingPaths.get(0))); + } else { + pathList.add(longestCommonSequence(matchingPaths)); + } + } + return pathList; + } + + /** + * Get last path component + * + * + * @param path string path separated by slashes + * @return rightmost entry + */ + public static String getLastPathComponent(final String path) { + return Iterables.getLast(Splitter.on("/").omitEmptyStrings().split(path), path); + } + + private static String toStringPath(final String[] pathComponents) { + List<String> tmp = Arrays.asList(pathComponents); + return Joiner.on('/').join(tmp.subList(0,tmp.size()-1)) + '/'; + } + + + private static String longestCommonSequence(final List<String[]> paths) { + + StringBuilder path = new StringBuilder(); + + for (int i = 0; i < paths.get(0).length; i++) { + String current = paths.get(0)[i]; + for (int j = 1; j < paths.size(); j++) { + if (!current.equals(paths.get(j)[i])) { + return path.toString(); + } + } + path.append(current); + path.append('/'); + } + return path.toString(); + } + +} diff --git a/src/main/java/com/gitblit/utils/RefLogUtils.java b/src/main/java/com/gitblit/utils/RefLogUtils.java index f08c99e7..355c1208 100644 --- a/src/main/java/com/gitblit/utils/RefLogUtils.java +++ b/src/main/java/com/gitblit/utils/RefLogUtils.java @@ -294,10 +294,10 @@ public class RefLogUtils { rc)); } } finally { - revWalk.release(); + revWalk.close(); } } finally { - odi.release(); + odi.close(); } } catch (Throwable t) { error(t, repository, "Failed to commit reflog entry to {0}"); @@ -395,12 +395,12 @@ public class RefLogUtils { } // release the treewalk - treeWalk.release(); + treeWalk.close(); // finish temporary in-core index used for this commit dcBuilder.finish(); } finally { - inserter.release(); + inserter.close(); } return inCoreIndex; } diff --git a/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java new file mode 100644 index 00000000..7df0693f --- /dev/null +++ b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java @@ -0,0 +1,42 @@ +// Copyright (C) 2014 Tom <tw201207@gmail.com> +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.gitblit.utils; + +import java.io.ByteArrayOutputStream; + +/** + * A {@link ByteArrayOutputStream} that can be reset to a specified position. + * + * @author Tom <tw201207@gmail.com> + */ +public class ResettableByteArrayOutputStream extends ByteArrayOutputStream { + + /** + * Reset the stream to the given position. If {@code mark} is <= 0, see {@link #reset()}. + * A no-op if the stream contains less than {@code mark} bytes. Otherwise, resets the + * current writing position to {@code mark}. Previously allocated buffer space will be + * reused in subsequent writes. + * + * @param mark + * to set the current writing position to. + */ + public synchronized void resetTo(int mark) { + if (mark <= 0) { + reset(); + } else if (mark < count) { + count = mark; + } + } + +} diff --git a/src/main/java/com/gitblit/utils/StringUtils.java b/src/main/java/com/gitblit/utils/StringUtils.java index 087de543..643c52c3 100644 --- a/src/main/java/com/gitblit/utils/StringUtils.java +++ b/src/main/java/com/gitblit/utils/StringUtils.java @@ -79,6 +79,19 @@ public class StringUtils { * @return plain text escaped for html
*/
public static String escapeForHtml(String inStr, boolean changeSpace) {
+ return escapeForHtml(inStr, changeSpace, 4);
+ }
+
+ /**
+ * Prepare text for html presentation. Replace sensitive characters with
+ * html entities.
+ *
+ * @param inStr
+ * @param changeSpace
+ * @param tabLength
+ * @return plain text escaped for html
+ */
+ public static String escapeForHtml(String inStr, boolean changeSpace, int tabLength) {
StringBuilder retStr = new StringBuilder();
int i = 0;
while (i < inStr.length()) {
@@ -93,7 +106,9 @@ public class StringUtils { } else if (changeSpace && inStr.charAt(i) == ' ') {
retStr.append(" ");
} else if (changeSpace && inStr.charAt(i) == '\t') {
- retStr.append(" ");
+ for (int j = 0; j < tabLength; j++) {
+ retStr.append(" ");
+ }
} else {
retStr.append(inStr.charAt(i));
}
diff --git a/src/main/java/com/gitblit/utils/X509Utils.java b/src/main/java/com/gitblit/utils/X509Utils.java index f0c1b9df..a2650be4 100644 --- a/src/main/java/com/gitblit/utils/X509Utils.java +++ b/src/main/java/com/gitblit/utils/X509Utils.java @@ -61,6 +61,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream;
import javax.crypto.Cipher;
+import javax.naming.ldap.LdapName;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -80,7 +81,10 @@ import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.openssl.PEMEncryptor;
import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -883,8 +887,11 @@ public class X509Utils { if (pemFile.exists()) {
pemFile.delete();
}
- PEMWriter pemWriter = new PEMWriter(new FileWriter(pemFile));
- pemWriter.writeObject(pair.getPrivate(), "DES-EDE3-CBC", clientMetadata.password.toCharArray(), new SecureRandom());
+ JcePEMEncryptorBuilder builder = new JcePEMEncryptorBuilder("DES-EDE3-CBC");
+ builder.setSecureRandom(new SecureRandom());
+ PEMEncryptor pemEncryptor = builder.build(clientMetadata.password.toCharArray());
+ JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemFile));
+ pemWriter.writeObject(pair.getPrivate(), pemEncryptor);
pemWriter.writeObject(userCert);
pemWriter.writeObject(caCert);
pemWriter.flush();
@@ -1111,17 +1118,18 @@ public class X509Utils { }
public static X509Metadata getMetadata(X509Certificate cert) {
- // manually split DN into OID components
- // this is instead of parsing with LdapName which:
- // (1) I don't trust the order of values
- // (2) it filters out values like EMAILADDRESS
- String dn = cert.getSubjectDN().getName();
Map<String, String> oids = new HashMap<String, String>();
- for (String kvp : dn.split(",")) {
- String [] val = kvp.trim().split("=");
- String oid = val[0].toUpperCase().trim();
- String data = val[1].trim();
- oids.put(oid, data);
+ try {
+ String dn = cert.getSubjectDN().getName();
+ LdapName ldapName = new LdapName(dn);
+ for (int i = 0; i < ldapName.size(); i++) {
+ String [] val = ldapName.get(i).trim().split("=", 2);
+ String oid = val[0].toUpperCase().trim();
+ String data = val[1].trim();
+ oids.put(oid, data);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
diff --git a/src/main/java/com/gitblit/utils/cli/CmdLineParser.java b/src/main/java/com/gitblit/utils/cli/CmdLineParser.java index e698eb54..09ae8366 100644 --- a/src/main/java/com/gitblit/utils/cli/CmdLineParser.java +++ b/src/main/java/com/gitblit/utils/cli/CmdLineParser.java @@ -429,5 +429,15 @@ public class CmdLineParser { public boolean isMultiValued() { return false; } + + @Override + public boolean help() { + return true; + } + + @Override + public String[] forbids() { + return new String [0]; + } } } diff --git a/src/main/java/com/gitblit/wicket/FilestoreUI.java b/src/main/java/com/gitblit/wicket/FilestoreUI.java new file mode 100644 index 00000000..8837ba18 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/FilestoreUI.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket; + +import org.apache.wicket.markup.html.basic.Label; + +import com.gitblit.models.FilestoreModel; +import com.gitblit.models.FilestoreModel.Status; + +/** + * Common filestore ui methods and classes. + * + * @author Paul Martin + * + */ +public class FilestoreUI { + + public static Label getStatusIcon(String wicketId, FilestoreModel item) { + return getStatusIcon(wicketId, item.getStatus()); + } + + public static Label getStatusIcon(String wicketId, Status status) { + Label label = new Label(wicketId); + + switch (status) { + case Upload_Pending: + WicketUtils.setCssClass(label, "fa fa-spinner fa-fw file-negative"); + break; + case Upload_In_Progress: + WicketUtils.setCssClass(label, "fa fa-spinner fa-spin fa-fw file-positive"); + break; + case Available: + WicketUtils.setCssClass(label, "fa fa-check fa-fw file-positive"); + break; + case Deleted: + WicketUtils.setCssClass(label, "fa fa-ban fa-fw file-negative"); + break; + case Unavailable: + WicketUtils.setCssClass(label, "fa fa-times fa-fw file-negative"); + break; + default: + WicketUtils.setCssClass(label, "fa fa-exclamation-triangle fa-fw file-negative"); + } + WicketUtils.setHtmlTooltip(label, status.toString()); + + return label; + } + +} diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java index d19630c2..296c2544 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java @@ -37,12 +37,14 @@ import com.gitblit.Keys; import com.gitblit.extensions.GitblitWicketPlugin; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IFilestoreManager; import com.gitblit.manager.IGitblit; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; import com.gitblit.manager.IUserManager; import com.gitblit.tickets.ITicketService; import com.gitblit.transport.ssh.IPublicKeyManager; @@ -62,6 +64,7 @@ import com.gitblit.wicket.pages.EditRepositoryPage; import com.gitblit.wicket.pages.EditTicketPage; import com.gitblit.wicket.pages.ExportTicketPage; import com.gitblit.wicket.pages.FederationRegistrationPage; +import com.gitblit.wicket.pages.FilestorePage; import com.gitblit.wicket.pages.ForkPage; import com.gitblit.wicket.pages.ForksPage; import com.gitblit.wicket.pages.GitSearchPage; @@ -90,7 +93,11 @@ import com.gitblit.wicket.pages.TicketsPage; import com.gitblit.wicket.pages.TreePage; import com.gitblit.wicket.pages.UserPage; import com.gitblit.wicket.pages.UsersPage; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +@Singleton public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { private final Class<? extends WebPage> homePageClass = MyDashboardPage.class; @@ -99,6 +106,10 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { private final Map<String, CacheControl> cacheablePages = new HashMap<String, CacheControl>(); + private final Provider<IPublicKeyManager> publicKeyManagerProvider; + + private final Provider<ITicketService> ticketServiceProvider; + private final IStoredSettings settings; private final XssFilter xssFilter; @@ -113,8 +124,6 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { private final IAuthenticationManager authenticationManager; - private final IPublicKeyManager publicKeyManager; - private final IRepositoryManager repositoryManager; private final IProjectManager projectManager; @@ -123,19 +132,29 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { private final IGitblit gitblit; + private final IServicesManager services; + + private final IFilestoreManager filestoreManager; + + @Inject public GitBlitWebApp( + Provider<IPublicKeyManager> publicKeyManagerProvider, + Provider<ITicketService> ticketServiceProvider, IRuntimeManager runtimeManager, IPluginManager pluginManager, INotificationManager notificationManager, IUserManager userManager, IAuthenticationManager authenticationManager, - IPublicKeyManager publicKeyManager, IRepositoryManager repositoryManager, IProjectManager projectManager, IFederationManager federationManager, - IGitblit gitblit) { + IGitblit gitblit, + IServicesManager services, + IFilestoreManager filestoreManager) { super(); + this.publicKeyManagerProvider = publicKeyManagerProvider; + this.ticketServiceProvider = ticketServiceProvider; this.settings = runtimeManager.getSettings(); this.xssFilter = runtimeManager.getXssFilter(); this.runtimeManager = runtimeManager; @@ -143,11 +162,12 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { this.notificationManager = notificationManager; this.userManager = userManager; this.authenticationManager = authenticationManager; - this.publicKeyManager = publicKeyManager; this.repositoryManager = repositoryManager; this.projectManager = projectManager; this.federationManager = federationManager; this.gitblit = gitblit; + this.services = services; + this.filestoreManager = filestoreManager; } @Override @@ -224,6 +244,9 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { mount("/user", UserPage.class, "user"); mount("/forks", ForksPage.class, "r"); mount("/fork", ForkPage.class, "r"); + + // filestore URL + mount("/filestore", FilestorePage.class); // allow started Wicket plugins to initialize for (PluginWrapper pluginWrapper : pluginManager.getPlugins()) { @@ -392,7 +415,7 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { */ @Override public IPublicKeyManager keys() { - return publicKeyManager; + return publicKeyManagerProvider.get(); } /* (non-Javadoc) @@ -428,11 +451,19 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { } /* (non-Javadoc) + * @see com.gitblit.wicket.Webapp#services() + */ + @Override + public IServicesManager services() { + return services; + } + + /* (non-Javadoc) * @see com.gitblit.wicket.Webapp#tickets() */ @Override public ITicketService tickets() { - return gitblit.getTicketService(); + return ticketServiceProvider.get(); } /* (non-Javadoc) @@ -454,4 +485,9 @@ public class GitBlitWebApp extends WebApplication implements GitblitWicketApp { public static GitBlitWebApp get() { return (GitBlitWebApp) WebApplication.get(); } + + @Override + public IFilestoreManager filestore() { + return filestoreManager; + } } diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties index eb92e2d2..36c416e7 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties @@ -561,6 +561,7 @@ gb.bugTickets = bugs gb.enhancementTickets = enhancements gb.taskTickets = tasks gb.questionTickets = questions +gb.maintenanceTickets = maintenance gb.requestTickets = enhancements & tasks gb.yourCreatedTickets = created by you gb.yourWatchedTickets = watched by you @@ -741,4 +742,32 @@ gb.sshKeyCommentDescription = Enter an optional comment. If blank, the comment w gb.permission = Permission gb.sshKeyPermissionDescription = Specify the access permission for the SSH key gb.transportPreference = Transport Preference -gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
\ No newline at end of file +gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning +gb.priority = priority +gb.severity = severity +gb.sortHighestPriority = highest priority +gb.sortLowestPriority = lowest priority +gb.sortHighestSeverity = highest severity +gb.sortLowestSeverity = lowest severity +gb.missingIntegrationBranchMore = The target integration branch does not exist in the repository! +gb.diffDeletedFileSkipped = (deleted) +gb.diffFileDiffTooLarge = Diff too large +gb.diffNewFile = New file +gb.diffDeletedFile = File was deleted +gb.diffRenamedFile = File was renamed from {0} +gb.diffCopiedFile = File was copied from {0} +gb.diffTruncated = Diff truncated after the above file +gb.opacityAdjust = Adjust opacity +gb.blinkComparator = Blink comparator +gb.imgdiffSubtract = Subtract (black = identical) +gb.deleteRepositoryHeader = Delete Repository +gb.deleteRepositoryDescription = Deleted repositories will be unrecoverable. +gb.show_whitespace = show whitespace +gb.ignore_whitespace = ignore whitespace +gb.allRepositories = All Repositories +gb.oid = object id +gb.filestore = filestore +gb.filestoreStats = Filestore contains {0} files with a total size of {1}. ({2} remaining) +gb.statusChangedOn = status changed on +gb.statusChangedBy = status changed by +gb.filestoreHelp = How to use the Filestore?
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties index 3ec330b7..eca3fd2a 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties @@ -743,3 +743,13 @@ gb.permission = Berechtigung gb.sshKeyPermissionDescription = Geben Sie die Zugriffberechtigung f\u00fcr den SSH Key an gb.transportPreference = \u00dcbertragungseinstellungen gb.transportPreferenceDescription = Geben Sie die \u00dcbertragungsart an, die Sie f\u00fcr das Klonen bevorzugen +gb.diffDeletedFileSkipped = (gel\u00f6scht) +gb.diffFileDiffTooLarge = Zu viele \u00c4nderungen; Diff wird nicht angezeigt +gb.diffNewFile = Neue Datei +gb.diffDeletedFile = Datei wurde gel\u00f6scht +gb.diffRenamedFile = Datei umbenannt von {0} +gb.diffCopiedFile = Datei kopiert von {0} +gb.diffTruncated = Diff nach obiger Datei abgeschnitten +gb.opacityAdjust = Transparenz +gb.blinkComparator = Blinkkomparator +gb.imgdiffSubtract = Pixeldifferenz (schwarz = identisch)
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties index b888beee..d479b3d6 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties @@ -672,3 +672,13 @@ gb.ticketIsClosed = Ce ticket est clos. gb.mergeToDescription = branche d'int\u00e9gration par d\u00e9faut pour fusionner les correctifs li\u00e9s aux tickets gb.myTickets = mes tickets gb.yourAssignedTickets = dont vous \u00eates responsable +gb.diffDeletedFileSkipped = (effac\u00e9) +gb.diffFileDiffTooLarge = Trop de diff\u00e9rences, affichage supprim\u00e9e +gb.diffNewFile = Nouveau fichier +gb.diffDeletedFile = Fichier a \u00e9t\u00e9 effac\u00e9 +gb.diffRenamedFile = Fichier renomm\u00e9 de {0} +gb.diffCopiedFile = Fichier copi\u00e9 de {0} +gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus +gb.opacityAdjust = ajuster l'opacit\u00e9 +gb.blinkComparator = Comparateur \u00e0 clignotement +gb.imgdiffSubtract = Diff\u00e9rence (noir = identique)
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties new file mode 100644 index 00000000..16a9c864 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties @@ -0,0 +1,772 @@ +#! +#! created/edited by Popeye version 0.54 (popeye.sourceforge.net) +#! encoding:ISO-8859-1 +gb.about = \u95dc\u65bc +gb.acceptNewPatchsets = \u5141\u8a31\u88dc\u4e01 +gb.acceptNewPatchsetsDescription = \u63a5\u53d7\u5230\u7248\u672c\u5009\u9032\u884c\u4fee\u88dc\u52d5\u4f5c +gb.acceptNewTickets = \u5141\u8a31\u5efa\u7acb\u4efb\u52d9\u55ae +gb.acceptNewTicketsDescription = \u5141\u8a31\u65b0\u589e"\u81ed\u87f2","\u512a\u5316","\u4efb\u52d9"\u5404\u985e\u578b\u4efb\u52d9\u55ae +gb.accessDenied = \u62d2\u7d55\u5b58\u53d6 +gb.accessLevel = \u5b58\u53d6\u7b49\u7d1a +gb.accessPermissions = \u5b58\u53d6\u6b0a\u9650 +gb.accessPermissionsDescription = restrict access by users and teams +gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories +gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories +gb.accessPolicy = \u5b58\u53d6\u653f\u7b56 +gb.accessPolicyDescription = \u9078\u64c7\u7528\u4f86\u63a7\u5236\u6587\u4ef6\u5eab\u7684\u5b58\u53d6\u653f\u7b56\u4ee5\u53ca\u6b0a\u9650\u8a2d\u5b9a +gb.accessRestriction = \u9650\u5236\u5b58\u53d6 +gb.accountPreferences = \u5e33\u865f\u8a2d\u5b9a +gb.accountPreferencesDescription = \u8a2d\u5b9a\u5e33\u865f\u9810\u8a2d\u503c +gb.action = \u52d5\u4f5c +gb.active = \u6d3b\u52d5 +gb.activeAuthors = \u6d3b\u8e8d\u7528\u6236 +gb.activeRepositories = \u6d3b\u8e8d\u7248\u672c\u5eab +gb.activity = \u6d3b\u52d5 +gb.add = \u65b0\u589e +gb.addComment = \u65b0\u589e\u8a3b\u89e3 +gb.addedNCommits = {0}\u500b\u6a94\u6848\u63d0\u4ea4\u5b8c\u7562 +gb.addedOneCommit = \u63d0\u4ea41\u500b\u6a94\u6848 +gb.addition = addition +gb.addSshKey = \u65b0\u589e SSH Key +gb.administration = \u7ba1\u7406\u6b0a\u9650 +gb.administrator = \u7ba1\u7406\u54e1 +gb.administratorPermission = Gitblit \u7ba1\u7406\u54e1 +gb.affiliationChanged = affiliation changed +gb.age = \u6642\u9593 +gb.all = \u5168\u90e8 +gb.allBranches = \u6240\u6709\u5206\u652f +gb.allowAuthenticatedDescription = \u6279\u51c6 RW+ \u6b0a\u9650\u7d66\u4e88\u5c08\u6848\u6210\u54e1 +gb.allowForks = \u5141\u8a31\u5efa\u7acb\u5206\u652f(forks) +gb.allowForksDescription = \u5141\u8a31\u5df2\u6388\u6b0a\u7684\u4f7f\u7528\u8005\u5f9e\u6587\u4ef6\u5eab\u5efa\u7acb\u5206\u652f(fork) +gb.allowNamedDescription = grant fine-grained permissions to named users or teams +gb.allProjects = \u5168\u90e8\u7fa4\u7d44 +gb.allTags = \u6240\u6709\u6a19\u7c64 +gb.anonymousCanNotPropose = \u533f\u540d\u8005\u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01 +gb.anonymousPolicy = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push +gb.anonymousPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u6587\u4ef6\u5230\u6587\u4ef6\u5eab +gb.anonymousUser= \u533f\u540d\u72c0\u614b +gb.any = \u4efb\u4f55 +gb.approve = \u901a\u904e +gb.at = at +gb.attributes = \u5c6c\u6027 +gb.authenticatedPushPolicy = Restrict Push (Authenticated) +gb.authenticatedPushPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u6aa2\u8996\u8207\u8907\u88fd(clone).\u6240\u6709\u6587\u4ef6\u5eab\u6210\u54e1\u7686\u6709RW+\u8207\u63a8\u9001(push)\u529f\u80fd. +gb.author = \u4f5c\u8005 +gb.authored = \u5df2\u6388\u6b0a +gb.authorizationControl = \u6388\u6b0a\u7ba1\u63a7 +gb.available = \u53ef\u7528 +gb.blame = \u7a76\u67e5 +gb.blinkComparator = Blink comparator +gb.blob = \u5340\u584a +gb.body = body +gb.bootDate = \u555f\u52d5\u65e5 +gb.branch = \u5206\u652f +gb.branches = \u5206\u652f +gb.branchStats = \u9019\u500b\u5206\u652f{2}\u6709{0}\u500b\u63d0\u4ea4\u4ee5\u53ca{1}\u500b\u6a19\u7c64 +gb.browse = \u700f\u89bd +gb.bugTickets = \u81ed\u87f2 +gb.busyCollectingGarbage = \u62b1\u6b49,Gitblit\u6b63\u5728\u56de\u6536\u7cfb\u7d71\u8cc7\u6e90\u4e2d:{0} +gb.byNAuthors = \u7d93\u7531{0}\u500b\u4f5c\u8005 +gb.byOneAuthor = \u7d93\u7531{0} +gb.caCompromise = CA compromise +gb.canAdmin = \u53ef\u7ba1\u7406 +gb.canAdminDescription = \u53ef\u7ba1\u7406Gitblit\u4f3a\u670d\u5668 +gb.cancel = \u53d6\u6d88 +gb.canCreate = \u53ef\u5efa\u7acb +gb.canCreateDescription = \u80fd\u5920\u5efa\u7acb\u79c1\u4eba\u6587\u4ef6\u5eab +gb.canFork = \u53ef\u5efa\u7acb\u5206\u652f(fork) +gb.canForkDescription = \u53ef\u4ee5\u5efa\u7acb\u6587\u4ef6\u5eab\u5206\u652f(fork),\u4e26\u4e14\u8907\u88fd\u5230\u79c1\u4eba\u6587\u4ef6\u5eab\u4e2d +gb.canNotLoadRepository = \u7121\u6cd5\u8f09\u5165\u7248\u672c\u5eab +gb.canNotProposePatchset = \u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01 +gb.certificate = \u8b49\u66f8 +gb.certificateRevoked = \u8b49\u66f8{0,number,0} \u5df2\u7d93\u88ab\u53d6\u6d88 +gb.certificates = \u8b49\u66f8 +gb.cessationOfOperation = cessation of operation +gb.changedFiles = \u5df2\u8b8a\u66f4\u904e\u7684\u6a94\u6848 +gb.changedStatus = changed the status +gb.changePassword = \u4fee\u6539\u5bc6\u78bc +gb.checkout = \u6aa2\u51fa(checkout) +gb.checkoutStep1 = Fetch the current patchset \u2014 run this from your project directory +gb.checkoutStep2 = \u5c07\u8a72\u88dc\u4e01\u8f49\u51fa\u5230\u65b0\u7684\u5206\u652f\u7136\u5f8c\u7528\u4f86\u6aa2\u8996 +gb.checkoutViaCommandLine = \u4f7f\u7528\u6307\u4ee4Checkout +gb.checkoutViaCommandLineNote = \u4f60\u53ef\u4ee5\u5f9e\u4f60\u6587\u4ef6\u5eab\u4e2dcheckout\u4e00\u4efd,\u7136\u5f8c\u9032\u884c\u6e2c\u8a66 +gb.clearCache = \u6e05\u9664\u5feb\u53d6 +gb.clientCertificateBundleSent = {0}\u7684\u7528\u6236\u8b49\u66f8\u5df2\u5bc4\u767c +gb.clientCertificateGenerated = \u6210\u529f\u7522\u751f{0}\u7684\u65b0\u8b49\u66f8 +gb.clone = \u8907\u88fd(clone) +gb.clonePermission = {0} \u8907\u88fd(clone) +gb.clonePolicy = Restrict Clone & Push +gb.clonePolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u770b\u6587\u4ef6\u5eab. \u4f46\u4f60\u80fd\u5920\u8907\u88fd(clone)\u8207\u63a8\u9001(push) +gb.cloneRestricted = authenticated clone & push +gb.closeBrowser = \u8acb\u95dc\u9589\u700f\u89bd\u5668\u7d50\u675f\u6b64\u767b\u5165\u968e\u6bb5 +gb.closed = \u95dc\u9589 +gb.closedMilestones = \u5df2\u95dc\u9589\u7684\u91cc\u7a0b\u7891(milestones) +gb.combinedMd5Rename = Gitblit\u4f7f\u7528md5\u65b9\u5f0f\u5c07\u5bc6\u78bc\u7de8\u78bc(\u7121\u6cd5\u9084\u539f).\u4f60\u5fc5\u9808\u8f38\u5165\u65b0\u5bc6\u78bc. +gb.comment = \u8a3b\u89e3 +gb.commented = \u5df2\u8a3b\u89e3 +gb.comments = \u8a3b\u89e3 +gb.commit = \u63d0\u4ea4 +gb.commitActivityAuthors = \u63d0\u4ea4\u6d3b\u8e8d\u7387(\u4f7f\u7528\u8005) +gb.commitActivityDOW = \u6bcf(\u65e5)\u9031\u63d0\u4ea4 +gb.commitActivityTrend = \u63d0\u4ea4\u8da8\u52e2\u5716 +gb.commitdiff = \u63d0\u4ea4\u5dee\u7570 +gb.commitIsNull = \u63d0\u4ea4\u5167\u5bb9\u662f\u7a7a\u7684 +gb.commitMessageRenderer = \u63d0\u4ea4\u8a0a\u606f\u5448\u73fe\u65b9\u5f0f +gb.commitMessageRendererDescription = \u63d0\u4ea4\u8a0a\u606f\u53ef\u4ee5\u4f7f\u7528\u6587\u5b57\u6216\u662f\u6a19\u8a18\u8a9e\u8a00(markup)\u5448\u73fe +gb.commits = \u63d0\u4ea4 +gb.commitsInPatchsetN = \u88dc\u4e01 {0} \u7684\u63d0\u4ea4 +gb.commitsTo = {0} commits to +gb.committed = \u5df2\u63d0\u4ea4 +gb.committer = \u78ba\u8a8d\u63d0\u4ea4\u8005 +gb.compare = \u6bd4\u5c0d +gb.compareToMergeBase = \u6bd4\u5c0d\u5f8c,\u5408\u4f75\u5230\u4e3b\u8981\u5de5\u4f5c\u5340 +gb.compareToN = \u8207{0}\u9032\u884c\u6bd4\u5c0d +gb.completeGravatarProfile = \u5b8c\u6210Gravator.com\u4e0a\u7684\u57fa\u672c\u8cc7\u6599\u8a2d\u5b9a +gb.confirmPassword = \u78ba\u8a8d\u5bc6\u78bc +gb.content = \u5167\u5bb9 +gb.copyToClipboard = \u8907\u88fd\u5230\u526a\u8cbc\u677f +gb.couldNotCreateFederationProposal = \u7121\u6cd5\u5efa\u7acb\u4e32\u9023\u7684\u5408\u4f5c\u63d0\u6848 +gb.couldNotFindFederationProposal = \u641c\u5c0b\u4e0d\u5230\u8981\u6c42\u4e32\u9023\u7684\u63d0\u6848 +gb.couldNotFindFederationRegistration = \u627e\u4e0d\u5230\u4e32\u9023\u8a3b\u518a\u55ae +gb.couldNotFindTag = \u627e\u4e0d\u5230\u6a19\u7c64{0} +gb.countryCode = \u570b\u5bb6\u4ee3\u78bc +gb.create = \u5efa\u7acb +gb.createdBy = created by +gb.createdNewBranch = \u5efa\u7acb\u65b0\u5206\u652f +gb.createdNewPullRequest = created pull request +gb.createdNewTag = \u5efa\u7acb\u65b0\u6a19\u7c64 +gb.createdThisTicket = \u5df2\u958b\u7acb\u7684\u4efb\u52d9\u55ae +gb.createFirstTicket = \u6309\u6b64\u9996\u767c\u4efb\u52d9\u55ae +gb.createPermission = {0} (push, ref creation) +gb.createReadme = \u5efa\u7acbREADME\u6a94\u6848 +gb.customFields = custom fields +gb.customFieldsDescription = custom fields available to Groovy hooks +gb.dailyActivity = \u6bcf\u65e5\u6d3b\u52d5 +gb.dashboard = \u5100\u8868\u677f +gb.date = \u65e5\u671f +gb.default = \u9810\u8a2d +gb.delete = \u522a\u9664 +gb.deletedBranch = deleted branch +gb.deletedTag = \u522a\u9664\u6a19\u7c64 +gb.deleteMilestone = \u522a\u9664\u91cc\u7a0b\u7891"{0}"? +gb.deletePermission = {0} (push, ref creation+deletion) +gb.deleteRepository = \u522a\u9664\u7248\u672c\u5eab"{0}"? +gb.deleteRepositoryDescription = \u7248\u672c\u5eab\u522a\u9664\u5c07\u7121\u6cd5\u9084\u539f +gb.deleteRepositoryHeader = \u522a\u9664\u7248\u672c\u5eab +gb.deleteUser = \u522a\u9664\u4f7f\u7528\u8005"{0}"? +gb.deletion = \u522a\u9664 +gb.description = \u6982\u8ff0 +gb.destinationUrl = \u50b3\u9001 +gb.destinationUrlDescription = \u50b3\u9001Gitblit\u9023\u7d50\u5230\u4f60\u7684\u5c08\u6848(proposal) +gb.diff = \u5dee\u7570 +gb.diffCopiedFile = File was copied from {0} +gb.diffDeletedFile = \u6a94\u6848\u5df2\u522a\u9664 +gb.diffDeletedFileSkipped = (\u522a\u9664) +gb.diffFileDiffTooLarge = \u6a94\u6848\u592a\u5927 +gb.diffNewFile = \u6bd4\u5c0d\u65b0\u6a94\u6848 +gb.diffRenamedFile = File was renamed from {0} +gb.diffStat = \u65b0\u589e{0}\u5217\u8207\u522a\u9664{1}\u5217 +gb.difftocurrent = \u6bd4\u5c0d\u5dee\u7570 +gb.diffTruncated = Diff truncated after the above file +gb.disableUser = \u505c\u7528\u5e33\u6236 +gb.disableUserDescription = \u8a72\u5e33\u6236\u7121\u6cd5\u4f7f\u7528 +gb.discussion = \u8a0e\u8ad6 +gb.displayName = \u986f\u793a\u7684\u540d\u7a31 +gb.displayNameDescription = \u5e0c\u671b\u986f\u793a\u7684\u540d\u7a31 +gb.docs = \u6a94\u6848\u5340 +gb.docsWelcome1 = \u4f60\u53ef\u4ee5\u4f7f\u7528\u6a94\u6848\u5340\u5efa\u7acb\u6587\u4ef6\u5eab\u7684\u6559\u5b78\u6a94\u6848 +gb.docsWelcome2 = \u63d0\u4ea4README.md \u6216 HOME.md\u5f8c,\u518d\u958b\u59cb\u65b0\u7684\u6587\u4ef6\u5eab +gb.doesNotExistInTree = {0}\u4e26\u6c92\u6709\u5728\u76ee\u9304{1}\u88e1\u9762 +gb.download = \u4e0b\u8f09 +gb.downloading = \u4e0b\u8f09ing +gb.due = due +gb.duration = \u9031\u671f +gb.duration.days = {0}\u5929 +gb.duration.months = {0}\u6708 +gb.duration.oneDay = 1\u5929 +gb.duration.oneMonth = 1\u6708 +gb.duration.oneYear = 1\u5e74 +gb.duration.years = {0}\u5e74 +gb.edit = \u7de8\u8f2f +gb.editMilestone = \u4fee\u6539milestone +gb.editTicket = \u4fee\u6539\u4efb\u52d9\u55ae +gb.editUsers = \u4fee\u6539\u5e33\u865f +gb.effective = \u6240\u6709\u6b0a\u9650 +gb.emailAddress = \u96fb\u5b50\u90f5\u4ef6 +gb.emailAddressDescription = \u7528\u4f86\u63a5\u6536\u901a\u77e5\u7684\u4e3b\u8981\u96fb\u5b50\u90f5\u4ef6 +gb.emailCertificateBundle = \u5bc4\u767c\u7528\u6236\u7aef\u8b49\u66f8 +gb.emailMeOnMyTicketChanges = \u6211\u7684\u4efb\u52d9\u55ae\u82e5\u6709\u8b8a\u66f4,\u8acb800\u91cc\u52a0\u6025(email)\u901a\u77e5\u6211 +gb.emailMeOnMyTicketChangesDescription = \u6211\u8655\u7406\u904e\u7684\u4efb\u52d9\u55ae\u8acbemail\u901a\u77e5\u6211 +gb.empty = \u7a7a\u7684 +gb.emptyRepository = \u7a7a\u7684\u7248\u672c\u5eab +gb.enableDocs = \u555f\u7528\u6a94\u6848\u5340 +gb.enableIncrementalPushTags = \u555f\u7528\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u529f\u80fd +gb.enableTickets = \u555f\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71 +gb.enhancementTickets = \u512a\u5316 +gb.enterKeystorePassword = \u8acb\u8f38\u5165Gitblit\u7684keystore\u5c08\u7528\u5bc6\u78bc +gb.error = \u932f\u8aa4 +gb.errorAdministrationDisabled = \u7ba1\u7406\u6b0a\u9650\u5df2\u53d6\u6d88 +gb.errorAdminLoginRequired = \u767b\u5165\u9700\u6709\u7ba1\u7406\u6b0a\u9650 +gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u8005\u80fd\u5efa\u7acb\u7248\u672c\u5eab +gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u8005\u8207\u7248\u672c\u5eab\u64c1\u6709\u8005\u80fd\u4fee\u6539\u7248\u672c\u5eab\u5c6c\u6027 +gb.excludeFromActivity = exclude from activity page +gb.excludeFromFederation = \u6392\u9664\u4e32\u9023 +gb.excludeFromFederationDescription = \u963b\u64cb\u5df2\u4e32\u9023\u7684Gitblit\u4f3a\u670d\u5668 +gb.excludePermission = {0} (\u6392\u9664) +gb.exclusions = \u6392\u9664 +gb.expired = \u904e\u671f +gb.expires = \u5230\u671f +gb.expiring = \u5c07\u8981\u904e\u671f +gb.export = \u532f\u51fa +gb.extensions = \u64f4\u5145 +gb.externalPermissions = {0} access permissions are externally maintained +gb.failedToFindAccount = \u7121\u6cd5\u641c\u5c0b\u5230\u5e33\u865f"{0}" +gb.failedToFindCommit = Failed to find commit "{0}" in {1}\! +gb.failedToFindGravatarProfile = \u7121\u6cd5\u627e\u5230\u5e33\u865f{0}\u7684Gravator\u8cc7\u6599 +gb.failedtoRead = \u8b80\u53d6\u5931\u6557 +gb.failedToReadMessage = Failed to read default message from {0}\! +gb.failedToSendProposal = \u63d0\u6848\u767c\u9001\u5931\u6557\! +gb.failedToUpdateUser = \u7121\u6cd5\u66f4\u65b0\u4f7f\u7528\u8005\u5e33\u865f +gb.federatedRepositoryDefinitions = \u7248\u672c\u5eab\u5b9a\u7fa9 +gb.federatedSettingDefinitions = setting definitions +gb.federatedUserDefinitions = user definitions +gb.federateOrigin = federate the origin +gb.federateThis = \u8207\u672c\u6587\u4ef6\u5eab\u4e32\u9023 +gb.federation = \u4e32\u9023 +gb.federationRegistration = federation registration +gb.federationRepositoryDescription = \u8207\u5176\u4ed6gitblit\u4f3a\u670d\u5668\u5206\u4eab\u4e00\u8d77\u4f7f\u7528\u9019\u500b\u7248\u672c\u5eab +gb.federationResults = federation pull results +gb.federationSets = \u4e32\u9023\u7d44\u5408 +gb.federationSetsDescription = \u6b64\u6587\u4ef6\u5eab\u5c07\u5305\u542b\u65bc\u6307\u5b9a\u7684\u4e32\u9023\u7fa4\u7d44(federation sets) +gb.federationStrategy = \u4e32\u9023\u7b56\u7565 +gb.federationStrategyDescription = \u63a7\u5236\u5982\u4f55\u5c07\u6587\u4ef6\u5eab\u8207\u5176\u4ed6Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u4e32\u9023 +gb.feed = \u8cc7\u6599\u8a02\u95b1 +gb.filesAdded = \u65b0\u589e{0}\u500b\u6a94\u6848 +gb.filesCopied = \u8907\u88fd{0}\u500b\u6a94\u6848 +gb.filesDeleted = \u522a\u9664{0}\u500b\u6a94\u6848 +gb.filesModified = \u4fee\u6539{0}\u500b\u6a94\u6848 +gb.filesRenamed = \u4fee\u6539{0}\u500b\u6a94\u6848\u540d\u7a31 +gb.filter = \u689d\u4ef6\u904e\u6ffe +gb.filters = \u67e5\u8a62\u689d\u4ef6 +gb.findSomeRepositories = \u641c\u5c0b\u6587\u4ef6\u5eab +gb.folder = \u76ee\u9304 +gb.fork = \u5efa\u7acb\u5206\u652f(fork) +gb.forkedFrom = forked from +gb.forkInProgress = fork in progress +gb.forkNotAuthorized = \u5f88\u62b1\u6b49, \u4f60\u7121\u5efa\u7acb\u6587\u4ef6\u5eab{0}\u5206\u652f(fork)\u7684\u6b0a\u9650 +gb.forkRepository = \u7248\u672c\u5eab{0}\u5efa\u7acb\u5206\u652f(fork)? +gb.forks = \u5206\u652f(forks) +gb.forksProhibited = \u7981\u6b62\u5efa\u7acb\u5206\u652f(forks) +gb.forksProhibitedWarning = \u672c\u6587\u4ef6\u5eab\u7981\u6b62\u5206\u652f(fork) +gb.free = \u91cb\u653e +gb.frequency = \u983b\u7387 +gb.from = from +gb.garbageCollection = \u56de\u6536\u7cfb\u7d71\u8cc7\u6e90 +gb.garbageCollectionDescription = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u529f\u80fd\u5c07\u6703\u6574\u9813\u9b06\u6563\u7528\u6236\u7aef\u63a8\u9001(push)\u7684\u7269\u4ef6, \u4e5f\u6703\u79fb\u9664\u6587\u4ef6\u5eab\u4e0a\u7121\u7528\u7684\u7269\u4ef6 +gb.gc = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u5668 +gb.gcPeriod = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u968e\u6bb5 +gb.gcPeriodDescription = \u56de\u6536\u9031\u671f +gb.gcThreshold = GC \u57fa\u6578(threshold) +gb.gcThresholdDescription = \u89f8\u767c\u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u7684\u6700\u5c0f\u7269\u4ef6\u5bb9\u91cf +gb.general = \u4e00\u822c +gb.generalDescription = \u4e00\u822c\u8a2d\u5b9a +gb.hasNotReviewed = \u5c1a\u672a\u6aa2\u6838\u904e +gb.head = HEAD +gb.headRef = \u9810\u8a2d\u5206\u652f(HEAD) +gb.headRefDescription = \u9810\u8a2d\u5206\u652f\u5c07\u6703\u8907\u88fd\u4ee5\u53ca\u986f\u793a\u5230\u532f\u7e3d\u9801\u9762 +gb.heapAllocated = \u5df2\u4f7f\u7528\u5806\u7a4d(Heap) +gb.heapMaximum = \u6700\u5927\u5806\u7a4d(heap) +gb.heapUsed = \u5df2\u4f7f\u7528\u7684\u5806\u7a4d(heap) +gb.history = \u6b77\u7a0b +gb.home = \u9996\u9801 +gb.hookScripts = hook\u7684\u8173\u672c +gb.hookScriptsDescription = \u7576\u63a8\u9001(push)\u81f3\u6b64Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u6642, \u57f7\u884cGroovy\u8173\u672c +gb.hostname = \u4e3b\u6a5f\u540d\u7a31 +gb.hostnameRequired = \u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31 +gb.ignore_whitespace =\u5ffd\u7565\u7a7a\u767d +gb.illegalCharacterRepositoryName = \u7248\u672c\u5eab\u540d\u7a31\u6709\u4e0d\u5408\u6cd5\u7684\u5b57\u5143"{0}" +gb.illegalLeadingSlash = \u7981\u6b62\u6839\u76ee\u9304(/) +gb.illegalPersonalRepositoryLocation = \u4f60\u79c1\u4eba\u7248\u672c\u5eab\u5fc5\u9808\u653e\u5728"{0}" +gb.illegalRelativeSlash = \u7981\u6b62\u76f8\u5c0d\u76ee\u9304(../) +gb.imgdiffSubtract = Subtract (black = identical) +gb.in = in +gb.inclusions = inclusions +gb.incrementalPushTagMessage = \u7576[{0}]\u5206\u652f\u63a8\u9001\u5f8c,\u81ea\u52d5\u7d66\u4e88\u6a19\u7c64\u865f. +gb.indexedBranches = \u5206\u652f\u7d22\u5f15 +gb.indexedBranchesDescription = \u9078\u5b9a\u6b32\u57f7\u884cLucene\u7d22\u5f15\u529f\u80fd\u7684\u5206\u652f +gb.inherited = \u7e7c\u627f +gb.initialCommit = \u521d\u6b21\u63d0\u4ea4 +gb.initialCommitDescription = \u4ee5\u4e0b\u6b65\u9a5f\u5c07\u6703\u8b93\u4f60\u99ac\u4e0a\u57f7\u884c<code>git clone</code>.\u5982\u679c\u4f60\u672c\u6a5f\u5df2\u6709\u6b64\u6587\u4ef6\u5eab\u4e14\u57f7\u884c\u904e<code>git init</code>,\u8acb\u8df3\u904e\u6b64\u6b65\u9a5f. +gb.initWithGitignore = \u5305\u542b .gitignore \u6a94\u6848 +gb.initWithGitignoreDescription = \u65b0\u589e\u4e00\u500b\u8a2d\u5b9a\u6a94\u7528\u4f86\u6307\u5b9a\u54ea\u4e9b\u6a94\u6848\u6216\u76ee\u9304\u9700\u8981\u5ffd\u7565 +gb.initWithReadme = \u5305\u542bREADME\u6587\u4ef6 +gb.initWithReadmeDescription = \u6587\u4ef6\u5eab\u5c07\u7522\u751f\u7c21\u55aeREADME\u6587\u4ef6 +gb.invalidExpirationDate = \u4e0d\u6b63\u78ba\u7684\u5230\u671f\u65e5 +gb.invalidUsernameOrPassword = \u932f\u8aa4\u7684\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc! +gb.isFederated = \u5df2\u7d93\u4e32\u9023 +gb.isFork = \u662f\u5206\u652f\u985e\u578b(fork) +gb.isFrozen = \u51cd\u7d50\u63a5\u6536 +gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001(push) +gb.isMirror = \u8a72\u6587\u4ef6\u5eab\u70ba\u93e1\u50cf(mirror) +gb.isNotValidFile = \u4e0d\u662f\u6b63\u5e38\u6a94\u6848 +gb.isSparkleshared = \u8a72\u6587\u4ef6\u5eab\u5df2\u70baSparkleshared (http://sparkleshare.org) +gb.issued = \u767c\u51fa +gb.issuer = issuer +gb.jceWarning = Your Java Runtime Environment does not have the "JCE Unlimited Strength Jurisdiction Policy" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files. +gb.key = \u91d1\u9470 +gb.keyCompromise = \u91d1\u9470\u5bc6\u78bc\u5916\u6d29 +gb.labels = \u6a19\u8a18 +gb.languagePreference = \u5e38\u7528\u8a9e\u8a00 +gb.languagePreferenceDescription = \u9078\u64c7\u4f60\u60f3\u8981\u7684Gitblit\u7ffb\u8b6f +gb.lastChange = \u6700\u8fd1\u4fee\u6539 +gb.lastLogin = \u6700\u8fd1\u767b\u5165 +gb.lastNDays = \u6700\u8fd1{0}\u5929 +gb.lastPull = \u4e0a\u6b21\u4e0b\u8f09(pull) +gb.leaveComment = \u7559\u4e0b\u8a3b\u89e3 +gb.line = \u884c +gb.loading = \u8f09\u5165 +gb.local = \u672c\u5730\u7aef +gb.locality = \u4f4d\u7f6e +gb.log = \u65e5\u8a8c +gb.login = \u767b\u5165 +gb.logout = \u767b\u51fa +gb.looksGood = \u770b\u8d77\u4f86\u5f88\u597d +gb.luceneDisabled = \u505c\u7528Lucene\u7d22\u5f15\u529f\u80fd +gb.mailingLists = \u90f5\u4ef6\u540d\u55ae +gb.maintenanceTickets = \u7dad\u8b77 +gb.manage = \u7ba1\u7406 +gb.manual = \u81ea\u884c\u8f38\u5165 +gb.markdown = markdown +gb.markdownFailure = \u89e3\u6790Markdown\u5931\u6557 +gb.maxActivityCommits = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u7387 +gb.maxActivityCommitsDescription = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u6578\u91cf +gb.maxHits = \u6700\u5927\u9ede\u64ca +gb.md5FingerPrint = MD5 Fingerprint +gb.mentions = \u63d0\u5230 +gb.mentionsMeTickets = \u63d0\u5230\u4f60 +gb.merge = \u5408\u4f75 +gb.mergeBase = \u57fa\u672c\u5408\u4f75 +gb.merged = \u5df2\u5408\u4f75 +gb.mergedPatchset = \u5c07\u88dc\u4e01\u5408\u4f75 +gb.mergedPullRequest = \u5408\u4f75\u63a8\u9001\u8981\u6c42 +gb.mergeSha = mergeSha +gb.mergeStep1 = Check out a new branch to review the changes \u2014 run this from your project directory +gb.mergeStep2 = Bring in the proposed changes and review +gb.mergeStep3 = \u5c07\u63d0\u6848\u4fee\u6539\u5167\u5bb9\u5408\u4f75\u5230\u4f3a\u670d\u5668\u4e0a +gb.mergeTo = \u5408\u4f75\u5230 +gb.mergeToDescription = \u9810\u8a2d\u5c07\u6587\u4ef6\u76f8\u95dc\u88dc\u4e01\u5305\u8207\u6307\u5b9a\u5206\u652f(branch)\u5408\u4f75 +gb.mergingViaCommandLine = \u7d93\u7531\u6307\u4ee4\u57f7\u884c\u5408\u4f75 +gb.mergingViaCommandLineNote = \u5982\u679c\u4f60\u4e0d\u60f3\u8981\u4f7f\u7528\u81ea\u52d5\u5408\u4f75\u529f\u80fd,\u6216\u662f\u6309\u4e0b\u5408\u4f75\u6309\u9215, \u4f60\u53ef\u4ee5\u4e0b\u6307\u4ee4\u624b\u52d5\u5408\u4f75 +gb.message = \u8a0a\u606f +gb.metricAuthorExclusions = \u91cf\u5316\u7d71\u8a08\u6642\u6392\u9664\u6d3b\u8e8d\u5e33\u6236 +gb.metrics = \u91cf\u5316\u7d71\u8a08 +gb.milestone = \u91cc\u7a0b\u7891 +gb.milestoneDeleteFailed = \u522a\u9664\u91cc\u7a0b\u7891"{0}"\u5931\u6557 +gb.milestoneProgress = {0}\u958b\u555f,{1}\u7d50\u675f +gb.milestones = \u91cc\u7a0b\u7891 +gb.mirrorOf = {0}\u7684\u93e1\u50cf +gb.mirrorWarning = \u8a72\u6587\u4ef6\u5eab\u5c6c\u65bc\u93e1\u50cf, \u4e0d\u80fd\u5920\u63a5\u6536\u63a8\u9001(push) +gb.miscellaneous = \u5176\u4ed6 +gb.missing = \u5931\u8aa4! +gb.missingIntegrationBranchMore = \u76ee\u6a19\u5206\u652f\u4e0d\u5728\u6b64\u7248\u672c\u5eab +gb.missingPermission = the repository for this permission is missing\! +gb.missingUsername = \u7f3a\u5c11\u4f7f\u7528\u8005\u540d\u7a31 +gb.modification = \u4fee\u6539 +gb.monthlyActivity = \u6708\u6d3b\u52d5 +gb.moreChanges = \u6240\u6709\u8b8a\u66f4... +gb.moreHistory = \u66f4\u591a\u6b77\u53f2\u7d00\u9304... +gb.moreLogs = \u66f4\u591a\u63d0\u4ea4 ... +gb.mutable = \u52d5\u614b\u7d66\u4e88 +gb.myDashboard = \u6211\u7684\u5100\u8868\u677f +gb.myFork = \u6aa2\u8996\u6211\u5efa\u7acb\u7684\u5206\u652f(fork) +gb.myProfile = \u6211\u7684\u57fa\u672c\u8cc7\u6599 +gb.myRepositories = \u6211\u7684\u7248\u672c\u5eab +gb.myTickets = \u6211\u7684\u4efb\u52d9\u55ae +gb.myUrlDescription = \u4f60Gitblit\u4f3a\u670d\u5668\u7684\u516c\u958bURL +gb.name = \u540d\u5b57 +gb.nameDescription = \u4f7f\u7528"/"\u505a\u70ba\u6587\u4ef6\u5eab\u7fa4\u7d44\u5206\u985e. \u5982: library/mycoolib.git +gb.namedPushPolicy = Restrict Push (Named) +gb.namedPushPolicyDescription = \u4efb\u4f55\u4eba\u7686\u53ef\u6aa2\u8996\u8207\u8907\u88fd(clone)\u6587\u4ef6\u5eab. \u4f60\u53ef\u53e6\u5916\u6307\u5b9a\u8ab0\u80fd\u5920\u6709\u63a8\u9001\u529f\u80fd(push) +gb.nAttachments = {0}\u500b\u9644\u4ef6 +gb.nClosedTickets = {0}\u9805\u7d50\u675f +gb.nComments = {0}\u500b\u8a3b\u89e3 +gb.nCommits = {0}\u4efd\u63d0\u4ea4 +gb.needsImprovement = \u9700\u8981\u512a\u5316 +gb.new = \u5efa\u7acb +gb.newCertificate = \u5efa\u7acb\u8b49\u66f8 +gb.newCertificateDefaults = \u65b0\u8b49\u66f8\u9810\u8a2d\u503c +gb.newClientCertificateMessage = \u6ce8\u610f:\n'password'\u5bc6\u78bc\u4e26\u4e0d\u662f\u4f7f\u7528\u8005\u5bc6\u78bc, \u800c\u662f\u7528\u4f86\u4fdd\u8b77\u4f7f\u7528\u8005\u500b\u4eba\u7684keystore.\u8a72\u5bc6\u78bc\u4e26\u4e0d\u6703\u5132\u5b58, \u56e0\u6b64\u5fc5\u9808\u8a2d\u5b9a\u63d0\u793a(hint), \u8a72\u63d0\u793a\u5c07\u6703\u5beb\u5728\u4f7f\u7528\u8005\u7684README\u6587\u4ef6\u88e1\u9762. +gb.newMilestone = \u5efa\u7acb\u91cc\u7a0b\u7891 +gb.newRepository = \u5efa\u7acb\u7248\u672c\u5eab +gb.newSSLCertificate = \u65b0\u7684\u4f3a\u670d\u5668SSL\u8b49\u66f8 +gb.newTeam = \u5efa\u7acb\u5718\u968a +gb.newTicket = \u65b0\u589e\u4efb\u52d9\u55ae +gb.newUser = \u5efa\u7acb\u4f7f\u7528\u8005 +gb.nextPull = next pull +gb.nFederationProposalsToReview = \u7e3d\u5171\u6709{0}\u500b\u4e32\u9023\u8a08\u756b\u7b49\u5f85\u5be9\u8996 +gb.nMoreCommits = \u9084\u6709{0}\u4efd\u63d0\u4ea4 \u00bb +gb.noActivity = \u904e\u53bb{0}\u5929\u4f86,\u4e26\u6c92\u6709\u6d3b\u52d5\u7d00\u9304 +gb.noActivityToday = \u4eca\u5929\u6c92\u6709\u6d3b\u52d5\u7d00\u9304 +gb.noComments = \u6c92\u6709\u5099\u8a3b +gb.noDescriptionGiven = \u6c92\u6709\u7d66\u4e88\u7c21\u8ff0 +gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances. +gb.noForks = {0}\u6c92\u6709\u5206\u652f(fork) +gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}. +gb.noHits = \u7121\u9ede\u64ca +gb.noIndexedRepositoriesWarning = \u8ddf\u4f60\u76f8\u95dc\u7684\u6587\u4ef6\u5eab\u4e26\u6c92\u6709\u505aLucene\u7d22\u5f15 +gb.noMaximum = \u7121\u6700\u5927\u503c +gb.noMilestoneSelected = \u672a\u9078\u53d6\u91cc\u7a0b\u7891 +gb.none = \u7121 +gb.nOpenTickets = {0}\u9805\u958b\u555f\u4e2d +gb.noPermission = \u522a\u9664\u9019\u500b\u6b0a\u9650 +gb.noProposals = \u62b1\u6b49, {0}\u6b64\u6642\u4e26\u4e0d\u662f\u53ef\u63a5\u53d7\u7684\u8a08\u756b +gb.noSelectedRepositoriesWarning = \u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u6587\u4ef6\u5eab +gb.notifyChangedOpenTickets = \u5df2\u958b\u555f\u7684\u4efb\u52d9\u55ae\u6709\u7570\u52d5\u8acb\u767c\u9001\u901a\u77e5 +gb.notRestricted = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push +gb.notSpecified = \u7121\u6307\u5b9a +gb.nParticipants = {0}\u500b\u53c3\u8207 +gb.nTotalTickets = \u7e3d\u5171{0}\u9805 +gb.object = \u7269\u4ef6 +gb.of = \u7684 +gb.ok = ok +gb.oneAttachment = {0}\u500b\u9644\u4ef6 +gb.oneComment = {0}\u500b\u8a3b\u89e3 +gb.oneCommit = 1\u500b\u63d0\u4ea4 +gb.oneCommitTo = 1\u500b\u63d0\u4ea4\u5230 +gb.oneMoreCommit = \u9084\u6709\u4e00\u500b\u63d0\u4ea4 \u00bb +gb.oneParticipant = {0}\u53c3\u8207 +gb.OneProposalToReview = \u6709\u4e00\u500b\u4e32\u9023\u7684\u63d0\u6848\u7b49\u5f85\u5be9\u67e5 +gb.opacityAdjust = Adjust opacity +gb.open = \u958b\u555f +gb.openMilestones = \u6253\u958b\u91cc\u7a0b\u7891 +gb.organization = \u7d44\u7e54 +gb.organizationalUnit = \u7d44\u7e54\u55ae\u4f4d +gb.origin = origin +gb.originDescription = \u6b64\u6587\u4ef6\u5eabURL\u5df2\u7d93\u88ab\u8907\u88fd(cloned)\u4e86 +gb.overdue = \u904e\u671f +gb.overview = \u6982\u89c0 +gb.owned = \u64c1\u6709\u7684 +gb.owner = \u64c1\u6709\u8005 +gb.ownerDescription = \u64c1\u6709\u8005\u53ef\u4fee\u6539\u6587\u4ef6\u5eab\u8a2d\u5b9a\u503c +gb.ownerPermission = \u6587\u4ef6\u5eab\u6240\u6709\u8005 +gb.owners = \u6240\u6709\u8005 +gb.ownersDescription = \u6240\u6709\u8005\u53ef\u4ee5\u7ba1\u7406\u6587\u4ef6\u5eab,\u4f46\u662f\u4e0d\u5141\u8a31\u4fee\u6539\u540d\u7a31(\u79c1\u4eba\u6587\u4ef6\u5eab\u4f8b\u5916) +gb.pageFirst = \u7b2c\u4e00\u7b46 +gb.pageNext = \u4e0b\u4e00\u9801 +gb.pagePrevious = \u4e0a\u4e00\u9801 +gb.pages = \u6587\u4ef6 +gb.parent = \u4e0a\u500b\u7248\u672c +gb.password = \u5bc6\u78bc +gb.passwordChangeAborted = \u53d6\u6d88\u5bc6\u78bc\u8b8a\u66f4 +gb.passwordChanged = \u5bc6\u78bc\u8b8a\u66f4\u6210\u529f +gb.passwordHint = \u5bc6\u78bc\u63d0\u793a +gb.passwordHintRequired = \u5bc6\u78bc\u63d0\u793a(\u5fc5\u8981) +gb.passwordsDoNotMatch = \u5bc6\u78bc\u4e0d\u76f8\u7b26 +gb.passwordTooShort = \u5bc6\u78bc\u904e\u77ed, \u6700\u5c11{0}\u500b\u5b57\u5143 +gb.patch = \u4fee\u88dc\u6a94 +gb.patchset = \u88dc\u4e01 +gb.patchsetAlreadyMerged = \u8a72\u88dc\u4e01\u5df2\u7d93\u5408\u4f75\u5230{0} +gb.patchsetMergeable = \u8a72\u88dc\u4e01\u53ef\u4ee5\u81ea\u52d5\u8207{0}\u5408\u4f75 +gb.patchsetMergeableMore = \u4f7f\u7528\u547d\u4ee4\u529f\u80fd,\u8b93\u6b64\u88dc\u4e01\u53ef\u4ee5\u8207{0}\u5408\u4f75 +gb.patchsetN = \u88dc\u4e01{0} +gb.patchsetNotApproved = \u8a72\u88dc\u4e01\u7248\u672c\u4e26\u6c92\u6709\u88ab\u6279\u51c6\u8207{0}\u5408\u4f75 +gb.patchsetNotApprovedMore = \u8a72\u88dc\u4e01\u5fc5\u9808\u7531\u5be9\u67e5\u8005\u6279\u51c6 +gb.patchsetNotMergeable = \u8a72\u88dc\u4e01\u4e0d\u80fd\u81ea\u52d5\u8207{0}\u5408\u4f75 +gb.patchsetNotMergeableMore = \u5fc5\u9808\u4ee5rebased\u6216\u662f\u624b\u52d5\u8207{0}\u5408\u4f75\u7684\u65b9\u5f0f\u624d\u80fd\u89e3\u6c7a\u8a72\u88dc\u4e01\u9020\u6210\u7684\u885d\u7a81 +gb.patchsetVetoedMore = \u5be9\u8996\u8005\u5df2\u7d93\u5c0d\u6b64\u88dc\u4e01\u6295\u7968 +gb.permission = \u6b0a\u9650 +gb.permissions = \u6b0a\u9650 +gb.permittedTeams = permitted teams +gb.permittedUsers = permitted users +gb.personalRepositories = \u500b\u4eba\u6587\u4ef6\u5eab +gb.pleaseGenerateClientCertificate = \u8acb\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684\u7528\u6236\u7aef\u8b49\u66f8 +gb.pleaseSelectGitIgnore = \u8acb\u9078\u64c7\u4e00\u500b.gitignore\u6a94\u6848 +gb.pleaseSelectProject = \u8acb\u9078\u64c7\u5c08\u6848! +gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal\! +gb.pleaseSetGitblitUrl = \u8acb\u8f38\u5165Gitblit URL ! +gb.pleaseSetRepositoryName = \u8acb\u8a2d\u5b9a\u7248\u672c\u5eab\u540d\u7a31 +gb.pleaseSetTeamName = \u8acb\u8f38\u5165\u5718\u968a\u540d\u7a31 +gb.pleaseSetUsername = \u8acb\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31 +gb.plugins = \u63d2\u4ef6 +gb.postReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4e26\u4e14\u5728refs\u5b8c\u7562\u5f8c</em>, \u5c07\u6703\u57f7\u884cPost-receive hook..<p>This is the appropriate hook for notifications, build triggers, etc.</p> +gb.postReceiveScripts = post-receive\u8173\u672c +gb.preferences = \u9810\u8a2d\u5e38\u7528\u503c +gb.preparingFork = \u6b63\u5728\u6e96\u5099\u8907\u88fd\u4e2d(fork)... +gb.preReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4f46\u5728\u9084\u6c92\u6709\u66f4\u65b0refs\u524d</em>, \u5c07\u6703\u57f7\u884cPre-receive hook. <p>This is the appropriate hook for rejecting a push.</p> +gb.preReceiveScripts = pre-receive \u8173\u672c +gb.preview = \u9810\u89bd +gb.priority = \u512a\u5148 +gb.privilegeWithdrawn = \u53d6\u6d88\u6b0a\u9650 +gb.project = \u7fa4\u7d44 +gb.projects = \u7fa4\u7d44 +gb.properties = \u5c6c\u6027 +gb.proposal = \u63d0\u6848 +gb.proposalError = \u62b1\u6b49, {0} \u4efd\u5831\u544a\u767c\u751f\u9810\u671f\u5916\u7684\u932f\u8aa4! +gb.proposalFailed = Sorry, {0} did not receive any proposal data\! +gb.proposalReceived = Proposal successfully received by {0}. +gb.proposals = \u8981\u6c42\u806f\u5408\u7684\u63d0\u6848 +gb.proposalTickets = \u63d0\u6848\u4fee\u6539 +gb.proposedThisChange = proposed this change +gb.proposeInstructions = To start, craft a patchset and upload it with Git. Gitblit will link your patchset to this ticket by the id. +gb.proposePatchset = \u63d0\u51fa\u88dc\u4e01 +gb.proposePatchsetNote = \u6b61\u8fce\u5c0d\u6b64\u4efb\u52d9\u55ae\u63d0\u4f9b\u88dc\u4e01 +gb.proposeWith = propose a patchset with {0} +gb.ptCheckout = Fetch & checkout the current patchset to a review branch +gb.ptDescription = the Gitblit patchset tool +gb.ptDescription1 = Barnum is a command-line companion for Git that simplifies the syntax for working with Gitblit Tickets and Patchsets. +gb.ptDescription2 = Barnum requires Python 3 and native Git. It runs on Windows, Linux, and Mac OS X. +gb.ptMerge = \u53d6\u5f97\u76ee\u524d\u88dc\u4e01,\u7136\u5f8c\u8207\u4f60\u672c\u6a5f\u7aef\u7684\u5206\u652f\u5408\u4f75 +gb.ptSimplifiedCollaboration = simplified collaboration syntax +gb.ptSimplifiedMerge = simplified merge syntax +gb.publicKey = \u516c\u958b\u91d1\u9470 +gb.pushedNCommitsTo = {0}\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3 +gb.pushedNewBranch = \u65b0\u5206\u652f\u5df2\u63a8\u9001(pushed) +gb.pushedNewTag = \u65b0\u6a19\u7c64\u5df2\u63a8\u9001(pushed) +gb.pushedOneCommitTo = 1\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3 +gb.pushPermission = {0}(\u63a8\u9001) +gb.pushRestricted = authenticated push +gb.queries = \u67e5\u8a62\u7d50\u679c +gb.query = \u67e5\u8a62 +gb.queryHelp = \u652f\u63f4\u6a19\u6e96\u67e5\u8a62\u8a9e\u6cd5.<p/><p/>\u8a73\u60c5\u8acb\u53c3\u8003 <a target\ = "_new" href\ = "http\://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> +gb.queryResults = results {0} - {1} ({2} hits) +gb.questionTickets = \u63d0\u554f +gb.raw = \u539f\u59cb +gb.reason = \u539f\u56e0 +gb.receive = \u63a5\u6536 +gb.received = \u5df2\u63a5\u6536 +gb.receiveSettings = \u8a2d\u5b9a\u63a5\u6536\u65b9\u5f0f +gb.receiveSettingsDescription = \u63a7\u7ba1\u63a8\u9001\u5230\u6587\u4ef6\u5eab\u7684\u63a5\u6536\u65b9\u5f0f +gb.recent = \u6700\u8fd1 +gb.recentActivity = \u6700\u8fd1\u6d3b\u8e8d\u72c0\u6cc1 +gb.recentActivityNone = \u904e\u53bb{0}\u5929/\u7121 +gb.recentActivityStats = \u904e\u53bb{0}\u5929,\u4e00\u5171\u6709{2}\u4eba\u57f7\u884c{1}\u4efd\u63d0\u4ea4 +gb.reflog = \u76f8\u95dc\u65e5\u8a8c +gb.refresh = \u5237\u65b0 +gb.refs = \u5f15\u7528 +gb.regexPermission = \u5df2\u7d93\u4f7f\u7528\u6b63\u898f\u8868\u793a\u5f0f(regular expression)"{0}" \u8a2d\u5b9a\u6b0a\u9650\u5b8c\u7562 +gb.registration = \u8a3b\u518a +gb.registrations = federation registrations +gb.releaseDate = \u767c\u8868\u65e5 +gb.remote = \u9060\u7aef +gb.removeVote = \u79fb\u9664\u6295\u7968 +gb.rename = \u6539\u540d\u7a31 +gb.repositories = \u6587\u4ef6\u5eab +gb.repository = \u7248\u672c\u5eab +gb.repositoryDeleted = \u7248\u672c\u5eab"{0}"\u5df2\u522a\u9664 +gb.repositoryDeleteFailed = \u522a\u9664\u7248\u672c\u5eab"{0}"\u5931\u6557! +gb.repositoryDoesNotAcceptPatchsets = \u8a72\u7248\u672c\u5eab\u4e0d\u63a5\u53d7\u88dc\u4e01 +gb.repositoryForked = \u7248\u672c\u5eab{0}\u5df2\u7d93\u5efa\u7acb\u5206\u652f(fork) +gb.repositoryForkFailed= \u5efa\u7acb\u5206\u652f(fork)\u5931\u6557 +gb.repositoryIsFrozen = \u8a72\u7248\u672c\u5eab\u5df2\u51cd\u7d50 +gb.repositoryIsMirror = \u8a72\u7248\u672c\u5eab\u70ba\u552f\u8b80\u8907\u672c +gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5eab! +gb.repositoryNotSpecifiedFor = \u7248\u672c\u5eab\u4e26\u6c92\u6709\u6307\u5b9a\u7d66 {0}\! +gb.repositoryPermissions = \u7248\u672c\u5eab\u6b0a\u9650 +gb.repositoryUrl = \u7248\u672c\u5eab url +gb.requestTickets = \u512a\u5316 & \u4efb\u52d9 +gb.requireApproval = \u9700\u6279\u51c6 +gb.requireApprovalDescription = \u5408\u4f75\u6309\u9215\u555f\u7528\u524d,\u88dc\u4e01\u5305\u5fc5\u9808\u5148\u6279\u51c6 +gb.reset = \u6e05\u9664 +gb.responsible = \u8ca0\u8cac\u4eba\u54e1 +gb.restrictedRepositories = restricted repositories +gb.review = \u8907\u67e5(review) +gb.reviewedPatchsetRev = reviewed patchset {0} revision {1}\: {2} +gb.reviewers = \u5be9\u67e5\u8005 +gb.reviewPatchset = review {0} patchset {1} +gb.reviews = reviews +gb.revisionHistory = \u4fee\u6539\u7d00\u9304 +gb.revokeCertificate = \u64a4\u56de\u8b49\u66f8 +gb.revokeCertificateReason = \u8acb\u8f38\u5165\u64a4\u56de\u8b49\u66f8\u7406\u7531 +gb.revoked = \u5df2\u64a4\u92b7 +gb.rewind = REWIND +gb.rewindPermission = {0} (push, ref creation+deletion+rewind) +gb.save = \u5132\u5b58 +gb.search = \u641c\u5c0b +gb.searchForAuthor = Search for commits authored by +gb.searchForCommitter = Search for commits committed by +gb.searchTickets = \u641c\u5c0b\u4efb\u52d9\u55ae +gb.searchTicketsTooltip = \u627e\u5230{0}\u4efd\u4efb\u52d9\u55ae +gb.searchTooltip = \u641c\u5c0b{0} +gb.searchTypeTooltip = \u9078\u64c7\u641c\u5c0b\u985e\u578b +gb.selectAccessRestriction = Please select access restriction\! +gb.selected = \u9078\u5b9a +gb.selectFederationStrategy = Please select federation strategy\! +gb.sendEmail = \u767cemail +gb.sendProposal = \u63d0\u6848 +gb.serialNumber = \u5e8f\u865f +gb.serveCertificate = \u555f\u7528\u4f7f\u7528\u6b64\u8b49\u66f8\u7684https\u529f\u80fd +gb.serverDoesNotAcceptPatchsets = \u672c\u4f3a\u670d\u5668\u4e0d\u63a5\u53d7\u88dc\u4e01 +gb.servers = \u4f3a\u670d\u5668 +gb.servletContainer = servlet\u5bb9\u5668 +gb.sessionEnded = session\u5df2\u7d93\u53d6\u6d88 +gb.setDefault = \u8a2d\u70ba\u9810\u8a2d\u503c +gb.settings = \u8a2d\u5b9a +gb.severity = \u91cd\u8981 +gb.sha1FingerPrint = SHA-1 Fingerprint +gb.show_whitespace = \u986f\u793a\u7a7a\u767d +gb.showHideDetails = \u986f\u793a/\u96b1\u85cf \u8a73\u89e3\u5167\u5bb9 +gb.showReadme = \u986f\u793areadme\u6587\u4ef6 +gb.showReadmeDescription = \u5728\u532f\u7e3d\u9801\u9762\u4e2d\u986f\u793a"readme"(markdown\u683c\u5f0f) +gb.showRemoteBranches = \u986f\u793a\u9060\u7aef\u5206\u652f +gb.showRemoteBranchesDescription = \u986f\u793a\u9060\u7aef\u5206\u652f(branches) +gb.signatureAlgorithm = \u7c3d\u7ae0\u6f14\u7b97\u6cd5 +gb.since = \u5f9e +gb.siteName = \u7ad9\u53f0\u540d\u7a31 +gb.siteNameDescription = \u4f3a\u670d\u5668\u7c21\u7a31 +gb.size = \u5bb9\u91cf +gb.skipSizeCalculation = \u7565\u904e\u5bb9\u91cf\u8a08\u7b97 +gb.skipSizeCalculationDescription = \u4e0d\u8a08\u7b97\u6587\u4ef6\u5eab\u5bb9\u91cf(\u52a0\u5feb\u7db2\u9801\u8f09\u5165\u901f\u5ea6) +gb.skipSummaryMetrics = \u7565\u904e\u91cf\u5316\u532f\u7e3d +gb.skipSummaryMetricsDescription = \u4e0d\u8981\u8a08\u7b97\u91cf\u5316\u4e26\u4e14\u986f\u793a\u5728\u532f\u7e3d\u9801\u9762\u4e0a(\u52a0\u5feb\u901f\u5ea6) +gb.sort = \u6392\u5e8f +gb.sortHighestPriority = \u6700\u9ad8\u512a\u5148 +gb.sortHighestSeverity = \u6700\u91cd\u8981 +gb.sortLeastComments = \u6700\u5c11\u5099\u8a3b +gb.sortLeastPatchsetRevisions = \u6700\u5c11\u88dc\u4e01\u4fee\u6539 +gb.sortLeastRecentlyUpdated = \u6700\u8fd1\u6700\u5c11\u8b8a\u52d5 +gb.sortLeastVotes = \u6700\u5c11\u6295\u7968 +gb.sortLowestPriority = \u6700\u4f4e\u512a\u5148 +gb.sortLowestSeverity = \u6700\u4e0d\u91cd\u8981 +gb.sortMostComments = \u6700\u591a\u5099\u8a3b +gb.sortMostPatchsetRevisions = \u6700\u591a\u88dc\u4e01\u4fee\u6b63 +gb.sortMostRecentlyUpdated = \u6700\u8fd1\u66f4\u65b0 +gb.sortMostVotes = \u6700\u591a\u6295\u7968 +gb.sortNewest = \u6700\u65b0 +gb.sortOldest = \u6700\u820a +gb.specified = \u6307\u5b9a\u7d66\u4e88(\u542b\u7cfb\u7d71\u9810\u8a2d) +gb.sshKeyCommentDescription = \u8acb\u8f38\u5165\u5099\u8a3b, \u82e5\u7121\u5099\u8a3b, \u5c07\u81ea\u8a02\u586b\u5165key data +gb.sshKeyPermissionDescription = \u6307\u5b9a\u8a72SSH key\u6240\u64c1\u6709\u7684\u5b58\u53d6\u6b0a\u9650 +gb.sshKeys = SSH Keys +gb.sshKeysDescription = SSH \u516c\u958b\u91d1\u9470\u662f\u5bc6\u78bc\u8a8d\u8b49\u5916\u66f4\u5b89\u5168\u7684\u9078\u9805 +gb.sslCertificateGenerated = \u6210\u529f\u7522\u751f\u7d66{0}\u7684\u670d\u5668SSL\u8b49\u66f8 +gb.sslCertificateGeneratedRestart = \u6210\u529f\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684SSL\u8b49\u66f8\n\u4f60\u5fc5\u9808\u91cd\u65b0\u555f\u52d5Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u624d\u80fd\u555f\u7528\u65b0\u7684\u8b49\u66f8\n\nf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''. +gb.star = \u91cd\u8981 +gb.stargazers = stargazers +gb.starred = \u91cd\u8981 +gb.starredAndOwned = \u91cd\u8981\u7684 & \u64c1\u6709\u7684 +gb.starredRepositories = \u91cd\u8981\u7684\u6587\u4ef6\u5eab +gb.starting = \u555f\u52d5\u4e2d +gb.stateProvince = \u5dde\u6216\u7701 +gb.stats = \u7d71\u8a08 +gb.status = \u72c0\u614b +gb.stepN = \u6b65\u9a5f{0} +gb.stopWatching = \u505c\u6b62\u8ffd\u8e64(watching) +gb.subject = \u6a19\u984c +gb.subscribe = \u8a02\u95b1 +gb.summary = \u532f\u7e3d +gb.superseded = \u5df2\u88ab\u66ff\u4ee3 +gb.tag = \u6a19\u7c64 +gb.tagger = tagger +gb.tags = \u6a19\u7c64 +gb.taskTickets = \u4efb\u52d9 +gb.team = \u5718\u968a +gb.teamCreated = \u5718\u968a"{0}"\u65b0\u589e\u6210\u529f. +gb.teamMembers = \u5718\u968a\u6210\u54e1 +gb.teamMemberships = \u5718\u968a\u6210\u54e1(memberships) +gb.teamMustSpecifyRepository = \u5718\u968a\u6700\u5c11\u8981\u6307\u5b9a\u4e00\u500b\u7248\u672c\u5eab +gb.teamName = \u5718\u968a\u540d\u7a31 +gb.teamNameUnavailable = \u5718\u968a"{0}"\u4e0d\u5b58\u5728. +gb.teamPermission = "{0}" \u5718\u968a\u6210\u54e1\u7684\u6b0a\u9650 +gb.teamPermissions = \u5718\u968a\u6b0a\u9650 +gb.teamPermissionsDescription = \u4f60\u53ef\u4ee5\u6307\u5b9a\u5718\u968a\u6b0a\u9650.\u9019\u4e9b\u8a2d\u5b9a\u5c07\u6703\u53d6\u4ee3\u539f\u672c\u5718\u968a\u9810\u8a2d\u6b0a\u9650 +gb.teams = \u53c3\u8207\u7684\u5718\u968a +gb.ticket = \u4efb\u52d9\u55ae +gb.ticketAssigned = \u5df2\u6307\u5b9a +gb.ticketComments = \u8a3b\u89e3 +gb.ticketId = \u4efb\u52d9\u55aeID +gb.ticketIsClosed = \u8a72\u4efb\u52d9\u55ae\u5df2\u7d93\u7d50\u6848 +gb.ticketN = \u4efb\u52d9\u55ae\u865f#{0} +gb.ticketOpenDate = \u767c\u884c\u65e5 +gb.ticketPatchset = {0}\u4efb\u52d9\u55ae,{1}\u88dc\u4e01 +gb.tickets = \u4efb\u52d9\u55ae +gb.ticketSettings = \u4efb\u52d9\u55ae\u5167\u5bb9\u8a2d\u5b9a +gb.ticketStatus = \u72c0\u614b +gb.ticketsWelcome = \u4f60\u53ef\u4ee5\u5229\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71\u5efa\u69cb\u51fa\u5f85\u8fa6\u4e8b\u9805, \u81ed\u87f2\u56de\u5831\u5340\u4ee5\u53ca\u88dc\u4e01\u5305\u7684\u5354\u540c\u5408\u4f5c +gb.time.daysAgo = {0}\u5929\u524d +gb.time.hoursAgo = {0}\u5c0f\u6642\u524d +gb.time.inDays = {0}\u5929\u5167 +gb.time.inHours = {0}\u5c0f\u6642\u5167 +gb.time.inMinutes = {0}\u5206\u9418\u5167 +gb.time.justNow = \u525b\u525b +gb.time.minsAgo = {0}\u5206\u9418\u524d +gb.time.monthsAgo = {0}\u6708\u524d +gb.time.oneYearAgo = 1\u5e74\u524d +gb.time.today = \u4eca\u5929 +gb.time.weeksAgo = {0}\u5468\u524d +gb.time.yearsAgo = {0}\u5e74\u524d +gb.time.yesterday = \u6628\u5929 +gb.title = \u6a19\u984c +gb.to = to +gb.toBranch = to {0} +gb.todaysActivityNone = \u4eca\u5929/\u7121 +gb.todaysActivityStats = \u4eca\u5929/\u6709{2}\u500b\u4f5c\u8005\u5b8c\u6210{1}\u500b\u63d0\u4ea4 +gb.token = token +gb.tokenAllDescription = \u6240\u6709\u7248\u672c\u5eab,\u4f7f\u7528\u8005\u8207\u8a2d\u5b9a +gb.tokenJurDescription = \u6240\u6709\u7248\u672c\u5eab +gb.tokens = federation tokens +gb.tokenUnrDescription = \u6240\u6709\u7248\u672c\u5eab\u8207\u4f7f\u7528\u8005 +gb.topic = \u8a71\u984c +gb.topicsAndLabels = \u8a71\u984c\u8207\u6a19\u8a18 +gb.transportPreference = \u9810\u8a2d\u901a\u8a0a\u5354\u5b9a +gb.transportPreferenceDescription = \u8a2d\u5b9a\u4f60\u5e38\u7528\u7684\u9023\u7dda\u901a\u8a0a\u5354\u5b9a\u4ee5\u7528\u4f86\u8907\u88fd(clone) +gb.tree = \u76ee\u9304 +gb.type = \u985e\u578b +gb.unauthorizedAccessForRepository = \u7248\u672c\u5eab\u672a\u6388\u6b0a\u5b58\u53d6 +gb.undefinedQueryWarning = \u672a\u8a2d\u5b9a\u67e5\u8a62\u689d\u4ef6 +gb.unspecified = \u672a\u6307\u5b9a +gb.unstar = \u53d6\u6d88 +gb.updated = \u5df2\u66f4\u65b0 +gb.updatedBy = updated by +gb.uploadedPatchsetN = \u88dc\u4e01{0}\u5df2\u4e0a\u50b3 +gb.uploadedPatchsetNRevisionN = \u88dc\u4e01{0}\u4fee\u6539\u7248\u672c{1}\u5df2\u4e0a\u50b3 +gb.url = URL +gb.useDocsDescription = \u8a08\u7b97\u6587\u4ef6\u5eab\u88e1\u9762\u7684Markdown\u6a94\u6848 +gb.useIncrementalPushTagsDescription = \u63a8\u9001\u6642\u5c07\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u865f\u78bc +gb.userCreated = \u6210\u529f\u5efa\u7acb\u65b0\u4f7f\u7528\u8005"{0}" +gb.userDeleted = \u4f7f\u7528\u8005"{0}"\u5df2\u522a\u9664 +gb.userDeleteFailed = \u4f7f\u7528\u8005"{0}"\u522a\u9664\u5931\u6557 +gb.username = \u4f7f\u7528\u8005\u540d\u7a31 +gb.usernameUnavailable = \u4f7f\u7528\u8005\u540d\u7a31"{0}"\u4e0d\u53ef\u7528 +gb.userPermissions = \u4f7f\u7528\u8005\u6b0a\u9650 +gb.userPermissionsDescription = \u4f60\u53ef\u4ee5\u91dd\u5c0d\u5e33\u865f\u8a2d\u5b9a\u6b0a\u9650(\u9019\u4e9b\u8a2d\u5b9a\u5c07\u8986\u84cb\u5718\u968a\u6216\u5176\u4ed6\u6b0a\u9650) +gb.users = \u4f7f\u7528\u8005 +gb.userServiceDoesNotPermitAddUser = {0}\u4e0d\u5141\u8a31\u65b0\u589e\u4f7f\u7528\u8005\u5e33\u865f +gb.userServiceDoesNotPermitPasswordChanges = {0}\u4e0d\u5141\u8a31\u4fee\u6539\u5bc6\u78bc +gb.useTicketsDescription = readonly, distributed Ticgit issues +gb.validFrom = valid from +gb.validity = validity +gb.validUntil = valid until +gb.verifyCommitter = \u63d0\u4ea4\u8005\u9700\u9a57\u8b49 +gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7b26\u5408\u63a8\u9001\u5e33\u865f +gb.verifyCommitterNote = \u6240\u6709\u5408\u4f75\u52d5\u4f5c\u7686\u9808\u5f37\u5236\u4f7f\u7528"--no-ff"\u53c3\u6578 +gb.version = \u7248\u672c +gb.veto = veto +gb.view = \u6aa2\u8996 +gb.viewAccess = \u4f60\u6c92\u6709Gitblit\u8b80\u53d6\u6216\u662f\u4fee\u6539\u6b0a\u9650 +gb.viewCertificate = \u6aa2\u8996\u8b49\u66f8 +gb.viewComparison = \u6bd4\u8f03\u9019{0}\u500b\u63d0\u4ea4 \u00bb +gb.viewPermission = {0} (\u6aa2\u8996) +gb.viewPolicy = Restrict View, Clone, & Push +gb.viewPolicyDescription = \u9078\u64c7\u53ef\u4ee5\u5728\u6587\u4ef6\u5eab\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u7684\u4f7f\u7528\u8005, \u9664\u6b64\u4e4b\u5916\u5176\u4ed6\u4eba\u7686\u7121\u6b0a\u9650 +gb.viewRestricted = authenticated view, clone, & push +gb.vote = \u5c0d{0}\u6295\u7968 +gb.voters = votes +gb.votes = votes +gb.warning = \u8b66\u544a +gb.watch = \u76e3\u770b{0} +gb.watchers = \u76e3\u770b\u8005 +gb.watching = \u76e3\u770b\u4e2d +gb.workingCopy = \u5de5\u4f5c\u8907\u672c +gb.workingCopyWarning = \u8a72\u6587\u4ef6\u5eab\u4ecd\u6709\u5de5\u4f5c\u8907\u672c,\u56e0\u6b64\u7121\u6cd5\u63a5\u53d7\u63a8\u9001(push) +gb.write = write +gb.youDoNotHaveClonePermission = \u4f60\u4e0d\u5141\u8a31\u8907\u88fd(clone)\u6b64\u6587\u4ef6\u5eab +gb.yourAssignedTickets = \u6307\u6d3e\u7d66\u4f60\u7684 +gb.yourCreatedTickets = \u7531\u4f60\u65b0\u589e\u7684 +gb.yourWatchedTickets = \u4f60\u60f3\u770b\u7684 +gb.zip = zip\u58d3\u7e2e\u6a94 +gb.ticketState = +gb.repositoryForkFailed = +gb.anonymousUser = +gb.oneAttachment = +gb.viewPolicy = +gb.emailMeOnMyTicketChangesDescription = diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketApp.java b/src/main/java/com/gitblit/wicket/GitblitWicketApp.java index 8d3d598d..fefa0f4a 100644 --- a/src/main/java/com/gitblit/wicket/GitblitWicketApp.java +++ b/src/main/java/com/gitblit/wicket/GitblitWicketApp.java @@ -8,12 +8,14 @@ import org.apache.wicket.markup.html.WebPage; import com.gitblit.IStoredSettings; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IFilestoreManager; import com.gitblit.manager.IGitblit; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IPluginManager; import com.gitblit.manager.IProjectManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; import com.gitblit.manager.IUserManager; import com.gitblit.tickets.ITicketService; import com.gitblit.transport.ssh.IPublicKeyManager; @@ -68,8 +70,12 @@ public interface GitblitWicketApp { public abstract IGitblit gitblit(); + public abstract IServicesManager services(); + public abstract ITicketService tickets(); public abstract TimeZone getTimezone(); + + public abstract IFilestoreManager filestore(); }
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java index 7865fb3b..68ad84a5 100644 --- a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java +++ b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java @@ -17,6 +17,8 @@ package com.gitblit.wicket; import java.util.Date;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.protocol.http.IWebApplicationFactory;
@@ -28,7 +30,6 @@ import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerWicketFilter;
import com.gitblit.manager.IProjectManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
@@ -37,8 +38,6 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
-import dagger.ObjectGraph;
-
/**
*
* Customization of the WicketFilter to allow smart browser-side caching of
@@ -47,7 +46,8 @@ import dagger.ObjectGraph; * @author James Moger
*
*/
-public class GitblitWicketFilter extends DaggerWicketFilter {
+@Singleton
+public class GitblitWicketFilter extends WicketFilter {
private IStoredSettings settings;
@@ -59,13 +59,19 @@ public class GitblitWicketFilter extends DaggerWicketFilter { private GitBlitWebApp webapp;
- @Override
- protected void inject(ObjectGraph dagger) {
- this.settings = dagger.get(IStoredSettings.class);
- this.runtimeManager = dagger.get(IRuntimeManager.class);
- this.repositoryManager = dagger.get(IRepositoryManager.class);
- this.projectManager = dagger.get(IProjectManager.class);
- this.webapp = dagger.get(GitBlitWebApp.class);
+ @Inject
+ public GitblitWicketFilter(
+ IStoredSettings settings,
+ IRuntimeManager runtimeManager,
+ IRepositoryManager repositoryManager,
+ IProjectManager projectManager,
+ GitBlitWebApp webapp) {
+
+ this.settings = settings;
+ this.runtimeManager = runtimeManager;
+ this.repositoryManager = repositoryManager;
+ this.projectManager = projectManager;
+ this.webapp = webapp;
}
@Override
diff --git a/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java b/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java new file mode 100644 index 00000000..1bf34e43 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket; + +import org.apache.wicket.markup.html.form.PasswordTextField; +import org.apache.wicket.model.IModel; + +/** + * PasswordText field which will not trim spaces from the input field. This + * ensures the password trimming behaviour is everywhere (ui/ssh/git) the same + * (#932). + */ +public class NonTrimmedPasswordTextField extends PasswordTextField +{ + private static final long serialVersionUID = 1L; + + public NonTrimmedPasswordTextField(final String id) + { + super(id); + } + + public NonTrimmedPasswordTextField(final String id, final IModel<String> model) + { + super(id, model); + } + + @Override + protected boolean shouldTrimInput() + { + return false; + } + +} diff --git a/src/main/java/com/gitblit/wicket/TicketsUI.java b/src/main/java/com/gitblit/wicket/TicketsUI.java index 0eff4cff..8d599523 100644 --- a/src/main/java/com/gitblit/wicket/TicketsUI.java +++ b/src/main/java/com/gitblit/wicket/TicketsUI.java @@ -21,6 +21,8 @@ import java.text.MessageFormat; import org.apache.wicket.markup.html.basic.Label; import com.gitblit.models.TicketModel; +import com.gitblit.models.TicketModel.Priority; +import com.gitblit.models.TicketModel.Severity; import com.gitblit.models.TicketModel.Status; import com.gitblit.models.TicketModel.Type; import com.gitblit.utils.StringUtils; @@ -38,37 +40,74 @@ public class TicketsUI { public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() }; public static Label getStateIcon(String wicketId, TicketModel ticket) { - return getStateIcon(wicketId, ticket.type, ticket.status); + return getStateIcon(wicketId, ticket.type, ticket.status, ticket.severity); } - public static Label getStateIcon(String wicketId, Type type, Status state) { + public static Label getStateIcon(String wicketId, Type type, Status state, Severity severity) { Label label = new Label(wicketId); if (type == null) { type = Type.defaultType; } switch (type) { case Proposal: - WicketUtils.setCssClass(label, "fa fa-code-fork"); + WicketUtils.setCssClass(label, "fa fa-code-fork fa-fw"); break; case Bug: - WicketUtils.setCssClass(label, "fa fa-bug"); + WicketUtils.setCssClass(label, "fa fa-bug fa-fw"); break; case Enhancement: - WicketUtils.setCssClass(label, "fa fa-magic"); + WicketUtils.setCssClass(label, "fa fa-magic fa-fw"); break; case Question: - WicketUtils.setCssClass(label, "fa fa-question"); + WicketUtils.setCssClass(label, "fa fa-question fa-fw"); + break; + case Maintenance: + WicketUtils.setCssClass(label, "fa fa-cogs fa-fw"); break; default: // standard ticket - WicketUtils.setCssClass(label, "fa fa-ticket"); + WicketUtils.setCssClass(label, "fa fa-ticket fa-fw"); + } + WicketUtils.setHtmlTooltip(label, getTypeState(type, state, severity)); + + return label; + } + + public static Label getPriorityIcon(String wicketId, Priority priority) { + Label label = new Label(wicketId); + if (priority == null) { + priority = Priority.defaultPriority; + } + switch (priority) { + case Urgent: + WicketUtils.setCssClass(label, "fa fa-step-forward fa-rotate-270"); + break; + case High: + WicketUtils.setCssClass(label, "fa fa-caret-up fa-lg"); + break; + case Low: + WicketUtils.setCssClass(label, "fa fa-caret-down fa-lg"); + break; + default: } - WicketUtils.setHtmlTooltip(label, getTypeState(type, state)); + WicketUtils.setHtmlTooltip(label, priority.toString()); + return label; } - public static String getTypeState(Type type, Status state) { - return state.toString() + " " + type.toString(); + public static String getPriorityClass(Priority priority) { + return String.format("priority-%s", priority); + } + + public static String getSeverityClass(Severity severity) { + return String.format("severity-%s", severity); + } + + public static String getTypeState(Type type, Status state, Severity severity) { + if (Severity.Unrated == severity) { + return state.toString() + " " + type.toString(); + } + return state.toString() + " " + type.toString() + ", " + severity.toString(); } public static String getLozengeClass(Status status, boolean subtle) { diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java index d47390d4..0d48e2be 100644 --- a/src/main/java/com/gitblit/wicket/WicketUtils.java +++ b/src/main/java/com/gitblit/wicket/WicketUtils.java @@ -29,12 +29,14 @@ import javax.servlet.http.HttpServletRequest; import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.Request;
+import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.resource.ContextRelativeResource;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -46,6 +48,7 @@ import com.gitblit.IStoredSettings; import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.Metric;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
@@ -56,6 +59,10 @@ public class WicketUtils { container.add(new SimpleAttributeModifier("class", value));
}
+ public static void addCssClass(Component container, String value) {
+ container.add(new AttributeAppender("class", new Model<String>(value), " "));
+ }
+
public static void setCssStyle(Component container, String value) {
container.add(new SimpleAttributeModifier("style", value));
}
@@ -324,6 +331,31 @@ public class WicketUtils { return new PageParameters(parameterMap);
}
+ public static PageParameters newDiffParameter(String repositoryName,
+ String objectId, DiffComparator diffComparator) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ if (StringUtils.isEmpty(objectId)) {
+ return newRepositoryParameter(repositoryName);
+ }
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("w", "" + diffComparator.ordinal());
+ return new PageParameters(parameterMap);
+ }
+
+ public static PageParameters newDiffParameter(String repositoryName,
+ String objectId, DiffComparator diffComparator, String blobPath) {
+ Map<String, String> parameterMap = new HashMap<String, String>();
+ if (StringUtils.isEmpty(objectId)) {
+ return newRepositoryParameter(repositoryName);
+ }
+ parameterMap.put("r", repositoryName);
+ parameterMap.put("h", objectId);
+ parameterMap.put("w", "" + diffComparator.ordinal());
+ parameterMap.put("f", blobPath);
+ return new PageParameters(parameterMap);
+ }
+
public static PageParameters newRangeParameter(String repositoryName,
String startRange, String endRange) {
Map<String, String> parameterMap = new HashMap<String, String>();
@@ -488,6 +520,11 @@ public class WicketUtils { return params.getString("st", null);
}
+ public static DiffComparator getDiffComparator(PageParameters params) {
+ int ordinal = params.getInt("w", 0);
+ return DiffComparator.values()[ordinal];
+ }
+
public static int getPage(PageParameters params) {
// index from 1
return params.getInt("pg", 1);
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm index 1512adab..43816cf7 100644 --- a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm +++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm @@ -9,7 +9,13 @@ </div>
<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
- <b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc"> </span></span></b>
+ <span style="color:{{item.c}};padding-right:2px;">
+ <span ng-show="item.y == 0" class="octicon octicon-centered octicon-repo"></span>
+ <span ng-show="item.y == 1" class="octicon octicon-centered octicon-repo-forked"></span>
+ <span ng-show="item.y == 2" class="octicon octicon-centered octicon-mirror"></span>
+ <span ng-show="item.y == 3" class="octicon octicon-centered octicon-repo-push"></span>
+ </span>
+
<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>
<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
<span ng-show="item.s" class="pull-right">
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html index 89a28b80..b998428c 100644 --- a/src/main/java/com/gitblit/wicket/pages/BasePage.html +++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html @@ -15,6 +15,7 @@ <link rel="stylesheet" href="bootstrap/css/bootstrap.css"/>
<link rel="stylesheet" href="bootstrap/css/iconic.css"/>
<link rel="stylesheet" href="fontawesome/css/font-awesome.min.css"/>
+ <link rel="stylesheet" href="octicons/octicons.css"/>
<link rel="stylesheet" type="text/css" href="gitblit.css"/>
</wicket:head>
@@ -50,5 +51,6 @@ <!-- Include scripts at end for faster page loading -->
<script type="text/javascript" src="bootstrap/js/jquery.js"></script>
<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>
+ <wicket:container wicket:id="bottomScripts"></wicket:container>
</body>
</html>
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java index b6967003..0d99f5e5 100644 --- a/src/main/java/com/gitblit/wicket/pages/BasePage.java +++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java @@ -42,6 +42,8 @@ import org.apache.wicket.markup.html.CSSPackageResource; import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.resources.JavascriptResourceReference;
+import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WebResponse;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
@@ -242,7 +244,7 @@ public abstract class BasePage extends SessionPage { protected void setupPage(String repositoryName, String pageName) {
add(new Label("title", getPageTitle(repositoryName)));
-
+ getBottomScriptContainer();
String rootLinkUrl = app().settings().getString(Keys.web.rootLink, urlFor(GitBlitWebApp.get().getHomePage(), null).toString());
ExternalLink rootLink = new ExternalLink("rootLink", rootLinkUrl);
WicketUtils.setHtmlTooltip(rootLink, app().settings().getString(Keys.web.siteName, Constants.NAME));
@@ -506,4 +508,40 @@ public abstract class BasePage extends SessionPage { return sb.toString();
}
+ private RepeatingView getBottomScriptContainer() {
+ RepeatingView bottomScriptContainer = (RepeatingView) get("bottomScripts");
+ if (bottomScriptContainer == null) {
+ bottomScriptContainer = new RepeatingView("bottomScripts");
+ bottomScriptContainer.setRenderBodyOnly(true);
+ add(bottomScriptContainer);
+ }
+ return bottomScriptContainer;
+ }
+
+ /**
+ * Adds a HTML script element loading the javascript designated by the given path.
+ *
+ * @param scriptPath
+ * page-relative path to the Javascript resource; normally starts with "scripts/"
+ */
+ protected void addBottomScript(String scriptPath) {
+ RepeatingView bottomScripts = getBottomScriptContainer();
+ Label script = new Label(bottomScripts.newChildId(), "<script type='text/javascript' src='"
+ + urlFor(new JavascriptResourceReference(this.getClass(), scriptPath)) + "'></script>\n");
+ bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
+ }
+
+ /**
+ * Adds a HTML script element containing the given code.
+ *
+ * @param code
+ * inline script code
+ */
+ protected void addBottomScriptInline(String code) {
+ RepeatingView bottomScripts = getBottomScriptContainer();
+ Label script = new Label(bottomScripts.newChildId(),
+ "<script type='text/javascript'>/*<![CDATA[*/\n" + code + "\n//]]>\n</script>\n");
+ bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
+ }
+
}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.java b/src/main/java/com/gitblit/wicket/pages/BlamePage.java index 3c850f29..e45bbbc8 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlamePage.java +++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.java @@ -154,6 +154,7 @@ public class BlamePage extends RepositoryPage { add(new Label("missingBlob").setVisible(false));
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
final Map<?, String> colorMap = initializeColors(activeBlameType, lines);
ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
@@ -212,7 +213,7 @@ public class BlamePage extends RepositoryPage { color = colorMap.get(entry.commitId);
break;
}
- Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true)).setEscapeModelStrings(false);
+ Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true, tabLength)).setEscapeModelStrings(false);
data.add(new SimpleAttributeModifier("style", "background-color: " + color + ";"));
item.add(data);
}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html index c6336429..d218436e 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html +++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html @@ -9,7 +9,7 @@ <!-- blob nav links -->
<div class="page_nav2">
- <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a>
+ <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a> | <a wicket:id="whitespaceLink"></a>
</div>
<!-- commit header -->
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java index 9cc3eae1..adf815e6 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java +++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java @@ -15,13 +15,17 @@ */
package com.gitblit.wicket.pages;
+import java.util.List;
+
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.Keys;
import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
@@ -29,6 +33,7 @@ import com.gitblit.wicket.CacheControl; import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@CacheControl(LastModified.BOOT)
@@ -39,20 +44,34 @@ public class BlobDiffPage extends RepositoryPage { final String blobPath = WicketUtils.getPath(params);
final String baseObjectId = WicketUtils.getBaseObjectId(params);
+ final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
Repository r = getRepository();
RevCommit commit = getCommit();
+ final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+
String diff;
if (StringUtils.isEmpty(baseObjectId)) {
// use first parent
- diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML).content;
+ RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0);
+ ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+ parent.getName(), commit.getName(), imageExtensions);
+ diff = DiffUtils.getDiff(r, commit, blobPath, diffComparator, DiffOutputType.HTML, handler, 3).content;
+ if (handler.getImgDiffCount() > 0) {
+ addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+ }
add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
} else {
// base commit specified
RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
- diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML).content;
+ ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+ baseCommit.getName(), commit.getName(), imageExtensions);
+ diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, diffComparator, DiffOutputType.HTML, handler, 3).content;
+ if (handler.getImgDiffCount() > 0) {
+ addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+ }
add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId,
blobPath)));
@@ -62,6 +81,8 @@ public class BlobDiffPage extends RepositoryPage { WicketUtils.newObjectParameter(repositoryName, objectId)));
add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
WicketUtils.newObjectParameter(repositoryName, objectId)));
+ add(new LinkPanel("whitespaceLink", null, getString(diffComparator.getOpposite().getTranslationKey()),
+ BlobDiffPage.class, WicketUtils.newDiffParameter(repositoryName, objectId, diffComparator.getOpposite(), blobPath)));
// diff page links
add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.html b/src/main/java/com/gitblit/wicket/pages/BlobPage.html index 3f9a959a..289c1498 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlobPage.html +++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.html @@ -40,9 +40,8 @@ </wicket:link>
</wicket:head>
+<body>
<wicket:extend>
-<!-- need to specify body.onload -->
-<body onload="prettyPrint()">
<!-- blob nav links -->
<div class="page_nav2">
@@ -61,6 +60,6 @@ <!-- blob image -->
<img wicket:id="blobImage" style="padding-top:5px;"></img>
-</body>
</wicket:extend>
+</body>
</html>
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java index 3c244f9a..1ef8f227 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlobPage.java +++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java @@ -137,6 +137,7 @@ public class BlobPage extends RepositoryPage { table = missingBlob(blobPath, commit);
} else {
table = generateSourceView(source, extension, type == 1);
+ addBottomScriptInline("jQuery(prettyPrint);");
}
add(new Label("blobText", table).setEscapeModelStrings(false));
add(new Image("blobImage").setVisible(false));
@@ -150,6 +151,7 @@ public class BlobPage extends RepositoryPage { table = missingBlob(blobPath, commit);
} else {
table = generateSourceView(source, null, false);
+ addBottomScriptInline("jQuery(prettyPrint);");
}
add(new Label("blobText", table).setEscapeModelStrings(false));
add(new Image("blobImage").setVisible(false));
@@ -193,7 +195,8 @@ public class BlobPage extends RepositoryPage { } else {
sb.append("<pre class=\"plainprint\">");
}
- lines = StringUtils.escapeForHtml(source, true).split("\n");
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+ lines = StringUtils.escapeForHtml(source, true, tabLength).split("\n");
sb.append("<table width=\"100%\"><tbody>");
diff --git a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java index a6aca22d..259a4bf4 100644 --- a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java @@ -19,7 +19,6 @@ import java.text.MessageFormat; import org.apache.wicket.RestartResponseException;
import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.StatelessForm;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
@@ -31,6 +30,7 @@ import com.gitblit.Keys; import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
public class ChangePasswordPage extends RootSubPage {
@@ -114,10 +114,10 @@ public class ChangePasswordPage extends RootSubPage { setResponsePage(RepositoriesPage.class);
}
};
- PasswordTextField passwordField = new PasswordTextField("password", password);
+ NonTrimmedPasswordTextField passwordField = new NonTrimmedPasswordTextField("password", password);
passwordField.setResetPassword(false);
form.add(passwordField);
- PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+ NonTrimmedPasswordTextField confirmPasswordField = new NonTrimmedPasswordTextField("confirmPassword",
confirmPassword);
confirmPasswordField.setResetPassword(false);
form.add(confirmPasswordField);
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html index 2c35a28c..2e0d57ce 100644 --- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html +++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html @@ -9,7 +9,7 @@ <!-- commitdiff nav links -->
<div class="page_nav2">
- <wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a>
+ <wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="whitespaceLink"></a>
</div>
<!-- commit header -->
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java index d827c449..b56d721c 100644 --- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java +++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java @@ -31,11 +31,13 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.Constants; +import com.gitblit.Keys; import com.gitblit.models.GitNote; import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.SubmoduleModel; import com.gitblit.servlet.RawServlet; import com.gitblit.utils.DiffUtils; +import com.gitblit.utils.DiffUtils.DiffComparator; import com.gitblit.utils.DiffUtils.DiffOutput; import com.gitblit.utils.DiffUtils.DiffOutputType; import com.gitblit.utils.JGitUtils; @@ -45,7 +47,7 @@ import com.gitblit.wicket.WicketUtils; import com.gitblit.wicket.panels.CommitHeaderPanel; import com.gitblit.wicket.panels.CommitLegendPanel; import com.gitblit.wicket.panels.DiffStatPanel; -import com.gitblit.wicket.panels.GravatarImage; +import com.gitblit.wicket.panels.AvatarImage; import com.gitblit.wicket.panels.LinkPanel; import com.gitblit.wicket.panels.RefsPanel; @@ -55,11 +57,9 @@ public class CommitDiffPage extends RepositoryPage { public CommitDiffPage(PageParameters params) { super(params); - Repository r = getRepository(); - - RevCommit commit = getCommit(); - - final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML); + final Repository r = getRepository(); + final RevCommit commit = getCommit(); + final DiffComparator diffComparator = WicketUtils.getDiffComparator(params); List<String> parents = new ArrayList<String>(); if (commit.getParentCount() > 0) { @@ -79,9 +79,20 @@ public class CommitDiffPage extends RepositoryPage { WicketUtils.newObjectParameter(repositoryName, objectId))); add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class, WicketUtils.newObjectParameter(repositoryName, objectId))); + add(new LinkPanel("whitespaceLink", null, getString(diffComparator.getOpposite().getTranslationKey()), + CommitDiffPage.class, WicketUtils.newDiffParameter(repositoryName, objectId, diffComparator.getOpposite()))); add(new CommitHeaderPanel("commitHeader", repositoryName, commit)); + final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions); + final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName, + parents.isEmpty() ? null : parents.get(0), commit.getName(), imageExtensions); + final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4); + final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, diffComparator, DiffOutputType.HTML, handler, tabLength); + if (handler.getImgDiffCount() > 0) { + addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs + } + // add commit diffstat int insertions = 0; int deletions = 0; @@ -105,7 +116,7 @@ public class CommitDiffPage extends RepositoryPage { item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef))); item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(), Constants.SearchType.AUTHOR)); - item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent())); + item.add(new AvatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent())); item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef .getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils())); item.add(new Label("noteContent", bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName, @@ -145,10 +156,10 @@ public class CommitDiffPage extends RepositoryPage { hasSubmodule = submodule.hasSubmodule; // add relative link - item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path)); + item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId)); } else { // add relative link - item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path)); + item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId)); } // quick links diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.java b/src/main/java/com/gitblit/wicket/pages/CommitPage.java index 072bb200..0a1a68d4 100644 --- a/src/main/java/com/gitblit/wicket/pages/CommitPage.java +++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.java @@ -44,7 +44,7 @@ import com.gitblit.wicket.panels.CommitHeaderPanel; import com.gitblit.wicket.panels.CommitLegendPanel;
import com.gitblit.wicket.panels.CompressedDownloadsPanel;
import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.RefsPanel;
@@ -133,7 +133,7 @@ public class CommitPage extends RepositoryPage { item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
Constants.SearchType.AUTHOR));
- item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
+ item.add(new AvatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
item.add(new Label("noteContent", bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName,
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.html b/src/main/java/com/gitblit/wicket/pages/ComparePage.html index d7df717c..74c98310 100644 --- a/src/main/java/com/gitblit/wicket/pages/ComparePage.html +++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.html @@ -18,6 +18,7 @@ <select wicket:id="fromRef" class="span3" />
<i class="icon-arrow-right"></i>
<select wicket:id="toRef" class="span3" />
+ <label style="padding:0px 5px;" class="checkbox"><input type="checkbox" wicket:id="ignoreWhitespaceCheckbox" /> <span wicket:id="ignoreWhitespaceLabel"></span></label>
<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
</form>
</div>
@@ -26,6 +27,7 @@ <input wicket:id="fromId" type="text" class="span3" />
<i class="icon-arrow-right"></i>
<input wicket:id="toId" type="text" class="span3" />
+ <label style="padding:0px 5px;" class="checkbox"><input type="checkbox" wicket:id="ignoreWhitespaceCheckbox" /> <span wicket:id="ignoreWhitespaceLabel"></span></label>
<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
</form>
</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.java b/src/main/java/com/gitblit/wicket/pages/ComparePage.java index 1ec66133..7e7ac2f5 100644 --- a/src/main/java/com/gitblit/wicket/pages/ComparePage.java +++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.java @@ -21,6 +21,7 @@ import java.util.List; import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.BookmarkablePageLink; @@ -37,12 +38,14 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import com.gitblit.Keys; import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.SubmoduleModel; import com.gitblit.servlet.RawServlet; import com.gitblit.utils.DiffUtils; +import com.gitblit.utils.DiffUtils.DiffComparator; import com.gitblit.utils.DiffUtils.DiffOutput; import com.gitblit.utils.DiffUtils.DiffOutputType; import com.gitblit.utils.JGitUtils; @@ -68,6 +71,8 @@ public class ComparePage extends RepositoryPage { IModel<String> fromRefId = new Model<String>(""); IModel<String> toRefId = new Model<String>(""); + IModel<Boolean> ignoreWhitespace = Model.of(true); + public ComparePage(PageParameters params) { super(params); Repository r = getRepository(); @@ -111,7 +116,15 @@ public class ComparePage extends RepositoryPage { fromCommitId.setObject(startId); toCommitId.setObject(endId); - final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML); + final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions); + final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName, + fromCommit.getName(), toCommit.getName(), imageExtensions); + final DiffComparator diffComparator = WicketUtils.getDiffComparator(params); + final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4); + final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, diffComparator, DiffOutputType.HTML, handler, tabLength); + if (handler.getImgDiffCount() > 0) { + addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs + } // add compare diffstat int insertions = 0; @@ -160,10 +173,10 @@ public class ComparePage extends RepositoryPage { hasSubmodule = submodule.hasSubmodule; // add relative link - item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path)); + item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId)); } else { // add relative link - item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path)); + item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId)); } // quick links @@ -204,6 +217,10 @@ public class ComparePage extends RepositoryPage { comparison.add(new Label("diffText", diff.content).setEscapeModelStrings(false)); } + // set the default DiffComparator + DiffComparator diffComparator = WicketUtils.getDiffComparator(params); + ignoreWhitespace.setObject(DiffComparator.IGNORE_WHITESPACE == diffComparator); + // // ref selection form // @@ -215,8 +232,13 @@ public class ComparePage extends RepositoryPage { public void onSubmit() { String from = ComparePage.this.fromRefId.getObject(); String to = ComparePage.this.toRefId.getObject(); + boolean ignoreWS = ignoreWhitespace.getObject(); PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to); + if (ignoreWS) { + params.put("w", 1); + } + String relativeUrl = urlFor(ComparePage.class, params).toString(); String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl); getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl)); @@ -237,6 +259,8 @@ public class ComparePage extends RepositoryPage { } refsForm.add(new DropDownChoice<String>("fromRef", fromRefId, refs).setEnabled(refs.size() > 0)); refsForm.add(new DropDownChoice<String>("toRef", toRefId, refs).setEnabled(refs.size() > 0)); + refsForm.add(new Label("ignoreWhitespaceLabel", getString(DiffComparator.IGNORE_WHITESPACE.getTranslationKey()))); + refsForm.add(new CheckBox("ignoreWhitespaceCheckbox", ignoreWhitespace)); add(refsForm); // @@ -250,8 +274,12 @@ public class ComparePage extends RepositoryPage { public void onSubmit() { String from = ComparePage.this.fromCommitId.getObject(); String to = ComparePage.this.toCommitId.getObject(); + boolean ignoreWS = ignoreWhitespace.getObject(); PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to); + if (ignoreWS) { + params.put("w", 1); + } String relativeUrl = urlFor(ComparePage.class, params).toString(); String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl); getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl)); @@ -265,6 +293,8 @@ public class ComparePage extends RepositoryPage { TextField<String> toIdField = new TextField<String>("toId", toCommitId); WicketUtils.setInputPlaceholder(toIdField, getString("gb.to") + "..."); idsForm.add(toIdField); + idsForm.add(new Label("ignoreWhitespaceLabel", getString(DiffComparator.IGNORE_WHITESPACE.getTranslationKey()))); + idsForm.add(new CheckBox("ignoreWhitespaceCheckbox", ignoreWhitespace)); add(idsForm); r.close(); diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html index 1e683b4f..7a55b9f5 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -20,6 +20,7 @@ <li><a href="#search" data-toggle="tab"><wicket:message key="gb.search"></wicket:message></a></li>
<li><a href="#gc" data-toggle="tab"><wicket:message key="gb.gc"></wicket:message></a></li>
<li><a href="#misc" data-toggle="tab"><wicket:message key="gb.miscellaneous"></wicket:message></a></li>
+ <li><a href="#admin" data-toggle="tab"><wicket:message key="gb.administration"></wicket:message></a></li>
</ul>
<!-- tab content -->
@@ -178,8 +179,16 @@ <div wicket:id="mailingLists"></div>
</div>
-
- <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /> <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" /></div>
+
+ <!-- administration -->
+ <div class="tab-pane" id="admin">
+
+ <h4><wicket:message key="gb.deleteRepositoryHeader"></wicket:message></h4>
+ <p><wicket:message key="gb.deleteRepositoryDescription"></wicket:message></p>
+ <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" />
+ </div>
+
+ <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div>
</div>
</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java index f537f33d..a43d8db0 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java @@ -39,6 +39,7 @@ import org.apache.wicket.model.util.CollectionModel; import org.apache.wicket.model.util.ListModel;
import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Role;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
@@ -221,14 +222,23 @@ public class EditTeamPage extends RootSubPage { // do not let the browser pre-populate these fields
form.add(new SimpleAttributeModifier("autocomplete", "off"));
- // not all user services support manipulating team memberships
+ // not all user providers support manipulating team memberships
boolean editMemberships = app().authentication().supportsTeamMembershipChanges(teamModel);
+ // not all user providers support manipulating the admin role
+ boolean changeAdminRole = app().authentication().supportsRoleChanges(teamModel, Role.ADMIN);
+
+ // not all user providers support manipulating the create role
+ boolean changeCreateRole = app().authentication().supportsRoleChanges(teamModel, Role.CREATE);
+
+ // not all user providers support manipulating the fork role
+ boolean changeForkRole = app().authentication().supportsRoleChanges(teamModel, Role.FORK);
+
// field names reflective match TeamModel fields
form.add(new TextField<String>("name"));
- form.add(new CheckBox("canAdmin"));
- form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true)));
- form.add(new CheckBox("canCreate"));
+ form.add(new CheckBox("canAdmin").setEnabled(changeAdminRole));
+ form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true) && changeForkRole));
+ form.add(new CheckBox("canCreate").setEnabled(changeCreateRole));
form.add(users.setEnabled(editMemberships));
mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
: StringUtils.flattenStrings(teamModel.mailingLists, " "));
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html index b5fe0ae5..b12d0c77 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html +++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html @@ -39,6 +39,8 @@ </div>
</td></tr>
<tr wicket:id="status"></tr>
+ <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
+ <tr wicket:id="priority"></tr>
<tr wicket:id="responsible"></tr>
<tr wicket:id="milestone"></tr>
<tr wicket:id="mergeto"></tr>
@@ -71,5 +73,9 @@ <th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="mergeto"></select></td>
</wicket:fragment>
+<wicket:fragment wicket:id="priorityFragment">
+ <th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td>
+</wicket:fragment>
+
</wicket:extend>
</html>
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java index c3d405bc..192b48ca 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java @@ -84,6 +84,10 @@ public class EditTicketPage extends RepositoryPage { private Label descriptionPreview;
+ private IModel<TicketModel.Priority> priorityModel;
+
+ private IModel<TicketModel.Severity> severityModel;
+
public EditTicketPage(PageParameters params) {
super(params);
@@ -117,6 +121,8 @@ public class EditTicketPage extends RepositoryPage { milestoneModel = Model.of();
mergeToModel = Model.of(ticket.mergeTo == null ? getRepositoryModel().mergeTo : ticket.mergeTo);
statusModel = Model.of(ticket.status);
+ priorityModel = Model.of(ticket.priority);
+ severityModel = Model.of(ticket.severity);
setStatelessHint(false);
setOutputMarkupId(true);
@@ -161,6 +167,9 @@ public class EditTicketPage extends RepositoryPage { status.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices));
form.add(status);
+ List<TicketModel.Severity> severityChoices = Arrays.asList(TicketModel.Severity.choices());
+ form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, severityChoices));
+
if (currentUser.canAdmin(ticket, getRepositoryModel())) {
// responsible
Set<String> userlist = new TreeSet<String>(ticket.getParticipants());
@@ -214,11 +223,17 @@ public class EditTicketPage extends RepositoryPage { milestones.add(new TicketMilestone(NIL));
}
+ // milestone
Fragment milestone = new Fragment("milestone", "milestoneFragment", this);
-
milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
form.add(milestone.setVisible(!milestones.isEmpty()));
+ // priority
+ Fragment priority = new Fragment("priority", "priorityFragment", this);
+ List<TicketModel.Priority> priorityChoices = Arrays.asList(TicketModel.Priority.choices());
+ priority.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, priorityChoices));
+ form.add(priority);
+
// mergeTo (integration branch)
List<String> branches = new ArrayList<String>();
for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -238,6 +253,7 @@ public class EditTicketPage extends RepositoryPage { form.add(new Label("responsible").setVisible(false));
form.add(new Label("milestone").setVisible(false));
form.add(new Label("mergeto").setVisible(false));
+ form.add(new Label("priority").setVisible(false));
}
form.add(new AjaxButton("update") {
@@ -316,6 +332,18 @@ public class EditTicketPage extends RepositoryPage { }
}
+ TicketModel.Priority priority = priorityModel.getObject();
+ if (!ticket.priority.equals(priority))
+ {
+ change.setField(Field.priority, priority);
+ }
+
+ TicketModel.Severity severity = severityModel.getObject();
+ if (!ticket.severity.equals(severity))
+ {
+ change.setField(Field.severity, severity);
+ }
+
String mergeTo = mergeToModel.getObject();
if ((StringUtils.isEmpty(ticket.mergeTo) && !StringUtils.isEmpty(mergeTo))
|| (!StringUtils.isEmpty(mergeTo) && !mergeTo.equals(ticket.mergeTo))) {
diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java index 454aa619..220bee3f 100644 --- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java @@ -27,7 +27,6 @@ import org.apache.wicket.extensions.markup.html.form.palette.Palette; import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.Model;
@@ -35,12 +34,14 @@ import org.apache.wicket.model.util.CollectionModel; import org.apache.wicket.model.util.ListModel;
import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Role;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
import com.gitblit.wicket.RequiresAdminRole;
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
@@ -177,7 +178,9 @@ public class EditUserPage extends RootSubPage { // update user permissions
for (RegistrantAccessPermission repositoryPermission : permissions) {
- userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+ if (repositoryPermission.mutable) {
+ userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+ }
}
Iterator<String> selectedTeams = teams.getSelectedChoices();
@@ -216,24 +219,33 @@ public class EditUserPage extends RootSubPage { // do not let the browser pre-populate these fields
form.add(new SimpleAttributeModifier("autocomplete", "off"));
- // not all user services support manipulating username and password
+ // not all user providers support manipulating username and password
boolean editCredentials = app().authentication().supportsCredentialChanges(userModel);
- // not all user services support manipulating display name
+ // not all user providers support manipulating display name
boolean editDisplayName = app().authentication().supportsDisplayNameChanges(userModel);
- // not all user services support manipulating email address
+ // not all user providers support manipulating email address
boolean editEmailAddress = app().authentication().supportsEmailAddressChanges(userModel);
- // not all user services support manipulating team memberships
+ // not all user providers support manipulating team memberships
boolean editTeams = app().authentication().supportsTeamMembershipChanges(userModel);
+ // not all user providers support manipulating the admin role
+ boolean changeAdminRole = app().authentication().supportsRoleChanges(userModel, Role.ADMIN);
+
+ // not all user providers support manipulating the create role
+ boolean changeCreateRole = app().authentication().supportsRoleChanges(userModel, Role.CREATE);
+
+ // not all user providers support manipulating the fork role
+ boolean changeForkRole = app().authentication().supportsRoleChanges(userModel, Role.FORK);
+
// field names reflective match UserModel fields
form.add(new TextField<String>("username").setEnabled(editCredentials));
- PasswordTextField passwordField = new PasswordTextField("password");
+ NonTrimmedPasswordTextField passwordField = new NonTrimmedPasswordTextField("password");
passwordField.setResetPassword(false);
form.add(passwordField.setEnabled(editCredentials));
- PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+ NonTrimmedPasswordTextField confirmPasswordField = new NonTrimmedPasswordTextField("confirmPassword",
confirmPassword);
confirmPasswordField.setResetPassword(false);
form.add(confirmPasswordField.setEnabled(editCredentials));
@@ -245,7 +257,7 @@ public class EditUserPage extends RootSubPage { // display a disabled-yet-checked checkbox
form.add(new CheckBox("canAdmin", Model.of(true)).setEnabled(false));
} else {
- form.add(new CheckBox("canAdmin"));
+ form.add(new CheckBox("canAdmin").setEnabled(changeAdminRole));
}
if (userModel.canFork() && !userModel.canFork) {
@@ -254,7 +266,7 @@ public class EditUserPage extends RootSubPage { form.add(new CheckBox("canFork", Model.of(true)).setEnabled(false));
} else {
final boolean forkingAllowed = app().settings().getBoolean(Keys.web.allowForking, true);
- form.add(new CheckBox("canFork").setEnabled(forkingAllowed));
+ form.add(new CheckBox("canFork").setEnabled(forkingAllowed && changeForkRole));
}
if (userModel.canCreate() && !userModel.canCreate) {
@@ -262,7 +274,7 @@ public class EditUserPage extends RootSubPage { // display a disabled-yet-checked checkbox
form.add(new CheckBox("canCreate", Model.of(true)).setEnabled(false));
} else {
- form.add(new CheckBox("canCreate"));
+ form.add(new CheckBox("canCreate").setEnabled(changeCreateRole));
}
form.add(new CheckBox("excludeFromFederation"));
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java index b3c52436..72d1e1a4 100644 --- a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java @@ -55,7 +55,7 @@ public class EmptyRepositoryPage extends RepositoryPage { }
HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
- List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
+ List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
RepositoryUrl primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
String url = primaryUrl != null ? primaryUrl.url : "";
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestorePage.html b/src/main/java/com/gitblit/wicket/pages/FilestorePage.html new file mode 100644 index 00000000..e373e704 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/FilestorePage.html @@ -0,0 +1,37 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" + xml:lang="en" + lang="en"> + +<body> +<wicket:extend> +<div class="container"> + + <div class="markdown" style="padding: 10px 0px 5px 0px;"> + <span wicket:id="repositoriesMessage">[repositories message]</span> + <span style="float:right"><a href="#" wicket:id="filestoreHelp"><span wicket:id="helpMessage">[help message]</span></a></span> + </div> + + <table class="repositories"> + <tr> + <th><wicket:message key="gb.status">[Object status]</wicket:message></th> + <th><wicket:message key="gb.statusChangedOn">[changedOn]</wicket:message></th> + <th><wicket:message key="gb.statusChangedBy">[changedBy]</wicket:message></th> + <th><wicket:message key="gb.oid">[Object ID]</wicket:message></th> + <th><wicket:message key="gb.size">[file size]</wicket:message></th> + </tr> + <tbody> + <tr wicket:id="fileRow"> + <td><center><span class="list" wicket:id="status">[Object state]</span></center></td> + <td><span class="list" wicket:id="on">[changedOn]</span></td> + <td><span class="list" wicket:id="by">[changedBy]</span></td> + <td class="sha256"><span class="list" wicket:id="oid">[Object ID]</span></td> + <td><span class="list" wicket:id="size">[file size]</span></td> + </tr> + </tbody> + </table> +</div> +</wicket:extend> +</body> +</html>
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/FilestorePage.java b/src/main/java/com/gitblit/wicket/pages/FilestorePage.java new file mode 100644 index 00000000..97d5f25b --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/FilestorePage.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.pages; + +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.wicket.Component; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.DataView; +import org.apache.wicket.markup.repeater.data.ListDataProvider; + +import com.gitblit.Constants; +import com.gitblit.models.FilestoreModel; +import com.gitblit.models.UserModel; +import com.gitblit.wicket.FilestoreUI; +import com.gitblit.wicket.RequiresAdminRole; +import com.gitblit.wicket.WicketUtils; + +/** + * Page to display the current status of the filestore. + * Certain errors also displayed to aid in fault finding + * + * @author Paul Martin + */ +@RequiresAdminRole +public class FilestorePage extends RootPage { + + public FilestorePage() { + super(); + setupPage("", ""); + + final List<FilestoreModel> files = app().filestore().getAllObjects(); + final long nBytesUsed = app().filestore().getFilestoreUsedByteCount(); + final long nBytesAvailable = app().filestore().getFilestoreAvailableByteCount(); + + String message = MessageFormat.format(getString("gb.filestoreStats"), files.size(), + FileUtils.byteCountToDisplaySize(nBytesUsed), FileUtils.byteCountToDisplaySize(nBytesAvailable) ); + + Component repositoriesMessage = new Label("repositoriesMessage", message) + .setEscapeModelStrings(false).setVisible(message.length() > 0); + + add(repositoriesMessage); + + BookmarkablePageLink<Void> helpLink = new BookmarkablePageLink<Void>("filestoreHelp", FilestoreUsage.class); + helpLink.add(new Label("helpMessage", getString("gb.filestoreHelp"))); + add(helpLink); + + + DataView<FilestoreModel> filesView = new DataView<FilestoreModel>("fileRow", + new ListDataProvider<FilestoreModel>(files)) { + private static final long serialVersionUID = 1L; + private int counter; + + @Override + protected void onBeforeRender() { + super.onBeforeRender(); + counter = 0; + } + + @Override + public void populateItem(final Item<FilestoreModel> item) { + final FilestoreModel entry = item.getModelObject(); + + DateFormat dateFormater = new SimpleDateFormat(Constants.ISO8601); + + UserModel user = app().users().getUserModel(entry.getChangedBy()); + user = user == null ? UserModel.ANONYMOUS : user; + + Label icon = FilestoreUI.getStatusIcon("status", entry); + item.add(icon); + item.add(new Label("on", dateFormater.format(entry.getChangedOn()))); + item.add(new Label("by", user.getDisplayName())); + + item.add(new Label("oid", entry.oid)); + item.add(new Label("size", FileUtils.byteCountToDisplaySize(entry.getSize()))); + + WicketUtils.setAlternatingBackground(item, counter); + counter++; + } + + }; + + add(filesView); + } +} diff --git a/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html new file mode 100644 index 00000000..e9bff47c --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html @@ -0,0 +1,69 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" + xml:lang="en" + lang="en"> + +<body> +<wicket:extend> +<div class="container"> +<div class="markdown"> +<div class="row"> +<div class="span10 offset1"> + + <div class="alert alert-danger"> + <h3><center>Using the Filestore</center></h3> + <p> + <strong>All clients intending to use the filestore must first install the <a href="https://git-lfs.github.com/">Git-LFS Client</a> and then run <code>git lfs init</code> to register the hooks globally.</strong><br/> + <i>This version of GitBlit has been verified with Git-LFS client version 0.6.0 which requires Git v1.8.2 or higher.</i> + </p> + </div> + + <h3>Clone</h3> + <p> + Just <code>git clone</code> as usual, no further action is required as GitBlit is configured to use the default Git-LFS end point <code>{repository}/info/lfs/objects/</code>.<br/> + <i>If the repository uses a 3rd party Git-LFS server you will need to <a href="https://github.com/github/git-lfs/blob/master/docs/spec.md#the-server">manually configure the correct endpoints</a></i>. + </p> + + <h3>Add</h3> + <p>After configuring the file types or paths to be tracked using <code>git lfs track "*.bin"</code> just add files as usual with <code>git add</code> command.<br/> + <i>Tracked files can also be configured manually using the <code>.gitattributes</code> file</i>.</p> + + <h3>Remove</h3> + <p>When you remove a Git-LFS tracked file only the pointer file will be removed from your repository.<br/> + <i>All files remain on the server to allow previous versions to be checked out.</i> + </p> + + <h3>Learn more...</h3> + <p><a href="https://github.com/github/git-lfs/blob/master/docs/spec.md">See the current Git-LFS specification for further details</a>.</p> + <br /> + + <div class="alert alert-warn"> + <h3><center>Limitations & Warnings</center></h3> + <p>GitBlit currently provides a server-only implementation of the opensource Git-LFS API, <a href="https://github.com/github/git-lfs/wiki/Implementations">other implementations</a> are available.<br/> + However, until <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=470333">JGit provides Git-LFS client capabilities</a> some GitBlit features may not be fully supported when using the filestore. + Notably: + <ul> + <li>Mirroring a repository that uses Git-LFS - Only the pointer files, not the large files, are mirrored.</li> + <li>Federation - Only the pointer files, not the large files, are transfered.</li> + </ul> + </p> + </div> + + <div class="alert alert-info"> + <h3><center>GitBlit Configuration</center></h3> + <p>GitBlit provides the following configuration items when using the filestore: + <h4>filestore.storageFolder</h4> + <p>Defines the path on the server where filestore objects are to be saved. This defaults to <code>${baseFolder}/lfs</code></p> + <h4>filestore.maxUploadSize</h4> + <p>Defines the maximum allowable size that can be uploaded to the filestore. Once a file is uploaded it will be unaffected by later changes in this property. This defaults to <code>-1</code> indicating no limits.</p> + </p> + </div> + +</div> +</div> +</div> +</div> +</wicket:extend> +</body> +</html> diff --git a/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java new file mode 100644 index 00000000..9bd8e55d --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.pages; + +public class FilestoreUsage extends RootSubPage { + + public FilestoreUsage() { + super(); + setupPage("", ""); + } + +} diff --git a/src/main/java/com/gitblit/wicket/pages/ForksPage.java b/src/main/java/com/gitblit/wicket/pages/ForksPage.java index 9fd7f4db..045f5f7e 100644 --- a/src/main/java/com/gitblit/wicket/pages/ForksPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ForksPage.java @@ -34,7 +34,7 @@ import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
public class ForksPage extends RepositoryPage {
@@ -63,7 +63,7 @@ public class ForksPage extends RepositoryPage { user = new UserModel(repository.projectPath.substring(1));
}
PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
- item.add(new GravatarImage("anAvatar", ident, 20));
+ item.add(new AvatarImage("anAvatar", ident, 20));
if (pageRepository.equals(repository)) {
// do not link to self
item.add(new Label("aProject", user.getDisplayName()));
@@ -136,6 +136,9 @@ public class ForksPage extends RepositoryPage { protected List<FlatFork> flatten(ForkModel node, int level) {
List<FlatFork> list = new ArrayList<FlatFork>();
+ if (node == null) {
+ return list;
+ }
list.add(new FlatFork(node.repository, level));
if (!node.isLeaf()) {
for (ForkModel fork : node.forks) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java new file mode 100644 index 00000000..dc0c5ae8 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java @@ -0,0 +1,170 @@ +/* + * Copyright 2014 Tom <tw201207@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.pages; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.protocol.http.WicketURLEncoder; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffEntry.Side; +import org.jsoup.nodes.Element; + +import com.gitblit.servlet.RawServlet; +import com.gitblit.utils.DiffUtils; +import com.gitblit.utils.HtmlBuilder; + +/** + * A {@link DiffUtils.BinaryDiffHandler BinaryDiffHandler} for images. + * + * @author Tom <tw201207@gmail.com> + */ +public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler { + + private final String oldCommitId; + private final String newCommitId; + private final String repositoryName; + private final BasePage page; + private final List<String> imageExtensions; + + private int imgDiffCount = 0; + + public ImageDiffHandler(final BasePage page, final String repositoryName, final String oldCommitId, final String newCommitId, + final List<String> imageExtensions) { + this.page = page; + this.repositoryName = repositoryName; + this.oldCommitId = oldCommitId; + this.newCommitId = newCommitId; + this.imageExtensions = imageExtensions; + } + + /** {@inheritDoc} */ + @Override + public String renderBinaryDiff(DiffEntry diffEntry) { + switch (diffEntry.getChangeType()) { + case MODIFY: + case RENAME: + case COPY: + // TODO: for very small images such as icons, the slider doesn't really help. Two possible + // approaches: either upscale them for display (may show blurry upscaled images), or show + // them side by side (may still be too small to really make out the differences). + String oldUrl = getImageUrl(diffEntry, Side.OLD); + String newUrl = getImageUrl(diffEntry, Side.NEW); + if (oldUrl != null && newUrl != null) { + imgDiffCount++; + String id = "imgdiff" + imgDiffCount; + HtmlBuilder builder = new HtmlBuilder("div"); + Element wrapper = builder.root().attr("class", "imgdiff-container").attr("id", "imgdiff-" + id); + Element container = wrapper.appendElement("div").attr("class", "imgdiff-ovr-slider").appendElement("div").attr("class", "imgdiff"); + Element old = container.appendElement("div").attr("class", "imgdiff-left"); + // style='max-width:640px;' is necessary for ensuring that the browser limits large images + // to some reasonable width, and to override the "img { max-width: 100%; }" from bootstrap.css, + // which would scale the left image to the width of its resizeable container, which isn't what + // we want here. Note that the max-width must be defined directly as inline style on the element, + // otherwise browsers ignore it if the image is larger, and we end up with an image display that + // is too wide. + // XXX: Maybe add a max-height, too, to limit portrait-oriented images to some reasonable height? + // (Like a 300x10000px image...) + old.appendElement("img").attr("class", "imgdiff-old").attr("id", id).attr("style", "max-width:640px;").attr("src", oldUrl); + container.appendElement("img").attr("class", "imgdiff").attr("style", "max-width:640px;").attr("src", newUrl); + wrapper.appendElement("br"); + Element controls = wrapper.appendElement("div"); + // Opacity slider + controls.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("a").attr("class", "imgdiff-opa-slider") + .attr("href", "#").attr("title", page.getString("gb.opacityAdjust")); + // Blink comparator: find Pluto! + controls.appendElement("a").attr("class", "imgdiff-link imgdiff-blink").attr("href", "#") + .attr("title", page.getString("gb.blinkComparator")) + .appendElement("img").attr("src", getStaticResourceUrl("blink32.png")).attr("width", "20"); + // Pixel subtraction, initially not displayed, will be shown by imgdiff.js depending on feature test. + // (Uses CSS mix-blend-mode, which isn't supported on all browsers yet). + controls.appendElement("a").attr("class", "imgdiff-link imgdiff-subtract").attr("href", "#") + .attr("title", page.getString("gb.imgdiffSubtract")).attr("style", "display:none;") + .appendElement("img").attr("src", getStaticResourceUrl("sub32.png")).attr("width", "20"); + return builder.toString(); + } + break; + case ADD: + String url = getImageUrl(diffEntry, Side.NEW); + if (url != null) { + return new HtmlBuilder("img").root().attr("class", "diff-img").attr("src", url).toString(); + } + break; + default: + break; + } + return null; + } + + /** Returns the number of image diffs generated so far by this {@link ImageDiffHandler}. */ + public int getImgDiffCount() { + return imgDiffCount; + } + + /** + * Constructs a URL that will fetch the designated resource in the git repository. The returned string will + * contain the URL fully URL-escaped, but note that it may still contain unescaped ampersands, so the result + * must still be run through HTML escaping if it is to be used in HTML. + * + * @return the URL to the image, if the given {@link DiffEntry} and {@link Side} refers to an image, or {@code null} otherwise. + */ + protected String getImageUrl(DiffEntry entry, Side side) { + String path = entry.getPath(side); + int i = path.lastIndexOf('.'); + if (i > 0) { + String extension = path.substring(i + 1); + for (String ext : imageExtensions) { + if (ext.equalsIgnoreCase(extension)) { + String commitId = Side.NEW.equals(side) ? newCommitId : oldCommitId; + if (commitId != null) { + return RawServlet.asLink(page.getContextUrl(), urlencode(repositoryName), commitId, urlencode(path)); + } else { + return null; + } + } + } + } + return null; + } + + /** + * Returns a URL that will fetch the designated static resource from within GitBlit. + */ + protected String getStaticResourceUrl(String contextRelativePath) { + return WebApplication.get().getRequestCycleProcessor().getRequestCodingStrategy().rewriteStaticRelativeUrl(contextRelativePath); + } + + /** + * Encode a URL component of a {@link RawServlet} URL in the special way that the servlet expects it. Note that + * the %-encoding used does not encode '&' or '<'. Slashes are not encoded in the result. + * + * @param component + * to encode using %-encoding + * @return the encoded component + */ + protected String urlencode(final String component) { + // RawServlet handles slashes itself. Note that only the PATH_INSTANCE fits the bill here: it encodes + // spaces as %20, and we just have to correct for encoded slashes. Java's standard URLEncoder would + // encode spaces as '+', and I don't know what effects that would have on other parts of GitBlit. It + // would also be wrong for path components (but fine for a query part), so we'd have to correct it, too. + // + // Actually, this should be done in RawServlet.asLink(). As it is now, this may be incorrect if that + // operation ever uses query parameters instead of paths, or if it is fixed to urlencode its path + // components. But I don't want to touch that static method in RawServlet. + return WicketURLEncoder.PATH_INSTANCE.encode(component, StandardCharsets.UTF_8.name()).replaceAll("%2[fF]", "/"); + } +} diff --git a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html index d4077830..ab3c0dae 100644 --- a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html +++ b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html @@ -14,7 +14,7 @@ <span class="icon-bar"></span> </a> <a class="brand" wicket:id="rootLink"> - <img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/> + <img src="gitblt_25_white.png" width="79" alt="gitblit" class="logo"/> </a> </div> diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html index 91a6ef43..d62b7b22 100644 --- a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html +++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html @@ -24,6 +24,7 @@ <div class="span3">
<h3><wicket:message key="gb.repositories"></wicket:message></h3>
<select wicket:id="repositories" ></select>
+ <label><input type="checkbox" wicket:id="allrepos" /> <span><wicket:message key="gb.allRepositories"></wicket:message></span></label> </div>
<div class="span9" style="margin-left:10px">
<div>
diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java index 4d4545a5..b2fd9037 100644 --- a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java +++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java @@ -17,11 +17,14 @@ package com.gitblit.wicket.pages; import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.ListMultipleChoice;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.Fragment;
@@ -66,6 +69,15 @@ public class LuceneSearchPage extends RootPage { int page = 1;
int pageSize = app().settings().getInteger(Keys.web.itemsPerPage, 50);
+ // display user-accessible selections
+ UserModel user = GitBlitWebSession.get().getUser();
+ List<String> availableRepositories = new ArrayList<String>();
+ for (RepositoryModel model : app().repositories().getRepositoryModels(user)) {
+ if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
+ availableRepositories.add(model.name);
+ }
+ }
+
if (params != null) {
String repository = WicketUtils.getRepositoryName(params);
if (!StringUtils.isEmpty(repository)) {
@@ -79,6 +91,10 @@ public class LuceneSearchPage extends RootPage { List<String> list = StringUtils.getStringsFromValue(value);
repositories.addAll(list);
}
+ + if (params.containsKey("allrepos")) {
+ repositories.addAll(availableRepositories);
+ }
if (params.containsKey("query")) {
query = params.getString("query", "");
@@ -96,14 +112,6 @@ public class LuceneSearchPage extends RootPage { }
}
- // display user-accessible selections
- UserModel user = GitBlitWebSession.get().getUser();
- List<String> availableRepositories = new ArrayList<String>();
- for (RepositoryModel model : app().repositories().getRepositoryModels(user)) {
- if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
- availableRepositories.add(model.name);
- }
- }
boolean luceneEnabled = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
if (luceneEnabled) {
if (availableRepositories.size() == 0) {
@@ -114,16 +122,18 @@ public class LuceneSearchPage extends RootPage { }
// enforce user-accessible repository selections
- ArrayList<String> searchRepositories = new ArrayList<String>();
+ Set<String> uniqueRepositories = new LinkedHashSet<String>();
for (String selectedRepository : repositories) {
if (availableRepositories.contains(selectedRepository)) {
- searchRepositories.add(selectedRepository);
+ uniqueRepositories.add(selectedRepository);
}
}
+ ArrayList<String> searchRepositories = new ArrayList<String>(uniqueRepositories);
// search form
final Model<String> queryModel = new Model<String>(query);
final Model<ArrayList<String>> repositoriesModel = new Model<ArrayList<String>>(searchRepositories);
+ final Model<Boolean> allreposModel = new Model<Boolean>(params != null && params.containsKey("allrepos"));
SessionlessForm<Void> form = new SessionlessForm<Void>("searchForm", getClass()) {
private static final long serialVersionUID = 1L;
@@ -135,13 +145,14 @@ public class LuceneSearchPage extends RootPage { error(getString("gb.undefinedQueryWarning"));
return;
}
- if (repositoriesModel.getObject().size() == 0) {
+ if (repositoriesModel.getObject().size() == 0 && !allreposModel.getObject()) {
error(getString("gb.noSelectedRepositoriesWarning"));
return;
}
PageParameters params = new PageParameters();
params.put("repositories", StringUtils.flattenStrings(repositoriesModel.getObject()));
params.put("query", queryModel.getObject());
+ params.put("allrepos", allreposModel.getObject());
LuceneSearchPage page = new LuceneSearchPage(params);
setResponsePage(page);
}
@@ -152,6 +163,7 @@ public class LuceneSearchPage extends RootPage { selections.setMaxRows(8);
form.add(selections.setEnabled(luceneEnabled));
form.add(new TextField<String>("query", queryModel).setEnabled(luceneEnabled));
+ form.add(new CheckBox("allrepos", allreposModel));
add(form.setEnabled(luceneEnabled));
// execute search
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html index b0bc1949..70869a1e 100644 --- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html +++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html @@ -25,18 +25,19 @@ <!-- query list --> <ul class="nav nav-list"> <li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li> - <li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li> - <li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li> - <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li> - <li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li> - <li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li> + <li><a wicket:id="changesQuery"><i class="fa fa-code-fork fa-fw"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li> + <li><a wicket:id="bugsQuery"><i class="fa fa-bug fa-fw"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li> + <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic fa-fw"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li> + <li><a wicket:id="tasksQuery"><i class="fa fa-ticket fa-fw"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li> + <li><a wicket:id="questionsQuery"><i class="fa fa-question fa-fw"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li> + <li><a wicket:id="maintenanceQuery"><i class="fa fa-cogs fa-fw"></i> <wicket:message key="gb.maintenanceTickets"></wicket:message></a></li> <li wicket:id="userDivider" class="divider"></li> - <li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li> - <li><a wicket:id="responsibleQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li> - <li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li> - <li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li> + <li><a wicket:id="createdQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li> + <li><a wicket:id="responsibleQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li> + <li><a wicket:id="watchedQuery"><i class="fa fa-eye fa-fw"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li> + <li><a wicket:id="mentionsQuery"><i class="fa fa-comment fa-fw"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li> <li class="divider"></li> - <li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li> + <li><a wicket:id="resetQuery"><i class="fa fa-bolt fa-fw"></i> <wicket:message key="gb.reset"></wicket:message></a></li> </ul> </div> @@ -62,6 +63,13 @@ </ul> </div> + <div class="btn-group"> + <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> <wicket:message key="gb.repository"></wicket:message>: <span style="font-weight:bold;" wicket:id="currentRepository"></span> <span class="caret"></span></a> + <ul class="dropdown-menu"> + <li wicket:id="repository"><span wicket:id="repositoryLink"></span></li> + </ul> + </div> + <div class="btn-group pull-right"> <div class="pagination pagination-right pagination-small"> <ul> @@ -81,4 +89,4 @@ </wicket:extend> </body> -</html>
\ No newline at end of file +</html> diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java index c207d561..591c7fef 100644 --- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java +++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java @@ -17,6 +17,10 @@ package com.gitblit.wicket.pages; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; import org.apache.wicket.PageParameters; @@ -27,6 +31,7 @@ import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.markup.repeater.data.ListDataProvider; import com.gitblit.Keys; +import com.gitblit.models.RepositoryModel; import com.gitblit.models.TicketModel; import com.gitblit.models.TicketModel.Status; import com.gitblit.models.UserModel; @@ -52,341 +57,437 @@ import com.gitblit.wicket.panels.UserTitlePanel; */ public class MyTicketsPage extends RootPage { - public MyTicketsPage() { - this(null); - } - - public MyTicketsPage(PageParameters params) { - super(params); - setupPage("", getString("gb.myTickets")); - - UserModel currentUser = GitBlitWebSession.get().getUser(); - if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) { - setRedirect(true); - setResponsePage(getApplication().getHomePage()); - return; - } - - final String username = currentUser.getName(); - final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name()); - final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null); - final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null); - final String queryParam = (params == null || StringUtils.isEmpty(params.getString("q", null))) ? "watchedby:" + username : params.getString("q", null); - final String searchParam = (params == null) ? "" : params.getString("s", null); - final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name(); - final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc")); - - // add the user title panel - add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets"))); - - // add search form - add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params)); - - // standard queries - add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class, - queryParameters( - Lucene.type.matches(TicketModel.Type.Proposal.name()), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class, - queryParameters( - Lucene.type.matches(TicketModel.Type.Bug.name()), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class, - queryParameters( - Lucene.type.matches(TicketModel.Type.Enhancement.name()), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class, - queryParameters( - Lucene.type.matches(TicketModel.Type.Task.name()), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class, - queryParameters( - Lucene.type.matches(TicketModel.Type.Question.name()), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class, - queryParameters( - null, - milestoneParam, - TicketsUI.openStatii, - null, - null, - true, - 1))); - - add(new Label("userDivider")); - add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class, - queryParameters( - Lucene.createdby.matches(username), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class, - queryParameters( - Lucene.watchedby.matches(username), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class, - queryParameters( - Lucene.mentions.matches(username), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class, - queryParameters( - Lucene.responsible.matches(username), - milestoneParam, - statiiParam, - assignedToParam, - sortBy, - desc, - 1))); - - // states - if (ArrayUtils.isEmpty(statiiParam)) { - add(new Label("selectedStatii", getString("gb.all"))); - } else { - add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ","))); - } - add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1))); - add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1))); - add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1))); - - // by status - List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values())); - statii.remove(Status.Closed); - ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii); - DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(final Item<Status> item) { - final Status status = item.getModelObject(); - PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1); - String css = TicketsUI.getStatusClass(status); - item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true)); - } - }; - add(statiiLinks); - - List<TicketSort> sortChoices = new ArrayList<TicketSort>(); - sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true)); - sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false)); - sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true)); - sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false)); - sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true)); - sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false)); - sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true)); - sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false)); - sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true)); - sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false)); - - TicketSort currentSort = sortChoices.get(0); - for (TicketSort ts : sortChoices) { - if (ts.sortBy.equals(sortBy) && desc == ts.desc) { - currentSort = ts; - break; - } - } - add(new Label("currentSort", currentSort.name)); - - ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices); - DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(final Item<TicketSort> item) { - final TicketSort ts = item.getModelObject(); - PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, 1); - item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true)); - } - }; - add(sortMenu); - - // Build Query here - QueryBuilder qb = new QueryBuilder(queryParam); - if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) { - // specify the states - boolean not = false; - QueryBuilder q = new QueryBuilder(); - for (String state : statiiParam) { - if (state.charAt(0) == '!') { - not = true; - q.and(Lucene.status.doesNotMatch(state.substring(1))); - } else { - q.or(Lucene.status.matches(state)); - } - } - if (not) { - qb.and(q.toString()); - } else { - qb.and(q.toSubquery().toString()); - } - } - - final String luceneQuery; - if (qb.containsField(Lucene.createdby.name()) - || qb.containsField(Lucene.responsible.name()) - || qb.containsField(Lucene.watchedby.name())) { - // focused "my tickets" query - luceneQuery = qb.build(); - } else { - // general "my tickets" query - QueryBuilder myQuery = new QueryBuilder(); - myQuery.or(Lucene.createdby.matches(username)); - myQuery.or(Lucene.responsible.matches(username)); - myQuery.or(Lucene.watchedby.matches(username)); - myQuery.and(qb.toSubquery().toString()); - luceneQuery = myQuery.build(); - } - - // paging links - int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1; - int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25); - - List<QueryResult> results; - if(StringUtils.isEmpty(searchParam)) { - results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc); - } else { - results = app().tickets().searchFor(null, searchParam, page, pageSize); - } - - int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults; - buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults); - - final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true); - add(new TicketListPanel("ticketList", results, showSwatch, true)); - } - - protected PageParameters queryParameters( - String query, - String milestone, - String[] states, - String assignedTo, - String sort, - boolean descending, - int page) { - - PageParameters params = WicketUtils.newRepositoryParameter(""); - if (!StringUtils.isEmpty(query)) { - params.add("q", query); - } - if (!StringUtils.isEmpty(milestone)) { - params.add(Lucene.milestone.name(), milestone); - } - if (!ArrayUtils.isEmpty(states)) { - for (String state : states) { - params.add(Lucene.status.name(), state); - } - } - if (!StringUtils.isEmpty(assignedTo)) { - params.add(Lucene.responsible.name(), assignedTo); - } - if (!StringUtils.isEmpty(sort)) { - params.add("sort", sort); - } - if (!descending) { - params.add("direction", "asc"); - } - if (page > 1) { - params.add("pg", "" + page); - } - return params; - } - - protected void buildPager( - final String query, - final String milestone, - final String [] states, - final String assignedTo, - final String sort, - final boolean desc, - final int page, - int pageSize, - int count, - int total) { - - boolean showNav = total > (2 * pageSize); - boolean allowPrev = page > 1; - boolean allowNext = (pageSize * (page - 1) + count) < total; - add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page - 1)).setEnabled(allowPrev).setVisible(showNav)); - add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page + 1)).setEnabled(allowNext).setVisible(showNav)); - - if (total <= pageSize) { - add(new Label("pageLink").setVisible(false)); - return; - } - - // determine page numbers to display - int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1)); - // preferred number of pagelinks - int segments = 5; - if (pages < segments) { - // not enough data for preferred number of page links - segments = pages; - } - int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1)); - int maxpage = Math.min(pages, minpage + (segments - 1)); - List<Integer> sequence = new ArrayList<Integer>(); - for (int i = minpage; i <= maxpage; i++) { - sequence.add(i); - } - - ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence); - DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(final Item<Integer> item) { - final Integer i = item.getModelObject(); - LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, i)); - link.setRenderBodyOnly(true); - if (i == page) { - WicketUtils.setCssClass(item, "active"); - } - item.add(link); - } - }; - add(pagesView); - } + public MyTicketsPage() { + this(null); + } + + public MyTicketsPage(PageParameters params) { + super(params); + setupPage("", getString("gb.myTickets")); + + UserModel currentUser = GitBlitWebSession.get().getUser(); + if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) { + setRedirect(true); + setResponsePage(getApplication().getHomePage()); + return; + } + + final String username = currentUser.getName(); + + final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name()); + final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null); + final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null); + final String queryParam = (params == null) ? null : params.getString("q", null); + final String searchParam = (params == null) ? "" : params.getString("s", null); + final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name(); + final String repositoryId = (params == null) ? "" : params.getString(Lucene.rid.name(), null); + final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc")); + + + // add the user title panel + add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets"))); + + // add search form + add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params)); + + // standard queries + add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class, + queryParameters( + Lucene.type.matches(TicketModel.Type.Proposal.name()), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class, + queryParameters( + Lucene.type.matches(TicketModel.Type.Bug.name()), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class, + queryParameters( + Lucene.type.matches(TicketModel.Type.Enhancement.name()), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class, + queryParameters( + Lucene.type.matches(TicketModel.Type.Task.name()), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class, + queryParameters( + Lucene.type.matches(TicketModel.Type.Question.name()), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("maintenanceQuery", MyTicketsPage.class, + queryParameters( + Lucene.type.matches(TicketModel.Type.Maintenance.name()), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class, + queryParameters( + null, + milestoneParam, + TicketsUI.openStatii, + null, + null, + true, + null, + 1))); + + add(new Label("userDivider")); + add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class, + queryParameters( + Lucene.createdby.matches(username), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class, + queryParameters( + Lucene.watchedby.matches(username), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class, + queryParameters( + Lucene.mentions.matches(username), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class, + queryParameters( + Lucene.responsible.matches(username), + milestoneParam, + statiiParam, + assignedToParam, + sortBy, + desc, + repositoryId, + 1))); + + // states + if (ArrayUtils.isEmpty(statiiParam)) { + add(new Label("selectedStatii", getString("gb.all"))); + } else { + add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ","))); + } + add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, repositoryId, 1))); + add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, repositoryId, 1))); + add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, repositoryId, 1))); + + // by status + List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values())); + statii.remove(Status.Closed); + ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii); + DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(final Item<Status> item) { + final Status status = item.getModelObject(); + PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, repositoryId, 1); + String css = TicketsUI.getStatusClass(status); + item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true)); + } + }; + add(statiiLinks); + + // by sort + List<TicketSort> sortChoices = new ArrayList<TicketSort>(); + sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false)); + sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false)); + sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false)); + sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false)); + sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false)); + sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false)); + sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true)); + sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false)); + + TicketSort currentSort = sortChoices.get(0); + for (TicketSort ts : sortChoices) { + if (ts.sortBy.equals(sortBy) && desc == ts.desc) { + currentSort = ts; + break; + } + } + add(new Label("currentSort", currentSort.name)); + + ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices); + DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(final Item<TicketSort> item) { + final TicketSort ts = item.getModelObject(); + PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, repositoryId, 1); + item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true)); + } + }; + add(sortMenu); + + // by repository + final List<QueryResult> tickets = + query(initializeQueryBuilder(null, username), 1, Integer.MAX_VALUE, sortBy, desc); + final List<RepositoryModel> repositoryChoices = correspondingRepositories(tickets); + Collections.sort(repositoryChoices); + + final RepositoryModel noneChoice = new RepositoryModel(); + noneChoice.name = getString("gb.all"); + repositoryChoices.add(0, noneChoice); + + RepositoryModel currentRepository = repositoryChoices.get(0); + for (RepositoryModel r : repositoryChoices) { + if (r.getRID().equals(repositoryId)) { + currentRepository = r; + break; + } + } + add(new Label("currentRepository", currentRepository.toString())); + + ListDataProvider<RepositoryModel> repositoryChoicesDp = new ListDataProvider<RepositoryModel>(repositoryChoices); + DataView<RepositoryModel> repositoryMenu = new DataView<RepositoryModel>("repository", repositoryChoicesDp) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(final Item<RepositoryModel> item) { + final RepositoryModel r = item.getModelObject(); + String rid = r == noneChoice ? null : r.getRID(); + PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, rid, 1); + item.add(new LinkPanel("repositoryLink", null, r.toString(), MyTicketsPage.class, params).setRenderBodyOnly(true)); + } + }; + add(repositoryMenu); + + // Update query with filter criteria + final QueryBuilder qb = initializeQueryBuilder(queryParam, username); + + if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) { + // specify the states + boolean not = false; + QueryBuilder q = new QueryBuilder(); + for (String state : statiiParam) { + if (state.charAt(0) == '!') { + not = true; + q.and(Lucene.status.doesNotMatch(state.substring(1))); + } else { + q.or(Lucene.status.matches(state)); + } + } + + if (not) { + qb.and(q.toString()); + } else { + qb.and(q.toSubquery().toString()); + } + } + + if (noneChoice != currentRepository && !qb.containsField(Lucene.rid.name())) { + QueryBuilder q1 = new QueryBuilder(); + q1.and(Lucene.rid.matches(repositoryId)); + qb.and(q1.toSubquery().toString()); + } + + // paging links + int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1; + int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25); + + final List<QueryResult> results = + StringUtils.isEmpty(searchParam) ? query(qb, page, pageSize, sortBy, desc) : search(searchParam, page, pageSize); + + int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults; + buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, repositoryId, page, pageSize, results.size(), totalResults); + + final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true); + add(new TicketListPanel("ticketList", results, showSwatch, true)); + } + + protected PageParameters queryParameters( + String query, + String milestone, + String[] states, + String assignedTo, + String sort, + boolean descending, + String repositoryId, + int page) { + + PageParameters params = WicketUtils.newRepositoryParameter(""); + if (!StringUtils.isEmpty(query)) { + params.add("q", query); + } + if (!StringUtils.isEmpty(milestone)) { + params.add(Lucene.milestone.name(), milestone); + } + if (!ArrayUtils.isEmpty(states)) { + for (String state : states) { + params.add(Lucene.status.name(), state); + } + } + if (!StringUtils.isEmpty(assignedTo)) { + params.add(Lucene.responsible.name(), assignedTo); + } + if (!StringUtils.isEmpty(sort)) { + params.add("sort", sort); + } + if (!descending) { + params.add("direction", "asc"); + } + if (!StringUtils.isEmpty(repositoryId)) { + params.add(Lucene.rid.name(), repositoryId); + } + if (page > 1) { + params.add("pg", "" + page); + } + return params; + } + + protected void buildPager( + final String query, + final String milestone, + final String [] states, + final String assignedTo, + final String sort, + final boolean desc, + final String repositoryId, + final int page, + int pageSize, + int count, + int total) { + + boolean showNav = total > (2 * pageSize); + boolean allowPrev = page > 1; + boolean allowNext = (pageSize * (page - 1) + count) < total; + add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page - 1)).setEnabled(allowPrev).setVisible(showNav)); + add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page + 1)).setEnabled(allowNext).setVisible(showNav)); + + if (total <= pageSize) { + add(new Label("pageLink").setVisible(false)); + return; + } + + // determine page numbers to display + int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1)); + // preferred number of pagelinks + int segments = 5; + if (pages < segments) { + // not enough data for preferred number of page links + segments = pages; + } + int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1)); + int maxpage = Math.min(pages, minpage + (segments - 1)); + List<Integer> sequence = new ArrayList<Integer>(); + for (int i = minpage; i <= maxpage; i++) { + sequence.add(i); + } + + ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence); + DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(final Item<Integer> item) { + final Integer i = item.getModelObject(); + LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, i)); + link.setRenderBodyOnly(true); + if (i == page) { + WicketUtils.setCssClass(item, "active"); + } + item.add(link); + } + }; + add(pagesView); + } + + private QueryBuilder initializeQueryBuilder(String queryparam, String username) { + final QueryBuilder qb = new QueryBuilder(queryparam); + + // focused "my tickets" + if (qb.containsField(Lucene.createdby.name()) + || qb.containsField(Lucene.responsible.name()) + || qb.containsField(Lucene.watchedby.name()) + || qb.containsField(Lucene.mentions.name())) { + + return qb; + } + + // general "my tickets" + return qb.andSubquery() + .or(Lucene.createdby.matches(username)) + .or(Lucene.responsible.matches(username)) + .or(Lucene.watchedby.matches(username)) + .or(Lucene.mentions.matches(username)) + .endSubquery(); + } + + private List<QueryResult> query(QueryBuilder qb, int page, int pageSize, String sortBy, boolean descending) { + return app().tickets().queryFor(qb.build(), page, pageSize, sortBy, descending); + } + + private List<QueryResult> search(String searchParam, int page, int pageSize) { + return app().tickets().searchFor(null, searchParam, page, pageSize); + } + + private List<RepositoryModel> correspondingRepositories(Collection<QueryResult> tickets) { + final HashMap<String, RepositoryModel> result = new HashMap<>(); + for (QueryResult ticket : tickets) { + RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository); + if (!result.containsKey(repository.getRID())) { + result.put(repository.getRID(), repository); + } + } + + return new ArrayList<>(result.values()); + } } diff --git a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java index b6c23591..d2589e6f 100644 --- a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java @@ -359,14 +359,14 @@ public class NewRepositoryPage extends RootSubPage { }
}
} finally {
- revWalk.release();
+ revWalk.close();
}
} catch (UnsupportedEncodingException e) {
logger().error(null, e);
} catch (IOException e) {
logger().error(null, e);
} finally {
- odi.release();
+ odi.close();
db.close();
}
return success;
diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html index 447c6aa4..9b5af023 100644 --- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html +++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html @@ -39,6 +39,8 @@ </div>
</td></tr>
<tr><th><wicket:message key="gb.type"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="type"></select></td></tr>
+ <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
+ <tr wicket:id="priority"></tr>
<tr wicket:id="responsible"></tr>
<tr wicket:id="milestone"></tr>
<tr wicket:id="mergeto"></tr>
@@ -67,5 +69,9 @@ <th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="mergeto"></select></td>
</wicket:fragment>
+<wicket:fragment wicket:id="priorityFragment">
+ <th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td>
+</wicket:fragment>
+
</wicket:extend>
</html>
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java index 8f28055a..0c52505c 100644 --- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java +++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java @@ -76,6 +76,10 @@ public class NewTicketPage extends RepositoryPage { private Label descriptionPreview;
+ private IModel<TicketModel.Priority> priorityModel;
+
+ private IModel<TicketModel.Severity> severityModel;
+
public NewTicketPage(PageParameters params) {
super(params);
@@ -95,6 +99,8 @@ public class NewTicketPage extends RepositoryPage { mergeToModel = Model.of(Repository.shortenRefName(getRepositoryModel().mergeTo));
responsibleModel = Model.of();
milestoneModel = Model.of();
+ severityModel = Model.of(TicketModel.Severity.defaultSeverity);
+ priorityModel = Model.of(TicketModel.Priority.defaultPriority);
setStatelessHint(false);
setOutputMarkupId(true);
@@ -105,6 +111,7 @@ public class NewTicketPage extends RepositoryPage { form.add(new DropDownChoice<TicketModel.Type>("type", typeModel, Arrays.asList(TicketModel.Type.choices())));
form.add(new TextField<String>("title", titleModel));
form.add(new TextField<String>("topic", topicModel));
+ form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, Arrays.asList(TicketModel.Severity.choices())));
final IModel<String> markdownPreviewModel = Model.of();
descriptionPreview = new Label("descriptionPreview", markdownPreviewModel);
@@ -152,6 +159,11 @@ public class NewTicketPage extends RepositoryPage { milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
form.add(milestone.setVisible(!milestones.isEmpty()));
+ // priority
+ Fragment priority = new Fragment("priority", "priorityFragment", this);
+ priority.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, Arrays.asList(TicketModel.Priority.choices())));
+ form.add(priority);
+
// integration branch
List<String> branches = new ArrayList<String>();
for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -171,6 +183,7 @@ public class NewTicketPage extends RepositoryPage { form.add(new Label("responsible").setVisible(false));
form.add(new Label("milestone").setVisible(false));
form.add(new Label("mergeto").setVisible(false));
+ form.add(new Label("priority").setVisible(false));
}
form.add(new AjaxButton("create") {
@@ -212,6 +225,20 @@ public class NewTicketPage extends RepositoryPage { change.setField(Field.milestone, milestone.name);
}
+ // severity
+ TicketModel.Severity severity = TicketModel.Severity.defaultSeverity;
+ if (severityModel.getObject() != null) {
+ severity = severityModel.getObject();
+ }
+ change.setField(Field.severity, severity);
+
+ // priority
+ TicketModel.Priority priority = TicketModel.Priority.defaultPriority;
+ if (priorityModel.getObject() != null) {
+ priority = priorityModel.getObject();
+ }
+ change.setField(Field.priority, priority);
+
// integration branch
String mergeTo = mergeToModel.getObject();
if (!StringUtils.isEmpty(mergeTo)) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html index 2d446ecc..fc28d178 100644 --- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html +++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html @@ -8,7 +8,7 @@ <wicket:extend>
<div class="container">
- <table class="repositories">
+ <table class="repositories projectlist">
<thead>
<tr>
<th class="left">
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java index f04fa78a..132a39dd 100644 --- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java @@ -15,6 +15,7 @@ */
package com.gitblit.wicket.pages;
+import java.util.Collections;
import java.util.List;
import org.apache.wicket.PageParameters;
@@ -69,6 +70,7 @@ public class ProjectsPage extends RootPage { }
List<ProjectModel> projects = getProjects(params);
+ Collections.sort(projects);
ListDataProvider<ProjectModel> dp = new ListDataProvider<ProjectModel>(projects);
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html index c0ac7eb7..bd59494a 100644 --- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html +++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html @@ -9,7 +9,7 @@ <!-- pager links -->
<div class="page_nav2">
- <a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> »</a>
+ <a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message> »</a>
</div>
<!-- ref log -->
@@ -17,7 +17,7 @@ <!-- pager links -->
<div style="padding-bottom:5px;">
- <a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> »</a>
+ <a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">« <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> »</a>
</div>
</wicket:extend>
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java index 29fd449f..44fb2227 100644 --- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java @@ -40,15 +40,26 @@ public class ReflogPage extends RepositoryPage { boolean hasMore = reflogPanel.hasMore();
add(reflogPanel);
- add(new BookmarkablePageLink<Void>("firstPage", ReflogPage.class,
+ add(new BookmarkablePageLink<Void>("firstPageTop", ReflogPage.class,
WicketUtils.newObjectParameter(repositoryName, objectId))
.setEnabled(pageNumber > 1));
- add(new BookmarkablePageLink<Void>("prevPage", ReflogPage.class,
+ add(new BookmarkablePageLink<Void>("prevPageTop", ReflogPage.class,
WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
.setEnabled(pageNumber > 1));
- add(new BookmarkablePageLink<Void>("nextPage", ReflogPage.class,
+ add(new BookmarkablePageLink<Void>("nextPageTop", ReflogPage.class,
WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
.setEnabled(hasMore));
+
+ add(new BookmarkablePageLink<Void>("firstPageBottom", ReflogPage.class,
+ WicketUtils.newObjectParameter(repositoryName, objectId))
+ .setEnabled(pageNumber > 1));
+ add(new BookmarkablePageLink<Void>("prevPageBottom", ReflogPage.class,
+ WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
+ .setEnabled(pageNumber > 1));
+ add(new BookmarkablePageLink<Void>("nextPageBottom", ReflogPage.class,
+ WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
+ .setEnabled(hasMore));
+
}
@Override
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html index 22544bc8..d4132421 100644 --- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html +++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html @@ -24,13 +24,20 @@ </div>
<div class="span7">
- <div>
- <span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
- <a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
- <img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
- </a>
+ <div style="display:inline-block;vertical-align:top;padding-right:5px;">
+ <span wicket:id="repoIcon"></span>
+ </div>
+ <div style="display:inline-block;">
+ <div>
+ <span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
+ <a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
+ <img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
+ </a>
+ </div>
+ <div>
+ <span class="gray" wicket:id="originRepository">[origin repository]</span>
+ </div>
</div>
- <span wicket:id="originRepository">[origin repository]</span>
</div>
</div>
</div>
@@ -60,6 +67,22 @@ <i wicket:id="icon" style="padding-right:3px;"></i><span wicket:id="label"></span>
</wicket:fragment>
+ <wicket:fragment wicket:id="repoIconFragment">
+ <span class="gray mega-octicon octicon-repo"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="mirrorIconFragment">
+ <span class="gray mega-octicon octicon-mirror"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="forkIconFragment">
+ <span class="gray mega-octicon octicon-repo-forked"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="cloneIconFragment">
+ <span class="gray mega-octicon octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+ </wicket:fragment>
+
<wicket:fragment wicket:id="originFragment">
<p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java index 134ee044..7e164a8c 100644 --- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java @@ -64,6 +64,7 @@ import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.BugtraqProcessor;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.ModelUtils;
import com.gitblit.utils.RefLogUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
@@ -73,6 +74,7 @@ import com.gitblit.wicket.WicketUtils; import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.NavigationPanel;
import com.gitblit.wicket.panels.RefsPanel;
+import com.google.common.base.Optional;
public abstract class RepositoryPage extends RootPage {
@@ -295,25 +297,38 @@ public abstract class RepositoryPage extends RootPage { RepositoryModel model = getRepositoryModel();
if (StringUtils.isEmpty(model.originRepository)) {
if (model.isMirror) {
+ add(new Fragment("repoIcon", "mirrorIconFragment", this));
Fragment mirrorFrag = new Fragment("originRepository", "mirrorFragment", this);
Label lbl = new Label("originRepository", MessageFormat.format(getString("gb.mirrorOf"), "<b>" + model.origin + "</b>"));
mirrorFrag.add(lbl.setEscapeModelStrings(false));
add(mirrorFrag);
} else {
- add(new Label("originRepository").setVisible(false));
+ if (model.isBare) {
+ add(new Fragment("repoIcon", "repoIconFragment", this));
+ } else {
+ add(new Fragment("repoIcon", "cloneIconFragment", this));
+ }
+ add(new Label("originRepository", Optional.of(model.description).or("")));
}
} else {
RepositoryModel origin = app().repositories().getRepositoryModel(model.originRepository);
if (origin == null) {
- // no origin repository
- add(new Label("originRepository").setVisible(false));
+ // no origin repository, show description if available
+ if (model.isBare) {
+ add(new Fragment("repoIcon", "repoIconFragment", this));
+ } else {
+ add(new Fragment("repoIcon", "cloneIconFragment", this));
+ }
+ add(new Label("originRepository", Optional.of(model.description).or("")));
} else if (!user.canView(origin)) {
// show origin repository without link
+ add(new Fragment("repoIcon", "forkIconFragment", this));
Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
add(forkFrag);
} else {
// link to origin repository
+ add(new Fragment("repoIcon", "forkIconFragment", this));
Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(model.originRepository),
SummaryPage.class, WicketUtils.newRepositoryParameter(model.originRepository)));
@@ -356,8 +371,10 @@ public abstract class RepositoryPage extends RootPage { add(new ExternalLink("myForkLink", "").setVisible(false));
} else {
String fork = app().repositories().getFork(user.username, model.name);
+ String userRepo = ModelUtils.getPersonalPath(user.username) + "/" + StringUtils.stripDotGit(StringUtils.getLastPathElement(model.name));
+ boolean hasUserRepo = app().repositories().hasRepository(userRepo);
boolean hasFork = fork != null;
- boolean canFork = user.canFork(model) && model.hasCommits;
+ boolean canFork = user.canFork(model) && model.hasCommits && !hasUserRepo;
if (hasFork || !canFork) {
// user not allowed to fork or fork already exists or repo forbids forking
diff --git a/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java index 8aec9e63..ceca1ec2 100644 --- a/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java @@ -76,8 +76,9 @@ public class ReviewProposalPage extends RootSubPage { sb.append(asParam(p, proposal.name, "exclude", ""));
sb.append(asParam(p, proposal.name, "include", ""));
+ final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
add(new Label("definition", StringUtils.breakLinesForHtml(StringUtils.escapeForHtml(sb
- .toString().trim(), true))).setEscapeModelStrings(false));
+ .toString().trim(), true, tabLength))).setEscapeModelStrings(false));
List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
proposal.repositories.values());
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.html b/src/main/java/com/gitblit/wicket/pages/RootPage.html index 2ff305f2..c51db819 100644 --- a/src/main/java/com/gitblit/wicket/pages/RootPage.html +++ b/src/main/java/com/gitblit/wicket/pages/RootPage.html @@ -14,7 +14,7 @@ <span class="icon-bar"></span>
</a>
<a class="brand" wicket:id="rootLink">
- <img src="logo.png" height="45" width="120" class="logo"/>
+ <img src="logo.png" width="120" class="logo"/>
</a>
<div class="nav-collapse">
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java index c4d4dd11..6ed5a357 100644 --- a/src/main/java/com/gitblit/wicket/pages/RootPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java @@ -36,11 +36,11 @@ import javax.servlet.http.HttpServletResponse; import org.apache.wicket.MarkupContainer; import org.apache.wicket.PageParameters; +import org.apache.wicket.RequestCycle; import org.apache.wicket.behavior.HeaderContributor; import org.apache.wicket.markup.html.IHeaderContributor; import org.apache.wicket.markup.html.IHeaderResponse; import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.form.PasswordTextField; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.html.panel.Fragment; @@ -71,9 +71,10 @@ import com.gitblit.models.UserModel; import com.gitblit.utils.ModelUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.NonTrimmedPasswordTextField; import com.gitblit.wicket.SessionlessForm; import com.gitblit.wicket.WicketUtils; -import com.gitblit.wicket.panels.GravatarImage; +import com.gitblit.wicket.panels.AvatarImage; import com.gitblit.wicket.panels.LinkPanel; import com.gitblit.wicket.panels.NavigationPanel; @@ -151,6 +152,7 @@ public abstract class RootPage extends BasePage { boolean authenticateAdmin = app().settings().getBoolean(Keys.web.authenticateAdminPages, true); boolean allowAdmin = app().settings().getBoolean(Keys.web.allowAdministration, true); boolean allowLucene = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true); + boolean displayUserPanel = app().settings().getBoolean(Keys.web.displayUserPanel, true); boolean isLoggedIn = GitBlitWebSession.get().isLoggedIn(); if (authenticateAdmin) { @@ -168,7 +170,7 @@ public abstract class RootPage extends BasePage { } } - if (authenticateView || authenticateAdmin) { + if (displayUserPanel && (authenticateView || authenticateAdmin)) { if (isLoggedIn) { UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this); add(userFragment); @@ -183,6 +185,11 @@ public abstract class RootPage extends BasePage { // navigation links List<NavLink> navLinks = new ArrayList<NavLink>(); if (!authenticateView || (authenticateView && isLoggedIn)) { + UserModel user = UserModel.ANONYMOUS; + if (isLoggedIn) { + user = GitBlitWebSession.get().getUser(); + } + navLinks.add(new PageNavLink(isLoggedIn ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class, getRootPageParameters())); if (isLoggedIn && app().tickets().isReady()) { @@ -190,6 +197,9 @@ public abstract class RootPage extends BasePage { } navLinks.add(new PageNavLink("gb.repositories", RepositoriesPage.class, getRootPageParameters())); + if (user.canAdmin()) { + navLinks.add(new PageNavLink("gb.filestore", FilestorePage.class, getRootPageParameters())); + } navLinks.add(new PageNavLink("gb.activity", ActivityPage.class, getRootPageParameters())); if (allowLucene) { navLinks.add(new PageNavLink("gb.search", LuceneSearchPage.class)); @@ -199,11 +209,6 @@ public abstract class RootPage extends BasePage { addDropDownMenus(navLinks); } - UserModel user = UserModel.ANONYMOUS; - if (isLoggedIn) { - user = GitBlitWebSession.get().getUser(); - } - // add nav link extensions List<NavLinkExtension> extensions = app().plugins().getExtensions(NavLinkExtension.class); for (NavLinkExtension ext : extensions) { @@ -278,7 +283,7 @@ public abstract class RootPage extends BasePage { request = ((WebRequest) getRequest()).getHttpServletRequest(); response = ((WebResponse) getResponse()).getHttpServletResponse(); - request.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, AuthenticationType.CREDENTIALS); + request.getSession().setAttribute(Constants.ATTRIB_AUTHTYPE, AuthenticationType.CREDENTIALS); // Set Cookie app().authentication().setCookie(request, response, user); @@ -564,7 +569,9 @@ public abstract class RootPage extends BasePage { String username = RootPage.this.username.getObject(); char[] password = RootPage.this.password.getObject().toCharArray(); - UserModel user = app().authentication().authenticate(username, password); + HttpServletRequest request = ((WebRequest)RequestCycle.get().getRequest()).getHttpServletRequest(); + + UserModel user = app().authentication().authenticate(username, password, request.getRemoteAddr()); if (user == null) { error(getString("gb.invalidUsernameOrPassword")); } else if (user.username.equals(Constants.FEDERATION_USER)) { @@ -580,7 +587,7 @@ public abstract class RootPage extends BasePage { TextField<String> unameField = new TextField<String>("username", username); WicketUtils.setInputPlaceholder(unameField, markupProvider.getString("gb.username")); loginForm.add(unameField); - PasswordTextField pwField = new PasswordTextField("password", password); + NonTrimmedPasswordTextField pwField = new NonTrimmedPasswordTextField("password", password); WicketUtils.setInputPlaceholder(pwField, markupProvider.getString("gb.password")); loginForm.add(pwField); add(loginForm); @@ -607,11 +614,11 @@ public abstract class RootPage extends BasePage { UserModel user = session.getUser(); boolean editCredentials = app().authentication().supportsCredentialChanges(user); HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest(); - AuthenticationType authenticationType = (AuthenticationType) request.getSession().getAttribute(Constants.AUTHENTICATION_TYPE); - boolean standardLogin = authenticationType.isStandard(); + AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE); + boolean standardLogin = (null != authenticationType) ? authenticationType.isStandard() : true; if (app().settings().getBoolean(Keys.web.allowGravatar, true)) { - add(new GravatarImage("username", user, "navbarGravatar", 20, false)); + add(new AvatarImage("username", user, "navbarGravatar", 20, false)); } else { add(new Label("username", user.getDisplayName())); } diff --git a/src/main/java/com/gitblit/wicket/pages/SessionPage.java b/src/main/java/com/gitblit/wicket/pages/SessionPage.java index 0dda9495..bcf8e978 100644 --- a/src/main/java/com/gitblit/wicket/pages/SessionPage.java +++ b/src/main/java/com/gitblit/wicket/pages/SessionPage.java @@ -56,34 +56,50 @@ public abstract class SessionPage extends WebPage { HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest(); HttpServletResponse response = ((WebResponse) getResponse()).getHttpServletResponse(); - if (session.isLoggedIn() && !session.isSessionInvalidated()) { - // already have a session, refresh usermodel to pick up - // any changes to permissions or roles (issue-186) - UserModel user = app().users().getUserModel(session.getUser().username); - - if (user == null || user.disabled) { - // user was deleted/disabled during session - app().authentication().logout(request, response, user); - session.setUser(null); - session.invalidateNow(); - return; + // If using container/external servlet authentication, use request attribute + String authedUser = (String) request.getAttribute(Constants.ATTRIB_AUTHUSER); + + // Default to trusting session authentication if not set in request by external processing + if (StringUtils.isEmpty(authedUser) && session.isLoggedIn()) { + authedUser = session.getUsername(); + } + + if (!StringUtils.isEmpty(authedUser)) { + // Avoid session fixation for non-session authentication + // If the authenticated user is different from the session user, discard + // the old session entirely, without trusting any session values + if (!authedUser.equals(session.getUsername())) { + session.replaceSession(); } - // validate cookie during session (issue-361) - if (user != null && app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) { - String requestCookie = app().authentication().getCookie(request); - if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) { - if (!requestCookie.equals(user.cookie)) { - // cookie was changed during our session - app().authentication().logout(request, response, user); - session.setUser(null); - session.invalidateNow(); - return; + if (!session.isSessionInvalidated()) { + // Refresh usermodel to pick up any changes to permissions or roles (issue-186) + UserModel user = app().users().getUserModel(authedUser); + + if (user == null || user.disabled) { + // user was deleted/disabled during session + app().authentication().logout(request, response, user); + session.setUser(null); + session.invalidateNow(); + return; + } + + // validate cookie during session (issue-361) + if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) { + String requestCookie = app().authentication().getCookie(request); + if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) { + if (!requestCookie.equals(user.cookie)) { + // cookie was changed during our session + app().authentication().logout(request, response, user); + session.setUser(null); + session.invalidateNow(); + return; + } } } + session.setUser(user); + return; } - session.setUser(user); - return; } // try to authenticate by servlet request @@ -91,16 +107,17 @@ public abstract class SessionPage extends WebPage { // Login the user if (user != null) { - // preserve the authentication type across session replacement - AuthenticationType authenticationType = (AuthenticationType) request.getSession() - .getAttribute(Constants.AUTHENTICATION_TYPE); + AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE); // issue 62: fix session fixation vulnerability - session.replaceSession(); + // but only if authentication was done in the container. + // It avoid double change of session, that some authentication method + // don't like + if (AuthenticationType.CONTAINER != authenticationType) { + session.replaceSession(); + } session.setUser(user); - request.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, authenticationType); - // Set Cookie app().authentication().setCookie(request, response, user); diff --git a/src/main/java/com/gitblit/wicket/pages/TagPage.java b/src/main/java/com/gitblit/wicket/pages/TagPage.java index 9eed2797..ffeea6f2 100644 --- a/src/main/java/com/gitblit/wicket/pages/TagPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TagPage.java @@ -31,7 +31,7 @@ import com.gitblit.utils.JGitUtils; import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.RefsPanel;
@@ -77,7 +77,7 @@ public class TagPage extends RepositoryPage { linkClass = CommitPage.class;
break;
}
- add(new GravatarImage("taggerAvatar", tagRef.getAuthorIdent()));
+ add(new AvatarImage("taggerAvatar", tagRef.getAuthorIdent()));
add(new RefsPanel("tagName", repositoryName, Arrays.asList(tagRef)));
add(new Label("tagId", tagRef.getObjectId().getName()));
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.html b/src/main/java/com/gitblit/wicket/pages/TicketPage.html index f3f38ec3..5ae005eb 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketPage.html +++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.html @@ -67,6 +67,8 @@ <div style="border: 1px solid #ccc;padding: 10px;margin: 5px 0px;">
<table class="summary" style="width: 100%">
<tr><th><wicket:message key="gb.type"></wicket:message></th><td><span wicket:id="ticketType">[type]</span></td></tr>
+ <tr><th><wicket:message key="gb.priority"></wicket:message></th><td><span wicket:id="priority">[priority]</span></td></tr>
+ <tr><th><wicket:message key="gb.severity"></wicket:message></th><td><span wicket:id="severity">[severity]</span></td></tr>
<tr><th><wicket:message key="gb.topic"></wicket:message></th><td><span wicket:id="ticketTopic">[topic]</span></td></tr>
<tr><th><wicket:message key="gb.responsible"></wicket:message></th><td><span wicket:id="responsible">[responsible]</span></td></tr>
<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td><span wicket:id="milestone">[milestone]</span></td></tr>
@@ -555,7 +557,6 @@ pt push</pre> </div>
</wicket:fragment>
-
<!-- VETOED PATCHSET FRAGMENT -->
<wicket:fragment wicket:id="vetoedFragment">
<div class="alert alert-error submit-info" style="padding:4px;">
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java index 19788f27..8bf5c6d9 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java @@ -88,10 +88,10 @@ import com.gitblit.utils.TimeUtils; import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.TicketsUI;
import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.AvatarImage;
import com.gitblit.wicket.panels.BasePanel.JavascriptTextPrompt;
import com.gitblit.wicket.panels.CommentPanel;
import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
import com.gitblit.wicket.panels.IconAjaxLink;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.ShockWaveComponent;
@@ -312,7 +312,7 @@ public class TicketPage extends RepositoryPage { if (user == null) {
user = new UserModel(username);
}
- item.add(new GravatarImage("participant", user.getDisplayName(),
+ item.add(new AvatarImage("participant", user.getDisplayName(),
user.emailAddress, null, 25, true));
}
};
@@ -519,6 +519,10 @@ public class TicketPage extends RepositoryPage { * TICKET METADATA
*/
add(new Label("ticketType", ticket.type.toString()));
+
+ add(new Label("priority", ticket.priority.toString()));
+ add(new Label("severity", ticket.severity.toString()));
+
if (StringUtils.isEmpty(ticket.topic)) {
add(new Label("ticketTopic").setVisible(false));
} else {
@@ -529,6 +533,8 @@ public class TicketPage extends RepositoryPage { }
+
+
/*
* VOTERS
*/
@@ -730,7 +736,7 @@ public class TicketPage extends RepositoryPage { } else {
// permit user to comment
Fragment newComment = new Fragment("newComment", "newCommentFragment", this);
- GravatarImage img = new GravatarImage("newCommentAvatar", user.username, user.emailAddress,
+ AvatarImage img = new AvatarImage("newCommentAvatar", user.username, user.emailAddress,
"gravatar-round", avatarWidth, true);
newComment.add(img);
CommentPanel commentPanel = new CommentPanel("commentPanel", user, ticket, null, TicketsPage.class);
@@ -746,7 +752,7 @@ public class TicketPage extends RepositoryPage { if (currentPatchset == null) {
// no patchset available
RepositoryUrl repoUrl = getRepositoryUrl(user, repository);
- boolean canPropose = repoUrl != null && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user);
+ boolean canPropose = repoUrl != null && repoUrl.hasPermission() && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user);
if (ticket.isOpen() && app().tickets().isAcceptingNewPatchsets(repository) && canPropose) {
// ticket & repo will accept a proposal patchset
// show the instructions for proposing a patchset
@@ -810,14 +816,14 @@ public class TicketPage extends RepositoryPage { public void populateItem(final Item<RevCommit> item) {
RevCommit commit = item.getModelObject();
PersonIdent author = commit.getAuthorIdent();
- item.add(new GravatarImage("authorAvatar", author.getName(), author.getEmailAddress(), null, 16, false));
+ item.add(new AvatarImage("authorAvatar", author.getName(), author.getEmailAddress(), null, 16, false));
item.add(new Label("author", commit.getAuthorIdent().getName()));
item.add(new LinkPanel("commitId", null, getShortObjectId(commit.getName()),
CommitPage.class, WicketUtils.newObjectParameter(repositoryName, commit.getName()), true));
item.add(new LinkPanel("diff", "link", getString("gb.diff"), CommitDiffPage.class,
WicketUtils.newObjectParameter(repositoryName, commit.getName()), true));
item.add(new Label("title", StringUtils.trimString(commit.getShortMessage(), Constants.LEN_SHORTLOG_REFS)));
- item.add(WicketUtils.createDateLabel("commitDate", JGitUtils.getCommitDate(commit), GitBlitWebSession
+ item.add(WicketUtils.createDateLabel("commitDate", JGitUtils.getAuthorDate(commit), GitBlitWebSession
.get().getTimezone(), getTimeUtils(), false));
item.add(new DiffStatPanel("commitDiffStat", 0, 0, true));
}
@@ -981,12 +987,12 @@ public class TicketPage extends RepositoryPage { UserModel commenter = app().users().getUserModel(entry.author);
if (commenter == null) {
// unknown user
- container.add(new GravatarImage("changeAvatar", entry.author,
+ container.add(new AvatarImage("changeAvatar", entry.author,
entry.author, null, avatarSize, false).setVisible(avatarSize > 0));
container.add(new Label("changeAuthor", entry.author.toLowerCase()));
} else {
// known user
- container.add(new GravatarImage("changeAvatar", commenter.getDisplayName(),
+ container.add(new AvatarImage("changeAvatar", commenter.getDisplayName(),
commenter.emailAddress, avatarSize > 24 ? "gravatar-round" : null,
avatarSize, true).setVisible(avatarSize > 0));
container.add(new LinkPanel("changeAuthor", null, commenter.getDisplayName(),
@@ -1425,6 +1431,12 @@ public class TicketPage extends RepositoryPage { Fragment mergePanel = new Fragment("mergePanel", "alreadyMergedFragment", this);
mergePanel.add(new Label("mergeTitle", MessageFormat.format(getString("gb.patchsetAlreadyMerged"), ticket.mergeTo)));
return mergePanel;
+ } else if (MergeStatus.MISSING_INTEGRATION_BRANCH == mergeStatus) {
+ // target/integration branch is missing
+ Fragment mergePanel = new Fragment("mergePanel", "notMergeableFragment", this);
+ mergePanel.add(new Label("mergeTitle", MessageFormat.format(getString("gb.patchsetNotMergeable"), ticket.mergeTo)));
+ mergePanel.add(new Label("mergeMore", MessageFormat.format(getString("gb.missingIntegrationBranchMore"), ticket.mergeTo)));
+ return mergePanel;
} else {
// patchset can not be cleanly merged
Fragment mergePanel = new Fragment("mergePanel", "notMergeableFragment", this);
@@ -1503,7 +1515,7 @@ public class TicketPage extends RepositoryPage { */
protected RepositoryUrl getRepositoryUrl(UserModel user, RepositoryModel repository) {
HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
- List<RepositoryUrl> urls = app().gitblit().getRepositoryUrls(req, user, repository);
+ List<RepositoryUrl> urls = app().services().getRepositoryUrls(req, user, repository);
if (ArrayUtils.isEmpty(urls)) {
return null;
}
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html index 3a3d977a..c686d1ff 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html +++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html @@ -31,17 +31,18 @@ <div class="hidden-phone">
<ul class="nav nav-list">
<li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li>
- <li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
- <li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
- <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
- <li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
- <li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+ <li><a wicket:id="changesQuery"><i class="fa fa-code-fork fa-fw"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
+ <li><a wicket:id="bugsQuery"><i class="fa fa-bug fa-fw"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
+ <li><a wicket:id="enhancementsQuery"><i class="fa fa-magic fa-fw"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
+ <li><a wicket:id="tasksQuery"><i class="fa fa-ticket fa-fw"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
+ <li><a wicket:id="questionsQuery"><i class="fa fa-question fa-fw"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+ <li><a wicket:id="maintenanceQuery"><i class="fa fa-cogs fa-fw"></i> <wicket:message key="gb.maintenanceTickets"></wicket:message></a></li>
<li wicket:id="userDivider" class="divider"></li>
- <li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
- <li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
- <li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
+ <li><a wicket:id="createdQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
+ <li><a wicket:id="watchedQuery"><i class="fa fa-eye fa-fw"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
+ <li><a wicket:id="mentionsQuery"><i class="fa fa-comment fa-fw"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
<li class="divider"></li>
- <li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
+ <li><a wicket:id="resetQuery"><i class="fa fa-bolt fa-fw"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
</ul>
</div>
<div wicket:id="dynamicQueries" class="hidden-phone"></div>
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java index 658cddec..ecfed250 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java @@ -272,6 +272,16 @@ public class TicketsPage extends RepositoryPage { sortBy,
desc,
1)));
+
+ add(new BookmarkablePageLink<Void>("maintenanceQuery", TicketsPage.class,
+ queryParameters(
+ Lucene.type.matches(TicketModel.Type.Maintenance.name()),
+ milestoneParam,
+ statiiParam,
+ assignedToParam,
+ sortBy,
+ desc,
+ 1)));
add(new BookmarkablePageLink<Void>("resetQuery", TicketsPage.class,
queryParameters(
@@ -454,7 +464,11 @@ public class TicketsPage extends RepositoryPage { sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
-
+ sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
+ sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
+ sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
+
TicketSort currentSort = sortChoices.get(0);
for (TicketSort ts : sortChoices) {
if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.java b/src/main/java/com/gitblit/wicket/pages/TreePage.java index 9ddbecf6..d7899dcb 100644 --- a/src/main/java/com/gitblit/wicket/pages/TreePage.java +++ b/src/main/java/com/gitblit/wicket/pages/TreePage.java @@ -52,7 +52,7 @@ public class TreePage extends RepositoryPage { Repository r = getRepository();
RevCommit commit = getCommit();
- List<PathModel> paths = JGitUtils.getFilesInPath(r, path, commit);
+ List<PathModel> paths = JGitUtils.getFilesInPath2(r, path, commit);
// tree page links
add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java index 8931d5e3..ea68f25b 100644 --- a/src/main/java/com/gitblit/wicket/pages/UserPage.java +++ b/src/main/java/com/gitblit/wicket/pages/UserPage.java @@ -104,7 +104,7 @@ public class UserPage extends RootPage { if (isMyProfile) {
addPreferences(user);
- if (app().gitblit().isServingSSH()) {
+ if (app().services().isServingSSH()) {
// show the SSH key management tab
addSshKeys(user);
} else {
@@ -183,7 +183,8 @@ public class UserPage extends RootPage { new Language("Norsk", "no"),
new Language("Język Polski", "pl"),
new Language("Português", "pt_BR"),
- new Language("中文", "zh_CN"));
+ new Language("簡體中文", "zh_CN"),
+ new Language("正體中文", "zh_TW"));
Locale locale = user.getPreferences().getLocale();
if (locale == null) {
@@ -248,14 +249,16 @@ public class UserPage extends RootPage { emailMeOnMyTicketChanges).setVisible(app().notifier().isSendingMail()));
List<Transport> availableTransports = new ArrayList<>();
- if (app().gitblit().isServingSSH()) {
+ if (app().services().isServingSSH()) {
availableTransports.add(Transport.SSH);
}
- if (app().gitblit().isServingHTTP()) {
- availableTransports.add(Transport.HTTPS);
+ if (app().services().isServingHTTP()) {
availableTransports.add(Transport.HTTP);
}
- if (app().gitblit().isServingGIT()) {
+ if (app().services().isServingHTTPS()) {
+ availableTransports.add(Transport.HTTPS);
+ }
+ if (app().services().isServingGIT()) {
availableTransports.add(Transport.GIT);
}
diff --git a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js new file mode 100644 index 00000000..61575132 --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js @@ -0,0 +1,263 @@ +/* + * Copyright 2014 Tom <tw201207@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function($) { + +/** + * Sets up elem as a slider; returns an access object. Elem must be positioned! + * Note that the element may contain other elements; this is used for instance + * for the image diff overlay slider. + * + * The styling of the slider is to be done in CSS. Currently recognized options: + * - initial: <float> clipped to [0..1], default 0 + * - handleClass: <string> to assign to the handle span element created. + * If no handleClass is specified, a very plain default style is assigned. + */ +function rangeSlider(elem, options) { + options = $.extend({ initial : 0 }, options || {}); + options.initial = Math.min(1.0, Math.max(0, options.initial)); + + var $elem = $(elem); + var $handle = $('<span></span>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' }); + var $root = $(document.documentElement); + var $doc = $(document); + var lastRatio = options.initial; + + /** Mousemove event handler to track the mouse and move the slider. Generates slider:pos events. */ + function track(e) { + var pos = $elem.offset().left; + var width = $elem.innerWidth(); + var handleWidth = $handle.outerWidth(false); + var range = width - handleWidth; + if (range <= 0) return; + var delta = Math.min(range, Math.max (0, e.pageX - pos - handleWidth / 2)); + lastRatio = delta / range; + $handle.css('left', "" + (delta * 100 / width) + '%'); + $elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] }); + } + + /** Mouseup event handler to stop mouse tracking. */ + function end(e) { + $doc.off('mousemove', track); + $doc.off('mouseup', end); + $root.removeClass('no-select'); + } + + /** Snaps the slider to the given ratio and generates a slider:pos event with the new ratio. */ + function setTo(ratio) { + var w = $elem.innerWidth(); + if (w <= 0 || $elem.is(':hidden')) return; + lastRatio = Math.min( 1.0, Math.max(0, ratio)); + $handle.css('left', "" + Math.max(0, 100 * (lastRatio * (w - $handle.outerWidth(false))) / w) + '%'); + $elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] }); + } + + /** + * Moves the slider to the given ratio, clipped to [0..1], in duration milliseconds. + * Generates slider:pos events during the animation. If duration <= 30, same as setTo. + * Default duration is 500ms. If a callback is given, it's called once the animation + * has completed. + */ + function moveTo(ratio, duration, callback) { + ratio = Math.min(1.0, Math.max(0, ratio)); + if (ratio === lastRatio) { + if (typeof callback == 'function') callback(); + return; + } + if (typeof duration == 'undefined') duration = 500; + if (duration <= 30) { + // Cinema is 24 or 48 frames/sec, so 20-40ms per frame. Makes no sense to animate for such a short duration. + setTo(ratio); + if (typeof callback == 'function') callback(); + } else { + var target = ratio * ($elem.innerWidth() - $handle.outerWidth(false)); + if (ratio > lastRatio) target--; else target++; + $handle.stop().animate({left: target}, + { 'duration' : duration, + 'step' : function() { + lastRatio = Math.min(1.0, Math.max(0, $handle.position().left / ($elem.innerWidth() - $handle.outerWidth(false)))); + $elem.trigger('slider:pos', { ratio : lastRatio, handle : $handle[0] }); + }, + 'complete' : function() { setTo(ratio); if (typeof callback == 'function') callback(); } // Ensure we have again a % value + } + ); + } + } + + /** + * As moveTo, but determines an appropriate duration in the range [0..maxDuration] on its own, + * depending on the distance the handle would move. If no maxDuration is given it defaults + * to 1500ms. + */ + function moveAuto(ratio, maxDuration, callback) { + if (typeof maxDuration == 'undefined') maxDuration = 1500; + var delta = ratio - lastRatio; + if (delta < 0) delta = -delta; + var speed = $elem.innerWidth() * delta * 2; + if (speed > maxDuration) speed = maxDuration; + moveTo(ratio, speed, callback); + } + + /** Returns the current ratio. */ + function getValue() { + return lastRatio; + } + + $elem.append($handle); + if (options.handleClass) { + $handle.addClass(options.handleClass); + } else { // Provide a default style so that it is at least visible + $handle.css({ width: '10px', height: '10px', background: 'white', border: '1px solid black' }); + } + if (options.initial) setTo(options.initial); + + /** Install mousedown handler to start mouse tracking. */ + $handle.on('mousedown', function(e) { + $root.addClass('no-select'); + $doc.on('mousemove', track); + $doc.on('mouseup', end); + e.stopPropagation(); + e.preventDefault(); + }); + + return { setRatio: setTo, moveRatio: moveTo, 'moveAuto': moveAuto, getRatio: getValue, handle: $handle[0] }; +} + +function setup() { + $('.imgdiff-container').each(function() { + var $this = $(this); + var $overlaySlider = $this.find('.imgdiff-ovr-slider').first(); + var $opacitySlider = $this.find('.imgdiff-opa-slider').first(); + var overlayAccess = rangeSlider($overlaySlider, {handleClass: 'imgdiff-ovr-handle'}); + var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'}); + var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity + var $div = $img.parent(); // This controls visibility: here we change width. + var blinking = false; + + $overlaySlider.on('slider:pos', function(e, data) { + var pos = $(data.handle).offset().left; + var imgLeft = $img.offset().left; // Global + var imgW = $img.outerWidth(true); + var imgOff = $img.position().left; // From left edge of $div + if (pos <= imgLeft) { + $div.width(0); + } else if (pos <= imgLeft + imgW) { + $div.width(pos - imgLeft + imgOff); + } else if ($div.width() < imgW + imgOff) { + $div.width(imgW + imgOff); + } + }); + $overlaySlider.css('cursor', 'pointer'); + $overlaySlider.on('mousedown', function(e) { + var newRatio = (e.pageX - $overlaySlider.offset().left) / $overlaySlider.innerWidth(); + var oldRatio = overlayAccess.getRatio(); + if (newRatio !== oldRatio) { + overlayAccess.moveAuto(newRatio); + } + }); + + var autoShowing = false; + $opacitySlider.on('slider:pos', function(e, data) { + if ($div.width() <= 0 && !blinking) { + // Make old image visible in a nice way, *then* adjust opacity + autoShowing = true; + overlayAccess.moveAuto(1.0, 500, function() { + $img.stop().animate( + {opacity: 1.0 - opacityAccess.getRatio()}, + {duration: 400, + complete: function () { + // In case the opacity handle was moved while we were trying to catch up + $img.css('opacity', 1.0 - opacityAccess.getRatio()); + autoShowing = false; + } + } + ); + }); + } else if (!autoShowing) { + $img.css('opacity', 1.0 - data.ratio); + } + }); + $opacitySlider.on('click', function(e) { + var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth(); + var oldRatio = opacityAccess.getRatio(); + if (newRatio !== oldRatio) { + if ($div.width() <= 0) { + overlayAccess.moveRatio(1.0, 500, function() {opacityAccess.moveAuto(newRatio);}); // Make old image visible in a nice way + } else { + opacityAccess.moveAuto(newRatio) + } + } + e.preventDefault(); + }); + + // Blinking before and after images is a good way for the human eye to catch differences. + var $blinker = $this.find('.imgdiff-blink'); + var initialOpacity = null; + $blinker.on('click', function(e) { + if (blinking) { + window.clearTimeout(blinking); + $blinker.children('img').first().css('border', '1px solid transparent'); + opacityAccess.setRatio(initialOpacity); + blinking = null; + } else { + $blinker.children('img').first().css('border', '1px solid #AAA'); + initialOpacity = opacityAccess.getRatio(); + var currentOpacity = 1.0; + function blink() { + opacityAccess.setRatio(currentOpacity); + currentOpacity = 1.0 - currentOpacity; + // Keep frequeny below 2Hz (i.e., delay above 500ms) + blinking = window.setTimeout(blink, 600); + } + if ($div.width() <= 0) { + overlayAccess.moveRatio(1.0, 500, blink); + } else { + blink(); + } + } + e.preventDefault(); + }); + + // Subtracting before and after images is another good way to detect differences. Result will be + // black where identical. + if (typeof $img[0].style.mixBlendMode != 'undefined') { + // Feature test: does the browser support the mix-blend-mode CSS property from the Compositing + // and Blending Level 1 spec (http://dev.w3.org/fxtf/compositing-1/#mix-blend-mode )? + // As of 2014-11, only Firefox >= 32 and Safari >= 7.1 support this. Other browsers will have to + // make do with the blink comparator only. + var $sub = $this.find('.imgdiff-subtract'); + $sub.css('display', 'inline-block'); + $sub.on('click', function (e) { + var curr = $img.css('mix-blend-mode'); + if (curr != 'difference') { + curr = 'difference'; + $sub.children('img').first().css('border', '1px solid #AAA'); + if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500); + opacityAccess.setRatio(0); + } else { + curr = 'normal'; + $sub.children('img').first().css('border', '1px solid transparent'); + + } + $img.css('mix-blend-mode', curr); + e.preventDefault(); + }); + } + }); +} + +$(setup); // Run on jQuery's dom-ready + +})(jQuery);
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java b/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java index d8dcdced..2c880247 100644 --- a/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java @@ -26,8 +26,8 @@ import org.apache.wicket.markup.html.form.RadioGroup; import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
@@ -51,8 +51,6 @@ public class AccessPolicyPanel extends BasePanel { private RadioGroup<AccessPolicy> policiesGroup;
- private IModel<Boolean> allowForks;
-
public AccessPolicyPanel(String wicketId, RepositoryModel repository) {
this(wicketId, repository, null);
}
@@ -146,13 +144,12 @@ public class AccessPolicyPanel extends BasePanel { }
add(policiesGroup);
- allowForks = Model.of(app().settings().getBoolean(Keys.web.allowForking, true));
- if (allowForks.getObject()) {
+ if (app().settings().getBoolean(Keys.web.allowForking, true)) {
Fragment fragment = new Fragment("allowForks", "allowForksFragment", this);
fragment.add(new BooleanOption("allowForks",
getString("gb.allowForks"),
getString("gb.allowForksDescription"),
- allowForks));
+ new PropertyModel<Boolean>(repository, "allowForks")));
add(fragment);
} else {
add(new Label("allowForks").setVisible(false));
@@ -165,7 +162,6 @@ public class AccessPolicyPanel extends BasePanel { AccessPolicy policy = policiesGroup.getModelObject();
repository.authorizationControl = policy.control;
repository.accessRestriction = policy.type;
- repository.allowForks = allowForks.getObject();
}
@Override
diff --git a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java index 35513bb3..062df84e 100644 --- a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java @@ -76,7 +76,7 @@ public class ActivityPanel extends BasePanel { .getWhen(), getTimeZone(), getTimeUtils()));
// avatar
- commitItem.add(new GravatarImage("avatar", commit.getAuthorIdent(), 40));
+ commitItem.add(new AvatarImage("avatar", commit.getAuthorIdent(), 40));
// merge icon
if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.html b/src/main/java/com/gitblit/wicket/panels/AvatarImage.html index 846a8f1b..846a8f1b 100644 --- a/src/main/java/com/gitblit/wicket/panels/GravatarImage.html +++ b/src/main/java/com/gitblit/wicket/panels/AvatarImage.html diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java b/src/main/java/com/gitblit/wicket/panels/AvatarImage.java index e4157577..a562042d 100644 --- a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java +++ b/src/main/java/com/gitblit/wicket/panels/AvatarImage.java @@ -17,9 +17,9 @@ package com.gitblit.wicket.panels; import org.eclipse.jgit.lib.PersonIdent; +import com.gitblit.AvatarGenerator; import com.gitblit.Keys; import com.gitblit.models.UserModel; -import com.gitblit.utils.ActivityUtils; import com.gitblit.wicket.ExternalImage; import com.gitblit.wicket.WicketUtils; @@ -29,36 +29,31 @@ import com.gitblit.wicket.WicketUtils; * @author James Moger * */ -public class GravatarImage extends BasePanel { +public class AvatarImage extends BasePanel { private static final long serialVersionUID = 1L; - public GravatarImage(String id, PersonIdent person) { + public AvatarImage(String id, PersonIdent person) { this(id, person, 0); } - public GravatarImage(String id, PersonIdent person, int width) { + public AvatarImage(String id, PersonIdent person, int width) { this(id, person.getName(), person.getEmailAddress(), "gravatar", width, true); } - public GravatarImage(String id, PersonIdent person, String cssClass, int width, boolean identicon) { + public AvatarImage(String id, PersonIdent person, String cssClass, int width, boolean identicon) { this(id, person.getName(), person.getEmailAddress(), cssClass, width, identicon); } - public GravatarImage(String id, UserModel user, String cssClass, int width, boolean identicon) { + public AvatarImage(String id, UserModel user, String cssClass, int width, boolean identicon) { this(id, user.getDisplayName(), user.emailAddress, cssClass, width, identicon); } - public GravatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean identicon) { + public AvatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean identicon) { super(id); - String email = emailaddress == null ? username.toLowerCase() : emailaddress.toLowerCase(); - String url; - if (identicon) { - url = ActivityUtils.getGravatarIdenticonUrl(email, width); - } else { - url = ActivityUtils.getGravatarThumbnailUrl(email, width); - } + AvatarGenerator avatarGenerator = app().runtime().getInjector().getInstance(AvatarGenerator.class); + String url = avatarGenerator.getURL(username, emailaddress, identicon, width); ExternalImage image = new ExternalImage("image", url); if (cssClass != null) { WicketUtils.setCssClass(image, cssClass); diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java index 4bf00f8a..7a564aa5 100644 --- a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java @@ -114,11 +114,13 @@ public class BranchesPanel extends BasePanel { public void populateItem(final Item<RefModel> item) {
final RefModel entry = item.getModelObject();
+ PageParameters shortUniqRef = WicketUtils.newObjectParameter(model.name,
+ Repository.shortenRefName(entry.getName()));
+
item.add(WicketUtils.createDateLabel("branchDate", entry.getDate(), getTimeZone(), getTimeUtils()));
item.add(new LinkPanel("branchName", "list name", StringUtils.trimString(
- entry.displayName, 28), LogPage.class, WicketUtils.newObjectParameter(
- model.name, entry.getName())));
+ entry.displayName, 28), LogPage.class, shortUniqRef));
String author = entry.getAuthorIdent().getName();
LinkPanel authorLink = new LinkPanel("branchAuthor", "list", author,
@@ -131,8 +133,7 @@ public class BranchesPanel extends BasePanel { String shortMessage = entry.getShortMessage();
String trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
LinkPanel shortlog = new LinkPanel("branchLog", "list subject", trimmedMessage,
- CommitPage.class, WicketUtils.newObjectParameter(model.name,
- entry.getName()));
+ CommitPage.class, shortUniqRef);
if (!shortMessage.equals(trimmedMessage)) {
shortlog.setTooltip(shortMessage);
}
@@ -140,27 +141,22 @@ public class BranchesPanel extends BasePanel { if (maxCount <= 0) {
Fragment fragment = new Fragment("branchLinks", showDelete? "branchPageAdminLinks" : "branchPageLinks", this);
- fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
- fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
+ fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, shortUniqRef));
+ fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, shortUniqRef));
String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null);
- fragment.add(new ExternalLink("raw", rawUrl));
- fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
- WicketUtils.newObjectParameter(model.name, entry.getName())));
+ fragment.add(new ExternalLink("raw", rawUrl));
+ fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class, shortUniqRef));
fragment.add(new ExternalLink("syndication", SyndicationServlet.asLink(
getRequest().getRelativePathPrefixToContextRoot(), model.name,
- entry.getName(), 0)));
+ Repository.shortenRefName(entry.getName()), 0)));
if (showDelete) {
fragment.add(createDeleteBranchLink(model, entry));
}
item.add(fragment);
} else {
Fragment fragment = new Fragment("branchLinks", "branchPanelLinks", this);
- fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
- fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
- .newObjectParameter(model.name, entry.getName())));
+ fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, shortUniqRef));
+ fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, shortUniqRef));
String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null);
fragment.add(new ExternalLink("raw", rawUrl));
item.add(fragment);
diff --git a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java index eb757501..249cd4ac 100644 --- a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java @@ -44,6 +44,6 @@ public class CommitHeaderPanel extends BasePanel { add(new Label("commitid", c.getName()));
add(new Label("author", c.getAuthorIdent().getName()));
add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
- add(new GravatarImage("authorAvatar", c.getAuthorIdent()));
+ add(new AvatarImage("authorAvatar", c.getAuthorIdent()));
}
}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java index decfda50..0c80f997 100644 --- a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java @@ -166,7 +166,7 @@ public class DigestsPanel extends BasePanel { } else if (isTag) { // link to tag logItem.add(new LinkPanel("refChanged", null, shortRefName, - TagPage.class, WicketUtils.newObjectParameter(change.repository, fullRefName))); + TagPage.class, WicketUtils.newObjectParameter(change.repository, shortRefName))); } else if (isTicket) { // link to ticket logItem.add(new LinkPanel("refChanged", null, shortRefName, @@ -174,7 +174,7 @@ public class DigestsPanel extends BasePanel { } else { // link to tree logItem.add(new LinkPanel("refChanged", null, shortRefName, - TreePage.class, WicketUtils.newObjectParameter(change.repository, fullRefName))); + TreePage.class, WicketUtils.newObjectParameter(change.repository, shortRefName))); } // to/from/etc @@ -216,7 +216,7 @@ public class DigestsPanel extends BasePanel { final RepositoryCommit commit = commitItem.getModelObject(); // author gravatar - commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false)); + commitItem.add(new AvatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false)); // merge icon if (commit.getParentCount() > 1) { diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java index 4433b043..e4ce5ced 100644 --- a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java +++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java @@ -125,7 +125,15 @@ public class FilterableRepositoryList extends BasePanel { item.t = getTimeUtils().timeAgo(repo.lastChange);
item.d = df.format(repo.lastChange);
item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name));
- item.wc = repo.isBare ? 0 : 1;
+ if (!repo.isBare) {
+ item.y = 3;
+ } else if (repo.isMirror) {
+ item.y = 2;
+ } else if (repo.isFork()) {
+ item.y = 1;
+ } else {
+ item.y = 0;
+ }
list.add(item);
}
@@ -147,6 +155,6 @@ public class FilterableRepositoryList extends BasePanel { String i; // information/description
long s; // stars
String c; // html color
- int wc; // working copy: 1 = true, 0 = false
+ int y; // type: 0 = normal, 1 = fork, 2 = mirror, 3 = clone
}
}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java index e1706a09..a3f127b1 100644 --- a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java @@ -116,7 +116,7 @@ public class HistoryPanel extends BasePanel { }
} catch (Exception e) {
} finally {
- tw.release();
+ tw.close();
}
}
}
@@ -160,7 +160,7 @@ public class HistoryPanel extends BasePanel { @Override
public void populateItem(final Item<RevCommit> item) {
final RevCommit entry = item.getModelObject();
- final Date date = JGitUtils.getCommitDate(entry);
+ final Date date = JGitUtils.getAuthorDate(entry);
item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/LogPanel.java b/src/main/java/com/gitblit/wicket/panels/LogPanel.java index 16fc746e..e9d240d0 100644 --- a/src/main/java/com/gitblit/wicket/panels/LogPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/LogPanel.java @@ -109,7 +109,7 @@ public class LogPanel extends BasePanel { @Override
public void populateItem(final Item<RevCommit> item) {
final RevCommit entry = item.getModelObject();
- final Date date = JGitUtils.getCommitDate(entry);
+ final Date date = JGitUtils.getAuthorDate(entry);
final boolean isMerge = entry.getParentCount() > 1;
item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html index 33345a0a..88de3b4c 100644 --- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html @@ -24,13 +24,28 @@ <p class="originRepository" style="margin-left:20px;" ><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
</wicket:fragment>
+ <wicket:fragment wicket:id="repoIconFragment">
+ <span class="mega-octicon octicon-repo"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="mirrorIconFragment">
+ <span class="mega-octicon octicon-mirror"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="forkIconFragment">
+ <span class="mega-octicon octicon-repo-forked"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="cloneIconFragment">
+ <span class="mega-octicon octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+ </wicket:fragment>
+
<div>
<div style="padding-top:15px;padding-bottom:15px;margin-right:15px;">
<div class="pull-right" style="text-align:right;padding-right:15px;">
<span wicket:id="repositoryLinks"></span>
<div>
<img class="inlineIcon" wicket:id="sparkleshareIcon" />
- <img class="inlineIcon" wicket:id="mirrorIcon" />
<img class="inlineIcon" wicket:id="frozenIcon" />
<img class="inlineIcon" wicket:id="federatedIcon" />
@@ -42,11 +57,15 @@ </div>
<div class="pageTitle" style="border:0px;">
- <div>
- <span class="repositorySwatch" wicket:id="repositorySwatch"></span>
- <span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
- </div>
- <span wicket:id="originRepository">[origin repository]</span>
+ <div style="display:inline-block;vertical-align:top;padding-right:5px;">
+ <span wicket:id="repoIcon"></span>
+ </div>
+ <div style="display:inline-block;">
+ <div>
+ <span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
+ </div>
+ <span wicket:id="originRepository">[origin repository]</span>
+ </div>
</div>
<div style="padding-left:20px;">
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java index 8630d201..efcb1cb6 100644 --- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java @@ -51,24 +51,27 @@ public class ProjectRepositoryPanel extends BasePanel { final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true);
- // repository swatch
- Component swatch;
- if (entry.isBare) {
- swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false);
- } else {
- swatch = new Label("repositorySwatch", "!");
- WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", parent));
- }
- WicketUtils.setCssBackground(swatch, entry.toString());
- add(swatch);
- swatch.setVisible(showSwatch);
-
PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath,
StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp));
add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils
.isEmpty(entry.description)));
+ Fragment iconFragment;
+ if (entry.isMirror) {
+ iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);
+ } else if (entry.isFork()) {
+ iconFragment = new Fragment("repoIcon", "forkIconFragment", this);
+ } else if (entry.isBare) {
+ iconFragment = new Fragment("repoIcon", "repoIconFragment", this);
+ } else {
+ iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);
+ }
+ if (showSwatch) {
+ WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));
+ }
+ add(iconFragment);
+
if (StringUtils.isEmpty(entry.originRepository)) {
add(new Label("originRepository").setVisible(false));
} else {
@@ -84,13 +87,7 @@ public class ProjectRepositoryPanel extends BasePanel { add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
}
- if (entry.isMirror) {
- add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png", localizer.getString("gb.isMirror", parent)));
- } else {
- add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
- }
-
- if (entry.isFrozen) {
+ if (!entry.isMirror && entry.isFrozen) {
add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", parent)));
} else {
add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
@@ -161,6 +158,7 @@ public class ProjectRepositoryPanel extends BasePanel { add(new Label("repositorySize", localizer.getString("gb.empty", parent)).setEscapeModelStrings(false));
}
- add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
+ add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
+ .getRelativePathPrefixToContextRoot(), entry.name, null, 0)));
}
}
diff --git a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java index baefc6bd..2235fd3d 100644 --- a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java @@ -271,7 +271,7 @@ public class ReflogPanel extends BasePanel { final RepositoryCommit commit = commitItem.getModelObject(); // author gravatar - commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false)); + commitItem.add(new AvatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false)); // merge icon if (commit.getParentCount() > 1) { diff --git a/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java index f37cc2aa..447e1789 100644 --- a/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java @@ -145,7 +145,7 @@ public class RegistrantPermissionsPanel extends BasePanel { } Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this); - userFragment.add(new GravatarImage("userAvatar", ident, 20)); + userFragment.add(new AvatarImage("userAvatar", ident, 20)); userFragment.add(new Label("userName", entry.registrant)); item.add(userFragment); } else { diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html index e2e7b72b..2de52b09 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html @@ -17,6 +17,22 @@ </tr>
</tbody>
</table>
+
+ <wicket:fragment wicket:id="repoIconFragment">
+ <span class="octicon octicon-centered octicon-repo"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="mirrorIconFragment">
+ <span class="octicon octicon-centered octicon-mirror"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="forkIconFragment">
+ <span class="octicon octicon-centered octicon-repo-forked"></span>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="cloneIconFragment">
+ <span class="octicon octicon-centered octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+ </wicket:fragment>
<wicket:fragment wicket:id="adminLinks">
<!-- page nav links -->
@@ -76,10 +92,10 @@ </wicket:fragment>
<wicket:fragment wicket:id="repositoryRow">
- <td class="left" style="padding-left:3px;" ><b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
+ <td class="left" style="padding-left:3px;" ><span wicket:id="repoIcon"></span><span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
<td class="hidden-phone"><span class="list" wicket:id="repositoryDescription">[repository description]</span></td>
<td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
- <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="mirrorIcon" /><img class="inlineIcon" wicket:id="forkIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+ <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
<td><span wicket:id="repositoryLastChange">[last change]</span></td>
<td class="rightAlign hidden-phone" style="text-align: right;padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span></td>
</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java index 8573e1a6..c3f07099 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -24,7 +24,6 @@ import java.util.Iterator; import java.util.List;
import java.util.Map;
-import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -183,24 +182,28 @@ public class RepositoriesPanel extends BasePanel { Fragment row = new Fragment("rowContent", "repositoryRow", this);
item.add(row);
+ // show colored repository type icon
+ Fragment iconFragment;
+ if (entry.isMirror) {
+ iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);
+ } else if (entry.isFork()) {
+ iconFragment = new Fragment("repoIcon", "forkIconFragment", this);
+ } else if (entry.isBare) {
+ iconFragment = new Fragment("repoIcon", "repoIconFragment", this);
+ } else {
+ iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);
+ }
+ if (showSwatch) {
+ WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));
+ }
+ row.add(iconFragment);
+
// try to strip group name for less cluttered list
String repoName = entry.toString();
if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
repoName = repoName.substring(currGroupName.length() + 1);
}
- // repository swatch
- Component swatch;
- if (entry.isBare){
- swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false);
- } else {
- swatch = new Label("repositorySwatch", "!");
- WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
- }
- WicketUtils.setCssBackground(swatch, entry.toString());
- row.add(swatch);
- swatch.setVisible(showSwatch);
-
if (linksActive) {
Class<? extends BasePage> linkPage = SummaryPage.class;
PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
@@ -228,21 +231,7 @@ public class RepositoriesPanel extends BasePanel { row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
}
- if (entry.isMirror) {
- row.add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png",
- getString("gb.isMirror")));
- } else {
- row.add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
- }
-
- if (entry.isFork()) {
- row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
- getString("gb.isFork")));
- } else {
- row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
- }
-
- if (entry.isFrozen) {
+ if (!entry.isMirror && entry.isFrozen) {
row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
getString("gb.isFrozen")));
} else {
@@ -255,24 +244,30 @@ public class RepositoriesPanel extends BasePanel { } else {
row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
}
- switch (entry.accessRestriction) {
- case NONE:
- row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
- break;
- case PUSH:
- row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
- accessRestrictionTranslations.get(entry.accessRestriction)));
- break;
- case CLONE:
- row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
- accessRestrictionTranslations.get(entry.accessRestriction)));
- break;
- case VIEW:
- row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
- accessRestrictionTranslations.get(entry.accessRestriction)));
- break;
- default:
- row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+
+ if (entry.isMirror) {
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
+ getString("gb.isMirror")));
+ } else {
+ switch (entry.accessRestriction) {
+ case NONE:
+ row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+ break;
+ case PUSH:
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+ accessRestrictionTranslations.get(entry.accessRestriction)));
+ break;
+ case CLONE:
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+ accessRestrictionTranslations.get(entry.accessRestriction)));
+ break;
+ case VIEW:
+ row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+ accessRestrictionTranslations.get(entry.accessRestriction)));
+ break;
+ default:
+ row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+ }
}
String owner = "";
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java index 938226a6..207f1250 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java @@ -80,11 +80,11 @@ public class RepositoryUrlPanel extends BasePanel { HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
- List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
+ List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
// grab primary url from the top of the list
primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
- boolean canClone = primaryUrl != null && ((primaryUrl.permission == null) || primaryUrl.permission.atLeast(AccessPermission.CLONE));
+ boolean canClone = primaryUrl != null && (!primaryUrl.hasPermission() || primaryUrl.permission.atLeast(AccessPermission.CLONE));
if (repositoryUrls.size() == 0 || !canClone) {
// no urls, nothing to show.
@@ -145,7 +145,7 @@ public class RepositoryUrlPanel extends BasePanel { fragment.add(content);
item.add(fragment);
- Label permissionLabel = new Label("permission", repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+ Label permissionLabel = new Label("permission", repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
String tooltip = getProtocolPermissionDescription(repository, repoUrl);
WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
@@ -165,7 +165,7 @@ public class RepositoryUrlPanel extends BasePanel { if (repository.isMirror) {
urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
getString("gb.isMirror")));
- } else if (app().gitblit().isServingRepositories()) {
+ } else if (app().services().isServingRepositories()) {
switch (repository.accessRestriction) {
case NONE:
urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
@@ -201,7 +201,7 @@ public class RepositoryUrlPanel extends BasePanel { urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
- Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.isExternal() ? externalPermission : primaryUrl.permission.toString());
+ Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.hasPermission() ? primaryUrl.permission.toString() : externalPermission);
String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
urlPanel.add(permissionLabel);
@@ -234,8 +234,8 @@ public class RepositoryUrlPanel extends BasePanel { // filter the urls for the client app
List<RepositoryUrl> urls = new ArrayList<RepositoryUrl>();
for (RepositoryUrl repoUrl : repositoryUrls) {
- if (clientApp.minimumPermission == null || repoUrl.permission == null) {
- // no minimum permission or external permissions, assume it is satisfactory
+ if (clientApp.minimumPermission == null || !repoUrl.hasPermission()) {
+ // no minimum permission or untracked permissions, assume it is satisfactory
if (clientApp.supportsTransport(repoUrl.url)) {
urls.add(repoUrl);
}
@@ -339,7 +339,7 @@ public class RepositoryUrlPanel extends BasePanel { }
protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
- Label permissionLabel = new Label(wicketId, repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+ Label permissionLabel = new Label(wicketId, repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
String tooltip = getProtocolPermissionDescription(repository, repoUrl);
WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
@@ -369,18 +369,7 @@ public class RepositoryUrlPanel extends BasePanel { RepositoryUrl repoUrl) {
if (!urlPermissionsMap.containsKey(repoUrl.url)) {
String note;
- if (repoUrl.isExternal()) {
- String protocol;
- int protocolIndex = repoUrl.url.indexOf("://");
- if (protocolIndex > -1) {
- // explicit protocol specified
- protocol = repoUrl.url.substring(0, protocolIndex);
- } else {
- // implicit SSH url
- protocol = "ssh";
- }
- note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
- } else {
+ if (repoUrl.hasPermission()) {
note = null;
String key;
switch (repoUrl.permission) {
@@ -411,6 +400,17 @@ public class RepositoryUrlPanel extends BasePanel { String description = MessageFormat.format(pattern, repoUrl.permission.toString());
note = description;
}
+ } else {
+ String protocol;
+ int protocolIndex = repoUrl.url.indexOf("://");
+ if (protocolIndex > -1) {
+ // explicit protocol specified
+ protocol = repoUrl.url.substring(0, protocolIndex);
+ } else {
+ // implicit SSH url
+ protocol = "ssh";
+ }
+ note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
}
urlPermissionsMap.put(repoUrl.url, note);
}
diff --git a/src/main/java/com/gitblit/wicket/panels/SearchPanel.java b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java index 5d0b2de1..09322bc7 100644 --- a/src/main/java/com/gitblit/wicket/panels/SearchPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java @@ -86,7 +86,7 @@ public class SearchPanel extends BasePanel { @Override
public void populateItem(final Item<RevCommit> item) {
final RevCommit entry = item.getModelObject();
- final Date date = JGitUtils.getCommitDate(entry);
+ final Date date = JGitUtils.getAuthorDate(entry);
item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html index ff689292..2cce7b18 100644 --- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html @@ -15,16 +15,18 @@ <img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="users_16x16.png"/>
<wicket:message key="gb.teams">[teams]</wicket:message>
</th>
+ <th class="hidden-phone" style="width:140px;"><wicket:message key="gb.type">[type]</wicket:message></th>
<th class="hidden-phone" style="width:140px;"><wicket:message key="gb.teamMembers">[team members]</wicket:message></th>
<th class="hidden-phone" style="width:100px;"><wicket:message key="gb.repositories">[repositories]</wicket:message></th>
<th style="width:80px;" class="right"></th>
</tr>
- <tbody>
+ <tbody>
<tr wicket:id="teamRow">
<td class="left" ><div class="list" wicket:id="teamname">[teamname]</div></td>
+ <td class="hidden-phone left" ><span style="font-size: 0.8em;" wicket:id="accountType">[account type]</span></td>
<td class="hidden-phone left" ><div class="list" wicket:id="members">[members]</div></td>
<td class="hidden-phone left" ><div class="list" wicket:id="repositories">[repositories]</div></td>
- <td class="rightAlign"><span wicket:id="teamLinks"></span></td>
+ <td class="rightAlign"><span wicket:id="teamLinks"></span></td>
</tr>
</tbody>
</table>
diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java index c1e1a43d..7f3fd9a2 100644 --- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java @@ -60,6 +60,7 @@ public class TeamsPanel extends BasePanel { EditTeamPage.class, WicketUtils.newTeamnameParameter(entry.name));
WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry.name);
item.add(editLink);
+ item.add(new Label("accountType", entry.accountType.name()));
item.add(new Label("members", entry.users.size() > 0 ? ("" + entry.users.size())
: ""));
item.add(new Label("repositories",
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html index 30f50367..659baeac 100644 --- a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html +++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html @@ -30,6 +30,9 @@ <td class="hidden-phone ticket-list-state">
<i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
</td>
+ <td class="ticket-list-priority">
+ <div wicket:id="priority"></div>
+ </td>
<td class="ticket-list-state">
<div wicket:id="status"></div>
</td>
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java index cc0b57a8..1fbe87cd 100644 --- a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java @@ -83,7 +83,10 @@ public class TicketListPanel extends BasePanel { item.add(new Label("ticketsLink").setVisible(false)); } - item.add(TicketsUI.getStateIcon("state", ticket.type, ticket.status)); + Label icon = TicketsUI.getStateIcon("state", ticket.type, ticket.status, ticket.severity); + WicketUtils.addCssClass(icon, TicketsUI.getSeverityClass(ticket.severity)); + item.add(icon); + item.add(new Label("id", "" + ticket.number)); UserModel creator = app().users().getUserModel(ticket.createdBy); if (creator != null) { @@ -153,7 +156,7 @@ public class TicketListPanel extends BasePanel { if (responsible == null) { responsible = new UserModel(ticket.responsible); } - GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(), + AvatarImage avatar = new AvatarImage("responsible", responsible.getDisplayName(), responsible.emailAddress, null, 16, true); avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName()); item.add(avatar); @@ -167,6 +170,11 @@ public class TicketListPanel extends BasePanel { // watching indicator item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername()))); + // priority indicator + Label priorityIcon = TicketsUI.getPriorityIcon("priority", ticket.priority); + WicketUtils.addCssClass(priorityIcon, TicketsUI.getPriorityClass(ticket.priority)); + item.add(priorityIcon.setVisible(true)); + // status indicator String css = TicketsUI.getLozengeClass(ticket.status, true); Label l = new Label("status", ticket.status.toString()); diff --git a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java index 2bf5ee7b..063889b2 100644 --- a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java +++ b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java @@ -25,7 +25,7 @@ public class UserTitlePanel extends BasePanel { public UserTitlePanel(String wicketId, UserModel user, String title) {
super(wicketId);
- add(new GravatarImage("userGravatar", user, "gravatar", 36, false));
+ add(new AvatarImage("userGravatar", user, "gravatar", 36, false));
add(new Label("userDisplayName", user.getDisplayName()));
add(new Label("userTitle", title));
}
diff --git a/src/main/java/login_zh_TW.mkd b/src/main/java/login_zh_TW.mkd new file mode 100644 index 00000000..68cfedf4 --- /dev/null +++ b/src/main/java/login_zh_TW.mkd @@ -0,0 +1,3 @@ +## 請登入 + +請輸入密碼,以便登入此Gitblit版控伺服器 diff --git a/src/main/java/welcome_zh_TW.mkd b/src/main/java/welcome_zh_TW.mkd new file mode 100644 index 00000000..eaaee652 --- /dev/null +++ b/src/main/java/welcome_zh_TW.mkd @@ -0,0 +1,3 @@ +## 歡迎來到Gitblit版本控管伺服器 + +一個快速讓您能存放自己Git文件庫的解決方案 [Git](http://www.git-scm.com) diff --git a/src/main/resources/blink32.png b/src/main/resources/blink32.png Binary files differnew file mode 100644 index 00000000..da593505 --- /dev/null +++ b/src/main/resources/blink32.png diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css index 748a3198..0cc8fd02 100644 --- a/src/main/resources/gitblit.css +++ b/src/main/resources/gitblit.css @@ -38,6 +38,19 @@ a.bugtraq { font-weight: bold;
}
+.gray {
+ color: #888;
+}
+
+.octicon-centered {
+ text-align: center;
+ width: 16px;
+}
+
+tr:hover .octicon-centered {
+ color:#eee;
+}
+
.label a.bugtraq { font-weight: normal; color: white; @@ -237,14 +250,13 @@ div.dashboardTitle small { }
.repositorynavbar {
- background-color: #fbfbfb;
+ background-color: #f8f8f8;
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
}
.repositorynavbar .title {
- line-height: 32px;
- padding: 5px 0px;
+ padding: 10px 0px;
}
.repositorynavbar .repository {
@@ -782,6 +794,10 @@ pre.prettyprint ol { td.ticket-list-state { vertical-align: middle; +}
+
+td.ticket-list-priority {
+ vertical-align: middle;
} .ticket-list-details { @@ -1334,19 +1350,6 @@ span.diff.unchanged { font-family: inherit;
}
-div.diff.hunk_header {
- -moz-border-bottom-colors: none;
- -moz-border-image: none;
- -moz-border-left-colors: none;
- -moz-border-right-colors: none;
- -moz-border-top-colors: none;
- border-color: #FFE0FF;
- border-style: dotted;
- border-width: 1px 0 0;
- margin-top: 2px;
- font-family: inherit;
-}
-
span.diff.hunk_info {
background-color: #FFEEFF;
color: #990099;
@@ -1358,62 +1361,242 @@ span.diff.hunk_section { font-family: inherit;
}
-div.diff.add2 {
- background-color: #DDFFDD;
- font-family: inherit;
+.diff-cell {
+ margin: 0px;
+ padding: 0 2px;
+ border: 0;
+ border-left: 1px solid #bbb;
+}
+
+.add2 {
+ background-color: #DDFFDD;
}
-div.diff.remove2 {
+.remove2 {
background-color: #FFDDDD;
- font-family: inherit;
}
-div.diff table {
+.context2 {
+ background-color: #FEFEFE;
+}
+
+.trailingws-add {
+ background-color: #99FF99;
+}
+
+.trailingws-sub {
+ background-color: #FF9999;
+}
+
+div.diff > table {
border-radius: 0;
border-right: 1px solid #bbb;
border-bottom: 1px solid #bbb;
width: 100%;
}
-div.diff table th, div.diff table td {
- margin: 0px;
- padding: 0px;
- font-family: monospace;
- border: 0;
+.diff-line {
+ background-color: #fbfbfb;
+ text-align: center;
+ color: #999;
+ padding-left: 2px;
+ padding-right: 2px;
+ width: 3em; /* Font-size relative! */
+ min-width: 3em;
+}
+
+.diff-line:before {
+ content: attr(data-lineno);
}
-div.diff table th {
- background-color: #f0f0f0;
+.diff-state {
+ background-color: #fbfbfb;
text-align: center;
color: #999;
- padding-left: 5px;
- padding-right: 5px;
- width: 30px;
+ padding-left: 2px;
+ padding-right: 2px;
+ width: 0.5em; /* Font-size relative! */
}
-div.diff table th.header {
- background-color: #D2C3AF;
- border-right: 0px;
- border-bottom: 1px solid #808080;
- font-family: inherit;
- font-size:0.9em;
- color: black;
- padding: 2px;
- text-align: left;
+.diff-state-add:before {
+ color: green;
+ font-weight: bold;
+ content: '+';
}
-div.diff table td.hunk_header {
+.diff-state-sub:before {
+ color: red;
+ font-weight: bold;
+ content: '-';
+}
+
+.hunk_header {
background-color: #dAe2e5 !important;
+ border-left: 1px solid #bbb;
border-top: 1px solid #bac2c5;
border-bottom: 1px solid #bac2c5;
color: #555;
}
-div.diff table td {
- border-left: 1px solid #bbb;
- background-color: #fbfbfb;
+/* Image diffs. */
+
+/* Note: can't use gradients; IE < 10 doesn't support them. Use pre-created pngs with transparency instead. */
+
+/* Set on body during mouse tracking. */
+.no-select {
+ -webkit-touch-callout:none;
+ -webkit-user-select:none;
+ -khtml-user-select:none;
+ -moz-user-select:none;
+ -ms-user-select:none;
+ user-select:none;
}
+div.imgdiff-container {
+ padding: 10px;
+ background: #EEE;
+}
+
+div.imgdiff {
+ margin: 10px 20px;
+ position:relative;
+ display: inline-block;
+ /* Checkerboard background to reveal transparency. */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAK0lEQVQoz2O8e/cuAzagpKSEVZyJgUQwqoEYwPj//3+sEvfu3RsNJfppAACQZwizxs5QrAAAAABJRU5ErkJggg==);
+ background-repeat: repeat;
+ /* Same with CSS:
+ background-color: white;
+ background-image: linear-gradient(45deg, #DDD 25%, transparent 25%, transparent 75%, #DDD 75%, #DDD), linear-gradient(45deg, #DDD 25%, transparent 25%, transparent 75%, #DDD 75%, #DDD);
+ background-size:16px 16px;
+ background-position:0 0, 8px 8px;
+ */
+}
+
+div.imgdiff-left {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 0;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+img.imgdiff {
+ user-select: none;
+ border: 1px solid #0F0;
+}
+img.imgdiff-old {
+ user-select: none;
+ border: 1px solid #F00;
+}
+
+.imgdiff-opa-container {
+ display: inline-block;
+ width: 200px;
+ height: 4px;
+ margin: 12px 35px 6px 35px;
+ padding: 0;
+ position: relative;
+ border: 1px solid #888;
+ background-color: #DDD;
+}
+
+.imgdiff-opa-container:before {
+ content: '';
+ position: absolute;
+ left: -20px;
+ top: -4px;
+ width : 12px;
+ height: 12px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7klEQVQoz13STUrDMRAF8N+02fQDhCpqNyJIvUEXXfYCXtlFsZeQLpRCpYJ01RakxM38IRgIQyZv3rx5SchVaw1c4xm36OEb7/jqcJHgPuZYYopx3h2xwwrriPgtyTzHCx5xg1ESnvJ8lR1fS8pYJvgBEwyywwlD9LGotX6W1DxNpknutkPgnGSzkgOOEzRoogY8zPx9L7VFa0Ku//lAlLTumO1PjYxuhnMT9yV93uUMw2Q+NwU/OGCL95KPskrr+o3mruCAD7xhU1LjOpkX6caoGXib4HVEXKLWKiK67/GEGe6SYJ+SNxFxqbX2/gBxKkhxx1tQIAAAAABJRU5ErkJggg==);
+ /* With CSS: background-image: radial-gradient(6px at 50% 50%, rgba(255, 255, 255, 255) 50%, rgba(255, 255, 255, 0) 6px); */
+}
+
+.imgdiff-opa-container:after {
+ content: '';
+ position: absolute;
+ right: -20px;
+ top: -4px;
+ width : 12px;
+ height: 12px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlklEQVQoz02Rr45TYRTE5/z7ynebYNBIFFuLXV2FQRIgWcUDkN3bBNnbR0CQJvsGGLCbIEFgSCrgGXCw7b3fOT2ILQm/ZJIRI2YyhBObzaZm5kVmvszMs8wkJtoR0ZZUr/vLyz/DMBCdwg+PcfzUvC3cHRGBTICZUYpBVb+KyPO+73/SZrOpEccv03hYHMbRp9bk6EEJQITTzFqttZjZ59ls9kwz88K9LQ7j6IfDXsdxgrcGEEFEqJRSAAQzn7fWXmtmvnJ3tNZkHCfc3t5iahOQgJWCBKCq1FqDiDzVzHwcEYgIcndMU8N+v0dmogNQzODufLcrzxiZhAQyEwBASGSedPL/wyDaMTNEJEUEpRi6rkPXdShWoKoQkWBmENGOmWhrxWBmrZSCe7ViPp9jPp+j1opSSpoZmxmY+YOS6rVmvqi1PgEQqkruzgAgImFmXGslVb1h5u2/4x65+/uIOG+tISLu+jLDzKCqN6r6pu/7bzIMA61Wq1/L5fIjgN8ict/MHphZmNl3VX1nJm+vrvof6/Xa/gJuQtCzTqj1dQAAAABJRU5ErkJggg==);
+ /* With CSS: background-image: radial-gradient(6px at 50% 50%, #888, #888 1px, transparent 6px); */
+}
+
+.imgdiff-opa-slider {
+ position:absolute;
+ top : 0;
+ left: -5px;
+ bottom: 0;
+ right: -5px;
+ text-align: left;
+}
+
+.imgdiff-opa-handle {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ top: -3px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+ /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-ovr-slider {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ text-align: left;
+}
+
+.imgdiff-ovr-handle {
+ display: inline-block;
+ width : 1px;
+ height: 100%;
+ top: 0px;
+ background-color: #444;
+ border-right: 1px solid #FFF;
+}
+
+.imgdiff-ovr-handle:before {
+ content: '';
+ position: absolute;
+ right: -4px;
+ bottom: -5px;
+ width : 10px;
+ height: 10px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+ /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-ovr-handle:after {
+ content: '';
+ position: absolute;
+ right: -4px;
+ top: -5px;
+ width : 10px;
+ height: 10px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+ /* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-link {
+ margin: 0px 4px;
+ text-decoration: none;
+ border: none;
+}
+
+.imgdiff-link > img {
+ border: 1px solid transparent; /* Avoid jumping when we change the border */
+ width: 20px;
+ height: 20px;
+ margin-bottom: 10px;
+}
+
+/* End image diffs */
+
td.changeType {
width: 15px;
}
@@ -1546,12 +1729,22 @@ table.pretty tr.commit { }
}
+td.sha256 {
+ max-width: 20em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
table.comments td {
padding: 4px;
line-height: 17px;
}
-table.repositories {
+table.projectlist {
+ margin-top: 10px;
+}
+
+table.repositories {
border:1px solid #ddd;
border-spacing: 0px;
width: 100%;
@@ -1735,7 +1928,7 @@ td.date { white-space: nowrap;
}
-span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1, td.sha1 {
+span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1, td.sha1, td.sha256 {
font-family: consolas, monospace;
font-size: 13px;
}
@@ -2079,4 +2272,85 @@ div.markdown table.text th, div.markdown table.text td { background-color: #fff; border-color: #ece7e2; color: #815b3a; -}
\ No newline at end of file +}
+.severity-catastrophic {
+ color:#D51900;
+}
+.severity-catastrophic:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: "●●●●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-critical {
+ color:#D55900;
+}
+.severity-critical:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●●●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-serious {
+ color:#E69F00;
+}
+.severity-serious:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-minor {
+ color:#009E73;
+}
+.severity-minor:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-negligible {
+ color:#0072B2;
+}
+.severity-negligible:after {
+ font-family: Helvetica,arial,freesans,clean,sans-serif ;
+ content: " ●";
+ font-weight:900;
+ font-size:.45em;
+ font-variant:small-caps;
+ display:flex;
+ white-space: pre;
+}
+.severity-unrated {
+}
+.priority-urgent {
+ color:#D51900;
+}
+.priority-high {
+ color:#D55900;
+}
+.priority-normal {
+}
+.priority-low {
+ color:#0072B2;
+}
+
+.file-positive {
+ color:#009E73;
+}
+
+.file-negative {
+ color:#D51900;
+}
diff --git a/src/main/resources/octicons/octicons-local.ttf b/src/main/resources/octicons/octicons-local.ttf Binary files differnew file mode 100644 index 00000000..03d78cca --- /dev/null +++ b/src/main/resources/octicons/octicons-local.ttf diff --git a/src/main/resources/octicons/octicons.css b/src/main/resources/octicons/octicons.css new file mode 100644 index 00000000..a5dcd153 --- /dev/null +++ b/src/main/resources/octicons/octicons.css @@ -0,0 +1,235 @@ +@font-face { + font-family: 'octicons'; + src: url('octicons.eot?#iefix') format('embedded-opentype'), + url('octicons.woff') format('woff'), + url('octicons.ttf') format('truetype'), + url('octicons.svg#octicons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* + +.octicon is optimized for 16px. +.mega-octicon is optimized for 32px but can be used larger. + +*/ +.octicon, .mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { font-size: 32px; } + + +.octicon-alert:before { content: '\f02d'} /* */ +.octicon-alignment-align:before { content: '\f08a'} /* */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /* */ +.octicon-alignment-unalign:before { content: '\f08b'} /* */ +.octicon-arrow-down:before { content: '\f03f'} /* */ +.octicon-arrow-left:before { content: '\f040'} /* */ +.octicon-arrow-right:before { content: '\f03e'} /* */ +.octicon-arrow-small-down:before { content: '\f0a0'} /* */ +.octicon-arrow-small-left:before { content: '\f0a1'} /* */ +.octicon-arrow-small-right:before { content: '\f071'} /* */ +.octicon-arrow-small-up:before { content: '\f09f'} /* */ +.octicon-arrow-up:before { content: '\f03d'} /* */ +.octicon-beer:before { content: '\f069'} /* */ +.octicon-book:before { content: '\f007'} /* */ +.octicon-bookmark:before { content: '\f07b'} /* */ +.octicon-briefcase:before { content: '\f0d3'} /* */ +.octicon-broadcast:before { content: '\f048'} /* */ +.octicon-browser:before { content: '\f0c5'} /* */ +.octicon-bug:before { content: '\f091'} /* */ +.octicon-calendar:before { content: '\f068'} /* */ +.octicon-check:before { content: '\f03a'} /* */ +.octicon-checklist:before { content: '\f076'} /* */ +.octicon-chevron-down:before { content: '\f0a3'} /* */ +.octicon-chevron-left:before { content: '\f0a4'} /* */ +.octicon-chevron-right:before { content: '\f078'} /* */ +.octicon-chevron-up:before { content: '\f0a2'} /* */ +.octicon-circle-slash:before { content: '\f084'} /* */ +.octicon-circuit-board:before { content: '\f0d6'} /* */ +.octicon-clippy:before { content: '\f035'} /* */ +.octicon-clock:before { content: '\f046'} /* */ +.octicon-cloud-download:before { content: '\f00b'} /* */ +.octicon-cloud-upload:before { content: '\f00c'} /* */ +.octicon-code:before { content: '\f05f'} /* */ +.octicon-color-mode:before { content: '\f065'} /* */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /* */ +.octicon-comment-discussion:before { content: '\f04f'} /* */ +.octicon-credit-card:before { content: '\f045'} /* */ +.octicon-dash:before { content: '\f0ca'} /* */ +.octicon-dashboard:before { content: '\f07d'} /* */ +.octicon-database:before { content: '\f096'} /* */ +.octicon-device-camera:before { content: '\f056'} /* */ +.octicon-device-camera-video:before { content: '\f057'} /* */ +.octicon-device-desktop:before { content: '\f27c'} /* */ +.octicon-device-mobile:before { content: '\f038'} /* */ +.octicon-diff:before { content: '\f04d'} /* */ +.octicon-diff-added:before { content: '\f06b'} /* */ +.octicon-diff-ignored:before { content: '\f099'} /* */ +.octicon-diff-modified:before { content: '\f06d'} /* */ +.octicon-diff-removed:before { content: '\f06c'} /* */ +.octicon-diff-renamed:before { content: '\f06e'} /* */ +.octicon-ellipsis:before { content: '\f09a'} /* */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /* */ +.octicon-file-binary:before { content: '\f094'} /* */ +.octicon-file-code:before { content: '\f010'} /* */ +.octicon-file-directory:before { content: '\f016'} /* */ +.octicon-file-media:before { content: '\f012'} /* */ +.octicon-file-pdf:before { content: '\f014'} /* */ +.octicon-file-submodule:before { content: '\f017'} /* */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /* */ +.octicon-file-symlink-file:before { content: '\f0b0'} /* */ +.octicon-file-text:before { content: '\f011'} /* */ +.octicon-file-zip:before { content: '\f013'} /* */ +.octicon-flame:before { content: '\f0d2'} /* */ +.octicon-fold:before { content: '\f0cc'} /* */ +.octicon-gear:before { content: '\f02f'} /* */ +.octicon-gift:before { content: '\f042'} /* */ +.octicon-gist:before { content: '\f00e'} /* */ +.octicon-gist-secret:before { content: '\f08c'} /* */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /* */ +.octicon-git-commit:before { content: '\f01f'} /* */ +.octicon-git-compare:before { content: '\f0ac'} /* */ +.octicon-git-merge:before { content: '\f023'} /* */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /* */ +.octicon-globe:before { content: '\f0b6'} /* */ +.octicon-graph:before { content: '\f043'} /* */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /* */ +.octicon-home:before { content: '\f08d'} /* */ +.octicon-horizontal-rule:before { content: '\f070'} /* */ +.octicon-hourglass:before { content: '\f09e'} /* */ +.octicon-hubot:before { content: '\f09d'} /* */ +.octicon-inbox:before { content: '\f0cf'} /* */ +.octicon-info:before { content: '\f059'} /* */ +.octicon-issue-closed:before { content: '\f028'} /* */ +.octicon-issue-opened:before { content: '\f026'} /* */ +.octicon-issue-reopened:before { content: '\f027'} /* */ +.octicon-jersey:before { content: '\f019'} /* */ +.octicon-jump-down:before { content: '\f072'} /* */ +.octicon-jump-left:before { content: '\f0a5'} /* */ +.octicon-jump-right:before { content: '\f0a6'} /* */ +.octicon-jump-up:before { content: '\f073'} /* */ +.octicon-key:before { content: '\f049'} /* */ +.octicon-keyboard:before { content: '\f00d'} /* */ +.octicon-law:before { content: '\f0d8'} /* */ +.octicon-light-bulb:before { content: '\f000'} /* */ +.octicon-link:before { content: '\f05c'} /* */ +.octicon-link-external:before { content: '\f07f'} /* */ +.octicon-list-ordered:before { content: '\f062'} /* */ +.octicon-list-unordered:before { content: '\f061'} /* */ +.octicon-location:before { content: '\f060'} /* */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /* */ +.octicon-logo-github:before { content: '\f092'} /* */ +.octicon-mail:before { content: '\f03b'} /* */ +.octicon-mail-read:before { content: '\f03c'} /* */ +.octicon-mail-reply:before { content: '\f051'} /* */ +.octicon-mark-github:before { content: '\f00a'} /* */ +.octicon-markdown:before { content: '\f0c9'} /* */ +.octicon-megaphone:before { content: '\f077'} /* */ +.octicon-mention:before { content: '\f0be'} /* */ +.octicon-microscope:before { content: '\f089'} /* */ +.octicon-milestone:before { content: '\f075'} /* */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /* */ +.octicon-mortar-board:before { content: '\f0d7'} /* */ +.octicon-move-down:before { content: '\f0a8'} /* */ +.octicon-move-left:before { content: '\f074'} /* */ +.octicon-move-right:before { content: '\f0a9'} /* */ +.octicon-move-up:before { content: '\f0a7'} /* */ +.octicon-mute:before { content: '\f080'} /* */ +.octicon-no-newline:before { content: '\f09c'} /* */ +.octicon-octoface:before { content: '\f008'} /* */ +.octicon-organization:before { content: '\f037'} /* */ +.octicon-package:before { content: '\f0c4'} /* */ +.octicon-paintcan:before { content: '\f0d1'} /* */ +.octicon-pencil:before { content: '\f058'} /* */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /* */ +.octicon-pin:before { content: '\f041'} /* */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /* */ +.octicon-playback-pause:before { content: '\f0bb'} /* */ +.octicon-playback-play:before { content: '\f0bf'} /* */ +.octicon-playback-rewind:before { content: '\f0bc'} /* */ +.octicon-plug:before { content: '\f0d4'} /* */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /* */ +.octicon-podium:before { content: '\f0af'} /* */ +.octicon-primitive-dot:before { content: '\f052'} /* */ +.octicon-primitive-square:before { content: '\f053'} /* */ +.octicon-pulse:before { content: '\f085'} /* */ +.octicon-puzzle:before { content: '\f0c0'} /* */ +.octicon-question:before { content: '\f02c'} /* */ +.octicon-quote:before { content: '\f063'} /* */ +.octicon-radio-tower:before { content: '\f030'} /* */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /* */ +.octicon-repo-clone:before { content: '\f04c'} /* */ +.octicon-repo-force-push:before { content: '\f04a'} /* */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /* */ +.octicon-repo-pull:before { content: '\f006'} /* */ +.octicon-repo-push:before { content: '\f005'} /* */ +.octicon-rocket:before { content: '\f033'} /* */ +.octicon-rss:before { content: '\f034'} /* */ +.octicon-ruby:before { content: '\f047'} /* */ +.octicon-screen-full:before { content: '\f066'} /* */ +.octicon-screen-normal:before { content: '\f067'} /* */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /* */ +.octicon-server:before { content: '\f097'} /* */ +.octicon-settings:before { content: '\f07c'} /* */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /* */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /* */ +.octicon-split:before { content: '\f0c6'} /* */ +.octicon-squirrel:before { content: '\f0b2'} /* */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /* */ +.octicon-steps:before { content: '\f0c7'} /* */ +.octicon-stop:before { content: '\f08f'} /* */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /* */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /* */ +.octicon-telescope:before { content: '\f088'} /* */ +.octicon-terminal:before { content: '\f0c8'} /* */ +.octicon-three-bars:before { content: '\f05e'} /* */ +.octicon-tools:before { content: '\f031'} /* */ +.octicon-trashcan:before { content: '\f0d0'} /* */ +.octicon-triangle-down:before { content: '\f05b'} /* */ +.octicon-triangle-left:before { content: '\f044'} /* */ +.octicon-triangle-right:before { content: '\f05a'} /* */ +.octicon-triangle-up:before { content: '\f0aa'} /* */ +.octicon-unfold:before { content: '\f039'} /* */ +.octicon-unmute:before { content: '\f0ba'} /* */ +.octicon-versions:before { content: '\f064'} /* */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /* */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/src/main/resources/octicons/octicons.eot b/src/main/resources/octicons/octicons.eot Binary files differnew file mode 100644 index 00000000..22881a8b --- /dev/null +++ b/src/main/resources/octicons/octicons.eot diff --git a/src/main/resources/octicons/octicons.less b/src/main/resources/octicons/octicons.less new file mode 100644 index 00000000..b054eb89 --- /dev/null +++ b/src/main/resources/octicons/octicons.less @@ -0,0 +1,233 @@ +@octicons-font-path: "."; +@octicons-version: "a594b5fd4cae0b2afd156bca8dad8d27ac3d7594"; + +@font-face { + font-family: 'octicons'; + src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')", + ~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')", + ~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')", + ~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')"; + font-weight: normal; + font-style: normal; +} + +// .octicon is optimized for 16px. +// .mega-octicon is optimized for 32px but can be used larger. +.octicon, .mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { font-size: 32px; } + +.octicon-alert:before { content: '\f02d'} /* */ +.octicon-alignment-align:before { content: '\f08a'} /* */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /* */ +.octicon-alignment-unalign:before { content: '\f08b'} /* */ +.octicon-arrow-down:before { content: '\f03f'} /* */ +.octicon-arrow-left:before { content: '\f040'} /* */ +.octicon-arrow-right:before { content: '\f03e'} /* */ +.octicon-arrow-small-down:before { content: '\f0a0'} /* */ +.octicon-arrow-small-left:before { content: '\f0a1'} /* */ +.octicon-arrow-small-right:before { content: '\f071'} /* */ +.octicon-arrow-small-up:before { content: '\f09f'} /* */ +.octicon-arrow-up:before { content: '\f03d'} /* */ +.octicon-beer:before { content: '\f069'} /* */ +.octicon-book:before { content: '\f007'} /* */ +.octicon-bookmark:before { content: '\f07b'} /* */ +.octicon-briefcase:before { content: '\f0d3'} /* */ +.octicon-broadcast:before { content: '\f048'} /* */ +.octicon-browser:before { content: '\f0c5'} /* */ +.octicon-bug:before { content: '\f091'} /* */ +.octicon-calendar:before { content: '\f068'} /* */ +.octicon-check:before { content: '\f03a'} /* */ +.octicon-checklist:before { content: '\f076'} /* */ +.octicon-chevron-down:before { content: '\f0a3'} /* */ +.octicon-chevron-left:before { content: '\f0a4'} /* */ +.octicon-chevron-right:before { content: '\f078'} /* */ +.octicon-chevron-up:before { content: '\f0a2'} /* */ +.octicon-circle-slash:before { content: '\f084'} /* */ +.octicon-circuit-board:before { content: '\f0d6'} /* */ +.octicon-clippy:before { content: '\f035'} /* */ +.octicon-clock:before { content: '\f046'} /* */ +.octicon-cloud-download:before { content: '\f00b'} /* */ +.octicon-cloud-upload:before { content: '\f00c'} /* */ +.octicon-code:before { content: '\f05f'} /* */ +.octicon-color-mode:before { content: '\f065'} /* */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /* */ +.octicon-comment-discussion:before { content: '\f04f'} /* */ +.octicon-credit-card:before { content: '\f045'} /* */ +.octicon-dash:before { content: '\f0ca'} /* */ +.octicon-dashboard:before { content: '\f07d'} /* */ +.octicon-database:before { content: '\f096'} /* */ +.octicon-device-camera:before { content: '\f056'} /* */ +.octicon-device-camera-video:before { content: '\f057'} /* */ +.octicon-device-desktop:before { content: '\f27c'} /* */ +.octicon-device-mobile:before { content: '\f038'} /* */ +.octicon-diff:before { content: '\f04d'} /* */ +.octicon-diff-added:before { content: '\f06b'} /* */ +.octicon-diff-ignored:before { content: '\f099'} /* */ +.octicon-diff-modified:before { content: '\f06d'} /* */ +.octicon-diff-removed:before { content: '\f06c'} /* */ +.octicon-diff-renamed:before { content: '\f06e'} /* */ +.octicon-ellipsis:before { content: '\f09a'} /* */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /* */ +.octicon-file-binary:before { content: '\f094'} /* */ +.octicon-file-code:before { content: '\f010'} /* */ +.octicon-file-directory:before { content: '\f016'} /* */ +.octicon-file-media:before { content: '\f012'} /* */ +.octicon-file-pdf:before { content: '\f014'} /* */ +.octicon-file-submodule:before { content: '\f017'} /* */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /* */ +.octicon-file-symlink-file:before { content: '\f0b0'} /* */ +.octicon-file-text:before { content: '\f011'} /* */ +.octicon-file-zip:before { content: '\f013'} /* */ +.octicon-flame:before { content: '\f0d2'} /* */ +.octicon-fold:before { content: '\f0cc'} /* */ +.octicon-gear:before { content: '\f02f'} /* */ +.octicon-gift:before { content: '\f042'} /* */ +.octicon-gist:before { content: '\f00e'} /* */ +.octicon-gist-secret:before { content: '\f08c'} /* */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /* */ +.octicon-git-commit:before { content: '\f01f'} /* */ +.octicon-git-compare:before { content: '\f0ac'} /* */ +.octicon-git-merge:before { content: '\f023'} /* */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /* */ +.octicon-globe:before { content: '\f0b6'} /* */ +.octicon-graph:before { content: '\f043'} /* */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /* */ +.octicon-home:before { content: '\f08d'} /* */ +.octicon-horizontal-rule:before { content: '\f070'} /* */ +.octicon-hourglass:before { content: '\f09e'} /* */ +.octicon-hubot:before { content: '\f09d'} /* */ +.octicon-inbox:before { content: '\f0cf'} /* */ +.octicon-info:before { content: '\f059'} /* */ +.octicon-issue-closed:before { content: '\f028'} /* */ +.octicon-issue-opened:before { content: '\f026'} /* */ +.octicon-issue-reopened:before { content: '\f027'} /* */ +.octicon-jersey:before { content: '\f019'} /* */ +.octicon-jump-down:before { content: '\f072'} /* */ +.octicon-jump-left:before { content: '\f0a5'} /* */ +.octicon-jump-right:before { content: '\f0a6'} /* */ +.octicon-jump-up:before { content: '\f073'} /* */ +.octicon-key:before { content: '\f049'} /* */ +.octicon-keyboard:before { content: '\f00d'} /* */ +.octicon-law:before { content: '\f0d8'} /* */ +.octicon-light-bulb:before { content: '\f000'} /* */ +.octicon-link:before { content: '\f05c'} /* */ +.octicon-link-external:before { content: '\f07f'} /* */ +.octicon-list-ordered:before { content: '\f062'} /* */ +.octicon-list-unordered:before { content: '\f061'} /* */ +.octicon-location:before { content: '\f060'} /* */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /* */ +.octicon-logo-github:before { content: '\f092'} /* */ +.octicon-mail:before { content: '\f03b'} /* */ +.octicon-mail-read:before { content: '\f03c'} /* */ +.octicon-mail-reply:before { content: '\f051'} /* */ +.octicon-mark-github:before { content: '\f00a'} /* */ +.octicon-markdown:before { content: '\f0c9'} /* */ +.octicon-megaphone:before { content: '\f077'} /* */ +.octicon-mention:before { content: '\f0be'} /* */ +.octicon-microscope:before { content: '\f089'} /* */ +.octicon-milestone:before { content: '\f075'} /* */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /* */ +.octicon-mortar-board:before { content: '\f0d7'} /* */ +.octicon-move-down:before { content: '\f0a8'} /* */ +.octicon-move-left:before { content: '\f074'} /* */ +.octicon-move-right:before { content: '\f0a9'} /* */ +.octicon-move-up:before { content: '\f0a7'} /* */ +.octicon-mute:before { content: '\f080'} /* */ +.octicon-no-newline:before { content: '\f09c'} /* */ +.octicon-octoface:before { content: '\f008'} /* */ +.octicon-organization:before { content: '\f037'} /* */ +.octicon-package:before { content: '\f0c4'} /* */ +.octicon-paintcan:before { content: '\f0d1'} /* */ +.octicon-pencil:before { content: '\f058'} /* */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /* */ +.octicon-pin:before { content: '\f041'} /* */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /* */ +.octicon-playback-pause:before { content: '\f0bb'} /* */ +.octicon-playback-play:before { content: '\f0bf'} /* */ +.octicon-playback-rewind:before { content: '\f0bc'} /* */ +.octicon-plug:before { content: '\f0d4'} /* */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /* */ +.octicon-podium:before { content: '\f0af'} /* */ +.octicon-primitive-dot:before { content: '\f052'} /* */ +.octicon-primitive-square:before { content: '\f053'} /* */ +.octicon-pulse:before { content: '\f085'} /* */ +.octicon-puzzle:before { content: '\f0c0'} /* */ +.octicon-question:before { content: '\f02c'} /* */ +.octicon-quote:before { content: '\f063'} /* */ +.octicon-radio-tower:before { content: '\f030'} /* */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /* */ +.octicon-repo-clone:before { content: '\f04c'} /* */ +.octicon-repo-force-push:before { content: '\f04a'} /* */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /* */ +.octicon-repo-pull:before { content: '\f006'} /* */ +.octicon-repo-push:before { content: '\f005'} /* */ +.octicon-rocket:before { content: '\f033'} /* */ +.octicon-rss:before { content: '\f034'} /* */ +.octicon-ruby:before { content: '\f047'} /* */ +.octicon-screen-full:before { content: '\f066'} /* */ +.octicon-screen-normal:before { content: '\f067'} /* */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /* */ +.octicon-server:before { content: '\f097'} /* */ +.octicon-settings:before { content: '\f07c'} /* */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /* */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /* */ +.octicon-split:before { content: '\f0c6'} /* */ +.octicon-squirrel:before { content: '\f0b2'} /* */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /* */ +.octicon-steps:before { content: '\f0c7'} /* */ +.octicon-stop:before { content: '\f08f'} /* */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /* */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /* */ +.octicon-telescope:before { content: '\f088'} /* */ +.octicon-terminal:before { content: '\f0c8'} /* */ +.octicon-three-bars:before { content: '\f05e'} /* */ +.octicon-tools:before { content: '\f031'} /* */ +.octicon-trashcan:before { content: '\f0d0'} /* */ +.octicon-triangle-down:before { content: '\f05b'} /* */ +.octicon-triangle-left:before { content: '\f044'} /* */ +.octicon-triangle-right:before { content: '\f05a'} /* */ +.octicon-triangle-up:before { content: '\f0aa'} /* */ +.octicon-unfold:before { content: '\f039'} /* */ +.octicon-unmute:before { content: '\f0ba'} /* */ +.octicon-versions:before { content: '\f064'} /* */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /* */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/src/main/resources/octicons/octicons.svg b/src/main/resources/octicons/octicons.svg new file mode 100644 index 00000000..ea3e0f16 --- /dev/null +++ b/src/main/resources/octicons/octicons.svg @@ -0,0 +1,198 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata> +(c) 2012-2014 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files +</metadata> +<defs> +<font id="octicons" horiz-adv-x="1024" > +<font-face font-family="octicons" font-weight="400" font-stretch="normal" units-per-em="1024" ascent="832" descent="-192" /> +<missing-glyph d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" /> +<glyph glyph-name="alert" unicode="" d="M1005.854 31.753000000000043l-438.286 767C556.173 818.694 534.967 831 512 831s-44.173-12.306-55.567-32.247l-438.286-767c-11.319-19.809-11.238-44.144 0.213-63.876C29.811-51.85500000000002 50.899-64 73.714-64h876.572c22.814 0 43.903 12.145 55.354 31.877S1017.173 11.94399999999996 1005.854 31.753000000000043zM576 64H448V192h128V64zM576 256H448V512h128V256z" horiz-adv-x="1024" /> +<glyph glyph-name="alignment-align" unicode="" d="M192 768C85.938 768 0 682.062 0 576s85.938-192 192-192c106.062 0 192 85.938 192 192S298.062 768 192 768zM672 224l160 160H384v-448l160 160 288-288 128 128L672 224z" horiz-adv-x="960" /> +<glyph glyph-name="alignment-aligned-to" unicode="" d="M384 256l128 128 288-288 160 160v-448H512l160 160L384 256zM192 384C85.938 384 0 469.938 0 576S85.938 768 192 768c106.062 0 192-85.938 192-192S298.062 384 192 384z" horiz-adv-x="960" /> +<glyph glyph-name="alignment-unalign" unicode="" d="M512 640L384 512 128 768 0 640l256-256L128 256l64-64 384 384L512 640zM640 256l128 128-64 64L320 64l64-64 128 128 256-256 128 128L640 256z" horiz-adv-x="896" /> +<glyph glyph-name="arrow-down" unicode="" d="M448 384V640H192v-256H0l320-384 320 384H448z" horiz-adv-x="640" /> +<glyph glyph-name="arrow-left" unicode="" d="M384 448V640L0 320l384-320V192h256V448H384z" horiz-adv-x="640" /> +<glyph glyph-name="arrow-right" unicode="" d="M640 320L256 640v-192H0v-256h256v-192L640 320z" horiz-adv-x="640" /> +<glyph glyph-name="arrow-small-down" unicode="" d="M256 384V512H128v-128H0l192-256 192 256H256z" horiz-adv-x="384" /> +<glyph glyph-name="arrow-small-left" unicode="" d="M256 384V512L0 320l256-192V256h128V384H256z" horiz-adv-x="384" /> +<glyph glyph-name="arrow-small-right" unicode="" d="M384 320L128 512v-128H0v-128h128v-128L384 320z" horiz-adv-x="384" /> +<glyph glyph-name="arrow-small-up" unicode="" d="M192 512L0 256h128v-128h128V256h128L192 512z" horiz-adv-x="384" /> +<glyph glyph-name="arrow-up" unicode="" d="M320 640L0 256h192v-256h256V256h192L320 640z" horiz-adv-x="640" /> +<glyph glyph-name="beer" unicode="" d="M896 576c-31 0-192 0-192 0v128c0 71-158 128-352 128s-352-57-352-128v-768c0-71 158-128 352-128s352 57 352 128v128s160 0 192 0 64 30 64 64 0 350 0 384-29 64-64 64z m-704-576h-64v512h64v-512z m192-64h-64v512h64v-512z m192 64h-64v512h64v-512z m-224 640c-124 0-224 29-224 64s100 64 224 64 224-29 224-64-100-64-224-64z m480-448h-128v256h128v-256z" horiz-adv-x="1024" /> +<glyph glyph-name="book" unicode="" d="M768 256h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-55 416c-167 0-209-32-233-56-24 24-66 56-233 56s-247-46-247-78v-586c29 16 119 48 214 56 115 9 234-9 234-32 0-16 8-31 31-32 0 0 0 0 1 0 0 0 0 0 1 0 23 1 31 16 31 32 0 23 119 41 234 32 94-7 185-40 214-56v586c0 32-80 78-247 78z m-265-572c-30 16-103 28-192 28s-170-12-192-27c0 0 0 411 0 443s64 59 192 59 192-27 192-59 0-444 0-444z m448 1c-22 15-103 27-192 27s-162-12-192-28c0 0 0 412 0 444s64 59 192 59 192-27 192-59 0-443 0-443z m-128 283h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m0 128h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-448-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0 256h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z" horiz-adv-x="1024" /> +<glyph glyph-name="bookmark" unicode="" d="M0 704v-768l192 128 192-128V704H0zM316.25 507.25l-71.875-51.938 27.188-83.406c2.75-8.375-0.688-11.062-7.562-6.594l-72 52.094-72-52.031c-6.844-4.469-10.312-1.781-7.562 6.594l27.219 83.406L67.783 507.25c-6.469 5.125-5 9.219 3.906 9.219l88 0.125 27.125 83.094c2.812 8.812 7.562 8.812 10.375 0l27.188-83.094 87.938-0.125C321.25 516.469 322.688 512.375 316.25 507.25z" horiz-adv-x="384" /> +<glyph glyph-name="briefcase" unicode="" d="M896 640H640v66c0 34.2-27.8 62-62 62H446c-34.2 0-62-27.8-62-62v-66H128c-35.3 0-64-28.7-64-64v-512c0-35.3 28.7-64 64-64h768c35.3 0 64 28.7 64 64V576C960 611.3 931.3 640 896 640zM448 688c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16v-48H448V688zM896 320H576v-64H448v64H128V576h64v-192h640V576h64V320z" horiz-adv-x="1024" /> +<glyph glyph-name="broadcast" unicode="" d="M448 640c142 0 256-115 256-256 0-69-28-132-72-178l-16-93c91 56 152 156 152 271 0 177-143 320-320 320s-320-143-320-320c0-115 61-215 152-271l-16 93c-45 46-72 109-72 178 0 142 114 256 256 256z m-64-320c-36 0-64-29-64-64v-128c0-36 30-64 64-64v-256h128v256c34 0 64 28 64 64v128c0 35-28 64-64 64s-64 0-64 0-28 0-64 0z m192 128c0 71-57 128-128 128s-128-57-128-128 57-128 128-128 128 57 128 128z m-128 384c-247 0-448-201-448-448 0-197 128-363 305-423l-12 72c-135 60-229 194-229 351 0 212 172 384 384 384s384-172 384-384c0-157-94-291-229-351l-12-72c177 60 305 225 305 423 0 247-201 448-448 448z" horiz-adv-x="896" /> +<glyph glyph-name="browser" unicode="" d="M320 640h64v-64h-64V640zM192 640h64v-64h-64V640zM64 640h64v-64H64V640zM832 0H64V512h768V0zM832 576H448v64h384V576zM896 640c0 35.35-28.65 64-64 64H64c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V640z" horiz-adv-x="896" /> +<glyph glyph-name="bug" unicode="" d="M243.621 675.469C190.747 618.688 205.34 528 205.34 528s53.968-64 160-64c106.031 0 160.031 64 160.031 64s14.375 89.469-37.375 146.312c32.375 18.031 51.438 44.094 43.562 61.812-8.938 19.969-48.375 21.75-88.25 3.969-14.812-6.594-27.438-14.969-37.25-23.875-12.438 2.25-25.625 3.781-40.72 3.781-14.061 0-26.561-1.344-38.344-3.25-9.656 8.75-22.062 16.875-36.531 23.344-39.875 17.719-79.375 15.938-88.25-3.969C194.465 718.781 212.497 693.438 243.621 675.469zM644.746 262.25c-8.25 1.75-16.125 2.75-23.75 3.5 0 2.125 0.375 4.125 0.375 6.312 0 33.594-4.75 65.654-12.438 96.125 16.438-1.406 37.375 2.375 58.562 11.779 39.875 17.781 65 48.375 56.125 68.219-8.875 19.969-48.375 21.75-88.25 3.969-18.625-8.312-33.812-19.469-44-30.906-7.75 18.25-16.5 35.781-26.812 51.719-30.188-25.156-87.312-62.719-167.062-71.062v-321.781c0 0-0.25-32-32.031-32-31.75 0-32 32-32 32V401.781c-79.811 8.344-136.968 45.969-167.093 71.062-9.875-15.312-18.375-32-25.938-49.344-10.281 10.625-24.625 20.844-41.969 28.594-39.875 17.719-79.375 15.938-88.25-3.969-8.906-19.906 16.25-50.438 56.125-68.219 19.844-8.846 39.531-12.812 55.469-12.096-7.656-30.404-12.469-62.344-12.469-95.812 0-2.188 0.375-4.25 0.438-6.5-6.719-0.75-13.688-1.75-20.781-3.25-51.969-10.75-91.781-37.625-88.844-59.812 2.938-22.312 47.5-31.5 99.594-20.688 6.781 1.375 13.438 3.125 19.781 5.062C128.684 146 143.34 108.125 163.622 75.5c-12.031-6.062-24.531-15-36.031-26.625C95.715 17 82.779-21.75 98.715-37.68799999999999c15.938-15.937 54.656-3 86.531 28.812 9.344 9.375 16.844 19.25 22.656 29C251.434-22.5 305.965-48 365.465-48c60.343 0 115.781 26.25 159.531 69.938 5.875-10.312 13.75-20.812 23.625-30.688 31.812-31.875 70.625-44.812 86.562-28.875s3 54.625-28.875 86.5c-12.312 12.375-25.688 21.75-38.438 27.938 20.125 32.5 34.625 70.375 43.688 111.062 7.188-2.25 14.688-4.375 22.562-6.062 52.061-10.812 96.625-1.562 99.625 20.688C736.558 224.625 696.746 251.5 644.746 262.25z" horiz-adv-x="733.886" /> +<glyph glyph-name="calendar" unicode="" d="M704 320h-64v-128h64V320zM576 320h-64v-128h64V320zM704 512h-64v-128h64V512zM832 320h-64v-128h64V320zM576 128h-64v-128h64V128zM768 832h-64v-128h64V832zM256 832h-64v-128h64V832zM832 512h-64v-128h64V512zM576 512h-64v-128h64V512zM320 128h-64v-128h64V128zM192 320h-64v-128h64V320zM320 320h-64v-128h64V320zM832 768v-128H640V768H320v-128H128V768H0v-896h960V768H832zM896-64H64V576h832V-64zM192 128h-64v-128h64V128zM448 512h-64v-128h64V512zM448 128h-64v-128h64V128zM320 512h-64v-128h64V512zM448 320h-64v-128h64V320zM704 128h-64v-128h64V128z" horiz-adv-x="1024" /> +<glyph glyph-name="check" unicode="" d="M640 640L256 256 128 384 0 256l256-256 512 512L640 640z" horiz-adv-x="768" /> +<glyph glyph-name="checklist" unicode="" d="M760.688 315.78099999999995l-49.812 49.656c-6.438 6.529-16.938 6.594-23.375 0L582.5 260.5 462.375 140.125l-93.031 93.125c-6.531 6.562-17.031 6.562-23.5 0l-49.719-49.688c-6.531-6.562-6.531-17.062 0-23.562l104.781-104.875 17.969-17.875 31.688-31.812c6.562-6.562 17.188-6.562 23.562 0l49.625 49.688L760.625 292.22C767.25 298.688 767.25 309.188 760.688 315.78099999999995zM228.469 251.188L278.156 301c42.469 42.375 116.344 42.438 158.781-0.062l25.312-25.312L576 384V704H0v-704h320l-91.531 92.125C184.688 136.062 184.688 207.375 228.469 251.188zM192 640h320v-64H192V640zM192 512h320v-64H192V512zM128 320H64v64h64V320zM128 448H64v64h64V448zM128 576H64v64h64V576zM192 384h64v-64h-64V384z" horiz-adv-x="765.602" /> +<glyph glyph-name="chevron-down" unicode="" d="M512 512L320 320 128 512 0 384l320-320 320 320L512 512z" horiz-adv-x="640" /> +<glyph glyph-name="chevron-left" unicode="" d="M448 512L320 640 0 320l320-320 128 128L256 320 448 512z" horiz-adv-x="448" /> +<glyph glyph-name="chevron-right" unicode="" d="M128 640L0 512l192-192L0 128l128-128 320 320L128 640z" horiz-adv-x="448" /> +<glyph glyph-name="chevron-up" unicode="" d="M320 576L0 256l128-128 192 192 192-192 128 128L320 576z" horiz-adv-x="640" /> +<glyph glyph-name="circle-slash" unicode="" d="M320 640C143.219 640 0 496.781 0 320c0-176.75 143.219-320 320-320 176.75 0 320 143.25 320 320C640 496.781 496.75 640 320 640zM320 512c27.656 0 53.688-6.094 77.438-16.562L144.562 242.562C134.094 266.312 128 292.34400000000005 128 320 128 426 213.938 512 320 512zM320 128c-28.031 0-54.531 6.375-78.594 17.125l253.906 252.5C505.875 373.812 512 347.719 512 320 512 213.938 426.062 128 320 128z" horiz-adv-x="640" /> +<glyph glyph-name="circuit-board" unicode="" d="M320 576c35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64s-64 28.654-64 64C256 547.346 284.654 576 320 576zM960 64c0-106.039-85.961-192-192-192H320l192 192h81.128c22.132-38.258 63.494-64 110.872-64 70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.377 0-88.74-25.742-110.872-64H448L156.044-99.95600000000002C100.845-66.23199999999997 64-5.419999999999959 64 64V576c0 106.039 85.961 192 192 192v-145.128C217.742 600.74 192 559.377 192 512c0-70.692 57.308-128 128-128 47.276 0 88.56 25.633 110.727 63.756l162.416 0.219C615.279 409.731 656.633 384 704 384c70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.388 0-88.758-25.753-110.887-64.025l-162.097-0.219c-11.246 19.54-27.503 35.828-47.016 47.116V768h384c106.039 0 192-85.961 192-192V64zM640 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 92.654 640 128zM640 512c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 476.654 640 512z" horiz-adv-x="1024" /> +<glyph glyph-name="clippy" unicode="" d="M704-64h-640v576h640v-192h64v320c0 35-29 64-64 64h-192c0 71-57 128-128 128s-128-57-128-128h-192c-35 0-64-29-64-64v-704c0-35 29-64 64-64h640c35 0 64 29 64 64v128h-64v-128z m-512 704c29 0 29 0 64 0s64 29 64 64 29 64 64 64 64-29 64-64 32-64 64-64 33 0 64 0 64-29 64-64h-512c0 39 28 64 64 64z m-64-512h128v64h-128v-64z m448 128v128l-256-192 256-192v128h320v128h-320z m-448-256h192v64h-192v-64z m320 448h-320v-64h320v64z m-192-128h-128v-64h128v64z" horiz-adv-x="896" /> +<glyph glyph-name="clock" unicode="" d="M384 256h256l64 64-64 64H512V576l-64 64-64-64V256zM448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.25 0-320 143.75-320 320 0 175.938 144.188 319.5 320 320 175.812-0.5 320-144.062 320-320C768 143.75 624.25 0 448 0z" horiz-adv-x="896" /> +<glyph glyph-name="cloud-download" unicode="" d="M832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.75 644.125 704 512 704c-132.156 0-245.562-80.25-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426.062 0 320s85.938-192 192-192c20.531 0 39.875 4.25 58.375 10.375C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.875C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.094C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.625 156.938 64.438C830 128.375 830.875 128 832 128c106.062 0 192 85.938 192 192S938.062 512 832 512zM576 320H448v-320H320l192-192 192 192H576V320z" horiz-adv-x="1024" /> +<glyph glyph-name="cloud-upload" unicode="" d="M512 448L320 256h128v-320h128V256h128L512 448zM832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.812 644.125 704 512 704c-132.156 0-245.562-80.188-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426 0 320c0-106.062 85.938-192 192-192 20.531 0 39.875 4.25 58.375 10.438C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.75C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.156C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.688 157 64.438 1 0 1.875-0.438 3-0.438 106.062 0 192 85.938 192 192C1024 426 938.062 512 832 512z" horiz-adv-x="1024" /> +<glyph glyph-name="code" unicode="" d="M608 640l-96-96 224-224L512 96l96-96 288 320L608 640zM288 640L0 320l288-320 96 96L160 320l224 224L288 640z" horiz-adv-x="896" /> +<glyph glyph-name="color-mode" unicode="" d="M0 704v-768h768V704H0zM64 0V640h640L64 0z" horiz-adv-x="768" /> +<glyph glyph-name="comment" unicode="" d="M768 704H128C66 704 0 640 0 576v-384c0-128 128-128 128-128h64v-256l256 256c0 0 258 0 320 0s128 68 128 128V576C896 638 832 704 768 704z" horiz-adv-x="896" /> +<glyph glyph-name="comment-discussion" unicode="" d="M256 320c0 64 0 192 0 192s-160 0-192 0-64-32-64-64 0-288 0-320 32-64 64-64 64 0 64 0v-192l194 192s162 0 192 0 62 32 62 64 0 64 0 64-128 0-192 0-128 64-128 128z m576 384c-32 0-416 0-448 0s-64-32-64-64 0-288 0-320 32-64 64-64 190 0 190 0l194-192v192s32 0 64 0 64 32 64 64 0 288 0 320-32 64-64 64z" horiz-adv-x="896" /> +<glyph glyph-name="credit-card" unicode="" d="M128 128h128v64h-128v-64z m192 0h128v64h-128v-64z m64 192h-256v-64h256v64z m-128 64h64l128 128h-64l-128-128z m192-128h192v64h-192v-64z m512 384c-32 0-864 0-896 0s-64-32-64-64 0-480 0-512 32-64 64-64 864 0 896 0 64 32 64 64 0 480 0 512-32 64-64 64z m0-256v-288s0-32-32-32h-832c-32 0-32 32-32 32v288h64l128 128h-192v32s0 32 32 32h832c32 0 32-32 32-32v-32h-384l-128-128h512z" horiz-adv-x="1024" /> +<glyph glyph-name="dash" unicode="" d="M0 384v-128h512V384H0z" horiz-adv-x="512" /> +<glyph glyph-name="dashboard" unicode="" d="M416 367.5c-61.562 0-111.5-49.938-111.5-111.5S354.438 144.5 416 144.5 527.5 194.438 527.5 256c0 8.5-1.125 16.75-3 24.688C606.125 375.625 732.5 523.656 800 608c23.125 28.875-2.312 56.188-32 32-85.188-69.375-232.312-194.688-326.906-275.594C433.031 366.281 424.625 367.5 416 367.5zM447.875 576.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S447.875 558.469 447.875 576.125zM639.875 320.125c0-17.656 14.375-32 32-32s32 14.344 32 32-14.375 32-32 32S639.875 337.781 639.875 320.125zM287.875 576.125c-17.656 0-32-14.344-32-32s14.344-32 32-32 32 14.344 32 32S305.531 576.125 287.875 576.125zM223.875 448.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S223.875 430.469 223.875 448.125zM127.875 320.125c0-17.656 14.344-32 32-32s32 14.344 32 32-14.344 32-32 32S127.875 337.781 127.875 320.125zM575.875 544.125c0 17.656-14.375 32-32 32s-32-14.344-32-32 14.375-32 32-32S575.875 526.469 575.875 544.125zM792.875 495.312l-68.75-89.938C731.625 378.188 736 349.625 736 320c0-176.75-143.312-320-320-320S96 143.25 96 320c0 176.688 143.312 320 320 320 65.875 0 127-19.969 177.875-54.094l79.25 60.625C602.375 702.406 513.25 736 416 736 186.25 736 0 549.75 0 320s186.25-416 416-416 416 186.25 416 416C832 382.719 817.75 442 792.875 495.312z" horiz-adv-x="832" /> +<glyph glyph-name="database" unicode="" d="M384-128C171.969-128 0-70.625 0 0c0 38.625 0 80.875 0 128 0 11.125 5.562 21.688 13.562 32C56.375 104.875 205.25 64 384 64s327.625 40.875 370.438 96c8-10.312 13.562-20.875 13.562-32 0-37.062 0-76.375 0-128C768-70.625 596-128 384-128zM384 128C171.969 128 0 185.375 0 256c0 38.656 0 80.844 0 128 0 6.781 2.562 13.375 6 19.906l0 0C7.938 408 10.5 412.031 13.562 416 56.375 360.906 205.25 320 384 320s327.625 40.906 370.438 96c3.062-3.969 5.625-8 7.562-12.094l0 0c3.438-6.531 6-13.125 6-19.906 0-37.062 0-76.344 0-128C768 185.375 596 128 384 128zM384 384C171.969 384 0 441.344 0 512c0 20.219 0 41.594 0 64 0 20.344 0 41.469 0 64C0 710.656 171.969 768 384 768c212 0 384-57.344 384-128 0-19.969 0-41.156 0-64 0-19.594 0-40.25 0-64C768 441.344 596 384 384 384zM384 704c-141.375 0-256-28.594-256-64s114.625-64 256-64 256 28.594 256 64S525.375 704 384 704z" horiz-adv-x="768" /> +<glyph glyph-name="device-camera" unicode="" d="M512 447.999c-70.691 0-127.999-57.308-127.999-127.999S441.309 192.00099999999998 512 192.00099999999998c5.713 0 11.337 0.38 16.852 1.105-46.344 7.058-81.851 47.079-81.851 95.394 0 53.295 43.204 96.499 96.499 96.499 48.314 0 88.336-35.507 95.394-81.851 0.726 5.515 1.105 11.139 1.105 16.852C639.999 390.691 582.691 447.999 512 447.999zM896 576H767.999L640 704H384L255.999 576H128c-35.348 0-64-28.652-64-64v-448c0-35.347 28.652-64 64-64h768c35.347 0 64 28.653 64 64V512C960 547.348 931.347 576 896 576zM416 640h192l64-64H352L416 640zM160.143 64C142.391 64 128 78.39099999999996 128 96.14300000000003V384h64v64h-64v31.857C128 497.609 142.391 512 160.143 512h182.526c-3.98-3.518-7.881-7.174-11.688-10.98-99.974-99.975-99.974-262.064 0-362.039l74.98-74.98H160.143zM512 128.00099999999998c-106.038 0-191.999 85.961-191.999 191.999S405.962 511.999 512 511.999 703.999 426.038 703.999 320 618.038 128.00099999999998 512 128.00099999999998zM832 352L681.327 512H832V352z" horiz-adv-x="1024" /> +<glyph glyph-name="device-camera-video" unicode="" d="M576 640c-35.347 0-64-28.653-64-64s28.653-64 64-64 64 28.653 64 64S611.347 640 576 640zM896 448L768 320v64c0 30.625-21.515 56.21-50.25 62.503C748.958 480.646 768 526.097 768 575.998 768 682.038 682.039 768 576 768c-101.123 0-183.986-78.178-191.45-177.393C350.516 621.306 305.442 640 256 640c-106.038 0-192-85.962-192-192.002C64 341.961 149.962 256 256 256h-64v-128h64v-128c0-35.347 28.653-64 64-64h384c35.347 0 64 28.653 64 64v64l128-128h64V448H896zM256 512c-35.347 0-64-28.653-64-64s28.653-64 64-64v-64c-70.692 0-128 57.308-128 127.999C128 518.692 185.308 576 256 576s128-57.307 128-128h-64C320 483.347 291.347 512 256 512zM576 128H448V256h128V128zM704 237.21299999999997c-33.526 33.547-70.276 70.317-73.373 73.414C624.837 316.418 616.837 320 608 320H416c-17.674 0-32-14.326-32-32v-192c0-8.329 3.183-15.915 8.396-21.607 0.53-0.58 39.123-39.164 74.409-74.393H352c-17.674 0-32 14.326-32 32V352c0 17.674 14.326 32 32 32h320c17.674 0 32-14.326 32-32V237.21299999999997zM576 448c-70.692 0-128 57.308-128 127.999C448 646.692 505.308 704 576 704s128-57.308 128-128.001C704 505.308 646.692 448 576 448zM896 128l-64 64 0.082 128.084L896 384.002V128z" horiz-adv-x="1024" /> +<glyph glyph-name="device-desktop" unicode="" d="M960 768c-32 0-864 0-896 0s-64-32-64-64 0-544 0-576 32-64 64-64 320 0 320 0-192-64-192-128c0-32 32-64 64-64s480 0 512 0 64 32 64 64c0 64-192 128-192 128s288 0 320 0 64 32 64 64 0 544 0 576-32 64-64 64z m0-640h-896v576h896v-576z m-64 512h-192c-384-64-542-300-576-384v-64h768v448z" horiz-adv-x="1024" /> +<glyph glyph-name="device-mobile" unicode="" d="M576 832H64C28.688 832 0 803.312 0 768v-896c0-35.375 28.688-64 64-64h512c35.375 0 64 28.625 64 64V768C640 803.312 611.375 832 576 832zM288 768h64c17.625 0 32-14.344 32-32s-14.375-32-32-32h-64c-17.656 0-32 14.344-32 32S270.344 768 288 768zM352-128h-64c-17.656 0-32 14.375-32 32s14.344 32 32 32h64c17.625 0 32-14.375 32-32S369.625-128 352-128zM576 0H64V640h512V0z" horiz-adv-x="640" /> +<glyph glyph-name="diff" unicode="" d="M448 576H320v-128H192v-128h128v-128h128V320h128V448H448V576zM192-64h384V64H192V-64zM640 832H128v-64h480l224-224v-608h64V576L640 832zM0 704v-896h768V512L576 704H0zM704-128H64V640h480l160-160V-128z" horiz-adv-x="896" /> +<glyph glyph-name="diff-added" unicode="" d="M512 512h-128v-128h-128v-128h128v-128h128v128h128v128h-128v128z m320 256c-32 0-736 0-768 0s-64-32-64-64 0-736 0-768 32-64 64-64 736 0 768 0 64 32 64 64 0 736 0 768-32 64-64 64z m-64-736c0-16-17-32-32-32s-558 0-576 0-32 12-32 32c0 16 0 560 0 576s16 32 32 32 561 0 576 0 32-16 32-32 0-560 0-576z" horiz-adv-x="896" /> +<glyph glyph-name="diff-ignored" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 194v-98h98l286 286v98h-98l-286-286z" horiz-adv-x="896" /> +<glyph glyph-name="diff-modified" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 416c-71 0-128-57-128-128s57-128 128-128 128 57 128 128-57 128-128 128z" horiz-adv-x="896" /> +<glyph glyph-name="diff-removed" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 224h384v128h-384v-128z" horiz-adv-x="896" /> +<glyph glyph-name="diff-renamed" unicode="" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 352h-192v-128h192v-128l256 192-256 192v-128z" horiz-adv-x="896" /> +<glyph glyph-name="ellipsis" unicode="" d="M640 512c-64 0-448 0-512 0s-128-64-128-128 0-64 0-128 64-128 128-128 448 0 512 0 128 64 128 128 0 64 0 128-64 128-128 128z m-384-256h-128v128h128v-128z m192 0h-128v128h128v-128z m192 0h-128v128h128v-128z" horiz-adv-x="768" /> +<glyph glyph-name="eye" unicode="" d="M512 704c-192 0-416-128-512-384 96-192 288-320 512-320s416 128 512 320c-96 256-320 384-512 384z m0-640c-192 0-352 128-384 256 32 128 192 256 384 256s352-128 384-256c-32-128-192-256-384-256z m0 448c-20 0-38-4-56-9 33-15 56-48 56-87 0-53-43-96-96-96-39 0-72 23-87 56-5-18-9-36-9-56 0-106 86-192 192-192s192 86 192 192-86 192-192 192z" horiz-adv-x="1024" /> +<glyph glyph-name="file-binary" unicode="" d="M0-128V768h576l192-192v-704H0zM704 512L512 704H64v-768h640V512zM320 320H128V576h192V320zM256 512h-64v-128h64V512zM256 64h64v-64H128v64h64V192h-64v64h128V64zM512 384h64v-64H384v64h64V512h-64v64h128V384zM576 0H384V256h192V0zM512 192h-64v-128h64V192z" horiz-adv-x="768" /> +<glyph glyph-name="file-code" unicode="" d="M288 448L128 288l160-160 64 64-96 96 96 96L288 448zM416 384l96-96-96-96 64-64 160 160L480 448 416 384zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64z" horiz-adv-x="768" /> +<glyph glyph-name="file-directory" unicode="" d="M832 640c-32 0-336 0-352 0s-32 16-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-704 0-704h896s0 544 0 576-32 64-64 64z m-448 0h-320s0 15 0 32 16 32 32 32 241 0 256 0 32-15 32-32 0-32 0-32z" horiz-adv-x="896" /> +<glyph glyph-name="file-media" unicode="" d="M576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 576v-512h128c0 70.625 57.344 128 128 128-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128 70.625 0 128-57.375 128-128h128V448L512 576H128z" horiz-adv-x="768" /> +<glyph glyph-name="file-pdf" unicode="" d="M576 768H0v-896h768V576L576 768zM64 704h255.812c-13.188-4.094-27.281-15.031-34.625-42.875-13.25-49.406-7.031-130.75 15.625-209.344C276.688 370.562 178.188 175.125 171.531 163.5c-15.625-4.875-65.344-23.625-107.531-59.812V704zM347.125 396.531c57.625-149.781 95-149.531 135.188-167.594C398.344 216 334.219 206.75 249.781 169.5 246.094 163.062 326.281 315.40599999999995 347.125 396.531zM704-64H65.844 64v0.375c0.781-0.062 1.094-0.375 1.844-0.375 33.812 0 84.75 21 180.562 182.375 38.188 15.438 72.062 26.875 78.469 28.938 58.812 14.875 125 26.625 187.562 33.375C566.875 153.5 639.125 135 680.25 132.375c9.625-0.5 16.062 1.188 23.75 2V-64zM704 246.625c-23.688 14.688-54 25-89.125 25-24.25 0-50.625-1.375-78.688-4.375-26.938 13-92.562 32.719-147.188 190.219 17.094 103.625 12.719 173.562 12.719 173.562 6.781 52.938-23.344 72.844-51.625 72.844 0 0-0.279 0.125-0.344 0.125H512l192-192V246.625z" horiz-adv-x="768" /> +<glyph glyph-name="file-submodule" unicode="" d="M832 320c-32 0-192 0-192 0 0 32-32 64-64 64s-96 0-128 0-64-32-64-64 0-320 0-320h512s0 224 0 256-32 64-64 64z m-256-64h-128s0 17 0 32 15 32 32 32 48 0 64 0 32-15 32-32 0-32 0-32z m256 320c-32 0-336 0-352 0s-32 17-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-640 0-640h320s0 352 0 384 32 64 64 64 224 0 256 0 64-32 64-64h192s0 96 0 128-32 64-64 64z m-448 0h-320s0 16 0 32 16 32 32 32 240 0 256 0 32-17 32-32 0-32 0-32z" horiz-adv-x="896" /> +<glyph glyph-name="file-symlink-directory" unicode="" d="M832 640h-352c-16 0-32 16-32 32s0 0 0 32-32 64-64 64h-320c-32 0-64-32-64-64s0-704 0-704h896s0 544 0 576-32 64-64 64z m-768 32c0 17 16 32 32 32h256c15 0 32-15 32-32s0-32 0-32h-320s0 15 0 32z m384-544v128c-125 0-224-56-256-192 0 209 107 320 256 320 0 49 0 128 0 128l256-192-256-192z" horiz-adv-x="896" /> +<glyph glyph-name="file-symlink-file" unicode="" d="M576 768h-576v-896h768v704l-192 192z m128-832h-640v768h448l192-192v-576z m-320 448c-149 0-256-111-256-320 32 136 131 192 256 192v-128l256 192-256 192s0-79 0-128z" horiz-adv-x="768" /> +<glyph glyph-name="file-text" unicode="" d="M448 576H128v-64h320V576zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 64h512v64H128V64zM128 192h512v64H128V192zM128 320h512v64H128V320z" horiz-adv-x="768" /> +<glyph glyph-name="file-zip" unicode="" d="M320 256v64h-64v-64H320zM320 384v64h-64v-64H320zM320 512v64h-64v-64H320zM192 448h64v64h-64V448zM576 768H0v-896h768V576L576 768zM704-64H64V704h192v-64h64v64h192l192-192V-64zM192 576h64v64h-64V576zM192 320h64v64h-64V320zM192 192l-64-64v-128h256V128l-64 64h-64v64h-64V192zM320 128v-64H192v64H320z" horiz-adv-x="768" /> +<glyph glyph-name="flame" unicode="" d="M433 787c50-134 24-207-32-265-61-64-156-112-223-206-89-125-104-400 217-472-135 71-164 277-18 406-38-125 32-205 119-176 85 29 141-32 139-102-1-48-20-89-69-112 209 37 293 210 293 342 0 174-155 198-77 344-93-8-125-69-116-169 6-66-63-111-114-81-41 25-40 73-4 109 77 76 107 251-115 382z" horiz-adv-x="1024" /> +<glyph glyph-name="fold" unicode="" d="M896 576H672l-64-64h192L672 384H224L96 512h192l-64 64H0v-63.999L160 352 0 192v-64h224l64 64H96l128 128h448l128-128H608l64-64h224v64L736 352l160 160.001V576zM640 640H512V832H384v-192H256l192-192L640 640zM256 64h128v-192h128V64h128L448 256 256 64z" horiz-adv-x="896" /> +<glyph glyph-name="gear" unicode="" d="M447.938 482C358.531 482 286 409.469 286 320c0-89.375 72.531-162.062 161.938-162.062 89.438 0 161.438 72.688 161.438 162.062C609.375 409.469 537.375 482 447.938 482zM772.625 226.938l-29.188-70.312 52.062-102.25 6.875-13.5-72.188-72.188L611.75 24.625l-70.312-28.875L505.75-113.5l-4.562-14.5H399.156L355-4.687999999999988l-70.312 29-102.404-51.938-13.5-6.75-72.156 72.125 55.875 118.5-28.969 70.25L14.469 262.125 0 266.812V368.781L123.406 413l28.969 70.188-51.906 102.469-6.844 13.438 72.062 72.062 118.594-55.844 70.219 29.031 35.656 109.188L394.75 768h102l44.188-123.469 70.125-29.031L713.5 667.469l13.625 6.844 72.125-72.062-55.875-118.406L772.25 413.5l109.375-35.656L896 373.25v-101.938L772.625 226.938z" horiz-adv-x="896" /> +<glyph glyph-name="gift" unicode="" d="M448-128h320V192H448V-128zM64-128h320V192H64V-128zM447.75 455.812c31.469 3.5 66.875 7.406 87.375 9.719C619 474.875 694.5 550.406 703.812 634.25c9.312 83.75-51 144.125-134.688 134.719C503.688 761.656 443.844 714 416 653.625 388.156 714 328.312 761.656 262.906 769.031 179.188 778.375 118.781 718 128.188 634.25c9.344-83.844 84.875-159.312 168.656-168.719 20.531-2.312 55.938-6.281 87.406-9.719C383.75 451.594 384 448 384 448h64C448 448 448.25 451.594 447.75 455.812zM555.375 691.312c45.25 5.062 78-27.562 72.875-72.875-5-45.312-45.875-86.156-91.125-91.219-45.375-5.031-78 27.594-72.938 72.906C469.249 645.436 510.125 686.281 555.375 691.312zM294.906 527.219c-45.25 5.062-86.062 45.906-91.125 91.219-5.063 45.313 27.594 77.938 72.812 72.875 45.312-5.031 86.156-45.875 91.222-91.188C372.875 554.812 340.219 522.188 294.906 527.219zM448 448v-192h384V448H448zM0 256h384V448H0V256z" horiz-adv-x="896" /> +<glyph glyph-name="gist" unicode="" d="M416 448l96-96-96-96 64-64 160 160-160 160-64-64z m-416 320v-832h768v832h-768z m704-768h-640v704h640v-704z m-352 256l-96 96 96 96-64 64-160-160 160-160 64 64z" horiz-adv-x="768" /> +<glyph glyph-name="gist-secret" unicode="" d="M193 128l128-192h-256l-65 256 257 64-64-128z m448 128l64-128-128-192h256l64 256-256 64z m-84 0h-216l44-102-64-218h256l-64 218 44 102z m84 192h-384l-128-64h640l-128 64z m-64 256l-128-64-128 64-64-192h384l-64 192z" horiz-adv-x="896" /> +<glyph glyph-name="git-branch" unicode="" d="M512 640c-71 0-128-57-128-128 0-47 26-88 64-110v-18c0-64-64-128-128-128-53 0-95-11-128-29v303c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 34-13 64-34 87 19 23 49 41 98 41 128 0 256 128 256 256v18c38 22 64 63 64 110 0 71-57 128-128 128z m-384 64c35 0 64-29 64-64s-29-64-64-64-64 29-64 64 29 64 64 64z m0-768c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m384 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="640" /> +<glyph glyph-name="git-commit" unicode="" d="M694.875 384C666.375 494.219 567.125 576 448 576c-119.094 0-218.375-81.781-246.906-192H0v-128h201.094C229.625 145.75 328.906 64 448 64c119.125 0 218.375 81.75 246.875 192H896V384H694.875zM448 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128C576 249.375 518.625 192 448 192z" horiz-adv-x="896" /> +<glyph glyph-name="git-compare" unicode="" d="M832 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-448 128s-32 0-64 0-64 32-64 64 0 402 0 402c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110 0 0 0-306 0-402s96-192 192-192c64 0 64 0 64 0v-128l192 192-192 192v-128z m-192 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="896" /> +<glyph glyph-name="git-merge" unicode="" d="M640 384c-47.625 0-88.625-26.312-110.625-64.906C523.625 319.5 518 320 512 320c-131.062 0-255.438 99.844-300.812 223.438C238.469 566.906 256 601.281 256 640c0 70.656-57.344 128-128 128S0 710.656 0 640c0-47.219 25.844-88.062 64-110.281V110.25C25.844 88.06200000000001 0 47.25 0 0c0-70.625 57.344-128 128-128s128 57.375 128 128c0 47.25-25.844 88.062-64 110.25V340.531C276.156 251.5 392.375 192 512 192c6.375 0 11.625 0.438 17.375 0.625C551.5 154.188 592.5 128 640 128c70.625 0 128 57.375 128 128C768 326.656 710.625 384 640 384zM128-64c-35.312 0-64 28.625-64 64 0 35.312 28.688 64 64 64 35.406 0 64-28.688 64-64C192-35.375 163.406-64 128-64zM128 576c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S163.406 576 128 576zM640 192c-35.312 0-64 28.625-64 64 0 35.406 28.688 64 64 64 35.375 0 64-28.594 64-64C704 220.625 675.375 192 640 192z" horiz-adv-x="768" /> +<glyph glyph-name="git-pull-request" unicode="" d="M704 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-512 832c-71 0-128-57-128-128 0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110v419c38 22 64 63 64 110 0 71-57 128-128 128z m0-832c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m0 640c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="768" /> +<glyph glyph-name="globe" unicode="" d="M512 704c-212.077 0-384-171.923-384-384s171.923-384 384-384c25.953 0 51.303 2.582 75.812 7.49-9.879 4.725-10.957 40.174-1.188 60.385 10.875 22.5 45 79.5 11.25 98.625s-24.375 27.75-45 49.875-12.19 25.451-13.5 31.125c-4.5 19.5 19.875 48.75 21 51.75s1.125 14.25 0.75 17.625S545.75 265.25 542 265.625s-5.625-6-10.875-6.375-28.125 13.875-33 17.625-7.125 12.75-13.875 19.5-7.5 1.5-18 5.625-44.25 16.5-70.125 27-28.125 25.219-28.5 35.625-15.75 25.5-22.961 36.375c-7.209 10.875-8.539 25.875-11.164 22.5s13.5-42.75 10.875-43.875-8.25 10.875-15.75 20.625 7.875 4.5-16.125 51.75 7.5 71.344 9 96 20.25-9 10.5 6.75 0.75 48.75-6.75 60.75S275 602 275 602c1.125 11.625 37.5 31.5 63.75 49.875s42.281 4.125 63.375-2.625 22.5-4.5 15.375 2.25 3 10.125 19.5 7.5 21-22.5 46.125-20.625 2.625-4.875 6-11.25-3.75-5.625-20.25-16.875S469.25 599 498.5 577.625s20.25 14.25 17.25 30S537.125 611 537.125 611c18-12 14.674-0.66 27.799-4.785S613.625 572 613.625 572c-44.625-24.375-16.5-27-9-32.625s-15.375-16.5-15.375-16.5c-9.375 9.375-10.875-0.375-16.875-3.75s-0.375-12-0.375-12c-31.031-4.875-24-37.5-23.625-45.375s-19.875-19.875-25.125-31.125S536.75 395 527 393.5s-19.5 36.75-72 22.5c-15.828-4.297-51-22.5-32.25-59.625s49.875 10.5 60.375 5.25-3-28.875-0.75-29.25 29.625-1.031 31.125-33 41.625-29.25 50.25-30 37.5 23.625 41.625 24.75S626 309.125 662 288.5s54.375-17.625 66.75-26.25 3.75-25.875 15.375-31.5 58.125 1.875 69.75-17.25-48-115.125-66.75-125.625S719.75 53.375 701 38s-45-34.406-69.75-49.125c-21.908-13.027-25.85-36.365-35.609-43.732C767.496-16.67999999999995 896 136.64999999999998 896 320 896 532.077 724.077 704 512 704zM602 343.625c-5.25-1.5-16.125-11.25-42.75 4.5s-45 12.75-47.25 15.375c0 0-2.25 6.375 9.375 7.5 23.871 2.311 54-22.125 60.75-22.5s10.125 6.75 22.125 2.883C616.25 347.52 607.25 345.125 602 343.625zM476.375 665.75c-2.615 1.902 2.166 4.092 5.016 7.875 1.645 2.186 0.425 5.815 2.484 7.875 5.625 5.625 33.375 13.5 27.949-1.875C506.4 664.25 480.5 662.75 476.375 665.75zM543.5 617c-9.375 0.375-31.443 2.707-27.375 6.75 15.844 15.75-6 20.25-19.5 21.375S477.5 653.75 484.25 654.5s33.75-0.375 38.25-4.125 28.875-13.5 30.375-20.625S552.875 616.625 543.5 617zM624.875 619.625c-7.5-6-45.24 21.529-52.5 27.75-31.5 27-48.375 18-54.99 22.5-6.617 4.5-4.26 10.5 5.865 19.5s38.625-3 55.125-4.875 35.625-14.625 36-29.781C614.75 639.564 632.375 625.625 624.875 619.625z" horiz-adv-x="1024" /> +<glyph glyph-name="graph" unicode="" d="M704 576H512v-640h192V576zM960 384H768v-448h192V384zM64-128V0h64v64H64V192h64v64H64V384h64v64H64V576h64v64H64V768h64V832H0v-1024h1024v64H64zM448 256H256v-320h192V256z" horiz-adv-x="1024" /> +<glyph glyph-name="heart" unicode="♥" d="M384-32c399 314 384 425 384 512s-72 192-192 192-192-128-192-128-72 128-192 128-192-105-192-192-15-198 384-512z" horiz-adv-x="768.199" /> +<glyph glyph-name="history" unicode="" d="M448 768c-90.938 0-175.312-27.531-245.938-74.062L128 768v-256h256l-88 88c45.438 24.688 96.688 40 152 40 176.75 0 320-143.219 320-320 0-176.75-143.25-320-320-320-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.406C24.312 480.5 0 403.406 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM447.031 1L512 64V256h128l64 64-64 64H512l-64 64L320 320l64-64v-192L447.031 1z" horiz-adv-x="896" /> +<glyph glyph-name="home" unicode="" d="M192 256l64-384h192V192h128v-320h192l64 384L512 576 192 256zM832 448V704H704l0.312-128.312L512 768 0 256h128l384 384 384-384h128L832 448z" horiz-adv-x="1024" /> +<glyph glyph-name="horizontal-rule" unicode="" d="M63.938 384h128v-128h64V639.938h-64V448h-128V639.938H0V256h63.938V384zM639.875 256V384h-63.938v-128H639.875zM639.875 448V575.938h-63.938V448H639.875zM447.938 448V575.938h128v64h-192V256h64V384h128v64H447.938zM0 0h639.875V128H0V0z" horiz-adv-x="639.875" /> +<glyph glyph-name="hourglass" unicode="" d="M571 320c118 85 197 240 197 384 0 71-172 128-384 128s-384-57-384-128c0-144 80-299 197-384-118-85-197-240-197-384 0-71 172-128 384-128s384 57 384 128c0 144-80 299-197 384z m-187 448c141 0 256-29 256-64s-115-64-256-64-256 29-256 64 115 64 256 64z m-64-706c-154-7-238-40-253-82 16 114 75 189 141 251 73 68 112 60 112 103v-273z m-105 352c-70 55-122 130-142 215 70-32 183-53 311-53s241 21 311 53c-20-85-72-160-142-215-24 17-70 34-169 34s-145-17-169-34z m233-352v273c0-43 39-35 112-103 66-62 125-138 141-251-14 41-99 75-253 82z" horiz-adv-x="768" /> +<glyph glyph-name="hubot" unicode="" d="M512 768c-283 0-512-229-512-512 0 0 0-192 0-256s64-128 128-128 704 0 768 0 128 64 128 128 0 256 0 256c0 283-229 512-512 512z m96-768h-192c-18 0-32 14-32 32s14 32 32 32h192c18 0 32-14 32-32s-14-32-32-32z m288 128c0-32-32-64-64-64s-128 0-128 0c0 32-32 64-64 64s-224 0-256 0-64-32-64-64c0 0-96 0-128 0s-64 32-64 64 0 360 0 360c78 129 220 216 384 216s306-87 384-216c0 0 0-328 0-360z m-128 384c-32 0-480 0-512 0s-64-32-64-64 0-96 0-128 32-64 64-64 480 0 512 0 64 32 64 64 0 96 0 128-32 64-64 64z m0-128l-64-64h-128l-64 64-64-64h-128l-64 64v64h64l64-64 64 64h128l64-64 64 64h64v-64z" horiz-adv-x="1024" /> +<glyph glyph-name="inbox" unicode="" d="M704 640H64L0 256v-256h768V256L704 640zM576 256l-64-128H256l-64 128H79l49 320h512l49-320H576z" horiz-adv-x="768" /> +<glyph glyph-name="info" unicode="" d="M448 448c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64z m0 320c-247 0-448-201-448-448s201-448 448-448 448 201 448 448-201 448-448 448z m0-768c-177 0-320 143-320 320s143 320 320 320 320-143 320-320-143-320-320-320z m64 320c0 32-32 64-64 64s-32 0-64 0-64-32-64-64h64s0-160 0-192 32-64 64-64 32 0 64 0 64 32 64 64h-64s0 160 0 192z" horiz-adv-x="896" /> +<glyph glyph-name="issue-closed" unicode="" d="M704 515.969l-96-96L768 256l256 256-96 96L769.25 449.219 704 515.969zM512 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 88.375 0 168.375-35.844 226.25-93.75l90.562 90.5C747.75 717.875 635.75 768 512 768 264.562 768 64 567.438 64 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448L759.75 119.75C768.688 130.75 684.75 0 512 0zM576 576H448v-320h128V576zM448 64h128V192H448V64z" horiz-adv-x="1024" /> +<glyph glyph-name="issue-opened" unicode="" d="M448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 176.75 0 320-143.219 320-320C768 143.25 624.75 0 448 0zM384 64h128V192H384V64zM384 256h128V576H384V256z" horiz-adv-x="896" /> +<glyph glyph-name="issue-reopened" unicode="" d="M639.125 64.75C585.75 24.625 520 0 448 0c-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.469C24.312 480.562 0 403.406 0 320c0-247.438 200.562-448 448-448 107.375 0 204.5 39.312 281.75 102.25L768-64V128H576L639.125 64.75zM384 64h128V192H384V64zM512 576H384v-320h128V576zM896 320c0 247.438-200.562 448-448 448-107.406 0-204.531-39.312-281.656-102.344L128 704v-192h192l-63.156 63.156C310.281 615.312 376 640 448 640c176.75 0 320-143.219 320-320 0-45.562-9.75-88.75-27-128h91v-99.5C871.688 159.438 896 236.5 896 320z" horiz-adv-x="896" /> +<glyph glyph-name="jersey" unicode="" d="M704 832h-192c0-32-33-64-97-64s-95 32-95 64h-192c0-128-2-384-128-384 0 0-1-544-1-576s32-64 64-64 672 0 704 0 64 32 64 64 0 576 0 576c-126 0-128 256-128 384z m-609-960c-16 0-31 10-31 32 0 32 0 480 0 480 119 64 128 192 128 384h64c0-96 32-191 160-192s160 96 160 192h64c0-186 32-276 64-339v-557s-593 0-609 0z m385 576l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z m-352 320l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z" horiz-adv-x="896" /> +<glyph glyph-name="jump-down" unicode="" d="M767.75 640H0.25L384 256.25 767.75 640zM0 128v-128h768V128H0z" horiz-adv-x="768" /> +<glyph glyph-name="jump-left" unicode="" d="M256.25 320L640-63.75v767.5L256.25 320zM0-64h128V704H0V-64z" horiz-adv-x="640" /> +<glyph glyph-name="jump-right" unicode="" d="M0-63.75L383.75 320 0 703.812V-63.75zM512 704v-768h128V704H512z" horiz-adv-x="640" /> +<glyph glyph-name="jump-up" unicode="" d="M0.188 0h767.5L384 383.75 0.188 0zM0 640v-128h768V640H0z" horiz-adv-x="768" /> +<glyph glyph-name="key" unicode="" d="M640.9 768.1c-141.4 0-256-114.6-256-256 0-19.6 2.2-38.6 6.4-56.9L0 64v-64l64-64h128l64 64v64h64v64h64v64h128l70.8 70.8c18.7-4.3 38.1-6.6 58.1-6.6 141.4 0 256 114.6 256 256S782.2 768.1 640.9 768.1zM384 320L64 0v64l320 320V320zM704 512c-35.3 0-64 28.7-64 64 0 35.3 28.7 64 64 64s64-28.7 64-64C768 540.7 739.3 512 704 512z" horiz-adv-x="896.9" /> +<glyph glyph-name="keyboard" unicode="" d="M640 256h64V384h-64V256zM768 576h-64v-128h64V576zM640 576h-64v-128h64V576zM512 256h64V384h-64V256zM384 64h320V192H384V64zM768 256h128V576h-64v-192h-64V256zM256 64h64V192h-64V64zM768 64h128V192H768V64zM512 576h-64v-128h64V576zM192 384h-64v-128h64V384zM192 192h-64v-128h64V192zM0 704v-768h1024V704H0zM960 0H64V640h896V0zM384 256h64V384h-64V256zM256 576H128v-128h128V576zM384 576h-64v-128h64V576zM256 256h64V384h-64V256z" horiz-adv-x="1024" /> +<glyph glyph-name="law" unicode="" d="M514 640c34 1 61 28 62 62 1 37-29 67-66 66-34-1-61-28-62-62-1-37 29-67 66-66z m464-384h-18l-127 246c18 2 36 9 52 16 24 11 29 43 11 62l-1 1c-11 11-28 15-43 8-14-6-34-13-53-13-56 0-81 64-287 64s-231-64-287-64c-20 0-39 6-53 13-15 6-32 3-43-8l-1-1c-18-19-13-50 11-62 16-8 34-14 52-16l-127-246h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15h-18l-127 245c83 7 127 49 191 49v-486c-35 0-64-29-64-64h-71c-28 0-57-29-57-64h512c0 35-29 64-71 64h-57c0 35-29 64-64 64v486c64 0 108-42 191-49l-127-245h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15z m-658 0h-192l96 180 96-180z m384 0l96 180 96-180h-192z" horiz-adv-x="1024" /> +<glyph glyph-name="light-bulb" unicode="" d="M512 768c-176.731 0-320-143.269-320-320 0-104.69 50.278-197.633 128-256.015V0c0-35.346 28.653-64 64-64 0-35.346 28.653-64 64-64h128c35.347 0 64 28.654 64 64 35.347 0 64 28.654 64 64V191.985C781.722 250.36699999999996 832 343.31 832 448 832 624.731 688.731 768 512 768zM640 32c0-17.673-14.326-32-32-32H416c-17.674 0-32 14.327-32 32v32h256V32zM704 278.693c-33.234-33.03-64-42.389-64-124.041V128h-64V256l128 128v64l-64 64-64-64-64 64-64-64-64 64-64-64v-64l128-128v-128h-64v26.652c0 81.652-30.766 91.011-64 124.041C280.177 323.82 256 383.082 256 448c0 141.385 114.615 256 256 256s256-114.615 256-256C768 383.082 743.823 323.82 704 278.693zM512 256L384 384v64l64-64 64 64 64-64 64 64v-64L512 256z" horiz-adv-x="1024" /> +<glyph glyph-name="link" unicode="" d="M768 576h-138c48-32 93-89 107-128h30c65 0 128-64 128-128s-65-128-128-128h-192c-63 0-128 64-128 128 0 23 7 45 18 64h-137c-5-21-8-42-8-64 0-128 127-256 255-256s65 0 193 0 256 128 256 256-128 256-256 256z m-481-384h-30c-65 0-128 64-128 128s65 128 128 128h192c63 0 128-64 128-128 0-23-7-45-18-64h137c5 21 8 42 8 64 0 128-127 256-255 256s-65 0-193 0-256-128-256-256 128-256 256-256h138c-48 32-93 89-107 128z" horiz-adv-x="1024" /> +<glyph glyph-name="link-external" unicode="" d="M640 64H128V574.094L256 576V704H0v-768h768V256H640V64zM384 704l128-128L320 384l128-128 192 192 128-128V704H384z" horiz-adv-x="768" /> +<glyph glyph-name="list-ordered" unicode="" d="M320 256h448v128h-448v-128z m0-256h448v128h-448v-128z m0 640v-128h448v128h-448z m-241-256h78v256h-36l-85-23v-50l43 2v-185z m110-206c0 36-12 78-96 78-33 0-64-6-83-16l1-66c21 10 42 15 67 15s32-11 32-28c0-26-30-58-110-112v-50h192v67l-91-2c49 30 87 66 87 113l1 1z" horiz-adv-x="768" /> +<glyph glyph-name="list-unordered" unicode="" d="M0 256h128v128h-128v-128z m0 256h128v128h-128v-128z m0-512h128v128h-128v-128z m256 256h512v128h-512v-128z m0 256h512v128h-512v-128z m0-512h512v128h-512v-128z" horiz-adv-x="768" /> +<glyph glyph-name="location" unicode="" d="M320 832c-177 0-320-143-320-320s160-416 320-704c160 288 320 527 320 704s-143 320-320 320z m0-448c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z" horiz-adv-x="640" /> +<glyph glyph-name="lock" unicode="" d="M704 384c-32 0-64 0-64 0s0 64 0 192-128 256-256 256-256-128-256-256 0-192 0-192-32 0-64 0-64-32-64-64 0-416 0-448 32-64 64-64 608 0 640 0 64 32 64 64 0 416 0 448-32 64-64 64z m-192-128h-384v-64h384v-64h-384v-64h384v-64h-384v-64h384v-64h-448v448h448v-64z m0 128h-256s0 128 0 192 64 128 128 128 128-64 128-128 0-192 0-192z" horiz-adv-x="768" /> +<glyph glyph-name="logo-github" unicode="" d="M552.73 499.865H311.557c-6.205 0-11.25-5.045-11.25-11.297v-117.887c0-6.252 5.045-11.272 11.25-11.272h94.109v-146.542c0 0-21.145-7.057-79.496-7.057-68.914 0-165.156 25.244-165.156 236.795 0 211.642 100.197 239.491 194.307 239.491 81.465 0 116.514-14.304 138.869-21.241 7.01-2.203 13.404 4.831 13.404 11.105L534.543 785.87c0 2.912-1.041 6.417-4.262 8.785C521.186 801.048 465.865 832 326.168 832 165.133 832 0 763.513 0 434.243 0 105.02099999999996 189.051 56 348.381 56c131.883 0 212.021 56.314 212.021 56.314 3.268 1.801 3.6 6.395 3.6 8.479V488.568C563.955 494.773 558.887 499.865 552.73 499.865zM1772.381 803.866h-135.695c-6.252 0-11.271-5.044-11.271-11.296v-262.393h-211.619V792.57c0 6.252-5.068 11.296-11.178 11.296h-135.838c-6.111 0-11.084-5.044-11.084-11.296v-710.473c0-6.299 5.021-11.32 11.084-11.32h135.838c6.203 0 11.178 5.068 11.178 11.32V385.933h211.619l-0.475-303.883c0-6.3 5.021-11.272 11.084-11.272h135.885c6.252 0 11.131 5.068 11.131 11.272l0.473 710.521C1783.607 798.822 1778.539 803.866 1772.381 803.866zM714.949 787.763c-48.357 0-87.574-39.572-87.574-88.403 0-48.855 39.217-88.428 87.574-88.428s87.527 39.572 87.527 88.428C802.477 748.19 763.307 787.763 714.949 787.763zM792.861 559.874c0 6.205-5.02 11.344-11.131 11.344H646.32c-6.348 0-11.746-6.394-11.746-12.67 0 0 0-394.654 0-469.867 0-13.735 8.572-17.903 19.703-17.903 0 0 57.688 0 121.959 0 13.311 0 16.814 6.536 16.814 18.188-0.094 25.197-0.094 123.808-0.094 142.942C792.861 250.09500000000003 792.861 559.874 792.861 559.874zM2297.973 570.152h-134.701c-6.158 0-11.084-5.092-11.084-11.344v-348.31c0 0-34.244-25.197-82.934-25.197-48.547 0-61.525 22.024-61.525 69.719 0 47.553 0 303.835 0 303.835 0 6.252-5.068 11.345-11.131 11.345h-136.643c-6.252 0-11.178-5.093-11.178-11.345 0 0 0-185.521 0-326.807 0-141.284 78.766-175.906 186.99-175.906 88.854 0 160.609 49.115 160.609 49.115s3.363-25.766 5.068-28.844c1.422-3.078 5.447-6.158 9.852-6.158h86.58c6.158 0 11.178 5.069 11.178 11.321l0.379 477.278C2309.15 565.0609999999999 2304.129 570.152 2297.973 570.152zM2666.932 586.1610000000001c-76.539 0-128.592-34.148-128.592-34.148V792.57c0 6.252-5.068 11.296-11.131 11.296h-136.264c-6.109 0-11.131-5.044-11.131-11.296l-0.379-710.521c0-6.3 5.068-11.272 11.225-11.272 0 0 94.773 0 94.869 0 4.215 0 7.389 2.179 9.805 5.968 2.369 3.837 5.73 32.775 5.73 32.775s55.557-52.763 161.035-52.763c123.807 0 194.758 62.804 194.758 281.906C2856.859 557.482 2743.471 586.1610000000001 2666.932 586.1610000000001zM2613.791 185.77499999999998c-46.701 1.421-78.34 22.64-78.34 22.64v225.07c0 0 31.307 19.206 69.672 22.593 48.547 4.31 95.438-10.326 95.438-126.13C2700.322 207.94100000000003 2679.199 183.83399999999995 2613.791 185.77499999999998zM1185.125 188.33299999999997c-5.969 0-21.219-2.368-36.85-2.368-49.92 0-66.971 23.256-66.971 53.331 0 30.218 0 199.85 0 199.85h101.926c6.252 0 11.178 5.044 11.178 11.343v109.48c0.094 6.299-4.926 11.344-11.178 11.344h-101.926l-0.143 134.535c0 5.092-2.699 7.625-8.572 7.625H933.861c-5.352 0-8.336-2.391-8.336-7.578v-139.035c0 0-69.576-16.79-74.266-18.188-4.641-1.326-8.051-5.684-8.051-10.822v-87.408c0-6.252 5.068-11.344 11.178-11.344h71.139c0 0 0-91.34 0-210.222 0-156.109 109.553-171.455 183.439-171.455 33.723 0 74.076 10.988 80.848 13.356 4.074 1.421 6.395 5.637 6.395 10.136l0.047 96.101C1196.254 183.312 1190.998 188.428 1185.125 188.33299999999997z" horiz-adv-x="2856.857" /> +<glyph glyph-name="mail" unicode="" d="M0 640v-640h896V640H0zM768 576L448 312 128 576H768zM64 512l252.031-191.625L64 128V512zM128 64l254 206.25L448 220l65.875 50.125L768 64H128zM832 128L579.625 320.062 832 512V128z" horiz-adv-x="896" /> +<glyph glyph-name="mail-read" unicode="" d="M576 448H256v-64h320V448zM384 576H256v-64h128V576zM768 603.469V704H627.188L448 832 268.812 704H128v-100.531L0 512v-640h896V512L768 603.469zM192 640h512v-244.812L448 184 192 395.188V640zM64 384l252.031-191.625L64 0V384zM128-64l254 206.25L448 92l65.875 50.125L768-64H128zM832 0L579.625 192.062 832 384V0z" horiz-adv-x="896" /> +<glyph glyph-name="mail-reply" unicode="" d="M384 672l-384-288 384-288v192c111 0 329-61 384-280 0 291-196 451-384 472v192z" horiz-adv-x="768" /> +<glyph glyph-name="mark-github" unicode="" d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" /> +<glyph glyph-name="markdown" unicode="" d="M950.154 640H73.846C33.127 640 0 606.873 0 566.154v-492.308C0 33.125 33.127 0 73.846 0h876.308c40.721 0 73.846 33.125 73.846 73.846V566.154C1024 606.873 990.875 640 950.154 640zM576 128.125L448 128V320l-96-123.077L256 320v-192H128V512h128l96-128 96 128 128 0.125V128.125zM767.091 96.125L608 320h96V512h128v-192h96L767.091 96.125z" horiz-adv-x="1024" /> +<glyph glyph-name="megaphone" unicode="" d="M832 800c-130 0-124-130-704-128C57.344 672 0 557.375 0 416s57.344-256 128-256c22.781 0 43.188-0.5 64.188-0.875L256-128l192-32 64 96-45.125 203.125C709.375 102.875 733.75 32 832 32c106 0 192 172 192 384C1024 628.031 938 800 832 800zM197 349.062c-39.188 1.469-82.188 2.25-127.562 2.625C66 371.406 64 393.094 64 416c0 88.375 28.688 192 64 192 39.031-0.125 75 0.438 109 1.406C209.656 562.438 192 493.688 192 416 192 392.688 194.062 370.562 197 349.062zM261.312 346.062C258.125 368.312 256 391.625 256 416c0 79.5 18.438 149.5 46.906 196.219 155.156 8.312 251.906 28.469 319.031 50.188C593.625 595.531 576 510.344 576 416c0-40 3.875-78 9.5-114.312C513.344 320.375 412.812 337.406 261.312 346.062zM832 128c-12.125 0-23.688 5.062-34.812 12.125-15.25 67.312-83.438 418.344 117.438 494.188C942.125 581.5 960 503.812 960 416 960 257 902.625 128 832 128z" horiz-adv-x="1024" /> +<glyph glyph-name="mention" unicode="" d="M466.697 732.899C238.66 760.898 31.1 598.735 3.102 370.698c-28-228.038 134.163-435.598 362.2-463.597 71.429-8.756 145.115 0.913 213.325 29.946l-0.016 0.032c24.404 10.357 35.788 38.538 25.431 62.939-10.359 24.403-38.538 35.787-62.94 25.43l-0.001 0.004c-52.472-22.339-109.15-29.799-164.1-23.067-175.413 21.538-300.153 181.2-278.616 356.613 21.538 175.413 181.199 300.154 356.613 278.616 175.412-21.538 300.154-181.199 278.617-356.612-4.309-35.083-21.542-55.725-61.6-55.725-42.5 0-64 45.889-64 81.222V432c0 26.51-21.49 48-48 48-9.699 0-18.72-2.887-26.269-7.833-25.684 20.259-57.437 33.87-94.349 38.402-105.246 12.923-201.045-61.924-213.967-167.17C212.508 238.15200000000004 287.354 142.35400000000004 392.6 129.43200000000002c57.379-7.045 116.216 14.707 157.871 53.13 24.959-28.124 59.866-47.624 100.121-52.567 87.707-10.769 167.537 51.602 178.307 139.309C856.898 497.34 694.734 704.899 466.697 732.899zM511.285 308.30100000000004c-6.462-52.623-54.361-90.047-106.985-83.585-52.623 6.461-90.046 54.36-83.585 106.984 6.461 52.623 54.361 90.046 106.984 83.585C480.322 408.823 517.746 360.924 511.285 308.30100000000004z" horiz-adv-x="832" /> +<glyph glyph-name="microscope" unicode="" d="M617-64c86.312 18.75 151 100 151 192 0 58.438-26.625 110.125-67.875 145.375C702.5 288.625 704 304.125 704 320c0 104.844-49.875 197.875-128 256l64 64v64l64 64L640 832l-64-64h-64L256 512l-128-64v-128l64-64h128l64 128 96 96c55.5-33.406 96-90.438 96-160-106.062 0-192-85.938-192-192H0v-64h192c19.125-14.25 42.062-22.125 64-32v-96H128L0-192h768L640-64H617zM512 128c0 35.375 28.625 64 64 64s64-28.625 64-64c0-35.312-28.625-64-64-64S512 92.68799999999999 512 128z" horiz-adv-x="768" /> +<glyph glyph-name="milestone" unicode="" d="M704 640H0v-256h704l128 128L704 640zM448 448H320V576h128V448zM448 832H320v-128h128V832zM320-192h128V320H320V-192z" horiz-adv-x="832" /> +<glyph glyph-name="mirror" unicode="" d="M320 512L128 320l192-192V256h384v-128l192 192L704 512v-128H320V512zM512 832L0 512v-704l512 256 512-256V512L512 832zM960-64L576 128v64H448v-64L64-64V448l384 256v-256h128V704l384-256V-64z" horiz-adv-x="1024" /> +<glyph glyph-name="mortar-board" unicode="" d="M501 244l-245 76s0-96 0-160 115-96 256-96 256 32 256 96 0 160 0 160l-245-76c-7-2-15-2-23 0h1z m18 409c-4 1-9 1-13 0l-489-152c-21-7-21-36 0-43l111-35v-113c-19-11-32-32-32-55 0-12 3-23 9-32-5-9-9-20-9-32v-165c0-35 128-35 128 0v165c0 12-3 23-9 32 5 9 9 20 9 32 0 24-13 44-32 55v93l313-98c4-1 9-1 13 0l489 152c21 7 21 36 0 43l-488 153z m-6-205c-35 0-64 14-64 32s29 32 64 32 64-14 64-32-29-32-64-32z" horiz-adv-x="1024" /> +<glyph glyph-name="move-down" unicode="" d="M640 512H448V832H192v-320H0l320-384L640 512zM0-192h640V0H0V-192z" horiz-adv-x="640" /> +<glyph glyph-name="move-left" unicode="" d="M0 0h192V640H0V0zM704 448V640L320 320l384-320V192h320V448H704z" horiz-adv-x="1024" /> +<glyph glyph-name="move-right" unicode="" d="M832 640v-640h192V640H832zM320 448H0v-256h320v-192l384 320L320 640V448z" horiz-adv-x="1024" /> +<glyph glyph-name="move-up" unicode="" d="M0 128h192v-320h256V128h192L320 512 0 128zM0 832v-192h640V832H0z" horiz-adv-x="640" /> +<glyph glyph-name="mute" unicode="" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM864 416l-64 64-96-96-96 96-63-63.5 95-96.5-96-96 64-64 96 96 96-96 64 64-96 96L864 416z" horiz-adv-x="896" /> +<glyph glyph-name="no-newline" unicode="" d="M896 512v-128H768V512L576 320l192-192V256h192c0 0 64 0.375 64 64s0 192 0 192H896zM224 544C100.281 544 0 443.719 0 320c0-123.75 100.281-224 224-224s224 100.25 224 224C448 443.719 347.719 544 224 544zM96 320c0 70.656 57.344 128 128 128 18.75 0 36.406-4.219 52.469-11.531L107.531 267.5C100.219 283.625 96 301.25 96 320zM224 192c-18.75 0-36.406 4.25-52.469 11.5l168.938 168.969C347.781 356.406 352 338.75 352 320 352 249.375 294.656 192 224 192z" horiz-adv-x="1024" /> +<glyph glyph-name="octoface" unicode="" d="M940.812 554.312c8.25 20.219 35.375 101.75-8.562 211.906 0 0-67.375 21.312-219.875-82.906C648.5 700.875 579.875 703.5 512 703.5c-67.906 0-136.438-2.625-200.5-20.25C159.031 787.531 91.719 766.219 91.719 766.219 47.812 656 74.938 574.531 83.188 554.312 31.5 498.438 0 427.125 0 339.656 0 10.437999999999988 213.25-64 510.844-64 808.562-64 1024 10.437999999999988 1024 339.656 1024 427.125 992.5 498.438 940.812 554.312zM512-1c-211.406 0-382.781 9.875-382.781 214.688 0 48.938 24.062 94.595 65.344 132.312 68.75 62.969 185.281 29.688 317.438 29.688 132.25 0 248.625 33.281 317.438-29.625 41.312-37.78 65.438-83.312 65.438-132.312C894.875 8.875 723.375-1 512-1zM351.156 319.562c-42.469 0-76.906-51.062-76.906-114.188s34.438-114.312 76.906-114.312c42.375 0 76.812 51.188 76.812 114.312S393.531 319.562 351.156 319.562zM672.875 319.562C630.5 319.562 596 268.5 596 205.375s34.5-114.312 76.875-114.312 76.812 51.188 76.812 114.312C749.75 268.5 715.312 319.562 672.875 319.562z" horiz-adv-x="1024" /> +<glyph glyph-name="organization" unicode="" d="M768 448h-64H576h-64-64-64-64H192h-64C57.344 448 0 390.656 0 320v-64c0-47.25 25.844-88.062 64-110.25V-64h256v-128h256V-64h256V145.75c38.125 22.188 64 62.938 64 110.25v64C896 390.656 838.625 448 768 448zM256 0H128V256H64v64c0 35.312 28.688 64 64 64h81.719c-11-18.875-17.719-40.562-17.719-64v-128c0-47.25 25.844-88.062 64-110.25V0zM576 128V256h-64v-384H384V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64v-128C640 156.625 611.375 128 576 128zM832 256h-64v-256H640v81.75c38.125 22.188 64 62.938 64 110.25V320c0 23.438-6.75 45.125-17.75 64H768c35.375 0 64-28.688 64-64V256zM303.688 514.625C338.875 474.125 390.156 448 448 448c57.875 0 109.125 26.125 144.312 66.625C614.125 475.062 655.688 448 704 448c70.625 0 128 57.344 128 128s-57.375 128-128 128c-25.625 0-49.375-7.688-69.375-20.688C614.875 768.438 539.062 832 448 832S281.094 768.438 261.375 683.312C241.344 696.312 217.594 704 192 704c-70.656 0-128-57.344-128-128s57.344-128 128-128C240.312 448 281.844 475.062 303.688 514.625zM704 640c35.375 0 64-28.594 64-64s-28.625-64-64-64c-35.312 0-64 28.594-64 64S668.688 640 704 640zM448 768c70.625 0 128-57.344 128-128s-57.375-128-128-128c-70.656 0-128 57.344-128 128S377.344 768 448 768zM192 512c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S227.406 512 192 512z" horiz-adv-x="896" /> +<glyph glyph-name="package" unicode="" d="M480 768L0 640v-576l480-128 480 128V640L480 768zM63.875 111.06600000000003L63.5 544l384.498-102.533 0.001-432.833L63.875 111.06600000000003zM63.5 608l160.254 42.734L640 539.735v-0.135l-160-42.667L63.5 608zM896.125 111.06600000000003L512.001 8.634000000000015l0.001 432.833L640 475.6v-156l128 34.135V509.733L896.5 544 896.125 111.06600000000003zM768 573.733v0.125L351.734 684.862 480 719.066 896.5 608 768 573.733z" horiz-adv-x="1024" /> +<glyph glyph-name="paintcan" unicode="" d="M384 832C171.923 832 0 660.077 0 448v-64c0-35.346 28.654-64 64-64v-320c0-70.692 143.269-128 320-128s320 57.308 320 128V320c35.346 0 64 28.654 64 64v64C768 660.077 596.077 832 384 832zM576 192v-32c0-17.673-14.327-32-32-32s-32 14.327-32 32v32c0 17.673-14.327 32-32 32s-32-14.327-32-32v-160c0-17.673-14.327-32-32-32s-32 14.327-32 32V160c0 17.673-14.327 32-32 32s-32-14.327-32-32v-32c0-35.346-28.654-64-64-64s-64 28.654-64 64v64c-35.346 0-64 28.654-64 64V371.193C186.382 340.108 279.318 320 384 320s197.618 20.108 256 51.193V256C640 220.654 611.346 192 576 192zM384 384c-107.433 0-199.393 26.474-237.372 64 37.979 37.526 129.939 64 237.372 64s199.393-26.474 237.372-64C583.393 410.474 491.433 384 384 384zM384 576c-176.62 0-319.816-57.236-319.996-127.867-0.001 0.001-0.002 0.001-0.003 0.002C64.075 624.804 207.314 768 384 768c176.731 0 320-143.269 320-320C704 518.692 560.731 576 384 576z" horiz-adv-x="768" /> +<glyph glyph-name="pencil" unicode="" d="M704 768L576 640l192-192 128 128L704 768zM0 64l0.688-192.562L192-128l512 512L512 576 0 64zM192-64H64V64h64v-64h64V-64z" horiz-adv-x="896" /> +<glyph glyph-name="person" unicode="" d="M448 640C448 746 362.062 832 256 832S64 746 64 640c0-106.062 85.938-192 192-192S448 533.938 448 640zM256 512c-70.656 0-128 57.344-128 128S185.344 768 256 768c70.625 0 128-57.344 128-128S326.625 512 256 512zM384 448H256 128C57.344 448 0 390.656 0 320v-128c0-70.625 57.344-128 128-128v-256h256V64c70.625 0 128 57.375 128 128V320C512 390.656 454.625 448 384 448zM448 192c0-35.375-28.625-64-64-64V256h-64v-384H192V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64V192z" horiz-adv-x="512" /> +<glyph glyph-name="pin" unicode="" d="M196 128l64-320 64 320c-20-2-43-3-64-3s-44 1-64 3z m254 299c-33 17-62 59-62 85v64c0 22 12 39 23 52 15 13 24 29 24 45 0 53-61 95-175 95s-175-42-175-95c0-16 9-32 24-45 11-13 23-30 23-52v-64c0-26-29-68-62-85-38-19-70-54-70-88 0-74 101-148 260-148s260 73 260 148c0 33-31 68-70 88z" horiz-adv-x="519.657" /> +<glyph glyph-name="playback-fast-forward" unicode="" d="M0 64l384 256L0 576V64zM768 320L384 576v-256-256L768 320z" horiz-adv-x="768" /> +<glyph glyph-name="playback-pause" unicode="" d="M0 0h192V640H0V0zM320 640v-640h192V640H320z" horiz-adv-x="512" /> +<glyph glyph-name="playback-play" unicode="" d="M0 640l512-320L0 0V640z" horiz-adv-x="512" /> +<glyph glyph-name="playback-rewind" unicode="" d="M384 320l384-256V576L384 320zM0 320l384-256V320 576L0 320z" horiz-adv-x="768" /> +<glyph glyph-name="plug" unicode="" d="M1003.386 627.336l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-45.707-45.707-90.51 90.51 45.707 45.707c24.744 24.744 24.744 64.861 0 89.605l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-47.973-47.973C621.76 802.446 537.237 795.66 482.502 740.926l-24.89-24.89c-109.011-109.011-121.948-277.692-38.854-400.892l-4.138-4.138c-62.392-62.392-62.484-163.493-0.275-225.999 12.41-12.469 12.642-33.327 0.121-45.683-12.509-12.343-32.655-12.292-45.101 0.153l-89.427 89.427c-62.637 62.638-164.63 63.747-227.299 1.141-62.542-62.479-62.562-163.829-0.058-226.332l8.763-8.763c24.744-24.744 64.861-24.744 89.605 0l0.905 0.905c24.744 24.744 24.744 64.861 0 89.605l-8.292 8.292c-12.329 12.329-13.085 32.418-1.098 45.081 12.437 13.138 33.174 13.353 45.882 0.645l89.328-89.328c62.92-62.92 165.504-63.814 228.081-0.553 61.793 62.468 61.65 163.161-0.431 225.451-12.55 12.592-12.777 32.866-0.207 45.437l4.151 4.151c123.2-83.095 291.881-70.158 400.892 38.854l24.89 24.89c54.734 54.735 61.52 139.258 20.362 201.382l47.973 47.973C1028.129 562.475 1028.129 602.593 1003.386 627.336zM889.796 333.632c-37.49-37.49-98.274-37.49-135.765 0L527.757 559.906c-37.49 37.49-37.49 98.274 0 135.765 29.556 29.556 73.585 35.804 109.269 18.759l-41.839-41.839c-24.744-24.744-24.744-64.861 0-89.604l0.905-0.905c24.744-24.744 64.861-24.744 89.605 0l45.707 45.707 90.51-90.51-45.707-45.707c-24.744-24.744-24.744-64.861 0-89.605l0.905-0.905c24.744-24.744 64.861-24.744 89.604 0l41.839 41.839C925.6 407.218 919.351 363.188 889.796 333.632z" horiz-adv-x="1024" /> +<glyph glyph-name="plus" unicode="" d="M384 384V640H256v-256H0v-128h256v-256h128V256h256V384H384z" horiz-adv-x="640" /> +<glyph glyph-name="podium" unicode="" d="M320 832c-32 0-64-32-64-64s0-64 0-64h-64l-192-192v-128h192l64-384-128-64v-64h512v64l-128 64 64 384h192v128l-192 192h-256v64s14 0 32 0 32 17 32 32-16 32-32 32 0 0-32 0z m0-832l-53 320h118l-1-320h-64z m-224 512l128 128h32v-64h64v64h224l128-128h-576z" horiz-adv-x="768" /> +<glyph glyph-name="primitive-dot" unicode="" d="M-0.088 320c0 141.5 114.5 256 256 256 141.438 0 256-114.5 256-256s-114.562-256-256-256C114.413 64-0.088 178.5-0.088 320z" horiz-adv-x="511.825" /> +<glyph glyph-name="primitive-square" unicode="" d="M512 64H0V576h512V64z" horiz-adv-x="512" /> +<glyph glyph-name="pulse" unicode="" d="M736 320.062L563.188 486.406 422.406 288 352 729.594 152.438 320.062H0V192h230.406L288 307.188l57.594-345.562L576 288l102.375-96H896V320.062H736z" horiz-adv-x="896" /> +<glyph glyph-name="puzzle" unicode="" d="M755.75 256.85c-13.95 9.96-28.52 16.59-43.47 19.92-8.84 1.69-18.06 2.33-27.57 1.81-8.99-0.5-17.56-1.68-25.69-3.52-6.1-1.69-12.22-3.89-18.35-6.59-18.18-8.02-33.89-18.12-46.79-30.33-12.22-12.9-22.32-28.62-30.34-46.79-2.7-6.12-4.9-12.24-6.59-18.34-1.84-8.14-3.03-16.7-3.52-25.69-0.52-9.51 0.12-18.73 1.81-27.57 3.33-14.95 9.96-29.52 19.92-43.47 3.89-5.44 8.08-10.4 12.56-14.88 20.06-20.03 45.83-30.7 75.42-34.11 8.92-1.02 18.12-1.68 26.53-4.48 5.12-1.7 9.16-4.08 12.08-7.02 6.65-6.6 7.63-16.1 2.5-27.24-3.15-6.84-7.7-13.45-12.96-18.84l-2.79-2.86c-3.93-3.92-6.41-6.4-7.05-7.04-3.13-3.16-6.1-6.15-9.06-9.15l-2.96-2.92c-10.52-10.58-21.09-21.12-31.66-31.65-22.76-22.82-45.57-45.58-68.38-68.36-7.5-7.5-15-15-22.5-22.49-3.46-3.45-7.07-6.38-10.78-8.79-1.8-1.22-3.49-2.24-5.18-3.16-19.6-9.89-41.43-5.92-59.24 11.88-5.4 5.4-10.62 10.62-15.85 15.84-30.25 30.25-60.48 60.52-90.77 90.73-8.59 8.57-17.13 17.08-25.68 25.59-6.12 6.09-12.67 11.85-19.56 17.06-5.72 4.33-11.59 7.56-17.46 9.73-21.16 7.32-41.41 2.01-54.67-13.26-3.81-4.8-7-10.47-9.39-16.94-3.43-9.26-4.6-19.47-5.9-29.36-4.9-37.53-25.8-68.43-55.98-82.65-7.48-3.65-15.49-6.29-23.9-7.78-7.95-1.41-15.95-1.71-23.85-1.04-26.61 1.35-49.48 13.09-68.51 32.57-1.68 1.67-2.1 2.09-2.51 2.51-19.48 19.02-31.22 41.9-32.57 68.5-0.68 7.9-0.37 15.9 1.04 23.85 1.49 8.41 4.13 16.43 7.78 23.9 14.22 30.18 45.13 51.07 82.65 55.97 9.89 1.29 20.1 2.47 29.36 5.9 6.94 2.56 12.96 6.05 17.97 10.23 14.54 13.15 19.59 32.63 12.84 52.34-2.78 7.35-6 13.22-10.33 18.94-5.21 6.88-10.97 13.43-17.06 19.55-8.51 8.55-17.03 17.09-25.55 25.63-26.92 26.98-53.84 53.88-80.75 80.78l-10.03 10.03c-5.22 5.22-10.45 10.45-15.26 15.27-18.39 18.4-22.35 40.22-12.46 59.82 0.92 1.69 1.94 3.37 3.08 5.05 2.49 3.84 5.42 7.45 8.87 10.91 7.49 7.5 14.99 15 22.49 22.5 22.77 22.81 45.54 45.62 68.36 68.38 10.53 10.57 21.06 21.14 31.65 31.66l2.92 2.96c2.99 2.97 5.99 5.93 8.98 8.9 0.8 0.81 3.28 3.29 7.2 7.22l2.86 2.79c5.39 5.26 12 9.8 18.84 12.96 11.14 5.13 20.63 4.15 27.24-2.5 2.94-2.92 5.32-6.96 7.02-12.08 2.79-8.41 3.45-17.61 4.48-26.53 3.41-29.59 14.08-55.35 34.11-75.41 4.49-4.48 9.44-8.67 14.88-12.56 13.95-9.96 28.52-16.59 43.47-19.92 8.84-1.69 18.06-2.33 27.57-1.81 8.99 0.5 17.56 1.68 25.69 3.52 6.1 1.69 12.22 3.89 18.35 6.59 18.18 8.02 33.89 18.12 46.79 30.33 12.22 12.9 22.32 28.62 30.34 46.79 2.7 6.12 4.9 12.24 6.59 18.34 1.84 8.14 3.03 16.7 3.52 25.69 0.52 9.51-0.12 18.73-1.81 27.57-3.33 14.95-9.96 29.52-19.92 43.47-3.89 5.44-8.08 10.4-12.56 14.88-20.06 20.03-45.83 30.7-75.42 34.11-8.92 1.02-18.12 1.68-26.53 4.48-5.12 1.7-9.16 4.08-12.08 7.02-6.65 6.6-7.63 16.1-2.5 27.24 3.15 6.84 7.7 13.45 12.96 18.84l2.79 2.86c3.93 3.92 6.41 6.4 7.05 7.04 3.13 3.16 6.1 6.15 9.06 9.15l2.96 2.92c10.52 10.58 21.09 21.12 31.66 31.65 22.76 22.82 45.57 45.58 68.38 68.35 7.5 7.5 15 15 22.5 22.49 3.46 3.45 7.07 6.38 10.78 8.79 1.8 1.22 3.49 2.24 5.18 3.16 19.6 9.89 41.43 5.92 59.24-11.88 5.4-5.4 10.62-10.62 15.85-15.84 30.25-30.25 60.48-60.52 90.77-90.73 8.59-8.57 17.13-17.08 25.68-25.59 6.12-6.09 12.67-11.85 19.56-17.06 5.72-4.33 11.59-7.56 17.46-9.73 21.16-7.32 41.41-2.01 54.67 13.26 3.81 4.8 7 10.47 9.39 16.94 3.43 9.26 4.6 19.47 5.9 29.36 4.9 37.53 25.8 68.43 55.98 82.65 7.48 3.65 15.49 6.28 23.9 7.78 7.95 1.41 15.95 1.71 23.85 1.04 26.61-1.35 49.48-13.09 68.51-32.57 1.68-1.67 2.1-2.09 2.51-2.51 19.48-19.02 31.22-41.9 32.57-68.5 0.68-7.9 0.37-15.9-1.04-23.85-1.49-8.41-4.13-16.43-7.78-23.9-14.22-30.18-45.13-51.07-82.65-55.97-9.89-1.29-20.1-2.47-29.36-5.9-6.94-2.56-12.96-6.05-17.97-10.23-14.54-13.15-19.59-32.63-12.84-52.34 2.78-7.35 6-13.22 10.33-18.94 5.21-6.88 10.97-13.43 17.06-19.55 8.51-8.55 17.03-17.09 25.55-25.63 30.26-30.33 60.54-60.56 90.78-90.81 5.22-5.22 10.45-10.45 15.26-15.27 18.39-18.4 22.35-40.22 12.46-59.82-0.92-1.69-1.94-3.37-3.08-5.05-2.49-3.84-5.42-7.45-8.87-10.91-7.49-7.5-14.99-15-22.49-22.5-22.77-22.81-45.54-45.62-68.36-68.38-10.53-10.57-21.06-21.14-31.65-31.66l-2.92-2.96c-2.99-2.97-5.99-5.93-8.98-8.9-0.8-0.81-3.28-3.29-7.2-7.22l-2.86-2.79c-5.39-5.26-12-9.8-18.84-12.96-11.14-5.13-20.63-4.15-27.24 2.5-2.94 2.92-5.32 6.96-7.02 12.08-2.79 8.41-3.45 17.61-4.48 26.53-3.41 29.59-14.08 55.35-34.11 75.41C766.15 248.76999999999998 761.19 252.97000000000003 755.75 256.85z" horiz-adv-x="1024" /> +<glyph glyph-name="question" unicode="" d="M448 64h128v128h-128v-128z m64 512c-96 0-192-96-192-192h128c0 32 32 64 64 64s64-32 64-64c0-64-128-64-128-128h128c64 22 128 64 128 160s-96 160-192 160z m0 256c-283 0-512-229-512-512s229-512 512-512 512 229 512 512-229 512-512 512z m0-896c-212 0-384 172-384 384s172 384 384 384 384-172 384-384-172-384-384-384z" horiz-adv-x="1024" /> +<glyph glyph-name="quote" unicode="" d="M0 320v-256h256V320H128c0 0 0 128 128 128V576C256 576 0 576 0 320zM640 448V576c0 0-256 0-256-256v-256h256V320H512C512 320 512 448 640 448z" horiz-adv-x="640" /> +<glyph glyph-name="radio-tower" unicode="" d="M306.838 441.261c15.868 16.306 15.868 42.731 0 59.037-20.521 21.116-30.643 48.417-30.705 76.124 0.062 27.77 10.183 55.039 30.705 76.186 15.868 16.337 15.868 42.764 0 59.069-7.934 8.184-18.272 12.275-28.706 12.275-10.371 0-20.804-4.029-28.738-12.213-36.266-37.297-54.633-86.433-54.57-135.317-0.062-48.792 18.305-97.927 54.57-135.161C265.262 424.955 290.97 424.955 306.838 441.261zM149.093 798.858c-8.121 8.309-18.68 12.463-29.3 12.463-10.558 0-21.179-4.154-29.237-12.463C30.8 737.509 0.751 656.856 0.813 576.422 0.751 496.081 30.8 415.272 90.494 353.985c16.181-16.618 42.356-16.618 58.537 0 16.118 16.587 16.118 43.513 0 60.067-43.7 44.98-65.44 103.456-65.44 162.368s21.74 117.449 65.44 162.368C165.149 755.439 165.149 782.365 149.093 798.858zM513.031 472.153c57.351 0 103.956 46.574 103.956 103.956 0 57.382-46.605 103.955-103.956 103.955-57.381 0-103.956-46.573-103.956-103.955C409.076 518.727 455.65 472.153 513.031 472.153zM933.539 798.233c-16.181 16.618-42.355 16.618-58.475 0-16.181-16.587-16.181-43.513 0-60.068 43.668-44.918 65.409-103.456 65.409-162.368 0-58.85-21.805-117.387-65.473-162.306-16.117-16.618-16.117-43.575 0.062-60.068 8.059-8.309 18.616-12.463 29.237-12.463 10.558 0 21.178 4.154 29.236 12.463 59.726 61.287 89.774 142.096 89.649 222.437C1023.313 656.138 993.264 736.947 933.539 798.233zM513.281 389.127L513.281 389.127c-26.489-0.062-53.04 6.466-77.091 19.429L235.057-127.59000000000003h95.209l54.819 63.973h255.891l53.977-63.973h95.272L589.124 408.431C565.384 395.655 539.395 389.127 513.281 389.127zM512.656 358.483L577.004 128.29999999999995H449.059L512.656 358.483zM385.086 0.3550000000000182l63.974 63.973h127.944l63.974-63.973H385.086zM717.194 710.958c-15.868-16.306-15.868-42.731 0-59.037 20.491-21.116 30.611-48.511 30.674-76.124-0.062-27.77-10.183-55.102-30.674-76.187-15.868-16.336-15.868-42.763 0-59.068 7.871-8.184 18.242-12.213 28.737-12.213 10.309 0 20.741 4.029 28.675 12.213 36.298 37.234 54.665 86.433 54.54 135.255 0.125 48.792-18.181 97.927-54.54 135.161C758.801 727.264 733.062 727.264 717.194 710.958z" horiz-adv-x="1024" /> +<glyph glyph-name="repo" unicode="" d="M320 576h-64v-64h64v64z m0 128h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 736 0 768-32 64-64 64z m0-800c0-16-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 17-32 32 0 96 0 96h640s0-80 0-96z m0 160h-512v576h513l-1-576z m-384 128h-64v-64h64v64z m0 128h-64v-64h64v64z" horiz-adv-x="768" /> +<glyph glyph-name="repo-clone" unicode="" d="M320 448h-64v-64h64v64z m-128 320h256v64s-352 0-384 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s286 0 320 0 64 32 64 64 0 192 0 192h-576v576z m512-640s0-79 0-96-14-32-32-32-288 0-288 0v64h-192v-64s-80 0-96 0-32 16-32 32 0 96 0 96h640z m-384 448h-64v-64h64v64z m-64-320h64v64h-64v-64z m704 576c-32 0-288 0-320 0s-64-32-64-64 0-352 0-384 32-64 64-64 64 0 64 0v-64l32 32 32-32v64s160 0 192 0 64 32 64 64 0 352 0 384-32 64-64 64z m-256-448s-15 0-32 0-32 15-32 32 0 32 0 32h64v-64z m256 32c0-16-15-32-32-32s-160 0-160 0v64h192s0-16 0-32z m0 96h-256v256h224s32 0 32-32 0-224 0-224z m-640 192h-64v-64h64v64z" horiz-adv-x="1024" /> +<glyph glyph-name="repo-force-push" unicode="" d="M768 768c0 32-32 64-64 64s-608 0-640 0-64-32-64-64 0-768 0-768 0 32 0 0 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 15-32 32 0 96 0 96h256v64h-128v576h512v-576h-128v-64h128s0-80 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768z m-272-320h144l-192 256-192-256h144l-144-192h128v-448h128v448h128l-144 192z" horiz-adv-x="767.896" /> +<glyph glyph-name="repo-forked" unicode="" d="M768 704c0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-106l-192-212-192 212v106c38 22 64 63 64 111 0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-156l256-282v-109c-38-22-64-63-64-111 0-71 57-128 128-128s128 57 128 128c0 47-26 89-64 111v109l256 282v156c38 22 64 63 64 111z m-640 63c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z m256-891c-34 0-62 28-62 62s28 62 62 62 62-28 62-62-28-62-62-62z m256 891c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z" horiz-adv-x="768" /> +<glyph glyph-name="repo-pull" unicode="" d="M1024 512l-192 192v-128h-384v-128h384v-128l192 192z m-320-320h-512v576h512v-128h64s0 96 0 128-32 64-64 64-608 0-640 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 384 0 384h-64v-192z m0-160c0-15-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 16-32 32 0 96 0 96h640s0-81 0-96z m-384 544h-64v-64h64v64z m0 128h-64v-64h64v64z m0-256h-64v-64h64v64z m-64-192h64v64h-64v-64z" horiz-adv-x="1024" /> +<glyph glyph-name="repo-push" unicode="" d="M448 512l-192-256h128v-448h128v448h128l-192 256z m-192 0h64v64h-64v-64z m64 192h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 14-32 32 0 96 0 96h256v64h-128v576h513l-1-576h-128v-64h128s0-79 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768-32 64-64 64z" horiz-adv-x="768" /> +<glyph glyph-name="rocket" unicode="" d="M716.737 707.944c-71.926-41.686-148.041-96.13-218.436-166.555-45-45.031-81.213-88.78-110.39-129.778L209.538 378.65 0.047 169.00300000000004l186.818-5.815 131.562 131.562c-46.439-96.224-50.536-160.019-50.536-160.019l58.854-58.792c0 0 65.827 6.255 162.737 53.163L355.107-5.119000000000028l5.88-186.881 209.585 209.521 33.086 179.252c41.403 29.02 85.185 65.046 129.716 109.545 70.425 70.455 124.837 146.541 166.555 218.466-45.97 9.351-88.125 28.488-121.397 61.668C745.257 619.819 725.994 661.975 716.737 707.944zM786.161 745.157c5.004-45 19.952-81.274 44.78-105.98 24.769-24.985 60.98-39.902 106.138-44.844C1003.063 727.677 1023.953 832 1023.953 832S919.63 811.142 786.161 745.157z" horiz-adv-x="1024" /> +<glyph glyph-name="rss" unicode="" d="M128 192C57.344 192 0 134.625 0 64s57.344-128 128-128 128 57.375 128 128S198.656 192 128 192zM128 448c0 0-64-2-64-64s64-64 64-64c141.375 0 256-114.625 256-256 0 0 0-64 64-64s64 64 64 64C512 276 340.031 448 128 448zM128 704c0 0-64 0-64-64s64-64 64-64c282.75 0 512-229.25 512-512 0 0 0-64 64-64s64 64 64 64C768 417.406 481.5 704 128 704z" horiz-adv-x="768" /> +<glyph glyph-name="ruby" unicode="" d="M768 704H256L0 448l512-512 512 512L768 704zM128 448l192 192h384l192-192L512 64 128 448zM704 576H512v-448l320 320L704 576z" horiz-adv-x="1024" /> +<glyph glyph-name="screen-full" unicode="" d="M128 64h639.875V576H128V64zM255.938 448h384v-256h-384V448zM64 639.938h191.938v64H0V448h64V639.938zM64 192H0v-255.938h255.938V0H64V192zM639.938 703.938v-64h191.938V448h64V703.938H639.938zM831.875 0H639.938v-63.938h255.938V192h-64V0z" horiz-adv-x="895.875" /> +<glyph glyph-name="screen-normal" unicode="" d="M127.938 640.062H0v-64h191.938V768h-64V640.062zM0-0.06200000000001182h127.938V-128h64V63.93799999999999H0V-0.06200000000001182zM768.062 640.062V768h-64v-191.938H896v64H768.062zM704.062-128h64V-0.06200000000001182H896v64H704.062V-128zM192.062 128H704V512H192.062V128zM320 384h256v-128H320V384z" horiz-adv-x="896" /> +<glyph glyph-name="search" unicode="" d="M960 0L710.875 249.125C746.438 307.188 768 374.844 768 448 768 660.031 596 832 384 832 171.969 832 0 660.031 0 448c0-212 171.969-384 384-384 73.156 0 140.812 21.562 198.875 57L832-128c17.5-17.5 46.5-17.375 64 0l64 64C977.5-46.5 977.5-17.5 960 0zM384 192c-141.375 0-256 114.625-256 256s114.625 256 256 256 256-114.625 256-256S525.375 192 384 192z" horiz-adv-x="973.125" /> +<glyph glyph-name="server" unicode="" d="M704 448h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192-128h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 832h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 64h-64v64h64v-64z" horiz-adv-x="768" /> +<glyph glyph-name="settings" unicode="" d="M64-64h128V128H64V-64zM192 704H64v-320h128V704zM512 704H384v-128h128V704zM0 192h256V320H0V192zM384-64h128V320H384V-64zM320 384h256V512H320V384zM832 704H704v-384h128V704zM640 256v-128h256V256H640zM704-64h128V64H704V-64z" horiz-adv-x="896" /> +<glyph glyph-name="sign-in" unicode="" d="M640 256L640 384 896 384 896 512 640 512 640 640 448 496 448 640 192 768 704 768 704 576 768 576 768 832 64 832 64 0 448-192 448 0 768 0 768 320 704 320 704 64 448 64 448 400z" horiz-adv-x="896" /> +<glyph glyph-name="sign-out" unicode="" d="M640 64H384V640L128 768h512v-192h64V832H0v-832l384-192V0h320V320h-64V64zM1024 448L768 640v-128H512v-128h256v-128L1024 448z" horiz-adv-x="1024" /> +<glyph glyph-name="split" unicode="" d="M448 576l-256 256-192-192 311-300c15 81 43 136 133 230l5 6z m128 256l133-133-197-197c-99-99-128-162-128-309v-384h256v384c0 52 19 94 53 128l197 197 133-133v448h-448z" horiz-adv-x="1024" /> +<glyph glyph-name="squirrel" unicode="" d="M768 768c-141.385 0-256-83.75-256-186.875C512 457.25 544 387 512 192c0 288-177 405.783-256 405.783 3.266 32.17-30.955 42.217-30.955 42.217s-14-7.124-19.354-21.583c-17.231 20.053-36.154 17.54-36.154 17.54l-8.491-37.081c0 0-117.045-40.876-118.635-206.292C56 371 141.311 353.898 201.887 364.882c57.157-2.956 42.991-50.648 30.193-63.446C178.083 247.438 128 320 64 320s-64-64 0-64 64-64 192-64c-198-77 0-256 0-256h-64c-64 0-64-64-64-64s256 0 384 0c192 0 320 64 320 222.182 0 54.34-27.699 114.629-64 162.228C697.057 349.433 782.453 427.566 832 384s192-64 192 128C1024 653.385 909.385 768 768 768zM160 448c-17.674 0-32 14.327-32 32 0 17.674 14.326 32 32 32 17.673 0 32-14.326 32-32C192 462.327 177.673 448 160 448z" horiz-adv-x="1024" /> +<glyph glyph-name="star" unicode="" d="M896 448l-313.5 40.781L448 768 313.469 488.781 0 448l230.469-208.875L171-63.93799999999999l277 148.812 277.062-148.812L665.5 239.125 896 448z" horiz-adv-x="896" /> +<glyph glyph-name="steps" unicode="" d="M136 768C60.89 768 0 667.71 0 544c0-68.83 17.02-141.84 34-254.54C47.3 201.16999999999996 79.67 128 136 128s94.08 48.79 94.08 137.97c0 30.37-24.97 78.75-26.08 120.03-2.02 74.46 49.93 104.17 49.93 173C253.93 682.71 211.1 768 136 768zM502.97 512c-75.1 0-117.93-85.29-117.93-209 0-68.83 51.95-98.54 49.93-173-1.109-41.28-26.08-89.66-26.08-120.03 0-89.18 37.75-137.97 94.08-137.97s88.7 73.17 102 161.46c16.98 112.7 34 185.71 34 254.54C638.97 411.71 578.08 512 502.97 512z" horiz-adv-x="640" /> +<glyph glyph-name="stop" unicode="" d="M704 832H320L0 512v-384l320-320h384l320 320V512L704 832zM896 192L640-64H384L128 192V448l256 256h256l256-256V192zM448 256h128V576H448V256zM448 64h128V192H448V64z" horiz-adv-x="1024" /> +<glyph glyph-name="sync" unicode="" d="M655.461 358.531c11.875-81.719-13.062-167.781-76.812-230.594-94.188-92.938-239.5-104.375-346.375-34.562l74.875 73L31.96 204.75 70.367-64l84.031 80.5c150.907-111.25 364.938-100.75 502.063 34.562 79.5 78.438 115.75 182.562 111.25 285.312L655.461 358.531zM189.46 511.938c94.156 92.938 239.438 104.438 346.313 34.562l-75-72.969 275.188-38.406L697.586 704l-83.938-80.688C462.711 734.656 248.742 724.031 111.585 588.75 32.085 510.344-4.133 406.219 0.335 303.5l112.25-22.125C100.71 363.125 125.71 449.094 189.46 511.938z" horiz-adv-x="768.051" /> +<glyph glyph-name="tag" unicode="" d="M384 768H128L0 640v-256l512-512 384 384L384 768zM64 416V608l96 96h192l448-448L512-32 64 416zM448 512L256 320l256-256 192 192L448 512zM352 320l96 96 160-160-96-96L352 320zM320 544c0 53-43 96-96 96s-96-43-96-96 43-96 96-96S320 491 320 544zM224 512c-17.656 0-32 14.344-32 32s14.344 32 32 32 32-14.344 32-32S241.656 512 224 512z" horiz-adv-x="896" /> +<glyph glyph-name="telescope" unicode="" d="M76 409c32 8 229 59 229 59-1-6-2-19-2-19 0-71 49-128 128-128s128 59 128 128c0 11-8 22-19 32l49-3s7 2 31 8c-51-14-108 31-126 99s8 135 60 149c-24-6-31-8-31-8l-168-110c-34-9-55-46-46-80 2-9 7-17 12-23-7-12-12-26-15-40-27 1-51 19-59 46-9 34 11 69 45 78l-245-65c-34-9-54-43-45-77s41-54 73-46z m419-153h-128v-64l-320-320h128l192 128v-128h128v128l192-128h128l-320 320v64z m429 448c-18 68-70 110-122 96-69-18-98-28-186-51-51-14-79-80-61-148s74-115 125-102c87 23 117 33 186 51 51 14 76 85 58 154z m-70-90c-17-5-42 17-51 51s-4 66 13 70 42-17 51-51 4-66-13-70z" horiz-adv-x="929.875" /> +<glyph glyph-name="terminal" unicode="" d="M831 705H63c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V641C895 676.35 866.35 705 831 705zM127 257l128 128L127 513l64 64 192-192L191 193 127 257zM639 193H383v64h256V193z" horiz-adv-x="896" /> +<glyph glyph-name="three-bars" unicode="" d="M0 640v-128h768v128h-768z m0-384h768v128h-768v-128z m0-256h768v128h-768v-128z" horiz-adv-x="768" /> +<glyph glyph-name="tools" unicode="" d="M286.547 366.984c16.843-16.812 81.716-85.279 81.716-85.279l35.968 37.093-56.373 58.248L456.072 491.98c0 0-48.842 47.623-27.468 28.655 20.438 75.903 1.812 160.589-55.842 220.243C315.608 800.064 234.392 819.47 161.425 799.096l123.653-127.715-32.53-125.309-121.06-33.438L7.898 640.3820000000001c-19.718-75.436-0.969-159.339 56.311-218.556C124.302 359.703 210.83 341.453 286.547 366.984zM698.815 242.769L549.694 95.46100000000001l245.932-254.805c20.062-20.812 46.498-31.188 72.872-31.188 26.25 0 52.624 10.375 72.811 31.188 40.249 41.624 40.249 108.997 0 150.62L698.815 242.769zM1023.681 670.162L867.06 832.001 405.387 354.703l56.373-58.248L185.425 10.839000000000055l-63.154-33.749-89.217-145.559 22.719-23.562 140.839 92.247 32.655 65.312 276.336 285.554 56.404-58.248L1023.681 670.162z" horiz-adv-x="1024" /> +<glyph glyph-name="trashcan" unicode="" d="M704 704H448c0 0 0 24.057 0 32 0 17.673-14.327 32-32 32s-32-14.327-32-32c0-17.673 0-32 0-32H128c-35.346 0-64-28.654-64-64v-64c0-35.346 28.654-64 64-64v-576c0-35.346 28.654-64 64-64h448c35.346 0 64 28.654 64 64V512c35.346 0 64 28.654 64 64v64C768 675.346 739.346 704 704 704zM640-32c0-17.673-14.327-32-32-32H224c-17.673 0-32 14.327-32 32V512h64v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480H384v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480h64L512 32c0-17.673 14.327-32 32-32s32 14.327 32 32V512h64V-32zM704 592c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v32c0 8.837 7.163 16 16 16h544c8.837 0 16-7.163 16-16V592z" horiz-adv-x="768" /> +<glyph glyph-name="triangle-down" unicode="" d="M0 448l383.75-383.75L767.5 448H0z" horiz-adv-x="767.5" /> +<glyph glyph-name="triangle-left" unicode="" d="M0 320.125l383.75-383.75v767.5L0 320.125z" horiz-adv-x="383.75" /> +<glyph glyph-name="triangle-right" unicode="" d="M0.062 703.75L383.812 320 0.062-63.75V703.75z" horiz-adv-x="383.875" /> +<glyph glyph-name="triangle-up" unicode="" d="M383.75 576L0 192.25h767.5L383.75 576z" horiz-adv-x="767.5" /> +<glyph glyph-name="unfold" unicode="" d="M384 448h128V640h128L448 832 256 640h128V448zM576 576v-64h224L672 384H224L96 512h224v64H0v-63.999L160 352 0 192v-64h320v64H96l128 128h448l128-128H576v-64h320v64L736 352l160 160.001V576H576zM512 256H384v-192H256l192-192 192 192H512V256z" horiz-adv-x="896" /> +<glyph glyph-name="unmute" unicode="" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM538.51 410.51c-12.496 12.497-32.758 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 24.994-24.993 24.994-65.516 0-90.51-12.496-12.496-12.496-32.758 0-45.255 12.497-12.496 32.759-12.496 45.255 0C588.497 279.47900000000004 588.497 360.523 538.51 410.51zM629.02 501.019c-12.495 12.497-32.758 12.497-45.255 0-12.495-12.496-12.495-32.758 0-45.255 74.981-74.98 74.981-196.548 0-271.528-12.495-12.497-12.495-32.76 0-45.256 12.497-12.496 32.76-12.496 45.255 0C728.994 238.95399999999995 728.994 401.045 629.02 501.019zM719.529 591.529c-12.497 12.497-32.76 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 124.968-124.968 124.968-327.58 0-452.548-12.496-12.497-12.496-32.759 0-45.255 12.495-12.497 32.758-12.497 45.255 0C869.49 198.433 869.49 441.568 719.529 591.529z" horiz-adv-x="896" /> +<glyph glyph-name="versions" unicode="" d="M0 128h128v64H64V448h64v64H0V128zM384 640v-640h512V640H384zM768 128H512V512h256V128zM192 64h128v64h-64V512h64v64H192V64z" horiz-adv-x="896" /> +<glyph glyph-name="x" unicode="" d="M640 512L512 640 320 448 128 640 0 512l192-192L0 128l128-128 192 192 192-192 128 128L448 320 640 512z" horiz-adv-x="640" /> +<glyph glyph-name="zap" unicode="⚡" d="M640 384H384L576 832 0 256h256L64-192 640 384z" horiz-adv-x="640" /> +</font> +</defs> +</svg> diff --git a/src/main/resources/octicons/octicons.ttf b/src/main/resources/octicons/octicons.ttf Binary files differnew file mode 100644 index 00000000..189ca281 --- /dev/null +++ b/src/main/resources/octicons/octicons.ttf diff --git a/src/main/resources/octicons/octicons.woff b/src/main/resources/octicons/octicons.woff Binary files differnew file mode 100644 index 00000000..2b770e42 --- /dev/null +++ b/src/main/resources/octicons/octicons.woff diff --git a/src/main/resources/octicons/sprockets-octicons.scss b/src/main/resources/octicons/sprockets-octicons.scss new file mode 100644 index 00000000..0e518fc1 --- /dev/null +++ b/src/main/resources/octicons/sprockets-octicons.scss @@ -0,0 +1,230 @@ +@font-face { + font-family: 'octicons'; + src: font-url('octicons.eot?#iefix') format('embedded-opentype'), + font-url('octicons.woff') format('woff'), + font-url('octicons.ttf') format('truetype'), + font-url('octicons.svg#octicons') format('svg'); + font-weight: normal; + font-style: normal; +} + +// .octicon is optimized for 16px. +// .mega-octicon is optimized for 32px but can be used larger. +.octicon, .mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mega-octicon { font-size: 32px; } + +.octicon-alert:before { content: '\f02d'} /* */ +.octicon-alignment-align:before { content: '\f08a'} /* */ +.octicon-alignment-aligned-to:before { content: '\f08e'} /* */ +.octicon-alignment-unalign:before { content: '\f08b'} /* */ +.octicon-arrow-down:before { content: '\f03f'} /* */ +.octicon-arrow-left:before { content: '\f040'} /* */ +.octicon-arrow-right:before { content: '\f03e'} /* */ +.octicon-arrow-small-down:before { content: '\f0a0'} /* */ +.octicon-arrow-small-left:before { content: '\f0a1'} /* */ +.octicon-arrow-small-right:before { content: '\f071'} /* */ +.octicon-arrow-small-up:before { content: '\f09f'} /* */ +.octicon-arrow-up:before { content: '\f03d'} /* */ +.octicon-beer:before { content: '\f069'} /* */ +.octicon-book:before { content: '\f007'} /* */ +.octicon-bookmark:before { content: '\f07b'} /* */ +.octicon-briefcase:before { content: '\f0d3'} /* */ +.octicon-broadcast:before { content: '\f048'} /* */ +.octicon-browser:before { content: '\f0c5'} /* */ +.octicon-bug:before { content: '\f091'} /* */ +.octicon-calendar:before { content: '\f068'} /* */ +.octicon-check:before { content: '\f03a'} /* */ +.octicon-checklist:before { content: '\f076'} /* */ +.octicon-chevron-down:before { content: '\f0a3'} /* */ +.octicon-chevron-left:before { content: '\f0a4'} /* */ +.octicon-chevron-right:before { content: '\f078'} /* */ +.octicon-chevron-up:before { content: '\f0a2'} /* */ +.octicon-circle-slash:before { content: '\f084'} /* */ +.octicon-circuit-board:before { content: '\f0d6'} /* */ +.octicon-clippy:before { content: '\f035'} /* */ +.octicon-clock:before { content: '\f046'} /* */ +.octicon-cloud-download:before { content: '\f00b'} /* */ +.octicon-cloud-upload:before { content: '\f00c'} /* */ +.octicon-code:before { content: '\f05f'} /* */ +.octicon-color-mode:before { content: '\f065'} /* */ +.octicon-comment-add:before, +.octicon-comment:before { content: '\f02b'} /* */ +.octicon-comment-discussion:before { content: '\f04f'} /* */ +.octicon-credit-card:before { content: '\f045'} /* */ +.octicon-dash:before { content: '\f0ca'} /* */ +.octicon-dashboard:before { content: '\f07d'} /* */ +.octicon-database:before { content: '\f096'} /* */ +.octicon-device-camera:before { content: '\f056'} /* */ +.octicon-device-camera-video:before { content: '\f057'} /* */ +.octicon-device-desktop:before { content: '\f27c'} /* */ +.octicon-device-mobile:before { content: '\f038'} /* */ +.octicon-diff:before { content: '\f04d'} /* */ +.octicon-diff-added:before { content: '\f06b'} /* */ +.octicon-diff-ignored:before { content: '\f099'} /* */ +.octicon-diff-modified:before { content: '\f06d'} /* */ +.octicon-diff-removed:before { content: '\f06c'} /* */ +.octicon-diff-renamed:before { content: '\f06e'} /* */ +.octicon-ellipsis:before { content: '\f09a'} /* */ +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { content: '\f04e'} /* */ +.octicon-file-binary:before { content: '\f094'} /* */ +.octicon-file-code:before { content: '\f010'} /* */ +.octicon-file-directory:before { content: '\f016'} /* */ +.octicon-file-media:before { content: '\f012'} /* */ +.octicon-file-pdf:before { content: '\f014'} /* */ +.octicon-file-submodule:before { content: '\f017'} /* */ +.octicon-file-symlink-directory:before { content: '\f0b1'} /* */ +.octicon-file-symlink-file:before { content: '\f0b0'} /* */ +.octicon-file-text:before { content: '\f011'} /* */ +.octicon-file-zip:before { content: '\f013'} /* */ +.octicon-flame:before { content: '\f0d2'} /* */ +.octicon-fold:before { content: '\f0cc'} /* */ +.octicon-gear:before { content: '\f02f'} /* */ +.octicon-gift:before { content: '\f042'} /* */ +.octicon-gist:before { content: '\f00e'} /* */ +.octicon-gist-secret:before { content: '\f08c'} /* */ +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { content: '\f020'} /* */ +.octicon-git-commit:before { content: '\f01f'} /* */ +.octicon-git-compare:before { content: '\f0ac'} /* */ +.octicon-git-merge:before { content: '\f023'} /* */ +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { content: '\f009'} /* */ +.octicon-globe:before { content: '\f0b6'} /* */ +.octicon-graph:before { content: '\f043'} /* */ +.octicon-heart:before { content: '\2665'} /* ♥ */ +.octicon-history:before { content: '\f07e'} /* */ +.octicon-home:before { content: '\f08d'} /* */ +.octicon-horizontal-rule:before { content: '\f070'} /* */ +.octicon-hourglass:before { content: '\f09e'} /* */ +.octicon-hubot:before { content: '\f09d'} /* */ +.octicon-inbox:before { content: '\f0cf'} /* */ +.octicon-info:before { content: '\f059'} /* */ +.octicon-issue-closed:before { content: '\f028'} /* */ +.octicon-issue-opened:before { content: '\f026'} /* */ +.octicon-issue-reopened:before { content: '\f027'} /* */ +.octicon-jersey:before { content: '\f019'} /* */ +.octicon-jump-down:before { content: '\f072'} /* */ +.octicon-jump-left:before { content: '\f0a5'} /* */ +.octicon-jump-right:before { content: '\f0a6'} /* */ +.octicon-jump-up:before { content: '\f073'} /* */ +.octicon-key:before { content: '\f049'} /* */ +.octicon-keyboard:before { content: '\f00d'} /* */ +.octicon-law:before { content: '\f0d8'} /* */ +.octicon-light-bulb:before { content: '\f000'} /* */ +.octicon-link:before { content: '\f05c'} /* */ +.octicon-link-external:before { content: '\f07f'} /* */ +.octicon-list-ordered:before { content: '\f062'} /* */ +.octicon-list-unordered:before { content: '\f061'} /* */ +.octicon-location:before { content: '\f060'} /* */ +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { content: '\f06a'} /* */ +.octicon-logo-github:before { content: '\f092'} /* */ +.octicon-mail:before { content: '\f03b'} /* */ +.octicon-mail-read:before { content: '\f03c'} /* */ +.octicon-mail-reply:before { content: '\f051'} /* */ +.octicon-mark-github:before { content: '\f00a'} /* */ +.octicon-markdown:before { content: '\f0c9'} /* */ +.octicon-megaphone:before { content: '\f077'} /* */ +.octicon-mention:before { content: '\f0be'} /* */ +.octicon-microscope:before { content: '\f089'} /* */ +.octicon-milestone:before { content: '\f075'} /* */ +.octicon-mirror-public:before, +.octicon-mirror:before { content: '\f024'} /* */ +.octicon-mortar-board:before { content: '\f0d7'} /* */ +.octicon-move-down:before { content: '\f0a8'} /* */ +.octicon-move-left:before { content: '\f074'} /* */ +.octicon-move-right:before { content: '\f0a9'} /* */ +.octicon-move-up:before { content: '\f0a7'} /* */ +.octicon-mute:before { content: '\f080'} /* */ +.octicon-no-newline:before { content: '\f09c'} /* */ +.octicon-octoface:before { content: '\f008'} /* */ +.octicon-organization:before { content: '\f037'} /* */ +.octicon-package:before { content: '\f0c4'} /* */ +.octicon-paintcan:before { content: '\f0d1'} /* */ +.octicon-pencil:before { content: '\f058'} /* */ +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { content: '\f018'} /* */ +.octicon-pin:before { content: '\f041'} /* */ +.octicon-playback-fast-forward:before { content: '\f0bd'} /* */ +.octicon-playback-pause:before { content: '\f0bb'} /* */ +.octicon-playback-play:before { content: '\f0bf'} /* */ +.octicon-playback-rewind:before { content: '\f0bc'} /* */ +.octicon-plug:before { content: '\f0d4'} /* */ +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { content: '\f05d'} /* */ +.octicon-podium:before { content: '\f0af'} /* */ +.octicon-primitive-dot:before { content: '\f052'} /* */ +.octicon-primitive-square:before { content: '\f053'} /* */ +.octicon-pulse:before { content: '\f085'} /* */ +.octicon-puzzle:before { content: '\f0c0'} /* */ +.octicon-question:before { content: '\f02c'} /* */ +.octicon-quote:before { content: '\f063'} /* */ +.octicon-radio-tower:before { content: '\f030'} /* */ +.octicon-repo-delete:before, +.octicon-repo:before { content: '\f001'} /* */ +.octicon-repo-clone:before { content: '\f04c'} /* */ +.octicon-repo-force-push:before { content: '\f04a'} /* */ +.octicon-gist-fork:before, +.octicon-repo-forked:before { content: '\f002'} /* */ +.octicon-repo-pull:before { content: '\f006'} /* */ +.octicon-repo-push:before { content: '\f005'} /* */ +.octicon-rocket:before { content: '\f033'} /* */ +.octicon-rss:before { content: '\f034'} /* */ +.octicon-ruby:before { content: '\f047'} /* */ +.octicon-screen-full:before { content: '\f066'} /* */ +.octicon-screen-normal:before { content: '\f067'} /* */ +.octicon-search-save:before, +.octicon-search:before { content: '\f02e'} /* */ +.octicon-server:before { content: '\f097'} /* */ +.octicon-settings:before { content: '\f07c'} /* */ +.octicon-log-in:before, +.octicon-sign-in:before { content: '\f036'} /* */ +.octicon-log-out:before, +.octicon-sign-out:before { content: '\f032'} /* */ +.octicon-split:before { content: '\f0c6'} /* */ +.octicon-squirrel:before { content: '\f0b2'} /* */ +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { content: '\f02a'} /* */ +.octicon-steps:before { content: '\f0c7'} /* */ +.octicon-stop:before { content: '\f08f'} /* */ +.octicon-repo-sync:before, +.octicon-sync:before { content: '\f087'} /* */ +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { content: '\f015'} /* */ +.octicon-telescope:before { content: '\f088'} /* */ +.octicon-terminal:before { content: '\f0c8'} /* */ +.octicon-three-bars:before { content: '\f05e'} /* */ +.octicon-tools:before { content: '\f031'} /* */ +.octicon-trashcan:before { content: '\f0d0'} /* */ +.octicon-triangle-down:before { content: '\f05b'} /* */ +.octicon-triangle-left:before { content: '\f044'} /* */ +.octicon-triangle-right:before { content: '\f05a'} /* */ +.octicon-triangle-up:before { content: '\f0aa'} /* */ +.octicon-unfold:before { content: '\f039'} /* */ +.octicon-unmute:before { content: '\f0ba'} /* */ +.octicon-versions:before { content: '\f064'} /* */ +.octicon-remove-close:before, +.octicon-x:before { content: '\f081'} /* */ +.octicon-zap:before { content: '\26A1'} /* ⚡ */ diff --git a/src/main/resources/sub32.png b/src/main/resources/sub32.png Binary files differnew file mode 100644 index 00000000..ebcfe13e --- /dev/null +++ b/src/main/resources/sub32.png diff --git a/src/site/design.mkd b/src/site/design.mkd index cd4b1b71..9ef302c1 100644 --- a/src/site/design.mkd +++ b/src/site/design.mkd @@ -57,6 +57,7 @@ The following dependencies are automatically downloaded by Gitblit GO (or alread - [jedis](https://github.com/xetorthio/jedis) (MIT)
- [Mina SSHD](https://mina.apache.org) (Apache 2.0)
- [pf4j](https://github.com/decebals/pf4j) (Apache 2.0)
+- [google-guice](https://code.google.com/p/google-guice) (Apache 2.0)
### Other Build Dependencies
- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
diff --git a/src/site/plugins_extensions.mkd b/src/site/plugins_extensions.mkd index 9e0d1705..82dd30bc 100644 --- a/src/site/plugins_extensions.mkd +++ b/src/site/plugins_extensions.mkd @@ -338,6 +338,16 @@ public class MyRepoLifeCycleListener extends RepositoryLifeCycleListener { } @Override + public void onFork(RepositoryModel origin, RepositoryModel fork) { + log.info("{} forked to {}", origin, fork); + } + + @Override + public void onRename(String oldName, RepositoryModel repo) { + log.info("{} renamed to {}", oldName, repo); + } + + @Override public void onDeletion(RepositoryModel repo) { log.info("Gitblit deleted {}", repo); } diff --git a/src/site/roadmap.mkd b/src/site/roadmap.mkd deleted file mode 100644 index ea643218..00000000 --- a/src/site/roadmap.mkd +++ /dev/null @@ -1,8 +0,0 @@ -## Roadmap
-
-This is not exactly a formal roadmap but it is a priority list of what might be implemented in future releases.
-This list is volatile and may not reflect what will be in the next release.
-
-* Add support for Project owners/administrators (ticket-75)
-* Add Project create/update pages
-* Integrate improvements for git-flow (ticket-55)
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd index de4ed471..4b065bf0 100644 --- a/src/site/rpc.mkd +++ b/src/site/rpc.mkd @@ -131,7 +131,7 @@ Use *SET_REPOSITORY_TEAM_PERMISSIONS* instead. ### Example: LIST_REPOSITORIES
-**url**: https://localhost/rpc?req=LIST_REPOSITORIES
+**url**: https://localhost/rpc/?req=LIST_REPOSITORIES
**response body**: Map<String, RepositoryModel> where the map key is the clone url of the repository
```json
@@ -183,7 +183,7 @@ Use *SET_REPOSITORY_TEAM_PERMISSIONS* instead. The original repository name is specified in the *name* url parameter. The new name is set within the JSON object.
-**url**: https://localhost/rpc?req=EDIT_REPOSITORY&name=libraries/xmlapache.git
+**url**: https://localhost/rpc/?req=EDIT_REPOSITORY&name=libraries/xmlapache.git
**post body**: RepositoryModel
```json
@@ -211,7 +211,7 @@ The original repository name is specified in the *name* url parameter. The new ```
### Example: LIST_USERS
-**url**: https://localhost/rpc?req=LIST_USERS
+**url**: https://localhost/rpc/?req=LIST_USERS
**response body**: List<UserModel>
```json
@@ -237,7 +237,7 @@ The original repository name is specified in the *name* url parameter. The new ```
### Example: LIST_SETTINGS
-**url**: https://localhost/rpc?req=LIST_SETTINGS
+**url**: https://localhost/rpc/?req=LIST_SETTINGS
**response body**: ServerSettings
```json
@@ -268,7 +268,7 @@ The original repository name is specified in the *name* url parameter. The new ```
### Example: LIST_STATUS
-**url**: https://localhost/rpc?req=LIST_STATUS
+**url**: https://localhost/rpc/?req=LIST_STATUS
**response body**: ServerStatus
```json
diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd index 87b67497..a3bf4451 100644 --- a/src/site/setup_authentication.mkd +++ b/src/site/setup_authentication.mkd @@ -83,10 +83,16 @@ Windows authentication is based on the use of Waffle and JNA. It is known to wo ### PAM Authentication
-PAM authentication is based on the use of libpam4j and JNA. To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine and the user that Gitblit runs-as must have root permissions.
+PAM authentication is based on the use of libpam4j and JNA. To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine.
realm.authenticationProviders = pam
- realm.pam.serviceName = system-auth
+ realm.pam.serviceName = gitblit
+
+Then define a gitblit authentication policy in `/etc/pam.d/gitblit`
+
+ # PAM configuration for the gitblit service
+ # Standard Un*x authentication.
+ @include common-auth
### Htpasswd Authentication
diff --git a/src/site/setup_bugtraq.mkd b/src/site/setup_bugtraq.mkd index ad4a75dd..fcd45d49 100644 --- a/src/site/setup_bugtraq.mkd +++ b/src/site/setup_bugtraq.mkd @@ -24,7 +24,7 @@ This file is formatted like a standard Git config file. Here are some quick exa logregex = "Change-Id:\\s*(I[A-Fa-f0-9]{40})"
[bugtraq "jira"]
- https://jira.atlassian.com/browse/%BUGID%
+ url = https://jira.atlassian.com/browse/%BUGID%
logregex = (JRA-\\d+)
[bugtraq "github"]
diff --git a/src/site/setup_fail2ban.mkd b/src/site/setup_fail2ban.mkd new file mode 100644 index 00000000..c735968d --- /dev/null +++ b/src/site/setup_fail2ban.mkd @@ -0,0 +1,24 @@ +## Configure fail2ban for Gitblit-SSH + +This procedure uses [fail2ban](http://www.fail2ban.org/). + +First, create a new filter file `gitblit.conf` in filter directory (Debian/CentOS: `/etc/fail2ban/filter.d/`) or into `filter.conf` file. Here is an example: + + [Definition] + failregex = Failed login attempt for .+, invalid credentials from <HOST>\s*$ + could not authenticate .*? \(/<HOST>:[0-9]*\) for SSH using the supplied password$ + ignoreregex = + +Then edit `jail.conf` to add "gitblit" service (Debian: `/etc/fail2ban/jail.conf`). For example: + + [gitblit] + enabled = true + port = 443,29418 + protocol = tcp + filter = gitblit + logpath = /var/log/gitblit.log + + +Reload fail2ban config to apply (`fail2ban-client reload`). + +Check the status of the gitblit fail2ban jail with `fail2ban-client status gitblit` diff --git a/src/site/setup_go.mkd b/src/site/setup_go.mkd index 81172447..c46e04ba 100644 --- a/src/site/setup_go.mkd +++ b/src/site/setup_go.mkd @@ -1,7 +1,7 @@ ## Gitblit GO Installation & Setup
1. Download and unzip Gitblit GO [${project.releaseVersion} (Windows)](%GCURL%gitblit-${project.releaseVersion}.zip) or [${project.releaseVersion} (Linux/OSX)](%GCURL%gitblit-${project.releaseVersion}.tar.gz).
-*Its best to eliminate spaces in the path name.*
+*It is best to eliminate spaces in the path name.*
2. The server itself is configured through a simple text file.
Open `data/gitblit.properties` in your favorite text editor and make sure to review and set:
- *server.httpPort* and *server.httpsPort*
@@ -9,7 +9,7 @@ Open `data/gitblit.properties` in your favorite text editor and make sure to rev **https** is strongly recommended because passwords are insecurely transmitted form your browser/git client using Basic authentication!
- *git.packedGitLimit* (set larger than the size of your largest repository)
3. Execute `authority.cmd` or `java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data` from a command-line
-**NOTE:** The Authority is a Swing GUI application. Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated certificates. Skipping this step will result in certificates with default metadata.
+**NOTE:** The Authority is a Swing GUI application. Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated self-signed certificates. Skipping this step will result in certificates with default metadata.
1. fill out the fields in the *new certificate defaults* dialog
2. enter the store password used in *server.storePassword* when prompted. This generates an SSL certificate for **localhost**.
3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from
@@ -30,6 +30,11 @@ If you are deploying Gitblit to a *nix platform, you might consider moving the d You can specify `GITBLIT_HOME` either as an environment variable or as a `-DGITBLIT_HOME` JVM system property.
+### Including Other Properties
+SINCE 1.7.0
+
+Gitblit supports loading it's settings from multiple properties files. You can achieve this using the `include=filename` key. This setting supports loading multiple files using a *comma* as the delimiter. They are processed in the order defined and they may be nested (i.e. your included properties may include properties, etc, etc).
+
### Creating your own Self-Signed SSL Certificate
Gitblit GO (and Gitblit Certificate Authority) automatically generates a Certificate Authority (CA) certificate and an ssl certificate signed by this CA certificate that is bound to *localhost*.
diff --git a/src/site/setup_war.mkd b/src/site/setup_war.mkd index a060bae1..21492b2a 100644 --- a/src/site/setup_war.mkd +++ b/src/site/setup_war.mkd @@ -31,3 +31,8 @@ This is a better way to configure your *baseFolder* because it survives WAR rede 1. Open TOMCAT_HOME/conf/context.xml
2. Insert an *Environment* node within the *Context* node.<pre><Environment name="baseFolder" type="java.lang.String" value="c:/projects/git/gitblit/data" override="false" /></pre>
+
+### Including Other Properties
+SINCE 1.7.0
+
+Gitblit supports loading it's settings from multiple properties files. You can achieve this using the `include=filename` key. This setting supports loading multiple files using a *comma* as the delimiter. They are processed in the order defined and they may be nested (i.e. your included properties may include properties, etc, etc).
diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd index ae954bfa..aec5c42a 100644 --- a/src/site/siteindex.mkd +++ b/src/site/siteindex.mkd @@ -23,12 +23,9 @@ <tbody>
<tr><th>License</th><td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a></td></tr>
<tr><th>Sources</th><td><a href="${project.scmUrl}">GitHub</a></td></tr>
- <tr><th>Issues</th><td><a href="${project.issuesUrl}">GoogleCode</a></td></tr>
+ <tr><th>Issues</th><td><a href="${project.issuesUrl}">GitHub</a></td></tr>
<tr><th>Discussion</th><td><a href="${project.forumUrl}">Gitblit Group</a></td></tr>
- <tr><th>Google+</th><td><a href="${project.socialNetworkUrl}">Gitblit+</a></td></tr>
<tr><th>Ohloh</th><td><a target="_top" href="http://www.ohloh.net/p/gitblit"><img border="0" width="100" height="16" src="http://www.ohloh.net/p/gitblit/widgets/project_thin_badge.gif" alt="Ohloh project report for Gitblit" /></a></td></tr>
- <tr><th>Donations</th><td>If you enjoy Gitblit and want to support its development, please consider making a donation to <a href="http://www.stjude.org">St. Jude Children's Research Hospital</a>.
- <a href="http://www.stjude.org" alt="St. Jude Children's Research Hospital"><img style="padding-top:10px;" src="stjude_150x150.gif"/></a></td></tr>
</tbody>
</table>
</div>
diff --git a/src/site/tickets_overview.mkd b/src/site/tickets_overview.mkd index 14e4ab99..10d0e18f 100644 --- a/src/site/tickets_overview.mkd +++ b/src/site/tickets_overview.mkd @@ -70,7 +70,7 @@ Gitblit has adopted Gerrit's three-repository workflow and *magic ref* design fo 1. The organizational unit of the Gitblit Tickets feature is the *ticket*. 2. A *ticket* can be used to report a bug, request an enhancement, ask a question, etc. A ticket can also be used to collaborate on a *patchset* that addresses the request. 3. A *patchset* is a series of commits from a merge base that exists in the target branch of your repository to the tip of the patchset. A patchset may only contain a single commit, or it may contain dozens. This is similar to the commits in a *Pull Request*. One important distinction here is that in Gitblit, each *Patchset* is developed on a separate branch and can be completely rewritten without losing the previous patchsets (this creates a new patchset). -4. A *ticket* monitors the development of *patchsets* by tracking *revisions* to *patchsets*. The ticket alslo monitors rewritten patchsets. Each *patchset* is developed on it's own Git branch. +4. A *ticket* monitors the development of *patchsets* by tracking *revisions* to *patchsets*. The ticket also monitors rewritten patchsets. Each *patchset* is developed on it's own Git branch. Tracking *patchsets* is similar in concept to Gerrit, but there is a critical difference. In Gerrit, *every* commit in the *patchset* has it's own ticket **AND** Git branch. In Gerrit, *patchsets* can be easily rewritten and for each rewritten commit, a new branch ref is created. This leads to an explosion in refs for the repository over time. In Gitblit, only the tip of the *patchset* gets a branch ref and this branch ref is updated, like a regular branch, unless a rewrite is detected. diff --git a/src/site/upgrade_go.mkd b/src/site/upgrade_go.mkd index 54d7ab59..a0092588 100644 --- a/src/site/upgrade_go.mkd +++ b/src/site/upgrade_go.mkd @@ -1,3 +1,17 @@ +## Upgrading Gitblit GO (1.7.0+)
+
+The default `gitblit.properties` file has been split into two files: `gitblit.properties`, which is the recommended file for setting your configuration, and `defaults.properties` which are Gitblit's default settings.
+
+ # Include Gitblit's 'defaults.properties' within your configuration.
+ #
+ # COMMA-DELIMITED
+ # SINCE 1.7.0
+ include = defaults.properties
+
+Notice that the default settings are *included* by your `gitblit.properties` file. The disadvantage to this approach is you must flip between discovering/reading the settings in `defaults.properties` and setting them in `gitblit.properties`, but there are some clear advantages too. This setup is not required. You may continue to keep all your settings in `gitblit.properties` like before.
+
+Additionally you may find it useful if you are maintaining several Gitblit instances to share common properties files.
+
## Upgrading Gitblit GO (1.2.1+)
1. Unzip Gitblit GO to a new folder
diff --git a/src/site/upgrade_war.mkd b/src/site/upgrade_war.mkd index 34ffd758..4165ca10 100644 --- a/src/site/upgrade_war.mkd +++ b/src/site/upgrade_war.mkd @@ -1,3 +1,21 @@ +## Upgrading Gitblit WAR (1.7.0+)
+
+The default `gitblit.properties` file has been split into two files: `gitblit.properties`, which is the recommended file for setting your configuration, and `defaults.properties` which are Gitblit's default settings.
+
+ # Include Gitblit's 'defaults.properties' within your configuration.
+ #
+ # COMMA-DELIMITED
+ # SINCE 1.7.0
+ include = defaults.properties
+
+Notice that the default settings are *included* by your `gitblit.properties` file. The disadvantage to this approach is you must flip between discovering/reading the settings in `defaults.properties` and setting them in `gitblit.properties`, but there are some clear advantages too. This setup is not required. You may continue to keep all your settings in `gitblit.properties` like before.
+
+Additionally you may find it useful if you are maintaining several Gitblit instances to share common properties files.
+
+## Upgrading Gitblit WAR (1.4.0+)
+
+The *baseFolder* context parameter has been replaced with a *baseFolder* JNDI env-entry. This means you can define the *baseFolder* from the administrative console of your servlet container and not have to manipulate anything in the web.xml file.
+
## Upgrading Gitblit WAR (1.2.1+)
1. Make sure your `WEB-INF/web.xml` *baseFolder* context parameter is not `${contextFolder}/WEB-INF/data`!
If it is, move your `WEB-INF/data` folder to a location writeable by your servlet container.
@@ -5,7 +23,6 @@ If it is, move your `WEB-INF/data` folder to a location writeable by your servle 3. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `${baseFolder}/gitblit.properties`.
-
## Upgrading Gitblit WAR (pre-1.2.1)
1. Create a `data` as outlined in step 1 of *Upgrading Gitblit GO (pre-1.2.1)*
@@ -14,7 +31,3 @@ If it is, move your `WEB-INF/data` folder to a location writeable by your servle 4. Copy the new WAR's `WEB-INF/data/gitblit.properties` file to your data folder
5. Manually apply any changes you made to your original web.xml file to the gitblit.properties file you copied to your data folder
6. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
-
-## Upgrading Gitblit WAR (1.4.0+)
-
-The *baseFolder* context parameter has been replaced with a *baseFolder* JNDI env-entry. This means you can define the *baseFolder* from the administrative console of your servlet container and not have to manipulate anything in the web.xml file.
\ No newline at end of file diff --git a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java index 0cdee6cb..f8dc8885 100644 --- a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java +++ b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java @@ -15,15 +15,44 @@ */ package com.gitblit.tests; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionContext; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; import org.junit.Test; +import com.gitblit.IUserService; +import com.gitblit.Keys; import com.gitblit.manager.AuthenticationManager; import com.gitblit.manager.IAuthenticationManager; -import com.gitblit.manager.IUserManager; +import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.RuntimeManager; import com.gitblit.manager.UserManager; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.tests.mock.MemorySettings; import com.gitblit.utils.XssFilter; @@ -35,35 +64,647 @@ import com.gitblit.utils.XssFilter.AllowXssFilter; * @author James Moger * */ +@SuppressWarnings("deprecation") public class AuthenticationManagerTest extends GitblitUnitTest { - IUserManager users; + UserManager users; + + private static final class DummyHttpServletRequest implements HttpServletRequest { + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public Enumeration<String> getAttributeNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public void setCharacterEncoding(String env) + throws UnsupportedEncodingException { + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public long getContentLengthLong() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getParameter(String name) { + return null; + } + + @Override + public Enumeration<String> getParameterNames() { + return null; + } + + @Override + public String[] getParameterValues(String name) { + return null; + } + + @Override + public Map<String, String[]> getParameterMap() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public String getScheme() { + return null; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 0; + } + + @Override + public BufferedReader getReader() throws IOException { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public void setAttribute(String name, Object o) { + } + + @Override + public void removeAttribute(String name) { + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration<Locale> getLocales() { + return null; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + return null; + } + + @Override + public String getRealPath(String path) { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, + ServletResponse servletResponse) + throws IllegalStateException { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public Cookie[] getCookies() { + return null; + } + + @Override + public long getDateHeader(String name) { + return 0; + } + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public Enumeration<String> getHeaders(String name) { + return null; + } + + @Override + public Enumeration<String> getHeaderNames() { + return null; + } + + @Override + public int getIntHeader(String name) { + return 0; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public String getPathInfo() { + return null; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public boolean isUserInRole(String role) { + if(role != null && "admin".equals(role)) { + return true; + } + return false; + } + + @Override + public Principal getUserPrincipal() { + return new Principal(){ + @Override + public String getName() { + return "sunnyjim"; + } + + }; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getRequestURI() { + return null; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession(boolean create) { + return null; + } + + final Map<String, Object> sessionAttributes = new HashMap<String, Object>(); + @Override + public HttpSession getSession() { + return new HttpSession() { + + @Override + public long getCreationTime() { + return 0; + } + + @Override + public String getId() { + return null; + } + + @Override + public long getLastAccessedTime() { + return 0; + } - MemorySettings getSettings() { - return new MemorySettings(new HashMap<String, Object>()); - } + @Override + public ServletContext getServletContext() { + return null; + } - IAuthenticationManager newAuthenticationManager() { - XssFilter xssFilter = new AllowXssFilter(); - RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start(); - users = new UserManager(runtime, null).start(); - AuthenticationManager auth = new AuthenticationManager(runtime, users).start(); - return auth; - } + @Override + public void setMaxInactiveInterval(int interval) { + } - @Test - public void testAuthenticate() throws Exception { - IAuthenticationManager auth = newAuthenticationManager(); + @Override + public int getMaxInactiveInterval() { + return 0; + } - UserModel user = new UserModel("sunnyjim"); + @Override + public HttpSessionContext getSessionContext() { + return null; + } + + @Override + public Object getAttribute(String name) { + return sessionAttributes.get(name); + } + + @Override + public Object getValue(String name) { + return null; + } + + @Override + public Enumeration<String> getAttributeNames() { + return Collections.enumeration(sessionAttributes.keySet()); + } + + @Override + public String[] getValueNames() { + return null; + } + + @Override + public void setAttribute(String name, + Object value) { + } + + @Override + public void putValue(String name, Object value) { + } + + @Override + public void removeAttribute(String name) { + } + + @Override + public void removeValue(String name) { + } + + @Override + public void invalidate() { + } + + @Override + public boolean isNew() { + return false; + } + + }; + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean authenticate(HttpServletResponse response) + throws IOException, ServletException { + return false; + } + + @Override + public void login(String username, String password) + throws ServletException { + } + + @Override + public void logout() throws ServletException { + } + + @Override + public Collection<Part> getParts() throws IOException, + ServletException { + return null; + } + + @Override + public Part getPart(String name) throws IOException, + ServletException { + return null; + } + + @Override + public <T extends HttpUpgradeHandler> T upgrade( + Class<T> handlerClass) throws IOException, + ServletException { + return null; + } + + } + + HashMap<String, Object> settings = new HashMap<String, Object>(); + + MemorySettings getSettings() { + return new MemorySettings(settings); + } + + IAuthenticationManager newAuthenticationManager() { + XssFilter xssFilter = new AllowXssFilter(); + RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start(); + users = new UserManager(runtime, null).start(); + final Map<String, UserModel> virtualUsers = new HashMap<String, UserModel>(); + users.setUserService(new IUserService() { + + @Override + public void setup(IRuntimeManager runtimeManager) { + } + + @Override + public String getCookie(UserModel model) { + return null; + } + + @Override + public UserModel getUserModel(char[] cookie) { + return null; + } + + @Override + public UserModel getUserModel(String username) { + return virtualUsers.get(username); + } + + @Override + public boolean updateUserModel(UserModel model) { + virtualUsers.put(model.username, model); + return true; + } + + @Override + public boolean updateUserModels(Collection<UserModel> models) { + return false; + } + + @Override + public boolean updateUserModel(String username, UserModel model) { + virtualUsers.put(username, model); + return true; + } + + @Override + public boolean deleteUserModel(UserModel model) { + return false; + } + + @Override + public boolean deleteUser(String username) { + return false; + } + + @Override + public List<String> getAllUsernames() { + return null; + } + + @Override + public List<UserModel> getAllUsers() { + return null; + } + + @Override + public List<String> getAllTeamNames() { + return null; + } + + @Override + public List<TeamModel> getAllTeams() { + return null; + } + + @Override + public List<String> getTeamNamesForRepositoryRole(String role) { + return null; + } + + @Override + public TeamModel getTeamModel(String teamname) { + return null; + } + + @Override + public boolean updateTeamModel(TeamModel model) { + return false; + } + + @Override + public boolean updateTeamModels(Collection<TeamModel> models) { + return false; + } + + @Override + public boolean updateTeamModel(String teamname, TeamModel model) { + return false; + } + + @Override + public boolean deleteTeamModel(TeamModel model) { + return false; + } + + @Override + public boolean deleteTeam(String teamname) { + return false; + } + + @Override + public List<String> getUsernamesForRepositoryRole(String role) { + return null; + } + + @Override + public boolean renameRepositoryRole(String oldRole, + String newRole) { + return false; + } + + @Override + public boolean deleteRepositoryRole(String role) { + return false; + } + + }); + AuthenticationManager auth = new AuthenticationManager(runtime, users).start(); + return auth; + } + + @Test + public void testAuthenticate() throws Exception { + IAuthenticationManager auth = newAuthenticationManager(); + + UserModel user = new UserModel("sunnyjim"); user.password = "password"; users.updateUserModel(user); - assertNotNull(auth.authenticate(user.username, user.password.toCharArray())); + assertNotNull(auth.authenticate(user.username, user.password.toCharArray(), null)); user.disabled = true; users.updateUserModel(user); - assertNull(auth.authenticate(user.username, user.password.toCharArray())); + assertNull(auth.authenticate(user.username, user.password.toCharArray(), null)); users.deleteUserModel(user); - } + } + + @Test + public void testContenairAuthenticate() throws Exception { + settings.put(Keys.realm.container.autoCreateAccounts, "true"); + settings.put(Keys.realm.container.autoAccounts.displayName, "displayName"); + settings.put(Keys.realm.container.autoAccounts.emailAddress, "emailAddress"); + settings.put(Keys.realm.container.autoAccounts.adminRole, "admin"); + settings.put(Keys.realm.container.autoAccounts.locale, "locale"); + + DummyHttpServletRequest request = new DummyHttpServletRequest(); + request.sessionAttributes.put("displayName", "Sunny Jim"); + request.sessionAttributes.put("emailAddress", "Jim.Sunny@gitblit.com"); + request.sessionAttributes.put("locale", "it"); + + IAuthenticationManager auth = newAuthenticationManager(); + + UserModel user = auth.authenticate(request); + + assertTrue(user.canAdmin); + assertEquals("Sunny Jim", user.displayName); + assertEquals("Jim.Sunny@gitblit.com", user.emailAddress); + assertEquals(Locale.ITALIAN, user.getPreferences().getLocale()); + } + + @Test + public void testContenairAuthenticateEmpty() throws Exception { + settings.put(Keys.realm.container.autoCreateAccounts, "true"); + settings.put(Keys.realm.container.autoAccounts.displayName, "displayName"); + settings.put(Keys.realm.container.autoAccounts.emailAddress, "emailAddress"); + settings.put(Keys.realm.container.autoAccounts.adminRole, "notAdmin"); + + DummyHttpServletRequest request = new DummyHttpServletRequest(); + + IAuthenticationManager auth = newAuthenticationManager(); + + UserModel user = auth.authenticate(request); + + assertFalse(user.canAdmin); + assertEquals("sunnyjim", user.displayName); + assertNull(user.emailAddress); + assertNull(user.getPreferences().getLocale()); + } + } diff --git a/src/test/java/com/gitblit/tests/DiffUtilsTest.java b/src/test/java/com/gitblit/tests/DiffUtilsTest.java index 9d627b80..e8e839a5 100644 --- a/src/test/java/com/gitblit/tests/DiffUtilsTest.java +++ b/src/test/java/com/gitblit/tests/DiffUtilsTest.java @@ -23,6 +23,7 @@ import org.junit.Test; import com.gitblit.models.AnnotatedLine;
import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
import com.gitblit.utils.DiffUtils.DiffOutputType;
import com.gitblit.utils.JGitUtils;
@@ -40,7 +41,7 @@ public class DiffUtilsTest extends GitblitUnitTest { Repository repository = GitBlitSuite.getHelloworldRepository();
RevCommit commit = JGitUtils.getCommit(repository,
"1d0c2933a4ae69c362f76797d42d6bd182d05176");
- String diff = DiffUtils.getCommitDiff(repository, commit, DiffOutputType.PLAIN).content;
+ String diff = DiffUtils.getCommitDiff(repository, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
repository.close();
assertTrue(diff != null && diff.length() > 0);
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\"";
@@ -54,7 +55,7 @@ public class DiffUtilsTest extends GitblitUnitTest { "8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca");
RevCommit commit = JGitUtils.getCommit(repository,
"1d0c2933a4ae69c362f76797d42d6bd182d05176");
- String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffOutputType.PLAIN).content;
+ String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
repository.close();
assertTrue(diff != null && diff.length() > 0);
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\"";
@@ -66,7 +67,7 @@ public class DiffUtilsTest extends GitblitUnitTest { Repository repository = GitBlitSuite.getHelloworldRepository();
RevCommit commit = JGitUtils.getCommit(repository,
"1d0c2933a4ae69c362f76797d42d6bd182d05176");
- String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffOutputType.PLAIN).content;
+ String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
repository.close();
assertTrue(diff != null && diff.length() > 0);
String expected = "- system.out.println(\"Hello World\");\n+ System.out.println(\"Hello World\"";
diff --git a/src/test/java/com/gitblit/tests/FilestoreManagerTest.java b/src/test/java/com/gitblit/tests/FilestoreManagerTest.java new file mode 100644 index 00000000..c76e9dd6 --- /dev/null +++ b/src/test/java/com/gitblit/tests/FilestoreManagerTest.java @@ -0,0 +1,547 @@ +package com.gitblit.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.codec.digest.DigestUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.AuthorizationControl; +import com.gitblit.Keys; +import com.gitblit.models.FilestoreModel.Status; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.FileUtils; + + +/** + * Test of the filestore manager and confirming filesystem updated + * + * @author Paul Martin + * + */ +public class FilestoreManagerTest extends GitblitUnitTest { + + private static final AtomicBoolean started = new AtomicBoolean(false); + + private static final BlobInfo blob_zero = new BlobInfo(0); + private static final BlobInfo blob_512KB = new BlobInfo(512*FileUtils.KB); + private static final BlobInfo blob_6MB = new BlobInfo(6*FileUtils.MB); + + private static int download_limit_default = -1; + private static int download_limit_test = 5*FileUtils.MB; + + private static final String invalid_hash_empty = ""; + private static final String invalid_hash_major = "INVALID_HASH"; + private static final String invalid_hash_regex_attack = blob_512KB.hash.replace('a', '*'); + private static final String invalid_hash_one_long = blob_512KB.hash.concat("a"); + private static final String invalid_hash_one_short = blob_512KB.hash.substring(1); + + + + @BeforeClass + public static void startGitblit() throws Exception { + started.set(GitBlitSuite.startGitblit()); + } + + @AfterClass + public static void stopGitblit() throws Exception { + if (started.get()) { + GitBlitSuite.stopGitblit(); + } + } + + + + @Test + public void testAdminAccess() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date()); + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + + UserModel u = new UserModel("admin"); + u.canAdmin = true; + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default); + + //Invalid hash tests + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut)); + + // Download prior to upload + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Bad input is rejected with no upload taking place + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Invalid_Size, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Hash_Mismatch, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + //Confirm no upload with bad input + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + assertArrayEquals(blob_512KB.blob, streamOut.toByteArray()); + + //Subsequent failed uploads do not affect file + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + assertArrayEquals(blob_512KB.blob, streamOut.toByteArray()); + + //Zero length upload is valid + assertEquals(Status.Available, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob))); + + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_zero.hash, u, r, streamOut)); + assertArrayEquals(blob_zero.blob, streamOut.toByteArray()); + + + //Pre-informed upload identifies identical errors as immediate upload + assertEquals(Status.Upload_Pending, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.Available, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + assertArrayEquals(blob_6MB.blob, streamOut.toByteArray()); + + //Confirm the relevant files exist + assertTrue("Admin did not save zero length file!", filestore().getStoragePath(blob_zero.hash).exists()); + assertTrue("Admin did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertTrue("Admin did not save 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists()); + + //Clear the files and cache to test upload limit property + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test); + + assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + assertArrayEquals(blob_512KB.blob, streamOut.toByteArray()); + + assertEquals(Status.Error_Exceeds_Size_Limit, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertTrue("Admin did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertFalse("Admin saved 6MB file despite (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists()); + + } + + @Test + public void testAuthenticatedAccess() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date()); + r.authorizationControl = AuthorizationControl.AUTHENTICATED; + r.accessRestriction = AccessRestrictionType.VIEW; + + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + + UserModel u = new UserModel("test"); + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default); + + //Invalid hash tests + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut)); + + // Download prior to upload + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Bad input is rejected with no upload taking place + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Invalid_Size, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Hash_Mismatch, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + //Confirm no upload with bad input + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + assertArrayEquals(blob_512KB.blob, streamOut.toByteArray()); + + //Subsequent failed uploads do not affect file + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + assertArrayEquals(blob_512KB.blob, streamOut.toByteArray()); + + //Zero length upload is valid + assertEquals(Status.Available, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob))); + + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_zero.hash, u, r, streamOut)); + assertArrayEquals(blob_zero.blob, streamOut.toByteArray()); + + + //Pre-informed upload identifies identical errors as immediate upload + assertEquals(Status.Upload_Pending, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.Available, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + assertArrayEquals(blob_6MB.blob, streamOut.toByteArray()); + + //Confirm the relevant files exist + assertTrue("Authenticated user did not save zero length file!", filestore().getStoragePath(blob_zero.hash).exists()); + assertTrue("Authenticated user did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertTrue("Authenticated user did not save 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists()); + + //Clear the files and cache to test upload limit property + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test); + + assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + assertArrayEquals(blob_512KB.blob, streamOut.toByteArray()); + + assertEquals(Status.Error_Exceeds_Size_Limit, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertTrue("Authenticated user did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertFalse("Authenticated user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists()); + + } + + @Test + public void testAnonymousAccess() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date()); + r.authorizationControl = AuthorizationControl.NAMED; + r.accessRestriction = AccessRestrictionType.CLONE; + + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + + UserModel u = UserModel.ANONYMOUS; + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default); + + //Invalid hash tests + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut)); + + // Download prior to upload + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Bad input is rejected with no upload taking place + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + //Confirm no upload with bad input + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Subsequent failed uploads do not affect file + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Zero length upload is valid + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_zero.hash, u, r, streamOut)); + + + //Pre-informed upload identifies identical errors as immediate upload + assertEquals(Status.AuthenticationRequired, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, -1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + //Confirm the relevant files do not exist + assertFalse("Anonymous user saved zero length file!", filestore().getStoragePath(blob_zero.hash).exists()); + assertFalse("Anonymous user 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertFalse("Anonymous user 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists()); + + //Clear the files and cache to test upload limit property + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertFalse("Anonymous user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertFalse("Anonymous user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists()); + + } + + @Test + public void testUnauthorizedAccess() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date()); + r.authorizationControl = AuthorizationControl.NAMED; + r.accessRestriction = AccessRestrictionType.VIEW; + + ByteArrayOutputStream streamOut = new ByteArrayOutputStream(); + + UserModel u = new UserModel("test"); + u.setRepositoryPermission(r.name, AccessPermission.CLONE); + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default); + + //Invalid hash tests + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut)); + assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut)); + + // Download prior to upload + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Bad input is rejected with no upload taking place + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + //Confirm no upload with bad input + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Subsequent failed uploads do not affect file + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + //Zero length upload is valid + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_zero.hash, u, r, streamOut)); + + + //Pre-informed upload identifies identical errors as immediate upload + assertEquals(Status.Error_Unauthorized, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r)); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, -1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob))); + + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + //Good input will accept the upload + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + //Confirm the relevant files exist + assertFalse("Unauthorized user saved zero length file!", filestore().getStoragePath(blob_zero.hash).exists()); + assertFalse("Unauthorized user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertFalse("Unauthorized user saved 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists()); + + //Clear the files and cache to test upload limit property + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut)); + + assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob))); + streamOut.reset(); + assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut)); + + assertFalse("Unauthorized user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists()); + assertFalse("Unauthorized user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists()); + + } + +} + +/* + * Test helper structure to create blobs of a given size + */ +final class BlobInfo { + public byte[] blob; + public String hash; + public int length; + + public BlobInfo(int nBytes) { + blob = new byte[nBytes]; + new java.util.Random().nextBytes(blob); + hash = DigestUtils.sha256Hex(blob); + length = nBytes; + } +}
\ No newline at end of file diff --git a/src/test/java/com/gitblit/tests/FilestoreServletTest.java b/src/test/java/com/gitblit/tests/FilestoreServletTest.java new file mode 100644 index 00000000..4e4b056a --- /dev/null +++ b/src/test/java/com/gitblit/tests/FilestoreServletTest.java @@ -0,0 +1,355 @@ +package com.gitblit.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.gitblit.Keys; +import com.gitblit.manager.FilestoreManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.models.FilestoreModel.Status; +import com.gitblit.servlet.FilestoreServlet; +import com.gitblit.utils.FileUtils; + +public class FilestoreServletTest extends GitblitUnitTest { + + private static final AtomicBoolean started = new AtomicBoolean(false); + + private static final String SHA256_EG = "9a712c5d4037503a2d5ee1d07ad191eb99d051e84cbb020c171a5ae19bbe3cbd"; + + private static final String repoName = "helloworld.git"; + + private static final String repoLfs = "/r/" + repoName + "/info/lfs/objects/"; + + @BeforeClass + public static void startGitblit() throws Exception { + started.set(GitBlitSuite.startGitblit()); + } + + @AfterClass + public static void stopGitblit() throws Exception { + if (started.get()) { + GitBlitSuite.stopGitblit(); + } + } + + + @Test + public void testRegexGroups() throws Exception { + + Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH); + + String basicUrl = "https://localhost:8080/r/test.git/info/lfs/objects/"; + String batchUrl = basicUrl + "batch"; + String oidUrl = basicUrl + SHA256_EG; + + Matcher m = p.matcher(batchUrl); + assertTrue(m.find()); + assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI)); + assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX)); + assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY)); + assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT)); + + m = p.matcher(oidUrl); + assertTrue(m.find()); + assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI)); + assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX)); + assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY)); + assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT)); + } + + @Test + public void testRegexGroupsNestedRepo() throws Exception { + + Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH); + + String basicUrl = "https://localhost:8080/r/nested/test.git/info/lfs/objects/"; + String batchUrl = basicUrl + "batch"; + String oidUrl = basicUrl + SHA256_EG; + + Matcher m = p.matcher(batchUrl); + assertTrue(m.find()); + assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI)); + assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX)); + assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY)); + assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT)); + + m = p.matcher(oidUrl); + assertTrue(m.find()); + assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI)); + assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX)); + assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY)); + assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT)); + } + + @Test + public void testDownload() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = gitblit().getRepositoryModel(repoName); + + UserModel u = new UserModel("admin"); + u.canAdmin = true; + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE); + + final BlobInfo blob = new BlobInfo(512*FileUtils.KB); + + //Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods + assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob))); + + final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash; + + HttpClient client = HttpClientBuilder.create().build(); + HttpGet request = new HttpGet(downloadURL); + + // add request header + request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME); + HttpResponse response = client.execute(request); + + assertEquals(200, response.getStatusLine().getStatusCode()); + + String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); + + String expectedContent = String.format("{%s:%s,%s:%d,%s:{%s:{%s:%s}}}", + "\"oid\"", "\"" + blob.hash + "\"", + "\"size\"", blob.length, + "\"actions\"", + "\"download\"", + "\"href\"", "\"" + downloadURL + "\""); + + assertEquals(expectedContent, content); + + + //Now try the binary download + request.removeHeaders(HttpHeaders.ACCEPT); + response = client.execute(request); + + assertEquals(200, response.getStatusLine().getStatusCode()); + + byte[] dlData = IOUtils.toByteArray(response.getEntity().getContent()); + + assertArrayEquals(blob.blob, dlData); + + } + + @Test + public void testDownloadMultiple() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = gitblit().getRepositoryModel(repoName); + + UserModel u = new UserModel("admin"); + u.canAdmin = true; + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE); + + final BlobInfo blob = new BlobInfo(512*FileUtils.KB); + + //Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods + assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob))); + + final String batchURL = GitBlitSuite.url + repoLfs + "batch"; + final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash; + + HttpClient client = HttpClientBuilder.create().build(); + HttpPost request = new HttpPost(batchURL); + + // add request header + request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME); + request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME); + + String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d},{%s:%s,%s:%d}]}", + "\"operation\"", "\"download\"", + "\"objects\"", + "\"oid\"", "\"" + blob.hash + "\"", + "\"size\"", blob.length, + "\"oid\"", "\"" + SHA256_EG + "\"", + "\"size\"", 0); + + HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8")); + request.setEntity(entity); + + HttpResponse response = client.execute(request); + + String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); + assertEquals(200, response.getStatusLine().getStatusCode()); + + String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}},{%s:%s,%s:%d,%s:{%s:%s,%s:%d}}]}", + "\"objects\"", + "\"oid\"", "\"" + blob.hash + "\"", + "\"size\"", blob.length, + "\"actions\"", + "\"download\"", + "\"href\"", "\"" + downloadURL + "\"", + "\"oid\"", "\"" + SHA256_EG + "\"", + "\"size\"", 0, + "\"error\"", + "\"message\"", "\"Object not available\"", + "\"code\"", 404 + ); + + assertEquals(expectedContent, responseMessage); + } + + @Test + public void testDownloadUnavailable() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE); + + final BlobInfo blob = new BlobInfo(512*FileUtils.KB); + + final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash; + + HttpClient client = HttpClientBuilder.create().build(); + HttpGet request = new HttpGet(downloadURL); + + // add request header + request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME); + HttpResponse response = client.execute(request); + + assertEquals(404, response.getStatusLine().getStatusCode()); + + String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); + + String expectedError = String.format("{%s:%s,%s:%d}", + "\"message\"", "\"Object not available\"", + "\"code\"", 404); + + assertEquals(expectedError, content); + } + + @Test + public void testUpload() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + RepositoryModel r = gitblit().getRepositoryModel(repoName); + + UserModel u = new UserModel("admin"); + u.canAdmin = true; + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE); + + final BlobInfo blob = new BlobInfo(512*FileUtils.KB); + + final String expectedUploadURL = GitBlitSuite.url + repoLfs + blob.hash; + final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch"; + + HttpClient client = HttpClientBuilder.create().build(); + HttpPost request = new HttpPost(initialUploadURL); + + // add request header + request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME); + request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME); + + String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d}]}", + "\"operation\"", "\"upload\"", + "\"objects\"", + "\"oid\"", "\"" + blob.hash + "\"", + "\"size\"", blob.length); + + HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8")); + request.setEntity(entity); + + HttpResponse response = client.execute(request); + String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); + assertEquals(200, response.getStatusLine().getStatusCode()); + + String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}}]}", + "\"objects\"", + "\"oid\"", "\"" + blob.hash + "\"", + "\"size\"", blob.length, + "\"actions\"", + "\"upload\"", + "\"href\"", "\"" + expectedUploadURL + "\""); + + assertEquals(expectedContent, responseMessage); + + + //Now try to upload the binary download + HttpPut putRequest = new HttpPut(expectedUploadURL); + putRequest.setEntity(new ByteArrayEntity(blob.blob)); + response = client.execute(putRequest); + + responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); + + assertEquals(200, response.getStatusLine().getStatusCode()); + + //Confirm behind the scenes that it is available + ByteArrayOutputStream savedBlob = new ByteArrayOutputStream(); + assertEquals(Status.Available, filestore().downloadBlob(blob.hash, u, r, savedBlob)); + assertArrayEquals(blob.blob, savedBlob.toByteArray()); + } + + @Test + public void testMalformedUpload() throws Exception { + + FileUtils.delete(filestore().getStorageFolder()); + filestore().clearFilestoreCache(); + + //No upload limit + settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE); + + final BlobInfo blob = new BlobInfo(512*FileUtils.KB); + + final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch"; + + HttpClient client = HttpClientBuilder.create().build(); + HttpPost request = new HttpPost(initialUploadURL); + + // add request header + request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME); + request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME); + + //Malformed JSON, comma instead of colon and unquoted strings + String content = String.format("{%s:%s,%s:[{%s:%s,%s,%d}]}", + "operation", "upload", + "objects", + "oid", blob.hash, + "size", blob.length); + + HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8")); + request.setEntity(entity); + + HttpResponse response = client.execute(request); + String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8"); + assertEquals(400, response.getStatusLine().getStatusCode()); + + String expectedError = String.format("{%s:%s,%s:%d}", + "\"message\"", "\"Malformed Git-LFS request\"", + "\"code\"", 400); + + assertEquals(expectedError, responseMessage); + } + +} diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java index 5a7dcea1..b01c82c4 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -63,9 +63,10 @@ import com.gitblit.utils.JGitUtils; GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
SshDaemonTest.class, GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,
FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
- ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class, + ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class,
- SshKeysDispatcherTest.class }) + SshKeysDispatcherTest.class, UITicketTest.class, PathUtilsTest.class, SshKerberosAuthenticationTest.class,
+ GravatarTest.class, FilestoreManagerTest.class, FilestoreServletTest.class })
public class GitBlitSuite {
public static final File BASEFOLDER = new File("data");
@@ -110,11 +111,11 @@ public class GitBlitSuite { return getRepository("test/gitective.git");
}
- public static Repository getTicketsTestRepository() { - JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close(); - return getRepository("gb-tickets.git"); - } - + public static Repository getTicketsTestRepository() {
+ JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close();
+ return getRepository("gb-tickets.git");
+ }
+
private static Repository getRepository(String name) {
try {
File gitDir = FileKey.resolve(new File(REPOSITORIES, name), FS.DETECTED);
diff --git a/src/test/java/com/gitblit/tests/GitBlitTest.java b/src/test/java/com/gitblit/tests/GitBlitTest.java index 9eaae7f8..e873ced2 100644 --- a/src/test/java/com/gitblit/tests/GitBlitTest.java +++ b/src/test/java/com/gitblit/tests/GitBlitTest.java @@ -176,7 +176,7 @@ public class GitBlitTest extends GitblitUnitTest { @Test
public void testAuthentication() throws Exception {
- assertTrue(authentication().authenticate("admin", "admin".toCharArray()) != null);
+ assertTrue(authentication().authenticate("admin", "admin".toCharArray(), null) != null);
}
@Test
diff --git a/src/test/java/com/gitblit/tests/GitblitUnitTest.java b/src/test/java/com/gitblit/tests/GitblitUnitTest.java index 9dceaaf4..58bc60e4 100644 --- a/src/test/java/com/gitblit/tests/GitblitUnitTest.java +++ b/src/test/java/com/gitblit/tests/GitblitUnitTest.java @@ -18,6 +18,7 @@ package com.gitblit.tests; import com.gitblit.IStoredSettings; import com.gitblit.manager.IAuthenticationManager; import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IFilestoreManager; import com.gitblit.manager.IGitblit; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IProjectManager; @@ -64,4 +65,8 @@ public class GitblitUnitTest extends org.junit.Assert { public static IGitblit gitblit() { return GitblitContext.getManager(IGitblit.class); } + + public static IFilestoreManager filestore() { + return GitblitContext.getManager(IFilestoreManager.class); + } } diff --git a/src/test/java/com/gitblit/tests/GravatarTest.java b/src/test/java/com/gitblit/tests/GravatarTest.java new file mode 100644 index 00000000..ba989d56 --- /dev/null +++ b/src/test/java/com/gitblit/tests/GravatarTest.java @@ -0,0 +1,74 @@ +package com.gitblit.tests; + +import org.junit.Test; + +import com.gitblit.AvatarGenerator; +import com.gitblit.GravatarGenerator; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.guice.AvatarGeneratorProvider; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.RuntimeManager; +import com.gitblit.tests.mock.MemorySettings; +import com.gitblit.utils.ActivityUtils; +import com.gitblit.utils.XssFilter; +import com.gitblit.utils.XssFilter.AllowXssFilter; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; + +public class GravatarTest extends GitblitUnitTest { + + public static class AvatarModule extends AbstractModule { + private final IStoredSettings settings; + + AvatarModule(IStoredSettings settings) { + this.settings = settings; + } + + @Override + protected void configure() { + bind(IStoredSettings.class).toInstance(settings); + bind(XssFilter.class).to(AllowXssFilter.class); + bind(IRuntimeManager.class).to(RuntimeManager.class); + bind(AvatarGenerator.class).toProvider(AvatarGeneratorProvider.class); + } + } + + @Test + public void gravatarIdenticonTest() { + IStoredSettings settings = new MemorySettings(); + settings.overrideSetting(Keys.web.avatarClass, GravatarGenerator.class.getName()); + + Injector injector = Guice.createInjector(new AvatarModule(settings)); + AvatarGenerator avatarGenerator = injector.getInstance(AvatarGenerator.class); + + String username = "username"; + String emailAddress = "emailAddress"; + int width = 10; + + String url = avatarGenerator.getURL(username, emailAddress, true, width); + assertNotNull(url); + + assertEquals(ActivityUtils.getGravatarIdenticonUrl(emailAddress, width), url); + } + + @Test + public void gravatarThumbnailTest() { + IStoredSettings settings = new MemorySettings(); + settings.overrideSetting(Keys.web.avatarClass, GravatarGenerator.class.getName()); + + Injector injector = Guice.createInjector(new AvatarModule(settings)); + AvatarGenerator avatarGenerator = injector.getInstance(AvatarGenerator.class); + + String username = "username"; + String emailAddress = "emailAddress"; + int width = 10; + + String url = avatarGenerator.getURL(username, emailAddress, false, width); + assertNotNull(url); + + assertEquals(ActivityUtils.getGravatarThumbnailUrl(emailAddress, width), url); + } + +} diff --git a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java index e2bb764e..26a49b24 100644 --- a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java +++ b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java @@ -200,43 +200,43 @@ public class HtpasswdAuthenticationTest extends GitblitUnitTest { public void testAuthenticationManager() { MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - UserModel user = auth.authenticate("user1", "pass1".toCharArray()); + UserModel user = auth.authenticate("user1", "pass1".toCharArray(), null); assertNotNull(user); assertEquals("user1", user.username); - user = auth.authenticate("user2", "pass2".toCharArray()); + user = auth.authenticate("user2", "pass2".toCharArray(), null); assertNotNull(user); assertEquals("user2", user.username); // Test different encryptions - user = auth.authenticate("plain", "passWord".toCharArray()); + user = auth.authenticate("plain", "passWord".toCharArray(), null); assertNotNull(user); assertEquals("plain", user.username); MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); - user = auth.authenticate("crypt", "password".toCharArray()); + user = auth.authenticate("crypt", "password".toCharArray(), null); assertNotNull(user); assertEquals("crypt", user.username); - user = auth.authenticate("md5", "password".toCharArray()); + user = auth.authenticate("md5", "password".toCharArray(), null); assertNotNull(user); assertEquals("md5", user.username); - user = auth.authenticate("sha", "password".toCharArray()); + user = auth.authenticate("sha", "password".toCharArray(), null); assertNotNull(user); assertEquals("sha", user.username); // Test leading and trailing whitespace - user = auth.authenticate("trailing", "whitespace".toCharArray()); + user = auth.authenticate("trailing", "whitespace".toCharArray(), null); assertNotNull(user); assertEquals("trailing", user.username); - user = auth.authenticate("tabbed", "frontAndBack".toCharArray()); + user = auth.authenticate("tabbed", "frontAndBack".toCharArray(), null); assertNotNull(user); assertEquals("tabbed", user.username); - user = auth.authenticate("leading", "whitespace".toCharArray()); + user = auth.authenticate("leading", "whitespace".toCharArray(), null); assertNotNull(user); assertEquals("leading", user.username); } @@ -323,55 +323,55 @@ public class HtpasswdAuthenticationTest extends GitblitUnitTest { { UserModel user = null; MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); - user = auth.authenticate("user1", "".toCharArray()); + user = auth.authenticate("user1", "".toCharArray(), null); assertNull("User 'user1' falsely authenticated.", user); - user = auth.authenticate("user1", "pass2".toCharArray()); + user = auth.authenticate("user1", "pass2".toCharArray(), null); assertNull("User 'user1' falsely authenticated.", user); - user = auth.authenticate("user2", "lalala".toCharArray()); + user = auth.authenticate("user2", "lalala".toCharArray(), null); assertNull("User 'user2' falsely authenticated.", user); - user = auth.authenticate("user3", "disabled".toCharArray()); + user = auth.authenticate("user3", "disabled".toCharArray(), null); assertNull("User 'user3' falsely authenticated.", user); - user = auth.authenticate("user4", "disabled".toCharArray()); + user = auth.authenticate("user4", "disabled".toCharArray(), null); assertNull("User 'user4' falsely authenticated.", user); - user = auth.authenticate("plain", "text".toCharArray()); + user = auth.authenticate("plain", "text".toCharArray(), null); assertNull("User 'plain' falsely authenticated.", user); - user = auth.authenticate("plain", "password".toCharArray()); + user = auth.authenticate("plain", "password".toCharArray(), null); assertNull("User 'plain' falsely authenticated.", user); MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); - user = auth.authenticate("crypt", "".toCharArray()); + user = auth.authenticate("crypt", "".toCharArray(), null); assertNull("User 'cyrpt' falsely authenticated.", user); - user = auth.authenticate("crypt", "passwd".toCharArray()); + user = auth.authenticate("crypt", "passwd".toCharArray(), null); assertNull("User 'crypt' falsely authenticated.", user); - user = auth.authenticate("md5", "".toCharArray()); + user = auth.authenticate("md5", "".toCharArray(), null); assertNull("User 'md5' falsely authenticated.", user); - user = auth.authenticate("md5", "pwd".toCharArray()); + user = auth.authenticate("md5", "pwd".toCharArray(), null); assertNull("User 'md5' falsely authenticated.", user); - user = auth.authenticate("sha", "".toCharArray()); + user = auth.authenticate("sha", "".toCharArray(), null); assertNull("User 'sha' falsely authenticated.", user); - user = auth.authenticate("sha", "letmein".toCharArray()); + user = auth.authenticate("sha", "letmein".toCharArray(), null); assertNull("User 'sha' falsely authenticated.", user); - user = auth.authenticate(" tabbed", "frontAndBack".toCharArray()); + user = auth.authenticate(" tabbed", "frontAndBack".toCharArray(), null); assertNull("User 'tabbed' falsely authenticated.", user); - user = auth.authenticate(" leading", "whitespace".toCharArray()); + user = auth.authenticate(" leading", "whitespace".toCharArray(), null); assertNull("User 'leading' falsely authenticated.", user); } diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java index c2cfb331..2cf4a5a1 100644 --- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java +++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java @@ -476,6 +476,15 @@ public class JGitUtilsTest extends GitblitUnitTest { }
@Test
+ public void testFilesInPath2() throws Exception {
+ assertEquals(0, JGitUtils.getFilesInPath2(null, null, null).size());
+ Repository repository = GitBlitSuite.getHelloworldRepository();
+ List<PathModel> files = JGitUtils.getFilesInPath2(repository, null, null);
+ repository.close();
+ assertTrue(files.size() > 10);
+ }
+
+ @Test
public void testDocuments() throws Exception {
Repository repository = GitBlitSuite.getTicgitRepository();
List<String> extensions = Arrays.asList(new String[] { ".mkd", ".md" });
diff --git a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java index 7c84ecc2..84dd138d 100644 --- a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java +++ b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java @@ -240,23 +240,23 @@ public class LdapAuthenticationTest extends GitblitUnitTest { @Test public void testAuthenticationManager() { - UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray()); + UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null); assertNotNull(userOneModel); assertNotNull(userOneModel.getTeam("git_admins")); assertNotNull(userOneModel.getTeam("git_users")); assertTrue(userOneModel.canAdmin); - UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray()); + UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null); assertNull(userOneModelFailedAuth); - UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray()); + UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray(), null); assertNotNull(userTwoModel); assertNotNull(userTwoModel.getTeam("git_users")); assertNull(userTwoModel.getTeam("git_admins")); assertNotNull(userTwoModel.getTeam("git admins")); assertTrue(userTwoModel.canAdmin); - UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray()); + UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray(), null); assertNotNull(userThreeModel); assertNotNull(userThreeModel.getTeam("git_users")); assertNull(userThreeModel.getTeam("git_admins")); @@ -269,10 +269,10 @@ public class LdapAuthenticationTest extends GitblitUnitTest { settings.put(Keys.realm.ldap.username, ""); settings.put(Keys.realm.ldap.password, ""); - UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray()); + UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null); assertNotNull(userOneModel); - UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray()); + UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null); assertNull(userOneModelFailedAuth); } diff --git a/src/test/java/com/gitblit/tests/PathUtilsTest.java b/src/test/java/com/gitblit/tests/PathUtilsTest.java new file mode 100644 index 00000000..209b8ee6 --- /dev/null +++ b/src/test/java/com/gitblit/tests/PathUtilsTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.tests; + +import com.gitblit.utils.PathUtils; +import org.junit.Test; + +import java.util.Arrays; + +public class PathUtilsTest extends GitblitUnitTest { + + private static final String[][][] testData = { + + { + // Folder contents + {".gitignore","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"}, + // Expected after compressing + {".gitignore", "src/main/java/", "docs/"} + }, + + { + {".gitignore","src/main/java/a.java", "src/main/b.java", "docs/c.md"}, + {".gitignore", "src/main/", "docs/"} + }, + + { + {".gitignore","src/x.java","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"}, + {".gitignore", "src/", "docs/"} + }, + }; + + + + + @Test + public void testCompressPaths() throws Exception { + + for (String[][] test : testData ) { + assertArrayEquals(test[1], PathUtils.compressPaths(Arrays.asList(test[0])).toArray(new String[]{})); + } + + } + + @Test + public void testGetLastPathComponent() { + assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/e.out"), "e.out"); + assertEquals(PathUtils.getLastPathComponent("e.out"), "e.out"); + assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/"), "d"); + assertEquals(PathUtils.getLastPathComponent("/a/b/c/d"), "d"); + assertEquals(PathUtils.getLastPathComponent("/"), "/"); + } + +}
\ No newline at end of file diff --git a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java index ad773b7a..7136fa79 100644 --- a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java +++ b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java @@ -65,7 +65,7 @@ public class RedmineAuthenticationTest extends GitblitUnitTest { @Test public void testAuthenticationManager() throws Exception { AuthenticationManager auth = newAuthenticationManager(); - UserModel userModel = auth.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray()); + UserModel userModel = auth.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray(), null); assertThat(userModel.getName(), is("redmineadminid")); assertThat(userModel.getDisplayName(), is("baz foo")); assertThat(userModel.emailAddress, is("baz@example.com")); diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java index dcaeaff8..c5deb7d5 100644 --- a/src/test/java/com/gitblit/tests/SshDaemonTest.java +++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java @@ -19,8 +19,8 @@ import java.io.File; import java.text.MessageFormat; import java.util.List; -import org.apache.sshd.ClientSession; -import org.apache.sshd.SshClient; +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.session.ClientSession; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.revwalk.RevCommit; diff --git a/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java b/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java new file mode 100644 index 00000000..4ba16b67 --- /dev/null +++ b/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.tests; + +import org.apache.sshd.server.auth.gss.GSSAuthenticator; +import org.apache.sshd.server.session.ServerSession; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.gitblit.manager.AuthenticationManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.models.UserModel; +import com.gitblit.tests.mock.MemorySettings; +import com.gitblit.transport.ssh.SshDaemonClient; +import com.gitblit.transport.ssh.SshKrbAuthenticator; + +public class SshKerberosAuthenticationTest extends GitblitUnitTest { + + private static class UserModelWrapper { + public UserModel um; + } + + @Test + public void testUserManager() { + IRuntimeManager rm = Mockito.mock(IRuntimeManager.class); + + //Build an UserManager that can build a UserModel + IUserManager im = Mockito.mock(IUserManager.class); + Mockito.doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + String user = (String) args[0]; + return new UserModel(user); + } + }).when(im).getUserModel(Mockito.anyString()); + + AuthenticationManager am = new AuthenticationManager(rm, im); + + GSSAuthenticator gssAuthenticator = new SshKrbAuthenticator(new MemorySettings(), am); + + ServerSession session = Mockito.mock(ServerSession.class); + + //Build an SshDaemonClient that can set and get the UserModel + final UserModelWrapper umw = new UserModelWrapper(); + SshDaemonClient client = Mockito.mock(SshDaemonClient.class); + Mockito.when(client.getUser()).thenReturn(umw.um); + Mockito.doAnswer(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + UserModel um = (UserModel) args[0]; + umw.um = um; + return null; + } + }).when(client).setUser(Mockito.any(UserModel.class)); + + Mockito.when(session.getAttribute(SshDaemonClient.KEY)).thenReturn(client); + Assert.assertTrue(gssAuthenticator.validateIdentity(session, "jhappy")); + + } +} diff --git a/src/test/java/com/gitblit/tests/SshUnitTest.java b/src/test/java/com/gitblit/tests/SshUnitTest.java index 43b51b74..27b4ec73 100644 --- a/src/test/java/com/gitblit/tests/SshUnitTest.java +++ b/src/test/java/com/gitblit/tests/SshUnitTest.java @@ -26,10 +26,10 @@ import java.security.KeyPairGenerator; import java.security.PublicKey; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.sshd.ClientChannel; -import org.apache.sshd.ClientSession; -import org.apache.sshd.SshClient; import org.apache.sshd.client.ServerKeyVerifier; +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.channel.ClientChannel; +import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.util.SecurityUtils; import org.junit.After; import org.junit.AfterClass; diff --git a/src/test/java/com/gitblit/tests/StringUtilsTest.java b/src/test/java/com/gitblit/tests/StringUtilsTest.java index 0fd42aad..7176b88c 100644 --- a/src/test/java/com/gitblit/tests/StringUtilsTest.java +++ b/src/test/java/com/gitblit/tests/StringUtilsTest.java @@ -50,7 +50,7 @@ public class StringUtilsTest extends GitblitUnitTest { public void testEscapeForHtml() throws Exception {
String input = "& < > \" \t";
String outputNoChange = "& < > " \t";
- String outputChange = "& < > " ";
+ String outputChange = "& < > " ";
assertEquals(outputNoChange, StringUtils.escapeForHtml(input, false));
assertEquals(outputChange, StringUtils.escapeForHtml(input, true));
}
diff --git a/src/test/java/com/gitblit/tests/TicketServiceTest.java b/src/test/java/com/gitblit/tests/TicketServiceTest.java index 1676e341..c654383d 100644 --- a/src/test/java/com/gitblit/tests/TicketServiceTest.java +++ b/src/test/java/com/gitblit/tests/TicketServiceTest.java @@ -293,9 +293,47 @@ public abstract class TicketServiceTest extends GitblitUnitTest { assertTrue("failed to delete label " + label.name, service.deleteLabel(getRepository(), label.name, "lucifer"));
}
}
-
-
-
+
+ @Test
+ public void testPriorityAndSeverity() throws Exception {
+ // C1: create and insert a ticket
+ Change c1 = newChange("testPriorityAndSeverity() " + Long.toHexString(System.currentTimeMillis()));
+ TicketModel ticket = service.createTicket(getRepository(), c1);
+ assertTrue(ticket.number > 0);
+ assertEquals(TicketModel.Priority.Normal, ticket.priority);
+ assertEquals(TicketModel.Severity.Unrated, ticket.severity);
+
+ TicketModel constructed = service.getTicket(getRepository(), ticket.number);
+ compare(ticket, constructed);
+
+ // C2: Change Priority max
+ Change c2 = new Change("C2");
+ c2.setField(Field.priority, TicketModel.Priority.Urgent);
+ constructed = service.updateTicket(getRepository(), ticket.number, c2);
+ assertNotNull(constructed);
+ assertEquals(2, constructed.changes.size());
+ assertEquals(TicketModel.Priority.Urgent, constructed.priority);
+ assertEquals(TicketModel.Severity.Unrated, constructed.severity);
+
+ // C3: Change Severity max
+ Change c3 = new Change("C3");
+ c3.setField(Field.severity, TicketModel.Severity.Catastrophic);
+ constructed = service.updateTicket(getRepository(), ticket.number, c3);
+ assertNotNull(constructed);
+ assertEquals(3, constructed.changes.size());
+ assertEquals(TicketModel.Priority.Urgent, constructed.priority);
+ assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
+
+ // C4: Change Priority min
+ Change c4 = new Change("C3");
+ c4.setField(Field.priority, TicketModel.Priority.Low);
+ constructed = service.updateTicket(getRepository(), ticket.number, c4);
+ assertNotNull(constructed);
+ assertEquals(4, constructed.changes.size());
+ assertEquals(TicketModel.Priority.Low, constructed.priority);
+ assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
+ }
+
private Change newChange(String summary) {
Change change = new Change("C1");
change.setField(Field.title, summary);
diff --git a/src/test/java/com/gitblit/tests/UITicketTest.java b/src/test/java/com/gitblit/tests/UITicketTest.java new file mode 100644 index 00000000..54aa1e1e --- /dev/null +++ b/src/test/java/com/gitblit/tests/UITicketTest.java @@ -0,0 +1,151 @@ +/* + * Copyright 2014 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.tests; + +import java.io.File; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.bouncycastle.util.Arrays; +import org.eclipse.jgit.lib.Repository; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.INotificationManager; +import com.gitblit.manager.IPluginManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.manager.NotificationManager; +import com.gitblit.manager.PluginManager; +import com.gitblit.manager.RepositoryManager; +import com.gitblit.manager.RuntimeManager; +import com.gitblit.manager.UserManager; +import com.gitblit.models.Mailing; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.TicketModel; +import com.gitblit.models.TicketModel.Attachment; +import com.gitblit.models.TicketModel.Change; +import com.gitblit.models.TicketModel.Field; +import com.gitblit.models.TicketModel.Patchset; +import com.gitblit.models.TicketModel.Priority; +import com.gitblit.models.TicketModel.Severity; +import com.gitblit.models.TicketModel.Status; +import com.gitblit.models.TicketModel.Type; +import com.gitblit.tests.mock.MemorySettings; +import com.gitblit.tickets.ITicketService; +import com.gitblit.tickets.ITicketService.TicketFilter; +import com.gitblit.tickets.QueryResult; +import com.gitblit.tickets.TicketIndexer.Lucene; +import com.gitblit.tickets.BranchTicketService; +import com.gitblit.tickets.TicketLabel; +import com.gitblit.tickets.TicketMilestone; +import com.gitblit.tickets.TicketNotifier; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.XssFilter; +import com.gitblit.utils.XssFilter.AllowXssFilter; + +/** + * Generates the range of tickets to ease testing of the look and feel of tickets + */ +public class UITicketTest extends GitblitUnitTest { + + private ITicketService service; + final String repoName = "UITicketTest.git"; + final RepositoryModel repo = new RepositoryModel(repoName, null, null, null); + + protected ITicketService getService(boolean deleteAll) throws Exception { + + IStoredSettings settings = getSettings(deleteAll); + XssFilter xssFilter = new AllowXssFilter(); + IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start(); + IPluginManager pluginManager = new PluginManager(runtimeManager).start(); + INotificationManager notificationManager = new NotificationManager(settings).start(); + IUserManager userManager = new UserManager(runtimeManager, pluginManager).start(); + IRepositoryManager repositoryManager = new RepositoryManager(runtimeManager, pluginManager, userManager).start(); + + BranchTicketService service = new BranchTicketService( + runtimeManager, + pluginManager, + notificationManager, + userManager, + repositoryManager).start(); + + if (deleteAll) { + service.deleteAll(repo); + } + return service; + } + + protected IStoredSettings getSettings(boolean deleteAll) throws Exception { + File dir = new File(GitBlitSuite.REPOSITORIES, repoName); + if (deleteAll) { + FileUtils.deleteDirectory(dir); + JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repoName).close(); + } + + File luceneDir = new File(dir, "tickets/lucene"); + luceneDir.mkdirs(); + + Map<String, Object> map = new HashMap<String, Object>(); + map.put(Keys.git.repositoriesFolder, GitBlitSuite.REPOSITORIES.getAbsolutePath()); + map.put(Keys.tickets.indexFolder, luceneDir.getAbsolutePath()); + + IStoredSettings settings = new MemorySettings(map); + return settings; + } + + @Before + public void setup() throws Exception { + service = getService(true); + } + + @After + public void cleanup() { + service.stop(); + } + + @Test + public void UITicketOptions() throws Exception { + + for (TicketModel.Type t : TicketModel.Type.values()) + { + for (TicketModel.Priority p : TicketModel.Priority.values()) + { + for (TicketModel.Severity s : TicketModel.Severity.values()) + { + assertNotNull(service.createTicket(repo, newChange(t, p, s))); + } + } + } + } + + private Change newChange(Type type, Priority priority, Severity severity) { + Change change = new Change("JUnit"); + change.setField(Field.title, String.format("Type: %s | Priority: %s | Severity: %s", type, priority, severity)); + change.setField(Field.type, type); + change.setField(Field.severity, severity); + change.setField(Field.priority, priority); + return change; + } + +}
\ No newline at end of file diff --git a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java index 7b563622..8897ef7e 100644 --- a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java +++ b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java @@ -30,6 +30,7 @@ import com.gitblit.models.ServerStatus; import com.gitblit.models.SettingModel; import com.gitblit.utils.XssFilter; import com.gitblit.utils.XssFilter.AllowXssFilter; +import com.google.inject.Injector; public class MockRuntimeManager implements IRuntimeManager { @@ -59,6 +60,11 @@ public class MockRuntimeManager implements IRuntimeManager { } @Override + public Injector getInjector() { + return null; + } + + @Override public void setBaseFolder(File folder) { this.baseFolder = folder; } @@ -79,26 +85,6 @@ public class MockRuntimeManager implements IRuntimeManager { } @Override - public boolean isServingRepositories() { - return true; - } - - @Override - public boolean isServingHTTP() { - return true; - } - - @Override - public boolean isServingGIT() { - return true; - } - - @Override - public boolean isServingSSH() { - return true; - } - - @Override public boolean isDebugMode() { return true; } |