.checkstyle
@@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false"> <local-check-config name="Gitblit" location="checkstyle.xml" type="project" description=""> <local-check-config name="Gitblit" location="src/main/config/checkstyle.xml" type="project" description=""> <additional-data name="protect-config-file" value="false"/> </local-check-config> <fileset name="all" enabled="true" check-config-name="Gitblit" local="true"> .classpath
@@ -1,55 +1,65 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src" /> <classpathentry kind="src" path="resources" /> <classpathentry kind="src" path="tests" output="bin/test-classes" /> <classpathentry kind="lib" path="ext/jcommander-1.17.jar" sourcepath="ext/src/jcommander-1.17-sources.jar" /> <classpathentry kind="lib" path="ext/log4j-1.2.17.jar" sourcepath="ext/src/log4j-1.2.17-sources.jar" /> <classpathentry kind="lib" path="ext/slf4j-api-1.6.6.jar" sourcepath="ext/src/slf4j-api-1.6.6-sources.jar" /> <classpathentry kind="lib" path="ext/slf4j-log4j12-1.6.6.jar" sourcepath="ext/src/slf4j-log4j12-1.6.6-sources.jar" /> <classpathentry kind="lib" path="ext/mail-1.4.3.jar" sourcepath="ext/src/mail-1.4.3-sources.jar" /> <classpathentry kind="lib" path="ext/javax.servlet-api-3.0.1.jar" sourcepath="ext/src/javax.servlet-api-3.0.1-sources.jar" /> <classpathentry kind="lib" path="ext/jetty-webapp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-webapp-7.6.8.v20121106-sources.jar" /> <classpathentry kind="lib" path="ext/jetty-ajp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-ajp-7.6.8.v20121106-sources.jar" /> <classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21-sources.jar" /> <classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21-sources.jar" /> <classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21-sources.jar" /> <classpathentry kind="lib" path="ext/googlecharts-1.4.21.jar" sourcepath="ext/src/googlecharts-1.4.21-sources.jar" /> <classpathentry kind="lib" path="ext/lucene-core-3.6.1.jar" sourcepath="ext/src/lucene-core-3.6.1-sources.jar" /> <classpathentry kind="lib" path="ext/lucene-highlighter-3.6.1.jar" sourcepath="ext/src/lucene-highlighter-3.6.1-sources.jar" /> <classpathentry kind="lib" path="ext/lucene-memory-3.6.1.jar" sourcepath="ext/src/lucene-memory-3.6.1-sources.jar" /> <classpathentry kind="lib" path="ext/lucene-queries-3.6.1.jar" sourcepath="ext/src/lucene-queries-3.6.1-sources.jar" /> <classpathentry kind="src" path="src/main/java" /> <classpathentry kind="src" path="src/test/java" output="bin/test-classes" /> <classpathentry kind="src" path="src/main/resources" /> <classpathentry kind="lib" path="ext/jcommander-1.17.jar" sourcepath="ext/src/jcommander-1.17.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.6.6.jar" sourcepath="ext/src/slf4j-api-1.6.6.jar" /> <classpathentry kind="lib" path="ext/slf4j-log4j12-1.6.6.jar" sourcepath="ext/src/slf4j-log4j12-1.6.6.jar" /> <classpathentry kind="lib" path="ext/mail-1.4.3.jar" sourcepath="ext/src/mail-1.4.3.jar" /> <classpathentry kind="lib" path="ext/javax.servlet-api-3.0.1.jar" sourcepath="ext/src/javax.servlet-api-3.0.1.jar" /> <classpathentry kind="lib" path="ext/jetty-webapp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-webapp-7.6.8.v20121106.jar" /> <classpathentry kind="lib" path="ext/jetty-ajp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-ajp-7.6.8.v20121106.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/googlecharts-1.4.21.jar" sourcepath="ext/src/googlecharts-1.4.21.jar" /> <classpathentry kind="lib" path="ext/lucene-core-3.6.1.jar" sourcepath="ext/src/lucene-core-3.6.1.jar" /> <classpathentry kind="lib" path="ext/lucene-highlighter-3.6.1.jar" sourcepath="ext/src/lucene-highlighter-3.6.1.jar" /> <classpathentry kind="lib" path="ext/lucene-memory-3.6.1.jar" sourcepath="ext/src/lucene-memory-3.6.1.jar" /> <classpathentry kind="lib" path="ext/lucene-queries-3.6.1.jar" sourcepath="ext/src/lucene-queries-3.6.1.jar" /> <classpathentry kind="lib" path="ext/jakarta-regexp-1.4.jar" /> <classpathentry kind="lib" path="ext/markdownpapers-core-1.3.2.jar" sourcepath="ext/src/markdownpapers-core-1.3.2-sources.jar" /> <classpathentry kind="lib" path="ext/org.eclipse.jgit-2.2.0.201212191850-r.jar" sourcepath="ext/src/org.eclipse.jgit-2.2.0.201212191850-r-sources.jar" /> <classpathentry kind="lib" path="ext/jsch-0.1.44-1.jar" sourcepath="ext/src/jsch-0.1.44-1-sources.jar" /> <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-2.2.0.201212191850-r-sources.jar" /> <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.47.jar" sourcepath="ext/src/bcprov-jdk15on-1.47-sources.jar" /> <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.47.jar" sourcepath="ext/src/bcmail-jdk15on-1.47-sources.jar" /> <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.47.jar" sourcepath="ext/src/bcpkix-jdk15on-1.47-sources.jar" /> <classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/src/rome-0.9-sources.jar" /> <classpathentry kind="lib" path="ext/jdom-1.0.jar" sourcepath="ext/src/jdom-1.0-sources.jar" /> <classpathentry kind="lib" path="ext/gson-1.7.2.jar" sourcepath="ext/src/gson-1.7.2-sources.jar" /> <classpathentry kind="lib" path="ext/groovy-all-1.8.8.jar" sourcepath="ext/src/groovy-all-1.8.8-sources.jar" /> <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0-sources.jar" /> <classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0-sources.jar" /> <classpathentry kind="lib" path="ext/markdownpapers-core-1.3.2.jar" sourcepath="ext/src/markdownpapers-core-1.3.2.jar" /> <classpathentry kind="lib" path="ext/org.eclipse.jgit-3.0.0.201306101825-r.jar" sourcepath="ext/src/org.eclipse.jgit-3.0.0.201306101825-r.jar" /> <classpathentry kind="lib" path="ext/jsch-0.1.46.jar" sourcepath="ext/src/jsch-0.1.46.jar" /> <classpathentry kind="lib" path="ext/JavaEWAH-0.5.6.jar" sourcepath="ext/src/JavaEWAH-0.5.6.jar" /> <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar" /> <classpathentry kind="lib" path="ext/bcprov-jdk15on-1.47.jar" sourcepath="ext/src/bcprov-jdk15on-1.47.jar" /> <classpathentry kind="lib" path="ext/bcmail-jdk15on-1.47.jar" sourcepath="ext/src/bcmail-jdk15on-1.47.jar" /> <classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.47.jar" sourcepath="ext/src/bcpkix-jdk15on-1.47.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/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-sources.jar" /> <classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0-sources.jar" /> <classpathentry kind="lib" path="ext/junit-4.10.jar" sourcepath="ext/src/junit-4.10-sources.jar" /> <classpathentry kind="lib" path="ext/hamcrest-core-1.1.jar" /> <classpathentry kind="lib" path="ext/seleniumhq/selenium-java-2.28.0.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/selenium-api-2.28.0.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/selenium-remote-driver-2.28.0.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/selenium-support-2.28.0.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/guava-12.0.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/json-20080701.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/commons-exec-1.1.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/httpcore-4.2.1.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/httpmime-4.2.1.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/httpclient-4.2.1.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/commons-logging-1.1.1.jar"/> <classpathentry kind="lib" path="ext/seleniumhq/selenium-firefox-driver-2.28.0.jar"/> <classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" /> <classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0.jar" /> <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/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" /> <classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" /> <classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" /> <classpathentry kind="lib" path="ext/selenium-support-2.28.0.jar" sourcepath="ext/src/selenium-support-2.28.0.jar" /> <classpathentry kind="lib" path="ext/selenium-firefox-driver-2.28.0.jar" sourcepath="ext/src/selenium-firefox-driver-2.28.0.jar" /> <classpathentry kind="lib" path="ext/selenium-remote-driver-2.28.0.jar" sourcepath="ext/src/selenium-remote-driver-2.28.0.jar" /> <classpathentry kind="lib" path="ext/cglib-nodep-2.1_3.jar" sourcepath="ext/src/cglib-nodep-2.1_3.jar" /> <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/httpclient-4.2.1.jar" sourcepath="ext/src/httpclient-4.2.1.jar" /> <classpathentry kind="lib" path="ext/httpcore-4.2.1.jar" sourcepath="ext/src/httpcore-4.2.1.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/commons-codec-1.6.jar" sourcepath="ext/src/commons-codec-1.6.jar" /> <classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" /> <classpathentry kind="lib" path="ext/commons-io-2.2.jar" sourcepath="ext/src/commons-io-2.2.jar" /> <classpathentry kind="output" path="bin/classes" /> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> </classpath> .gitignore
@@ -1,35 +1,24 @@ /temp /lib /ext /build /keystore /*.zip /gitblit.properties /users.properties /site /git /target /build.properties /war /*.war /proposals /*.jar /federation.properties /mailtest.properties /.settings/*.prefs /src/WEB-INF/reference.properties /bin/ /.settings/ /javadoc /express /build-demo.xml /users.conf *.directory /.gradle /projects.conf /pom.xml /deploy /*.jks /x509test /certs /temp /lib /ext /build /site /git /build.properties /federation.properties /mailtest.properties /test-users.conf /.settings/ /src/main/java/reference.properties /src/main/java/WEB-INF/reference.properties /bin/ /build-demo.xml *.directory /.gradle /pom.xml /x509test /data /*.conf /build-next.xml /*.ps1 /*.sh NOTICE
@@ -255,3 +255,51 @@ http://tukaani.org/xz/java.html --------------------------------------------------------------------------- Iconic --------------------------------------------------------------------------- Iconic, release under the Creative Commons Share Alike 3.0 License. http://somerandomdude.com/work/iconic --------------------------------------------------------------------------- AngularJS --------------------------------------------------------------------------- AngularJS, release under the MIT License. http://angularjs.org/ --------------------------------------------------------------------------- FreeMarker --------------------------------------------------------------------------- FreeMarker, release under a modified BSD License. (http://www.freemarker.org/docs/app_license.html) http://www.freemarker.org/ --------------------------------------------------------------------------- Waffle --------------------------------------------------------------------------- Waffle, release under the Eclipse Public License, version 1.0 http://dblock.github.io/waffle --------------------------------------------------------------------------- JNA --------------------------------------------------------------------------- JNA, release under the Lesser GNU Public License, version 2.1 https://github.com/twall/jna --------------------------------------------------------------------------- Guava --------------------------------------------------------------------------- Guava, release under the Apache License 2.0. https://code.google.com/p/guava-libraries README.markdown
@@ -32,4 +32,9 @@ 3. Select your gitblit project root and **Refresh** the project, this should correct all build problems. 4. Using JUnit, execute the `com.gitblit.tests.GitBlitSuite` test suite.<br/> *This will clone some repositories from the web and run through the unit tests.* 5. Execute the *com.gitblit.Launcher* class to start Gitblit. 5. Execute the *com.gitblit.GitBlitServer* class to start Gitblit GO. Building Tips & Tricks ---------------------- 1. If you are running Ant from an ANSI-capable console, consider setting the `MX_COLOR` ennvironment variable before executing Ant.<pre>set MX_COLOR=true</pre> 2. The build script will honor your Maven proxy settings. If you need to fine-tune this, please review the [settings.moxie](http://gitblit.github.io/moxie/settings.html) documentation. build.moxie
New file @@ -0,0 +1,160 @@ # # Gitblit project descriptor # # Specify minimum Moxie version required to build requires: 0.7.4 # Project Metadata name: Gitblit description: pure Java Git solution groupId: com.gitblit artifactId: gitblit version: 1.3.1-SNAPSHOT inceptionYear: 2011 # Current stable release releaseVersion: 1.3.0 releaseDate: 2013-07-14 # Project urls url: 'http://gitblit.com' issuesUrl: 'http://code.google.com/p/gitblit/issues/list' socialNetworkUrl: 'https://plus.google.com/114464678392593421684' forumUrl: 'http://groups.google.com/group/gitblit' # Licenses section included for POM generation licenses: - { name: Apache ASL v2.0 url: 'http://www.apache.org/licenses/LICENSE-2.0.html' } # Developers section included for POM generation developers: - { id: james name: James Moger url: 'https://plus.google.com/u/0/116428776452027956920' organization: VAS organizationUrl: 'http://www.vas.com' roles: developer } # SCM section included for POM generation scm: { connection: 'scm:git:git://github.com/gitblit/gitblit.git' developerConnection: 'scm:git:https://github.com/gitblit/gitblit.git' url: 'https://github.com/gitblit/gitblit' tag: HEAD } # Mainclass is used for setting jar manifests and using the mx:run target mainclass: com.gitblit.GitBlitServer # Moxie supports multiple source directories and allows you to assign # a scope to each directory. sourceDirectories: - compile 'src/main/java' - test 'src/test/java' # Moxie supports one site-scoped directory for mx:doc - site 'src/site' resourceDirectories: - compile 'src/main/resources' - site 'src/site/resources' # compile for Java 6 class format tasks: { 'mx:javac' : { source: 1.6 target: 1.6 compiler: javac1.6 encoding: UTF-8 # stop complaints about bootstrap classpath when compiling with Java 7 compilerArgs: '-Xlint:-options' } } # Generate Eclipse project files. # Generate IntelliJ IDEA module files. # Generate a distribution Maven POM (not suitable for building with Maven). apply: eclipse, intellij, pom # Copy all retrieved dependencies to the "ext" directory. # Generated IDE settings (.classpath, etc) will use the artifacts # from this project-relative directory. This allows the IDE settings # to be version-controlled and shared. dependencyDirectory: ext # Register the Eclipse JGit Maven repositories registeredRepositories: - { id: jgit, url: 'http://download.eclipse.org/jgit/maven' } - { id: jgit-snapshots, url: 'https://repo.eclipse.org/content/groups/snapshots' } # Source all dependencies from the following repositories in the specified order repositories: central, jgit-snapshots, jgit # Convenience properties for dependencies properties: { jetty.version : 7.6.8.v20121106 wicket.version : 1.4.21 lucene.version : 3.6.1 jgit.version : 3.0.0.201306101825-r groovy.version : 1.8.8 bouncycastle.version : 1.47 selenium.version : 2.28.0 } # Dependencies # # May be tagged with ":label" notation to group dependencies. # # "@extension" fetches the artifact with the specified extension # and ignores all transitive dependencies. # # "!groupId" or "!groupId:artifactId" excludes all matching transitive # dependencies in that dependency's dependency graph. # dependencies: # Standard dependencies - compile 'com.beust:jcommander:1.17' :fedclient :authority - compile 'log4j:log4j:1.2.17' :war :fedclient :authority - compile 'org.slf4j:slf4j-api:1.6.6' :war :fedclient :authority - compile 'org.slf4j:slf4j-log4j12:1.6.6' :war :fedclient :authority - compile 'javax.mail:mail:1.4.3' :war :fedclient :authority - compile 'javax.servlet:javax.servlet-api:3.0.1' :fedclient - compile 'org.eclipse.jetty.aggregate:jetty-webapp:${jetty.version}' @jar - compile 'org.eclipse.jetty:jetty-ajp:${jetty.version}' @jar - compile 'org.apache.wicket:wicket:${wicket.version}' :war !org.mockito - compile 'org.apache.wicket:wicket-auth-roles:${wicket.version}' :war !org.mockito - compile 'org.apache.wicket:wicket-extensions:${wicket.version}' :war !org.mockito - compile 'org.wicketstuff:googlecharts:${wicket.version}' :war - compile 'org.apache.lucene:lucene-core:${lucene.version}' :war :fedclient - compile 'org.apache.lucene:lucene-highlighter:${lucene.version}' :war :fedclient - compile 'org.apache.lucene:lucene-memory:${lucene.version}' :war :fedclient - compile 'org.tautua.markdownpapers:markdownpapers-core:1.3.2' :war - compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager :authority - compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :fedclient :manager :authority - 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 'rome:rome:0.9' :war :manager :api - compile 'com.google.code.gson:gson:1.7.2' :war :fedclient :manager :api - compile 'org.codehaus.groovy:groovy-all:${groovy.version}' :war - compile 'com.unboundid:unboundid-ldapsdk:2.3.0' :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 '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 - test 'junit' # Dependencies for Selenium web page testing - 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}' # Dependencies with the "build" scope are retrieved # and injected into the Ant runtime classpath - build 'jacoco' build.xml
@@ -1,117 +1,68 @@ <?xml version="1.0" encoding="UTF-8"?> <project name="gitblit" default="compile" basedir="."> <!-- Google Code upload task --> <taskdef classname="net.bluecow.googlecode.ant.GoogleCodeUploadTask" classpath="${basedir}/tools/ant-googlecode-0.0.3.jar" name="gcupload"/> <!-- GenJar task --> <taskdef resource="genjar.properties" classpath="${basedir}/tools/GenJar.jar" /> <!-- Project Properties --> <property name="project.jar" value="gitblit.jar" /> <property name="project.mainclass" value="com.gitblit.Launcher" /> <property name="project.build.dir" value="${basedir}/build" /> <property name="project.deploy.dir" value="${basedir}/deploy" /> <property name="project.war.dir" value="${basedir}/war" /> <property name="project.jar.dir" value="${basedir}/jar" /> <property name="project.site.dir" value="${basedir}/target/site" /> <property name="project.target.dir" value="${basedir}/target" /> <property name="project.resources.dir" value="${basedir}/resources" /> <property name="project.express.dir" value="${basedir}/express" /> <property name="project.maven.repo.url" value="enter here your Maven repo URL" /> <property name="project.maven.repo.id" value="gitblit.maven.repo" /> <available property="hasBuildProps" file="${basedir}/build.properties"/> <project name="gitblit" default="compile" xmlns:mx="antlib:org.moxie"> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Load build.properties, if available ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildprops" if="hasBuildProps"> <!-- Load publication servers, paths, and credentials --> <loadproperties> <file file="${basedir}/build.properties" /> </loadproperties> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Scrape the version info from code and setup the build properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildinfo" depends="buildprops"> <!-- extract Gitblit version number from source code --> <loadfile property="gb.version" srcfile="${basedir}/src/com/gitblit/Constants.java"> <filterchain> <linecontains> <contains value="public static final String VERSION = " /> </linecontains> <striplinebreaks /> <tokenfilter> <replacestring from="public static final String VERSION = "" to="" /> <replacestring from="";" to="" /> <trim /> </tokenfilter> </filterchain> </loadfile> <!-- extract Gitblit version date from source code --> <loadfile property="gb.versionDate" srcfile="${basedir}/src/com/gitblit/Constants.java"> <filterchain> <linecontains> <contains value="public static final String VERSION_DATE = " /> </linecontains> <striplinebreaks /> <tokenfilter> <replacestring from="public static final String VERSION_DATE = "" to="" /> <replacestring from="";" to="" /> <trim /> </tokenfilter> </filterchain> </loadfile> <!-- extract JGit version number from source code --> <loadfile property="jgit.version" srcfile="${basedir}/src/com/gitblit/Constants.java"> <filterchain> <linecontains> <contains value="public static final String JGIT_VERSION = " /> </linecontains> <striplinebreaks /> <tokenfilter> <replacestring from="public static final String JGIT_VERSION = "" to="" /> <replacestring from="";" to="" /> <trim /> </tokenfilter> </filterchain> </loadfile> <property name="distribution.zipfile" value="gitblit-${gb.version}.zip" /> <property name="distribution.warfile" value="gitblit-${gb.version}.war" /> <property name="distribution.jarfile" value="gitblit-${gb.version}.jar" /> <property name="distribution.pomfile" value="${basedir}/pom.xml" /> <property name="fedclient.zipfile" value="fedclient-${gb.version}.zip" /> <property name="manager.zipfile" value="manager-${gb.version}.zip" /> <property name="authority.zipfile" value="authority-${gb.version}.zip" /> <property name="gbapi.zipfile" value="gbapi-${gb.version}.zip" /> <property name="express.zipfile" value="express-${gb.version}.zip" /> <property name="distribution.pomfileTmplt" value="tmplt.pom.xml" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compile ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="compile" depends="buildinfo" description="Retrieves dependencies and compiles Gitblit from source"> <!-- cleanup old builds --> <delete dir="${project.target.dir}" /> <mkdir dir="${project.target.dir}" /> Retrieve Moxie Toolkit <!-- cleanup old builds --> documentation @ http://gitblit.github.io/moxie ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <property name="moxie.version" value="0.7.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" /> <!-- Download Moxie from it's Maven repository to user.home --> <mkdir dir="${moxie.dir}" /> <get src="${moxie.url}/org/moxie/moxie-toolkit/${moxie.version}/${moxie.jar}" dest="${moxie.dir}" skipexisting="true" verbose="true" /> <!-- Register Moxie tasks --> <taskdef uri="antlib:org.moxie"> <classpath location="${moxie.dir}/${moxie.jar}" /> </taskdef> <!-- Project directories --> <property name="project.src.dir" value="${basedir}/src/main/java" /> <property name="project.resources.dir" value="${basedir}/src/main/resources" /> <property name="project.distrib.dir" value="${basedir}/src/main/distrib" /> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Initialize Moxie and setup build properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="prepare"> <!-- Setup Ant build from build.moxie and resolve dependencies. If it exists, build.properties is automatically loaded. Explicitly set mxroot allowing CI servers to override the default. --> <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="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" /> <property name="gbapi.zipfile" value="gbapi-${project.version}.zip" /> <property name="express.zipfile" value="express-${project.version}.zip" /> <!-- Download links --> <property name="gc.url" value="http://code.google.com/p/gitblit/downloads/detail?name=" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Cleanup all build artifacts and directories ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="clean" depends="prepare" description="Cleanup all build artifacts and directories"> <!-- cleanup legacy build structure --> <!-- this can be eliminated after 1.3.0 release --> <delete> <fileset dir="${basedir}"> <include name="*.zip" /> @@ -119,33 +70,47 @@ <include name="*.jar" /> </fileset> </delete> <delete dir="${basedir}/deploy" failonerror="false" /> <delete dir="${basedir}/express" failonerror="false" /> <delete dir="${basedir}/jar" failonerror="false" /> <delete dir="${basedir}/javadoc" failonerror="false" /> <delete dir="${basedir}/site" failonerror="false" /> <delete dir="${basedir}/temp" failonerror="false" /> <delete dir="${basedir}/war" failonerror="false" /> <!-- copy required distribution files to data folder --> <!-- Clean build and target directories --> <mx:clean /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Setup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="setup" depends="prepare" description="Setup up project"> <!-- copy distrib/data to project data directory --> <mkdir dir="${basedir}/data" /> <copy todir="${basedir}/data" overwrite="false"> <fileset dir="${basedir}/distrib"> <include name="gitblit.properties" /> <include name="users.conf" /> <include name="projects.conf" /> </fileset> <fileset dir="${project.distrib.dir}/data" /> </copy> <!-- copy required distribution files to project folder --> <mkdir dir="${basedir}/data/certs" /> <copy todir="${basedir}/data/certs" overwrite="false"> <fileset dir="${basedir}/distrib"> <include name="authority.conf" /> <include name="*.tmpl" /> </fileset> </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 clientapps.json to the source directory. this file is only used if a local file is not provided. --> <copy tofile="${project.src.dir}/clientapps.json" overwrite="true" file="${project.distrib.dir}/data/clientapps.json" /> <!-- copy required distribution files to project folder --> <mkdir dir="${basedir}/data/groovy" /> <copy todir="${basedir}/data/groovy" overwrite="false"> <fileset dir="${basedir}/distrib/groovy" /> </copy> <!-- upgrade existing workspace to data folder --> <!-- upgrade existing workspace to data directory this code can be eliminated after 1.3.0 release --> <move todir="${basedir}/data" overwrite="true" failonerror="false"> <fileset dir="${basedir}"> <include name="users.conf" /> @@ -164,274 +129,126 @@ <move todir="${basedir}/data/proposals" overwrite="true" failonerror="false"> <fileset dir="${basedir}/proposals" /> </move> <delete dir="${basedir}/javadoc" failonerror="false" /> <delete dir="${basedir}/site" failonerror="false" /> <delete dir="${basedir}/temp" failonerror="false" /> <!-- copy gitblit.properties to the WEB-INF folder. this file is only used for parsing setting descriptions. --> <copy tofile="${basedir}/src/WEB-INF/reference.properties" overwrite="true" file="${basedir}/distrib/gitblit.properties" /> <!-- Compile the build tool and execute it. This downloads missing compile-time dependencies from Maven. --> <delete dir="${project.build.dir}" /> <mkdir dir="${project.build.dir}" /> <javac debug="true" srcdir="${basedir}/src" destdir="${project.build.dir}" includeantruntime="false"> <include name="com/gitblit/build/Build.java" /> <include name="com/gitblit/Constants.java" /> <include name="com/gitblit/utils/StringUtils.java" /> </javac> <java classpath="${project.build.dir}" classname="com.gitblit.build.Build" failonerror="true"> <syspropertyset id="proxy.properties"> <propertyref prefix="java.net.useSystemProxies"/> <propertyref prefix="http."/> <propertyref prefix="https."/> <propertyref prefix="ftp."/> <propertyref prefix="socksProxy"/> </syspropertyset> </java> <!-- Compile Project --> <path id="master-classpath"> <fileset dir="${basedir}/ext"> <include name="*.jar" /> </fileset> <pathelement path="${project.build.dir}" /> </path> <javac debug="true" destdir="${project.build.dir}" failonerror="false" includeantruntime="false"> <src path="${basedir}/src" /> <classpath refid="master-classpath" /> </javac> <copy todir="${project.build.dir}"> <fileset dir="${basedir}/src" excludes="**/*.java,**/thumbs.db" /> </copy> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compile ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <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" outputclass="com.gitblit.Keys" todir="${project.src.dir}" /> <!-- Compile project --> <mx:javac scope="compile" clean="true" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Report the compile dependencies on the console ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="report" depends="prepare" description="generate dependency report"> <!-- Report compile dependencies to the console --> <mx:report scope="compile" destfile="${project.targetDirectory}/dependencies.txt" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="test" depends="compile" description="compiles Gitblit from source and runs unit tests"> <!-- Compile unit tests --> <mx:javac scope="test" /> <!-- Run unit tests --> <mx:test failonerror="true" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Run Gitblit GO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="run" depends="compile" description="Run Gitblit GO"> <!-- run the mainclass in a separate JVM --> <mx:run fork="true" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build Gitblit GO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildGO" depends="compile,buildAuthority" description="Build Gitblit GO distribution"> <target name="buildGO" depends="compile" description="Build Gitblit GO distribution"> <echo>Building Gitblit GO ${gb.version}</echo> <echo>Building Gitblit GO ${project.version}</echo> <!-- Delete the deploy folder --> <delete dir="${project.deploy.dir}" /> <local name="go.dir" /> <property name="go.dir" value="${project.outputDirectory}/go" /> <delete dir="${go.dir}" /> <!-- Create deployment folder structure --> <mkdir dir="${project.deploy.dir}" /> <copy todir="${project.deploy.dir}"> <fileset dir="${basedir}/distrib"> <include name="**/*" /> <exclude name="federation.properties" /> <exclude name="openshift.mkd" /> <exclude name="authority.conf" /> <exclude name="users.conf" /> <exclude name="projects.conf" /> <exclude name="gitblit.properties" /> <exclude name="*.tmpl" /> <exclude name="groovy/**" /> </fileset> <fileset dir="${basedir}"> <prepareDataDirectory toDir="${go.dir}/data" /> <!-- Build jar --> <mx:jar destfile="${go.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" /> <!-- Create GO Windows Zip deployment --> <mx:zip basedir="${go.dir}"> <!-- LICENSE and NOTICE --> <fileset dir="${basedir}" > <include name="LICENSE" /> <include name="NOTICE" /> </fileset> </copy> <!-- Copy the supported Groovy hook scripts --> <mkdir dir="${project.deploy.dir}/data/groovy" /> <copy todir="${project.deploy.dir}/data/groovy"> <fileset dir="${basedir}/distrib/groovy"> <include name="sendmail.groovy" /> <include name="sendmail-html.groovy" /> <include name="jenkins.groovy" /> <include name="protect-refs.groovy" /> <include name="fogbugz.groovy" /> <include name="thebuggenie.groovy" /> </fileset> </copy> <copy tofile="${project.deploy.dir}/authority.jar" file="${project.target.dir}/authority-${gb.version}.jar" /> <!-- Prepare the data folder --> <mkdir dir="${project.deploy.dir}/data"/> <copy todir="${project.deploy.dir}/data"> <fileset dir="${basedir}/distrib"> <include name="users.conf" /> <include name="projects.conf" /> <include name="gitblit.properties" /> </fileset> </copy> <!-- Certificate templates --> <mkdir dir="${project.deploy.dir}/data/certs"/> <mkdir dir="${project.deploy.dir}/data/certs"/> <copy todir="${project.deploy.dir}/data/certs"> <fileset dir="${basedir}/distrib"> <include name="*.tmpl" /> <include name="authority.conf" /> </fileset> </copy> <!-- Build jar --> <jar jarfile="${project.deploy.dir}/${project.jar}"> <fileset dir="${project.build.dir}"> <include name="**/*" /> <exclude name="com/gitblit/client/**" /> </fileset> <fileset dir="${project.resources.dir}"> <exclude name="thumbs.db" /> </fileset> <manifest> <attribute name="Main-Class" value="${project.mainclass}" /> </manifest> </jar> <!-- Windows distrib files --> <zipfileset dir="${project.distrib.dir}/win" /> <!-- Gitblit Authority data --> <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" /> <!-- include all dependencies --> <dependencies prefix="ext" /> </mx:zip> <!-- Gitblit library dependencies --> <mkdir dir="${project.deploy.dir}/ext"/> <copy todir="${project.deploy.dir}/ext"> <fileset dir="${basedir}/ext"> <exclude name="src/**" /> <exclude name="junit*.jar" /> <exclude name="hamcrest*.jar" /> <exclude name="commons-net*.jar" /> <!-- Create GO Linux/OSX tar.gz deployment --> <mx:tar basedir="${go.dir}" longfile="gnu" compression="gzip"> <!-- LICENSE and NOTICE --> <fileset dir="${basedir}" > <include name="LICENSE" /> <include name="NOTICE" /> </fileset> </copy> <!-- Build the docs for the deploy --> <antcall target="buildDocs" inheritall="true" inheritrefs="true"> <param name="docs.output.dir" value="${project.deploy.dir}/docs" /> </antcall> <!-- Create Zip deployment --> <zip destfile="${project.target.dir}/${distribution.zipfile}"> <fileset dir="${project.deploy.dir}"> <include name="**/*" /> </fileset> </zip> <!-- Linux/OSX distrib files --> <tarfileset dir="${project.distrib.dir}/linux" filemode="755" /> <!-- Gitblit Authority data --> <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" /> <!-- include all dependencies --> <dependencies prefix="ext" /> </mx:tar> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build Gitblit Docs which are bundled with GO and WAR downloads ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildDocs"> <!-- Build Docs --> <mkdir dir="${docs.output.dir}" /> <copy todir="${docs.output.dir}"> <!-- Copy selected Gitblit resources --> <fileset dir="${project.resources.dir}"> <include name="bootstrap/**/*" /> <include name="gitblit.css" /> <include name="gitblt_25_white.png" /> <include name="gitblt-favicon.png" /> <include name="lock_go_16x16.png" /> <include name="lock_pull_16x16.png" /> <include name="shield_16x16.png" /> <include name="cold_16x16.png" /> <include name="bug_16x16.png" /> <include name="book_16x16.png" /> <include name="blank.png" /> <include name="federated_16x16.png" /> <include name="arrow_page.png" /> </fileset> <!-- Copy Doc images --> <fileset dir="${basedir}/docs"> <include name="*.png" /> <include name="*.gif" /> </fileset> </copy> <!-- Copy google-code-prettify --> <mkdir dir="${docs.output.dir}/prettify" /> <copy todir="${docs.output.dir}/prettify"> <fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify"> <exclude name="thumbs.db" /> </fileset> </copy> <!-- Build deployment doc pages --> <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildSite"> <classpath refid="master-classpath" /> <arg value="--sourceFolder" /> <arg value="${basedir}/docs" /> <arg value="--outputFolder" /> <arg value="${docs.output.dir}" /> <arg value="--pageHeader" /> <arg value="${basedir}/docs/doc_header.html" /> <arg value="--pageFooter" /> <arg value="${basedir}/docs/doc_footer.html" /> <arg value="--skip" /> <arg value="screenshots" /> <arg value="--skip" /> <arg value="releases" /> <arg value="--alias" /> <arg value="index=overview" /> <arg value="--alias" /> <arg value="properties=settings" /> <arg value="--substitute" /> <arg value="%VERSION%=${gb.version}" /> <arg value="--substitute" /> <arg value="%GO%=${distribution.zipfile}" /> <arg value="--substitute" /> <arg value="%WAR%=${distribution.warfile}" /> <arg value="--substitute" /> <arg value="%FEDCLIENT%=${fedclient.zipfile}" /> <arg value="--substitute" /> <arg value="%MANAGER%=${manager.zipfile}" /> <arg value="--substitute" /> <arg value="%API%=${gbapi.zipfile}" /> <arg value="--substitute" /> <arg value="%EXPRESS%=${express.zipfile}" /> <arg value="--substitute" /> <arg value="%BUILDDATE%=${gb.versionDate}" /> <arg value="--substitute" /> <arg value="%JGIT%=${jgit.version}" /> <arg value="--properties" /> <arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" /> <arg value="--nomarkdown" /> <arg value="%BEGINCODE%:%ENDCODE%" /> <arg value="--substitute" /> <arg value=""%BEGINCODE%=<pre class='prettyprint lang-java'>"" /> <arg value="--substitute" /> <arg value="%ENDCODE%=</pre>" /> <arg value="--regex" /> <arg value=""\b(issue)(\s*[#]?|-){0,1}(\d+)\b!!!<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>"" /> </java> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build Gitblit WAR @@ -439,180 +256,72 @@ --> <target name="buildWAR" depends="compile" description="Build Gitblit WAR"> <echo>Building Gitblit WAR ${gb.version}</echo> <echo>Building Gitblit WAR ${project.version}</echo> <local name="war.dir" /> <property name="war.dir" value="${project.outputDirectory}/war" /> <delete dir="${war.dir}" /> <delete dir="${project.war.dir}" /> <local name="webinf" /> <property name="webinf" value="${war.dir}/WEB-INF" /> <!-- Copy web.xml and users.conf to WEB-INF --> <copy todir="${project.war.dir}/WEB-INF"> <fileset dir="${basedir}/src/WEB-INF"> <include name="web.xml" /> </fileset> <fileset dir="${basedir}"> <include name="LICENSE" /> <include name="NOTICE" /> </fileset> </copy> <!-- Copy gitblit.properties as reference.properties --> <copy tofile="${project.war.dir}/WEB-INF/reference.properties" file="${basedir}/distrib/gitblit.properties"/> <!-- Build the docs for the WAR build --> <antcall target="buildDocs" inheritall="true" inheritrefs="true"> <param name="docs.output.dir" value="${project.war.dir}/WEB-INF/docs" /> </antcall> <!-- Generate the docs for the WAR build --> <generateDocs toDir="${webinf}/docs" /> <!-- Copy users.conf to WEB-INF/data --> <mkdir dir="${project.war.dir}/WEB-INF/data" /> <copy todir="${project.war.dir}/WEB-INF/data"> <fileset dir="${basedir}/distrib"> <include name="users.conf" /> <include name="projects.conf" /> <include name="gitblit.properties" /> </fileset> </copy> <!-- Prepare the data directory --> <prepareDataDirectory toDir="${webinf}/data" /> <!-- Copy the supported Groovy hook scripts --> <mkdir dir="${project.war.dir}/WEB-INF/data/groovy" /> <copy todir="${project.war.dir}/WEB-INF/data/groovy"> <fileset dir="${basedir}/distrib/groovy"> <include name="sendmail.groovy" /> <include name="sendmail-html.groovy" /> <include name="jenkins.groovy" /> <include name="protect-refs.groovy" /> <include name="fogbugz.groovy" /> <include name="thebuggenie.groovy" /> </fileset> </copy> <!-- Build the WAR web.xml from the prototype web.xml --> <mx:webxml sourcefile="${project.src.dir}/WEB-INF/web.xml" destfile="${webinf}/web.xml"> <replace token="@gb.version@" value="${project.version}" /> </mx:webxml> <!-- Build the WAR web.xml from the prototype web.xml --> <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildWebXml"> <classpath refid="master-classpath" /> <arg value="--sourceFile" /> <arg value="${basedir}/src/WEB-INF/web.xml" /> <arg value="--destinationFile" /> <arg value="${project.war.dir}/WEB-INF/web.xml" /> </java> <!-- Gitblit resources --> <copy todir="${project.war.dir}"> <fileset dir="${project.resources.dir}"> <exclude name="thumbs.db" /> </fileset> </copy> <!-- Gitblit library dependencies --> <mkdir dir="${project.war.dir}/WEB-INF/lib"/> <copy todir="${project.war.dir}/WEB-INF/lib"> <fileset dir="${basedir}/ext"> <exclude name="src/**" /> <exclude name="jcommander*.jar" /> <exclude name="jetty*.jar" /> <exclude name="junit*.jar" /> <exclude name="hamcrest*.jar" /> <exclude name="servlet*.jar" /> <exclude name="javax.servlet*.jar" /> </fileset> </copy> <!-- Gitblit classes --> <mkdir dir="${project.war.dir}/WEB-INF/classes"/> <copy todir="${project.war.dir}/WEB-INF/classes"> <fileset dir="${project.build.dir}"> <exclude name="WEB-INF/" /> <exclude name="com/gitblit/tests/" /> <exclude name="com/gitblit/build/**" /> <exclude name="com/gitblit/client/**" /> <exclude name="com/gitblit/AddIndexedBranch*.class" /> <exclude name="com/gitblit/GitBlitServer*.class" /> <exclude name="com/gitblit/Launcher*.class" /> <exclude name="com/gitblit/authority/**" /> </fileset> </copy> <!-- Gitblit jar --> <mx:genjar destfile="${webinf}/lib/gitblit.jar" includeresources="false" excludeclasspathjars="true"> <!-- Specify all web.xml servlets and filters --> <class name="com.gitblit.GitBlit" /> <class name="com.gitblit.Keys" /> <class name="com.gitblit.DownloadZipFilter" /> <class name="com.gitblit.DownloadZipServlet" /> <class name="com.gitblit.EnforceAuthenticationFilter" /> <class name="com.gitblit.FederationServlet" /> <class name="com.gitblit.GitFilter" /> <class name="com.gitblit.git.GitServlet" /> <class name="com.gitblit.LogoServlet" /> <class name="com.gitblit.PagesFilter" /> <class name="com.gitblit.PagesServlet" /> <class name="com.gitblit.RobotsTxtServlet" /> <class name="com.gitblit.RpcFilter" /> <class name="com.gitblit.RpcServlet" /> <class name="com.gitblit.SyndicationFilter" /> <class name="com.gitblit.SyndicationServlet" /> <class name="com.gitblit.SparkleShareInviteServlet" /> <class name="com.gitblit.wicket.GitBlitWebApp" /> <!-- Manually include alternative User Services --> <class name="com.gitblit.LdapUserService" /> <class name="com.gitblit.RedmineUserService" /> <class name="com.gitblit.SalesforceUserService" /> <class name="com.gitblit.WindowsUserService" /> </mx:genjar> <!-- Build the WAR file --> <jar basedir="${project.war.dir}" destfile="${project.target.dir}/${distribution.warfile}" compress="true" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build Gitblit JAR for usage in other projects plug-ins (i.e. Gerrit) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildJAR" depends="compile" description="Build Gitblit JAR"> <echo>Building Gitblit JAR ${gb.version}</echo> <delete dir="${project.jar.dir}" /> <!-- Gitblit classes --> <mkdir dir="${project.jar.dir}"/> <copy todir="${project.jar.dir}"> <fileset dir="${project.build.dir}"> <exclude name="WEB-INF/" /> <exclude name="com/gitblit/tests/" /> <exclude name="com/gitblit/build/**" /> <exclude name="com/gitblit/client/**" /> <exclude name="com/gitblit/authority/**" /> <exclude name="com/gitblit/AddIndexedBranch*.class" /> <exclude name="com/gitblit/GitBlitServer*.class" /> <exclude name="com/gitblit/Launcher*.class" /> </fileset> </copy> <copy todir="${project.jar.dir}/static"> <mx:zip basedir="${war.dir}" destfile="${project.targetDirectory}/${distribution.warfile}" compress="true" > <!-- Resources in root --> <fileset dir="${project.resources.dir}"> <exclude name="thumbs.db" /> <exclude name="*.mkd" /> </fileset> </copy> <!-- Build the JAR file --> <jar basedir="${project.jar.dir}" destfile="${distribution.jarfile}" compress="true" /> <!-- WEB-INF directory --> <zipfileset prefix="WEB-INF" dir="${basedir}" > <include name="LICENSE" /> <include name="NOTICE" /> </zipfileset> <zipfileset prefix="WEB-INF" file="${project.compileOutputDirectory}/WEB-INF/weblogic.xml" /> <!-- include "war" tagged dependencies --> <dependencies prefix="WEB-INF/lib" tag="war" /> </mx:zip> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build pom.xml for GitBlit JAR Maven module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildMaven" depends="buildJAR" description="Build pom.xml for Gitblit JAR Maven module"> <copy tofile="${distribution.pomfile}" file="${distribution.pomfileTmplt}"/> <replace file="${distribution.pomfile}" token="@gb.version@" value="${gb.version}" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Install Gitblit JAR for usage as Maven module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="installMaven" depends="buildMaven" description="Install Gitblit JAR as Maven module"> <exec executable="mvn"> <arg value="install:install-file" /> <arg value="-Dfile=${distribution.jarfile}" /> <arg value="-DpomFile=${distribution.pomfile}" /> <arg value="-DcreateChecksum=true" /> </exec> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Upload Gitblit JAR to remote Maven repository ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="uploadMaven" depends="buildJAR" description="Upload Gitblit JAR to remote Maven repository"> <exec executable="mvn"> <arg value="deploy:deploy-file" /> <arg value="-Dfile=${distribution.jarfile}" /> <arg value="-DpomFile=${distribution.pomfile}" /> <arg value="-Durl=${project.maven.repo.url}" /> <arg value="-DrepositoryId=${project.maven.repo.id}" /> <arg value="-DcreateChecksum=true" /> </exec> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -620,156 +329,116 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildFederationClient" depends="compile" description="Builds the stand-alone Gitblit federation client"> <echo>Building Gitblit Federation Client ${gb.version}</echo> <echo>Building Gitblit Federation Client ${project.version}</echo> <genjar jarfile="${project.target.dir}/fedclient.jar"> <class name="com.gitblit.FederationClientLauncher" /> <resource file="${project.build.dir}/log4j.properties" /> <classfilter> <exclude name="org.apache." /> <exclude name="org.bouncycastle." /> <exclude name="org.eclipse." /> <exclude name="org.slf4j." /> <exclude name="com.beust." /> <exclude name="com.google." /> <exclude name="com.unboundid." /> </classfilter> <classpath refid="master-classpath" /> <manifest> <attribute name="Main-Class" value="com.gitblit.FederationClientLauncher" /> <attribute name="Specification-Version" value="${gb.version}" /> <attribute name="Release-Date" value="${gb.versionDate}" /> </manifest> </genjar> <!-- 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}/fedclient.jar"> <mainclass name="com.gitblit.FederationClient" /> <class name="com.gitblit.Keys" /> <launcher paths="ext" /> <resource file="${project.compileOutputDirectory}/log4j.properties" /> </mx:genjar> <!-- Build the federation client zip file --> <zip destfile="${project.target.dir}/${fedclient.zipfile}"> <mx:zip destfile="${project.targetDirectory}/${fedclient.zipfile}"> <fileset dir="${basedir}"> <include name="LICENSE" /> <include name="NOTICE" /> </fileset> <fileset dir="${project.target.dir}"> <fileset dir="${project.targetDirectory}"> <include name="fedclient.jar" /> </fileset> <fileset dir="${basedir}/distrib"> <fileset dir="${project.distrib.dir}"> <include name="federation.properties" /> </fileset> </zip> <!-- include "fedclient" tagged dependencies --> <dependencies prefix="ext" tag="fedclient" /> </mx:zip> <!-- Cleanup --> <delete file="${project.target.dir}/fedclient.jar" /> <delete file="${project.targetDirectory}/fedclient.jar" /> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build a Gitblit filesystem for deployment to RedHat OpenShif Expresst Build a Gitblit filesystem for deployment to RedHat OpenShift Express ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildExpress" depends="compile" description="Build exploded WAR file suitable for deployment to OpenShift Express"> <echo>Building Gitblit Express for RedHat OpenShift ${gb.version}</echo> <echo>Building Gitblit Express for RedHat OpenShift ${project.version}</echo> <delete dir="${project.express.dir}" /> <local name="express.dir" /> <property name="express.dir" value="${project.outputDirectory}/express" /> <delete dir="${express.dir}" /> <!-- Create the OpenShift filesystem --> <property name="deployments.root" value="${project.express.dir}/deployments/ROOT.war"/> <local name="deployments.root" /> <property name="deployments.root" value="${express.dir}/deployments/ROOT.war"/> <mkdir dir="${deployments.root}" /> <touch file="${project.express.dir}/deployments/ROOT.war.dodeploy" /> <touch file="${express.dir}/deployments/ROOT.war.dodeploy" /> <!-- Copy the Gitblit OpenShift readme file --> <copy tofile="${project.express.dir}/README.gitblit" file="${basedir}/distrib/openshift.mkd"/> <local name="webinf" /> <property name="webinf" value="${deployments.root}/WEB-INF" /> <!-- Copy LICENSE and NOTICE to WEB-INF --> <copy todir="${deployments.root}/WEB-INF"> <!-- Prepare the data directory --> <prepareDataDirectory toDir="${webinf}/data" /> <!-- Build the Express web.xml from the prototype web.xml and gitblit.properties --> <!-- THIS FILE IS NOT OVERRIDDEN ONCE IT IS BUILT!!! --> <mx:webxml sourcefile="${project.src.dir}/WEB-INF/web.xml" destfile="${webinf}/web.xml" propertiesFile="${project.distrib.dir}/data/gitblit.properties" skip="server.*"> <replace token="@gb.version@" value="${project.version}" /> </mx:webxml> <!-- Gitblit classes --> <mx:genjar destfile="${webinf}/lib/gitblit.jar" includeresources="false" excludeclasspathjars="true"> <class name="com.gitblit.Keys" /> <!-- Specify all web.xml servlets and filters --> <class name="com.gitblit.GitBlit" /> <class name="com.gitblit.DownloadZipFilter" /> <class name="com.gitblit.DownloadZipServlet" /> <class name="com.gitblit.EnforceAuthenticationFilter" /> <class name="com.gitblit.FederationServlet" /> <class name="com.gitblit.GitFilter" /> <class name="com.gitblit.git.GitServlet" /> <class name="com.gitblit.LogoServlet" /> <class name="com.gitblit.PagesFilter" /> <class name="com.gitblit.PagesServlet" /> <class name="com.gitblit.RobotsTxtServlet" /> <class name="com.gitblit.RpcFilter" /> <class name="com.gitblit.RpcServlet" /> <class name="com.gitblit.SyndicationFilter" /> <class name="com.gitblit.SyndicationServlet" /> <class name="com.gitblit.SparkleShareInviteServlet" /> <class name="com.gitblit.wicket.GitBlitWebApp" /> <!-- Manually include alternative User Services --> <class name="com.gitblit.LdapUserService" /> <class name="com.gitblit.RedmineUserService" /> <class name="com.gitblit.SalesforceUserService" /> <class name="com.gitblit.WindowsUserService" /> </mx:genjar> <!-- Build Express Zip file --> <mx:zip basedir="${express.dir}" destfile="${project.targetDirectory}/${express.zipfile}"> <fileset dir="${basedir}"> <include name="LICENSE" /> <include name="NOTICE" /> </fileset> </copy> <!-- Copy gitblit.properties as reference.properties --> <copy tofile="${deployments.root}/WEB-INF/reference.properties" file="${basedir}/distrib/gitblit.properties"/> <!-- Copy users.conf and gitblit.properties --> <mkdir dir="${deployments.root}/WEB-INF/data" /> <copy todir="${deployments.root}/WEB-INF/data"> <fileset dir="${basedir}/distrib"> <include name="users.conf" /> <include name="projects.conf" /> <include name="gitblit.properties" /> </fileset> </copy> <!-- Copy the supported Groovy hook scripts --> <mkdir dir="${deployments.root}/WEB-INF/data/groovy" /> <copy todir="${deployments.root}/WEB-INF/data/groovy"> <fileset dir="${basedir}/distrib/groovy"> <include name="sendmail.groovy" /> <include name="sendmail-html.groovy" /> <include name="jenkins.groovy" /> <include name="protect-refs.groovy" /> <include name="fogbugz.groovy" /> <include name="thebuggenie.groovy" /> </fileset> </copy> <!-- Build the WAR web.xml from the prototype web.xml and gitblit.properties --> <!-- THIS FILE IS NOT OVERRIDDEN ONCE IT IS BUILT!!! --> <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildWebXml"> <classpath refid="master-classpath" /> <arg value="--sourceFile" /> <arg value="${basedir}/src/WEB-INF/web.xml" /> <arg value="--destinationFile" /> <arg value="${deployments.root}/WEB-INF/web.xml" /> <arg value="--propertiesFile" /> <arg value="${basedir}/distrib/gitblit.properties" /> </java> <!-- Gitblit resources --> <copy todir="${deployments.root}"> <fileset dir="${project.resources.dir}"> <!-- README --> <zipfileset fullpath="README.gitblit" file="${project.siteSourceDirectory}/openshift.mkd" /> <!-- resources --> <zipfileset prefix="deployments/ROOT.war" dir="${project.resources.dir}"> <exclude name="thumbs.db" /> </fileset> </copy> <!-- Gitblit library dependencies --> <mkdir dir="${deployments.root}/WEB-INF/lib"/> <copy todir="${deployments.root}/WEB-INF/lib"> <fileset dir="${basedir}/ext"> <exclude name="src/**" /> <exclude name="jcommander*.jar" /> <exclude name="jetty*.jar" /> <exclude name="junit*.jar" /> <exclude name="hamcrest*.jar" /> <exclude name="servlet*.jar" /> <exclude name="javax.servlet*.jar" /> <exclude name="jsslutils*.jar" /> <exclude name="jcalendar*.jar" /> </fileset> </copy> <!-- Gitblit classes --> <jar destfile="${deployments.root}/WEB-INF/lib/gitblit-${gb.version}.jar"> <fileset dir="${project.build.dir}"> <exclude name="WEB-INF/" /> <exclude name="com/gitblit/tests/" /> <exclude name="com/gitblit/build/**" /> <exclude name="com/gitblit/client/**" /> <exclude name="com/gitblit/GitBlitServer*.class" /> <exclude name="com/gitblit/Launcher*.class" /> <exclude name="com/gitblit/authority/**" /> </fileset> </jar> <!-- Build Express Zip file --> <zip destfile="${project.target.dir}/${express.zipfile}"> <fileset dir="${project.express.dir}" /> </zip> <exclude name="*.mkd" /> </zipfileset> <!-- include "war" tagged dependencies --> <dependencies prefix="deployments/ROOT.war/WEB-INF/lib" tag="war" /> </mx:zip> </target> @@ -780,72 +449,69 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildManager" depends="compile" description="Builds the stand-alone Gitblit Manager"> <echo>Building Gitblit Manager ${gb.version}</echo> <echo>Building Gitblit Manager ${project.version}</echo> <genjar jarfile="${project.target.dir}/manager-${gb.version}.jar"> <resource file="${basedir}/src/com/gitblit/client/splash.png" /> <resource file="${basedir}/resources/gitblt-favicon.png" /> <resource file="${basedir}/resources/gitweb-favicon.png" /> <resource file="${basedir}/resources/git-orange-16x16.png" /> <resource file="${basedir}/resources/user_16x16.png" /> <resource file="${basedir}/resources/users_16x16.png" /> <resource file="${basedir}/resources/settings_16x16.png" /> <resource file="${basedir}/resources/lock_go_16x16.png" /> <resource file="${basedir}/resources/lock_pull_16x16.png" /> <resource file="${basedir}/resources/shield_16x16.png" /> <resource file="${basedir}/resources/federated_16x16.png" /> <resource file="${basedir}/resources/cold_16x16.png" /> <resource file="${basedir}/resources/book_16x16.png" /> <resource file="${basedir}/resources/bug_16x16.png" /> <resource file="${basedir}/resources/health_16x16.png" /> <resource file="${basedir}/resources/feed_16x16.png" /> <resource file="${basedir}/resources/bullet_feed.png" /> <resource file="${basedir}/resources/search-icon.png" /> <resource file="${basedir}/resources/commit_changes_16x16.png" /> <resource file="${basedir}/resources/commit_merge_16x16.png" /> <resource file="${basedir}/resources/commit_divide_16x16.png" /> <resource file="${basedir}/resources/star_16x16.png" /> <resource file="${basedir}/resources/blank.png" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_es.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ja.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ko.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_nl.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_pl.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties" /> <!-- 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"> <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" /> <resource file="${project.resources.dir}/git-orange-16x16.png" /> <resource file="${project.resources.dir}/user_16x16.png" /> <resource file="${project.resources.dir}/users_16x16.png" /> <resource file="${project.resources.dir}/settings_16x16.png" /> <resource file="${project.resources.dir}/lock_go_16x16.png" /> <resource file="${project.resources.dir}/lock_pull_16x16.png" /> <resource file="${project.resources.dir}/shield_16x16.png" /> <resource file="${project.resources.dir}/federated_16x16.png" /> <resource file="${project.resources.dir}/cold_16x16.png" /> <resource file="${project.resources.dir}/book_16x16.png" /> <resource file="${project.resources.dir}/bug_16x16.png" /> <resource file="${project.resources.dir}/health_16x16.png" /> <resource file="${project.resources.dir}/feed_16x16.png" /> <resource file="${project.resources.dir}/bullet_feed.png" /> <resource file="${project.resources.dir}/search-icon.png" /> <resource file="${project.resources.dir}/commit_changes_16x16.png" /> <resource file="${project.resources.dir}/commit_merge_16x16.png" /> <resource file="${project.resources.dir}/commit_divide_16x16.png" /> <resource file="${project.resources.dir}/star_16x16.png" /> <resource file="${project.resources.dir}/blank.png" /> <resource file="${project.src.dir}/log4j.properties" /> <resource> <!-- inlcude all translations --> <fileset dir="${project.src.dir}/com/gitblit/wicket"> <include name="*.properties" /> </fileset> </resource> <class name="com.gitblit.client.GitblitManagerLauncher" /> <classfilter> <exclude name="org.apache." /> <exclude name="org.bouncycastle." /> <exclude name="org.eclipse." /> <exclude name="org.slf4j." /> <exclude name="com.beust." /> <exclude name="com.google." /> <exclude name="com.unboundid." /> </classfilter> <classpath refid="master-classpath" /> <mainclass name="com.gitblit.client.GitblitManagerLauncher" /> <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" /> <manifest> <attribute name="Main-Class" value="com.gitblit.client.GitblitManagerLauncher" /> <attribute name="SplashScreen-Image" value="splash.png" /> <attribute name="Specification-Version" value="${gb.version}" /> <attribute name="Release-Date" value="${gb.versionDate}" /> </manifest> </genjar> </mx:genjar> <!-- Build Manager Zip file --> <zip destfile="${project.target.dir}/${manager.zipfile}"> <mx:zip destfile="${project.targetDirectory}/${manager.zipfile}"> <fileset dir="${basedir}"> <include name="LICENSE" /> <include name="NOTICE" /> </fileset> <fileset dir="${project.target.dir}"> <include name="manager-${gb.version}.jar" /> <fileset dir="${project.targetDirectory}"> <include name="manager.jar" /> </fileset> </zip> <!-- include "manager" tagged dependencies --> <dependencies prefix="ext" tag="manager" /> </mx:zip> <!-- Cleanup --> <delete file="${project.target.dir}/manager-${gb.version}.jar" /> <delete file="${project.targetDirectory}/manager.jar" /> </target> @@ -855,69 +521,67 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildAuthority" depends="compile" description="Builds the stand-alone Gitblit Authority"> <echo>Building Gitblit Authority ${gb.version}</echo> <echo>Building Gitblit Authority ${project.version}</echo> <genjar jarfile="${project.target.dir}/authority-${gb.version}.jar"> <resource file="${basedir}/src/com/gitblit/client/splash.png" /> <resource file="${basedir}/resources/gitblt-favicon.png" /> <resource file="${basedir}/resources/user_16x16.png" /> <resource file="${basedir}/resources/users_16x16.png" /> <resource file="${basedir}/resources/rosette_16x16.png" /> <resource file="${basedir}/resources/rosette_32x32.png" /> <resource file="${basedir}/resources/vcard_16x16.png" /> <resource file="${basedir}/resources/settings_16x16.png" /> <resource file="${basedir}/resources/settings_32x32.png" /> <resource file="${basedir}/resources/search-icon.png" /> <resource file="${basedir}/resources/mail_16x16.png" /> <resource file="${basedir}/resources/script_16x16.png" /> <resource file="${basedir}/resources/blank.png" /> <resource file="${basedir}/resources/bullet_green.png" /> <resource file="${basedir}/resources/bullet_orange.png" /> <resource file="${basedir}/resources/bullet_red.png" /> <resource file="${basedir}/resources/bullet_white.png" /> <resource file="${basedir}/resources/bullet_delete.png" /> <resource file="${basedir}/resources/bullet_key.png" /> <resource file="${basedir}/src/log4j.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_es.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ja.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ko.properties" /> <resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_pl.properties" /> <!-- generate jar by traversing the class hierarchy of the specified classes, exclude any classes in "authority" classpath jars --> <mx:genjar tag="authority" excludeClasspathJars="true" destfile="${project.targetDirectory}/authority.jar"> <resource file="${project.src.dir}/com/gitblit/client/splash.png" /> <resource file="${project.resources.dir}/gitblt-favicon.png" /> <resource file="${project.resources.dir}/user_16x16.png" /> <resource file="${project.resources.dir}/users_16x16.png" /> <resource file="${project.resources.dir}/rosette_16x16.png" /> <resource file="${project.resources.dir}/rosette_32x32.png" /> <resource file="${project.resources.dir}/vcard_16x16.png" /> <resource file="${project.resources.dir}/settings_16x16.png" /> <resource file="${project.resources.dir}/settings_32x32.png" /> <resource file="${project.resources.dir}/search-icon.png" /> <resource file="${project.resources.dir}/mail_16x16.png" /> <resource file="${project.resources.dir}/script_16x16.png" /> <resource file="${project.resources.dir}/blank.png" /> <resource file="${project.resources.dir}/bullet_green.png" /> <resource file="${project.resources.dir}/bullet_orange.png" /> <resource file="${project.resources.dir}/bullet_red.png" /> <resource file="${project.resources.dir}/bullet_white.png" /> <resource file="${project.resources.dir}/bullet_delete.png" /> <resource file="${project.resources.dir}/bullet_key.png" /> <resource file="${project.src.dir}/log4j.properties" /> <resource> <!-- inlcude all translations --> <fileset dir="${project.src.dir}/com/gitblit/wicket"> <include name="*.properties" /> </fileset> </resource> <class name="com.gitblit.authority.GitblitAuthorityLauncher" /> <classfilter> <exclude name="org.apache." /> <exclude name="org.bouncycastle." /> <exclude name="org.eclipse." /> <exclude name="org.slf4j." /> <exclude name="com.beust." /> <exclude name="com.google." /> <exclude name="com.unboundid." /> </classfilter> <classpath refid="master-classpath" /> <mainclass name="com.gitblit.authority.Launcher" /> <class name="com.gitblit.Keys" /> <manifest> <attribute name="Main-Class" value="com.gitblit.authority.GitblitAuthorityLauncher" /> <attribute name="SplashScreen-Image" value="splash.png" /> <attribute name="Specification-Version" value="${gb.version}" /> <attribute name="Release-Date" value="${gb.versionDate}" /> </manifest> </genjar> </mx:genjar> <!-- Build Authority Zip file --> <zip destfile="${project.target.dir}/${authority.zipfile}"> <mx:zip destfile="${project.targetDirectory}/${authority.zipfile}"> <fileset dir="${basedir}"> <include name="LICENSE" /> <include name="NOTICE" /> </fileset> <fileset dir="${project.target.dir}"> <include name="authority-${gb.version}.jar" /> <fileset dir="${project.targetDirectory}"> <include name="authority.jar" /> </fileset> <zipfileset dir="${basedir}/distrib" prefix="data/certs"> <include name="authority.conf" /> <include name="mail.tmpl" /> <include name="instructions.tmpl" /> <zipfileset dir="${project.distrib.dir}/data" prefix="data"> <include name="users.conf" /> <include name="gitblit.properties" /> </zipfileset> </zip> <!-- Gitblit Authority data --> <zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" /> <!-- include "authority" tagged dependencies --> <dependencies prefix="ext" tag="authority" /> </mx:zip> <!-- Cleanup --> <delete file="${project.targetDirectory}/authority.jar" /> </target> <!-- @@ -926,29 +590,25 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildApiLibrary" depends="compile" description="Builds the Gitblit RPC client library"> <echo>Building Gitblit API Library ${gb.version}</echo> <echo>Building Gitblit API Library ${project.version}</echo> <local name="javadoc.dir" /> <property name="javadoc.dir" value="${project.outputDirectory}/javadoc" /> <delete dir="${javadoc.dir}" /> <!-- Build API Library jar --> <genjar jarfile="${project.target.dir}/gbapi-${gb.version}.jar"> <mx:genjar tag="" includeResources="false" excludeClasspathJars="true" destfile="${project.targetDirectory}/gbapi-${project.version}.jar"> <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" /> <classpath refid="master-classpath" /> <classfilter> <exclude name="com.google.gson." /> <exclude name="com.sun.syndication." /> </classfilter> <manifest> <attribute name="Specification-Version" value="${gb.version}" /> <attribute name="Release-Date" value="${gb.versionDate}" /> </manifest> </genjar> </mx:genjar> <!-- Build API sources jar --> <zip destfile="${project.target.dir}/gbapi-${gb.version}-sources.jar"> <fileset dir="${basedir}/src" defaultexcludes="yes"> <zip destfile="${project.targetDirectory}/gbapi-${project.version}-sources.jar"> <fileset dir="${project.src.dir}" defaultexcludes="yes"> <include name="com/gitblit/Constants.java"/> <include name="com/gitblit/GitBlitException.java"/> <include name="com/gitblit/Keys.java"/> @@ -959,8 +619,8 @@ </zip> <!-- Build API JavaDoc jar --> <javadoc destdir="${project.target.dir}/javadoc"> <fileset dir="${basedir}/src" defaultexcludes="yes"> <mx:javadoc destdir="${javadoc.dir}" redirect="true"> <fileset dir="${project.src.dir}" defaultexcludes="yes"> <include name="com/gitblit/Constants.java"/> <include name="com/gitblit/GitBlitException.java"/> <include name="com/gitblit/Keys.java"/> @@ -968,37 +628,34 @@ <include name="com/gitblit/models/**/*.java"/> <include name="com/gitblit/utils/**/*.java"/> </fileset> </javadoc> <zip destfile="${project.target.dir}/gbapi-${gb.version}-javadoc.jar"> <fileset dir="${project.target.dir}/javadoc" /> </mx:javadoc> <zip destfile="${project.targetDirectory}/gbapi-${project.version}-javadoc.jar"> <fileset dir="${javadoc.dir}" /> </zip> <!-- Build the API library zip file --> <zip destfile="${project.target.dir}/${gbapi.zipfile}"> <mx:zip destfile="${project.targetDirectory}/${gbapi.zipfile}"> <fileset dir="${basedir}"> <include name="LICENSE" /> <include name="NOTICE" /> </fileset> <fileset dir="${project.target.dir}"> <include name="gbapi-${gb.version}.jar" /> <include name="gbapi-${gb.version}-sources.jar" /> <include name="gbapi-${gb.version}-javadoc.jar" /> <fileset dir="${project.targetDirectory}"> <include name="gbapi-${project.version}.jar" /> <include name="gbapi-${project.version}-sources.jar" /> <include name="gbapi-${project.version}-javadoc.jar" /> </fileset> <fileset dir="${basedir}/ext"> <exclude name="src/**" /> <include name="gson*.jar" /> <include name="rome*.jar" /> <include name="jdom*.jar" /> </fileset> </zip> <!-- include "api" tagged dependencies --> <dependencies prefix="ext" tag="api" /> </mx:zip> <!-- Cleanup --> <delete> <fileset dir="${project.target.dir}"> <fileset dir="${project.targetDirectory}"> <include name="javadoc/**" /> <include name="gbapi-${gb.version}.jar" /> <include name="gbapi-${gb.version}-sources.jar" /> <include name="gbapi-${gb.version}-javadoc.jar" /> <include name="gbapi-${project.version}.jar" /> <include name="gbapi-${project.version}-sources.jar" /> <include name="gbapi-${project.version}-javadoc.jar" /> </fileset> </delete> </target> @@ -1009,162 +666,148 @@ Build the Gitblit Website ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildSite" depends="compile" description="Build the Gitblit website"> <target name="buildSite" depends="prepare" description="Build the Gitblit website"> <echo>Building Gitblit Website ${gb.version}</echo> <echo>Building Gitblit Website ${project.version}</echo> <property name="releaselog" value="${basedir}/releases.moxie" /> <!-- Build Site --> <delete dir="${project.site.dir}" /> <mkdir dir="${project.site.dir}" /> <copy todir="${project.site.dir}"> <!-- Copy selected Gitblit resources --> <fileset dir="${project.resources.dir}"> <include name="bootstrap/**/*" /> <include name="gitblit.css" /> <include name="gitblt_25_white.png" /> <include name="gitblt-favicon.png" /> <include name="lock_go_16x16.png" /> <include name="lock_pull_16x16.png" /> <include name="shield_16x16.png" /> <include name="cold_16x16.png" /> <include name="bug_16x16.png" /> <include name="book_16x16.png" /> <include name="blank.png" /> <include name="federated_16x16.png" /> <include name="arrow_page.png" /> </fileset> <!-- Copy Doc images --> <fileset dir="${basedir}/docs"> <include name="*.png" /> <include name="*.gif" /> <include name="*.js" /> </fileset> </copy> <mx:doc googleplusid="114464678392593421684" googleanalyticsid="UA-24377072-1" googlePlusOne="true" minify="true" customless="custom.less"> <structure> <menu name="about"> <page name="overview" src="siteindex.mkd" out="index.html" headerLinks="false" /> <page name="features" src="features.mkd" /> <page name="screenshots" src="screenshots.mkd" /> </menu> <menu name="documentation" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <menu name="Gitblit GO" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <page name="setup GO" src="setup_go.mkd" /> <page name="upgrade GO" src="upgrade_go.mkd" /> </menu> <divider /> <menu name="Gitblit WAR" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <page name="setup WAR" src="setup_war.mkd" /> <page name="upgrade WAR" src="upgrade_war.mkd" /> </menu> <divider /> <menu name="Gitblit Express" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <page name="setup Express" src="setup_express.mkd" /> <page name="upgrade Express" src="upgrade_express.mkd" /> </menu> <divider /> <page name="administration" src="administration.mkd" /> <page name="authentication" src="setup_authentication.mkd" /> <page name="push hooks" src="setup_hooks.mkd" /> <page name="lucene indexing" src="setup_lucene.mkd" /> <page name="reverse proxies" src="setup_proxy.mkd" /> <page name="client app menus" src="setup_clientmenus.mkd" /> <divider /> <page name="Gitblit as a viewer" src="setup_viewer.mkd" /> <divider /> <page name="git client setup" src="setup_client.mkd" /> <divider /> <page name="federation" src="federation.mkd" /> <divider /> <page name="settings" src="properties.mkd" /> <page name="faq" src="faq.mkd" /> <divider /> <page name="design" src="design.mkd" /> <page name="rpc" src="rpc.mkd" /> </menu> <menu name="releases"> <page name="release notes" out="releasenotes.html"> <template src="releasecurrent.ftl" data="${releaselog}" /> </page> <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"> <link name="Gitblit GO (Windows)" src="${gc.url}gitblit-${project.releaseVersion}.zip" /> <link name="Gitblit GO (Linux/OSX)" src="${gc.url}gitblit-${project.releaseVersion}.tar.gz" /> <link name="Gitblit WAR" src="${gc.url}gitblit-${project.releaseVersion}.war" /> <link name="Gitblit Express" src="${gc.url}express-${project.releaseVersion}.zip" /> <divider /> <link name="Gitblit Manager" src="${gc.url}manager-${project.releaseVersion}.zip" /> <link name="Federation Client" src="${gc.url}fedclient-${project.releaseVersion}.zip" /> <divider /> <link name="API Library" src="${gc.url}gbapi-${project.releaseVersion}.zip" /> </menu> <menu name="links"> <link name="Gitblit Demo (RELEASE)" src="https://demo-gitblit.rhcloud.com" /> <link name="Gitblit Next (SNAPSHOT)" src="https://next-gitblit.rhcloud.com" /> <divider /> <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> <divider /> </structure> <replace token="%GCURL%" value="${gc.url}" /> <properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.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>" /> <!-- Set the logo from the mx:doc resources --> <logo file="${project.resources.dir}/gitblt_25_white.png" /> <favicon file="${project.resources.dir}/gitblt-favicon.png" /> <resource> <fileset dir="${project.resources.dir}"> <include name="lock_go_16x16.png" /> <include name="lock_pull_16x16.png" /> <include name="shield_16x16.png" /> <include name="cold_16x16.png" /> <include name="bug_16x16.png" /> <include name="book_16x16.png" /> <include name="blank.png" /> <include name="federated_16x16.png" /> <include name="arrow_page.png" /> </fileset> </resource> </mx:doc> <!-- Copy Fancybox --> <mkdir dir="${project.site.dir}/fancybox" /> <copy todir="${project.site.dir}/fancybox"> <fileset dir="${basedir}/docs/fancybox"> <exclude name="thumbs.db" /> </fileset> </copy> <!-- Copy google-code-prettify --> <mkdir dir="${basedir}/src/com/gitblit/wicket/pages/prettify" /> <copy todir="${project.site.dir}/prettify"> <fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify"> <mkdir dir="${project.siteTargetDirectory}/fancybox" /> <copy todir="${project.siteTargetDirectory}/fancybox"> <fileset dir="${project.siteSourceDirectory}/fancybox"> <exclude name="thumbs.db" /> </fileset> </copy> <!-- Generate thumbnails of screenshots --> <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildThumbnails"> <classpath refid="master-classpath" /> <arg value="--sourceFolder" /> <arg value="${basedir}/docs/screenshots" /> <arg value="--destinationFolder" /> <arg value="${project.site.dir}/thumbs" /> <arg value="--maximumDimension" /> <arg value="250" /> </java> <mx:thumbs input="png" output="png" maximumDimension="250" sourceDir="${project.siteSourceDirectory}/screenshots" destDir="${project.siteTargetDirectory}/thumbs" /> <!-- Copy screenshots --> <mkdir dir="${project.site.dir}/screenshots" /> <copy todir="${project.site.dir}/screenshots"> <fileset dir="${basedir}/docs/screenshots"> <mkdir dir="${project.siteTargetDirectory}/screenshots" /> <copy todir="${project.siteTargetDirectory}/screenshots"> <fileset dir="${project.siteSourceDirectory}/screenshots"> <include name="*.png" /> </fileset> </copy> <!-- Build site pages --> <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildSite"> <classpath refid="master-classpath" /> <arg value="--sourceFolder" /> <arg value="${basedir}/docs" /> <arg value="--outputFolder" /> <arg value="${project.site.dir}" /> <arg value="--pageHeader" /> <arg value="${basedir}/docs/site_header.html" /> <arg value="--pageFooter" /> <arg value="${basedir}/docs/site_footer.html" /> <arg value="--analyticsSnippet" /> <arg value="${basedir}/docs/site_analytics.html" /> <arg value="--adSnippet" /> <arg value="${basedir}/docs/site_ads.html" /> <arg value="--alias" /> <arg value="index=overview" /> <arg value="--alias" /> <arg value="properties=settings" /> <arg value="--substitute" /> <arg value="%VERSION%=${gb.version}" /> <arg value="--substitute" /> <arg value="%GO%=${distribution.zipfile}" /> <arg value="--substitute" /> <arg value="%WAR%=${distribution.warfile}" /> <arg value="--substitute" /> <arg value="%FEDCLIENT%=${fedclient.zipfile}" /> <arg value="--substitute" /> <arg value="%MANAGER%=${manager.zipfile}" /> <arg value="--substitute" /> <arg value="%API%=${gbapi.zipfile}" /> <arg value="--substitute" /> <arg value="%EXPRESS%=${express.zipfile}" /> <arg value="--substitute" /> <arg value="%BUILDDATE%=${gb.versionDate}" /> <arg value="--substitute" /> <arg value="%JGIT%=${jgit.version}" /> <arg value="--properties" /> <arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" /> <arg value="--nomarkdown" /> <arg value="%BEGINCODE%:%ENDCODE%" /> <arg value="--substitute" /> <arg value=""%BEGINCODE%=<pre class='prettyprint lang-java'>"" /> <arg value="--substitute" /> <arg value="%ENDCODE%=</pre>" /> <arg value="--regex" /> <arg value=""\b(issue)(\s*[#]?|-){0,1}(\d+)\b!!!<a href='http://code.google.com/p/gitblit/issues/detail?id=$3'>issue $3</a>"" /> </java> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compile from source, publish binaries, and build & deploy site Build all binaries and site ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="buildAll" depends="buildAuthority,buildGO,buildWAR,buildExpress,buildFederationClient,buildManager,buildApiLibrary,buildSite"> <!-- Cleanup --> <delete dir="${project.build.dir}" /> <delete dir="${project.war.dir}" /> <delete dir="${project.deploy.dir}" /> <delete dir="${project.express.dir}" /> </target> <target name="buildAll" depends="buildAuthority,buildGO,buildWAR,buildExpress,buildFederationClient,buildManager,buildApiLibrary,buildSite" /> <!-- @@ -1172,18 +815,9 @@ Update the gh-pages branch with the current site ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="updateGhPages" depends="buildSite"> <target name="updateGhPages"> <!-- Build gh-pages branch --> <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildGhPages"> <classpath refid="master-classpath" /> <arg value="--sourceFolder" /> <arg value="${basedir}/target/site" /> <arg value="--repository" /> <arg value="${basedir}" /> <arg value="--obliterate" /> </java> <mx:ghpages repositorydir="${basedir}" obliterate="true" /> </target> @@ -1192,69 +826,72 @@ Publish binaries to Google Code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="publishBinaries" depends="buildGO,buildWAR,buildExpress,buildFederationClient,buildManager,buildApiLibrary" description="Publish the Gitblit binaries to Google Code"> <target name="publishBinaries" depends="prepare" description="Publish the Gitblit binaries to Google Code"> <echo>Uploading Gitblit ${gb.version} binaries</echo> <echo>Uploading Gitblit ${project.version} binaries</echo> <!-- Upload Gitblit GO ZIP file --> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.target.dir}/${distribution.zipfile}" targetfilename="gitblit-${gb.version}.zip" summary="Gitblit GO v${gb.version} (standalone, integrated Gitblit server)" labels="Featured, Type-Package, OpSys-All" /> <!-- Upload Gitblit GO Windows ZIP file --> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${distribution.zipfile}" targetFilename="gitblit-${project.version}.zip" summary="Gitblit GO v${project.version} (standalone, integrated Gitblit server for Windows)" /> <!-- Upload Gitblit GO Linux/Unix tar.gz file --> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${distribution.tgzfile}" targetFilename="gitblit-${project.version}.tar.gz" summary="Gitblit GO v${project.version} (standalone, integrated Gitblit server for Linux/Unix)" /> <!-- Upload Gitblit WAR file --> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.target.dir}/${distribution.warfile}" targetfilename="gitblit-${gb.version}.war" summary="Gitblit WAR v${gb.version} (standard WAR webapp for servlet containers)" labels="Featured, Type-Package, OpSys-All" /> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${distribution.warfile}" targetFilename="gitblit-${project.version}.war" summary="Gitblit WAR v${project.version} (standard WAR webapp for servlet containers)" /> <!-- Upload Gitblit FedClient --> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.target.dir}/${fedclient.zipfile}" targetfilename="fedclient-${gb.version}.zip" summary="Gitblit Federation Client v${gb.version} (command-line tool to clone data from federated Gitblit instances)" labels="Featured, Type-Package, OpSys-All" /> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${fedclient.zipfile}" targetFilename="fedclient-${project.version}.zip" summary="Gitblit Federation Client v${project.version} (command-line tool to clone data from federated Gitblit instances)" /> <!-- Upload Gitblit Manager --> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.target.dir}/${manager.zipfile}" targetfilename="manager-${gb.version}.zip" summary="Gitblit Manager v${gb.version} (Swing tool to remotely administer a Gitblit server)" labels="Featured, Type-Package, OpSys-All" /> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${manager.zipfile}" targetFilename="manager-${project.version}.zip" summary="Gitblit Manager v${project.version} (Swing tool to remotely administer a Gitblit server)" /> <!-- Upload Gitblit API Library --> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.target.dir}/${gbapi.zipfile}" targetfilename="gbapi-${gb.version}.zip" summary="Gitblit API Library v${gb.version} (JSON RPC library to integrate with your software)" labels="Featured, Type-Package, OpSys-All" /> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${gbapi.zipfile}" targetFilename="gbapi-${project.version}.zip" summary="Gitblit API Library v${project.version} (JSON RPC library to integrate with your software)" /> <!-- Upload Gitblit Express for RedHat OpenShift --> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.target.dir}/${express.zipfile}" targetfilename="express-${gb.version}.zip" summary="Gitblit Express v${gb.version} (run Gitblit on RedHat's OpenShift cloud)" labels="Featured, Type-Package, OpSys-All" /> <mx:gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/${express.zipfile}" targetFilename="express-${project.version}.zip" summary="Gitblit Express v${project.version} (run Gitblit on RedHat's OpenShift cloud)" /> </target> @@ -1262,33 +899,262 @@ <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Publish site to site hosting service You must add ext/commons-net-1.4.0.jar to your ANT classpath. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="publishSite" depends="buildSite,updateGhPages" description="Publish the Gitblit site to a webserver (requires ext/commons-net-1.4.0.jar)" > <target name="publishSite" depends="clean,buildSite,updateGhPages" description="Publish the Gitblit site to a host" > <echo>Uploading Gitblit ${gb.version} website</echo> <echo>Uploading Gitblit ${project.version} website</echo> <ftp server="${ftp.server}" <mx:ftp server="${ftp.server}" userid="${ftp.user}" password="${ftp.password}" remotedir="${ftp.dir}" passive="true" verbose="yes"> <fileset dir="${project.site.dir}" /> </ftp> <fileset dir="${project.siteTargetDirectory}" /> </mx:ftp> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Compile from source, publish binaries, and build & deploy site Tag a new version and prepare for the next development cycle. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="tagRelease" depends="prepare"> <!-- release --> <property name="dryrun" value="false" /> <mx:version stage="release" dryrun="${dryrun}" /> <property name="project.tag" value="v${project.version}" /> <!-- commit build.moxie & releases.moxie (automatic) --> <mx:commit showtitle="no"> <message>Prepare ${project.version} release</message> <tag name="${project.tag}"> <message>${project.name} ${project.version} release</message> </tag> </mx:commit> <!-- create the release process script --> <mx:if> <os family="windows" /> <then> <!-- Windows PowerShell script --> <!-- set-executionpolicy remotesigned --> <property name="recipe" value="release_${project.version}.ps1" /> </then> <else> <!-- Bash script --> <property name="recipe" value="release_${project.version}.sh" /> </else> </mx:if> <delete file="${recipe}" failonerror="false" quiet="true" verbose="false" /> <!-- Work-around for lack of proper ant property substitution in copy --> <property name="dollar" value="$"/> <copy file="release.template" tofile="${recipe}"> <filterset begintoken="${dollar}{" endtoken="}"> <filter token="project.version" value="${project.version}" /> <filter token="project.commitId" value="${project.commitId}" /> <filter token="project.tag" value="${project.tag}" /> </filterset> </copy> <chmod file="${recipe}" perm="ugo+rx" /> <!-- next cycle --> <mx:version stage="snapshot" incrementNumber="incremental" dryrun="${dryrun}" /> <mx:commit showtitle="no"> <message>Reset build identifiers for next development cycle</message> </mx:commit> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build Gitblit Docs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="publishAll" depends="publishBinaries,publishSite"> <!-- Cleanup --> <delete dir="${project.build.dir}" /> <delete dir="${project.war.dir}" /> <delete dir="${project.deploy.dir}" /> <macrodef name="generateDocs"> <attribute name="toDir"/> <sequential> <mx:doc toDir="@{toDir}" minify="true" customless="custom.less"> <structure> <menu name="about"> <page name="overview" src="siteindex.mkd" out="index.html" headerLinks="false" /> <page name="features" src="features.mkd" /> </menu> <menu name="documentation"> <menu name="Gitblit GO" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <page name="setup GO" src="setup_go.mkd" /> <page name="upgrade GO" src="upgrade_go.mkd" /> </menu> <divider /> <menu name="Gitblit WAR" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <page name="setup WAR" src="setup_war.mkd" /> <page name="upgrade WAR" src="upgrade_war.mkd" /> </menu> <divider /> <menu name="Gitblit Express" pager="true" pagerPlacement="bottom" pagerLayout="justified"> <page name="setup Express" src="setup_express.mkd" /> <page name="upgrade Express" src="upgrade_express.mkd" /> </menu> <divider /> <page name="administration" src="administration.mkd" /> <page name="authentication" src="setup_authentication.mkd" /> <page name="push hooks" src="setup_hooks.mkd" /> <page name="lucene indexing" src="setup_lucene.mkd" /> <page name="reverse proxies" src="setup_proxy.mkd" /> <page name="client app menus" src="setup_clientmenus.mkd" /> <divider /> <page name="Gitblit as a viewer" src="setup_viewer.mkd" /> <divider /> <page name="git client setup" src="setup_client.mkd" /> <divider /> <page name="federation" src="federation.mkd" /> <divider /> <page name="settings" src="properties.mkd" /> <page name="faq" src="faq.mkd" /> <divider /> <page name="design" src="design.mkd" /> <page name="rpc" src="rpc.mkd" /> </menu> <menu name="changelog"> <page name="current release" src="releasecurrent.mkd" /> <page name="older releases" src="releasehistory.mkd" /> </menu> <menu name="links"> <link name="Gitblit Demo (RELEASE)" src="https://demo-gitblit.rhcloud.com" /> <link name="Gitbilt Next (SNAPSHOT)" src="https://next-gitblit.rhcloud.com" /> <divider /> <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" /> <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>" /> <!-- Set the logo from the mx:doc resources --> <logo file="${project.resources.dir}/gitblt_25_white.png" /> <favicon file="${project.resources.dir}/gitblt-favicon.png" /> <resource> <fileset dir="${project.resources.dir}"> <include name="lock_go_16x16.png" /> <include name="lock_pull_16x16.png" /> <include name="shield_16x16.png" /> <include name="cold_16x16.png" /> <include name="bug_16x16.png" /> <include name="book_16x16.png" /> <include name="blank.png" /> <include name="federated_16x16.png" /> <include name="arrow_page.png" /> </fileset> </resource> </mx:doc> </sequential> </macrodef> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Macro to create a pristine data directory for the target build ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <macrodef name="prepareDataDirectory"> <attribute name="toDir"/> <sequential> <mkdir dir="@{toDir}" /> <copy todir="@{toDir}" overwrite="false"> <fileset dir="${project.distrib.dir}/data"> <include name="users.conf" /> <include name="projects.conf" /> <include name="gitblit.properties" /> </fileset> </copy> <mkdir dir="@{toDir}/git" /> <copy todir="@{toDir}/git" overwrite="false"> <fileset dir="${project.distrib.dir}/data/git"> <include name="project.mkd" /> </fileset> </copy> <mkdir dir="@{toDir}/groovy" /> <copy todir="@{toDir}/groovy"> <fileset dir="${project.distrib.dir}/data/groovy"> <include name="sendmail.groovy" /> <include name="sendmail-html.groovy" /> <include name="jenkins.groovy" /> <include name="protect-refs.groovy" /> <include name="fogbugz.groovy" /> <include name="thebuggenie.groovy" /> </fileset> </copy> </sequential> </macrodef> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Macro to upload binaries to GoogleCode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <macrodef name="googleUpload"> <attribute name="sourceFile"/> <attribute name="targetFile"/> <attribute name="description"/> <sequential> <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="gitblit" filename="${project.targetDirectory}/@{sourceFile}" targetfilename="@{targetFile}" summary="@{description}" labels="Featured, Type-Package, OpSys-All" /> </sequential> </macrodef> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Install Gitblit JAR for usage as Maven module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="installMaven" depends="compile" description="Install Gitblit JAR as Maven module"> <local name="project.jar" /> <property name="project.jar" value="${project.outputDirectory}/gitblit.jar" /> <property name="resourceFolderPrefix" value="" /> <mx:jar destfile="${project.jar}" includeresources="true" resourceFolderPrefix="${resourceFolderPrefix}" /> <exec executable="mvn"> <arg value="install:install-file" /> <arg value="-Dfile=${project.jar}" /> <arg value="-DpomFile=${basedir}/pom.xml" /> <arg value="-DcreateChecksum=true" /> </exec> </target> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Upload Gitblit JAR to remote Maven repository build.properties: project.maven.repo.url = http://whatever.com/maven2 project.maven.repo.id = whateverId ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> <target name="uploadMaven" depends="compile" description="Upload Gitblit JAR to remote Maven repository"> <local name="project.jar" /> <property name="project.jar" value="${project.outputDirectory}/gitblit.jar" /> <mx:jar destfile="${project.jar}" includeresources="true" /> <exec executable="mvn"> <arg value="deploy:deploy-file" /> <arg value="-Dfile=${project.jar}" /> <arg value="-DpomFile=${basedir}/pom.xml" /> <arg value="-Durl=${project.maven.repo.url}" /> <arg value="-DrepositoryId=${project.maven.repo.id}" /> <arg value="-DcreateChecksum=true" /> </exec> </target> </project> distrib/add-indexed-branch.cmd
File was deleted distrib/authority.cmd
File was deleted distrib/federation.properties
File was deleted distrib/gitblit
File was deleted distrib/gitblit-stop.cmd
File was deleted distrib/gitblit.cmd
File was deleted distrib/gitblit.properties
File was deleted distrib/groovy/sendmail-html.groovy
File was deleted distrib/install-service-centos.sh
File was deleted distrib/install-service.sh
File was deleted distrib/installService.cmd
File was deleted docs/00_index.mkd
File was deleted docs/01_features.mkd
File was deleted docs/01_screenshots.mkd
File was deleted docs/01_setup.mkd
File was deleted docs/02_federation.mkd
File was deleted docs/02_rpc.mkd
File was deleted docs/03_faq.mkd
File was deleted docs/04_design.mkd
File was deleted docs/04_releases.mkd
File was deleted docs/05_roadmap.mkd
File was deleted docs/doc_footer.html
File was deleted docs/doc_header.html
File was deleted docs/site_footer.html
File was deleted docs/site_header.html
File was deleted gitblit.iml
@@ -5,9 +5,9 @@ <output-test url="file://$MODULE_DIR$/bin/test-classes" /> <exclude-output /> <content url="file://$MODULE_DIR$"> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/resources" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" /> </content> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="module-library"> @@ -17,7 +17,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jcommander-1.17-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jcommander-1.17.jar!/" /> </SOURCES> </library> </orderEntry> @@ -28,7 +28,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/log4j-1.2.17-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/log4j-1.2.17.jar!/" /> </SOURCES> </library> </orderEntry> @@ -39,7 +39,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.6.6-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.6.6.jar!/" /> </SOURCES> </library> </orderEntry> @@ -50,7 +50,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.6.6-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.6.6.jar!/" /> </SOURCES> </library> </orderEntry> @@ -61,7 +61,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/mail-1.4.3-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/mail-1.4.3.jar!/" /> </SOURCES> </library> </orderEntry> @@ -72,7 +72,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/javax.servlet-api-3.0.1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/javax.servlet-api-3.0.1.jar!/" /> </SOURCES> </library> </orderEntry> @@ -83,7 +83,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jetty-webapp-7.6.8.v20121106-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jetty-webapp-7.6.8.v20121106.jar!/" /> </SOURCES> </library> </orderEntry> @@ -94,7 +94,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jetty-ajp-7.6.8.v20121106-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jetty-ajp-7.6.8.v20121106.jar!/" /> </SOURCES> </library> </orderEntry> @@ -105,7 +105,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21.jar!/" /> </SOURCES> </library> </orderEntry> @@ -116,7 +116,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21.jar!/" /> </SOURCES> </library> </orderEntry> @@ -127,7 +127,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21.jar!/" /> </SOURCES> </library> </orderEntry> @@ -138,7 +138,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/googlecharts-1.4.21-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/googlecharts-1.4.21.jar!/" /> </SOURCES> </library> </orderEntry> @@ -149,7 +149,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/lucene-core-3.6.1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/lucene-core-3.6.1.jar!/" /> </SOURCES> </library> </orderEntry> @@ -160,7 +160,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-3.6.1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-3.6.1.jar!/" /> </SOURCES> </library> </orderEntry> @@ -171,7 +171,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-3.6.1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-3.6.1.jar!/" /> </SOURCES> </library> </orderEntry> @@ -182,7 +182,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-3.6.1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-3.6.1.jar!/" /> </SOURCES> </library> </orderEntry> @@ -202,40 +202,51 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/markdownpapers-core-1.3.2-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/markdownpapers-core-1.3.2.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="org.eclipse.jgit-2.2.0.201212191850-r.jar"> <library name="org.eclipse.jgit-3.0.0.201306101825-r.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-2.2.0.201212191850-r.jar!/" /> <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-3.0.0.201306101825-r.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-2.2.0.201212191850-r-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-3.0.0.201306101825-r.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="jsch-0.1.44-1.jar"> <library name="jsch-0.1.46.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/jsch-0.1.44-1.jar!/" /> <root url="jar://$MODULE_DIR$/ext/jsch-0.1.46.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.44-1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.46.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar"> <library name="JavaEWAH-0.5.6.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar!/" /> <root url="jar://$MODULE_DIR$/ext/JavaEWAH-0.5.6.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-2.2.0.201212191850-r-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/JavaEWAH-0.5.6.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar!/" /> </SOURCES> </library> </orderEntry> @@ -246,7 +257,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.47-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.47.jar!/" /> </SOURCES> </library> </orderEntry> @@ -257,7 +268,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.47-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.47.jar!/" /> </SOURCES> </library> </orderEntry> @@ -268,7 +279,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.47-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.47.jar!/" /> </SOURCES> </library> </orderEntry> @@ -279,7 +290,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/rome-0.9-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/rome-0.9.jar!/" /> </SOURCES> </library> </orderEntry> @@ -290,7 +301,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jdom-1.0-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/jdom-1.0.jar!/" /> </SOURCES> </library> </orderEntry> @@ -301,7 +312,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2.jar!/" /> </SOURCES> </library> </orderEntry> @@ -312,7 +323,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8.jar!/" /> </SOURCES> </library> </orderEntry> @@ -323,7 +334,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0.jar!/" /> </SOURCES> </library> </orderEntry> @@ -334,7 +345,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/ivy-2.2.0-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/ivy-2.2.0.jar!/" /> </SOURCES> </library> </orderEntry> @@ -354,7 +365,7 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/commons-compress-1.4.1-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/commons-compress-1.4.1.jar!/" /> </SOURCES> </library> </orderEntry> @@ -365,28 +376,261 @@ </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/xz-1.0-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/xz-1.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="junit-4.10.jar"> <orderEntry type="module-library"> <library name="force-partner-api-24.0.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/junit-4.10.jar!/" /> <root url="jar://$MODULE_DIR$/ext/force-partner-api-24.0.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/junit-4.10-sources.jar!/" /> <root url="jar://$MODULE_DIR$/ext/src/force-partner-api-24.0.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="force-wsc-24.0.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/force-wsc-24.0.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/force-wsc-24.0.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="js-1.7R2.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/js-1.7R2.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/js-1.7R2.jar!/" /> </SOURCES> </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"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.5.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.5.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="platform-3.5.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/platform-3.5.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/platform-3.5.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="jna-3.5.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/jna-3.5.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/jna-3.5.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library"> <library name="guava-13.0.1.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/guava-13.0.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/guava-13.0.1.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="hamcrest-core-1.1.jar"> <library name="junit-4.11.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/hamcrest-core-1.1.jar!/" /> <root url="jar://$MODULE_DIR$/ext/junit-4.11.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/junit-4.11.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="hamcrest-core-1.3.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/hamcrest-core-1.3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/hamcrest-core-1.3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="selenium-java-2.28.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/selenium-java-2.28.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/selenium-java-2.28.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="selenium-support-2.28.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/selenium-support-2.28.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/selenium-support-2.28.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="selenium-firefox-driver-2.28.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/selenium-firefox-driver-2.28.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/selenium-firefox-driver-2.28.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="selenium-remote-driver-2.28.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/selenium-remote-driver-2.28.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/selenium-remote-driver-2.28.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="cglib-nodep-2.1_3.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/cglib-nodep-2.1_3.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/cglib-nodep-2.1_3.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="json-20080701.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/json-20080701.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/json-20080701.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="selenium-api-2.28.0.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/selenium-api-2.28.0.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/selenium-api-2.28.0.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="httpclient-4.2.1.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/httpclient-4.2.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.2.1.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="httpcore-4.2.1.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/httpcore-4.2.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.2.1.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="commons-logging-1.1.1.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.1.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="commons-codec-1.6.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/commons-codec-1.6.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/commons-codec-1.6.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="commons-exec-1.1.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/commons-exec-1.1.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/commons-exec-1.1.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="module-library" scope="TEST"> <library name="commons-io-2.2.jar"> <CLASSES> <root url="jar://$MODULE_DIR$/ext/commons-io-2.2.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> <root url="jar://$MODULE_DIR$/ext/src/commons-io-2.2.jar!/" /> </SOURCES> </library> </orderEntry> <orderEntry type="inheritedJdk" /> release.template
New file @@ -0,0 +1,52 @@ #!/bin/bash # # ${project.version} release script # # go back one commit to RELEASE commit echo "" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Checking out ${project.version} RELEASE commit ${project.commitId}" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "" git checkout ${project.commitId} # build RELEASE artifacts echo "" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Building ${project.version} RELEASE artifacts" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "" ant clean buildAll # upload artifacts echo "" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Uploading ${project.version} artifacts" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "" ant publishBinaries # build site, update gh-pages, and ftp upload site to hosting provider echo "" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Building ${project.version} website" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "" ant publishSite # return to project master echo "" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Checking out master" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "" git checkout master # push project branches echo "" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Pushing master, gh-pages, and tag ${project.tag}" echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "" git push origin master gh-pages tag ${project.tag} releases.moxie
New file @@ -0,0 +1,920 @@ # # ${project.version} release # r18: { title: ${project.name} ${project.version} released id: ${project.version} date: ${project.buildDate} note: '' If you have forked repositories and your are upgrading from 1.2.x to 1.3.x, please DO NOT RELOCATE your repositories folder when running 1.3.x the first time. Gitblit will update forked repository configs on the first execution and it is critical that ${git.repositoriesFolder} points to the same location used by 1.2.x. '' html: ~ text: ~ security: ~ fixes: - Gitblit-as-viewer with no repository urls failed to display summary page (issue 269) - Fixed missing model class dependencies in Gitblit Manager build - Fix for IE10 compatibility mode - Reset dashboard and activity commit cache on branch REWIND or DELETE - Fixed bug with adding new local users with external authentication - Fixed missing clone url on the empty repository page changes: - updated Chinese translation - updated Dutch translation - updated Spanish translation - updated Korean translation additions: - Added optional browser-side page caching using Last-Modified and Cache-Control for the dashboard, activity, project, and several repository pages dependencyChanges: ~ settings: - { name: 'web.pageCacheExpires', defaultValue: 0 } contributors: - Rainer Alföldi - Liyu Wang - Jeroen Baten - James Moger - Stardrad Yin - Chad Horohoe - Eduardo Guervós Narvaez - Dongsu, KIM } # # 1.3.0 # r17: { title: Gitblit 1.3.0 Released id: 1.3.0 date: 2013-07-14 html: '' Release highlights include: <ul> <li>integrated git daemon</li> <li>compare refs or commits page</li> <li>completed the Gitblit reflog (formerly pushlog) introduced in 1.2.1</li> <li>added new dashboard pages</li> <li>added a stars feature</li> <li>improved the repository url panel to show your access permission and to offer native app clone links</li> <li>improved navigation and theme</li> <li>customizable page header colors and logo</li> <li>recent activity commit caching to improve performance of dashboard and activity pages</li> <li>Windows authentication</li> <li>Salesforce.com authentication</li> <li>lots of bug fixes</li> </ul> <p> </p> Thank you to <a href="http://syntevo.com">syntevo</a>, <a href="http://atlassian.com">Atlassian</a>, <a href="http://fournova.com">fournova</a>, and <a href="http://github.com">Github</a> for their permission and use of their artwork for the native app clone menus. '' note: '' If you have forked repositories and your are upgrading to 1.3.0, please DO NOT RELOCATE your repositories folder when running 1.3.0 the first time. Gitblit will update forked repository configs on the first execution and it is critical that ${git.repositoriesFolder} points to the same location used by 1.2.x. '' security: - Raw servlet was insecure. If someone knew the exact repository name and path to a file, the raw blob could be retrieved bypassing security constraints. (issue 198) fixes: - Use bash instead of sh in Linux/OSX shell scripts (issue 154) - Fix NPE when getting user's fork without repository list caching (issue 182) - Fix internal error on folder history links (issue 192) - Fix NPE in repositories panel when viewing a federation proposal (issue 195) - Fix NPEs when initializing the context on a servlet containers which returns a null contextFolder (issue 199) - Fixed incorrect icon file name for .doc files (issue 200) - Do not queue emails with no recipients (issue 201) - Disable view and blame links for deleted blobs (issue 216) - Fixed 1.2.x regression with individually symlinked repositories (issue 217) - Fixed UTF-8 encoding errors in email notifications (issue 218) - Fixed NPE in 1.2.1 Federation Client (issue 219) - Fixed extracting Groovy scripts on Express installs (issue 220) - Ensure Redmine url is properly formatted (issue 223) - Use standard ServletRequestWrapper instead of custom wrapper (issue 224) - Switch commit message back to a pre and ensure that it is properly escaped when combined with commit message regex substitution (issue 242) - Fixed AddIndexedBranch tool --branch parameter (issue 247) - Improve NPE handling for hook script enumeration (issue-253) - Workaround missing commit information in blame page (JGit bug 374382, issue-254) - Ignore orphan ".git" folder in the repositories root folder (issue-256) - Fixed bug where a null permission was added to a user model on a repository rename when the permission had really been inherited from a team membership (issue-259) - Fixed committer verification with merge commits (issue-264) - Fixed bug in submodule repository linking (issue-266) - Could not reset settings with $ or { characters through Gitblit Manager because they are not properly escaped - Added more error checking to blob page and blame page - Disable SNI extensions for client SSL connections - Fixed prettify language extension loading - Fixed index out of bounds exceptions when generating client certificates for a user when the user's table has been filtered - Fixed AddindexedBranch tool when specifying the non-default branch. - Fixed submodule diff display changes: - Retrieve summary and metric graphs from Google over https (issue-61) - Persist originRepository (for forks) in the repository config instead of relying on parsing origin urls which are susceptible to filesystem relocation (issue 190) - Improved error logging for servlet containers which provide a null contextFolder (issue 199) - Improve Gerrit change ref decoration in the refs panel (issue 206) - Display full commit message on commitdiff page (issue-258) - Improved the repository url display. This display now indicates your repository access permission, per-protocol. - Automatically encode/decode usernames for urls using %XX notation on space, @, and \ - Disable Gson's pretty printing which has a huge performance gain - Properly set application/json content-type on api calls - Make days back filter choices a setting - Changed default days back filter setting to 7 days - Set rel="nofollow" on compressed download links - Improved page title - Updated Polish translation - Updated Japanese translation additions: - Added a ui for the ref log introduced in 1.2.1 (issue-177) - Added weblogic.xml to WAR for deployment on WebLogic (issue 199) - Support setting a custom header logo (issue 208) - Support header color customizations (issue 209) - Support username substitution in web.otherUrls (issue 213) - Option to force client-side basic authentication instead of form-based authentication if web.authenticateViewPages=true (issue 222) - Set author as tooltip of last change column in the repositories panel (issue-238) - Setting to automatically create an user account based on an authenticated user principal from the servlet container (issue-246) - Added WindowsUserService to authenticate users against Windows accounts (issue-250) - Global and per-repository setting to exclude authors from metrics (issue-251) - Added commit cache to improve Activity, Dashboard, and Project page generation times - Added SalesForce.com user service - Added simple star/unstar function to flag or bookmark interesting repositories - Added Dashboard page which shows a news feed for starred repositories and offers a filterable list of repositories you care about - Added client application menus for Git, SmartGit/Hg, SourceTree, Tower, GitHub for Windows, and GitHub for Mac - Added GO http/https connector thread pool size setting - Added a server setting to force a particular translation/Locale for all sessions - Added smart Git Daemon serving. If enabled, git:// access will be offered for any repository which permits anonymous access. If the repository permits anonymous cloning, anonymous git:// clone will be permitted while anonmymous git:// pushes will be rejected. - Option to automatically tag branch tips on each push with an incremental revision number - Implemented multiple repository owners - Optional periodic LDAP user and team pre-fetching & synchronization - Added config setting to use SMTPS - Added option to index all local branches in AddIndexedBranches tool - Display name and version in Tomcat Manager - FogBugz post-receive hook script - Chinese translation - Support --baseFolder parameter in Federation Client contributors: - James Moger - Bandarupalli Satyanarayana - Chad Horohoe - Christian Aistleitner - Colin Bowern - David Ostrovsky - Egbert Teeselink - Hige Maniya - Hirotaka Honma - Ikslawek - Jay Meyer - John Crygier - Kensuke Matsuzaki - Laurens Vrijnsen - Lee Grofit - Lukasz Jader - Martijn Laan - Matthias Bauer - Michael Pailloncy - Michael Schaefers - Oliver Doepner - Philip Boutros - Rafael Cavazin - Ryan Schneider - Sakurai Youhei - Sarah Haselbauer - Slawomir Bochenski - Stardrad Yin - Thomas Pummer - William Whittle - Yukihiko Sawanobori - github/akquinet - github/dapengme dependencyChanges: - JGit 3.0.0.201306101825-r - Iconic font - AngularJS 1.0.7 - FreeMarker 2.3.19 - Waffle 1.5 - JNA 3.5.0 - Guava 13.0.1 settings: - { name: 'git.daemonBindInterface', defaultValue: 'localhost' } - { name: 'git.daemonPort', defaultValue: 0 } - { name: 'git.defaultIncrementalPushTagPrefix', defaultValue: 'r' } - { name: 'mail.smtps', defaultValue: 'false' } - { name: 'realm.container.autoCreateAccounts', defaultValue: 'false' } - { name: 'realm.salesforce.backingUserService', defaultValue: 'users.conf' } - { name: 'realm.salesforce.orgId', defaultValue: 0 } - { name: 'realm.windows.defaultDomain', defaultValue: ' ' } - { name: 'realm.windows.backingUserService', defaultValue: 'users.conf' } - { name: 'web.activityDuration', defaultValue: 7 } - { name: 'web.activityDurationChoices', defaultValue: '1 3 7 14 21 28' } - { name: 'web.activityCacheDays', defaultValue: 14 } - { name: 'web.allowAppCloneLinks', defaultValue: 'true' } - { name: 'web.forceDefaultLocale', defaultValue: ' ' } - { name: 'web.headerLogo', defaultValue: '${baseFolder}/logo.png' } - { name: 'web.headerBackgroundColor', defaultValue: ' ' } - { name: 'web.headerForegroundColor', defaultValue: ' ' } - { name: 'web.headerHoverColor', defaultValue: ' ' } - { name: 'web.headerBorderColor', defaultValue: ' ' } - { name: 'web.headerBorderFocusColor', defaultValue: ' ' } - { name: 'web.metricAuthorExclusions', defaultValue: ' ' } - { name: 'web.overviewReflogCount', defaultValue: 5 } - { name: 'web.reflogChangesPerPage', defaultValue: 10 } - { name: 'server.nioThreadPoolSize', defaultValue: 50 } } # # 1.2.1 # r16: { title: Gitblit 1.2.1 Released id: 1.2.1 date: 2013-01-15 html: '' Because there are now several types of files and folders that must be considered Gitblit data, the default location for data has changed. <p /> You will need to move a few files around when upgrading. Please review the <a href="upgrade_go.html">upgrading GO</a> or <a href="upgrade_war.html">upgrading WAR</a> page for details. <p /> <b>Express Users</b> make sure to update your web.xml file with the ${baseFolder} values! '' fixes: - Fixed nullpointer on recursively calculating folder sizes when there is a named pipe or symlink in the hierarchy - Added nullchecking when concurrently forking a repository and trying to display the fork network (issue-187) - Fixed bug where permission changes were not visible in the web ui to a logged-in user until the user logged-out and then logged back in again (issue-186) - Fixed nullpointer on creating a repository with mixed case (issue 185) - Include missing model classes in api library (issue-184) - Fixed nullpointer when using *web.allowForking = true* && *git.cacheRepositoryList = false* (issue 182) - Likely fix for commit and commitdiff page failures when a submodule reference changes (issue 178) - Build project models from the repository model cache, when possible, to reduce page load time (issue 172) - Fixed loading of Brazilian Portuguese translation from *nix server additions: - ''Fanout PubSub service for self-hosted [Sparkleshare](http://sparkleshare.org) notifications. This service is disabled by default.'' - ''Implemented a simple push log based on a hidden, orphan branch refs/gitblit/pushes (issue 177) The push log is not currently visible in the ui, but the data will be collected and it will be exposed to the ui in the next release.'' - Support for locally and remotely authenticated accounts in LdapUserService and RedmineUserService (issue 183) - Added Dutch translation changes: - ''Gitblit GO and Gitblit WAR are now both configured by `gitblit.properties`. WAR is no longer configured by `web.xml`. However, Express for OpenShift continues to be configured by `web.xml`.'' - Support for a *--baseFolder* command-line argument for Gitblit GO and Gitblit Certificate Authority - Support for specifying a *${baseFolder}* parameter in `gitblit.properties` and `web.xml` for several settings - Improve history display of a submodule link - Updated Korean translation - Updated checkstyle definition settings: - { name: fanout.bindInterface, defaultValue: localhost } - { name: fanout.port, defaultValue: 0 } - { name: fanout.useNio, defaultValue: 'true' } - { name: fanout.connectionLimit, defaultValue: 0 } contributors: - James Moger - github/mystygage - Dongsu, KIM - Jeroen Baten - github/inaiat } # # 1.2.0 # r15: { title: Gitblit 1.2.0 Released id: 1.2.0 date: 2012-12-31 note: '' The permissions model has changed in the 1.2.0 release. If you are updating your server, you must also update any Gitblit Manager and Federation Client installs to 1.2.0 as well. The data model used by the RPC mechanism has changed slightly for the new permissions infrastructure. '' fixes: - Fixed regression in *isFrozen* (issue 181) - Author metrics can be broken by newlines in email addresses from converted repositories (issue 176) - Set subjectAlternativeName on generated SSL cert if CN is an ip address (issue 170) - Fixed incorrect links on history page for files not in the current/active commit (issue 166) - Empty repository page failed to handle missing repository (issue 160) - Fixed broken ticgit urls (issue 157) - Exclude submodules from zip downloads (issue 151) - Fixed bug where repository ownership was not updated on rename user - Fixed bug in create/rename repository if you explicitly specified the alias for the root group (e.g. main/myrepo) (issue 143) - Wrapped Markdown parser with improved exception handler (issue 142) - Fixed duplicate entries in repository cache (issue 140) - Fixed connection leak in LDAPUserService (issue 139) - Fixed bug in commit page where changes to a submodule threw a null pointer exception (issue 132) - Fixed bug in the diff view for filenames that have non-ASCII characters (issue 128) additions: - '' Implemented discrete repository permissions (issue 36) - V (view in web ui, RSS feeds, download zip) - R (clone) - RW (clone and push) - RWC (clone and push with ref creation) - RWD (clone and push with ref creation, deletion) - RW+ (clone and push with ref creation, deletion, rewind) While not as sophisticated as Gitolite, this does give finer access controls. These permissions fit in cleanly with the existing users.conf and users.properties files. In Gitblit <= 1.1.0, all your existing user accounts have RW+ access. If you are upgrading to 1.2.0, the RW+ access is *preserved* and you will have to lower/adjust accordingly. '' - ''Implemented *case-insensitive* regex repository permission matching (issue 36) This allows you to specify a permission like `RW:mygroup/.*` to grant push privileges to all repositories within the *mygroup* project/folder.'' - Added DELETE, CREATE, and NON-FAST-FORWARD ref change logging - ''Added support for personal repositories. Personal repositories can be created by accounts with the *create* permission and are stored in *git.repositoriesFolder/~username*. Each user with personal repositories will have a user page, something like the GitHub profile page. Personal repositories have all the same features as common repositories, except personal repositories can be renamed by their owner.'' - ''Added support for server-side forking of a repository to a personal repository (issue 137) In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*. The clone inherits the access list of its origin. i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork. This is to facilitate collaboration. The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions. If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit.'' - Added optional *create-on-push* support - Added **experimental** JGit-based garbage collection service. This service is disabled by default. - ''Added support for X509 client certificate authentication. (issue 106) You can require all git servlet access be authenticated by a client certificate. You may also specify the OID fingerprint to use for mapping a certificate to a username. It should be noted that the user account MUST already exist in Gitblit for this authentication mechanism to work; this mechanism can not be used to automatically create user accounts from a certificate.'' - Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores. <u>The store files have been renamed!</u> - Added support for Gitblit GO to require usage of client certificates to access the entire server. - Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication. - Added web.shortCommitId setting to control length of shortened commit ids - Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174) - Added simple project pages. A project is a subfolder off the *git.repositoriesFolder*. - Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135) - Delete branch feature (issue 121) - Added line links to blob view (issue 130) - Added HTML sendmail hook script and Gitblit.sendHtmlMail method - Added RedmineUserService - Support for committer verification. Requires use of *--no-ff* when merging branches or pull requests. See setup page for details. - Added Brazilian Portuguese translation changes: - Added server setting to specify keystore alias for ssl certificate (issue 98) - Added optional global and per-repository activity page commit contribution throttle to help tame *really* active repositories (issue 173) - Added support for symlinks in tree page and commit page (issue 171) - All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order. - Added *groovy* and *scala* to *web.prettyPrintExtensions* - Added short commit id column to log and history tables (issue 168) - Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration - Use https Gravatar urls to avoid browser complaints - Added frm to default pretty print extensions (issue 156) - Expose ReceivePack to Groovy push hooks (issue 125) - Redirect to summary page when refreshing the empty repository page on a repository that is not empty (issue 129) - Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (issue 126) - ''LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list. If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership. Otherwise, User.canAdmin() is controlled by Gitblit.'' - Support servlet container authentication for existing UserModels (issue 68) settings: - { name: web.allowForking, defaultValue: 'true' } - { name: git.allowCreateOnPush, defaultValue: 'true' } - { name: git.allowGarbageCollection, defaultValue: 'false' } - { name: git.garbageCollectionHour, defaultValue: 0 } - { name: git.defaultGarbageCollectionThreshold, defaultValue: 500k } - { name: git.defaultGarbageCollectionPeriod, defaultValue: 7 days } - { name: git.requireClientCertificates, defaultValue: 'false' } - { name: git.enforceCertificateValidity, defaultValue: 'true' } - { name: git.certificateUsernameOIDs, defaultValue: CN } - { name: web.shortCommitIdLength, defaultValue: 8 } - { name: web.compressedDownloads, defaultValue: zip gz } - { name: server.requireClientCertificates, defaultValue: 'false' } dependencyChanges: - Jetty 7.6.8 - JGit 2.2.0.201212191850-r - Groovy 1.8.8 - Wicket 1.4.21 - Lucene 3.6.1 - BouncyCastle 1.47 - MarkdownPapers 1.3.2 - JCalendar 1.3.2 - Commons-Compress 1.4.1 - XZ for Java 1.0 contributors: - James Moger - github/rafaelcavazin - github/mallowlabs - github/sauthieg - github/ajermakovics - github/kevinanderson1 - github/jpyeron } # # 1.1.0 # r14: { title: Gitblit 1.1.0 Released id: 1.1.0 date: 2012-08-25 note: If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories. Please be sure to provide ample heap resources as appropriate for your installation. fixes: - Bypass Wicket's inability to handle direct url addressing of a view-restricted, grouped repository for new, unauthenticated sessions (e.g. click link from email or rss feed without having an active Wicket session) - Fixed MailExecutor's failure to cope with mail server connection troubles resulting in 100% CPU usage - Fixed generated urls in Groovy *sendmail* hook script for grouped repositories - Fixed generated urls in RSS feeds for grouped repositories - Fixed nullpointer exception in git servlet security filter (issue 123) - Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103) - Gitblit could not delete a Lucene index in a working copy on index upgrade - Do not index submodule links (issue 119) - Restore original user or team object on failure to update (issue 118) - Fixes to relative path determination in repository search algorithm for symlinks (issue 116) - Fix to GitServlet to allow pushing to symlinked repositories (issue 116) - Repository URL now uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115) - Output real RAW content, not simulated RAW content (issue 114) - Fixed Lucene charset encoding bug when reindexing a repository (issue 112) - Fixed search box linking to Lucene page for grouped repository on Tomcat (issue 111) - Fixed null pointer in LdapUserSerivce if account has a null email address (issue 110) - Really fixed failure to update a GO setting from the manager (issue 85) additions: - Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103) - Preliminary bare repository submodule support - '' *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url. For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git* **Note:** You may not need this control to work with submodules, but it is there if you do. - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)* - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails. - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository. '' - '' Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117) NAMED is the original behavior for authorizing against a list of permitted users or permitted teams. AUTHENTICATED allows restricted access for any authenticated user. This is a looser authorization control. '' - Added default authorization control setting (AUTHENTICATED or NAMED) - Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103) - Added setting to specify regex exclusions for repositories (issue 103) - Blob page now supports displaying images (issue 6) - Non-image binary files can now be downloaded using the RAW link - Support StartTLS in LdapUserService (issue 122) - Added Korean translation changes: - Line breaks inserted for readability in raw Markdown content display in the event of a parsing/transformation error. An error message is now displayed prepended to the raw content. - Improve UTF-8 reading for Markdown files - Updated Polish translation - Updated Japanese translation - Updated Spanish translation settings: - { name: git.cacheRepositoryList, defaultValue: 'true' } - { name: git.submoduleUrlPatterns, defaultValue: * } - { name: git.searchExclusions, defaultValue: * } - { name: git.searchRecursionDepth, defaultValue: -1 } - { name: git.defaultAuthorizationControl, defaultValue: NAMED } contributors: - James Moger - Steffen Gebert } # # 1.0.0 # r13: { title: Gitblit 1.0.0 Released id: 1.0.0 date: 2012-07-14 fixes: - Fixed bug in Lucene search where old/stale blobs were never properly deleted during incremental updates. This resulted in duplicate blob entries in the index. - Fixed intermittent bug in identifying line numbers in Lucene search (issue 105) - Adjust repository identification algorithm to handle the scenario where a repository name collides with a group/folder name (e.g. foo.git and foo/bar.git) (issue 104) - Fixed bug where a repository set as *authenticated push* did not have anonymous clone access (issue 96) - Fixed bug in Basic authentication if passwords had a colon - Fixed bug where the Gitblit Manager could not update a setting that was not referenced in reference.properties (issue 85) changes: - ''**Updated Lucene index version which will force a rebuild of ALL your Lucene indexes** Make sure to properly set *web.blobEncodings* before starting Gitblit if you are updating! (issue 97)'' - Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101) - ''IUserService interface has changed to better accomodate custom authentication and/or custom authorization< The default `users.conf` now supports persisting display names and email addresses.'' - Updated Japanese translation additions: - Added setting to allow specification of a robots.txt file (issue 99) - ''Added setting to control Responsive layout or Fixed-Width layout (issue 101) Responsive layout is now the default. This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components. It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install.'' - Added setting to control charsets for blob string decoding. Default encodings are UTF-8, ISO-8859-1, and the server default charset. (issue 97) - ''Exposed JGit internal configuration settings in gitblit.properties/web.xml (issue 93) Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.'' - Added default access restriction. Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88) - Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script - ''Added setting to control Groovy Grape root folder (location where resolved dependencies are stored) [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath.'' - Added LDAP User Service with many new *realm.ldap* keys - ''Added support for custom repository properties for Groovy hooks Custom repository properties complement hook scripts by providing text field prompts in the web ui and the Gitblit Manager for the defined properties. This allows your push hooks to be parameterized.'' - Added script to facilitate proxy environment setup on Linux - Added Polish translation - Added Spanish translation settings: - { name: groovy.grapeFolder, defaultValue: groovy/grape } - { name: web.robots.txt, defaultValue: } - { name: web.useResponsiveLayout, defaultValue: 'true' } - { name: web.blobEncodings, defaultValue: UTF-8 ISO-8859-1 } - { name: git.defaultAccessRestriction, defaultValue: NONE } - { name: git.packedGitWindowSize, defaultValue: 8k } - { name: git.packedGitLimit, defaultValue: 10m } - { name: git.deltaBaseCacheLimit, defaultValue: 10m } - { name: git.packedGitOpenFiles, defaultValue: 128 } - { name: git.streamFileThreshold, defaultValue: 50m } - { name: git.packedGitMmap, defaultValue: 'false' } dependencyChanges: - Bootstrap 2.0.4 - JGit 2.0.0.201206130900-r - Groovy 1.8.6 - Gson 1.7.2 - Log4J 1.2.17 - SLF4J 1.6.6 - Apache Commons Daemon 1.0.10 - Ivy 2.2.0 contributors: - James Moger - Eduardo Guervos Narvaez - Lukasz Jader - github/mragab - github/jcrygier - github/zakki - github/peterloron } # # 0.9.3 # r12: { title: Gitblit 0.9.3 Released id: 0.9.3 date: 2012-04-11 fixes: - Fixed bug where you could not remove all selections from a RepositoryModel list (permitted users, permitted teams, hook scripts, federation sets, etc) (issue 81) - Automatically set *java.awt.headless=true* for Gitblit GO contributors: - James Moger } # # 0.9.2 # r11: { title: Gitblit 0.9.2 Released id: 0.9.2 date: 2012-04-04 changes: - Added *clientLogger* bound variable to Groovy hook mechanism to allow custom info and error messages to be returned to the client fixes: - Fixed absolute path/canonical path discrepancy between Gitblit and JGit regarding use of symlinks (issue 78) - Fixed row layout on activity page (issue 79) - Fixed Centos service script - Fixed EditRepositoryPage for IE8; missing save button (issue 80) contributors: - James Moger - github/jonnybbb - github/mohamedmansour - github/jcrygier } # # 0.9.1 # r10: { title: Gitblit 0.9.1 Released id: 0.9.1 date: 2012-03-27 fixes: - Lucene folder was stored in working copy instead of in .git folder contributors: - James Moger } # # 0.9.0 # r9: { title: Gitblit 0.9.0 Released id: 0.9.0 date: 2012-03-27 security: - Fixed session fixation vulnerability where the session identifier was not reset during the login process (issue 62) changes: - Reject pushes to a repository with a working copy (i.e. non-bare repository) (issue-49) - Changed default web.datetimestampLongFormat from *EEEE, MMMM d, yyyy h:mm a z* to *EEEE, MMMM d, yyyy HH:mm Z* (issue 50) - Expanded commit age coloring from 2 days to 30 days (issue 57) additions: - ''Added optional Lucene branch indexing (issue 16) Repository branches may be optionally indexed by Lucene for improved searching. To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_. Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository Lucene index.) If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched.<br/> If the repository does not specify any indexed branches then repository commit-traversal search is used. **Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M).<br/> See the [setup](setup.html) page for additional details.'' - Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54) - Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59) - ''On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49) Push requests to these repositories will be rejected.'' - On all non-bare Repository pages show *WORKING COPY* in the upper right corner (issue 49) - New setting to prevent display/serving non-bare repositories - Added *protect-refs.groovy* - Allow setting default branch (relinking HEAD) to a branch or a tag - Added Ubuntu service init script (issue 72) - Added partial Japanese translation fixes: - Ensure that Welcome message is parsed using UTF-8 encoding (issue 74) - Activity page chart layout broken by Google (issue 73) - Uppercase repositories not selectable in edit palettes (issue 71) - Not all git notes were properly displayed on the commit page (issue 70) - Activity page now displays all local branches (issue 65) - Fixed (harmless) nullpointer on pushing to an empty repository (issue 69) - Fixed possible nullpointer from the servlet container on startup (issue 67) - Fixed UTF-8 encoding bug on diff page (issue 66) - Fixed timezone bugs on the activity page (issue 54) - Prevent add/edit team with no selected repositories (issue 56) - Disallow browser autocomplete on add/edit user/team/repository pages - Fixed username case-sensitivity issues (issue 43) - Disregard searching a subfolder if Gitblit does not have filesystem permissions (issue 51) settings: - { name: web.allowLuceneIndexing, defaultValue: 'true' } - { name: web.luceneIgnoreExtensions, defaultValue: 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip } - { name: web.timezone, defaultValue: } - { name: server.ajpPort, defaultValue: 0 } - { name: server.ajpBindInterface, defaultValue: localhost } - { name: git.onlyAccessBareRepositories, defaultValue: 'false' } dependencyChanges: - Bootstrap 2.0.2 - MarkdownPapers 1.2.7 - JGit 1.3.0.201202151440-r - Wicket 1.4.20 contributors: - James Moger - github/lemval - github/zakki - github/plm } # # 0.8.2 # r8: { title: Gitblit 0.8.2 Released id: 0.8.2 date: 2012-01-13 fixes: - Fixed bug when upgrading from users.properties to users.conf (issue 41) contributors: - James Moger } # # 0.8.1 # r7: { title: Gitblit 0.8.1 Released id: 0.8.1 date: 2012-01-11 fixes: - Include missing icon resource for the manager (issue 40) - Fixed sendmail.groovy message content with incorrect tag/branch labels contributors: - James Moger } # # 0.8.0 # r6: { title: Gitblit 0.8.0 Released id: 0.8.0 date: 2012-01-11 additions: - ''Platform-independent, Groovy push hook script mechanism. Hook scripts can be set per-repository, per-team, or globally for all repositories.'' - ''*sendmail.groovy* for optional email notifications on push. You must properly configure your SMTP server settings in `gitblit.properties` or `web.xml` to use *sendmail.groovy*.'' - New global key for mailing lists. This is used in conjunction with the *sendmail.groovy* hook script. All repositories that use the *sendmail.groovy* script will include these addresses in the notification process. Please see the Setup page for more details about configuring sendmail. - *com.gitblit.GitblitUserService*. This is a wrapper object for the built-in user service implementations. For those wanting to only implement custom authentication it is recommended to subclass GitblitUserService and override the appropriate methods. Going forward, this will help insulate custom authentication from new IUserService API and/or changes in model classes. - ''New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`) This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties). This will open the door for more advanced Gitblit features. For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service. The original `users.properties` file and the corresponding implementation are **deprecated**.'' - Teams for specifying user-repository access in bulk. Teams may also specify mailing lists addresses and pre- & post- receive hook scripts. - Gravatar integration - Activity page for aggregated repository activity. This is a timeline of commit activity over the last N days for one or more repositories. - *Filters* menu for the Repositories page and Activity page. You can filter by federation set, team, and simple custom regular expressions. Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27) - Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy - JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation - Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit. This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31) - Repositories with a *gh-pages* branch will now have a *pages* link which will serve the content of this branch. All resource requests are against the repository, Gitblit does not checkout/export this branch to a temporary filesystem. Jekyll templating is not supported. - Gitblit Express bundle to get started running Gitblit on RedHat OpenShift cloud <span class="label label-warning">BETA</span> changes: - Dropped display of trailing .git from repository names - ''Gitblit GO is now monolithic like the WAR build. (issue 30) This change helps adoption of GO in environments without an internet connection or with a restricted connection.'' - Unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests fixes: - Several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete - Renaming a repository into a new subfolder failed (issue 33) settings: - { name: groovy.scriptsFolder, defaultValue: groovy } - { name: groovy.preReceiveScripts, defaultValue: } - { name: groovy.postReceiveScripts, defaultValue: } - { name: mail.mailingLists, defaultValue: } - { name: realm.userService, defaultValue: users.conf } - { name: web.allowGravatar, defaultValue: 'true' } - { name: web.activityDuration, defaultValue: 14 } - { name: web.timeFormat, defaultValue: HH:mm } - { name: web.datestampLongFormat, defaultValue: "EEEE, MMMM d, yyyy" } - { name: web.customFilters, defaultValue: } - { name: web.allowFlashCopyToClipboard, defaultValue: 'true' } dependencyChanges: - JGit 1.2.0 - Groovy 1.8.5 - Clippy contributors: - James Moger } # # 0.7.0 # r5: { title: Gitblit 0.7.0 Released id: 0.7.0 date: 2011-11-11 security: - fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28) fixes: - ''federation protocol timestamps. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard. **This breaks 0.6.0 federation clients/servers.**'' - collision on rename for repositories and users - Gitblit can now browse the Linux kernel repository (issue 25) - Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23) - Set the RSS content type of syndication feeds for Firefox 4 (issue 22) - RSS feeds are now properly encoded to UTF-8 - RSS feeds now properly generate parameterized links if *web.mountParameters=false* - Null pointer exception if did not set federation strategy (issue 20) - Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later changes: - updated ui with Twitter Bootstrap CSS toolkit - repositories list performance by caching repository sizes (issue 27) - summary page performance by caching metric calculations (issue 25) additions: - authenticated JSON RPC mechanism - Gitblit API RSS/JSON RPC library - Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server. - per-repository setting to skip size calculation (faster repositories page loading) - per-repository setting to skip summary metrics calculation (faster summary page loading) - IUserService.setup(IStoredSettings) for custom user service implementations - setting to control Gitblit GO context path for proxy setups - *combined-md5* password storage option which stores the hash of username+password as the password - repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them - RSS feeds now include regex substitutions on commit messages for bug trackers, etc settings: - { name: web.loginMessage, defaultValue: gitblit } - { name: web.enableRpcServlet, defaultValue: 'true' } - { name: web.enableRpcManagement, defaultValue: 'false' } - { name: web.enableRpcAdministration, defaultValue: 'false' } - { name: server.contextPath, defaultValue: / } dependencyChanges: - MarkdownPapers 1.2.5 - Wicket 1.4.19 contributors: - James Moger - github/dadalar - github/alyandon - github/trygvis } # # 0.6.0 # r4: { title: Gitblit 0.6.0 Released id: 0.6.0 date: 2011-09-27 fixes: - syndication urls for WAR deployments - authentication for zip downloads additions: - federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances. This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit. - user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance settings: - { name: federation.name, defaultValue: } - { name: federation.passphrase, defaultValue: } - { name: federation.allowProposals, defaultValue: 'false' } - { name: federation.proposalsFolder, defaultValue: proposals } - { name: federation.defaultFrequency, defaultValue: 60 mins } - { name: federation.sets, defaultValue: } - { name: "mail.*", defaultValue: } dependencyChanges: - MarkdownPapers 1.1.1 - Wicket 1.4.18 - JGit 1.1.0 - google-gson - javamail contributors: - James Moger } # # 0.5.2 # r3: { title: Gitblit 0.5.2 Released id: 0.5.2 date: 2011-07-27 fixes: - active repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14) - bare-cloned repositories were listed as (empty) and were not clickable (issue 13) - default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12) - repositories can now be reliably deleted and renamed (issue 10) - users can now change their passwords (issue 1) - always show root repository group first, i.e. do not sort root group with other groups - tone-down repository group header color additions: - optionally display repository on-disk size on repositories page - forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers settings: - { name: web.showRepositorySizes, defaultValue: 'true' } - { name: web.forwardSlashCharacter, defaultValue: / } dependencyChanges: - MarkdownPapers 1.1.0 - Jetty 7.4.3 contributors: - James Moger } # # 0.5.1 # r2: { title: Gitblit 0.5.1 Released id: 0.5.1 date: 2011-06-28 changes: - clarified SSL certificate generation and configuration for both server-side and client-side - added some more troubleshooting information to documentation - replaced JavaService with Apache Commons Daemon contributors: - James Moger } # # 0.5.0 # r1: { title: Gitblit 0.5.0 Released id: 0.5.0 date: 2011-06-26 text: initial release contributors: - James Moger } snapshot: &r18 release: &r17 releases: &r[1..17] resources/arrow_page.pngBinary files differ
resources/bootstrap/css/bootstrap-responsive.css
File was deleted resources/gitblit.css
File was deleted resources/gitblt-favicon.pngBinary files differ
src/WEB-INF/web.xml
File was deleted src/com/gitblit/AddIndexedBranch.java
File was deleted src/com/gitblit/AuthenticationFilter.java
File was deleted src/com/gitblit/ConfigUserService.java
File was deleted src/com/gitblit/Constants.java
File was deleted src/com/gitblit/DownloadZipServlet.java
File was deleted src/com/gitblit/FederationClient.java
File was deleted src/com/gitblit/FederationClientLauncher.java
File was deleted src/com/gitblit/FederationPullExecutor.java
File was deleted src/com/gitblit/FileUserService.java
File was deleted src/com/gitblit/GCExecutor.java
File was deleted src/com/gitblit/GitBlit.java
File was deleted src/com/gitblit/GitBlitServer.java
File was deleted src/com/gitblit/GitFilter.java
File was deleted src/com/gitblit/GitServlet.java
File was deleted src/com/gitblit/GitblitUserService.java
File was deleted src/com/gitblit/IStoredSettings.java
File was deleted src/com/gitblit/IUserService.java
File was deleted src/com/gitblit/JsonServlet.java
File was deleted src/com/gitblit/Launcher.java
File was deleted src/com/gitblit/LdapUserService.java
File was deleted src/com/gitblit/MailExecutor.java
File was deleted src/com/gitblit/PagesFilter.java
File was deleted src/com/gitblit/PagesServlet.java
File was deleted src/com/gitblit/RedmineUserService.java
File was deleted src/com/gitblit/RpcServlet.java
File was deleted src/com/gitblit/ServletRequestWrapper.java
File was deleted src/com/gitblit/authority/CertificatesTableModel.java
File was deleted src/com/gitblit/authority/GitblitAuthority.java
File was deleted src/com/gitblit/authority/GitblitAuthorityLauncher.java
File was deleted src/com/gitblit/authority/RequestFocusListener.java
File was deleted src/com/gitblit/authority/UserOidsPanel.java
File was deleted src/com/gitblit/build/Build.java
File was deleted src/com/gitblit/build/BuildGhPages.java
File was deleted src/com/gitblit/build/BuildSite.java
File was deleted src/com/gitblit/build/BuildThumbnails.java
File was deleted src/com/gitblit/build/BuildWebXml.java
File was deleted src/com/gitblit/client/BranchRenderer.java
File was deleted src/com/gitblit/client/EditRepositoryDialog.java
File was deleted src/com/gitblit/client/GitblitManager.java
File was deleted src/com/gitblit/client/GitblitManagerLauncher.java
File was deleted src/com/gitblit/client/Translation.java
File was deleted src/com/gitblit/models/Activity.java
File was deleted src/com/gitblit/models/AnnotatedLine.java
File was deleted src/com/gitblit/models/PathModel.java
File was deleted src/com/gitblit/models/PushLogEntry.java
File was deleted src/com/gitblit/models/RepositoryCommit.java
File was deleted src/com/gitblit/models/RepositoryModel.java
File was deleted src/com/gitblit/models/ServerStatus.java
File was deleted src/com/gitblit/models/TeamModel.java
File was deleted src/com/gitblit/models/UserModel.java
File was deleted src/com/gitblit/utils/ActivityUtils.java
File was deleted src/com/gitblit/utils/ConnectionUtils.java
File was deleted src/com/gitblit/utils/DiffUtils.java
File was deleted src/com/gitblit/utils/GitBlitDiffFormatter.java
File was deleted src/com/gitblit/utils/JGitUtils.java
File was deleted src/com/gitblit/utils/JsonUtils.java
File was deleted src/com/gitblit/utils/ObjectCache.java
File was deleted src/com/gitblit/utils/PushLogUtils.java
File was deleted src/com/gitblit/utils/RpcUtils.java
File was deleted src/com/gitblit/utils/StringUtils.java
File was deleted src/com/gitblit/utils/TimeUtils.java
File was deleted src/com/gitblit/wicket/AuthorizationStrategy.java
File was deleted src/com/gitblit/wicket/GitBlitWebApp.java
File was deleted src/com/gitblit/wicket/GitBlitWebApp.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_es.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_ja.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_ko.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_nl.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_pl.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
File was deleted src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties
File was deleted src/com/gitblit/wicket/GitBlitWebSession.java
File was deleted src/com/gitblit/wicket/PageRegistration.java
File was deleted src/com/gitblit/wicket/WicketUtils.java
File was deleted src/com/gitblit/wicket/charting/GoogleChart.java
File was deleted src/com/gitblit/wicket/charting/GooglePieChart.java
File was deleted src/com/gitblit/wicket/pages/ActivityPage.html
File was deleted src/com/gitblit/wicket/pages/ActivityPage.java
File was deleted src/com/gitblit/wicket/pages/BasePage.html
File was deleted src/com/gitblit/wicket/pages/BasePage.java
File was deleted src/com/gitblit/wicket/pages/BlamePage.html
File was deleted src/com/gitblit/wicket/pages/BlamePage.java
File was deleted src/com/gitblit/wicket/pages/BlobDiffPage.java
File was deleted src/com/gitblit/wicket/pages/BlobPage.html
File was deleted src/com/gitblit/wicket/pages/BlobPage.java
File was deleted src/com/gitblit/wicket/pages/BranchesPage.java
File was deleted src/com/gitblit/wicket/pages/CommitDiffPage.html
File was deleted src/com/gitblit/wicket/pages/CommitDiffPage.java
File was deleted src/com/gitblit/wicket/pages/CommitPage.html
File was deleted src/com/gitblit/wicket/pages/CommitPage.java
File was deleted src/com/gitblit/wicket/pages/DocsPage.java
File was deleted src/com/gitblit/wicket/pages/EditRepositoryPage.html
File was deleted src/com/gitblit/wicket/pages/EditRepositoryPage.java
File was deleted src/com/gitblit/wicket/pages/EditTeamPage.java
File was deleted src/com/gitblit/wicket/pages/EditUserPage.java
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage.html
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage.java
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html
File was deleted src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html
File was deleted src/com/gitblit/wicket/pages/FederationPage.html
File was deleted src/com/gitblit/wicket/pages/FederationRegistrationPage.html
File was deleted src/com/gitblit/wicket/pages/FederationRegistrationPage.java
File was deleted src/com/gitblit/wicket/pages/ForksPage.java
File was deleted src/com/gitblit/wicket/pages/GitSearchPage.html
File was deleted src/com/gitblit/wicket/pages/GitSearchPage.java
File was deleted src/com/gitblit/wicket/pages/GravatarProfilePage.html
File was deleted src/com/gitblit/wicket/pages/HistoryPage.html
File was deleted src/com/gitblit/wicket/pages/HistoryPage.java
File was deleted src/com/gitblit/wicket/pages/LogPage.html
File was deleted src/com/gitblit/wicket/pages/LogPage.java
File was deleted src/com/gitblit/wicket/pages/LogoutPage.java
File was deleted src/com/gitblit/wicket/pages/LuceneSearchPage.html
File was deleted src/com/gitblit/wicket/pages/MarkdownPage.java
File was deleted src/com/gitblit/wicket/pages/MetricsPage.java
File was deleted src/com/gitblit/wicket/pages/PatchPage.java
File was deleted src/com/gitblit/wicket/pages/ProjectPage.html
File was deleted src/com/gitblit/wicket/pages/ProjectPage.java
File was deleted src/com/gitblit/wicket/pages/ProjectsPage.html
File was deleted src/com/gitblit/wicket/pages/ProjectsPage.java
File was deleted src/com/gitblit/wicket/pages/RawPage.java
File was deleted src/com/gitblit/wicket/pages/RepositoriesPage.html
File was deleted src/com/gitblit/wicket/pages/RepositoriesPage.java
File was deleted src/com/gitblit/wicket/pages/RepositoryPage.html
File was deleted src/com/gitblit/wicket/pages/RepositoryPage.java
File was deleted src/com/gitblit/wicket/pages/RootPage.html
File was deleted src/com/gitblit/wicket/pages/RootPage.java
File was deleted src/com/gitblit/wicket/pages/RootSubPage.html
File was deleted src/com/gitblit/wicket/pages/SendProposalPage.java
File was deleted src/com/gitblit/wicket/pages/SummaryPage.html
File was deleted src/com/gitblit/wicket/pages/SummaryPage.java
File was deleted src/com/gitblit/wicket/pages/TagPage.html
File was deleted src/com/gitblit/wicket/pages/TagPage.java
File was deleted src/com/gitblit/wicket/pages/TagsPage.java
File was deleted src/com/gitblit/wicket/pages/TicketPage.java
File was deleted src/com/gitblit/wicket/pages/TicketsPage.html
File was deleted src/com/gitblit/wicket/pages/TicketsPage.java
File was deleted src/com/gitblit/wicket/pages/TreePage.java
File was deleted src/com/gitblit/wicket/pages/UserPage.html
File was deleted src/com/gitblit/wicket/pages/UsersPage.html
File was deleted src/com/gitblit/wicket/pages/prettify/lang-apollo.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-css.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-hs.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-lisp.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-lua.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-ml.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-proto.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-scala.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-sql.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-vb.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-vhdl.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-wiki.js
File was deleted src/com/gitblit/wicket/pages/prettify/lang-yaml.js
File was deleted src/com/gitblit/wicket/pages/prettify/prettify.css
File was deleted src/com/gitblit/wicket/pages/prettify/prettify.js
File was deleted src/com/gitblit/wicket/panels/ActivityPanel.html
File was deleted src/com/gitblit/wicket/panels/ActivityPanel.java
File was deleted src/com/gitblit/wicket/panels/BasePanel.java
File was deleted src/com/gitblit/wicket/panels/BranchesPanel.html
File was deleted src/com/gitblit/wicket/panels/CommitHeaderPanel.java
File was deleted src/com/gitblit/wicket/panels/CompressedDownloadsPanel.java
File was deleted src/com/gitblit/wicket/panels/GravatarImage.java
File was deleted src/com/gitblit/wicket/panels/HistoryPanel.java
File was deleted src/com/gitblit/wicket/panels/LinkPanel.java
File was deleted src/com/gitblit/wicket/panels/LogPanel.html
File was deleted src/com/gitblit/wicket/panels/LogPanel.java
File was deleted src/com/gitblit/wicket/panels/NavigationPanel.java
File was deleted src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
File was deleted src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
File was deleted src/com/gitblit/wicket/panels/RefsPanel.java
File was deleted src/com/gitblit/wicket/panels/RepositoriesPanel.java
File was deleted src/com/gitblit/wicket/panels/RepositoryUrlPanel.html
File was deleted src/com/gitblit/wicket/panels/RepositoryUrlPanel.java
File was deleted src/com/gitblit/wicket/panels/TagsPanel.html
File was deleted src/com/gitblit/wicket/panels/TagsPanel.java
File was deleted src/main/config/checkstyle.xml
src/main/distrib/data/certs/authority.conf
src/main/distrib/data/certs/instructions.tmpl
src/main/distrib/data/certs/mail.tmpl
src/main/distrib/data/clientapps.json
New file @@ -0,0 +1,81 @@ [ { "name": "Git", "title": "Git", "description": "a fast, open-source, distributed VCS", "legal": "released under the GPLv2 open source license", "command": "git clone ${repoUrl}", "productUrl": "http://git-scm.com", "icon": "git-black_32x32.png", "isActive": true }, { "name": "SmartGit/Hg", "title": "syntevo SmartGit/Hg\u2122", "description": "a Git client for Windows, Mac, & Linux", "legal": "\u00a9 2013 syntevo GmbH. All rights reserved.", "cloneUrl": "smartgit://cloneRepo/${repoUrl}", "productUrl": "http://www.syntevo.com/smartgithg", "platforms": [ "windows", "macintosh", "linux" ], "icon": "smartgithg_32x32.png", "isActive": true }, { "name": "SourceTree", "title": "Atlassian SourceTree\u2122", "description": "a free Git client for Windows or Mac", "legal": "\u00a9 2013 Atlassian. All rights reserved.", "cloneUrl": "sourcetree://cloneRepo/${repoUrl}", "productUrl": "http://sourcetreeapp.com", "platforms": [ "windows", "macintosh" ], "icon": "sourcetree_32x32.png", "isActive": true }, { "name": "Tower", "title": "fournova Tower\u2122", "description": "a Git client for Mac", "legal": "\u00a9 2013 fournova Software GmbH. All rights reserved.", "cloneUrl": "gittower://openRepo/${repoUrl}", "productUrl": "http://www.git-tower.com", "platforms": [ "macintosh" ], "icon": "tower_32x32.png", "isActive": true }, { "name": "GitHub", "title": "GitHub\u2122 for Mac", "description": "a free Git client for Mac OS X", "legal": "\u00a9 2013 GitHub. All rights reserved.", "cloneUrl": "github-mac://openRepo/${repoUrl}", "productUrl": "http://mac.github.com", "transports": [ "http", "https" ], "platforms": [ "macintosh" ], "icon": "github_32x32.png", "isActive": true }, { "name": "GitHub", "title": "GitHub\u2122 for Windows", "description": "a free Git client for Windows", "legal": "\u00a9 2013 GitHub. All rights reserved.", "cloneUrl": "github-windows://openRepo/${repoUrl}", "productUrl": "http://windows.github.com", "transports": [ "http", "https" ], "platforms": [ "windows" ], "icon": "github_32x32.png", "isActive": true }, { "name": "SparkleShare", "title": "SparkleShare\u2122", "description": "an open source collaboration and sharing tool", "legal": "released under the GPLv3 open source license", "cloneUrl": "sparkleshare://addProject/${baseUrl}/sparkleshare/${repoUrl}.xml", "productUrl": "http://sparkleshare.org", "platforms": [ "windows", "macintosh", "linux" ], "icon": "sparkleshare_32x32.png", "minimumPermission" : "RW+", "isActive": false } ] src/main/distrib/data/git/project.mkd
New file @@ -0,0 +1,12 @@ This project contains all repositories created directly in the root of *git.repositoriesFolder*. This message is stored in *git.repositoriesFolder*/**project.mkd** #### Other Projects Each project, or repository group, may specify it's own message by defining a **project.mkd** file in the project folder. <pre> <i>git.repositoriesFolder</i>/projecta/<b>project.mkd</b> <i>git.repositoriesFolder</i>/projectb/<b>project.mkd</b> </pre> src/main/distrib/data/gitblit.properties
New file @@ -0,0 +1,1513 @@ # # 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 = localhost # 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 # 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 # 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 # 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 = NONE # 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 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 # 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 # 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 # Largest object size, in bytes, that JGit will allocate as a contiguous byte # array. Any file revision larger than this threshold will have to be streamed, # typically requiring the use of temporary files under $GIT_DIR/objects to implement # psuedo-random access during delta decompression. # # Servers with very high traffic should set this to be larger than the size of # their common big files. For example a server managing the Android platform # typically has to deal with ~10-12 MiB XML files, so 15 m would be a reasonable # setting in that environment. Setting this too high may cause the JVM to run out # of heap space when handling very big binary files, such as device firmware or # CD-ROM ISO images. Make sure to adjust your JVM heap accordingly. # # Default is 50 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.streamFileThreshold = 50m # 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 # # 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 = localhost # 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 # 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 the full path to a simple user properties file (users.properties) # OR a fully qualified class name that implements the IUserService interface. # # Alternative user services: # com.gitblit.LdapUserService # com.gitblit.RedmineUserService # com.gitblit.SalesforceUserService # com.gitblit.WindowsUserService # # Any custom user service implementation must have a public default constructor. # # SINCE 0.5.0 # RESTART REQUIRED # BASEFOLDER realm.userService = ${baseFolder}/users.conf # 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 = # 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 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 # 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 # 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 # Choose the diff presentation style: gitblt, gitweb, or plain # # SINCE 0.5.0 web.diffStyle = gitblit # 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 # Generates a line graph of repository activity over time on the Summary page. # This uses the Google Charts API. # # SINCE 0.5.0 web.generateActivityGraph = 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 # 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 # 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 # 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 png so swf 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 hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl 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 # Image extensions # # SPACE-DELIMITED # SINCE 0.5.0 web.imageExtensions = bmp jpg gif png # Registered extensions for binary blobs # # SPACE-DELIMITED # SINCE 0.5.0 web.binaryExtensions = jar pdf tar.gz 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 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!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a> # SINCE 0.5.0 regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a> # Example per-repository regex substitutions overrides global # SINCE 0.5.0 regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $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 # 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 # The WindowsUserService must be backed by another user service for standard user # and team management. # default: users.conf # # RESTART REQUIRED # BASEFOLDER # SINCE 1.3.0 realm.windows.backingUserService = ${baseFolder}/users.conf # Allow or prohibit Windows guest account logins # # SINCE 1.3.0 realm.windows.allowGuests = false # 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 SalesforceUserService must be backed by another user service for standard user # and team management. # default: users.conf # # RESTART REQUIRED # BASEFOLDER # SINCE 1.3.0 realm.salesforce.backingUserService = ${baseFolder}/users.conf # 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 # The LdapUserService must be backed by another user service for standard user # and team management. # default: users.conf # # SINCE 1.0.0 # RESTART REQUIRED # BASEFOLDER realm.ldap.backingUserService = ${baseFolder}/users.conf # 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})) # 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". # # 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 # Defines the cache period to be used when caching LDAP queries. This is currently # only used for LDAP user synchronization. # # Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS' # default: 2 MINUTES # # RESTART REQUIRED realm.ldap.ldapCachePeriod = 2 MINUTES # Defines whether to synchronize all LDAP users into the backing user service # # Valid values: true, false # If left blank, false is assumed realm.ldap.synchronizeUsers.enable = false # Defines whether to delete non-existent LDAP users from the backing user service # during synchronization. depends on realm.ldap.synchronizeUsers.enable = true # # Valid values: true, false # If left blank, true is assumed realm.ldap.synchronizeUsers.removeDeleted = true # 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 realm.ldap.uid = uid # The RedmineUserService must be backed by another user service for standard user # and team management. # default: users.conf # # RESTART REQUIRED # BASEFOLDER realm.redmine.backingUserService = ${baseFolder}/users.conf # URL of the Redmine. realm.redmine.url = http://example.com/redmine # # Server Settings # # The temporary folder to decompress the embedded gitblit webapp. # # SINCE 0.5.0 # RESTART REQUIRED # BASEFOLDER server.tempFolder = ${baseFolder}/temp # Use Jetty NIO connectors. If false, Jetty Socket connectors will be used. # # SINCE 0.5.0 # RESTART REQUIRED server.useNio = true # Specify the maximum number of concurrent http/https worker threads to allow. # # 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 # Port for serving an Apache JServ Protocol (AJP) 1.3 connector for integrating # Gitblit GO into an Apache HTTP server setup. <= 0 disables this connector. # Recommended value: 8009 # # SINCE 0.9.0 # RESTART REQUIRED server.ajpPort = 0 # 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 = localhost # 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 = localhost # Specify the interface for Jetty to bind the AJP 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.9.0 # RESTART REQUIRED server.ajpBindInterface = localhost # 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 src/main/distrib/data/groovy/.gitignore
src/main/distrib/data/groovy/blockpush.groovy
src/main/distrib/data/groovy/fogbugz.groovy
src/main/distrib/data/groovy/jenkins.groovy
src/main/distrib/data/groovy/localclone.groovy
src/main/distrib/data/groovy/protect-refs.groovy
src/main/distrib/data/groovy/sendmail-html.groovy
New file @@ -0,0 +1,516 @@ /* * 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. */ import com.gitblit.GitBlit import com.gitblit.Keys import com.gitblit.models.RepositoryModel import com.gitblit.models.TeamModel import com.gitblit.models.UserModel import com.gitblit.utils.JGitUtils import java.text.SimpleDateFormat import org.eclipse.jgit.api.Status; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.diff.DiffEntry.ChangeType; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.IndexDiff; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Config import org.eclipse.jgit.patch.FileHeader; import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand import org.eclipse.jgit.transport.ReceiveCommand.Result import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.util.io.DisabledOutputStream; import org.slf4j.Logger import groovy.xml.MarkupBuilder import java.io.IOException; import java.security.MessageDigest /** * Sample Gitblit Post-Receive Hook: sendmail-html * * The Post-Receive hook is executed AFTER the pushed commits have been applied * to the Git repository. This is the appropriate point to trigger an * integration build or to send a notification. * * This script is only executed when pushing to *Gitblit*, not to other Git * tooling you may be using. * * If this script is specified in *groovy.postReceiveScripts* of gitblit.properties * or web.xml then it will be executed by any repository when it receives a * push. If you choose to share your script then you may have to consider * tailoring control-flow based on repository access restrictions. * * Scripts may also be specified per-repository in the repository settings page. * Shared scripts will be excluded from this list of available scripts. * * This script is dynamically reloaded and it is executed within it's own * exception handler so it will not crash another script nor crash Gitblit. * * If you want this hook script to fail and abort all subsequent scripts in the * chain, "return false" at the appropriate failure points. * * Bound Variables: * gitblit Gitblit Server com.gitblit.GitBlit * repository Gitblit Repository com.gitblit.models.RepositoryModel * user Gitblit User com.gitblit.models.UserModel * commands JGit commands Collection<org.eclipse.jgit.transport.ReceiveCommand> * url Base url for Gitblit java.lang.String * logger Logs messages to Gitblit org.slf4j.Logger * clientLogger Logs messages to Git client com.gitblit.utils.ClientLogger * * Accessing Gitblit Custom Fields: * def myCustomField = repository.customFields.myCustomField * */ com.gitblit.models.UserModel userModel = user // Indicate we have started the script logger.info("sendmail-html hook triggered by ${user.username} for ${repository.name}") /* * Primitive email notification. * This requires the mail settings to be properly configured in Gitblit. */ Repository r = gitblit.getRepository(repository.name) // reuse existing repository config settings, if available Config config = r.getConfig() def mailinglist = config.getString('hooks', null, 'mailinglist') def emailprefix = config.getString('hooks', null, 'emailprefix') // set default values def toAddresses = [] if (emailprefix == null) { emailprefix = '[Gitblit]' } if (mailinglist != null) { def addrs = mailinglist.split(/(,|\s)/) toAddresses.addAll(addrs) } // add all mailing lists defined in gitblit.properties or web.xml toAddresses.addAll(gitblit.getStrings(Keys.mail.mailingLists)) // add all team mailing lists def teams = gitblit.getRepositoryTeams(repository) for (team in teams) { TeamModel model = gitblit.getTeamModel(team) if (model.mailingLists) { toAddresses.addAll(model.mailingLists) } } // add all mailing lists for the repository toAddresses.addAll(repository.mailingLists) // define the summary and commit urls def repo = repository.name def summaryUrl = url + "/summary?r=$repo" def baseCommitUrl = url + "/commit?r=$repo&h=" def baseBlobDiffUrl = url + "/blobdiff/?r=$repo&h=" def baseCommitDiffUrl = url + "/commitdiff/?r=$repo&h=" def forwardSlashChar = gitblit.getString(Keys.web.forwardSlashCharacter, '/') if (gitblit.getBoolean(Keys.web.mountParameters, true)) { repo = repo.replace('/', forwardSlashChar).replace('/', '%2F') summaryUrl = url + "/summary/$repo" baseCommitUrl = url + "/commit/$repo/" baseBlobDiffUrl = url + "/blobdiff/$repo/" baseCommitDiffUrl = url + "/commitdiff/$repo/" } class HtmlMailWriter { Repository repository def url def baseCommitUrl def baseCommitDiffUrl def baseBlobDiffUrl def mountParameters def forwardSlashChar def includeGravatar def shortCommitIdLength def commitCount = 0 def commands def writer = new StringWriter(); def builder = new MarkupBuilder(writer) def writeStyle() { builder.style(type:"text/css", ''' .table td { vertical-align: middle; } tr.noborder td { border: none; padding-top: 0px; } .gravatar-column { width: 5%; } .author-column { width: 20%; } .commit-column { width: 5%; } .status-column { width: 10%; } .table-disable-hover.table tbody tr:hover td, .table-disable-hover.table tbody tr:hover th { background-color: inherit; } .table-disable-hover.table-striped tbody tr:nth-child(odd):hover td, .table-disable-hover.table-striped tbody tr:nth-child(odd):hover th { background-color: #f9f9f9; } ''') } def writeBranchTitle(type, name, action, number) { builder.div('class' : 'pageTitle') { builder.span('class':'project') { mkp.yield "$type " span('class': 'repository', name ) if (number > 0) { mkp.yield " $action ($number commits)" } else { mkp.yield " $action" } } } } def writeBranchDeletedTitle(type, name) { builder.div('class' : 'pageTitle', 'style':'color:red') { builder.span('class':'project') { mkp.yield "$type " span('class': 'repository', name ) mkp.yield " deleted" } } } def commitUrl(RevCommit commit) { "${baseCommitUrl}$commit.id.name" } def commitDiffUrl(RevCommit commit) { "${baseCommitDiffUrl}$commit.id.name" } def encoded(String path) { path.replace('/', forwardSlashChar).replace('/', '%2F') } def blobDiffUrl(objectId, path) { if (mountParameters) { // REST style "${baseBlobDiffUrl}${objectId.name()}/${encoded(path)}" } else { "${baseBlobDiffUrl}${objectId.name()}&f=${path}" } } def writeCommitTable(commits, includeChangedPaths=true) { // Write commits table builder.table('class':"table table-disable-hover") { thead { tr { th(colspan: includeGravatar ? 2 : 1, "Author") th( "Commit" ) th( "Message" ) } } tbody() { // Write all the commits for (commit in commits) { writeCommit(commit) if (includeChangedPaths) { // Write detail on that particular commit tr('class' : 'noborder') { td (colspan: includeGravatar ? 3 : 2) td (colspan:2) { writeStatusTable(commit) } } } } } } } def writeCommit(commit) { def abbreviated = repository.newObjectReader().abbreviate(commit.id, shortCommitIdLength).name() def author = commit.authorIdent.name def email = commit.authorIdent.emailAddress def message = commit.shortMessage builder.tr { if (includeGravatar) { td('class':"gravatar-column") { img(src:gravatarUrl(email), 'class':"gravatar") } } td('class':"author-column", author) td('class':"commit-column") { a(href:commitUrl(commit)) { span('class':"label label-info", abbreviated ) } } td { mkp.yield message a('class':'link', href:commitDiffUrl(commit), " [commitdiff]" ) } } } def writeStatusLabel(style, tooltip) { builder.span('class' : style, 'title' : tooltip ) } def writeAddStatusLine(ObjectId id, FileHeader header) { builder.td('class':'changeType') { writeStatusLabel("addition", "addition") } builder.td { a(href:blobDiffUrl(id, header.newPath), header.newPath) } } def writeCopyStatusLine(ObjectId id, FileHeader header) { builder.td('class':'changeType') { writeStatusLabel("rename", "rename") } builder.td() { a(href:blobDiffUrl(id, header.newPath), header.oldPath + " copied to " + header.newPath) } } def writeDeleteStatusLine(ObjectId id, FileHeader header) { builder.td('class':'changeType') { writeStatusLabel("deletion", "deletion") } builder.td() { a(href:blobDiffUrl(id, header.oldPath), header.oldPath) } } def writeModifyStatusLine(ObjectId id, FileHeader header) { builder.td('class':'changeType') { writeStatusLabel("modification", "modification") } builder.td() { a(href:blobDiffUrl(id, header.oldPath), header.oldPath) } } def writeRenameStatusLine(ObjectId id, FileHeader header) { builder.td('class':'changeType') { writeStatusLabel("rename", "rename") } builder.td() { mkp.yield header.oldPath mkp.yieldUnescaped "<b> -&rt; </b>" a(href:blobDiffUrl(id, header.newPath), header.newPath) } } def writeStatusLine(ObjectId id, FileHeader header) { builder.tr { switch (header.changeType) { case ChangeType.ADD: writeAddStatusLine(id, header) break; case ChangeType.COPY: writeCopyStatusLine(id, header) break; case ChangeType.DELETE: writeDeleteStatusLine(id, header) break; case ChangeType.MODIFY: writeModifyStatusLine(id, header) break; case ChangeType.RENAME: writeRenameStatusLine(id, header) break; } } } def writeStatusTable(RevCommit commit) { DiffFormatter formatter = new DiffFormatter(DisabledOutputStream.INSTANCE) formatter.setRepository(repository) formatter.setDetectRenames(true) formatter.setDiffComparator(RawTextComparator.DEFAULT); def diffs RevWalk rw = new RevWalk(repository) if (commit.parentCount > 0) { RevCommit parent = rw.parseCommit(commit.parents[0].id) diffs = formatter.scan(parent.tree, commit.tree) } else { diffs = formatter.scan(new EmptyTreeIterator(), new CanonicalTreeParser(null, rw.objectReader, commit.tree)) } rw.dispose() // Write status table builder.table('class':"plain") { tbody() { for (DiffEntry entry in diffs) { FileHeader header = formatter.toFileHeader(entry) writeStatusLine(commit.id, header) } } } } def md5(text) { def digest = MessageDigest.getInstance("MD5") //Quick MD5 of text def hash = new BigInteger(1, digest.digest(text.getBytes())) .toString(16) .padLeft(32, "0") hash.toString() } def gravatarUrl(email) { def cleaned = email.trim().toLowerCase() "http://www.gravatar.com/avatar/${md5(cleaned)}?s=30" } def writeNavbar() { builder.div('class':"navbar navbar-fixed-top") { div('class':"navbar-inner") { div('class':"container") { a('class':"brand", href:"${url}", title:"GitBlit") { img(src:"${url}/gitblt_25_white.png", width:"79", height:"25", 'class':"logo") } } } } } def write() { builder.html { head { link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap.css") link(rel:"stylesheet", href:"${url}/gitblit.css") link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap-responsive.css") writeStyle() } body { writeNavbar() div('class':"container") { for (command in commands) { def ref = command.refName def refType = 'Branch' if (ref.startsWith('refs/heads/')) { ref = command.refName.substring('refs/heads/'.length()) } else if (ref.startsWith('refs/tags/')) { ref = command.refName.substring('refs/tags/'.length()) refType = 'Tag' } switch (command.type) { case ReceiveCommand.Type.CREATE: def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse() commitCount += commits.size() if (refType == 'Branch') { // new branch writeBranchTitle(refType, ref, "created", commits.size()) writeCommitTable(commits, true) } else { // new tag writeBranchTitle(refType, ref, "created", 0) writeCommitTable(commits, false) } break case ReceiveCommand.Type.UPDATE: def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse() commitCount += commits.size() // fast-forward branch commits table // Write header writeBranchTitle(refType, ref, "updated", commits.size()) writeCommitTable(commits) break case ReceiveCommand.Type.UPDATE_NONFASTFORWARD: def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse() commitCount += commits.size() // non-fast-forward branch commits table // Write header writeBranchTitle(refType, ref, "updated [NON fast-forward]", commits.size()) writeCommitTable(commits) break case ReceiveCommand.Type.DELETE: // deleted branch/tag writeBranchDeletedTitle(refType, ref) break default: break } } } } } writer.toString() } } def mailWriter = new HtmlMailWriter() mailWriter.repository = r mailWriter.baseCommitUrl = baseCommitUrl mailWriter.baseBlobDiffUrl = baseBlobDiffUrl mailWriter.baseCommitDiffUrl = baseCommitDiffUrl mailWriter.forwardSlashChar = forwardSlashChar mailWriter.commands = commands mailWriter.url = url mailWriter.mountParameters = GitBlit.getBoolean(Keys.web.mountParameters, true) mailWriter.includeGravatar = GitBlit.getBoolean(Keys.web.allowGravatar, true) mailWriter.shortCommitIdLength = GitBlit.getInteger(Keys.web.shortCommitIdLength, 8) def content = mailWriter.write() // close the repository reference r.close() // tell Gitblit to send the message (Gitblit filters duplicate addresses) def repositoryName = repository.name.substring(0, repository.name.length() - 4) gitblit.sendHtmlMail("${emailprefix} ${userModel.displayName} pushed ${mailWriter.commitCount} commits => $repositoryName", content, toAddresses) src/main/distrib/data/groovy/sendmail.groovy
src/main/distrib/data/groovy/thebuggenie.groovy
src/main/distrib/data/projects.conf
src/main/distrib/data/users.conf
src/main/distrib/federation.properties
New file @@ -0,0 +1,83 @@ # # Git Repository Settings # # Base folder for repositories # Use forward slashes even on Windows!! # e.g. c:/gitrepos # # SINCE 0.5.0 # RESTART REQUIRED git.repositoriesFolder = ${baseFolder}/git # 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 # 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 = # 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 src/main/distrib/linux/add-indexed-branch.sh
New file @@ -0,0 +1,21 @@ #!/bin/bash # -------------------------------------------------------------------------- # This is for Lucene search integration. # # Allows you to add an indexed branch specification to the repository config # for all matching repositories in the specified folder. # # All repositories are included unless excluded using a --skip parameter. # --skip supports simple wildcard fuzzy matching however only 1 asterisk is # allowed per parameter. # # Always use forward-slashes for the path separator in your parameters!! # # Set FOLDER to the server's git.repositoriesFolder # Set BRANCH ("default" or fully qualified ref - i.e. refs/heads/master) # Set EXCLUSIONS for any repositories that you do not want to change # -------------------------------------------------------------------------- SET FOLDER=git SET EXCLUSIONS=--skip test.git --skip group/test* SET BRANCH=default java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.AddIndexedBranch --repositoriesFolder %FOLDER% --branch %BRANCH% %EXCLUSIONS% src/main/distrib/linux/authority.sh
New file @@ -0,0 +1,2 @@ #!/bin/bash java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data src/main/distrib/linux/gitblit-stop.sh
New file @@ -0,0 +1,2 @@ #!/bin/bash java -jar gitblit.jar --baseFolder data --stop src/main/distrib/linux/gitblit.sh
New file @@ -0,0 +1,2 @@ #!/bin/bash java -jar gitblit.jar --baseFolder data src/main/distrib/linux/install-service-centos.sh
New file @@ -0,0 +1,3 @@ #!/bin/bash sudo cp service-centos.sh /etc/init.d/gitblit sudo chkconfig --add gitblit src/main/distrib/linux/install-service-ubuntu.sh
New file @@ -0,0 +1,3 @@ #!/bin/bash sudo cp service-ubuntu.sh /etc/init.d/gitblit sudo update-rc.d gitblit defaults src/main/distrib/linux/java-proxy-config.sh
src/main/distrib/linux/service-centos.sh
src/main/distrib/linux/service-ubuntu.sh
src/main/distrib/win/add-indexed-branch.cmd
New file @@ -0,0 +1,20 @@ @REM -------------------------------------------------------------------------- @REM This is for Lucene search integration. @REM @REM Allows you to add an indexed branch specification to the repository config @REM for all matching repositories in the specified folder. @REM @REM All repositories are included unless excluded using a --skip parameter. @REM --skip supports simple wildcard fuzzy matching however only 1 asterisk is @REM allowed per parameter. @REM @REM Always use forward-slashes for the path separator in your parameters!! @REM @REM Set FOLDER to the server's git.repositoriesFolder @REM Set BRANCH ("default" or fully qualified ref - i.e. refs/heads/master) @REM Set EXCLUSIONS for any repositories that you do not want to change @REM -------------------------------------------------------------------------- @SET FOLDER=c:/gitblit/git @SET EXCLUSIONS=--skip test.git --skip group/test* @SET BRANCH=default @java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.AddIndexedBranch --repositoriesFolder %FOLDER% --branch %BRANCH% %EXCLUSIONS% %* src/main/distrib/win/amd64/gitblit.exeBinary files differ
src/main/distrib/win/authority.cmd
New file @@ -0,0 +1 @@ @java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data %* src/main/distrib/win/gitblit-stop.cmd
New file @@ -0,0 +1 @@ @java -jar gitblit.jar --stop --baseFolder data %* src/main/distrib/win/gitblit.cmd
New file @@ -0,0 +1 @@ @java -jar gitblit.jar --baseFolder data %* src/main/distrib/win/gitblitw.exeBinary files differ
src/main/distrib/win/ia64/gitblit.exeBinary files differ
src/main/distrib/win/installService.cmd
New file @@ -0,0 +1,38 @@ @REM Install Gitblit as a Windows service. @REM gitblitw.exe (prunmgr.exe) is a GUI application for monitoring @REM and configuring the Gitblit procrun service. @REM @REM By default this tool launches the service properties dialog @REM but it also has some other very useful functionality. @REM @REM http://commons.apache.org/daemon/procrun.html @REM arch = x86, amd64, or ia32 SET ARCH=amd64 @REM Be careful not to introduce trailing whitespace after the ^ characters. @REM Use ; or # to separate values in the --StartParams parameter. "%CD%\%ARCH%\gitblit.exe" //IS//gitblit ^ --DisplayName="gitblit" ^ --Description="a pure Java Git solution" ^ --Startup=auto ^ --LogPath="%CD%\logs" ^ --LogLevel=INFO ^ --LogPrefix=gitblit ^ --StdOutput=auto ^ --StdError=auto ^ --StartPath="%CD%" ^ --StartClass=org.moxie.MxLauncher ^ --StartMethod=main ^ --StartParams="--storePassword;gitblit;--baseFolder;%CD%\data" ^ --StartMode=jvm ^ --StopPath="%CD%" ^ --StopClass=org.moxie.MxLauncher ^ --StopMethod=main ^ --StopParams="--stop;--baseFolder;%CD%\data" ^ --StopMode=jvm ^ --Classpath="%CD%\gitblit.jar" ^ --Jvm=auto ^ --JvmMx=1024 src/main/distrib/win/uninstallService.cmd
src/main/distrib/win/x86/gitblit.exeBinary files differ
src/main/java/.gitignore
New file @@ -0,0 +1 @@ /clientapps.json src/main/java/WEB-INF/web.xml
New file @@ -0,0 +1,292 @@ <?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. --> <context-param> <param-name>baseFolder</param-name> <param-value>${contextFolder}/WEB-INF/data</param-value> </context-param> <!-- Gitblit Displayname --> <display-name> Gitblit - @gb.version@ </display-name> <!-- PARAMS --> <!-- Gitblit Context Listener --><!-- STRIP <listener> <listener-class>com.gitblit.GitBlit</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.git.GitServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>GitServlet</servlet-name> <url-pattern>/git/*</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.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.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.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.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.RpcServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RpcServlet</servlet-name> <url-pattern>/rpc/*</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.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.LogoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LogoServlet</servlet-name> <url-pattern>/logo.png</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.RobotsTxtServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RobotsTxtServlet</servlet-name> <url-pattern>/robots.txt</url-pattern> </servlet-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.GitFilter</filter-class> </filter> <filter-mapping> <filter-name>GitFilter</filter-name> <url-pattern>/git/*</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.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.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.RpcFilter</filter-class> </filter> <filter-mapping> <filter-name>RpcFilter</filter-name> <url-pattern>/rpc/*</url-pattern> </filter-mapping> <!-- Pges 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.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.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>applicationClassName</param-name> <param-value>com.gitblit.wicket.GitBlitWebApp</param-value> </init-param> <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> * PagesFilter <url-pattern> * PagesServlet <url-pattern> * com.gitblit.Constants.PAGES_PATH --> <param-value>git/,feed/,zip/,federation/,rpc/,pages/,robots.txt,logo.png,sparkleshare/</param-value> </init-param> </filter> <filter-mapping> <filter-name>wicketFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> src/main/java/WEB-INF/weblogic.xml
New file @@ -0,0 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <wls:weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wls="http://www.bea.com/ns/weblogic/90" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd"> <wls:weblogic-version>12.1.1</wls:weblogic-version> <wls:context-root>gitblit</wls:context-root> <wls:container-descriptor> <wls:show-archived-real-path-enabled>true</wls:show-archived-real-path-enabled> <wls:prefer-web-inf-classes>true</wls:prefer-web-inf-classes> </wls:container-descriptor> </wls:weblogic-web-app> src/main/java/com/gitblit/.gitignore
src/main/java/com/gitblit/AccessRestrictionFilter.java
src/main/java/com/gitblit/AddIndexedBranch.java
New file @@ -0,0 +1,149 @@ /* * 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; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache.FileKey; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.util.FS; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import com.gitblit.models.RefModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.StringUtils; /** * Utility class to add an indexBranch setting to matching repositories. * * @author James Moger * */ public class AddIndexedBranch { public static void main(String... args) { Params params = new Params(); JCommander jc = new JCommander(params); try { jc.parse(args); } catch (ParameterException t) { System.err.println(t.getMessage()); jc.usage(); return; } // create a lowercase set of excluded repositories Set<String> exclusions = new TreeSet<String>(); for (String exclude : params.exclusions) { exclusions.add(exclude.toLowerCase()); } // determine available repositories File folder = new File(params.folder); List<String> repoList = JGitUtils.getRepositoryList(folder, false, true, -1, null); int modCount = 0; int skipCount = 0; for (String repo : repoList) { boolean skip = false; for (String exclusion : exclusions) { if (StringUtils.fuzzyMatch(repo, exclusion)) { skip = true; break; } } if (skip) { System.out.println("skipping " + repo); skipCount++; continue; } try { // load repository config File gitDir = FileKey.resolve(new File(folder, repo), FS.DETECTED); Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build(); StoredConfig config = repository.getConfig(); config.load(); Set<String> indexedBranches = new LinkedHashSet<String>(); // add all local branches to index if(params.addAllLocalBranches) { List<RefModel> list = JGitUtils.getLocalBranches(repository, true, -1); for (RefModel refModel : list) { System.out.println(MessageFormat.format("adding [gitblit] indexBranch={0} for {1}", refModel.getName(), repo)); indexedBranches.add(refModel.getName()); } } else { // add only one branch to index ('default' if not specified) System.out.println(MessageFormat.format("adding [gitblit] indexBranch={0} for {1}", params.branch, repo)); indexedBranches.add(params.branch); } String [] branches = config.getStringList("gitblit", null, "indexBranch"); if (!ArrayUtils.isEmpty(branches)) { for (String branch : branches) { indexedBranches.add(branch); } } config.setStringList("gitblit", null, "indexBranch", new ArrayList<String>(indexedBranches)); config.save(); modCount++; } catch (Exception e) { System.err.println(repo); e.printStackTrace(); } } System.out.println(MessageFormat.format("updated {0} repository configurations, skipped {1}", modCount, skipCount)); } /** * JCommander Parameters class for AddIndexedBranch. */ @Parameters(separators = " ") private static class Params { @Parameter(names = { "--repositoriesFolder" }, description = "The root repositories folder ", required = true) public String folder; @Parameter(names = { "--branch" }, description = "The branch to index", required = false) public String branch = "default"; @Parameter(names = { "--skip" }, description = "Skip the named repository (simple fizzy matching is supported)", required = false) public List<String> exclusions = new ArrayList<String>(); @Parameter(names = { "--all-local-branches" }, description = "Add all local branches to index. If specified, the --branch parameter is not considered.", required = false) public boolean addAllLocalBranches = false; } } src/main/java/com/gitblit/AuthenticationFilter.java
New file @@ -0,0 +1,187 @@ /* * 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.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.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()); /** * 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 = GitBlit.self().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()); } } } } /** * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(final FilterConfig config) throws ServletException { } /** * @see javax.servlet.Filter#destroy() */ @Override public void destroy() { } /** * 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.canAccessRepository(role); } @Override public Principal getUserPrincipal() { return user; } } } src/main/java/com/gitblit/ConfigUserService.java
New file @@ -0,0 +1,1105 @@ /* * 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.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.models.UserRepositoryPreferences; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * ConfigUserService is Gitblit's default user service implementation since * version 0.8.0. * * Users and their repository memberships are stored in a git-style config file * which is cached and dynamically reloaded when modified. This file is * plain-text, human-readable, and may be edited with a text editor. * * Additionally, this format allows for expansion of the user model without * bringing in the complexity of a database. * * @author James Moger * */ public class ConfigUserService implements IUserService { private static final String TEAM = "team"; private static final String USER = "user"; private static final String PASSWORD = "password"; private static final String DISPLAYNAME = "displayName"; private static final String EMAILADDRESS = "emailAddress"; private static final String ORGANIZATIONALUNIT = "organizationalUnit"; private static final String ORGANIZATION = "organization"; private static final String LOCALITY = "locality"; private static final String STATEPROVINCE = "stateProvince"; private static final String COUNTRYCODE = "countryCode"; private static final String COOKIE = "cookie"; private static final String REPOSITORY = "repository"; private static final String ROLE = "role"; private static final String MAILINGLIST = "mailingList"; private static final String PRERECEIVE = "preReceiveScript"; private static final String POSTRECEIVE = "postReceiveScript"; private static final String STARRED = "starred"; private static final String LOCALE = "locale"; private final File realmFile; private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class); private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>(); private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>(); private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>(); private volatile long lastModified; private volatile boolean forceReload; public ConfigUserService(File realmFile) { this.realmFile = realmFile; } /** * Setup the user service. * * @param settings * @since 0.7.0 */ @Override public void setup(IStoredSettings settings) { } /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return true; } /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return true; } /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { return true; } /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return true; } /** * Does the user service support cookie authentication? * * @return true or false */ @Override public boolean supportsCookies() { return true; } /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ @Override public String getCookie(UserModel model) { if (!StringUtils.isEmpty(model.cookie)) { return model.cookie; } read(); UserModel storedModel = users.get(model.username.toLowerCase()); return storedModel.cookie; } /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ @Override public UserModel authenticate(char[] cookie) { String hash = new String(cookie); if (StringUtils.isEmpty(hash)) { return null; } read(); UserModel model = null; if (cookies.containsKey(hash)) { model = cookies.get(hash); } return model; } /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { read(); UserModel returnedUser = null; UserModel user = getUserModel(username); if (user == null) { return null; } if (user.password.startsWith(StringUtils.MD5_TYPE)) { // password digest String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { // username+password digest String md5 = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username.toLowerCase() + new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.equals(new String(password))) { // plain-text password returnedUser = user; } return returnedUser; } /** * Logout a user. * * @param user */ @Override public void logout(UserModel user) { } /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ @Override public UserModel getUserModel(String username) { read(); UserModel model = users.get(username.toLowerCase()); if (model != null) { // clone the model, otherwise all changes to this object are // live and unpersisted model = DeepCopier.copy(model); } return model; } /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ @Override public boolean updateUserModel(UserModel model) { return updateUserModel(model.username, model); } /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateUserModels(Collection<UserModel> models) { try { read(); for (UserModel model : models) { UserModel originalUser = users.remove(model.username.toLowerCase()); users.put(model.username.toLowerCase(), model); // null check on "final" teams because JSON-sourced UserModel // can have a null teams object if (model.teams != null) { for (TeamModel team : model.teams) { TeamModel t = teams.get(team.name.toLowerCase()); if (t == null) { // new team team.addUser(model.username); teams.put(team.name.toLowerCase(), team); } else { // do not clobber existing team definition // maybe because this is a federated user t.addUser(model.username); } } // check for implicit team removal if (originalUser != null) { for (TeamModel team : originalUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(model.username); } } } } } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete user object keyed by username. * This method allows for renaming a user. * * @param username * the old username * @param model * the user object to use for username * @return true if update is successful */ @Override public boolean updateUserModel(String username, UserModel model) { UserModel originalUser = null; try { read(); originalUser = users.remove(username.toLowerCase()); users.put(model.username.toLowerCase(), model); // null check on "final" teams because JSON-sourced UserModel // can have a null teams object if (model.teams != null) { for (TeamModel team : model.teams) { TeamModel t = teams.get(team.name.toLowerCase()); if (t == null) { // new team team.addUser(username); teams.put(team.name.toLowerCase(), team); } else { // do not clobber existing team definition // maybe because this is a federated user t.removeUser(username); t.addUser(model.username); } } // check for implicit team removal if (originalUser != null) { for (TeamModel team : originalUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(username); } } } } write(); return true; } catch (Throwable t) { if (originalUser != null) { // restore original user users.put(originalUser.username.toLowerCase(), originalUser); } else { // drop attempted add users.remove(model.username.toLowerCase()); } logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Deletes the user object from the user service. * * @param model * @return true if successful */ @Override public boolean deleteUserModel(UserModel model) { return deleteUser(model.username); } /** * Delete the user object with the specified username * * @param username * @return true if successful */ @Override public boolean deleteUser(String username) { try { // Read realm file read(); UserModel model = users.remove(username.toLowerCase()); if (model == null) { // user does not exist return false; } // remove user from team for (TeamModel team : model.teams) { TeamModel t = teams.get(team.name); if (t == null) { // new team team.removeUser(username); teams.put(team.name.toLowerCase(), team); } else { // existing team t.removeUser(username); } } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); } return false; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<String> getAllTeamNames() { read(); List<String> list = new ArrayList<String>(teams.keySet()); Collections.sort(list); return list; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<TeamModel> getAllTeams() { read(); List<TeamModel> list = new ArrayList<TeamModel>(teams.values()); list = DeepCopier.copy(list); Collections.sort(list); return list; } /** * Returns the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction */ @Override public List<String> getTeamnamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { read(); for (Map.Entry<String, TeamModel> entry : teams.entrySet()) { TeamModel model = entry.getValue(); if (model.hasRepositoryPermission(role)) { list.add(model.name); } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t); } Collections.sort(list); return list; } /** * Sets the list of all teams who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param teamnames * @return true if successful */ @Override public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) { try { Set<String> specifiedTeams = new HashSet<String>(); for (String teamname : teamnames) { specifiedTeams.add(teamname.toLowerCase()); } read(); // identify teams which require add or remove role for (TeamModel team : teams.values()) { // team has role, check against revised team list if (specifiedTeams.contains(team.name.toLowerCase())) { team.addRepositoryPermission(role); } else { // remove role from team team.removeRepositoryPermission(role); } } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t); } return false; } /** * Retrieve the team object for the specified team name. * * @param teamname * @return a team object or null * @since 0.8.0 */ @Override public TeamModel getTeamModel(String teamname) { read(); TeamModel model = teams.get(teamname.toLowerCase()); if (model != null) { // clone the model, otherwise all changes to this object are // live and unpersisted model = DeepCopier.copy(model); } return model; } /** * Updates/writes a complete team object. * * @param model * @return true if update is successful * @since 0.8.0 */ @Override public boolean updateTeamModel(TeamModel model) { return updateTeamModel(model.name, model); } /** * Updates/writes all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateTeamModels(Collection<TeamModel> models) { try { read(); for (TeamModel team : models) { teams.put(team.name.toLowerCase(), team); } write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete team object keyed by teamname. * This method allows for renaming a team. * * @param teamname * the old teamname * @param model * the team object to use for teamname * @return true if update is successful * @since 0.8.0 */ @Override public boolean updateTeamModel(String teamname, TeamModel model) { TeamModel original = null; try { read(); original = teams.remove(teamname.toLowerCase()); teams.put(model.name.toLowerCase(), model); write(); return true; } catch (Throwable t) { if (original != null) { // restore original team teams.put(original.name.toLowerCase(), original); } else { // drop attempted add teams.remove(model.name.toLowerCase()); } logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t); } return false; } /** * Deletes the team object from the user service. * * @param model * @return true if successful * @since 0.8.0 */ @Override public boolean deleteTeamModel(TeamModel model) { return deleteTeam(model.name); } /** * Delete the team object with the specified teamname * * @param teamname * @return true if successful * @since 0.8.0 */ @Override public boolean deleteTeam(String teamname) { try { // Read realm file read(); teams.remove(teamname.toLowerCase()); write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t); } return false; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<String> getAllUsernames() { read(); List<String> list = new ArrayList<String>(users.keySet()); Collections.sort(list); return list; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<UserModel> getAllUsers() { read(); List<UserModel> list = new ArrayList<UserModel>(users.values()); list = DeepCopier.copy(list); Collections.sort(list); return list; } /** * Returns the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction */ @Override public List<String> getUsernamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { read(); for (Map.Entry<String, UserModel> entry : users.entrySet()) { UserModel model = entry.getValue(); if (model.hasRepositoryPermission(role)) { list.add(model.username); } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); } Collections.sort(list); return list; } /** * Sets the list of all uses who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param usernames * @return true if successful */ @Override @Deprecated public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { try { Set<String> specifiedUsers = new HashSet<String>(); for (String username : usernames) { specifiedUsers.add(username.toLowerCase()); } read(); // identify users which require add or remove role for (UserModel user : users.values()) { // user has role, check against revised user list if (specifiedUsers.contains(user.username.toLowerCase())) { user.addRepositoryPermission(role); } else { // remove role from user user.removeRepositoryPermission(role); } } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); } return false; } /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ @Override public boolean renameRepositoryRole(String oldRole, String newRole) { try { read(); // identify users which require role rename for (UserModel model : users.values()) { if (model.hasRepositoryPermission(oldRole)) { AccessPermission permission = model.removeRepositoryPermission(oldRole); model.setRepositoryPermission(newRole, permission); } } // identify teams which require role rename for (TeamModel model : teams.values()) { if (model.hasRepositoryPermission(oldRole)) { AccessPermission permission = model.removeRepositoryPermission(oldRole); model.setRepositoryPermission(newRole, permission); } } // persist changes write(); return true; } catch (Throwable t) { logger.error( MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); } return false; } /** * Removes a repository role from all users. * * @param role * @return true if successful */ @Override public boolean deleteRepositoryRole(String role) { try { read(); // identify users which require role rename for (UserModel user : users.values()) { user.removeRepositoryPermission(role); } // identify teams which require role rename for (TeamModel team : teams.values()) { team.removeRepositoryPermission(role); } // persist changes write(); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); } return false; } /** * Writes the properties file. * * @throws IOException */ private synchronized void write() throws IOException { // Write a temporary copy of the users file File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect()); // write users for (UserModel model : users.values()) { if (!StringUtils.isEmpty(model.password)) { config.setString(USER, model.username, PASSWORD, model.password); } if (!StringUtils.isEmpty(model.cookie)) { config.setString(USER, model.username, COOKIE, model.cookie); } if (!StringUtils.isEmpty(model.displayName)) { config.setString(USER, model.username, DISPLAYNAME, model.displayName); } if (!StringUtils.isEmpty(model.emailAddress)) { config.setString(USER, model.username, EMAILADDRESS, model.emailAddress); } if (!StringUtils.isEmpty(model.organizationalUnit)) { config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit); } if (!StringUtils.isEmpty(model.organization)) { config.setString(USER, model.username, ORGANIZATION, model.organization); } if (!StringUtils.isEmpty(model.locality)) { config.setString(USER, model.username, LOCALITY, model.locality); } if (!StringUtils.isEmpty(model.stateProvince)) { config.setString(USER, model.username, STATEPROVINCE, model.stateProvince); } if (!StringUtils.isEmpty(model.countryCode)) { config.setString(USER, model.username, COUNTRYCODE, model.countryCode); } if (model.getPreferences() != null) { if (!StringUtils.isEmpty(model.getPreferences().locale)) { config.setString(USER, model.username, LOCALE, model.getPreferences().locale); } } // user roles List<String> roles = new ArrayList<String>(); if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } 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); } config.setStringList(USER, model.username, ROLE, roles); // discrete repository permissions if (model.permissions != null && !model.canAdmin) { List<String> permissions = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { permissions.add(entry.getValue().asRole(entry.getKey())); } } config.setStringList(USER, model.username, REPOSITORY, permissions); } // user preferences if (model.getPreferences() != null) { List<String> starred = model.getPreferences().getStarredRepositories(); if (starred.size() > 0) { config.setStringList(USER, model.username, STARRED, starred); } } } // write teams for (TeamModel model : teams.values()) { // team roles List<String> roles = new ArrayList<String>(); if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } 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); } config.setStringList(TEAM, model.name, ROLE, roles); if (!model.canAdmin) { // write team permission for non-admin teams if (model.permissions == null) { // null check on "final" repositories because JSON-sourced TeamModel // can have a null repositories object if (!ArrayUtils.isEmpty(model.repositories)) { config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>( model.repositories)); } } else { // discrete repository permissions List<String> permissions = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git permissions.add(entry.getValue().asRole(entry.getKey())); } } config.setStringList(TEAM, model.name, REPOSITORY, permissions); } } // null check on "final" users because JSON-sourced TeamModel // can have a null users object if (!ArrayUtils.isEmpty(model.users)) { config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users)); } // null check on "final" mailing lists because JSON-sourced // TeamModel can have a null users object if (!ArrayUtils.isEmpty(model.mailingLists)) { config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>( model.mailingLists)); } // null check on "final" preReceiveScripts because JSON-sourced // TeamModel can have a null preReceiveScripts object if (!ArrayUtils.isEmpty(model.preReceiveScripts)) { config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts); } // null check on "final" postReceiveScripts because JSON-sourced // TeamModel can have a null postReceiveScripts object if (!ArrayUtils.isEmpty(model.postReceiveScripts)) { config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts); } } config.save(); // manually set the forceReload flag because not all JVMs support real // millisecond resolution of lastModified. (issue-55) forceReload = true; // If the write is successful, delete the current file and rename // the temporary copy to the original filename. if (realmFileCopy.exists() && realmFileCopy.length() > 0) { if (realmFile.exists()) { if (!realmFile.delete()) { throw new IOException(MessageFormat.format("Failed to delete {0}!", realmFile.getAbsolutePath())); } } if (!realmFileCopy.renameTo(realmFile)) { throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath())); } } else { throw new IOException(MessageFormat.format("Failed to save {0}!", realmFileCopy.getAbsolutePath())); } } /** * Reads the realm file and rebuilds the in-memory lookup tables. */ protected synchronized void read() { if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) { forceReload = false; lastModified = realmFile.lastModified(); users.clear(); cookies.clear(); teams.clear(); try { StoredConfig config = new FileBasedConfig(realmFile, FS.detect()); config.load(); Set<String> usernames = config.getSubsections(USER); for (String username : usernames) { UserModel user = new UserModel(username.toLowerCase()); user.password = config.getString(USER, username, PASSWORD); user.displayName = config.getString(USER, username, DISPLAYNAME); user.emailAddress = config.getString(USER, username, EMAILADDRESS); user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT); user.organization = config.getString(USER, username, ORGANIZATION); user.locality = config.getString(USER, username, LOCALITY); user.stateProvince = config.getString(USER, username, STATEPROVINCE); user.countryCode = config.getString(USER, username, COUNTRYCODE); user.cookie = config.getString(USER, username, COOKIE); user.getPreferences().locale = config.getString(USER, username, LOCALE); if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) { user.cookie = StringUtils.getSHA1(user.username + user.password); } // 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); // repository memberships if (!user.canAdmin) { // non-admin, read permissions Set<String> repositories = new HashSet<String>(Arrays.asList(config .getStringList(USER, username, REPOSITORY))); for (String repository : repositories) { user.addRepositoryPermission(repository); } } // starred repositories Set<String> starred = new HashSet<String>(Arrays.asList(config .getStringList(USER, username, STARRED))); for (String repository : starred) { UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(repository); prefs.starred = true; } // update cache users.put(user.username, user); if (!StringUtils.isEmpty(user.cookie)) { cookies.put(user.cookie, user); } } // load the teams Set<String> teamnames = config.getSubsections(TEAM); for (String teamname : teamnames) { 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); if (!team.canAdmin) { // non-admin team, read permissions team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname, REPOSITORY))); } team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER))); team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname, MAILINGLIST))); team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM, teamname, PRERECEIVE))); team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM, teamname, POSTRECEIVE))); teams.put(team.name.toLowerCase(), team); // set the teams on the users for (String user : team.users) { UserModel model = users.get(user); if (model != null) { model.teams.add(team); } } } } catch (Exception e) { logger.error(MessageFormat.format("Failed to read {0}", realmFile), e); } } } protected long lastModified() { return lastModified; } @Override public String toString() { return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")"; } } src/main/java/com/gitblit/Constants.java
New file @@ -0,0 +1,494 @@ /* * 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.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.URL; import java.util.jar.Attributes; import java.util.jar.Manifest; /** * Constant values used by Gitblit. * * @author James Moger * */ public class Constants { public static final String NAME = "Gitblit"; public static final String FULL_NAME = "Gitblit - a pure Java Git solution"; public static final String ADMIN_ROLE = "#admin"; public static final String FORK_ROLE = "#fork"; public static final String CREATE_ROLE = "#create"; public static final String NOT_FEDERATED_ROLE = "#notfederated"; public static final String NO_ROLE = "#none"; public static final String EXTERNAL_ACCOUNT = "#externalAccount"; public static final String PROPERTIES_FILE = "gitblit.properties"; public static final String GIT_PATH = "/git/"; public static final String ZIP_PATH = "/zip/"; public static final String SYNDICATION_PATH = "/feed/"; public static final String FEDERATION_PATH = "/federation/"; public static final String RPC_PATH = "/rpc/"; public static final String PAGES = "/pages/"; public static final String SPARKLESHARE_INVITE_PATH = "/sparkleshare/"; public static final String BORDER = "***********************************************************"; public static final String FEDERATION_USER = "$gitblit"; public static final String PROPOSAL_EXT = ".json"; public static final String ENCODING = "UTF-8"; public static final int LEN_SHORTLOG = 78; public static final int LEN_SHORTLOG_REFS = 60; public static final String DEFAULT_BRANCH = "default"; public static final String CONFIG_GITBLIT = "gitblit"; public static final String CONFIG_CUSTOM_FIELDS = "customFields"; public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ"; public static final String baseFolder = "baseFolder"; public static final String baseFolder$ = "${" + baseFolder + "}"; public static final String contextFolder$ = "${contextFolder}"; public static final String HEAD = "HEAD"; public static final String R_GITBLIT = "refs/gitblit/"; public static final String R_HEADS = "refs/heads/"; public static final String R_NOTES = "refs/notes/"; public static final String R_CHANGES = "refs/changes/"; public static final String R_PULL= "refs/pull/"; public static final String R_TAGS = "refs/tags/"; public static final String R_REMOTES = "refs/remotes/"; public static String getVersion() { String v = Constants.class.getPackage().getImplementationVersion(); if (v == null) { return "0.0.0-SNAPSHOT"; } return v; } public static String getGitBlitVersion() { return NAME + " v" + getVersion(); } public static String getBuildDate() { return getManifestValue("build-date", "PENDING"); } private static String getManifestValue(String attrib, String defaultValue) { Class<?> clazz = Constants.class; String className = clazz.getSimpleName() + ".class"; String classPath = clazz.getResource(className).toString(); if (!classPath.startsWith("jar")) { // Class not from JAR return defaultValue; } try { String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; Manifest manifest = new Manifest(new URL(manifestPath).openStream()); Attributes attr = manifest.getMainAttributes(); String value = attr.getValue(attrib); return value; } catch (Exception e) { } return defaultValue; } /** * Enumeration representing the four access restriction levels. */ public static enum AccessRestrictionType { NONE, PUSH, CLONE, VIEW; public static AccessRestrictionType fromName(String name) { for (AccessRestrictionType type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return NONE; } public boolean exceeds(AccessRestrictionType type) { return this.ordinal() > type.ordinal(); } public boolean atLeast(AccessRestrictionType type) { return this.ordinal() >= type.ordinal(); } public String toString() { return name(); } public boolean isValidPermission(AccessPermission permission) { switch (this) { case VIEW: // VIEW restriction // all access permissions are valid return true; case CLONE: // CLONE restriction // only CLONE or greater access permissions are valid return permission.atLeast(AccessPermission.CLONE); case PUSH: // PUSH restriction // only PUSH or greater access permissions are valid return permission.atLeast(AccessPermission.PUSH); case NONE: // NO access restriction // all access permissions are invalid return false; } return false; } } /** * Enumeration representing the types of authorization control for an * access restricted resource. */ public static enum AuthorizationControl { AUTHENTICATED, NAMED; public static AuthorizationControl fromName(String name) { for (AuthorizationControl type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return NAMED; } public String toString() { return name(); } } /** * Enumeration representing the types of federation tokens. */ public static enum FederationToken { ALL, USERS_AND_REPOSITORIES, REPOSITORIES; public static FederationToken fromName(String name) { for (FederationToken type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return REPOSITORIES; } public String toString() { return name(); } } /** * Enumeration representing the types of federation requests. */ public static enum FederationRequest { POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_TEAMS, PULL_SETTINGS, PULL_SCRIPTS, STATUS; public static FederationRequest fromName(String name) { for (FederationRequest type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return PULL_REPOSITORIES; } public String toString() { return name(); } } /** * Enumeration representing the statii of federation requests. */ public static enum FederationPullStatus { PENDING, FAILED, SKIPPED, PULLED, MIRRORED, NOCHANGE, EXCLUDED; public static FederationPullStatus fromName(String name) { for (FederationPullStatus type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return PENDING; } @Override public String toString() { return name(); } } /** * Enumeration representing the federation types. */ public static enum FederationStrategy { EXCLUDE, FEDERATE_THIS, FEDERATE_ORIGIN; public static FederationStrategy fromName(String name) { for (FederationStrategy type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return FEDERATE_THIS; } public boolean exceeds(FederationStrategy type) { return this.ordinal() > type.ordinal(); } public boolean atLeast(FederationStrategy type) { return this.ordinal() >= type.ordinal(); } @Override public String toString() { return name(); } } /** * Enumeration representing the possible results of federation proposal * requests. */ public static enum FederationProposalResult { ERROR, FEDERATION_DISABLED, MISSING_DATA, NO_PROPOSALS, NO_POKE, ACCEPTED; @Override public String toString() { return name(); } } /** * Enumeration representing the possible remote procedure call requests from * a client. */ public static enum RpcRequest { // Order is important here. anything above LIST_SETTINGS requires // administrator privileges and web.allowRpcManagement. CLEAR_REPOSITORY_CACHE, GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, LIST_SETTINGS, CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY, LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER, LIST_TEAMS, CREATE_TEAM, EDIT_TEAM, DELETE_TEAM, LIST_REPOSITORY_MEMBERS, SET_REPOSITORY_MEMBERS, LIST_REPOSITORY_TEAMS, SET_REPOSITORY_TEAMS, LIST_REPOSITORY_MEMBER_PERMISSIONS, SET_REPOSITORY_MEMBER_PERMISSIONS, LIST_REPOSITORY_TEAM_PERMISSIONS, SET_REPOSITORY_TEAM_PERMISSIONS, LIST_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS, EDIT_SETTINGS, LIST_STATUS; public static RpcRequest fromName(String name) { for (RpcRequest type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return null; } public boolean exceeds(RpcRequest type) { return this.ordinal() > type.ordinal(); } @Override public String toString() { return name(); } } /** * Enumeration of the search types. */ public static enum SearchType { AUTHOR, COMMITTER, COMMIT; public static SearchType forName(String name) { for (SearchType type : values()) { if (type.name().equalsIgnoreCase(name)) { return type; } } return COMMIT; } @Override public String toString() { return name().toLowerCase(); } } /** * The types of objects that can be indexed and queried. */ public static enum SearchObjectType { commit, blob, issue; static SearchObjectType fromName(String name) { for (SearchObjectType value : values()) { if (value.name().equals(name)) { return value; } } return null; } } /** * The access permissions available for a repository. */ public static enum AccessPermission { NONE("N"), EXCLUDE("X"), VIEW("V"), CLONE("R"), PUSH("RW"), CREATE("RWC"), DELETE("RWD"), REWIND("RW+"), OWNER("RW+"); public static final AccessPermission [] NEWPERMISSIONS = { EXCLUDE, VIEW, CLONE, PUSH, CREATE, DELETE, REWIND }; public static AccessPermission LEGACY = REWIND; public final String code; private AccessPermission(String code) { this.code = code; } public boolean atMost(AccessPermission perm) { return ordinal() <= perm.ordinal(); } public boolean atLeast(AccessPermission perm) { return ordinal() >= perm.ordinal(); } public boolean exceeds(AccessPermission perm) { return ordinal() > perm.ordinal(); } public String asRole(String repository) { return code + ":" + repository; } @Override public String toString() { return code; } public static AccessPermission permissionFromRole(String role) { String [] fields = role.split(":", 2); if (fields.length == 1) { // legacy/undefined assume full permissions return AccessPermission.LEGACY; } else { // code:repository return AccessPermission.fromCode(fields[0]); } } public static String repositoryFromRole(String role) { String [] fields = role.split(":", 2); if (fields.length == 1) { // legacy/undefined assume full permissions return role; } else { // code:repository return fields[1]; } } public static AccessPermission fromCode(String code) { for (AccessPermission perm : values()) { if (perm.code.equalsIgnoreCase(code)) { return perm; } } return AccessPermission.NONE; } } public static enum RegistrantType { REPOSITORY, USER, TEAM; } public static enum PermissionType { MISSING, ANONYMOUS, EXPLICIT, TEAM, REGEX, OWNER, ADMINISTRATOR; } public static enum GCStatus { READY, COLLECTING; public boolean exceeds(GCStatus s) { return ordinal() > s.ordinal(); } } public static enum AuthenticationType { CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER; public boolean isStandard() { return ordinal() <= COOKIE.ordinal(); } } public static enum AccountType { LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS; public boolean isLocal() { return this == LOCAL; } } @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Unused { } } src/main/java/com/gitblit/DownloadZipFilter.java
src/main/java/com/gitblit/DownloadZipServlet.java
New file @@ -0,0 +1,218 @@ /* * 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.IOException; import java.net.SocketException; import java.text.MessageFormat; import java.text.ParseException; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.utils.CompressionUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; /** * Streams out a zip file from the specified repository for any tree path at any * revision. * * @author James Moger * */ public class DownloadZipServlet extends HttpServlet { private static final long serialVersionUID = 1L; private transient Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class); public static enum Format { zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2"); public final String extension; Format(String ext) { this.extension = ext; } public static Format fromName(String name) { for (Format format : values()) { if (format.name().equalsIgnoreCase(name)) { return format; } } return zip; } } public DownloadZipServlet() { super(); } /** * Returns an url to this servlet for the specified parameters. * * @param baseURL * @param repository * @param objectId * @param path * @param format * @return an url */ public static String asLink(String baseURL, String repository, String objectId, String path, Format format) { if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { baseURL = baseURL.substring(0, baseURL.length() - 1); } return baseURL + Constants.ZIP_PATH + "?r=" + repository + (path == null ? "" : ("&p=" + path)) + (objectId == null ? "" : ("&h=" + objectId)) + (format == null ? "" : ("&format=" + format.name())); } /** * Creates a zip stream from the repository of the requested data. * * @param request * @param response * @throws javax.servlet.ServletException * @throws java.io.IOException */ private void processRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { if (!GitBlit.getBoolean(Keys.web.allowZipDownloads, true)) { logger.warn("Zip downloads are disabled"); response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } Format format = Format.zip; String repository = request.getParameter("r"); String basePath = request.getParameter("p"); String objectId = request.getParameter("h"); String f = request.getParameter("format"); if (!StringUtils.isEmpty(f)) { format = Format.fromName(f); } try { String name = repository; if (name.indexOf('/') > -1) { name = name.substring(name.lastIndexOf('/') + 1); } name = StringUtils.stripDotGit(name); if (!StringUtils.isEmpty(basePath)) { name += "-" + basePath.replace('/', '_'); } if (!StringUtils.isEmpty(objectId)) { name += "-" + objectId; } Repository r = GitBlit.self().getRepository(repository); if (r == null) { if (GitBlit.self().isCollectingGarbage(repository)) { error(response, MessageFormat.format("# Error\nGitblit is busy collecting garbage in {0}", repository)); return; } else { error(response, MessageFormat.format("# Error\nFailed to find repository {0}", repository)); return; } } RevCommit commit = JGitUtils.getCommit(r, objectId); if (commit == null) { error(response, MessageFormat.format("# Error\nFailed to find commit {0}", objectId)); r.close(); return; } Date date = JGitUtils.getCommitDate(commit); String contentType = "application/octet-stream"; response.setContentType(contentType + "; charset=" + response.getCharacterEncoding()); response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\""); response.setDateHeader("Last-Modified", date.getTime()); response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0); try { switch (format) { case zip: CompressionUtils.zip(r, basePath, objectId, response.getOutputStream()); break; case tar: CompressionUtils.tar(r, basePath, objectId, response.getOutputStream()); break; case gz: CompressionUtils.gz(r, basePath, objectId, response.getOutputStream()); break; case xz: CompressionUtils.xz(r, basePath, objectId, response.getOutputStream()); break; case bzip2: CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream()); break; } response.flushBuffer(); } catch (SocketException t) { String message = t.getMessage() == null ? "" : t.getMessage().toLowerCase(); if (message.contains("reset") || message.contains("broken pipe")) { logger.error("Client aborted zip download: " + message); } else { logger.error("Failed to write attachment to client", t); } } catch (Throwable t) { logger.error("Failed to write attachment to client", t); } // close the repository r.close(); } catch (Throwable t) { logger.error("Failed to write attachment to client", t); } } private void error(HttpServletResponse response, String mkd) throws ServletException, IOException, ParseException { String content = MarkdownUtils.transformMarkdown(mkd); response.setContentType("text/html; charset=" + Constants.ENCODING); response.getWriter().write(content); } @Override protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { processRequest(request, response); } @Override protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { processRequest(request, response); } } src/main/java/com/gitblit/EnforceAuthenticationFilter.java
New file @@ -0,0 +1,102 @@ /* * Copyright 2013 Laurens Vrijnsen * 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 java.io.IOException; import java.text.MessageFormat; 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.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.UserModel; /** * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so. * It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if * both are true, any unauthorized access will be met with a HTTP Basic Authentication header. * * @author Laurens Vrijnsen * */ public class EnforceAuthenticationFilter implements Filter { protected transient Logger logger = LoggerFactory.getLogger(getClass()); /* * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ @Override public void init(FilterConfig filterConfig) throws ServletException { // nothing to be done } //init /* * This does the actual filtering: is the user authenticated? If not, enforce HTTP authentication (401) * * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { /* * Determine whether to enforce the BASIC authentication: */ @SuppressWarnings("static-access") Boolean mustForceAuth = GitBlit.self().getBoolean(Keys.web.authenticateViewPages, false) && GitBlit.self().getBoolean(Keys.web.enforceHttpBasicAuthentication, false); HttpServletRequest HttpRequest = (HttpServletRequest)request; HttpServletResponse HttpResponse = (HttpServletResponse)response; UserModel user = GitBlit.self().authenticate(HttpRequest); if (mustForceAuth && (user == null)) { // not authenticated, enforce now: logger.debug(MessageFormat.format("EnforceAuthFilter: user not authenticated for URL {0}!", request.toString())); @SuppressWarnings("static-access") String CHALLENGE = MessageFormat.format("Basic realm=\"{0}\"", GitBlit.self().getString("web.siteName","")); HttpResponse.setHeader("WWW-Authenticate", CHALLENGE); HttpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } else { // user is authenticated, or don't care, continue handling chain.doFilter( request, response ); } // authenticated } // doFilter /* * @see javax.servlet.Filter#destroy() */ @Override public void destroy() { // Nothing to be done } // destroy } src/main/java/com/gitblit/FederationClient.java
New file @@ -0,0 +1,143 @@ /* * 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.List; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import com.gitblit.models.FederationModel; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.StringUtils; /** * Command-line client to pull federated Gitblit repositories. * * @author James Moger * */ public class FederationClient { public static void main(String[] args) { Params params = new Params(); JCommander jc = new JCommander(params); try { jc.parse(args); } catch (ParameterException t) { usage(jc, 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); IStoredSettings 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.frequency = params.frequency; 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 GitBlit.self().configureContext(settings, baseFolder, false); FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon); executor.run(); if (!params.isDaemon) { System.out.println("Finished."); System.exit(0); } } private static void usage(JCommander jc, ParameterException t) { System.out.println(Constants.getGitBlitVersion()); System.out.println(); if (t != null) { System.out.println(t.getMessage()); System.out.println(); } if (jc != null) { jc.usage(); } System.exit(0); } /** * JCommander Parameters class for FederationClient. */ @Parameters(separators = " ") private static class Params { @Parameter(names = { "--registrations" }, description = "Gitblit Federation Registrations File", required = false) public String registrationsFile = "${baseFolder}/federation.properties"; @Parameter(names = { "--daemon" }, description = "Runs in daemon mode to schedule and pull repositories", required = false) public boolean isDaemon; @Parameter(names = { "--url" }, description = "URL of Gitblit instance to mirror from", required = false) public String url; @Parameter(names = { "--mirror" }, description = "Mirror repositories", required = false) public boolean mirror; @Parameter(names = { "--bare" }, description = "Create bare repositories", required = false) public boolean bare; @Parameter(names = { "--token" }, description = "Federation Token", required = false) public String token; @Parameter(names = { "--frequency" }, description = "Period to wait between pull attempts (requires --daemon)", required = false) public String frequency = "60 mins"; @Parameter(names = { "--baseFolder" }, description = "Base folder for received data", required = false) public String baseFolder; @Parameter(names = { "--repositoriesFolder" }, description = "Destination folder for cloned repositories", required = false) public String repositoriesFolder; } } src/main/java/com/gitblit/FederationPullExecutor.java
New file @@ -0,0 +1,523 @@ /* * 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 static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.InetAddress; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.FederationPullStatus; import com.gitblit.Constants.FederationStrategy; import com.gitblit.GitBlitException.ForbiddenException; import com.gitblit.models.FederationModel; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.FileUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.JGitUtils.CloneResult; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; /** * FederationPullExecutor pulls repository updates and, optionally, user * accounts and server settings from registered Gitblit instances. */ public class FederationPullExecutor implements Runnable { private final Logger logger = LoggerFactory.getLogger(FederationPullExecutor.class); private final List<FederationModel> registrations; private final boolean isDaemon; /** * Constructor for specifying a single federation registration. This * constructor is used to schedule the next pull execution. * * @param registration */ private FederationPullExecutor(FederationModel registration) { this(Arrays.asList(registration), true); } /** * Constructor to specify a group of federation registrations. This is * normally used at startup to pull and then schedule the next update based * on each registrations frequency setting. * * @param registrations * @param isDaemon * if true, registrations are rescheduled in perpetuity. if * false, the federation pull operation is executed once. */ public FederationPullExecutor(List<FederationModel> registrations, boolean isDaemon) { this.registrations = registrations; this.isDaemon = isDaemon; } /** * Run method for this pull executor. */ @Override public void run() { for (FederationModel registration : registrations) { FederationPullStatus was = registration.getLowestStatus(); try { Date now = new Date(System.currentTimeMillis()); pull(registration); sendStatusAcknowledgment(registration); registration.lastPull = now; FederationPullStatus is = registration.getLowestStatus(); if (is.ordinal() < was.ordinal()) { // the status for this registration has downgraded logger.warn("Federation pull status of {0} is now {1}", registration.name, is.name()); if (registration.notifyOnError) { String message = "Federation pull of " + registration.name + " @ " + registration.url + " is now at " + is.name(); GitBlit.self() .sendMailToAdministrators( "Pull Status of " + registration.name + " is " + is.name(), message); } } } catch (Throwable t) { logger.error(MessageFormat.format( "Failed to pull from federated gitblit ({0} @ {1})", registration.name, registration.url), t); } finally { if (isDaemon) { schedule(registration); } } } } /** * Mirrors a repository and, optionally, the server's users, and/or * configuration settings from a origin Gitblit instance. * * @param registration * @throws Exception */ private void pull(FederationModel registration) throws Exception { Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration, true); String registrationFolder = registration.folder.toLowerCase().trim(); // confirm valid characters in server alias Character c = StringUtils.findInvalidCharacter(registrationFolder); if (c != null) { logger.error(MessageFormat .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!", c, registrationFolder, registration.name)); return; } File repositoriesFolder = GitBlit.getRepositoriesFolder(); File registrationFolderFile = new File(repositoriesFolder, registrationFolder); registrationFolderFile.mkdirs(); // Clone/Pull the repository for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) { String cloneUrl = entry.getKey(); RepositoryModel repository = entry.getValue(); if (!repository.hasCommits) { logger.warn(MessageFormat.format( "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.", repository.name, registration.name, registration.url)); registration.updateStatus(repository, FederationPullStatus.SKIPPED); continue; } // Determine local repository name String repositoryName; if (StringUtils.isEmpty(registrationFolder)) { repositoryName = repository.name; } else { repositoryName = registrationFolder + "/" + repository.name; } if (registration.bare) { // bare repository, ensure .git suffix if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { repositoryName += DOT_GIT_EXT; } } else { // normal repository, strip .git suffix if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { repositoryName = repositoryName.substring(0, repositoryName.indexOf(DOT_GIT_EXT)); } } // confirm that the origin of any pre-existing repository matches // the clone url String fetchHead = null; Repository existingRepository = GitBlit.self().getRepository(repositoryName); if (existingRepository == null && GitBlit.self().isCollectingGarbage(repositoryName)) { logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName)); continue; } if (existingRepository != null) { StoredConfig config = existingRepository.getConfig(); config.load(); String origin = config.getString("remote", "origin", "url"); RevCommit commit = JGitUtils.getCommit(existingRepository, org.eclipse.jgit.lib.Constants.FETCH_HEAD); if (commit != null) { fetchHead = commit.getName(); } existingRepository.close(); if (!origin.startsWith(registration.url)) { logger.warn(MessageFormat .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.", repository.name, registration.name, registration.url)); registration.updateStatus(repository, FederationPullStatus.SKIPPED); continue; } } // clone/pull this repository CredentialsProvider credentials = new UsernamePasswordCredentialsProvider( Constants.FEDERATION_USER, registration.token); logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}", repository.name, registration.name, registration.url)); CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name, cloneUrl, registration.bare, credentials); Repository r = GitBlit.self().getRepository(repositoryName); RepositoryModel rm = GitBlit.self().getRepositoryModel(repositoryName); repository.isFrozen = registration.mirror; if (result.createdRepository) { // default local settings repository.federationStrategy = FederationStrategy.EXCLUDE; repository.isFrozen = registration.mirror; repository.showRemoteBranches = !registration.mirror; logger.info(MessageFormat.format(" cloning {0}", repository.name)); registration.updateStatus(repository, FederationPullStatus.MIRRORED); } else { // fetch and update boolean fetched = false; RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD); String newFetchHead = commit.getName(); fetched = fetchHead == null || !fetchHead.equals(newFetchHead); if (registration.mirror) { // mirror if (fetched) { // update local branches to match the remote tracking branches for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) { if (ref.displayName.startsWith("origin/")) { String branch = org.eclipse.jgit.lib.Constants.R_HEADS + ref.displayName.substring(ref.displayName.indexOf('/') + 1); String hash = ref.getReferencedObjectId().getName(); JGitUtils.setBranchRef(r, branch, hash); logger.info(MessageFormat.format(" resetting {0} of {1} to {2}", branch, repository.name, hash)); } } String newHead; if (StringUtils.isEmpty(repository.HEAD)) { newHead = newFetchHead; } else { newHead = repository.HEAD; } JGitUtils.setHEADtoRef(r, newHead); logger.info(MessageFormat.format(" resetting HEAD of {0} to {1}", repository.name, newHead)); registration.updateStatus(repository, FederationPullStatus.MIRRORED); } else { // indicate no commits pulled registration.updateStatus(repository, FederationPullStatus.NOCHANGE); } } else { // non-mirror if (fetched) { // indicate commits pulled to origin/master registration.updateStatus(repository, FederationPullStatus.PULLED); } else { // indicate no commits pulled registration.updateStatus(repository, FederationPullStatus.NOCHANGE); } } // preserve local settings repository.isFrozen = rm.isFrozen; repository.federationStrategy = rm.federationStrategy; // merge federation sets Set<String> federationSets = new HashSet<String>(); if (rm.federationSets != null) { federationSets.addAll(rm.federationSets); } if (repository.federationSets != null) { federationSets.addAll(repository.federationSets); } repository.federationSets = new ArrayList<String>(federationSets); // merge indexed branches Set<String> indexedBranches = new HashSet<String>(); if (rm.indexedBranches != null) { indexedBranches.addAll(rm.indexedBranches); } if (repository.indexedBranches != null) { indexedBranches.addAll(repository.indexedBranches); } repository.indexedBranches = new ArrayList<String>(indexedBranches); } // only repositories that are actually _cloned_ from the origin // Gitblit repository are marked as federated. If the origin // is from somewhere else, these repositories are not considered // "federated" repositories. repository.isFederated = cloneUrl.startsWith(registration.url); GitBlit.self().updateConfiguration(r, repository); r.close(); } IUserService userService = null; try { // Pull USERS // TeamModels are automatically pulled because they are contained // within the UserModel. The UserService creates unknown teams // and updates existing teams. Collection<UserModel> users = FederationUtils.getUsers(registration); if (users != null && users.size() > 0) { File realmFile = new File(registrationFolderFile, registration.name + "_users.conf"); realmFile.delete(); userService = new ConfigUserService(realmFile); for (UserModel user : users) { userService.updateUserModel(user.username, user); // merge the origin permissions and origin accounts into // the user accounts of this Gitblit instance if (registration.mergeAccounts) { // reparent all repository permissions if the local // repositories are stored within subfolders if (!StringUtils.isEmpty(registrationFolder)) { if (user.permissions != null) { // pulling from >= 1.2 version Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions); user.permissions.clear(); for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) { user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue()); } } else { // pulling from <= 1.1 version List<String> permissions = new ArrayList<String>(user.repositories); user.repositories.clear(); for (String permission : permissions) { user.addRepositoryPermission(registrationFolder + "/" + permission); } } } // insert new user or update local user UserModel localUser = GitBlit.self().getUserModel(user.username); if (localUser == null) { // create new local user GitBlit.self().updateUserModel(user.username, user, true); } else { // update repository permissions of local user if (user.permissions != null) { // pulling from >= 1.2 version Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions); for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) { localUser.setRepositoryPermission(entry.getKey(), entry.getValue()); } } else { // pulling from <= 1.1 version for (String repository : user.repositories) { localUser.addRepositoryPermission(repository); } } localUser.password = user.password; localUser.canAdmin = user.canAdmin; GitBlit.self().updateUserModel(localUser.username, localUser, false); } for (String teamname : GitBlit.self().getAllTeamnames()) { TeamModel team = GitBlit.self().getTeamModel(teamname); if (user.isTeamMember(teamname) && !team.hasUser(user.username)) { // new team member team.addUser(user.username); GitBlit.self().updateTeamModel(teamname, team, false); } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) { // remove team member team.removeUser(user.username); GitBlit.self().updateTeamModel(teamname, team, false); } // update team repositories TeamModel remoteTeam = user.getTeam(teamname); if (remoteTeam != null) { if (remoteTeam.permissions != null) { // pulling from >= 1.2 for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){ team.setRepositoryPermission(entry.getKey(), entry.getValue()); } GitBlit.self().updateTeamModel(teamname, team, false); } else if(!ArrayUtils.isEmpty(remoteTeam.repositories)) { // pulling from <= 1.1 team.addRepositoryPermissions(remoteTeam.repositories); GitBlit.self().updateTeamModel(teamname, team, false); } } } } } } } catch (ForbiddenException e) { // ignore forbidden exceptions } catch (IOException e) { logger.warn(MessageFormat.format( "Failed to retrieve USERS from federated gitblit ({0} @ {1})", registration.name, registration.url), e); } try { // Pull TEAMS // We explicitly pull these even though they are embedded in // UserModels because it is possible to use teams to specify // mailing lists or push scripts without specifying users. if (userService != null) { Collection<TeamModel> teams = FederationUtils.getTeams(registration); if (teams != null && teams.size() > 0) { for (TeamModel team : teams) { userService.updateTeamModel(team); } } } } catch (ForbiddenException e) { // ignore forbidden exceptions } catch (IOException e) { logger.warn(MessageFormat.format( "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})", registration.name, registration.url), e); } try { // Pull SETTINGS Map<String, String> settings = FederationUtils.getSettings(registration); if (settings != null && settings.size() > 0) { Properties properties = new Properties(); properties.putAll(settings); FileOutputStream os = new FileOutputStream(new File(registrationFolderFile, registration.name + "_" + Constants.PROPERTIES_FILE)); properties.store(os, null); os.close(); } } catch (ForbiddenException e) { // ignore forbidden exceptions } catch (IOException e) { logger.warn(MessageFormat.format( "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})", registration.name, registration.url), e); } try { // Pull SCRIPTS Map<String, String> scripts = FederationUtils.getScripts(registration); if (scripts != null && scripts.size() > 0) { for (Map.Entry<String, String> script : scripts.entrySet()) { String scriptName = script.getKey(); if (scriptName.endsWith(".groovy")) { scriptName = scriptName.substring(0, scriptName.indexOf(".groovy")); } File file = new File(registrationFolderFile, registration.name + "_" + scriptName + ".groovy"); FileUtils.writeContent(file, script.getValue()); } } } catch (ForbiddenException e) { // ignore forbidden exceptions } catch (IOException e) { logger.warn(MessageFormat.format( "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})", registration.name, registration.url), e); } } /** * Sends a status acknowledgment to the origin Gitblit instance. This * includes the results of the federated pull. * * @param registration * @throws Exception */ private void sendStatusAcknowledgment(FederationModel registration) throws Exception { if (!registration.sendStatus) { // skip status acknowledgment return; } InetAddress addr = InetAddress.getLocalHost(); String federationName = GitBlit.getString(Keys.federation.name, null); if (StringUtils.isEmpty(federationName)) { federationName = addr.getHostName(); } FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration); logger.info(MessageFormat.format("Pull status sent to {0}", registration.url)); } /** * Schedules the next check of the federated Gitblit instance. * * @param registration */ private void schedule(FederationModel registration) { // schedule the next pull int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency); registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L)); GitBlit.self().executor() .schedule(new FederationPullExecutor(registration), mins, TimeUnit.MINUTES); logger.info(MessageFormat.format( "Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}", registration.name, registration.url, registration.nextPull)); } } src/main/java/com/gitblit/FederationServlet.java
src/main/java/com/gitblit/FileSettings.java
src/main/java/com/gitblit/FileUserService.java
New file @@ -0,0 +1,1146 @@ /* * 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.io.FileWriter; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * FileUserService is Gitblit's original default user service implementation. * * Users and their repository memberships are stored in a simple properties file * which is cached and dynamically reloaded when modified. * * This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService * which is still a human-readable, editable, plain-text file but it is more * flexible for storing additional fields. * * @author James Moger * */ @Deprecated public class FileUserService extends FileSettings implements IUserService { private final Logger logger = LoggerFactory.getLogger(FileUserService.class); private final Map<String, String> cookies = new ConcurrentHashMap<String, String>(); private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>(); public FileUserService(File realmFile) { super(realmFile.getAbsolutePath()); } /** * Setup the user service. * * @param settings * @since 0.7.0 */ @Override public void setup(IStoredSettings settings) { } /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return true; } /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return false; } /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { return false; } /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return true; } /** * Does the user service support cookie authentication? * * @return true or false */ @Override public boolean supportsCookies() { return true; } /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ @Override public String getCookie(UserModel model) { if (!StringUtils.isEmpty(model.cookie)) { return model.cookie; } Properties allUsers = super.read(); String value = allUsers.getProperty(model.username); String[] roles = value.split(","); String password = roles[0]; String cookie = StringUtils.getSHA1(model.username + password); return cookie; } /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ @Override public UserModel authenticate(char[] cookie) { String hash = new String(cookie); if (StringUtils.isEmpty(hash)) { return null; } read(); UserModel model = null; if (cookies.containsKey(hash)) { String username = cookies.get(hash); model = getUserModel(username); } return model; } /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { Properties allUsers = read(); String userInfo = allUsers.getProperty(username); if (StringUtils.isEmpty(userInfo)) { return null; } UserModel returnedUser = null; UserModel user = getUserModel(username); if (user.password.startsWith(StringUtils.MD5_TYPE)) { // password digest String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { // username+password digest String md5 = StringUtils.COMBINED_MD5_TYPE + StringUtils.getMD5(username.toLowerCase() + new String(password)); if (user.password.equalsIgnoreCase(md5)) { returnedUser = user; } } else if (user.password.equals(new String(password))) { // plain-text password returnedUser = user; } return returnedUser; } /** * Logout a user. * * @param user */ @Override public void logout(UserModel user) { } /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ @Override public UserModel getUserModel(String username) { Properties allUsers = read(); String userInfo = allUsers.getProperty(username.toLowerCase()); if (userInfo == null) { return null; } UserModel model = new UserModel(username.toLowerCase()); String[] userValues = userInfo.split(","); model.password = userValues[0]; for (int i = 1; i < userValues.length; i++) { String role = userValues[i]; switch (role.charAt(0)) { case '#': // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { model.canAdmin = true; } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { model.canFork = true; } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) { model.canCreate = true; } else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) { model.excludeFromFederation = true; } break; default: model.addRepositoryPermission(role); } } // set the teams for the user for (TeamModel team : teams.values()) { if (team.hasUser(username)) { model.teams.add(DeepCopier.copy(team)); } } return model; } /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ @Override public boolean updateUserModel(UserModel model) { return updateUserModel(model.username, model); } /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ @Override public boolean updateUserModels(Collection<UserModel> models) { try { Properties allUsers = read(); for (UserModel model : models) { updateUserCache(allUsers, model.username, model); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete user object keyed by username. * This method allows for renaming a user. * * @param username * the old username * @param model * the user object to use for username * @return true if update is successful */ @Override public boolean updateUserModel(String username, UserModel model) { try { Properties allUsers = read(); updateUserCache(allUsers, username, model); write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Updates/writes and replaces a complete user object keyed by username. * This method allows for renaming a user. * * @param username * the old username * @param model * the user object to use for username * @return true if update is successful */ private boolean updateUserCache(Properties allUsers, String username, UserModel model) { try { UserModel oldUser = getUserModel(username); List<String> roles; if (model.permissions == null) { roles = new ArrayList<String>(); } else { // discrete repository permissions roles = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git roles.add(entry.getValue().asRole(entry.getKey())); } } } // Permissions if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } StringBuilder sb = new StringBuilder(); if (!StringUtils.isEmpty(model.password)) { sb.append(model.password); } sb.append(','); for (String role : roles) { sb.append(role); sb.append(','); } // trim trailing comma sb.setLength(sb.length() - 1); allUsers.remove(username.toLowerCase()); allUsers.put(model.username.toLowerCase(), sb.toString()); // null check on "final" teams because JSON-sourced UserModel // can have a null teams object if (model.teams != null) { // update team cache for (TeamModel team : model.teams) { TeamModel t = getTeamModel(team.name); if (t == null) { // new team t = team; } t.removeUser(username); t.addUser(model.username); updateTeamCache(allUsers, t.name, t); } // check for implicit team removal if (oldUser != null) { for (TeamModel team : oldUser.teams) { if (!model.isTeamMember(team.name)) { team.removeUser(username); updateTeamCache(allUsers, team.name, team); } } } } return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update user model {0}!", model.username), t); } return false; } /** * Deletes the user object from the user service. * * @param model * @return true if successful */ @Override public boolean deleteUserModel(UserModel model) { return deleteUser(model.username); } /** * Delete the user object with the specified username * * @param username * @return true if successful */ @Override public boolean deleteUser(String username) { try { // Read realm file Properties allUsers = read(); UserModel user = getUserModel(username); allUsers.remove(username); for (TeamModel team : user.teams) { TeamModel t = getTeamModel(team.name); if (t == null) { // new team t = team; } t.removeUser(username); updateTeamCache(allUsers, t.name, t); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete user {0}!", username), t); } return false; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<String> getAllUsernames() { Properties allUsers = read(); List<String> list = new ArrayList<String>(); for (String user : allUsers.stringPropertyNames()) { if (user.charAt(0) == '@') { // skip team user definitions continue; } list.add(user); } Collections.sort(list); return list; } /** * Returns the list of all users available to the login service. * * @return list of all usernames */ @Override public List<UserModel> getAllUsers() { read(); List<UserModel> list = new ArrayList<UserModel>(); for (String username : getAllUsernames()) { list.add(getUserModel(username)); } Collections.sort(list); return list; } /** * Returns the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction */ @Override public List<String> getUsernamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { Properties allUsers = read(); for (String username : allUsers.stringPropertyNames()) { if (username.charAt(0) == '@') { continue; } String value = allUsers.getProperty(username); String[] values = value.split(","); // skip first value (password) for (int i = 1; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { list.add(username); break; } } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t); } Collections.sort(list); return list; } /** * Sets the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param usernames * @return true if successful */ @Override public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { try { Set<String> specifiedUsers = new HashSet<String>(usernames); Set<String> needsAddRole = new HashSet<String>(specifiedUsers); Set<String> needsRemoveRole = new HashSet<String>(); // identify users which require add and remove role Properties allUsers = read(); for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] values = value.split(","); // skip first value (password) for (int i = 1; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { // user has role, check against revised user list if (specifiedUsers.contains(username)) { needsAddRole.remove(username); } else { // remove role from user needsRemoveRole.add(username); } break; } } } // add roles to users for (String user : needsAddRole) { String userValues = allUsers.getProperty(user); userValues += "," + role; allUsers.put(user, userValues); } // remove role from user for (String user : needsRemoveRole) { String[] values = allUsers.getProperty(user).split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String value = values[i]; if (!value.equalsIgnoreCase(role)) { sb.append(value); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t); } return false; } /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ @Override public boolean renameRepositoryRole(String oldRole, String newRole) { try { Properties allUsers = read(); Set<String> needsRenameRole = new HashSet<String>(); // identify users which require role rename for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); // skip first value (password) for (int i = 1; i < roles.length; i++) { String repository = AccessPermission.repositoryFromRole(roles[i]); if (repository.equalsIgnoreCase(oldRole)) { needsRenameRole.add(username); break; } } } // rename role for identified users for (String user : needsRenameRole) { String userValues = allUsers.getProperty(user); String[] values = userValues.split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); sb.append(newRole); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String repository = AccessPermission.repositoryFromRole(values[i]); if (repository.equalsIgnoreCase(oldRole)) { AccessPermission permission = AccessPermission.permissionFromRole(values[i]); sb.append(permission.asRole(newRole)); sb.append(','); } else { sb.append(values[i]); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error( MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t); } return false; } /** * Removes a repository role from all users. * * @param role * @return true if successful */ @Override public boolean deleteRepositoryRole(String role) { try { Properties allUsers = read(); Set<String> needsDeleteRole = new HashSet<String>(); // identify users which require role rename for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); // skip first value (password) for (int i = 1; i < roles.length; i++) { String repository = AccessPermission.repositoryFromRole(roles[i]); if (repository.equalsIgnoreCase(role)) { needsDeleteRole.add(username); break; } } } // delete role for identified users for (String user : needsDeleteRole) { String userValues = allUsers.getProperty(user); String[] values = userValues.split(","); String password = values[0]; StringBuilder sb = new StringBuilder(); sb.append(password); sb.append(','); // skip first value (password) for (int i = 1; i < values.length; i++) { String repository = AccessPermission.repositoryFromRole(values[i]); if (!repository.equalsIgnoreCase(role)) { sb.append(values[i]); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(user, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete role {0}!", role), t); } return false; } /** * Writes the properties file. * * @param properties * @throws IOException */ private void write(Properties properties) throws IOException { // Write a temporary copy of the users file File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp"); FileWriter writer = new FileWriter(realmFileCopy); properties .store(writer, " Gitblit realm file format:\n username=password,\\#permission,repository1,repository2...\n @teamname=!username1,!username2,!username3,repository1,repository2..."); writer.close(); // If the write is successful, delete the current file and rename // the temporary copy to the original filename. if (realmFileCopy.exists() && realmFileCopy.length() > 0) { if (propertiesFile.exists()) { if (!propertiesFile.delete()) { throw new IOException(MessageFormat.format("Failed to delete {0}!", propertiesFile.getAbsolutePath())); } } if (!realmFileCopy.renameTo(propertiesFile)) { throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!", realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath())); } } else { throw new IOException(MessageFormat.format("Failed to save {0}!", realmFileCopy.getAbsolutePath())); } } /** * Reads the properties file and rebuilds the in-memory cookie lookup table. */ @Override protected synchronized Properties read() { long lastRead = lastModified(); boolean reload = forceReload(); Properties allUsers = super.read(); if (reload || (lastRead != lastModified())) { // reload hash cache cookies.clear(); teams.clear(); for (String username : allUsers.stringPropertyNames()) { String value = allUsers.getProperty(username); String[] roles = value.split(","); if (username.charAt(0) == '@') { // team definition TeamModel team = new TeamModel(username.substring(1)); List<String> repositories = new ArrayList<String>(); List<String> users = new ArrayList<String>(); List<String> mailingLists = new ArrayList<String>(); List<String> preReceive = new ArrayList<String>(); List<String> postReceive = new ArrayList<String>(); for (String role : roles) { if (role.charAt(0) == '!') { users.add(role.substring(1)); } else if (role.charAt(0) == '&') { mailingLists.add(role.substring(1)); } else if (role.charAt(0) == '^') { preReceive.add(role.substring(1)); } else if (role.charAt(0) == '%') { postReceive.add(role.substring(1)); } else { switch (role.charAt(0)) { case '#': // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { team.canAdmin = true; } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { team.canFork = true; } else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) { team.canCreate = true; } break; default: repositories.add(role); } repositories.add(role); } } if (!team.canAdmin) { // only read permissions for non-admin teams team.addRepositoryPermissions(repositories); } team.addUsers(users); team.addMailingLists(mailingLists); team.preReceiveScripts.addAll(preReceive); team.postReceiveScripts.addAll(postReceive); teams.put(team.name.toLowerCase(), team); } else { // user definition String password = roles[0]; cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase()); } } } return allUsers; } @Override public String toString() { return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")"; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<String> getAllTeamNames() { List<String> list = new ArrayList<String>(teams.keySet()); Collections.sort(list); return list; } /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ @Override public List<TeamModel> getAllTeams() { List<TeamModel> list = new ArrayList<TeamModel>(teams.values()); list = DeepCopier.copy(list); Collections.sort(list); return list; } /** * Returns the list of all teams who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @return list of all teamnames that can bypass the access restriction */ @Override public List<String> getTeamnamesForRepositoryRole(String role) { List<String> list = new ArrayList<String>(); try { Properties allUsers = read(); for (String team : allUsers.stringPropertyNames()) { if (team.charAt(0) != '@') { // skip users continue; } String value = allUsers.getProperty(team); String[] values = value.split(","); for (int i = 0; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { // strip leading @ list.add(team.substring(1)); break; } } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t); } Collections.sort(list); return list; } /** * Sets the list of all teams who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param teamnames * @return true if successful */ @Override public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) { try { Set<String> specifiedTeams = new HashSet<String>(teamnames); Set<String> needsAddRole = new HashSet<String>(specifiedTeams); Set<String> needsRemoveRole = new HashSet<String>(); // identify teams which require add and remove role Properties allUsers = read(); for (String team : allUsers.stringPropertyNames()) { if (team.charAt(0) != '@') { // skip users continue; } String name = team.substring(1); String value = allUsers.getProperty(team); String[] values = value.split(","); for (int i = 0; i < values.length; i++) { String r = values[i]; if (r.equalsIgnoreCase(role)) { // team has role, check against revised team list if (specifiedTeams.contains(name)) { needsAddRole.remove(name); } else { // remove role from team needsRemoveRole.add(name); } break; } } } // add roles to teams for (String name : needsAddRole) { String team = "@" + name; String teamValues = allUsers.getProperty(team); teamValues += "," + role; allUsers.put(team, teamValues); } // remove role from team for (String name : needsRemoveRole) { String team = "@" + name; String[] values = allUsers.getProperty(team).split(","); StringBuilder sb = new StringBuilder(); for (int i = 0; i < values.length; i++) { String value = values[i]; if (!value.equalsIgnoreCase(role)) { sb.append(value); sb.append(','); } } sb.setLength(sb.length() - 1); // update properties allUsers.put(team, sb.toString()); } // persist changes write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to set teamnames for role {0}!", role), t); } return false; } /** * Retrieve the team object for the specified team name. * * @param teamname * @return a team object or null * @since 0.8.0 */ @Override public TeamModel getTeamModel(String teamname) { read(); TeamModel team = teams.get(teamname.toLowerCase()); if (team != null) { // clone the model, otherwise all changes to this object are // live and unpersisted team = DeepCopier.copy(team); } return team; } /** * Updates/writes a complete team object. * * @param model * @return true if update is successful * @since 0.8.0 */ @Override public boolean updateTeamModel(TeamModel model) { return updateTeamModel(model.name, model); } /** * Updates/writes all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ public boolean updateTeamModels(Collection<TeamModel> models) { try { Properties allUsers = read(); for (TeamModel model : models) { updateTeamCache(allUsers, model.name, model); } write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t); } return false; } /** * Updates/writes and replaces a complete team object keyed by teamname. * This method allows for renaming a team. * * @param teamname * the old teamname * @param model * the team object to use for teamname * @return true if update is successful * @since 0.8.0 */ @Override public boolean updateTeamModel(String teamname, TeamModel model) { try { Properties allUsers = read(); updateTeamCache(allUsers, teamname, model); write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t); } return false; } private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) { StringBuilder sb = new StringBuilder(); List<String> roles; if (model.permissions == null) { // legacy, use repository list if (model.repositories != null) { roles = new ArrayList<String>(model.repositories); } else { roles = new ArrayList<String>(); } } else { // discrete repository permissions roles = new ArrayList<String>(); for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) { if (entry.getValue().exceeds(AccessPermission.NONE)) { // code:repository (e.g. RW+:~james/myrepo.git roles.add(entry.getValue().asRole(entry.getKey())); } } } // Permissions if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } if (model.canFork) { roles.add(Constants.FORK_ROLE); } if (model.canCreate) { roles.add(Constants.CREATE_ROLE); } for (String role : roles) { sb.append(role); sb.append(','); } if (!ArrayUtils.isEmpty(model.users)) { for (String user : model.users) { sb.append('!'); sb.append(user); sb.append(','); } } if (!ArrayUtils.isEmpty(model.mailingLists)) { for (String address : model.mailingLists) { sb.append('&'); sb.append(address); sb.append(','); } } if (!ArrayUtils.isEmpty(model.preReceiveScripts)) { for (String script : model.preReceiveScripts) { sb.append('^'); sb.append(script); sb.append(','); } } if (!ArrayUtils.isEmpty(model.postReceiveScripts)) { for (String script : model.postReceiveScripts) { sb.append('%'); sb.append(script); sb.append(','); } } // trim trailing comma sb.setLength(sb.length() - 1); allUsers.remove("@" + teamname); allUsers.put("@" + model.name, sb.toString()); // update team cache teams.remove(teamname.toLowerCase()); teams.put(model.name.toLowerCase(), model); } /** * Deletes the team object from the user service. * * @param model * @return true if successful * @since 0.8.0 */ @Override public boolean deleteTeamModel(TeamModel model) { return deleteTeam(model.name); } /** * Delete the team object with the specified teamname * * @param teamname * @return true if successful * @since 0.8.0 */ @Override public boolean deleteTeam(String teamname) { Properties allUsers = read(); teams.remove(teamname.toLowerCase()); allUsers.remove("@" + teamname); try { write(allUsers); return true; } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t); } return false; } } src/main/java/com/gitblit/GCExecutor.java
New file @@ -0,0 +1,239 @@ /* * 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; import java.lang.reflect.Field; import java.text.MessageFormat; import java.util.Calendar; import java.util.Date; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.api.GarbageCollectCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.RepositoryModel; import com.gitblit.utils.FileUtils; /** * The GC executor handles periodic garbage collection in repositories. * * @author James Moger * */ public class GCExecutor implements Runnable { public static enum GCStatus { READY, COLLECTING; public boolean exceeds(GCStatus s) { return ordinal() > s.ordinal(); } } private final Logger logger = LoggerFactory.getLogger(GCExecutor.class); private final IStoredSettings settings; private AtomicBoolean running = new AtomicBoolean(false); private AtomicBoolean forceClose = new AtomicBoolean(false); private final Map<String, GCStatus> gcCache = new ConcurrentHashMap<String, GCStatus>(); public GCExecutor(IStoredSettings settings) { this.settings = settings; } /** * Indicates if the GC executor is ready to process repositories. * * @return true if the GC executor is ready to process repositories */ public boolean isReady() { return settings.getBoolean(Keys.git.enableGarbageCollection, false); } public boolean isRunning() { return running.get(); } public boolean lock(String repositoryName) { return setGCStatus(repositoryName, GCStatus.COLLECTING); } /** * Tries to set a GCStatus for the specified repository. * * @param repositoryName * @return true if the status has been set */ private boolean setGCStatus(String repositoryName, GCStatus status) { String key = repositoryName.toLowerCase(); if (gcCache.containsKey(key)) { if (gcCache.get(key).exceeds(GCStatus.READY)) { // already collecting or blocked return false; } } gcCache.put(key, status); return true; } /** * Returns true if Gitblit is actively collecting garbage in this repository. * * @param repositoryName * @return true if actively collecting garbage */ public boolean isCollectingGarbage(String repositoryName) { String key = repositoryName.toLowerCase(); return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key)); } /** * Resets the GC status to ready. * * @param repositoryName */ public void releaseLock(String repositoryName) { gcCache.put(repositoryName.toLowerCase(), GCStatus.READY); } public void close() { forceClose.set(true); } @Override public void run() { if (!isReady()) { return; } running.set(true); Date now = new Date(); for (String repositoryName : GitBlit.self().getRepositoryList()) { if (forceClose.get()) { break; } if (isCollectingGarbage(repositoryName)) { logger.warn(MessageFormat.format("Already collecting garbage from {0}?!?", repositoryName)); continue; } boolean garbageCollected = false; RepositoryModel model = null; Repository repository = null; try { model = GitBlit.self().getRepositoryModel(repositoryName); repository = GitBlit.self().getRepository(repositoryName); if (repository == null) { logger.warn(MessageFormat.format("GCExecutor is missing repository {0}?!?", repositoryName)); continue; } if (!isRepositoryIdle(repository)) { logger.debug(MessageFormat.format("GCExecutor is skipping {0} because it is not idle", repositoryName)); continue; } // By setting the GCStatus to COLLECTING we are // disabling *all* access to this repository from Gitblit. // Think of this as a clutch in a manual transmission vehicle. if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) { logger.warn(MessageFormat.format("Can not acquire GC lock for {0}, skipping", repositoryName)); continue; } logger.debug(MessageFormat.format("GCExecutor locked idle repository {0}", repositoryName)); Git git = new Git(repository); GarbageCollectCommand gc = git.gc(); Properties stats = gc.getStatistics(); // determine if this is a scheduled GC Calendar cal = Calendar.getInstance(); cal.setTime(model.lastGC); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); cal.add(Calendar.DATE, model.gcPeriod); Date gcDate = cal.getTime(); boolean shouldCollectGarbage = now.after(gcDate); // determine if filesize triggered GC long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500*1024L); long sizeOfLooseObjects = (Long) stats.get("sizeOfLooseObjects"); boolean hasEnoughGarbage = sizeOfLooseObjects >= gcThreshold; // if we satisfy one of the requirements, GC boolean hasGarbage = sizeOfLooseObjects > 0; if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) { long looseKB = sizeOfLooseObjects/1024L; logger.info(MessageFormat.format("Collecting {1} KB of loose objects from {0}", repositoryName, looseKB)); // do the deed gc.call(); garbageCollected = true; } } catch (Exception e) { logger.error("Error collecting garbage in " + repositoryName, e); } finally { // cleanup if (repository != null) { if (garbageCollected) { // update the last GC date model.lastGC = new Date(); GitBlit.self().updateConfiguration(repository, model); } repository.close(); } // reset the GC lock releaseLock(repositoryName); logger.debug(MessageFormat.format("GCExecutor released GC lock for {0}", repositoryName)); } } running.set(false); } private boolean isRepositoryIdle(Repository repository) { try { // Read the use count. // An idle use count is 2: // +1 for being in the cache // +1 for the repository parameter in this method Field useCnt = Repository.class.getDeclaredField("useCnt"); useCnt.setAccessible(true); int useCount = ((AtomicInteger) useCnt.get(repository)).get(); return useCount == 2; } catch (Exception e) { logger.warn(MessageFormat .format("Failed to reflectively determine use count for repository {0}", repository.getDirectory().getPath()), e); } return false; } } src/main/java/com/gitblit/GitBlit.java
New file @@ -0,0 +1,3889 @@ /* * 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.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.security.Principal; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.apache.wicket.RequestCycle; import org.apache.wicket.protocol.http.WebResponse; import org.apache.wicket.resource.ContextRelativeResource; import org.apache.wicket.util.resource.ResourceStreamNotFoundException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; import org.eclipse.jgit.lib.RepositoryCache.FileKey; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AccountType; import com.gitblit.Constants.AuthenticationType; import com.gitblit.Constants.AuthorizationControl; import com.gitblit.Constants.FederationRequest; import com.gitblit.Constants.FederationStrategy; import com.gitblit.Constants.FederationToken; import com.gitblit.Constants.PermissionType; import com.gitblit.Constants.RegistrantType; import com.gitblit.fanout.FanoutNioService; import com.gitblit.fanout.FanoutService; import com.gitblit.fanout.FanoutSocketService; import com.gitblit.git.GitDaemon; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; import com.gitblit.models.ForkModel; import com.gitblit.models.GitClientApplication; import com.gitblit.models.Metric; import com.gitblit.models.ProjectModel; import com.gitblit.models.RefModel; 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; import com.gitblit.models.SettingModel; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.Base64; import com.gitblit.utils.ByteFormat; import com.gitblit.utils.CommitCache; import com.gitblit.utils.ContainerUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.JGitUtils.LastChange; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.MetricUtils; import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; import com.gitblit.utils.X509Utils.X509Metadata; import com.gitblit.wicket.GitBlitWebSession; import com.gitblit.wicket.WicketUtils; import com.google.gson.Gson; import com.google.gson.JsonIOException; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; /** * GitBlit is the servlet context listener singleton that acts as the core for * the web ui and the servlets. This class is either directly instantiated by * the GitBlitServer class (Gitblit GO) or is reflectively instantiated from the * definition in the web.xml file (Gitblit WAR). * * This class is the central logic processor for Gitblit. All settings, user * object, and repository object operations pass through this class. * * Repository Resolution. There are two pathways for finding repositories. One * pathway, for web ui display and repository authentication & authorization, is * within this class. The other pathway is through the standard GitServlet. * * @author James Moger * */ public class GitBlit implements ServletContextListener { private static GitBlit gitblit; private final Logger logger = LoggerFactory.getLogger(GitBlit.class); private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5); private final List<FederationModel> federationRegistrations = Collections .synchronizedList(new ArrayList<FederationModel>()); private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>(); private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>(); private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>(); private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>(); private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>(); private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>(); private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>(""); private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>(); private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>(); private ServletContext servletContext; private File baseFolder; private File repositoriesFolder; private IUserService userService; private IStoredSettings settings; private ServerSettings settingsModel; private ServerStatus serverStatus; private MailExecutor mailExecutor; private LuceneExecutor luceneExecutor; private GCExecutor gcExecutor; private TimeZone timezone; private FileBasedConfig projectConfigs; private FanoutService fanoutService; private GitDaemon gitDaemon; public GitBlit() { if (gitblit == null) { // set the static singleton reference gitblit = this; } } public GitBlit(final IUserService userService) { this.userService = userService; gitblit = this; } /** * Returns the Gitblit singleton. * * @return gitblit singleton */ public static GitBlit self() { if (gitblit == null) { new GitBlit(); } return gitblit; } /** * Returns the boot date of the Gitblit server. * * @return the boot date of Gitblit */ public static Date getBootDate() { return self().serverStatus.bootDate; } /** * Returns the most recent change date of any repository served by Gitblit. * * @return a date */ public static Date getLastActivityDate() { Date date = null; for (String name : self().getRepositoryList()) { Repository r = self().getRepository(name); Date lastChange = JGitUtils.getLastChange(r).when; r.close(); if (lastChange != null && (date == null || lastChange.after(date))) { date = lastChange; } } return date; } /** * Determine if this is the GO variant of Gitblit. * * @return true if this is the GO variant of Gitblit. */ public static boolean isGO() { return self().settings instanceof FileSettings; } /** * 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 */ public static boolean isServingRepositories() { return getBoolean(Keys.git.enableGitServlet, true) || (getInteger(Keys.git.daemonPort, 0) > 0); } /** * 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 */ public static boolean isSendingMail() { return self().mailExecutor.isReady(); } /** * Returns the preferred timezone for the Gitblit instance. * * @return a timezone */ public static TimeZone getTimezone() { if (self().timezone == null) { String tzid = getString("web.timezone", null); if (StringUtils.isEmpty(tzid)) { self().timezone = TimeZone.getDefault(); return self().timezone; } self().timezone = TimeZone.getTimeZone(tzid); } return self().timezone; } /** * Returns the active settings. * * @return the active settings */ public static IStoredSettings getSettings() { return self().settings; } /** * Returns the user-defined blob encodings. * * @return an array of encodings, may be empty */ public static String [] getEncodings() { return getStrings(Keys.web.blobEncodings).toArray(new String[0]); } /** * Returns the boolean value for the specified key. If the key does not * exist or the value for the key can not be interpreted as a boolean, the * defaultValue is returned. * * @see IStoredSettings.getBoolean(String, boolean) * @param key * @param defaultValue * @return key value or defaultValue */ public static boolean getBoolean(String key, boolean defaultValue) { return self().settings.getBoolean(key, defaultValue); } /** * Returns the integer value for the specified key. If the key does not * exist or the value for the key can not be interpreted as an integer, the * defaultValue is returned. * * @see IStoredSettings.getInteger(String key, int defaultValue) * @param key * @param defaultValue * @return key value or defaultValue */ public static int getInteger(String key, int defaultValue) { return self().settings.getInteger(key, defaultValue); } /** * Returns the integer list for the specified key. If the key does not * exist or the value for the key can not be interpreted as an integer, an * empty list is returned. * * @see IStoredSettings.getIntegers(String key) * @param key * @return key value or defaultValue */ public static List<Integer> getIntegers(String key) { return self().settings.getIntegers(key); } /** * Returns the value in bytes for the specified key. If the key does not * exist or the value for the key can not be interpreted as an integer, the * defaultValue is returned. * * @see IStoredSettings.getFilesize(String key, int defaultValue) * @param key * @param defaultValue * @return key value or defaultValue */ public static int getFilesize(String key, int defaultValue) { return self().settings.getFilesize(key, defaultValue); } /** * Returns the value in bytes for the specified key. If the key does not * exist or the value for the key can not be interpreted as a long, the * defaultValue is returned. * * @see IStoredSettings.getFilesize(String key, long defaultValue) * @param key * @param defaultValue * @return key value or defaultValue */ public static long getFilesize(String key, long defaultValue) { return self().settings.getFilesize(key, defaultValue); } /** * Returns the char value for the specified key. If the key does not exist * or the value for the key can not be interpreted as a character, the * defaultValue is returned. * * @see IStoredSettings.getChar(String key, char defaultValue) * @param key * @param defaultValue * @return key value or defaultValue */ public static char getChar(String key, char defaultValue) { return self().settings.getChar(key, defaultValue); } /** * Returns the string value for the specified key. If the key does not exist * or the value for the key can not be interpreted as a string, the * defaultValue is returned. * * @see IStoredSettings.getString(String key, String defaultValue) * @param key * @param defaultValue * @return key value or defaultValue */ public static String getString(String key, String defaultValue) { return self().settings.getString(key, defaultValue); } /** * Returns a list of space-separated strings from the specified key. * * @see IStoredSettings.getStrings(String key) * @param n * @return list of strings */ public static List<String> getStrings(String key) { return self().settings.getStrings(key); } /** * Returns a map of space-separated key-value pairs from the specified key. * * @see IStoredSettings.getStrings(String key) * @param n * @return map of string, string */ public static Map<String, String> getMap(String key) { return self().settings.getMap(key); } /** * Returns the list of keys whose name starts with the specified prefix. If * the prefix is null or empty, all key names are returned. * * @see IStoredSettings.getAllKeys(String key) * @param startingWith * @return list of keys */ public static List<String> getAllKeys(String startingWith) { return self().settings.getAllKeys(startingWith); } /** * Is Gitblit running in debug mode? * * @return true if Gitblit is running in debug mode */ public static boolean isDebugMode() { return self().settings.getBoolean(Keys.web.debugMode, false); } /** * Returns the file object for the specified configuration key. * * @return the file */ public static File getFileOrFolder(String key, String defaultFileOrFolder) { String fileOrFolder = GitBlit.getString(key, defaultFileOrFolder); return getFileOrFolder(fileOrFolder); } /** * Returns the file object which may have it's base-path determined by * environment variables for running on a cloud hosting service. All Gitblit * file or folder retrievals are (at least initially) funneled through this * method so it is the correct point to globally override/alter filesystem * access based on environment or some other indicator. * * @return the file */ public static File getFileOrFolder(String fileOrFolder) { return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, self().baseFolder, fileOrFolder); } /** * Returns the path of the repositories folder. This method checks to see if * Gitblit is running on a cloud service and may return an adjusted path. * * @return the repositories folder path */ public static File getRepositoriesFolder() { return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git"); } /** * Returns the path of the proposals folder. This method checks to see if * Gitblit is running on a cloud service and may return an adjusted path. * * @return the proposals folder path */ public static File getProposalsFolder() { return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals"); } /** * Returns the path of the Groovy folder. This method checks to see if * Gitblit is running on a cloud service and may return an adjusted path. * * @return the Groovy scripts folder path */ public static File getGroovyScriptsFolder() { return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy"); } /** * Updates the list of server settings. * * @param settings * @return true if the update succeeded */ public boolean updateSettings(Map<String, String> updatedSettings) { return settings.saveSettings(updatedSettings); } public ServerStatus getStatus() { // update heap memory status serverStatus.heapAllocated = Runtime.getRuntime().totalMemory(); serverStatus.heapFree = Runtime.getRuntime().freeMemory(); return serverStatus; } /** * Returns a list of repository URLs and the user access permission. * * @param request * @param user * @param repository * @return a list of repository urls */ public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) { if (user == null) { user = UserModel.ANONYMOUS; } String username = 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)); } } // git daemon url String gitDaemonUrl = getGitDaemonUrl(request, user, repository); if (!StringUtils.isEmpty(gitDaemonUrl)) { AccessPermission permission = getGitDaemonAccessPermission(user, repository); if (permission.exceeds(AccessPermission.NONE)) { 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)); } } return list; } protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) { StringBuilder sb = new StringBuilder(); sb.append(HttpUtils.getGitblitURL(request)); sb.append(Constants.GIT_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(); } protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) { if (gitDaemon != null) { String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); if (bindInterface.equals("localhost") && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) { // git daemon is bound to localhost and the request is from elsewhere return null; } if (user.canClone(repository)) { String servername = request.getServerName(); String url = gitDaemon.formatUrl(servername, repository.name); return url; } } return null; } protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) { if (gitDaemon != null && user.canClone(repository)) { AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission; if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) { if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) { // can not authenticate clone via anonymous git protocol gitDaemonPermission = AccessPermission.NONE; } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) { // can not authenticate push via anonymous git protocol gitDaemonPermission = AccessPermission.CLONE; } else { // normal user permission } } return gitDaemonPermission; } return AccessPermission.NONE; } /** * Returns the list of custom client applications to be used for the * repository url panel; * * @return a collection of client applications */ public Collection<GitClientApplication> getClientApplications() { // prefer user definitions, if they exist File userDefs = new File(baseFolder, "clientapps.json"); if (userDefs.exists()) { Date lastModified = new Date(userDefs.lastModified()); if (clientApplications.hasCurrent("user", lastModified)) { return clientApplications.getObject("user"); } else { // (re)load user definitions try { InputStream is = new FileInputStream(userDefs); Collection<GitClientApplication> clients = readClientApplications(is); is.close(); if (clients != null) { clientApplications.updateObject("user", lastModified, clients); return clients; } } catch (IOException e) { logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e); } } } // no user definitions, use system definitions if (!clientApplications.hasCurrent("system", new Date(0))) { try { InputStream is = getClass().getResourceAsStream("/clientapps.json"); Collection<GitClientApplication> clients = readClientApplications(is); is.close(); if (clients != null) { clientApplications.updateObject("system", new Date(0), clients); } } catch (IOException e) { logger.error("Failed to deserialize clientapps.json resource!", e); } } return clientApplications.getObject("system"); } private Collection<GitClientApplication> readClientApplications(InputStream is) { try { Type type = new TypeToken<Collection<GitClientApplication>>() { }.getType(); InputStreamReader reader = new InputStreamReader(is); Gson gson = JsonUtils.gson(); Collection<GitClientApplication> links = gson.fromJson(reader, type); return links; } catch (JsonIOException e) { logger.error("Error deserializing client applications!", e); } catch (JsonSyntaxException e) { logger.error("Error deserializing client applications!", e); } return null; } /** * Set the user service. The user service authenticates all users and is * responsible for managing user permissions. * * @param userService */ public void setUserService(IUserService userService) { logger.info("Setting up user service " + userService.toString()); this.userService = userService; this.userService.setup(settings); } public boolean supportsAddUser() { return supportsCredentialChanges(new UserModel("")); } /** * Returns true if the user's credentials can be changed. * * @param user * @return true if the user service supports credential changes */ public boolean supportsCredentialChanges(UserModel user) { if (user == null) { return false; } else if (AccountType.LOCAL.equals(user.accountType)) { // local account, we can change credentials return true; } else { // external account, ask user service return userService.supportsCredentialChanges(); } } /** * Returns true if the user's display name can be changed. * * @param user * @return true if the user service supports display name changes */ public boolean supportsDisplayNameChanges(UserModel user) { return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges(); } /** * Returns true if the user's email address can be changed. * * @param user * @return true if the user service supports email address changes */ public boolean supportsEmailAddressChanges(UserModel user) { return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges(); } /** * Returns true if the user's team memberships can be changed. * * @param user * @return true if the user service supports team membership changes */ public boolean supportsTeamMembershipChanges(UserModel user) { return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges(); } /** * Returns true if the username represents an internal account * * @param username * @return true if the specified username represents an internal account */ protected boolean isInternalAccount(String username) { return !StringUtils.isEmpty(username) && (username.equalsIgnoreCase(Constants.FEDERATION_USER) || username.equalsIgnoreCase(UserModel.ANONYMOUS.username)); } /** * Authenticate a user based on a username and password. * * @see IUserService.authenticate(String, char[]) * @param username * @param password * @return a user object or null */ public UserModel authenticate(String username, char[] password) { if (StringUtils.isEmpty(username)) { // can not authenticate empty username return null; } String usernameDecoded = decodeUsername(username); String pw = new String(password); if (StringUtils.isEmpty(pw)) { // can not authenticate empty password return null; } // check to see if this is the federation user if (canFederate()) { if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) { List<String> tokens = getFederationTokens(); if (tokens.contains(pw)) { return getFederationUser(); } } } // delegate authentication to the user service if (userService == null) { return null; } return userService.authenticate(usernameDecoded, password); } /** * Authenticate a user based on their cookie. * * @param cookies * @return a user object or null */ protected UserModel authenticate(Cookie[] cookies) { if (userService == null) { return null; } if (userService.supportsCookies()) { if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { if (cookie.getName().equals(Constants.NAME)) { String value = cookie.getValue(); return userService.authenticate(value.toCharArray()); } } } } return null; } /** * Authenticate a user based on HTTP request parameters. * * Authentication by X509Certificate is tried first and then by cookie. * * @param httpRequest * @return a user object or null */ public UserModel authenticate(HttpServletRequest httpRequest) { return authenticate(httpRequest, false); } /** * Authenticate a user based on HTTP request parameters. * * Authentication by X509Certificate, servlet container principal, cookie, * and BASIC header. * * @param httpRequest * @param requiresCertificate * @return a user object or null */ public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { // try to authenticate by certificate boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true); String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]); UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids); if (model != null) { // grab real user model and preserve certificate serial number UserModel user = getUserModel(model.username); X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); if (user != null) { flagWicketSession(AuthenticationType.CERTIFICATE); logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}", user.username, metadata.serialNumber, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}", model.username, metadata.serialNumber, httpRequest.getRemoteAddr())); } } if (requiresCertificate) { // caller requires client certificate authentication (e.g. git servlet) return null; } // try to authenticate by servlet container principal Principal principal = httpRequest.getUserPrincipal(); if (principal != null) { String username = principal.getName(); if (!StringUtils.isEmpty(username)) { boolean internalAccount = isInternalAccount(username); UserModel user = getUserModel(username); if (user != null) { // existing user flagWicketSession(AuthenticationType.CONTAINER); logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false) && !internalAccount) { // auto-create user from an authenticated container principal user = new UserModel(username.toLowerCase()); user.displayName = username; user.password = Constants.EXTERNAL_ACCOUNT; userService.updateUserModel(user); flagWicketSession(AuthenticationType.CONTAINER); logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else if (!internalAccount) { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}", principal.getName(), httpRequest.getRemoteAddr())); } } } // try to authenticate by cookie if (allowCookieAuthentication()) { UserModel user = authenticate(httpRequest.getCookies()); if (user != null) { flagWicketSession(AuthenticationType.COOKIE); logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", user.username, httpRequest.getRemoteAddr())); return user; } } // try to authenticate by BASIC final String authorization = httpRequest.getHeader("Authorization"); if (authorization != null && authorization.startsWith("Basic")) { // Authorization: Basic base64credentials String base64Credentials = authorization.substring("Basic".length()).trim(); String credentials = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // credentials = username:password final String[] values = credentials.split(":",2); if (values.length == 2) { String username = values[0]; char[] password = values[1].toCharArray(); UserModel user = authenticate(username, password); if (user != null) { flagWicketSession(AuthenticationType.CREDENTIALS); logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}", username, credentials, httpRequest.getRemoteAddr())); } } } return null; } protected void flagWicketSession(AuthenticationType authenticationType) { RequestCycle requestCycle = RequestCycle.get(); if (requestCycle != null) { // flag the Wicket session, if this is a Wicket request GitBlitWebSession session = GitBlitWebSession.get(); session.authenticationType = authenticationType; } } /** * Open a file resource using the Servlet container. * @param file to open * @return InputStream of the opened file * @throws ResourceStreamNotFoundException */ public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException { ContextRelativeResource res = WicketUtils.getResource(file); return res.getResourceStream().getInputStream(); } /** * Sets a cookie for the specified user. * * @param response * @param user */ public void setCookie(WebResponse response, UserModel user) { if (userService == null) { return; } GitBlitWebSession session = GitBlitWebSession.get(); boolean standardLogin = session.authenticationType.isStandard(); if (userService.supportsCookies() && standardLogin) { Cookie userCookie; if (user == null) { // clear cookie for logout userCookie = new Cookie(Constants.NAME, ""); } else { // set cookie for login String cookie = userService.getCookie(user); if (StringUtils.isEmpty(cookie)) { // create empty cookie userCookie = new Cookie(Constants.NAME, ""); } else { // create real cookie userCookie = new Cookie(Constants.NAME, cookie); userCookie.setMaxAge(Integer.MAX_VALUE); } } userCookie.setPath("/"); response.addCookie(userCookie); } } /** * Logout a user. * * @param user */ public void logout(UserModel user) { if (userService == null) { return; } userService.logout(user); } /** * Encode the username for user in an url. * * @param name * @return the encoded name */ protected String encodeUsername(String name) { return name.replace("@", "%40").replace(" ", "%20").replace("\\", "%5C"); } /** * Decode a username from an encoded url. * * @param name * @return the decoded name */ protected String decodeUsername(String name) { return name.replace("%40", "@").replace("%20", " ").replace("%5C", "\\"); } /** * Returns the list of all users available to the login service. * * @see IUserService.getAllUsernames() * @return list of all usernames */ public List<String> getAllUsernames() { List<String> names = new ArrayList<String>(userService.getAllUsernames()); return names; } /** * Returns the list of all users available to the login service. * * @see IUserService.getAllUsernames() * @return list of all usernames */ public List<UserModel> getAllUsers() { List<UserModel> users = userService.getAllUsers(); return users; } /** * Delete the user object with the specified username * * @see IUserService.deleteUser(String) * @param username * @return true if successful */ public boolean deleteUser(String username) { if (StringUtils.isEmpty(username)) { return false; } String usernameDecoded = decodeUsername(username); return userService.deleteUser(usernameDecoded); } protected UserModel getFederationUser() { // the federation user is an administrator UserModel federationUser = new UserModel(Constants.FEDERATION_USER); federationUser.canAdmin = true; return federationUser; } /** * Retrieve the user object for the specified username. * * @see IUserService.getUserModel(String) * @param username * @return a user object or null */ public UserModel getUserModel(String username) { if (StringUtils.isEmpty(username)) { return null; } String usernameDecoded = decodeUsername(username); UserModel user = userService.getUserModel(usernameDecoded); return user; } /** * Returns the effective list of permissions for this user, taking into account * team memberships, ownerships. * * @param user * @return the effective list of permissions for the user */ public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) { if (StringUtils.isEmpty(user.username)) { // new user return new ArrayList<RegistrantAccessPermission>(); } Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>(); set.addAll(user.getRepositoryPermissions()); // Flag missing repositories for (RegistrantAccessPermission permission : set) { if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) { RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant); if (rm == null) { permission.permissionType = PermissionType.MISSING; permission.mutable = false; continue; } } } // TODO reconsider ownership as a user property // manually specify personal repository ownerships for (RepositoryModel rm : repositoryListCache.values()) { if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) { RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND, PermissionType.OWNER, RegistrantType.REPOSITORY, null, false); // user may be owner of a repository to which they've inherited // a team permission, replace any existing perm with owner perm set.remove(rp); set.add(rp); } } List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set); Collections.sort(list); return list; } /** * Returns the list of users and their access permissions for the specified * repository including permission source information such as the team or * regular expression which sets the permission. * * @param repository * @return a list of RegistrantAccessPermissions */ public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) { List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(); if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) { // no permissions needed, REWIND for everyone! return list; } if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) { // no permissions needed, REWIND for authenticated! return list; } // NAMED users and teams for (UserModel user : userService.getAllUsers()) { RegistrantAccessPermission ap = user.getRepositoryPermission(repository); if (ap.permission.exceeds(AccessPermission.NONE)) { list.add(ap); } } return list; } /** * Sets the access permissions to the specified repository for the specified users. * * @param repository * @param permissions * @return true if the user models have been updated */ public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) { List<UserModel> users = new ArrayList<UserModel>(); for (RegistrantAccessPermission up : permissions) { if (up.mutable) { // only set editable defined permissions UserModel user = userService.getUserModel(up.registrant); user.setRepositoryPermission(repository.name, up.permission); users.add(user); } } return userService.updateUserModels(users); } /** * Returns the list of all users who have an explicit access permission * for the specified repository. * * @see IUserService.getUsernamesForRepositoryRole(String) * @param repository * @return list of all usernames that have an access permission for the repository */ public List<String> getRepositoryUsers(RepositoryModel repository) { return userService.getUsernamesForRepositoryRole(repository.name); } /** * Sets the list of all uses who are allowed to bypass the access * restriction placed on the specified repository. * * @see IUserService.setUsernamesForRepositoryRole(String, List<String>) * @param repository * @param usernames * @return true if successful */ @Deprecated public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) { // rejects all changes since 1.2.0 because this would elevate // all discrete access permissions to RW+ return false; } /** * Adds/updates a complete user object keyed by username. This method allows * for renaming a user. * * @see IUserService.updateUserModel(String, UserModel) * @param username * @param user * @param isCreate * @throws GitBlitException */ public void updateUserModel(String username, UserModel user, boolean isCreate) throws GitBlitException { if (!username.equalsIgnoreCase(user.username)) { if (userService.getUserModel(user.username) != null) { throw new GitBlitException(MessageFormat.format( "Failed to rename ''{0}'' because ''{1}'' already exists.", username, user.username)); } // rename repositories and owner fields for all repositories for (RepositoryModel model : getRepositoryModels(user)) { if (model.isUsersPersonalRepository(username)) { // personal repository model.addOwner(user.username); String oldRepositoryName = model.name; model.name = "~" + user.username + model.name.substring(model.projectPath.length()); model.projectPath = "~" + user.username; updateRepositoryModel(oldRepositoryName, model, false); } else if (model.isOwner(username)) { // common/shared repo model.addOwner(user.username); updateRepositoryModel(model.name, model, false); } } } if (!userService.updateUserModel(username, user)) { throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!"); } } /** * Returns the list of available teams that a user or repository may be * assigned to. * * @return the list of teams */ public List<String> getAllTeamnames() { List<String> teams = new ArrayList<String>(userService.getAllTeamNames()); return teams; } /** * Returns the list of available teams that a user or repository may be * assigned to. * * @return the list of teams */ public List<TeamModel> getAllTeams() { List<TeamModel> teams = userService.getAllTeams(); return teams; } /** * Returns the TeamModel object for the specified name. * * @param teamname * @return a TeamModel object or null */ public TeamModel getTeamModel(String teamname) { return userService.getTeamModel(teamname); } /** * Returns the list of teams and their access permissions for the specified * repository including the source of the permission such as the admin flag * or a regular expression. * * @param repository * @return a list of RegistrantAccessPermissions */ public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) { List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(); for (TeamModel team : userService.getAllTeams()) { RegistrantAccessPermission ap = team.getRepositoryPermission(repository); if (ap.permission.exceeds(AccessPermission.NONE)) { list.add(ap); } } Collections.sort(list); return list; } /** * Sets the access permissions to the specified repository for the specified teams. * * @param repository * @param permissions * @return true if the team models have been updated */ public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) { List<TeamModel> teams = new ArrayList<TeamModel>(); for (RegistrantAccessPermission tp : permissions) { if (tp.mutable) { // only set explicitly defined access permissions TeamModel team = userService.getTeamModel(tp.registrant); team.setRepositoryPermission(repository.name, tp.permission); teams.add(team); } } return userService.updateTeamModels(teams); } /** * Returns the list of all teams who have an explicit access permission for * the specified repository. * * @see IUserService.getTeamnamesForRepositoryRole(String) * @param repository * @return list of all teamnames with explicit access permissions to the repository */ public List<String> getRepositoryTeams(RepositoryModel repository) { return userService.getTeamnamesForRepositoryRole(repository.name); } /** * Sets the list of all uses who are allowed to bypass the access * restriction placed on the specified repository. * * @see IUserService.setTeamnamesForRepositoryRole(String, List<String>) * @param repository * @param teamnames * @return true if successful */ @Deprecated public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) { // rejects all changes since 1.2.0 because this would elevate // all discrete access permissions to RW+ return false; } /** * Updates the TeamModel object for the specified name. * * @param teamname * @param team * @param isCreate */ public void updateTeamModel(String teamname, TeamModel team, boolean isCreate) throws GitBlitException { if (!teamname.equalsIgnoreCase(team.name)) { if (userService.getTeamModel(team.name) != null) { throw new GitBlitException(MessageFormat.format( "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname, team.name)); } } if (!userService.updateTeamModel(teamname, team)) { throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!"); } } /** * Delete the team object with the specified teamname * * @see IUserService.deleteTeam(String) * @param teamname * @return true if successful */ public boolean deleteTeam(String teamname) { return userService.deleteTeam(teamname); } /** * Adds the repository to the list of cached repositories if Gitblit is * configured to cache the repository list. * * @param model */ private void addToCachedRepositoryList(RepositoryModel model) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { repositoryListCache.put(model.name.toLowerCase(), model); // update the fork origin repository with this repository clone if (!StringUtils.isEmpty(model.originRepository)) { if (repositoryListCache.containsKey(model.originRepository)) { RepositoryModel origin = repositoryListCache.get(model.originRepository); origin.addFork(model.name); } } } } /** * Removes the repository from the list of cached repositories. * * @param name * @return the model being removed */ private RepositoryModel removeFromCachedRepositoryList(String name) { if (StringUtils.isEmpty(name)) { return null; } return repositoryListCache.remove(name.toLowerCase()); } /** * Clears all the cached metadata for the specified repository. * * @param repositoryName */ private void clearRepositoryMetadataCache(String repositoryName) { repositorySizeCache.remove(repositoryName); repositoryMetricsCache.remove(repositoryName); } /** * Resets the repository list cache. * */ public void resetRepositoryListCache() { logger.info("Repository cache manually reset"); repositoryListCache.clear(); } /** * Calculate the checksum of settings that affect the repository list cache. * @return a checksum */ private String getRepositoryListSettingsChecksum() { StringBuilder ns = new StringBuilder(); ns.append(settings.getString(Keys.git.cacheRepositoryList, "")).append('\n'); ns.append(settings.getString(Keys.git.onlyAccessBareRepositories, "")).append('\n'); ns.append(settings.getString(Keys.git.searchRepositoriesSubfolders, "")).append('\n'); ns.append(settings.getString(Keys.git.searchRecursionDepth, "")).append('\n'); ns.append(settings.getString(Keys.git.searchExclusions, "")).append('\n'); String checksum = StringUtils.getSHA1(ns.toString()); return checksum; } /** * Compare the last repository list setting checksum to the current checksum. * If different then clear the cache so that it may be rebuilt. * * @return true if the cached repository list is valid since the last check */ private boolean isValidRepositoryList() { String newChecksum = getRepositoryListSettingsChecksum(); boolean valid = newChecksum.equals(repositoryListSettingsChecksum.get()); repositoryListSettingsChecksum.set(newChecksum); if (!valid && settings.getBoolean(Keys.git.cacheRepositoryList, true)) { logger.info("Repository list settings have changed. Clearing repository list cache."); repositoryListCache.clear(); } return valid; } /** * Returns the list of all repositories available to Gitblit. This method * does not consider user access permissions. * * @return list of all repositories */ public List<String> getRepositoryList() { if (repositoryListCache.size() == 0 || !isValidRepositoryList()) { // we are not caching OR we have not yet cached OR the cached list is invalid long startTime = System.currentTimeMillis(); List<String> repositories = JGitUtils.getRepositoryList(repositoriesFolder, settings.getBoolean(Keys.git.onlyAccessBareRepositories, false), settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true), settings.getInteger(Keys.git.searchRecursionDepth, -1), settings.getStrings(Keys.git.searchExclusions)); if (!settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // we are not caching StringUtils.sortRepositorynames(repositories); return repositories; } else { // we are caching this list String msg = "{0} repositories identified in {1} msecs"; // optionally (re)calculate repository sizes if (getBoolean(Keys.web.showRepositorySizes, true)) { ByteFormat byteFormat = new ByteFormat(); msg = "{0} repositories identified with calculated folder sizes in {1} msecs"; for (String repository : repositories) { RepositoryModel model = getRepositoryModel(repository); if (!model.skipSizeCalculation) { model.size = byteFormat.format(calculateSize(model)); } } } else { // update cache for (String repository : repositories) { getRepositoryModel(repository); } } // rebuild fork networks for (RepositoryModel model : repositoryListCache.values()) { if (!StringUtils.isEmpty(model.originRepository)) { if (repositoryListCache.containsKey(model.originRepository)) { RepositoryModel origin = repositoryListCache.get(model.originRepository); origin.addFork(model.name); } } } long duration = System.currentTimeMillis() - startTime; logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration)); } } // return sorted copy of cached list List<String> list = new ArrayList<String>(); for (RepositoryModel model : repositoryListCache.values()) { list.add(model.name); } StringUtils.sortRepositorynames(list); return list; } /** * Returns the JGit repository for the specified name. * * @param repositoryName * @return repository or null */ public Repository getRepository(String repositoryName) { return getRepository(repositoryName, true); } /** * Returns the JGit repository for the specified name. * * @param repositoryName * @param logError * @return repository or null */ public Repository getRepository(String repositoryName, boolean logError) { if (isCollectingGarbage(repositoryName)) { logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName)); return null; } File dir = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED); if (dir == null) return null; Repository r = null; try { FileKey key = FileKey.exact(dir, FS.DETECTED); r = RepositoryCache.open(key, true); } catch (IOException e) { if (logError) { logger.error("GitBlit.getRepository(String) failed to find " + new File(repositoriesFolder, repositoryName).getAbsolutePath()); } } return r; } /** * Returns the list of repository models that are accessible to the user. * * @param user * @return list of repository models accessible to user */ public List<RepositoryModel> getRepositoryModels(UserModel user) { long methodStart = System.currentTimeMillis(); List<String> list = getRepositoryList(); List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(); for (String repo : list) { RepositoryModel model = getRepositoryModel(user, repo); if (model != null) { if (!model.hasCommits) { // only add empty repositories that user can push to if (UserModel.ANONYMOUS.canPush(model) || user != null && user.canPush(model)) { repositories.add(model); } } else { repositories.add(model); } } } if (getBoolean(Keys.web.showRepositorySizes, true)) { int repoCount = 0; long startTime = System.currentTimeMillis(); ByteFormat byteFormat = new ByteFormat(); for (RepositoryModel model : repositories) { if (!model.skipSizeCalculation) { repoCount++; model.size = byteFormat.format(calculateSize(model)); } } long duration = System.currentTimeMillis() - startTime; if (duration > 250) { // only log calcualtion time if > 250 msecs logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs", repoCount, duration)); } } long duration = System.currentTimeMillis() - methodStart; logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs", repositories.size(), user == null ? "anonymous" : user.username, duration)); return repositories; } /** * Returns a repository model if the repository exists and the user may * access the repository. * * @param user * @param repositoryName * @return repository model or null */ public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) { RepositoryModel model = getRepositoryModel(repositoryName); if (model == null) { return null; } if (user == null) { user = UserModel.ANONYMOUS; } if (user.canView(model)) { return model; } return null; } /** * Returns the repository model for the specified repository. This method * does not consider user access permissions. * * @param repositoryName * @return repository model or null */ public RepositoryModel getRepositoryModel(String repositoryName) { if (!repositoryListCache.containsKey(repositoryName)) { RepositoryModel model = loadRepositoryModel(repositoryName); if (model == null) { return null; } addToCachedRepositoryList(model); return model; } // cached model RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase()); if (gcExecutor.isCollectingGarbage(model.name)) { // Gitblit is busy collecting garbage, use our cached model RepositoryModel rm = DeepCopier.copy(model); rm.isCollectingGarbage = true; return rm; } // check for updates Repository r = getRepository(model.name); if (r == null) { // repository is missing removeFromCachedRepositoryList(repositoryName); logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName)); return null; } FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r); if (config.isOutdated()) { // reload model logger.debug(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName)); model = loadRepositoryModel(model.name); removeFromCachedRepositoryList(model.name); addToCachedRepositoryList(model); } else { // update a few repository parameters if (!model.hasCommits) { // update hasCommits, assume a repository only gains commits :) model.hasCommits = JGitUtils.hasCommits(r); } LastChange lc = JGitUtils.getLastChange(r); model.lastChange = lc.when; model.lastChangeAuthor = lc.who; if (!model.skipSizeCalculation) { ByteFormat byteFormat = new ByteFormat(); model.size = byteFormat.format(calculateSize(model)); } } r.close(); // return a copy of the cached model return DeepCopier.copy(model); } /** * Returns the star count of the repository. * * @param repository * @return the star count */ public long getStarCount(RepositoryModel repository) { long count = 0; for (UserModel user : getAllUsers()) { if (user.getPreferences().isStarredRepository(repository.name)) { count++; } } return count; } private void reloadProjectMarkdown(ProjectModel project) { // project markdown File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd"); if (pmkd.exists()) { Date lm = new Date(pmkd.lastModified()); if (!projectMarkdownCache.hasCurrent(project.name, lm)) { String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n"); projectMarkdownCache.updateObject(project.name, lm, mkd); } project.projectMarkdown = projectMarkdownCache.getObject(project.name); } // project repositories markdown File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd"); if (rmkd.exists()) { Date lm = new Date(rmkd.lastModified()); if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) { String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n"); projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd); } project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name); } } /** * Returns the map of project config. This map is cached and reloaded if * the underlying projects.conf file changes. * * @return project config map */ private Map<String, ProjectModel> getProjectConfigs() { if (projectCache.isEmpty() || projectConfigs.isOutdated()) { try { projectConfigs.load(); } catch (Exception e) { } // project configs String rootName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main"); ProjectModel rootProject = new ProjectModel(rootName, true); Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>(); // cache the root project under its alias and an empty path configs.put("", rootProject); configs.put(rootProject.name.toLowerCase(), rootProject); for (String name : projectConfigs.getSubsections("project")) { ProjectModel project; if (name.equalsIgnoreCase(rootName)) { project = rootProject; } else { project = new ProjectModel(name); } project.title = projectConfigs.getString("project", name, "title"); project.description = projectConfigs.getString("project", name, "description"); reloadProjectMarkdown(project); configs.put(name.toLowerCase(), project); } projectCache.clear(); projectCache.putAll(configs); } return projectCache; } /** * Returns a list of project models for the user. * * @param user * @param includeUsers * @return list of projects that are accessible to the user */ public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) { Map<String, ProjectModel> configs = getProjectConfigs(); // per-user project lists, this accounts for security and visibility Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>(); // root project map.put("", configs.get("")); for (RepositoryModel model : getRepositoryModels(user)) { String rootPath = StringUtils.getRootPath(model.name).toLowerCase(); if (!map.containsKey(rootPath)) { ProjectModel project; if (configs.containsKey(rootPath)) { // clone the project model because it's repository list will // be tailored for the requesting user project = DeepCopier.copy(configs.get(rootPath)); } else { project = new ProjectModel(rootPath); } map.put(rootPath, project); } map.get(rootPath).addRepository(model); } // sort projects, root project first List<ProjectModel> projects; if (includeUsers) { // all projects projects = new ArrayList<ProjectModel>(map.values()); Collections.sort(projects); projects.remove(map.get("")); projects.add(0, map.get("")); } else { // all non-user projects projects = new ArrayList<ProjectModel>(); ProjectModel root = map.remove(""); for (ProjectModel model : map.values()) { if (!model.isUserProject()) { projects.add(model); } } Collections.sort(projects); projects.add(0, root); } return projects; } /** * Returns the project model for the specified user. * * @param name * @param user * @return a project model, or null if it does not exist */ public ProjectModel getProjectModel(String name, UserModel user) { for (ProjectModel project : getProjectModels(user, true)) { if (project.name.equalsIgnoreCase(name)) { return project; } } return null; } /** * Returns a project model for the Gitblit/system user. * * @param name a project name * @return a project model or null if the project does not exist */ public ProjectModel getProjectModel(String name) { Map<String, ProjectModel> configs = getProjectConfigs(); ProjectModel project = configs.get(name.toLowerCase()); if (project == null) { project = new ProjectModel(name); if (name.length() > 0 && name.charAt(0) == '~') { UserModel user = getUserModel(name.substring(1)); if (user != null) { project.title = user.getDisplayName(); project.description = "personal repositories"; } } } else { // clone the object project = DeepCopier.copy(project); } if (StringUtils.isEmpty(name)) { // get root repositories for (String repository : getRepositoryList()) { if (repository.indexOf('/') == -1) { project.addRepository(repository); } } } else { // get repositories in subfolder String folder = name.toLowerCase() + "/"; for (String repository : getRepositoryList()) { if (repository.toLowerCase().startsWith(folder)) { project.addRepository(repository); } } } if (project.repositories.size() == 0) { // no repositories == no project return null; } reloadProjectMarkdown(project); return project; } /** * Returns the list of project models that are referenced by the supplied * repository model list. This is an alternative method exists to ensure * Gitblit does not call getRepositoryModels(UserModel) twice in a request. * * @param repositoryModels * @param includeUsers * @return a list of project models */ public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) { Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>(); for (RepositoryModel repository : repositoryModels) { if (!includeUsers && repository.isPersonalRepository()) { // exclude personal repositories continue; } if (!projects.containsKey(repository.projectPath)) { ProjectModel project = getProjectModel(repository.projectPath); if (project == null) { logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!", repository.projectPath)); continue; } projects.put(repository.projectPath, project); // clear the repo list in the project because that is the system // list, not the user-accessible list and start building the // user-accessible list project.repositories.clear(); project.repositories.add(repository.name); project.lastChange = repository.lastChange; } else { // update the user-accessible list // this is used for repository count ProjectModel project = projects.get(repository.projectPath); project.repositories.add(repository.name); if (project.lastChange.before(repository.lastChange)) { project.lastChange = repository.lastChange; } } } return new ArrayList<ProjectModel>(projects.values()); } /** * Workaround JGit. I need to access the raw config object directly in order * to see if the config is dirty so that I can reload a repository model. * If I use the stock JGit method to get the config it already reloads the * config. If the config changes are made within Gitblit this is fine as * the returned config will still be flagged as dirty. BUT... if the config * is manipulated outside Gitblit then it fails to recognize this as dirty. * * @param r * @return a config */ private StoredConfig getRepositoryConfig(Repository r) { try { Field f = r.getClass().getDeclaredField("repoConfig"); f.setAccessible(true); StoredConfig config = (StoredConfig) f.get(r); return config; } catch (Exception e) { logger.error("Failed to retrieve \"repoConfig\" via reflection", e); } return r.getConfig(); } /** * Create a repository model from the configuration and repository data. * * @param repositoryName * @return a repositoryModel or null if the repository does not exist */ private RepositoryModel loadRepositoryModel(String repositoryName) { Repository r = getRepository(repositoryName); if (r == null) { return null; } RepositoryModel model = new RepositoryModel(); model.isBare = r.isBare(); File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git"); if (model.isBare) { model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory()); } else { model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile()); } if (StringUtils.isEmpty(model.name)) { // Repository is NOT located relative to the base folder because it // is symlinked. Use the provided repository name. model.name = repositoryName; } model.hasCommits = JGitUtils.hasCommits(r); LastChange lc = JGitUtils.getLastChange(r); model.lastChange = lc.when; model.lastChangeAuthor = lc.who; model.projectPath = StringUtils.getFirstPathElement(repositoryName); StoredConfig config = r.getConfig(); boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url")); if (config != null) { model.description = getConfig(config, "description", ""); model.originRepository = getConfig(config, "originRepository", null); model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", ""))); model.useTickets = getConfig(config, "useTickets", false); model.useDocs = getConfig(config, "useDocs", false); model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false); model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null); model.allowForks = getConfig(config, "allowForks", true); model.accessRestriction = AccessRestrictionType.fromName(getConfig(config, "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null))); model.authorizationControl = AuthorizationControl.fromName(getConfig(config, "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null))); model.verifyCommitter = getConfig(config, "verifyCommitter", false); model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin); model.isFrozen = getConfig(config, "isFrozen", false); model.showReadme = getConfig(config, "showReadme", false); model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false); model.skipSummaryMetrics = getConfig(config, "skipSummaryMetrics", false); model.federationStrategy = FederationStrategy.fromName(getConfig(config, "federationStrategy", null)); model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "federationSets"))); model.isFederated = getConfig(config, "isFederated", false); model.gcThreshold = getConfig(config, "gcThreshold", settings.getString(Keys.git.defaultGarbageCollectionThreshold, "500KB")); model.gcPeriod = getConfig(config, "gcPeriod", settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)); try { model.lastGC = new SimpleDateFormat(Constants.ISO8601).parse(getConfig(config, "lastGC", "1970-01-01'T'00:00:00Z")); } catch (Exception e) { model.lastGC = new Date(0); } model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0)); model.origin = config.getString("remote", "origin", "url"); if (model.origin != null) { model.origin = model.origin.replace('\\', '/'); } model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "preReceiveScript"))); model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "postReceiveScript"))); model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "mailingList"))); model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "indexBranch"))); model.metricAuthorExclusions = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "metricAuthorExclusions"))); // Custom defined properties model.customFields = new LinkedHashMap<String, String>(); for (String aProperty : config.getNames(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS)) { model.customFields.put(aProperty, config.getString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, aProperty)); } } model.HEAD = JGitUtils.getHEADRef(r); model.availableRefs = JGitUtils.getAvailableHeadTargets(r); model.sparkleshareId = JGitUtils.getSparkleshareId(r); r.close(); if (StringUtils.isEmpty(model.originRepository) && model.origin != null && model.origin.startsWith("file://")) { // repository was cloned locally... perhaps as a fork try { File folder = new File(new URI(model.origin)); String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder); if (!StringUtils.isEmpty(originRepo)) { // ensure origin still exists File repoFolder = new File(getRepositoriesFolder(), originRepo); if (repoFolder.exists()) { model.originRepository = originRepo.toLowerCase(); // persist the fork origin updateConfiguration(r, model); } } } catch (URISyntaxException e) { logger.error("Failed to determine fork for " + model, e); } } return model; } /** * Determines if this server has the requested repository. * * @param n * @return true if the repository exists */ public boolean hasRepository(String repositoryName) { return hasRepository(repositoryName, false); } /** * Determines if this server has the requested repository. * * @param n * @param caseInsensitive * @return true if the repository exists */ public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) { if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // if we are caching use the cache to determine availability // otherwise we end up adding a phantom repository to the cache return repositoryListCache.containsKey(repositoryName.toLowerCase()); } Repository r = getRepository(repositoryName, false); if (r == null) { return false; } r.close(); return true; } /** * Determines if the specified user has a fork of the specified origin * repository. * * @param username * @param origin * @return true the if the user has a fork */ public boolean hasFork(String username, String origin) { return getFork(username, origin) != null; } /** * Gets the name of a user's fork of the specified origin * repository. * * @param username * @param origin * @return the name of the user's fork, null otherwise */ public String getFork(String username, String origin) { String userProject = "~" + username.toLowerCase(); if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { String userPath = userProject + "/"; // collect all origin nodes in fork network Set<String> roots = new HashSet<String>(); roots.add(origin); RepositoryModel originModel = repositoryListCache.get(origin); while (originModel != null) { if (!ArrayUtils.isEmpty(originModel.forks)) { for (String fork : originModel.forks) { if (!fork.startsWith(userPath)) { roots.add(fork); } } } if (originModel.originRepository != null) { roots.add(originModel.originRepository); originModel = repositoryListCache.get(originModel.originRepository); } else { // break originModel = null; } } for (String repository : repositoryListCache.keySet()) { if (repository.startsWith(userPath)) { RepositoryModel model = repositoryListCache.get(repository); if (!StringUtils.isEmpty(model.originRepository)) { if (roots.contains(model.originRepository)) { // user has a fork in this graph return model.name; } } } } } else { // not caching ProjectModel project = getProjectModel(userProject); if (project == null) { return null; } for (String repository : project.repositories) { if (repository.startsWith(userProject)) { RepositoryModel model = getRepositoryModel(repository); if (model.originRepository.equalsIgnoreCase(origin)) { // user has a fork return model.name; } } } } // user does not have a fork return null; } /** * Returns the fork network for a repository by traversing up the fork graph * to discover the root and then down through all children of the root node. * * @param repository * @return a ForkModel */ public ForkModel getForkNetwork(String repository) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // find the root, cached RepositoryModel model = repositoryListCache.get(repository.toLowerCase()); while (model.originRepository != null) { model = repositoryListCache.get(model.originRepository); } ForkModel root = getForkModelFromCache(model.name); return root; } else { // find the root, non-cached RepositoryModel model = getRepositoryModel(repository.toLowerCase()); while (model.originRepository != null) { model = getRepositoryModel(model.originRepository); } ForkModel root = getForkModel(model.name); return root; } } private ForkModel getForkModelFromCache(String repository) { RepositoryModel model = repositoryListCache.get(repository.toLowerCase()); if (model == null) { return null; } ForkModel fork = new ForkModel(model); if (!ArrayUtils.isEmpty(model.forks)) { for (String aFork : model.forks) { ForkModel fm = getForkModelFromCache(aFork); if (fm != null) { fork.forks.add(fm); } } } return fork; } private ForkModel getForkModel(String repository) { RepositoryModel model = getRepositoryModel(repository.toLowerCase()); if (model == null) { return null; } ForkModel fork = new ForkModel(model); if (!ArrayUtils.isEmpty(model.forks)) { for (String aFork : model.forks) { ForkModel fm = getForkModel(aFork); if (fm != null) { fork.forks.add(fm); } } } return fork; } /** * Returns the size in bytes of the repository. Gitblit caches the * repository sizes to reduce the performance penalty of recursive * calculation. The cache is updated if the repository has been changed * since the last calculation. * * @param model * @return size in bytes */ public long calculateSize(RepositoryModel model) { if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) { return repositorySizeCache.getObject(model.name); } File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED); long size = com.gitblit.utils.FileUtils.folderSize(gitDir); repositorySizeCache.updateObject(model.name, model.lastChange, size); return size; } /** * Ensure that a cached repository is completely closed and its resources * are properly released. * * @param repositoryName */ private void closeRepository(String repositoryName) { Repository repository = getRepository(repositoryName); if (repository == null) { return; } RepositoryCache.close(repository); // assume 2 uses in case reflection fails int uses = 2; try { // The FileResolver caches repositories which is very useful // for performance until you want to delete a repository. // I have to use reflection to call close() the correct // number of times to ensure that the object and ref databases // are properly closed before I can delete the repository from // the filesystem. Field useCnt = Repository.class.getDeclaredField("useCnt"); useCnt.setAccessible(true); uses = ((AtomicInteger) useCnt.get(repository)).get(); } catch (Exception e) { logger.warn(MessageFormat .format("Failed to reflectively determine use count for repository {0}", repositoryName), e); } if (uses > 0) { logger.info(MessageFormat .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases", repositoryName, uses, uses)); for (int i = 0; i < uses; i++) { repository.close(); } } // close any open index writer/searcher in the Lucene executor luceneExecutor.close(repositoryName); } /** * Returns the metrics for the default branch of the specified repository. * This method builds a metrics cache. The cache is updated if the * repository is updated. A new copy of the metrics list is returned on each * call so that modifications to the list are non-destructive. * * @param model * @param repository * @return a new array list of metrics */ public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) { if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) { return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name)); } List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, getTimezone()); repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics); return new ArrayList<Metric>(metrics); } /** * Returns the gitblit string value for the specified key. If key is not * set, returns defaultValue. * * @param config * @param field * @param defaultValue * @return field value or defaultValue */ private String getConfig(StoredConfig config, String field, String defaultValue) { String value = config.getString(Constants.CONFIG_GITBLIT, null, field); if (StringUtils.isEmpty(value)) { return defaultValue; } return value; } /** * Returns the gitblit boolean value for the specified key. If key is not * set, returns defaultValue. * * @param config * @param field * @param defaultValue * @return field value or defaultValue */ private boolean getConfig(StoredConfig config, String field, boolean defaultValue) { return config.getBoolean(Constants.CONFIG_GITBLIT, field, defaultValue); } /** * Returns the gitblit string value for the specified key. If key is not * set, returns defaultValue. * * @param config * @param field * @param defaultValue * @return field value or defaultValue */ private int getConfig(StoredConfig config, String field, int defaultValue) { String value = config.getString(Constants.CONFIG_GITBLIT, null, field); if (StringUtils.isEmpty(value)) { return defaultValue; } try { return Integer.parseInt(value); } catch (Exception e) { } return defaultValue; } /** * Creates/updates the repository model keyed by reopsitoryName. Saves all * repository settings in .git/config. This method allows for renaming * repositories and will update user access permissions accordingly. * * All repositories created by this method are bare and automatically have * .git appended to their names, which is the standard convention for bare * repositories. * * @param repositoryName * @param repository * @param isCreate * @throws GitBlitException */ public void updateRepositoryModel(String repositoryName, RepositoryModel repository, boolean isCreate) throws GitBlitException { if (gcExecutor.isCollectingGarbage(repositoryName)) { throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}", repositoryName)); } Repository r = null; String projectPath = StringUtils.getFirstPathElement(repository.name); if (!StringUtils.isEmpty(projectPath)) { if (projectPath.equalsIgnoreCase(getString(Keys.web.repositoryRootGroupName, "main"))) { // strip leading group name repository.name = repository.name.substring(projectPath.length() + 1); } } if (isCreate) { // ensure created repository name ends with .git if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; } if (hasRepository(repository.name)) { throw new GitBlitException(MessageFormat.format( "Can not create repository ''{0}'' because it already exists.", repository.name)); } // create repository logger.info("create repository " + repository.name); r = JGitUtils.createRepository(repositoriesFolder, repository.name); } else { // rename repository if (!repositoryName.equalsIgnoreCase(repository.name)) { if (!repository.name.toLowerCase().endsWith( org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; } if (new File(repositoriesFolder, repository.name).exists()) { throw new GitBlitException(MessageFormat.format( "Failed to rename ''{0}'' because ''{1}'' already exists.", repositoryName, repository.name)); } closeRepository(repositoryName); File folder = new File(repositoriesFolder, repositoryName); File destFolder = new File(repositoriesFolder, repository.name); if (destFolder.exists()) { throw new GitBlitException( MessageFormat .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.", repositoryName, repository.name)); } File parentFile = destFolder.getParentFile(); if (!parentFile.exists() && !parentFile.mkdirs()) { throw new GitBlitException(MessageFormat.format( "Failed to create folder ''{0}''", parentFile.getAbsolutePath())); } if (!folder.renameTo(destFolder)) { throw new GitBlitException(MessageFormat.format( "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName, repository.name)); } // rename the roles if (!userService.renameRepositoryRole(repositoryName, repository.name)) { throw new GitBlitException(MessageFormat.format( "Failed to rename repository permissions ''{0}'' to ''{1}''.", repositoryName, repository.name)); } // rename fork origins in their configs if (!ArrayUtils.isEmpty(repository.forks)) { for (String fork : repository.forks) { Repository rf = getRepository(fork); try { StoredConfig config = rf.getConfig(); String origin = config.getString("remote", "origin", "url"); origin = origin.replace(repositoryName, repository.name); config.setString("remote", "origin", "url", origin); config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.name); config.save(); } catch (Exception e) { logger.error("Failed to update repository fork config for " + fork, e); } rf.close(); } } // update this repository's origin's fork list if (!StringUtils.isEmpty(repository.originRepository)) { RepositoryModel origin = repositoryListCache.get(repository.originRepository); if (origin != null && !ArrayUtils.isEmpty(origin.forks)) { origin.forks.remove(repositoryName); origin.forks.add(repository.name); } } // clear the cache clearRepositoryMetadataCache(repositoryName); repository.resetDisplayName(); } // load repository logger.info("edit repository " + repository.name); r = getRepository(repository.name); } // update settings if (r != null) { updateConfiguration(r, repository); // only update symbolic head if it changes String currentRef = JGitUtils.getHEADRef(r); if (!StringUtils.isEmpty(repository.HEAD) && !repository.HEAD.equals(currentRef)) { logger.info(MessageFormat.format("Relinking {0} HEAD from {1} to {2}", repository.name, currentRef, repository.HEAD)); if (JGitUtils.setHEADtoRef(r, repository.HEAD)) { // clear the cache clearRepositoryMetadataCache(repository.name); } } // close the repository object r.close(); } // update repository cache removeFromCachedRepositoryList(repositoryName); // model will actually be replaced on next load because config is stale addToCachedRepositoryList(repository); } /** * Updates the Gitblit configuration for the specified repository. * * @param r * the Git repository * @param repository * the Gitblit repository model */ public void updateConfiguration(Repository r, RepositoryModel repository) { StoredConfig config = r.getConfig(); config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description); config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.originRepository); config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners)); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags); if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) || repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) { config.unset(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix"); } else { config.setString(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix", repository.incrementalPushTagPrefix); } config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks); config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name()); config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name()); config.setBoolean(Constants.CONFIG_GITBLIT, null, "verifyCommitter", repository.verifyCommitter); config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches); config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen); config.setBoolean(Constants.CONFIG_GITBLIT, null, "showReadme", repository.showReadme); config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSizeCalculation", repository.skipSizeCalculation); config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSummaryMetrics", repository.skipSummaryMetrics); config.setString(Constants.CONFIG_GITBLIT, null, "federationStrategy", repository.federationStrategy.name()); config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated); config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold); if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) { // use default from config config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod"); } else { config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod); } if (repository.lastGC != null) { config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC)); } if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) { // use default from config config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits"); } else { config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits); } updateList(config, "federationSets", repository.federationSets); updateList(config, "preReceiveScript", repository.preReceiveScripts); updateList(config, "postReceiveScript", repository.postReceiveScripts); updateList(config, "mailingList", repository.mailingLists); updateList(config, "indexBranch", repository.indexedBranches); updateList(config, "metricAuthorExclusions", repository.metricAuthorExclusions); // User Defined Properties if (repository.customFields != null) { if (repository.customFields.size() == 0) { // clear section config.unsetSection(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS); } else { for (Entry<String, String> property : repository.customFields.entrySet()) { // set field String key = property.getKey(); String value = property.getValue(); config.setString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, key, value); } } } try { config.save(); } catch (IOException e) { logger.error("Failed to save repository config!", e); } } private void updateList(StoredConfig config, String field, List<String> list) { // a null list is skipped, not cleared // this is for RPC administration where an older manager might be used if (list == null) { return; } if (ArrayUtils.isEmpty(list)) { config.unset(Constants.CONFIG_GITBLIT, null, field); } else { config.setStringList(Constants.CONFIG_GITBLIT, null, field, list); } } /** * Deletes the repository from the file system and removes the repository * permission from all repository users. * * @param model * @return true if successful */ public boolean deleteRepositoryModel(RepositoryModel model) { return deleteRepository(model.name); } /** * Deletes the repository from the file system and removes the repository * permission from all repository users. * * @param repositoryName * @return true if successful */ public boolean deleteRepository(String repositoryName) { try { closeRepository(repositoryName); // clear the repository cache clearRepositoryMetadataCache(repositoryName); RepositoryModel model = removeFromCachedRepositoryList(repositoryName); if (model != null && !ArrayUtils.isEmpty(model.forks)) { resetRepositoryListCache(); } File folder = new File(repositoriesFolder, repositoryName); if (folder.exists() && folder.isDirectory()) { FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY); if (userService.deleteRepositoryRole(repositoryName)) { logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName)); return true; } } } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t); } return false; } /** * Returns an html version of the commit message with any global or * repository-specific regular expression substitution applied. * * @param repositoryName * @param text * @return html version of the commit message */ public String processCommitMessage(String repositoryName, String text) { String html = StringUtils.breakLinesForHtml(text); Map<String, String> map = new HashMap<String, String>(); // global regex keys if (settings.getBoolean(Keys.regex.global, false)) { for (String key : settings.getAllKeys(Keys.regex.global)) { if (!key.equals(Keys.regex.global)) { String subKey = key.substring(key.lastIndexOf('.') + 1); map.put(subKey, settings.getString(key, "")); } } } // repository-specific regex keys List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "." + repositoryName.toLowerCase()); for (String key : keys) { String subKey = key.substring(key.lastIndexOf('.') + 1); map.put(subKey, settings.getString(key, "")); } for (Entry<String, String> entry : map.entrySet()) { String definition = entry.getValue().trim(); String[] chunks = definition.split("!!!"); if (chunks.length == 2) { html = html.replaceAll(chunks[0], chunks[1]); } else { logger.warn(entry.getKey() + " improperly formatted. Use !!! to separate match from replacement: " + definition); } } return html; } /** * Returns Gitblit's scheduled executor service for scheduling tasks. * * @return scheduledExecutor */ public ScheduledExecutorService executor() { return scheduledExecutor; } public static boolean canFederate() { String passphrase = getString(Keys.federation.passphrase, ""); return !StringUtils.isEmpty(passphrase); } /** * Configures this Gitblit instance to pull any registered federated gitblit * instances. */ private void configureFederation() { boolean validPassphrase = true; String passphrase = settings.getString(Keys.federation.passphrase, ""); if (StringUtils.isEmpty(passphrase)) { logger.warn("Federation passphrase is blank! This server can not be PULLED from."); validPassphrase = false; } if (validPassphrase) { // standard tokens for (FederationToken tokenType : FederationToken.values()) { logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(), getFederationToken(tokenType))); } // federation set tokens for (String set : settings.getStrings(Keys.federation.sets)) { logger.info(MessageFormat.format("Federation Set {0} token = {1}", set, getFederationToken(set))); } } // Schedule the federation executor List<FederationModel> registrations = getFederationRegistrations(); if (registrations.size() > 0) { FederationPullExecutor executor = new FederationPullExecutor(registrations, true); scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES); } } /** * Returns the list of federated gitblit instances that this instance will * try to pull. * * @return list of registered gitblit instances */ public List<FederationModel> getFederationRegistrations() { if (federationRegistrations.isEmpty()) { federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings)); } return federationRegistrations; } /** * Retrieve the specified federation registration. * * @param name * the name of the registration * @return a federation registration */ public FederationModel getFederationRegistration(String url, String name) { // check registrations for (FederationModel r : getFederationRegistrations()) { if (r.name.equals(name) && r.url.equals(url)) { return r; } } // check the results for (FederationModel r : getFederationResultRegistrations()) { if (r.name.equals(name) && r.url.equals(url)) { return r; } } return null; } /** * Returns the list of federation sets. * * @return list of federation sets */ public List<FederationSet> getFederationSets(String gitblitUrl) { List<FederationSet> list = new ArrayList<FederationSet>(); // generate standard tokens for (FederationToken type : FederationToken.values()) { FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type)); fset.repositories = getRepositories(gitblitUrl, fset.token); list.add(fset); } // generate tokens for federation sets for (String set : settings.getStrings(Keys.federation.sets)) { FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES, getFederationToken(set)); fset.repositories = getRepositories(gitblitUrl, fset.token); list.add(fset); } return list; } /** * Returns the list of possible federation tokens for this Gitblit instance. * * @return list of federation tokens */ public List<String> getFederationTokens() { List<String> tokens = new ArrayList<String>(); // generate standard tokens for (FederationToken type : FederationToken.values()) { tokens.add(getFederationToken(type)); } // generate tokens for federation sets for (String set : settings.getStrings(Keys.federation.sets)) { tokens.add(getFederationToken(set)); } return tokens; } /** * Returns the specified federation token for this Gitblit instance. * * @param type * @return a federation token */ public String getFederationToken(FederationToken type) { return getFederationToken(type.name()); } /** * Returns the specified federation token for this Gitblit instance. * * @param value * @return a federation token */ public String getFederationToken(String value) { String passphrase = settings.getString(Keys.federation.passphrase, ""); return StringUtils.getSHA1(passphrase + "-" + value); } /** * Compares the provided token with this Gitblit instance's tokens and * determines if the requested permission may be granted to the token. * * @param req * @param token * @return true if the request can be executed */ public boolean validateFederationRequest(FederationRequest req, String token) { String all = getFederationToken(FederationToken.ALL); String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES); String jur = getFederationToken(FederationToken.REPOSITORIES); switch (req) { case PULL_REPOSITORIES: return token.equals(all) || token.equals(unr) || token.equals(jur); case PULL_USERS: case PULL_TEAMS: return token.equals(all) || token.equals(unr); case PULL_SETTINGS: case PULL_SCRIPTS: return token.equals(all); default: break; } return false; } /** * Acknowledge and cache the status of a remote Gitblit instance. * * @param identification * the identification of the pulling Gitblit instance * @param registration * the registration from the pulling Gitblit instance * @return true if acknowledged */ public boolean acknowledgeFederationStatus(String identification, FederationModel registration) { // reset the url to the identification of the pulling Gitblit instance registration.url = identification; String id = identification; if (!StringUtils.isEmpty(registration.folder)) { id += "-" + registration.folder; } federationPullResults.put(id, registration); return true; } /** * Returns the list of registration results. * * @return the list of registration results */ public List<FederationModel> getFederationResultRegistrations() { return new ArrayList<FederationModel>(federationPullResults.values()); } /** * Submit a federation proposal. The proposal is cached locally and the * Gitblit administrator(s) are notified via email. * * @param proposal * the proposal * @param gitblitUrl * the url of your gitblit instance to send an email to * administrators * @return true if the proposal was submitted */ public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) { // convert proposal to json String json = JsonUtils.toJsonString(proposal); try { // make the proposals folder File proposalsFolder = getProposalsFolder(); proposalsFolder.mkdirs(); // cache json to a file File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT); com.gitblit.utils.FileUtils.writeContent(file, json); } catch (Exception e) { logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e); } // send an email, if possible sendMailToAdministrators("Federation proposal from " + proposal.url, "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token); return true; } /** * Returns the list of pending federation proposals * * @return list of federation proposals */ public List<FederationProposal> getPendingFederationProposals() { List<FederationProposal> list = new ArrayList<FederationProposal>(); File folder = getProposalsFolder(); if (folder.exists()) { File[] files = folder.listFiles(new FileFilter() { @Override public boolean accept(File file) { return file.isFile() && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT); } }); for (File file : files) { String json = com.gitblit.utils.FileUtils.readContent(file, null); FederationProposal proposal = JsonUtils.fromJsonString(json, FederationProposal.class); list.add(proposal); } } return list; } /** * Get repositories for the specified token. * * @param gitblitUrl * the base url of this gitblit instance * @param token * the federation token * @return a map of <cloneurl, RepositoryModel> */ public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) { Map<String, String> federationSets = new HashMap<String, String>(); for (String set : getStrings(Keys.federation.sets)) { federationSets.put(getFederationToken(set), set); } // Determine the Gitblit clone url StringBuilder sb = new StringBuilder(); sb.append(gitblitUrl); sb.append(Constants.GIT_PATH); sb.append("{0}"); String cloneUrl = sb.toString(); // Retrieve all available repositories UserModel user = getFederationUser(); List<RepositoryModel> list = getRepositoryModels(user); // create the [cloneurl, repositoryModel] map Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); for (RepositoryModel model : list) { // by default, setup the url for THIS repository String url = MessageFormat.format(cloneUrl, model.name); switch (model.federationStrategy) { case EXCLUDE: // skip this repository continue; case FEDERATE_ORIGIN: // federate the origin, if it is defined if (!StringUtils.isEmpty(model.origin)) { url = model.origin; } break; default: break; } if (federationSets.containsKey(token)) { // include repositories only for federation set String set = federationSets.get(token); if (model.federationSets.contains(set)) { repositories.put(url, model); } } else { // standard federation token for ALL repositories.put(url, model); } } return repositories; } /** * Creates a proposal from the token. * * @param gitblitUrl * the url of this Gitblit instance * @param token * @return a potential proposal */ public FederationProposal createFederationProposal(String gitblitUrl, String token) { FederationToken tokenType = FederationToken.REPOSITORIES; for (FederationToken type : FederationToken.values()) { if (token.equals(getFederationToken(type))) { tokenType = type; break; } } Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token); FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token, repositories); return proposal; } /** * Returns the proposal identified by the supplied token. * * @param token * @return the specified proposal or null */ public FederationProposal getPendingFederationProposal(String token) { List<FederationProposal> list = getPendingFederationProposals(); for (FederationProposal proposal : list) { if (proposal.token.equals(token)) { return proposal; } } return null; } /** * Deletes a pending federation proposal. * * @param a * proposal * @return true if the proposal was deleted */ public boolean deletePendingFederationProposal(FederationProposal proposal) { File folder = getProposalsFolder(); File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT); return file.delete(); } /** * Returns the list of all Groovy push hook scripts. Script files must have * .groovy extension * * @return list of available hook scripts */ public List<String> getAllScripts() { File groovyFolder = getGroovyScriptsFolder(); File[] files = groovyFolder.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isFile() && pathname.getName().endsWith(".groovy"); } }); List<String> scripts = new ArrayList<String>(); if (files != null) { for (File file : files) { String script = file.getName().substring(0, file.getName().lastIndexOf('.')); scripts.add(script); } } return scripts; } /** * Returns the list of pre-receive scripts the repository inherited from the * global settings and team affiliations. * * @param repository * if null only the globally specified scripts are returned * @return a list of scripts */ public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) { Set<String> scripts = new LinkedHashSet<String>(); // Globals for (String script : getStrings(Keys.groovy.preReceiveScripts)) { if (script.endsWith(".groovy")) { scripts.add(script.substring(0, script.lastIndexOf('.'))); } else { scripts.add(script); } } // Team Scripts if (repository != null) { for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) { TeamModel team = userService.getTeamModel(teamname); if (!ArrayUtils.isEmpty(team.preReceiveScripts)) { scripts.addAll(team.preReceiveScripts); } } } return new ArrayList<String>(scripts); } /** * Returns the list of all available Groovy pre-receive push hook scripts * that are not already inherited by the repository. Script files must have * .groovy extension * * @param repository * optional parameter * @return list of available hook scripts */ public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) { Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository)); // create list of available scripts by excluding inherited scripts List<String> scripts = new ArrayList<String>(); for (String script : getAllScripts()) { if (!inherited.contains(script)) { scripts.add(script); } } return scripts; } /** * Returns the list of post-receive scripts the repository inherited from * the global settings and team affiliations. * * @param repository * if null only the globally specified scripts are returned * @return a list of scripts */ public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) { Set<String> scripts = new LinkedHashSet<String>(); // Global Scripts for (String script : getStrings(Keys.groovy.postReceiveScripts)) { if (script.endsWith(".groovy")) { scripts.add(script.substring(0, script.lastIndexOf('.'))); } else { scripts.add(script); } } // Team Scripts if (repository != null) { for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) { TeamModel team = userService.getTeamModel(teamname); if (!ArrayUtils.isEmpty(team.postReceiveScripts)) { scripts.addAll(team.postReceiveScripts); } } } return new ArrayList<String>(scripts); } /** * Returns the list of unused Groovy post-receive push hook scripts that are * not already inherited by the repository. Script files must have .groovy * extension * * @param repository * optional parameter * @return list of available hook scripts */ public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) { Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository)); // create list of available scripts by excluding inherited scripts List<String> scripts = new ArrayList<String>(); for (String script : getAllScripts()) { if (!inherited.contains(script)) { scripts.add(script); } } return scripts; } /** * Search the specified repositories using the Lucene query. * * @param query * @param page * @param pageSize * @param repositories * @return */ public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) { List<SearchResult> srs = luceneExecutor.search(query, page, pageSize, repositories); return srs; } /** * Notify the administrators by email. * * @param subject * @param message */ public void sendMailToAdministrators(String subject, String message) { List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses); sendMail(subject, message, toAddresses); } /** * Notify users by email of something. * * @param subject * @param message * @param toAddresses */ public void sendMail(String subject, String message, Collection<String> toAddresses) { this.sendMail(subject, message, toAddresses.toArray(new String[0])); } /** * Notify users by email of something. * * @param subject * @param message * @param toAddresses */ public void sendMail(String subject, String message, String... toAddresses) { if (toAddresses == null || toAddresses.length == 0) { logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject)); return; } try { Message mail = mailExecutor.createMessage(toAddresses); if (mail != null) { mail.setSubject(subject); MimeBodyPart messagePart = new MimeBodyPart(); messagePart.setText(message, "utf-8"); messagePart.setHeader("Content-Type", "text/plain; charset=\"utf-8\""); messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable"); MimeMultipart multiPart = new MimeMultipart(); multiPart.addBodyPart(messagePart); mail.setContent(multiPart); mailExecutor.queue(mail); } } catch (MessagingException e) { logger.error("Messaging error", e); } } /** * Notify users by email of something. * * @param subject * @param message * @param toAddresses */ public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) { this.sendHtmlMail(subject, message, toAddresses.toArray(new String[0])); } /** * Notify users by email of something. * * @param subject * @param message * @param toAddresses */ public void sendHtmlMail(String subject, String message, String... toAddresses) { if (toAddresses == null || toAddresses.length == 0) { logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject)); return; } try { Message mail = mailExecutor.createMessage(toAddresses); if (mail != null) { mail.setSubject(subject); MimeBodyPart messagePart = new MimeBodyPart(); messagePart.setText(message, "utf-8"); messagePart.setHeader("Content-Type", "text/html; charset=\"utf-8\""); messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable"); MimeMultipart multiPart = new MimeMultipart(); multiPart.addBodyPart(messagePart); mail.setContent(multiPart); mailExecutor.queue(mail); } } catch (MessagingException e) { logger.error("Messaging error", e); } } /** * Returns the descriptions/comments of the Gitblit config settings. * * @return SettingsModel */ public ServerSettings getSettingsModel() { // ensure that the current values are updated in the setting models for (String key : settings.getAllKeys(null)) { SettingModel setting = settingsModel.get(key); if (setting == null) { // unreferenced setting, create a setting model setting = new SettingModel(); setting.name = key; settingsModel.add(setting); } setting.currentValue = settings.getString(key, ""); } settingsModel.pushScripts = getAllScripts(); return settingsModel; } /** * Parse the properties file and aggregate all the comments by the setting * key. A setting model tracks the current value, the default value, the * description of the setting and and directives about the setting. * * @return Map<String, SettingModel> */ private ServerSettings loadSettingModels() { ServerSettings settingsModel = new ServerSettings(); settingsModel.supportsCredentialChanges = userService.supportsCredentialChanges(); settingsModel.supportsDisplayNameChanges = userService.supportsDisplayNameChanges(); settingsModel.supportsEmailAddressChanges = userService.supportsEmailAddressChanges(); settingsModel.supportsTeamMembershipChanges = userService.supportsTeamMembershipChanges(); try { // Read bundled Gitblit properties to extract setting descriptions. // This copy is pristine and only used for populating the setting // models map. InputStream is = getClass().getResourceAsStream("/reference.properties"); BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is)); StringBuilder description = new StringBuilder(); SettingModel setting = new SettingModel(); String line = null; while ((line = propertiesReader.readLine()) != null) { if (line.length() == 0) { description.setLength(0); setting = new SettingModel(); } else { if (line.charAt(0) == '#') { if (line.length() > 1) { String text = line.substring(1).trim(); if (SettingModel.CASE_SENSITIVE.equals(text)) { setting.caseSensitive = true; } else if (SettingModel.RESTART_REQUIRED.equals(text)) { setting.restartRequired = true; } else if (SettingModel.SPACE_DELIMITED.equals(text)) { setting.spaceDelimited = true; } else if (text.startsWith(SettingModel.SINCE)) { try { setting.since = text.split(" ")[1]; } catch (Exception e) { setting.since = text; } } else { description.append(text); description.append('\n'); } } } else { String[] kvp = line.split("=", 2); String key = kvp[0].trim(); setting.name = key; setting.defaultValue = kvp[1].trim(); setting.currentValue = setting.defaultValue; setting.description = description.toString().trim(); settingsModel.add(setting); description.setLength(0); setting = new SettingModel(); } } } propertiesReader.close(); } catch (NullPointerException e) { logger.error("Failed to find resource copy of gitblit.properties"); } catch (IOException e) { logger.error("Failed to load resource copy of gitblit.properties"); } return settingsModel; } /** * Configure the Gitblit singleton with the specified settings source. This * source may be file settings (Gitblit GO) or may be web.xml settings * (Gitblit WAR). * * @param settings */ public void configureContext(IStoredSettings settings, File folder, boolean startFederation) { this.settings = settings; this.baseFolder = folder; repositoriesFolder = getRepositoriesFolder(); logger.info("Gitblit base folder = " + folder.getAbsolutePath()); logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath()); logger.info("Gitblit settings = " + settings.toString()); // prepare service executors mailExecutor = new MailExecutor(settings); luceneExecutor = new LuceneExecutor(settings, repositoriesFolder); gcExecutor = new GCExecutor(settings); // calculate repository list settings checksum for future config changes repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum()); // build initial repository list if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { logger.info("Identifying available repositories..."); getRepositoryList(); } logTimezone("JVM", TimeZone.getDefault()); logTimezone(Constants.NAME, getTimezone()); serverStatus = new ServerStatus(isGO()); if (this.userService == null) { String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties"); IUserService loginService = null; try { // check to see if this "file" is a login service class Class<?> realmClass = Class.forName(realm); loginService = (IUserService) realmClass.newInstance(); } catch (Throwable t) { loginService = new GitblitUserService(); } setUserService(loginService); } // load and cache the project metadata projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect()); getProjectConfigs(); configureMailExecutor(); configureLuceneIndexing(); configureGarbageCollector(); if (startFederation) { configureFederation(); } configureJGit(); configureFanout(); configureGitDaemon(); configureCommitCache(); ContainerUtils.CVE_2007_0450.test(); } protected void configureMailExecutor() { if (mailExecutor.isReady()) { logger.info("Mail executor is scheduled to process the message queue every 2 minutes."); scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES); } else { logger.warn("Mail server is not properly configured. Mail services disabled."); } } protected void configureLuceneIndexing() { scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES); logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes."); } protected void configureGarbageCollector() { // schedule gc engine if (gcExecutor.isReady()) { logger.info("GC executor is scheduled to scan repositories every 24 hours."); Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0)); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); Date cd = c.getTime(); Date now = new Date(); int delay = 0; if (cd.before(now)) { c.add(Calendar.DATE, 1); cd = c.getTime(); } delay = (int) ((cd.getTime() - now.getTime())/TimeUtils.MIN); String when = delay + " mins"; if (delay > 60) { when = MessageFormat.format("{0,number,0.0} hours", ((float)delay)/60f); } logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when)); scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60*24, TimeUnit.MINUTES); } } protected void configureJGit() { // Configure JGit WindowCacheConfig cfg = new WindowCacheConfig(); cfg.setPackedGitWindowSize(settings.getFilesize(Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize())); cfg.setPackedGitLimit(settings.getFilesize(Keys.git.packedGitLimit, cfg.getPackedGitLimit())); cfg.setDeltaBaseCacheLimit(settings.getFilesize(Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit())); cfg.setPackedGitOpenFiles(settings.getFilesize(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles())); cfg.setStreamFileThreshold(settings.getFilesize(Keys.git.streamFileThreshold, cfg.getStreamFileThreshold())); cfg.setPackedGitMMAP(settings.getBoolean(Keys.git.packedGitMmap, cfg.isPackedGitMMAP())); try { cfg.install(); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize())); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitLimit, cfg.getPackedGitLimit())); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit())); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles())); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.streamFileThreshold, cfg.getStreamFileThreshold())); logger.debug(MessageFormat.format("{0} = {1}", Keys.git.packedGitMmap, cfg.isPackedGitMMAP())); } catch (IllegalArgumentException e) { logger.error("Failed to configure JGit parameters!", e); } } protected void configureFanout() { // startup Fanout PubSub service if (settings.getInteger(Keys.fanout.port, 0) > 0) { String bindInterface = settings.getString(Keys.fanout.bindInterface, null); int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT); boolean useNio = settings.getBoolean(Keys.fanout.useNio, true); int limit = settings.getInteger(Keys.fanout.connectionLimit, 0); if (useNio) { if (StringUtils.isEmpty(bindInterface)) { fanoutService = new FanoutNioService(port); } else { fanoutService = new FanoutNioService(bindInterface, port); } } else { if (StringUtils.isEmpty(bindInterface)) { fanoutService = new FanoutSocketService(port); } else { fanoutService = new FanoutSocketService(bindInterface, port); } } fanoutService.setConcurrentConnectionLimit(limit); fanoutService.setAllowAllChannelAnnouncements(false); fanoutService.start(); } } protected void configureGitDaemon() { int port = settings.getInteger(Keys.git.daemonPort, 0); String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost"); if (port > 0) { try { gitDaemon = new GitDaemon(bindInterface, port, getRepositoriesFolder()); gitDaemon.start(); } catch (IOException e) { gitDaemon = null; logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e); } } } protected void configureCommitCache() { int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14); if (daysToCache <= 0) { logger.info("commit cache disabled"); } else { long start = System.nanoTime(); long repoCount = 0; long commitCount = 0; logger.info(MessageFormat.format("preparing {0} day commit cache. please wait...", daysToCache)); CommitCache.instance().setCacheDays(daysToCache); Date cutoff = CommitCache.instance().getCutoffDate(); for (String repositoryName : getRepositoryList()) { RepositoryModel model = getRepositoryModel(repositoryName); if (model.hasCommits && model.lastChange.after(cutoff)) { repoCount++; Repository repository = getRepository(repositoryName); for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) { if (!ref.getDate().after(cutoff)) { // branch not recently updated continue; } List<?> commits = CommitCache.instance().getCommits(repositoryName, repository, ref.getName()); if (commits.size() > 0) { logger.info(MessageFormat.format(" cached {0} commits for {1}:{2}", commits.size(), repositoryName, ref.getName())); commitCount += commits.size(); } } repository.close(); } } logger.info(MessageFormat.format("built {0} day commit cache of {1} commits across {2} repositories in {3} msecs", daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start))); } } protected final Logger getLogger() { return logger; } protected final ScheduledExecutorService getScheduledExecutor() { return scheduledExecutor; } protected final LuceneExecutor getLuceneExecutor() { return luceneExecutor; } private void logTimezone(String type, TimeZone zone) { SimpleDateFormat df = new SimpleDateFormat("z Z"); df.setTimeZone(zone); String offset = df.format(new Date()); logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")"); } /** * Configure Gitblit from the web.xml, if no configuration has already been * specified. * * @see ServletContextListener.contextInitialize(ServletContextEvent) */ @Override public void contextInitialized(ServletContextEvent contextEvent) { servletContext = contextEvent.getServletContext(); if (settings == null) { // Gitblit is running in a servlet container ServletContext context = contextEvent.getServletContext(); WebXmlSettings webxmlSettings = new WebXmlSettings(context); String contextRealPath = context.getRealPath("/"); File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; String openShift = System.getenv("OPENSHIFT_DATA_DIR"); if (!StringUtils.isEmpty(openShift)) { // Gitblit is running in OpenShift/JBoss File base = new File(openShift); logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); // gitblit.properties setting overrides File overrideFile = new File(base, "gitblit.properties"); webxmlSettings.applyOverrides(overrideFile); // Copy the included scripts to the configured groovy folder String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"); File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path); if (!localScripts.exists()) { File warScripts = new File(contextFolder, "/WEB-INF/data/groovy"); if (!warScripts.equals(localScripts)) { try { com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles()); } catch (IOException e) { logger.error(MessageFormat.format( "Failed to copy included Groovy scripts from {0} to {1}", warScripts, localScripts)); } } } // configure context using the web.xml configureContext(webxmlSettings, base, true); } else { // Gitblit is running in a standard servlet container logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>")); String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); if (path.contains(Constants.contextFolder$) && contextFolder == null) { // warn about null contextFolder (issue-199) logger.error(""); logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!", Constants.baseFolder, Constants.contextFolder$, context.getServerInfo())); logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder)); logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder)); logger.error(""); } File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path); base.mkdirs(); // try to extract the data folder resource to the baseFolder File localSettings = new File(base, "gitblit.properties"); if (!localSettings.exists()) { extractResources(context, "/WEB-INF/data/", base); } // delegate all config to baseFolder/gitblit.properties file FileSettings settings = new FileSettings(localSettings.getAbsolutePath()); configureContext(settings, base, true); } } settingsModel = loadSettingModels(); serverStatus.servletContainer = servletContext.getServerInfo(); } protected void extractResources(ServletContext context, String path, File toDir) { for (String resource : context.getResourcePaths(path)) { // extract the resource to the directory if it does not exist File f = new File(toDir, resource.substring(path.length())); if (!f.exists()) { InputStream is = null; OutputStream os = null; try { if (resource.charAt(resource.length() - 1) == '/') { // directory f.mkdirs(); extractResources(context, resource, f); } else { // file f.getParentFile().mkdirs(); is = context.getResourceAsStream(resource); os = new FileOutputStream(f); byte [] buffer = new byte[4096]; int len = 0; while ((len = is.read(buffer)) > -1) { os.write(buffer, 0, len); } } } catch (FileNotFoundException e) { logger.error("Failed to find resource \"" + resource + "\"", e); } catch (IOException e) { logger.error("Failed to copy resource \"" + resource + "\" to " + f, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { // ignore } } if (os != null) { try { os.close(); } catch (IOException e) { // ignore } } } } } } /** * Gitblit is being shutdown either because the servlet container is * shutting down or because the servlet container is re-deploying Gitblit. */ @Override public void contextDestroyed(ServletContextEvent contextEvent) { logger.info("Gitblit context destroyed by servlet container."); scheduledExecutor.shutdownNow(); luceneExecutor.close(); gcExecutor.close(); if (fanoutService != null) { fanoutService.stop(); } if (gitDaemon != null) { gitDaemon.stop(); } } /** * * @return true if we are running the gc executor */ public boolean isCollectingGarbage() { return gcExecutor.isRunning(); } /** * Returns true if Gitblit is actively collecting garbage in this repository. * * @param repositoryName * @return true if actively collecting garbage */ public boolean isCollectingGarbage(String repositoryName) { return gcExecutor.isCollectingGarbage(repositoryName); } /** * Creates a personal fork of the specified repository. The clone is view * restricted by default and the owner of the source repository is given * access to the clone. * * @param repository * @param user * @return the repository model of the fork, if successful * @throws GitBlitException */ public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException { String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name))); String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name); // clone the repository try { JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null); } catch (Exception e) { throw new GitBlitException(e); } // create a Gitblit repository model for the clone RepositoryModel cloneModel = repository.cloneAs(cloneName); // owner has REWIND/RW+ permissions cloneModel.addOwner(user.username); updateRepositoryModel(cloneName, cloneModel, false); // add the owner of the source repository to the clone's access list if (!ArrayUtils.isEmpty(repository.owners)) { for (String owner : repository.owners) { UserModel originOwner = getUserModel(owner); if (originOwner != null) { originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE); updateUserModel(originOwner.username, originOwner, false); } } } // grant origin's user list clone permission to fork List<String> users = getRepositoryUsers(repository); List<UserModel> cloneUsers = new ArrayList<UserModel>(); for (String name : users) { if (!name.equalsIgnoreCase(user.username)) { UserModel cloneUser = getUserModel(name); if (cloneUser.canClone(repository)) { // origin user can clone origin, grant clone access to fork cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE); } cloneUsers.add(cloneUser); } } userService.updateUserModels(cloneUsers); // grant origin's team list clone permission to fork List<String> teams = getRepositoryTeams(repository); List<TeamModel> cloneTeams = new ArrayList<TeamModel>(); for (String name : teams) { TeamModel cloneTeam = getTeamModel(name); if (cloneTeam.canClone(repository)) { // origin team can clone origin, grant clone access to fork cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE); } cloneTeams.add(cloneTeam); } userService.updateTeamModels(cloneTeams); // add this clone to the cached model addToCachedRepositoryList(cloneModel); return cloneModel; } /** * Allow to understand if GitBlit supports and is configured to allow * cookie-based authentication. * * @return status of Cookie authentication enablement. */ public boolean allowCookieAuthentication() { return GitBlit.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies(); } } src/main/java/com/gitblit/GitBlitException.java
src/main/java/com/gitblit/GitBlitServer.java
New file @@ -0,0 +1,642 @@ /* * 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.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URI; import java.net.URL; import java.net.UnknownHostException; import java.security.ProtectionDomain; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Scanner; import org.eclipse.jetty.ajp.Ajp13SocketConnector; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.bio.SocketConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.session.HashSessionManager; import org.eclipse.jetty.server.ssl.SslConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import com.gitblit.authority.GitblitAuthority; import com.gitblit.authority.NewCertificateConfig; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; import com.gitblit.utils.X509Utils; import com.gitblit.utils.X509Utils.X509Log; import com.gitblit.utils.X509Utils.X509Metadata; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldif.LDIFReader; /** * GitBlitServer is the embedded Jetty server for Gitblit GO. This class starts * and stops an instance of Jetty that is configured from a combination of the * gitblit.properties file and command line parameters. JCommander is used to * simplify command line parameter processing. This class also automatically * generates a self-signed certificate for localhost, if the keystore does not * already exist. * * @author James Moger * */ public class GitBlitServer { private static Logger logger; public static void main(String... args) { GitBlitServer server = new GitBlitServer(); // filter out the baseFolder parameter List<String> filtered = new ArrayList<String>(); String folder = "data"; for (int i = 0; i< args.length; i++) { String arg = args[i]; if (arg.equals("--baseFolder")) { if (i + 1 == args.length) { System.out.println("Invalid --baseFolder parameter!"); System.exit(-1); } else if (args[i + 1] != ".") { folder = args[i + 1]; } i = i + 1; } else { filtered.add(arg); } } Params.baseFolder = folder; Params params = new Params(); JCommander jc = new JCommander(params); try { jc.parse(filtered.toArray(new String[filtered.size()])); if (params.help) { server.usage(jc, null); } } catch (ParameterException t) { server.usage(jc, t); } if (params.stop) { server.stop(params); } else { server.start(params); } } /** * Display the command line usage of Gitblit GO. * * @param jc * @param t */ protected final void usage(JCommander jc, ParameterException t) { System.out.println(Constants.BORDER); System.out.println(Constants.getGitBlitVersion()); System.out.println(Constants.BORDER); System.out.println(); if (t != null) { System.out.println(t.getMessage()); System.out.println(); } if (jc != null) { jc.usage(); System.out .println("\nExample:\n java -server -Xmx1024M -jar gitblit.jar --repositoriesFolder c:\\git --httpPort 80 --httpsPort 443"); } System.exit(0); } /** * Stop Gitblt GO. */ public void stop(Params params) { try { Socket s = new Socket(InetAddress.getByName("127.0.0.1"), params.shutdownPort); OutputStream out = s.getOutputStream(); System.out.println("Sending Shutdown Request to " + Constants.NAME); out.write("\r\n".getBytes()); out.flush(); s.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * Start Gitblit GO. */ protected final void start(Params params) { final File baseFolder = new File(Params.baseFolder).getAbsoluteFile(); FileSettings settings = params.FILESETTINGS; if (!StringUtils.isEmpty(params.settingsfile)) { if (new File(params.settingsfile).exists()) { settings = new FileSettings(params.settingsfile); } } 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); System.setProperty("java.awt.headless", "true"); String osname = System.getProperty("os.name"); String osversion = System.getProperty("os.version"); logger.info("Running on " + osname + " (" + osversion + ")"); List<Connector> connectors = new ArrayList<Connector>(); // conditionally configure the http connector if (params.port > 0) { Connector httpConnector = createConnector(params.useNIO, params.port, settings.getInteger(Keys.server.threadPoolSize, 50)); String bindInterface = settings.getString(Keys.server.httpBindInterface, null); if (!StringUtils.isEmpty(bindInterface)) { logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}", params.port, bindInterface)); httpConnector.setHost(bindInterface); } if (params.port < 1024 && !isWindows()) { logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!"); } connectors.add(httpConnector); } // conditionally configure the https connector if (params.securePort > 0) { File certificatesConf = new File(baseFolder, X509Utils.CA_CONFIG); File serverKeyStore = new File(baseFolder, X509Utils.SERVER_KEY_STORE); File serverTrustStore = new File(baseFolder, X509Utils.SERVER_TRUST_STORE); File caRevocationList = new File(baseFolder, X509Utils.CA_REVOCATION_LIST); // generate CA & web certificates, create certificate stores X509Metadata metadata = new X509Metadata("localhost", params.storePassword); // set default certificate values from config file if (certificatesConf.exists()) { FileBasedConfig config = new FileBasedConfig(certificatesConf, FS.detect()); try { config.load(); } catch (Exception e) { logger.error("Error parsing " + certificatesConf, e); } NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config); certificateConfig.update(metadata); } metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR); X509Utils.prepareX509Infrastructure(metadata, baseFolder, new X509Log() { @Override public void log(String message) { BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(new File(baseFolder, X509Utils.CERTS + File.separator + "log.txt"), true)); writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message)); writer.newLine(); writer.flush(); } catch (Exception e) { LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { } } } } }); if (serverKeyStore.exists()) { Connector secureConnector = createSSLConnector(params.alias, serverKeyStore, serverTrustStore, params.storePassword, caRevocationList, params.useNIO, params.securePort, settings.getInteger(Keys.server.threadPoolSize, 50), params.requireClientCertificates); String bindInterface = settings.getString(Keys.server.httpsBindInterface, null); if (!StringUtils.isEmpty(bindInterface)) { logger.warn(MessageFormat.format( "Binding ssl connector on port {0,number,0} to {1}", params.securePort, bindInterface)); secureConnector.setHost(bindInterface); } if (params.securePort < 1024 && !isWindows()) { logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!"); } connectors.add(secureConnector); } else { logger.warn("Failed to find or load Keystore?"); logger.warn("SSL connector DISABLED."); } } // conditionally configure the ajp connector if (params.ajpPort > 0) { Connector ajpConnector = createAJPConnector(params.ajpPort); String bindInterface = settings.getString(Keys.server.ajpBindInterface, null); if (!StringUtils.isEmpty(bindInterface)) { logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}", params.ajpPort, bindInterface)); ajpConnector.setHost(bindInterface); } if (params.ajpPort < 1024 && !isWindows()) { logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!"); } connectors.add(ajpConnector); } // tempDir is where the embedded Gitblit web application is expanded and // where Jetty creates any necessary temporary files File tempDir = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.temp); if (tempDir.exists()) { try { FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY); } catch (IOException x) { logger.warn("Failed to delete temp dir " + tempDir.getAbsolutePath(), x); } } if (!tempDir.mkdirs()) { logger.warn("Failed to create temp dir " + tempDir.getAbsolutePath()); } Server server = new Server(); server.setStopAtShutdown(true); server.setConnectors(connectors.toArray(new Connector[connectors.size()])); // Get the execution path of this class // We use this to set the WAR path. ProtectionDomain protectionDomain = GitBlitServer.class.getProtectionDomain(); URL location = protectionDomain.getCodeSource().getLocation(); // Root WebApp Context WebAppContext rootContext = new WebAppContext(); rootContext.setContextPath(settings.getString(Keys.server.contextPath, "/")); rootContext.setServer(server); rootContext.setWar(location.toExternalForm()); rootContext.setTempDirectory(tempDir); // Set cookies HttpOnly so they are not accessible to JavaScript engines HashSessionManager sessionManager = new HashSessionManager(); sessionManager.setHttpOnly(true); // Use secure cookies if only serving https sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0); rootContext.getSessionHandler().setSessionManager(sessionManager); // Ensure there is a defined User Service String realmUsers = params.userService; if (StringUtils.isEmpty(realmUsers)) { logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.userService)); return; } // Override settings from the command-line settings.overrideSetting(Keys.realm.userService, params.userService); settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder); settings.overrideSetting(Keys.git.daemonPort, params.gitPort); // Start up an in-memory LDAP server, if configured try { if (StringUtils.isEmpty(params.ldapLdifFile) == false) { File ldifFile = new File(params.ldapLdifFile); if (ldifFile != null && ldifFile.exists()) { URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server)); String firstLine = new Scanner(ldifFile).nextLine(); String rootDN = firstLine.substring(4); String bindUserName = settings.getString(Keys.realm.ldap.username, ""); String bindPassword = settings.getString(Keys.realm.ldap.password, ""); // Get the port int port = ldapUrl.getPort(); if (port == -1) port = 389; InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(rootDN); config.addAdditionalBindCredentials(bindUserName, bindPassword); config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", port)); config.setSchema(null); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); ds.importFromLDIF(true, new LDIFReader(ldifFile)); ds.startListening(); logger.info("LDAP Server started at ldap://localhost:" + port); } } } catch (Exception e) { // Completely optional, just show a warning logger.warn("Unable to start LDAP server", e); } // Set the server's contexts server.setHandler(rootContext); // Setup the GitBlit context GitBlit gitblit = getGitBlitInstance(); gitblit.configureContext(settings, baseFolder, true); rootContext.addEventListener(gitblit); try { // start the shutdown monitor if (params.shutdownPort > 0) { Thread shutdownMonitor = new ShutdownMonitorThread(server, params); shutdownMonitor.start(); } // start Jetty server.start(); server.join(); } catch (Exception e) { e.printStackTrace(); System.exit(100); } } protected GitBlit getGitBlitInstance() { return GitBlit.self(); } /** * Creates an http connector. * * @param useNIO * @param port * @param threadPoolSize * @return an http connector */ private Connector createConnector(boolean useNIO, int port, int threadPoolSize) { Connector connector; if (useNIO) { logger.info("Setting up NIO SelectChannelConnector on port " + port); SelectChannelConnector nioconn = new SelectChannelConnector(); nioconn.setSoLingerTime(-1); if (threadPoolSize > 0) { nioconn.setThreadPool(new QueuedThreadPool(threadPoolSize)); } connector = nioconn; } else { logger.info("Setting up SocketConnector on port " + port); SocketConnector sockconn = new SocketConnector(); if (threadPoolSize > 0) { sockconn.setThreadPool(new QueuedThreadPool(threadPoolSize)); } connector = sockconn; } connector.setPort(port); connector.setMaxIdleTime(30000); return connector; } /** * Creates an https connector. * * SSL renegotiation will be enabled if the JVM is 1.6.0_22 or later. * oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html * * @param certAlias * @param keyStore * @param clientTrustStore * @param storePassword * @param caRevocationList * @param useNIO * @param port * @param threadPoolSize * @param requireClientCertificates * @return an https connector */ private Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore, String storePassword, File caRevocationList, boolean useNIO, int port, int threadPoolSize, boolean requireClientCertificates) { GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias, keyStore, clientTrustStore, storePassword, caRevocationList); SslConnector connector; if (useNIO) { logger.info("Setting up NIO SslSelectChannelConnector on port " + port); SslSelectChannelConnector ssl = new SslSelectChannelConnector(factory); ssl.setSoLingerTime(-1); if (requireClientCertificates) { factory.setNeedClientAuth(true); } else { factory.setWantClientAuth(true); } if (threadPoolSize > 0) { ssl.setThreadPool(new QueuedThreadPool(threadPoolSize)); } connector = ssl; } else { logger.info("Setting up NIO SslSocketConnector on port " + port); SslSocketConnector ssl = new SslSocketConnector(factory); if (threadPoolSize > 0) { ssl.setThreadPool(new QueuedThreadPool(threadPoolSize)); } connector = ssl; } connector.setPort(port); connector.setMaxIdleTime(30000); return connector; } /** * Creates an ajp connector. * * @param port * @return an ajp connector */ private Connector createAJPConnector(int port) { logger.info("Setting up AJP Connector on port " + port); Ajp13SocketConnector ajp = new Ajp13SocketConnector(); ajp.setPort(port); if (port < 1024 && !isWindows()) { logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!"); } return ajp; } /** * Tests to see if the operating system is Windows. * * @return true if this is a windows machine */ private boolean isWindows() { return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1; } /** * The ShutdownMonitorThread opens a socket on a specified port and waits * for an incoming connection. When that connection is accepted a shutdown * message is issued to the running Jetty server. * * @author James Moger * */ private static class ShutdownMonitorThread extends Thread { private final ServerSocket socket; private final Server server; private final Logger logger = LoggerFactory.getLogger(ShutdownMonitorThread.class); public ShutdownMonitorThread(Server server, Params params) { this.server = server; setDaemon(true); setName(Constants.NAME + " Shutdown Monitor"); ServerSocket skt = null; try { skt = new ServerSocket(params.shutdownPort, 1, InetAddress.getByName("127.0.0.1")); } catch (Exception e) { logger.warn("Could not open shutdown monitor on port " + params.shutdownPort, e); } socket = skt; } @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); } } } /** * JCommander Parameters class for GitBlitServer. */ @Parameters(separators = " ") public static class Params { public static String baseFolder; private final FileSettings FILESETTINGS = new FileSettings(new File(baseFolder, Constants.PROPERTIES_FILE).getAbsolutePath()); /* * Server parameters */ @Parameter(names = { "-h", "--help" }, description = "Show this help") public Boolean help = false; @Parameter(names = { "--stop" }, description = "Stop Server") public Boolean stop = false; @Parameter(names = { "--tempFolder" }, description = "Folder for server to extract built-in webapp") public String temp = FILESETTINGS.getString(Keys.server.tempFolder, "temp"); /* * GIT Servlet Parameters */ @Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder") public String repositoriesFolder = FILESETTINGS.getString(Keys.git.repositoriesFolder, "git"); /* * Authentication Parameters */ @Parameter(names = { "--userService" }, description = "Authentication and Authorization Service (filename or fully qualified classname)") public String userService = FILESETTINGS.getString(Keys.realm.userService, "users.conf"); /* * JETTY Parameters */ @Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.") public Boolean useNIO = FILESETTINGS.getBoolean(Keys.server.useNio, true); @Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)") public Integer port = FILESETTINGS.getInteger(Keys.server.httpPort, 0); @Parameter(names = "--httpsPort", description = "HTTPS port to serve. (port <= 0 will disable this connector)") public Integer securePort = FILESETTINGS.getInteger(Keys.server.httpsPort, 8443); @Parameter(names = "--ajpPort", description = "AJP port to serve. (port <= 0 will disable this connector)") public Integer ajpPort = FILESETTINGS.getInteger(Keys.server.ajpPort, 0); @Parameter(names = "--gitPort", description = "Git Daemon port to serve. (port <= 0 will disable this connector)") public Integer gitPort = FILESETTINGS.getInteger(Keys.git.daemonPort, 9418); @Parameter(names = "--alias", description = "Alias of SSL certificate in keystore for serving https.") public String alias = FILESETTINGS.getString(Keys.server.certificateAlias, ""); @Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.") public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, ""); @Parameter(names = "--shutdownPort", description = "Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)") public Integer shutdownPort = FILESETTINGS.getInteger(Keys.server.shutdownPort, 8081); @Parameter(names = "--requireClientCertificates", description = "Require client X509 certificates for https connections.") public Boolean requireClientCertificates = FILESETTINGS.getBoolean(Keys.server.requireClientCertificates, false); /* * Setting overrides */ @Parameter(names = { "--settings" }, description = "Path to alternative settings") public String settingsfile; @Parameter(names = { "--ldapLdifFile" }, description = "Path to LDIF file. This will cause an in-memory LDAP server to be started according to gitblit settings") public String ldapLdifFile; } } src/main/java/com/gitblit/GitFilter.java
New file @@ -0,0 +1,246 @@ /* * 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.text.MessageFormat; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AuthorizationControl; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; /** * The GitFilter is an AccessRestrictionFilter which ensures that Git client * requests for push, clone, or view restricted repositories are authenticated * and authorized. * * @author James Moger * */ 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[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD", "/objects" }; /** * Extract the repository name from the url. * * @param cloneUrl * @return repository name */ public static String getRepositoryName(String value) { String repository = value; // get the repository name from the url by finding a known url suffix for (String urlSuffix : suffixes) { if (repository.indexOf(urlSuffix) > -1) { repository = repository.substring(0, repository.indexOf(urlSuffix)); } } return repository; } /** * Extract the repository name from the url. * * @param url * @return repository name */ @Override protected String extractRepositoryName(String url) { return GitFilter.getRepositoryName(url); } /** * Analyze the url and returns the action of the request. Return values are * either "/git-receive-pack" or "/git-upload-pack". * * @param serverUrl * @return action of the request */ @Override protected String getUrlRequestAction(String suffix) { if (!StringUtils.isEmpty(suffix)) { if (suffix.startsWith(gitReceivePack)) { return gitReceivePack; } else if (suffix.startsWith(gitUploadPack)) { return gitUploadPack; } else if (suffix.contains("?service=git-receive-pack")) { return gitReceivePack; } else if (suffix.contains("?service=git-upload-pack")) { return gitUploadPack; } else { return gitUploadPack; } } return null; } /** * Determine if a non-existing repository can be created using this filter. * * @return true if the server allows repository creation on-push */ @Override protected boolean isCreationAllowed() { return GitBlit.getBoolean(Keys.git.allowCreateOnPush, true); } /** * Determine if the repository can receive pushes. * * @param repository * @param action * @return true if the action may be performed */ @Override protected boolean isActionAllowed(RepositoryModel repository, String action) { // the log here has been moved into ReceiveHook to provide clients with // error messages return true; } @Override protected boolean requiresClientCertificate() { return GitBlit.getBoolean(Keys.git.requiresClientCertificate, false); } /** * Determine if the repository requires authentication. * * @param repository * @param action * @return true if authentication required */ @Override protected boolean requiresAuthentication(RepositoryModel repository, String action) { 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); } return false; } /** * Determine if the user can access the repository and perform the specified * action. * * @param repository * @param user * @param action * @return true if user may execute the action on the repository */ @Override protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) { // Git Servlet disabled return false; } if (action.equals(gitReceivePack)) { // Push request if (user.canPush(repository)) { return true; } else { // user is unauthorized to push to this repository logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}", user.username, repository)); return false; } } else if (action.equals(gitUploadPack)) { // Clone request if (user.canClone(repository)) { return true; } else { // user is unauthorized to clone this repository logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}", user.username, repository)); return false; } } return true; } /** * An authenticated user with the CREATE role can create a repository on * push. * * @param user * @param repository * @param action * @return the repository model, if it is created, null otherwise */ @Override protected RepositoryModel createRepository(UserModel user, String repository, String action) { boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action); if (isPush) { if (user.canCreate(repository)) { // user is pushing to a new repository // validate name if (repository.startsWith("../")) { logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository)); return null; } if (repository.contains("/../")) { logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository)); return null; } // confirm valid characters in repository name Character c = StringUtils.findInvalidCharacter(repository); if (c != null) { logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository)); return null; } // create repository RepositoryModel model = new RepositoryModel(); model.name = repository; model.addOwner(user.username); model.projectPath = StringUtils.getFirstPathElement(repository); if (model.isUsersPersonalRepository(user.username)) { // personal repository, default to private for user model.authorizationControl = AuthorizationControl.NAMED; model.accessRestriction = AccessRestrictionType.VIEW; } else { // common repository, user default server settings model.authorizationControl = AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, "")); model.accessRestriction = AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, "")); } // create the repository try { GitBlit.self().updateRepositoryModel(model.name, model, true); logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name)); return GitBlit.self().getRepositoryModel(model.name); } catch (GitBlitException e) { logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e); } } else { logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository)); } } // repository could not be created or action was not a push return null; } } src/main/java/com/gitblit/GitblitSslContextFactory.java
src/main/java/com/gitblit/GitblitTrustManager.java
src/main/java/com/gitblit/GitblitUserService.java
New file @@ -0,0 +1,337 @@ /* * 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.io.IOException; import java.text.MessageFormat; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccountType; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.StringUtils; /** * This class wraps the default user service and is recommended as the starting * point for custom user service implementations. * * This does seem a little convoluted, but the idea is to allow IUserService to * evolve with new methods and implementations without breaking custom * authentication implementations. * * The most common implementation of a custom IUserService is to only override * authentication and then delegate all other functionality to one of Gitblit's * user services. This class optimizes that use-case. * * Extending GitblitUserService allows for authentication customization without * having to keep-up-with IUSerService API changes. * * @author James Moger * */ public class GitblitUserService implements IUserService { protected IUserService serviceImpl; private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class); public GitblitUserService() { } @Override public void setup(IStoredSettings settings) { File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf"); serviceImpl = createUserService(realmFile); logger.info("GUS delegating to " + serviceImpl.toString()); } @SuppressWarnings("deprecation") protected IUserService createUserService(File realmFile) { IUserService service = null; if (realmFile.getName().toLowerCase().endsWith(".properties")) { // v0.5.0 - v0.7.0 properties-based realm file service = new FileUserService(realmFile); } else if (realmFile.getName().toLowerCase().endsWith(".conf")) { // v0.8.0+ config-based realm file service = new ConfigUserService(realmFile); } assert service != null; if (!realmFile.exists()) { // Create the Administrator account for a new realm file try { realmFile.createNewFile(); } catch (IOException x) { logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x); } UserModel admin = new UserModel("admin"); admin.password = "admin"; admin.canAdmin = true; admin.excludeFromFederation = true; service.updateUserModel(admin); } if (service instanceof FileUserService) { // automatically create a users.conf realm file from the original // users.properties file File usersConfig = new File(realmFile.getParentFile(), "users.conf"); if (!usersConfig.exists()) { logger.info(MessageFormat.format("Automatically creating {0} based on {1}", usersConfig.getAbsolutePath(), realmFile.getAbsolutePath())); ConfigUserService configService = new ConfigUserService(usersConfig); for (String username : service.getAllUsernames()) { UserModel userModel = service.getUserModel(username); configService.updateUserModel(userModel); } } // issue suggestion about switching to users.conf logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file"); } return service; } @Override public String toString() { return getClass().getSimpleName(); } @Override public boolean supportsCredentialChanges() { return serviceImpl.supportsCredentialChanges(); } @Override public boolean supportsDisplayNameChanges() { return serviceImpl.supportsDisplayNameChanges(); } @Override public boolean supportsEmailAddressChanges() { return serviceImpl.supportsEmailAddressChanges(); } @Override public boolean supportsTeamMembershipChanges() { return serviceImpl.supportsTeamMembershipChanges(); } @Override public boolean supportsCookies() { return serviceImpl.supportsCookies(); } @Override public String getCookie(UserModel model) { return serviceImpl.getCookie(model); } @Override public UserModel authenticate(char[] cookie) { UserModel user = serviceImpl.authenticate(cookie); setAccountType(user); return user; } @Override public UserModel authenticate(String username, char[] password) { UserModel user = serviceImpl.authenticate(username, password); setAccountType(user); return user; } @Override public void logout(UserModel user) { serviceImpl.logout(user); } @Override public UserModel getUserModel(String username) { UserModel user = serviceImpl.getUserModel(username); setAccountType(user); return user; } @Override public boolean updateUserModel(UserModel model) { return serviceImpl.updateUserModel(model); } @Override public boolean updateUserModels(Collection<UserModel> models) { return serviceImpl.updateUserModels(models); } @Override public boolean updateUserModel(String username, UserModel model) { if (model.isLocalAccount() || supportsCredentialChanges()) { if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) { // teams are externally controlled - copy from original model UserModel existingModel = getUserModel(username); model = DeepCopier.copy(model); model.teams.clear(); model.teams.addAll(existingModel.teams); } return serviceImpl.updateUserModel(username, model); } if (model.username.equals(username)) { // passwords are not persisted by the backing user service model.password = null; if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) { // teams are externally controlled- copy from original model UserModel existingModel = getUserModel(username); model = DeepCopier.copy(model); model.teams.clear(); model.teams.addAll(existingModel.teams); } return serviceImpl.updateUserModel(username, model); } logger.error("Users can not be renamed!"); return false; } @Override public boolean deleteUserModel(UserModel model) { return serviceImpl.deleteUserModel(model); } @Override public boolean deleteUser(String username) { return serviceImpl.deleteUser(username); } @Override public List<String> getAllUsernames() { return serviceImpl.getAllUsernames(); } @Override public List<UserModel> getAllUsers() { List<UserModel> users = serviceImpl.getAllUsers(); for (UserModel user : users) { setAccountType(user); } return users; } @Override public List<String> getAllTeamNames() { return serviceImpl.getAllTeamNames(); } @Override public List<TeamModel> getAllTeams() { return serviceImpl.getAllTeams(); } @Override public List<String> getTeamnamesForRepositoryRole(String role) { return serviceImpl.getTeamnamesForRepositoryRole(role); } @Override @Deprecated public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) { return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames); } @Override public TeamModel getTeamModel(String teamname) { return serviceImpl.getTeamModel(teamname); } @Override public boolean updateTeamModel(TeamModel model) { return serviceImpl.updateTeamModel(model); } @Override public boolean updateTeamModels(Collection<TeamModel> models) { return serviceImpl.updateTeamModels(models); } @Override public boolean updateTeamModel(String teamname, TeamModel model) { if (!supportsTeamMembershipChanges()) { // teams are externally controlled - copy from original model TeamModel existingModel = getTeamModel(teamname); model = DeepCopier.copy(model); model.users.clear(); model.users.addAll(existingModel.users); } return serviceImpl.updateTeamModel(teamname, model); } @Override public boolean deleteTeamModel(TeamModel model) { return serviceImpl.deleteTeamModel(model); } @Override public boolean deleteTeam(String teamname) { return serviceImpl.deleteTeam(teamname); } @Override public List<String> getUsernamesForRepositoryRole(String role) { return serviceImpl.getUsernamesForRepositoryRole(role); } @Override @Deprecated public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) { return serviceImpl.setUsernamesForRepositoryRole(role, usernames); } @Override public boolean renameRepositoryRole(String oldRole, String newRole) { return serviceImpl.renameRepositoryRole(oldRole, newRole); } @Override public boolean deleteRepositoryRole(String role) { return serviceImpl.deleteRepositoryRole(role); } protected boolean isLocalAccount(String username) { UserModel user = getUserModel(username); return user != null && user.isLocalAccount(); } protected void setAccountType(UserModel user) { if (user != null) { if (!StringUtils.isEmpty(user.password) && !Constants.EXTERNAL_ACCOUNT.equalsIgnoreCase(user.password) && !"StoredInLDAP".equalsIgnoreCase(user.password)) { user.accountType = AccountType.LOCAL; } else { user.accountType = getAccountType(); } } } protected AccountType getAccountType() { return AccountType.LOCAL; } } src/main/java/com/gitblit/IStoredSettings.java
New file @@ -0,0 +1,344 @@ /* * 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.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.utils.StringUtils; /** * Base class for stored settings implementations. * * @author James Moger * */ public abstract class IStoredSettings { protected final Logger logger; protected final Properties overrides = new Properties(); public IStoredSettings(Class<? extends IStoredSettings> clazz) { logger = LoggerFactory.getLogger(clazz); } protected abstract Properties read(); private Properties getSettings() { Properties props = read(); props.putAll(overrides); return props; } /** * Returns the list of keys whose name starts with the specified prefix. If * the prefix is null or empty, all key names are returned. * * @param startingWith * @return list of keys */ public List<String> getAllKeys(String startingWith) { List<String> keys = new ArrayList<String>(); Properties props = getSettings(); if (StringUtils.isEmpty(startingWith)) { keys.addAll(props.stringPropertyNames()); } else { startingWith = startingWith.toLowerCase(); for (Object o : props.keySet()) { String key = o.toString(); if (key.toLowerCase().startsWith(startingWith)) { keys.add(key); } } } return keys; } /** * Returns the boolean value for the specified key. If the key does not * exist or the value for the key can not be interpreted as a boolean, the * defaultValue is returned. * * @param key * @param defaultValue * @return key value or defaultValue */ public boolean getBoolean(String name, boolean defaultValue) { Properties props = getSettings(); if (props.containsKey(name)) { String value = props.getProperty(name); if (!StringUtils.isEmpty(value)) { return Boolean.parseBoolean(value.trim()); } } return defaultValue; } /** * Returns the integer value for the specified key. If the key does not * exist or the value for the key can not be interpreted as an integer, the * defaultValue is returned. * * @param key * @param defaultValue * @return key value or defaultValue */ public int getInteger(String name, int defaultValue) { Properties props = getSettings(); if (props.containsKey(name)) { try { String value = props.getProperty(name); if (!StringUtils.isEmpty(value)) { return Integer.parseInt(value.trim()); } } catch (NumberFormatException e) { logger.warn("Failed to parse integer for " + name + " using default of " + defaultValue); } } return defaultValue; } /** * Returns the long value for the specified key. If the key does not * exist or the value for the key can not be interpreted as an long, the * defaultValue is returned. * * @param key * @param defaultValue * @return key value or defaultValue */ public long getLong(String name, long defaultValue) { Properties props = getSettings(); if (props.containsKey(name)) { try { String value = props.getProperty(name); if (!StringUtils.isEmpty(value)) { return Long.parseLong(value.trim()); } } catch (NumberFormatException e) { logger.warn("Failed to parse long for " + name + " using default of " + defaultValue); } } return defaultValue; } /** * Returns an int filesize from a string value such as 50m or 50mb * @param name * @param defaultValue * @return an int filesize or defaultValue if the key does not exist or can * not be parsed */ public int getFilesize(String name, int defaultValue) { String val = getString(name, null); if (StringUtils.isEmpty(val)) { return defaultValue; } return com.gitblit.utils.FileUtils.convertSizeToInt(val, defaultValue); } /** * Returns an long filesize from a string value such as 50m or 50mb * @param n * @param defaultValue * @return a long filesize or defaultValue if the key does not exist or can * not be parsed */ public long getFilesize(String key, long defaultValue) { String val = getString(key, null); if (StringUtils.isEmpty(val)) { return defaultValue; } return com.gitblit.utils.FileUtils.convertSizeToLong(val, defaultValue); } /** * Returns the char value for the specified key. If the key does not exist * or the value for the key can not be interpreted as a char, the * defaultValue is returned. * * @param key * @param defaultValue * @return key value or defaultValue */ public char getChar(String name, char defaultValue) { Properties props = getSettings(); if (props.containsKey(name)) { String value = props.getProperty(name); if (!StringUtils.isEmpty(value)) { return value.trim().charAt(0); } } return defaultValue; } /** * Returns the string value for the specified key. If the key does not exist * or the value for the key can not be interpreted as a string, the * defaultValue is returned. * * @param key * @param defaultValue * @return key value or defaultValue */ public String getString(String name, String defaultValue) { Properties props = getSettings(); if (props.containsKey(name)) { String value = props.getProperty(name); if (value != null) { return value.trim(); } } return defaultValue; } /** * Returns the string value for the specified key. If the key does not * exist an exception is thrown. * * @param key * @return key value */ public String getRequiredString(String name) { Properties props = getSettings(); if (props.containsKey(name)) { String value = props.getProperty(name); if (value != null) { return value.trim(); } } throw new RuntimeException("Property (" + name + ") does not exist"); } /** * Returns a list of space-separated strings from the specified key. * * @param name * @return list of strings */ public List<String> getStrings(String name) { return getStrings(name, " "); } /** * Returns a list of strings from the specified key using the specified * string separator. * * @param name * @param separator * @return list of strings */ public List<String> getStrings(String name, String separator) { List<String> strings = new ArrayList<String>(); Properties props = getSettings(); if (props.containsKey(name)) { String value = props.getProperty(name); strings = StringUtils.getStringsFromValue(value, separator); } return strings; } /** * Returns a list of space-separated integers from the specified key. * * @param name * @return list of strings */ public List<Integer> getIntegers(String name) { return getIntegers(name, " "); } /** * Returns a list of integers from the specified key using the specified * string separator. * * @param name * @param separator * @return list of integers */ public List<Integer> getIntegers(String name, String separator) { List<Integer> ints = new ArrayList<Integer>(); Properties props = getSettings(); if (props.containsKey(name)) { String value = props.getProperty(name); List<String> strings = StringUtils.getStringsFromValue(value, separator); for (String str : strings) { try { int i = Integer.parseInt(str); ints.add(i); } catch (NumberFormatException e) { } } } return ints; } /** * Returns a map of strings from the specified key. * * @param name * @return map of string, string */ public Map<String, String> getMap(String name) { Map<String, String> map = new LinkedHashMap<String, String>(); for (String string : getStrings(name)) { String[] kvp = string.split("=", 2); String key = kvp[0]; String value = kvp[1]; map.put(key, value); } return map; } /** * Override the specified key with the specified value. * * @param key * @param value */ public void overrideSetting(String key, String value) { overrides.put(key, value); } /** * Override the specified key with the specified value. * * @param key * @param value */ public void overrideSetting(String key, int value) { overrides.put(key, "" + value); } /** * Updates the values for the specified keys and persists the entire * configuration file. * * @param map * of key, value pairs * @return true if successful */ public abstract boolean saveSettings(Map<String, String> updatedSettings); } src/main/java/com/gitblit/IUserService.java
New file @@ -0,0 +1,325 @@ /* * 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.util.Collection; import java.util.List; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; /** * Implementations of IUserService control all aspects of UserModel objects and * user authentication. * * @author James Moger * */ public interface IUserService { /** * Setup the user service. This method allows custom implementations to * retrieve settings from gitblit.properties or the web.xml file without * relying on the GitBlit static singleton. * * @param settings * @since 0.7.0 */ void setup(IStoredSettings settings); /** * Does the user service support changes to credentials? * * @return true or false * @since 1.0.0 */ boolean supportsCredentialChanges(); /** * Does the user service support changes to user display name? * * @return true or false * @since 1.0.0 */ boolean supportsDisplayNameChanges(); /** * Does the user service support changes to user email address? * * @return true or false * @since 1.0.0 */ boolean supportsEmailAddressChanges(); /** * Does the user service support changes to team memberships? * * @return true or false * @since 1.0.0 */ boolean supportsTeamMembershipChanges(); /** * Does the user service support cookie authentication? * * @return true or false */ boolean supportsCookies(); /** * Returns the cookie value for the specified user. * * @param model * @return cookie value */ String getCookie(UserModel model); /** * Authenticate a user based on their cookie. * * @param cookie * @return a user object or null */ UserModel authenticate(char[] cookie); /** * Authenticate a user based on a username and password. * * @param username * @param password * @return a user object or null */ UserModel authenticate(String username, char[] password); /** * Logout a user. * * @param user */ void logout(UserModel user); /** * Retrieve the user object for the specified username. * * @param username * @return a user object or null */ UserModel getUserModel(String username); /** * Updates/writes a complete user object. * * @param model * @return true if update is successful */ boolean updateUserModel(UserModel model); /** * Updates/writes all specified user objects. * * @param models a list of user models * @return true if update is successful * @since 1.2.0 */ boolean updateUserModels(Collection<UserModel> models); /** * Adds/updates a user object keyed by username. This method allows for * renaming a user. * * @param username * the old username * @param model * the user object to use for username * @return true if update is successful */ boolean updateUserModel(String username, UserModel model); /** * Deletes the user object from the user service. * * @param model * @return true if successful */ boolean deleteUserModel(UserModel model); /** * Delete the user object with the specified username * * @param username * @return true if successful */ boolean deleteUser(String username); /** * Returns the list of all users available to the login service. * * @return list of all usernames */ List<String> getAllUsernames(); /** * Returns the list of all users available to the login service. * * @return list of all users * @since 0.8.0 */ List<UserModel> getAllUsers(); /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ List<String> getAllTeamNames(); /** * Returns the list of all teams available to the login service. * * @return list of all teams * @since 0.8.0 */ List<TeamModel> getAllTeams(); /** * Returns the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction * @since 0.8.0 */ List<String> getTeamnamesForRepositoryRole(String role); /** * Sets the list of all teams who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param teamnames * @return true if successful * @since 0.8.0 */ @Deprecated boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames); /** * Retrieve the team object for the specified team name. * * @param teamname * @return a team object or null * @since 0.8.0 */ TeamModel getTeamModel(String teamname); /** * Updates/writes a complete team object. * * @param model * @return true if update is successful * @since 0.8.0 */ boolean updateTeamModel(TeamModel model); /** * Updates/writes all specified team objects. * * @param models a list of team models * @return true if update is successful * @since 1.2.0 */ boolean updateTeamModels(Collection<TeamModel> models); /** * Updates/writes and replaces a complete team object keyed by teamname. * This method allows for renaming a team. * * @param teamname * the old teamname * @param model * the team object to use for teamname * @return true if update is successful * @since 0.8.0 */ boolean updateTeamModel(String teamname, TeamModel model); /** * Deletes the team object from the user service. * * @param model * @return true if successful * @since 0.8.0 */ boolean deleteTeamModel(TeamModel model); /** * Delete the team object with the specified teamname * * @param teamname * @return true if successful * @since 0.8.0 */ boolean deleteTeam(String teamname); /** * Returns the list of all users who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @return list of all usernames that can bypass the access restriction * @since 0.8.0 */ List<String> getUsernamesForRepositoryRole(String role); /** * Sets the list of all uses who are allowed to bypass the access * restriction placed on the specified repository. * * @param role * the repository name * @param usernames * @return true if successful */ @Deprecated boolean setUsernamesForRepositoryRole(String role, List<String> usernames); /** * Renames a repository role. * * @param oldRole * @param newRole * @return true if successful */ boolean renameRepositoryRole(String oldRole, String newRole); /** * Removes a repository role from all users. * * @param role * @return true if successful */ boolean deleteRepositoryRole(String role); /** * @See java.lang.Object.toString(); * @return string representation of the login service */ String toString(); } src/main/java/com/gitblit/JsonServlet.java
New file @@ -0,0 +1,130 @@ /* * 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.BufferedReader; import java.io.IOException; 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.StringUtils; /** * Servlet class for interpreting json requests. * * @author James Moger * */ public abstract class JsonServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected final int forbiddenCode = HttpServletResponse.SC_FORBIDDEN; protected final int notAllowedCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED; protected final int failureCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; protected final Logger logger; public JsonServlet() { super(); logger = LoggerFactory.getLogger(getClass()); } /** * Processes an gson request. * * @param request * @param response * @throws javax.servlet.ServletException * @throws java.io.IOException */ protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; @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); } protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, Class<X> clazz) throws IOException { String json = readJson(request, response); if (StringUtils.isEmpty(json)) { return null; } X object = JsonUtils.fromJsonString(json.toString(), clazz); return object; } protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, Type type) throws IOException { String json = readJson(request, response); if (StringUtils.isEmpty(json)) { return null; } X object = JsonUtils.fromJsonString(json.toString(), type); return object; } 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(); } 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("application/json"); response.getWriter().append(json); } } } src/main/java/com/gitblit/Launcher.java
New file @@ -0,0 +1,148 @@ /* * 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.io.FileFilter; import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.security.ProtectionDomain; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Launch helper class that adds all jars found in the local "lib" & "ext" * folders and then calls the application main. Using this technique we do not * have to specify a classpath and we can dynamically add jars to the * distribution. * * @author James Moger * */ public class Launcher { public static final boolean DEBUG = false; /** * Parameters of the method to add an URL to the System classes. */ private static final Class<?>[] PARAMETERS = new Class[] { URL.class }; public static void main(String[] args) { if (DEBUG) { System.out.println("jcp=" + System.getProperty("java.class.path")); ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain(); System.out.println("launcher=" + protectionDomain.getCodeSource().getLocation().toExternalForm()); } // Load the JARs in the lib and ext folder String[] folders = new String[] { "lib", "ext" }; List<File> jars = new ArrayList<File>(); for (String folder : folders) { if (folder == null) { continue; } File libFolder = new File(folder); if (!libFolder.exists()) { continue; } List<File> found = findJars(libFolder.getAbsoluteFile()); jars.addAll(found); } // sort the jars by name and then reverse the order so the newer version // of the library gets loaded in the event that this is an upgrade Collections.sort(jars); Collections.reverse(jars); if (jars.size() == 0) { for (String folder : folders) { File libFolder = new File(folder); // this is a test of adding a comment // more really interesting things System.err.println("Failed to find any JARs in " + libFolder.getPath()); } System.exit(-1); } else { for (File jar : jars) { try { jar.canRead(); addJarFile(jar); } catch (Throwable t) { t.printStackTrace(); } } } // Start Server GitBlitServer.main(args); } public static List<File> findJars(File folder) { List<File> jars = new ArrayList<File>(); if (folder.exists()) { File[] libs = folder.listFiles(new FileFilter() { @Override public boolean accept(File file) { return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar"); } }); if (libs != null && libs.length > 0) { jars.addAll(Arrays.asList(libs)); if (DEBUG) { for (File jar : jars) { System.out.println("found " + jar); } } } } return jars; } /** * Adds a file to the classpath * * @param f * the file to be added * @throws IOException */ public static void addJarFile(File f) throws IOException { if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) { // don't add source or javadoc jars to runtime classpath return; } URL u = f.toURI().toURL(); if (DEBUG) { System.out.println("load=" + u.toExternalForm()); } URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class<?> sysclass = URLClassLoader.class; try { Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS); method.setAccessible(true); method.invoke(sysloader, new Object[] { u }); } catch (Throwable t) { throw new IOException(MessageFormat.format( "Error, could not add {0} to system classloader", f.getPath()), t); } } } src/main/java/com/gitblit/LdapUserService.java
New file @@ -0,0 +1,496 @@ /* * Copyright 2012 John Crygier * 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; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccountType; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.ExtendedResult; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPSearchException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; import com.unboundid.util.ssl.SSLUtil; import com.unboundid.util.ssl.TrustAllTrustManager; /** * Implementation of an LDAP user service. * * @author John Crygier */ public class LdapUserService extends GitblitUserService { public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class); private IStoredSettings settings; private AtomicLong lastLdapUserSync = new AtomicLong(0L); public LdapUserService() { super(); } private long getSynchronizationPeriod() { final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES"); try { final String[] s = cacheDuration.split(" ", 2); long duration = Long.parseLong(s[0]); TimeUnit timeUnit = TimeUnit.valueOf(s[1]); return timeUnit.toMillis(duration); } catch (RuntimeException ex) { throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'"); } } @Override public void setup(IStoredSettings settings) { this.settings = settings; String file = settings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf"); File realmFile = GitBlit.getFileOrFolder(file); serviceImpl = createUserService(realmFile); logger.info("LDAP User Service backed by " + serviceImpl.toString()); synchronizeLdapUsers(); } protected synchronized void synchronizeLdapUsers() { final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false); if (enabled) { if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) { logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server)); final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true); LDAPConnection ldapConnection = getLdapConnection(); if (ldapConnection != null) { try { String accountBase = settings.getString(Keys.realm.ldap.accountBase, ""); String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid"); String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); accountPattern = StringUtils.replace(accountPattern, "${username}", "*"); SearchResult result = doSearch(ldapConnection, accountBase, accountPattern); if (result != null && result.getEntryCount() > 0) { final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>(); for (SearchResultEntry loggingInUser : result.getSearchEntries()) { final String username = loggingInUser.getAttribute(uidAttribute).getValue(); logger.debug("LDAP synchronizing: " + username); UserModel user = getUserModel(username); if (user == null) { user = new UserModel(username); } if (!supportsTeamMembershipChanges()) getTeamsFromLdap(ldapConnection, username, loggingInUser, user); // Get User Attributes setUserAttributes(user, loggingInUser); // store in map ldapUsers.put(username.toLowerCase(), user); } if (deleteRemovedLdapUsers) { logger.debug("detecting removed LDAP users..."); for (UserModel userModel : super.getAllUsers()) { if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) { if (! ldapUsers.containsKey(userModel.username)) { logger.info("deleting removed LDAP user " + userModel.username + " from backing user service"); super.deleteUser(userModel.username); } } } } super.updateUserModels(ldapUsers.values()); if (!supportsTeamMembershipChanges()) { final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>(); for (UserModel user : ldapUsers.values()) { for (TeamModel userTeam : user.teams) { userTeams.put(userTeam.name, userTeam); } } updateTeamModels(userTeams.values()); } } lastLdapUserSync.set(System.currentTimeMillis()); } finally { ldapConnection.close(); } } } } } private LDAPConnection getLdapConnection() { try { URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server)); String bindUserName = settings.getString(Keys.realm.ldap.username, ""); String bindPassword = settings.getString(Keys.realm.ldap.password, ""); int ldapPort = ldapUrl.getPort(); if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) { // SSL if (ldapPort == -1) // Default Port ldapPort = 636; SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword); } else { if (ldapPort == -1) // Default Port ldapPort = 389; LDAPConnection conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword); if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) { SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); ExtendedResult extendedResult = conn.processExtendedOperation( new StartTLSExtendedRequest(sslUtil.createSSLContext())); if (extendedResult.getResultCode() != ResultCode.SUCCESS) { throw new LDAPException(extendedResult.getResultCode()); } } return conn; } } catch (URISyntaxException e) { logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e); } catch (GeneralSecurityException e) { logger.error("Unable to create SSL Connection", e); } catch (LDAPException e) { logger.error("Error Connecting to LDAP", e); } return null; } /** * Credentials are defined in the LDAP server and can not be manipulated * from Gitblit. * * @return false * @since 1.0.0 */ @Override public boolean supportsCredentialChanges() { return false; } /** * If no displayName pattern is defined then Gitblit can manage the display name. * * @return true if Gitblit can manage the user display name * @since 1.0.0 */ @Override public boolean supportsDisplayNameChanges() { return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, "")); } /** * If no email pattern is defined then Gitblit can manage the email address. * * @return true if Gitblit can manage the user email address * @since 1.0.0 */ @Override public boolean supportsEmailAddressChanges() { 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 * changes must be made on the LDAP server by the LDAP administrator. * * @return true or false * @since 1.0.0 */ public boolean supportsTeamMembershipChanges() { return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false); } @Override protected AccountType getAccountType() { return AccountType.LDAP; } @Override public UserModel authenticate(String username, char[] password) { if (isLocalAccount(username)) { // local account, bypass LDAP authentication return super.authenticate(username, password); } String simpleUsername = getSimpleUsername(username); LDAPConnection ldapConnection = getLdapConnection(); if (ldapConnection != null) { try { // Find the logging in user's DN String accountBase = settings.getString(Keys.realm.ldap.accountBase, ""); String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))"); accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); SearchResult result = doSearch(ldapConnection, accountBase, accountPattern); if (result != null && result.getEntryCount() == 1) { SearchResultEntry loggingInUser = result.getSearchEntries().get(0); String loggingInUserDN = loggingInUser.getDN(); if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) { logger.debug("LDAP authenticated: " + username); UserModel user = null; synchronized (this) { user = getUserModel(simpleUsername); if (user == null) // create user object for new authenticated user user = new UserModel(simpleUsername); // create a user cookie if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { user.cookie = StringUtils.getSHA1(user.username + new String(password)); } if (!supportsTeamMembershipChanges()) getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user); // Get User Attributes setUserAttributes(user, loggingInUser); // Push the ldap looked up values to backing file super.updateUserModel(user); if (!supportsTeamMembershipChanges()) { for (TeamModel userTeam : user.teams) updateTeamModel(userTeam); } } return user; } } } finally { ldapConnection.close(); } } return null; } /** * Set the admin attribute from team memberships retrieved from LDAP. * If we are not storing teams in LDAP and/or we have not defined any * administrator teams, then do not change the admin flag. * * @param user */ private void setAdminAttribute(UserModel user) { if (!supportsTeamMembershipChanges()) { List<String> admins = settings.getStrings(Keys.realm.ldap.admins); // if we have defined administrative teams, then set admin flag // otherwise leave admin flag unchanged if (!ArrayUtils.isEmpty(admins)) { user.canAdmin = false; for (String admin : admins) { if (admin.startsWith("@")) { // Team if (user.getTeam(admin.substring(1)) != null) user.canAdmin = true; } else if (user.getName().equalsIgnoreCase(admin)) user.canAdmin = true; } } } } private void setUserAttributes(UserModel user, SearchResultEntry userEntry) { // Is this user an admin? setAdminAttribute(user); // Don't want visibility into the real password, make up a dummy user.password = Constants.EXTERNAL_ACCOUNT; user.accountType = getAccountType(); // Get full name Attribute String displayName = settings.getString(Keys.realm.ldap.displayName, ""); if (!StringUtils.isEmpty(displayName)) { // Replace embedded ${} with attributes if (displayName.contains("${")) { for (Attribute userAttribute : userEntry.getAttributes()) displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue()); user.displayName = displayName; } else { Attribute attribute = userEntry.getAttribute(displayName); if (attribute != null && attribute.hasValue()) { user.displayName = attribute.getValue(); } } } // Get email address Attribute String email = settings.getString(Keys.realm.ldap.email, ""); if (!StringUtils.isEmpty(email)) { if (email.contains("${")) { for (Attribute userAttribute : userEntry.getAttributes()) email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue()); user.emailAddress = email; } else { Attribute attribute = userEntry.getAttribute(email); if (attribute != null && attribute.hasValue()) { user.emailAddress = attribute.getValue(); } } } } private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) { String loggingInUserDN = loggingInUser.getDN(); user.teams.clear(); // Clear the users team memberships - we're going to get them from LDAP String groupBase = settings.getString(Keys.realm.ldap.groupBase, ""); String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))"); groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN)); groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername)); // Fill in attributes into groupMemberPattern for (Attribute userAttribute : loggingInUser.getAttributes()) groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue())); SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, groupMemberPattern); if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) { for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) { SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i); String teamName = teamEntry.getAttribute("cn").getValue(); TeamModel teamModel = getTeamModel(teamName); if (teamModel == null) teamModel = createTeamFromLdap(teamEntry); user.teams.add(teamModel); teamModel.addUser(user.getName()); } } } private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) { TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn")); // potentially retrieve other attributes here in the future return answer; } private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) { try { return ldapConnection.search(base, SearchScope.SUB, filter); } catch (LDAPSearchException e) { logger.error("Problem Searching LDAP", e); return null; } } private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) { try { // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN ldapConnection.bind(userDn, password); return true; } catch (LDAPException e) { logger.error("Error authenticating user", e); return false; } } @Override public List<String> getAllUsernames() { synchronizeLdapUsers(); return super.getAllUsernames(); } @Override public List<UserModel> getAllUsers() { synchronizeLdapUsers(); return super.getAllUsers(); } /** * Returns a simple username without any domain prefixes. * * @param username * @return a simple username */ protected String getSimpleUsername(String username) { int lastSlash = username.lastIndexOf('\\'); if (lastSlash > -1) { username = username.substring(lastSlash + 1); } return username; } // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java public static final String escapeLDAPSearchFilter(String filter) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < filter.length(); i++) { char curChar = filter.charAt(i); switch (curChar) { case '\\': sb.append("\\5c"); break; case '*': sb.append("\\2a"); break; case '(': sb.append("\\28"); break; case ')': sb.append("\\29"); break; case '\u0000': sb.append("\\00"); break; default: sb.append(curChar); } } return sb.toString(); } } src/main/java/com/gitblit/LogoServlet.java
New file @@ -0,0 +1,96 @@ /* * 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 java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Handles requests for logo.png * * @author James Moger * */ public class LogoServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final long lastModified = System.currentTimeMillis(); public LogoServlet() { super(); } @Override protected long getLastModified(HttpServletRequest req) { File file = GitBlit.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png"); if (file.exists()) { return Math.max(lastModified, file.lastModified()); } else { return lastModified; } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InputStream is = null; try { String contentType = null; File file = GitBlit.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png"); if (file.exists()) { // custom logo ServletContext context = request.getSession().getServletContext(); contentType = context.getMimeType(file.getName()); response.setContentLength((int) file.length()); response.setDateHeader("Last-Modified", Math.max(lastModified, file.lastModified())); is = new FileInputStream(file); } else { // default logo response.setDateHeader("Last-Modified", lastModified); is = getClass().getResourceAsStream("/logo.png"); } if (contentType == null) { contentType = "image/png"; } response.setContentType(contentType); response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); OutputStream os = response.getOutputStream(); byte[] buf = new byte[4096]; int bytesRead = is.read(buf); while (bytesRead != -1) { os.write(buf, 0, bytesRead); bytesRead = is.read(buf); } os.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if(is != null) { is.close(); } } } } src/main/java/com/gitblit/LuceneExecutor.java
src/main/java/com/gitblit/MailExecutor.java
New file @@ -0,0 +1,226 @@ /* * 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.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.regex.Pattern; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.utils.StringUtils; /** * The mail executor handles sending email messages asynchronously from queue. * * @author James Moger * */ public class MailExecutor implements Runnable { private final Logger logger = LoggerFactory.getLogger(MailExecutor.class); private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>(); private final Session session; private final IStoredSettings settings; public MailExecutor(IStoredSettings settings) { this.settings = settings; final String mailUser = settings.getString(Keys.mail.username, null); final String mailPassword = settings.getString(Keys.mail.password, null); final boolean smtps = settings.getBoolean(Keys.mail.smtps, false); boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword); String server = settings.getString(Keys.mail.server, ""); if (StringUtils.isEmpty(server)) { session = null; return; } int port = settings.getInteger(Keys.mail.port, 25); boolean isGMail = false; if (server.equals("smtp.gmail.com")) { port = 465; isGMail = true; } Properties props = new Properties(); props.setProperty("mail.smtp.host", server); props.setProperty("mail.smtp.port", String.valueOf(port)); props.setProperty("mail.smtp.auth", String.valueOf(authenticate)); props.setProperty("mail.smtp.auths", String.valueOf(authenticate)); if (isGMail || smtps) { props.setProperty("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.socketFactory.port", String.valueOf(port)); props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); props.put("mail.smtp.socketFactory.fallback", "false"); } if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) { // SMTP requires authentication session = Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { PasswordAuthentication passwordAuthentication = new PasswordAuthentication( mailUser, mailPassword); return passwordAuthentication; } }); } else { // SMTP does not require authentication session = Session.getInstance(props); } } /** * Indicates if the mail executor can send emails. * * @return true if the mail executor is ready to send emails */ public boolean isReady() { return session != null; } /** * Create a message. * * @param toAddresses * @return a message */ public Message createMessage(String... toAddresses) { return createMessage(Arrays.asList(toAddresses)); } /** * Create a message. * * @param toAddresses * @return a message */ public Message createMessage(List<String> toAddresses) { MimeMessage message = new MimeMessage(session); try { String fromAddress = settings.getString(Keys.mail.fromAddress, null); if (StringUtils.isEmpty(fromAddress)) { fromAddress = "gitblit@gitblit.com"; } InternetAddress from = new InternetAddress(fromAddress, "Gitblit"); message.setFrom(from); // determine unique set of addresses Set<String> uniques = new HashSet<String>(); for (String address : toAddresses) { uniques.add(address.toLowerCase()); } Pattern validEmail = Pattern .compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); List<InternetAddress> tos = new ArrayList<InternetAddress>(); for (String address : uniques) { if (StringUtils.isEmpty(address)) { continue; } if (validEmail.matcher(address).find()) { try { tos.add(new InternetAddress(address)); } catch (Throwable t) { } } } message.setRecipients(Message.RecipientType.BCC, tos.toArray(new InternetAddress[tos.size()])); message.setSentDate(new Date()); } catch (Exception e) { logger.error("Failed to properly create message", e); } return message; } /** * Returns the status of the mail queue. * * @return true, if the queue is empty */ public boolean hasEmptyQueue() { return queue.isEmpty(); } /** * Queue's an email message to be sent. * * @param message * @return true if the message was queued */ public boolean queue(Message message) { if (!isReady()) { return false; } try { message.saveChanges(); } catch (Throwable t) { logger.error("Failed to save changes to message!", t); } queue.add(message); return true; } @Override public void run() { if (!queue.isEmpty()) { if (session != null) { // send message via mail server List<Message> failures = new ArrayList<Message>(); Message message = null; while ((message = queue.poll()) != null) { try { if (settings.getBoolean(Keys.mail.debug, false)) { logger.info("send: " + StringUtils.trimString(message.getSubject(), 60)); } Transport.send(message); } catch (Throwable e) { logger.error("Failed to send message", e); failures.add(message); } } // push the failures back onto the queue for the next cycle queue.addAll(failures); } } } public void sendNow(Message message) throws Exception { Transport.send(message); } } src/main/java/com/gitblit/PagesFilter.java
New file @@ -0,0 +1,126 @@ /* * 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; import org.eclipse.jgit.lib.Repository; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; /** * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages * requests for a view-restricted repository are authenticated and authorized. * * @author James Moger * */ public class PagesFilter extends AccessRestrictionFilter { /** * Extract the repository name from the url. * * @param url * @return repository name */ @Override protected String extractRepositoryName(String url) { // get the repository name from the url by finding a known url suffix String repository = ""; Repository r = null; int offset = 0; while (r == null) { int slash = url.indexOf('/', offset); if (slash == -1) { repository = url; } else { repository = url.substring(0, slash); } r = GitBlit.self().getRepository(repository, false); if (r == null) { // try again offset = slash + 1; } else { // close the repo r.close(); } if (repository.equals(url)) { // either only repository in url or no repository found break; } } return repository; } /** * Analyze the url and returns the action of the request. * * @param cloneUrl * @return action of the request */ @Override protected String getUrlRequestAction(String suffix) { return "VIEW"; } /** * Determine if a non-existing repository can be created using this filter. * * @return true if the filter allows repository creation */ @Override protected boolean isCreationAllowed() { return false; } /** * Determine if the action may be executed on the repository. * * @param repository * @param action * @return true if the action may be performed */ @Override protected boolean isActionAllowed(RepositoryModel repository, String action) { return true; } /** * Determine if the repository requires authentication. * * @param repository * @param action * @return true if authentication required */ @Override protected boolean requiresAuthentication(RepositoryModel repository, String action) { return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW); } /** * Determine if the user can access the repository and perform the specified * action. * * @param repository * @param user * @param action * @return true if user may execute the action on the repository */ @Override protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { return user.canView(repository); } } src/main/java/com/gitblit/PagesServlet.java
New file @@ -0,0 +1,261 @@ /* * 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; import java.io.IOException; import java.text.MessageFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.models.RefModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; /** * Serves the content of a gh-pages branch. * * @author James Moger * */ public class PagesServlet extends HttpServlet { private static final long serialVersionUID = 1L; private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class); public PagesServlet() { super(); } /** * 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)); } /** * Retrieves the specified resource from the gh-pages branch of the * repository. * * @param request * @param response * @throws javax.servlet.ServletException * @throws java.io.IOException */ private void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String path = request.getPathInfo(); if (path.toLowerCase().endsWith(".git")) { // forward to url with trailing / // this is important for relative pages links response.sendRedirect(request.getServletPath() + path + "/"); return; } if (path.charAt(0) == '/') { // strip leading / path = path.substring(1); } // determine repository and resource from url String repository = ""; String resource = ""; Repository r = null; int offset = 0; while (r == null) { int slash = path.indexOf('/', offset); if (slash == -1) { repository = path; } else { repository = path.substring(0, slash); } r = GitBlit.self().getRepository(repository, false); offset = slash + 1; if (offset > 0) { resource = path.substring(offset); } if (repository.equals(path)) { // either only repository in url or no repository found break; } } ServletContext context = request.getSession().getServletContext(); try { if (r == null) { // repository not found! String mkd = MessageFormat.format( "# Error\nSorry, no valid **repository** specified in this url: {0}!", repository); error(response, mkd); return; } // retrieve the content from the repository RefModel pages = JGitUtils.getPagesBranch(r); RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName()); if (commit == null) { // branch not found! String mkd = MessageFormat.format( "# Error\nSorry, the repository {0} does not have a **gh-pages** branch!", repository); error(response, mkd); r.close(); return; } String [] encodings = GitBlit.getEncodings(); RevTree tree = commit.getTree(); byte[] content = null; if (StringUtils.isEmpty(resource)) { // find resource List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions); List<String> extensions = new ArrayList<String>(markdownExtensions.size() + 2); extensions.add("html"); extensions.add("htm"); extensions.addAll(markdownExtensions); for (String ext : extensions){ String file = "index." + ext; String stringContent = JGitUtils.getStringContent(r, tree, file, encodings); if(stringContent == null){ continue; } content = stringContent.getBytes(Constants.ENCODING); if (content != null) { resource = file; // assume text/html unless the servlet container // overrides response.setContentType("text/html; charset=" + Constants.ENCODING); break; } } } else { // specific resource try { String contentType = context.getMimeType(resource); if (contentType == null) { contentType = "text/plain"; } if (contentType.startsWith("text")) { content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes( Constants.ENCODING); } else { content = JGitUtils.getByteContent(r, tree, resource, false); } response.setContentType(contentType); } catch (Exception e) { } } // no content, try custom 404 page if (ArrayUtils.isEmpty(content)) { String custom404 = JGitUtils.getStringContent(r, tree, "404.html", encodings); if (!StringUtils.isEmpty(custom404)) { content = custom404.getBytes(Constants.ENCODING); } // still no content if (ArrayUtils.isEmpty(content)) { String str = MessageFormat.format( "# Error\nSorry, the requested resource **{0}** was not found.", resource); content = MarkdownUtils.transformMarkdown(str).getBytes(Constants.ENCODING); } try { // output the content logger.warn("Pages 404: " + resource); response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.getOutputStream().write(content); response.flushBuffer(); } catch (Throwable t) { logger.error("Failed to write page to client", t); } return; } // check to see if we should transform markdown files for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) { if (resource.endsWith(ext)) { String mkd = new String(content, Constants.ENCODING); content = MarkdownUtils.transformMarkdown(mkd).getBytes(Constants.ENCODING); response.setContentType("text/html; charset=" + Constants.ENCODING); break; } } try { // output the content response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime()); response.getOutputStream().write(content); response.flushBuffer(); } catch (Throwable t) { logger.error("Failed to write page to client", t); } // close the repository r.close(); } catch (Throwable t) { logger.error("Failed to write page to client", t); } } private void error(HttpServletResponse response, String mkd) throws ServletException, IOException, ParseException { String content = MarkdownUtils.transformMarkdown(mkd); response.setContentType("text/html; charset=" + Constants.ENCODING); response.getWriter().write(content); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } } src/main/java/com/gitblit/RedmineUserService.java
New file @@ -0,0 +1,187 @@ package com.gitblit; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import org.apache.wicket.util.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccountType; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.ConnectionUtils; import com.gitblit.utils.StringUtils; import com.google.gson.Gson; /** * Implementation of an Redmine user service.<br> * you can login to gitblit with Redmine user id and api key. */ public class RedmineUserService extends GitblitUserService { private final Logger logger = LoggerFactory.getLogger(RedmineUserService.class); private IStoredSettings settings; private String testingJson; private class RedmineCurrent { private class RedmineUser { public String login; public String firstname; public String lastname; public String mail; } public RedmineUser user; } public RedmineUserService() { super(); } @Override public void setup(IStoredSettings settings) { this.settings = settings; String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf"); File realmFile = GitBlit.getFileOrFolder(file); serviceImpl = createUserService(realmFile); logger.info("Redmine User Service backed by " + serviceImpl.toString()); } @Override public boolean supportsCredentialChanges() { return false; } @Override public boolean supportsDisplayNameChanges() { return false; } @Override public boolean supportsEmailAddressChanges() { return false; } @Override public boolean supportsTeamMembershipChanges() { return false; } @Override protected AccountType getAccountType() { return AccountType.REDMINE; } @Override public UserModel authenticate(String username, char[] password) { if (isLocalAccount(username)) { // local account, bypass Redmine authentication return super.authenticate(username, password); } String jsonString = null; try { // first attempt by username/password jsonString = getCurrentUserAsJson(username, password); } catch (Exception e1) { logger.warn("Failed to authenticate via username/password against Redmine"); try { // second attempt is by apikey jsonString = getCurrentUserAsJson(null, password); username = null; } catch (Exception e2) { logger.error("Failed to authenticate via apikey against Redmine", e2); return null; } } if (StringUtils.isEmpty(jsonString)) { logger.error("Received empty authentication response from Redmine"); return null; } RedmineCurrent current = null; try { current = new Gson().fromJson(jsonString, RedmineCurrent.class); } catch (Exception e) { logger.error("Failed to deserialize Redmine json response: " + jsonString, e); return null; } if (StringUtils.isEmpty(username)) { // if the username has been reset because of apikey authentication // then use the email address of the user. this is the original // behavior as contributed by github/mallowlabs username = current.user.mail; } UserModel user = getUserModel(username); if (user == null) // create user object for new authenticated user user = new UserModel(username.toLowerCase()); // create a user cookie if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { user.cookie = StringUtils.getSHA1(user.username + new String(password)); } // update user attributes from Redmine user.accountType = getAccountType(); user.displayName = current.user.firstname + " " + current.user.lastname; user.emailAddress = current.user.mail; user.password = Constants.EXTERNAL_ACCOUNT; if (!StringUtils.isEmpty(current.user.login)) { // only admin users can get login name // evidently this is an undocumented behavior of Redmine user.canAdmin = true; } // TODO consider Redmine group mapping for team membership // http://www.redmine.org/projects/redmine/wiki/Rest_Users // push the changes to the backing user service super.updateUserModel(user); return user; } private String getCurrentUserAsJson(String username, char [] password) throws IOException { if (testingJson != null) { // for testing return testingJson; } String url = this.settings.getString(Keys.realm.redmine.url, ""); if (!url.endsWith("/")) { url = url.concat("/"); } HttpURLConnection http; if (username == null) { // apikey authentication String apiKey = String.valueOf(password); String apiUrl = url + "users/current.json?key=" + apiKey; http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null); } else { // username/password BASIC authentication String apiUrl = url + "users/current.json"; http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password); } http.setRequestMethod("GET"); http.connect(); InputStreamReader reader = new InputStreamReader(http.getInputStream()); return IOUtils.toString(reader); } /** * set json response. do NOT invoke from production code. * @param json json */ public void setTestingCurrentUserAsJson(String json) { this.testingJson = json; } } src/main/java/com/gitblit/RobotsTxtServlet.java
src/main/java/com/gitblit/RpcFilter.java
src/main/java/com/gitblit/RpcServlet.java
New file @@ -0,0 +1,348 @@ /* * 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.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.Repository; import com.gitblit.Constants.RpcRequest; import com.gitblit.models.RefModel; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.RpcUtils; /** * Handles remote procedure calls. * * @author James Moger * */ public class RpcServlet extends JsonServlet { private static final long serialVersionUID = 1L; public static final int PROTOCOL_VERSION = 5; public RpcServlet() { super(); } /** * Processes an rpc request. * * @param request * @param response * @throws javax.servlet.ServletException * @throws java.io.IOException */ @Override protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RpcRequest reqType = RpcRequest.fromName(request.getParameter("req")); String objectName = request.getParameter("name"); logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType, request.getRemoteAddr())); UserModel user = (UserModel) request.getUserPrincipal(); boolean allowManagement = user != null && user.canAdmin() && GitBlit.getBoolean(Keys.web.enableRpcManagement, false); boolean allowAdmin = user != null && user.canAdmin() && GitBlit.getBoolean(Keys.web.enableRpcAdministration, false); Object result = null; if (RpcRequest.GET_PROTOCOL.equals(reqType)) { // Return the protocol version result = PROTOCOL_VERSION; } else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) { // Determine the Gitblit clone url String gitblitUrl = HttpUtils.getGitblitURL(request); StringBuilder sb = new StringBuilder(); sb.append(gitblitUrl); sb.append(Constants.GIT_PATH); sb.append("{0}"); String cloneUrl = sb.toString(); // list repositories List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user); Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>(); for (RepositoryModel model : list) { String url = MessageFormat.format(cloneUrl, model.name); repositories.put(url, model); } result = repositories; } else if (RpcRequest.LIST_BRANCHES.equals(reqType)) { // list all local branches in all repositories accessible to user Map<String, List<String>> localBranches = new HashMap<String, List<String>>(); List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user); for (RepositoryModel model : models) { if (!model.hasCommits) { // skip empty repository continue; } if (model.isCollectingGarbage) { // skip garbage collecting repository logger.warn(MessageFormat.format("Temporarily excluding {0} from RPC, busy collecting garbage", model.name)); continue; } // get local branches Repository repository = GitBlit.self().getRepository(model.name); List<RefModel> refs = JGitUtils.getLocalBranches(repository, false, -1); if (model.showRemoteBranches) { // add remote branches if repository displays them refs.addAll(JGitUtils.getRemoteBranches(repository, false, -1)); } if (refs.size() > 0) { List<String> branches = new ArrayList<String>(); for (RefModel ref : refs) { branches.add(ref.getName()); } localBranches.put(model.name, branches); } repository.close(); } result = localBranches; } else if (RpcRequest.LIST_USERS.equals(reqType)) { // list users List<String> names = GitBlit.self().getAllUsernames(); List<UserModel> users = new ArrayList<UserModel>(); for (String name : names) { users.add(GitBlit.self().getUserModel(name)); } result = users; } else if (RpcRequest.LIST_TEAMS.equals(reqType)) { // list teams List<String> names = GitBlit.self().getAllTeamnames(); List<TeamModel> teams = new ArrayList<TeamModel>(); for (String name : names) { teams.add(GitBlit.self().getTeamModel(name)); } result = teams; } else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) { // create repository RepositoryModel model = deserialize(request, response, RepositoryModel.class); try { GitBlit.self().updateRepositoryModel(model.name, model, true); } catch (GitBlitException e) { response.setStatus(failureCode); } } else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) { // edit repository RepositoryModel model = deserialize(request, response, RepositoryModel.class); // name specifies original repository name in event of rename String repoName = objectName; if (repoName == null) { repoName = model.name; } try { GitBlit.self().updateRepositoryModel(repoName, model, false); } catch (GitBlitException e) { response.setStatus(failureCode); } } else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) { // delete repository RepositoryModel model = deserialize(request, response, RepositoryModel.class); GitBlit.self().deleteRepositoryModel(model); } else if (RpcRequest.CREATE_USER.equals(reqType)) { // create user UserModel model = deserialize(request, response, UserModel.class); try { GitBlit.self().updateUserModel(model.username, model, true); } catch (GitBlitException e) { response.setStatus(failureCode); } } else if (RpcRequest.EDIT_USER.equals(reqType)) { // edit user UserModel model = deserialize(request, response, UserModel.class); // name parameter specifies original user name in event of rename String username = objectName; if (username == null) { username = model.username; } try { GitBlit.self().updateUserModel(username, model, false); } catch (GitBlitException e) { response.setStatus(failureCode); } } else if (RpcRequest.DELETE_USER.equals(reqType)) { // delete user UserModel model = deserialize(request, response, UserModel.class); if (!GitBlit.self().deleteUser(model.username)) { response.setStatus(failureCode); } } else if (RpcRequest.CREATE_TEAM.equals(reqType)) { // create team TeamModel model = deserialize(request, response, TeamModel.class); try { GitBlit.self().updateTeamModel(model.name, model, true); } catch (GitBlitException e) { response.setStatus(failureCode); } } else if (RpcRequest.EDIT_TEAM.equals(reqType)) { // edit team TeamModel model = deserialize(request, response, TeamModel.class); // name parameter specifies original team name in event of rename String teamname = objectName; if (teamname == null) { teamname = model.name; } try { GitBlit.self().updateTeamModel(teamname, model, false); } catch (GitBlitException e) { response.setStatus(failureCode); } } else if (RpcRequest.DELETE_TEAM.equals(reqType)) { // delete team TeamModel model = deserialize(request, response, TeamModel.class); if (!GitBlit.self().deleteTeam(model.name)) { response.setStatus(failureCode); } } else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) { // get repository members RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); result = GitBlit.self().getRepositoryUsers(model); } else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) { // rejected since 1.2.0 response.setStatus(failureCode); } else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) { // get repository member permissions RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); result = GitBlit.self().getUserAccessPermissions(model); } else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) { // set the repository permissions for the specified users RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE); result = GitBlit.self().setUserAccessPermissions(model, permissions); } else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) { // get repository teams RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); result = GitBlit.self().getRepositoryTeams(model); } else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) { // rejected since 1.2.0 response.setStatus(failureCode); } else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) { // get repository team permissions RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); result = GitBlit.self().getTeamAccessPermissions(model); } else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) { // set the repository permissions for the specified teams RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE); result = GitBlit.self().setTeamAccessPermissions(model, permissions); } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) { // return the list of federation registrations if (allowAdmin) { result = GitBlit.self().getFederationRegistrations(); } else { response.sendError(notAllowedCode); } } else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) { // return the list of federation result registrations if (allowAdmin && GitBlit.canFederate()) { result = GitBlit.self().getFederationResultRegistrations(); } else { response.sendError(notAllowedCode); } } else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) { // return the list of federation proposals if (allowAdmin && GitBlit.canFederate()) { result = GitBlit.self().getPendingFederationProposals(); } else { response.sendError(notAllowedCode); } } else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) { // return the list of federation sets if (allowAdmin && GitBlit.canFederate()) { String gitblitUrl = HttpUtils.getGitblitURL(request); result = GitBlit.self().getFederationSets(gitblitUrl); } else { response.sendError(notAllowedCode); } } else if (RpcRequest.LIST_SETTINGS.equals(reqType)) { // return the server's settings ServerSettings settings = GitBlit.self().getSettingsModel(); if (allowAdmin) { // return all settings result = settings; } else { // anonymous users get a few settings to allow browser launching List<String> keys = new ArrayList<String>(); keys.add(Keys.web.siteName); keys.add(Keys.web.mountParameters); keys.add(Keys.web.syndicationEntries); if (allowManagement) { // keys necessary for repository and/or user management keys.add(Keys.realm.minPasswordLength); keys.add(Keys.realm.passwordStorage); keys.add(Keys.federation.sets); } // build the settings ServerSettings managementSettings = new ServerSettings(); for (String key : keys) { managementSettings.add(settings.get(key)); } if (allowManagement) { managementSettings.pushScripts = settings.pushScripts; } result = managementSettings; } } else if (RpcRequest.EDIT_SETTINGS.equals(reqType)) { // update settings on the server if (allowAdmin) { Map<String, String> settings = deserialize(request, response, RpcUtils.SETTINGS_TYPE); GitBlit.self().updateSettings(settings); } else { response.sendError(notAllowedCode); } } else if (RpcRequest.LIST_STATUS.equals(reqType)) { // return the server's status information if (allowAdmin) { result = GitBlit.self().getStatus(); } else { response.sendError(notAllowedCode); } } else if (RpcRequest.CLEAR_REPOSITORY_CACHE.equals(reqType)) { // clear the repository list cache if (allowManagement) { GitBlit.self().resetRepositoryListCache(); } else { response.sendError(notAllowedCode); } } // send the result of the request serialize(response, result); } } src/main/java/com/gitblit/SalesforceUserService.java
New file @@ -0,0 +1,137 @@ package com.gitblit; import java.io.File; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccountType; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; import com.sforce.soap.partner.Connector; import com.sforce.soap.partner.GetUserInfoResult; import com.sforce.soap.partner.PartnerConnection; import com.sforce.ws.ConnectionException; import com.sforce.ws.ConnectorConfig; public class SalesforceUserService extends GitblitUserService { public static final Logger logger = LoggerFactory .getLogger(SalesforceUserService.class); private IStoredSettings settings; protected AccountType getAccountType() { return AccountType.SALESFORCE; } @Override public void setup(IStoredSettings settings) { this.settings = settings; String file = settings.getString( Keys.realm.salesforce.backingUserService, "${baseFolder}/users.conf"); File realmFile = GitBlit.getFileOrFolder(file); serviceImpl = createUserService(realmFile); logger.info("Salesforce User Service backed by " + serviceImpl.toString()); } @Override public UserModel authenticate(String username, char[] password) { if (isLocalAccount(username)) { // local account, bypass Salesforce authentication return super.authenticate(username, password); } ConnectorConfig config = new ConnectorConfig(); config.setUsername(username); config.setPassword(new String(password)); try { PartnerConnection connection = Connector.newConnection(config); GetUserInfoResult info = connection.getUserInfo(); String org = settings.getString(Keys.realm.salesforce.orgId, "0") .trim(); if (!org.equals("0")) { if (!org.equals(info.getOrganizationId())) { logger.warn("Access attempted by user of an invalid org: " + info.getUserName() + ", org: " + info.getOrganizationName() + "(" + info.getOrganizationId() + ")"); return null; } } logger.info("Authenticated user " + info.getUserName() + " using org " + info.getOrganizationName() + "(" + info.getOrganizationId() + ")"); String simpleUsername = getSimpleUsername(info); UserModel user = null; synchronized (this) { user = getUserModel(simpleUsername); if (user == null) user = new UserModel(simpleUsername); if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { user.cookie = StringUtils.getSHA1(user.username + new String(password)); } setUserAttributes(user, info); super.updateUserModel(user); } return user; } catch (ConnectionException e) { logger.error("Failed to authenticate", e); } return null; } private void setUserAttributes(UserModel user, GetUserInfoResult info) { // Don't want visibility into the real password, make up a dummy user.password = Constants.EXTERNAL_ACCOUNT; user.accountType = getAccountType(); // Get full name Attribute user.displayName = info.getUserFullName(); // Get email address Attribute user.emailAddress = info.getUserEmail(); } /** * Simple user name is the first part of the email address. */ private String getSimpleUsername(GetUserInfoResult info) { String email = info.getUserEmail(); return email.split("@")[0]; } @Override public boolean supportsCredentialChanges() { return false; } @Override public boolean supportsDisplayNameChanges() { return false; } @Override public boolean supportsEmailAddressChanges() { return false; } } src/main/java/com/gitblit/SparkleShareInviteServlet.java
New file @@ -0,0 +1,113 @@ /* * 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 java.io.IOException; 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.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; /** * Handles requests for Sparkleshare Invites * * @author James Moger * */ public class SparkleShareInviteServlet extends HttpServlet { private static final long serialVersionUID = 1L; public SparkleShareInviteServlet() { super(); } @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); } protected void processRequest(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { // extract repo name from request String repoUrl = request.getPathInfo().substring(1); // trim trailing .xml if (repoUrl.endsWith(".xml")) { repoUrl = repoUrl.substring(0, repoUrl.length() - 4); } String servletPath = Constants.GIT_PATH; int schemeIndex = repoUrl.indexOf("://") + 3; String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex)); String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length()); String username = null; int fetchIndex = repoUrl.indexOf('@'); if (fetchIndex > -1) { username = repoUrl.substring(schemeIndex, fetchIndex); } UserModel user; if (StringUtils.isEmpty(username)) { user = GitBlit.self().authenticate(request); } else { user = GitBlit.self().getUserModel(username); } if (user == null) { user = UserModel.ANONYMOUS; username = ""; } // ensure that the requested repository exists RepositoryModel model = GitBlit.self().getRepositoryModel(path); if (model == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path)); return; } StringBuilder sb = new StringBuilder(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); sb.append("<sparkleshare><invite>\n"); sb.append(MessageFormat.format("<address>{0}</address>\n", host)); sb.append(MessageFormat.format("<remote_path>{0}{1}</remote_path>\n", servletPath, model.name)); if (GitBlit.getInteger(Keys.fanout.port, 0) > 0) { // Gitblit is running it's own fanout service for pubsub notifications sb.append(MessageFormat.format("<announcements_url>tcp://{0}:{1}</announcements_url>\n", request.getServerName(), GitBlit.getString(Keys.fanout.port, ""))); } sb.append("</invite></sparkleshare>\n"); // write invite to client response.setContentType("application/xml"); response.setContentLength(sb.length()); response.getWriter().append(sb.toString()); } } src/main/java/com/gitblit/SyndicationFilter.java
src/main/java/com/gitblit/SyndicationServlet.java
src/main/java/com/gitblit/WebXmlSettings.java
src/main/java/com/gitblit/WindowsUserService.java
New file @@ -0,0 +1,194 @@ /* * 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 java.io.File; import java.util.Set; import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import waffle.windows.auth.IWindowsAccount; import waffle.windows.auth.IWindowsAuthProvider; import waffle.windows.auth.IWindowsComputer; import waffle.windows.auth.IWindowsIdentity; import waffle.windows.auth.impl.WindowsAuthProviderImpl; import com.gitblit.Constants.AccountType; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; import com.sun.jna.platform.win32.Win32Exception; /** * Implementation of a Windows user service. * * @author James Moger */ public class WindowsUserService extends GitblitUserService { private final Logger logger = LoggerFactory.getLogger(WindowsUserService.class); private IStoredSettings settings; private IWindowsAuthProvider waffle; public WindowsUserService() { super(); } @Override public void setup(IStoredSettings settings) { this.settings = settings; String file = settings.getString(Keys.realm.windows.backingUserService, "${baseFolder}/users.conf"); File realmFile = GitBlit.getFileOrFolder(file); serviceImpl = createUserService(realmFile); logger.info("Windows User Service backed by " + serviceImpl.toString()); waffle = new WindowsAuthProviderImpl(); IWindowsComputer computer = waffle.getCurrentComputer(); logger.info(" name = " + computer.getComputerName()); logger.info(" status = " + describeJoinStatus(computer.getJoinStatus())); logger.info(" memberOf = " + computer.getMemberOf()); //logger.info(" groups = " + Arrays.asList(computer.getGroups())); } protected String describeJoinStatus(String value) { if ("NetSetupUnknownStatus".equals(value)) { return "unknown"; } else if ("NetSetupUnjoined".equals(value)) { return "not joined"; } else if ("NetSetupWorkgroupName".equals(value)) { return "joined to a workgroup"; } else if ("NetSetupDomainName".equals(value)) { return "joined to a domain"; } return value; } @Override public boolean supportsCredentialChanges() { return false; } @Override public boolean supportsDisplayNameChanges() { return false; } @Override public boolean supportsEmailAddressChanges() { return true; } @Override public boolean supportsTeamMembershipChanges() { return true; } @Override protected AccountType getAccountType() { return AccountType.WINDOWS; } @Override public UserModel authenticate(String username, char[] password) { if (isLocalAccount(username)) { // local account, bypass Windows authentication return super.authenticate(username, password); } String defaultDomain = settings.getString(Keys.realm.windows.defaultDomain, null); if (StringUtils.isEmpty(defaultDomain)) { // ensure that default domain is null defaultDomain = null; } if (defaultDomain != null) { // sanitize username if (username.startsWith(defaultDomain + "\\")) { // strip default domain from domain\ username username = username.substring(defaultDomain.length() + 1); } else if (username.endsWith("@" + defaultDomain)) { // strip default domain from username@domain username = username.substring(0, username.lastIndexOf('@')); } } IWindowsIdentity identity = null; try { if (username.indexOf('@') > -1 || username.indexOf('\\') > -1) { // manually specified domain identity = waffle.logonUser(username, new String(password)); } else { // no domain specified, use default domain identity = waffle.logonDomainUser(username, defaultDomain, new String(password)); } } catch (Win32Exception e) { logger.error(e.getMessage()); return null; } if (identity.isGuest() && !settings.getBoolean(Keys.realm.windows.allowGuests, false)) { logger.warn("Guest account access is disabled"); identity.dispose(); return null; } UserModel user = getUserModel(username); if (user == null) // create user object for new authenticated user user = new UserModel(username.toLowerCase()); // create a user cookie if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) { user.cookie = StringUtils.getSHA1(user.username + new String(password)); } // update user attributes from Windows identity user.accountType = getAccountType(); String fqn = identity.getFqn(); if (fqn.indexOf('\\') > -1) { user.displayName = fqn.substring(fqn.lastIndexOf('\\') + 1); } else { user.displayName = fqn; } user.password = Constants.EXTERNAL_ACCOUNT; Set<String> groupNames = new TreeSet<String>(); for (IWindowsAccount group : identity.getGroups()) { groupNames.add(group.getFqn()); } if (groupNames.contains("BUILTIN\\Administrators")) { // local administrator user.canAdmin = true; } // TODO consider mapping Windows groups to teams // push the changes to the backing user service super.updateUserModel(user); // cleanup resources identity.dispose(); return user; } } src/main/java/com/gitblit/authority/AuthorityWorker.java
src/main/java/com/gitblit/authority/CertificateStatus.java
src/main/java/com/gitblit/authority/CertificateStatusRenderer.java
src/main/java/com/gitblit/authority/CertificatesTableModel.java
New file @@ -0,0 +1,169 @@ /* * 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.authority; import java.math.BigInteger; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Comparator; import java.util.Date; import javax.swing.table.AbstractTableModel; import com.gitblit.client.Translation; import com.gitblit.utils.X509Utils.RevocationReason; /** * Table model of a list of user certificate models. * * @author James Moger * */ public class CertificatesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; UserCertificateModel ucm; enum Columns { SerialNumber, Status, Reason, Issued, Expires; @Override public String toString() { return name().replace('_', ' '); } } public CertificatesTableModel() { } @Override public int getRowCount() { return ucm == null || ucm.certs == null ? 0 : ucm.certs.size(); } @Override public int getColumnCount() { return Columns.values().length; } @Override public String getColumnName(int column) { Columns col = Columns.values()[column]; switch (col) { case SerialNumber: return Translation.get("gb.serialNumber"); case Issued: return Translation.get("gb.issued"); case Expires: return Translation.get("gb.expires"); case Status: return Translation.get("gb.status"); case Reason: return Translation.get("gb.reason"); } return ""; } /** * Returns <code>Object.class</code> regardless of <code>columnIndex</code>. * * @param columnIndex * the column being queried * @return the Object.class */ public Class<?> getColumnClass(int columnIndex) { Columns col = Columns.values()[columnIndex]; switch (col) { case Status: return CertificateStatus.class; case Issued: return Date.class; case Expires: return Date.class; case SerialNumber: return BigInteger.class; default: return String.class; } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { Columns col = Columns.values()[columnIndex]; switch (col) { default: return false; } } @Override public Object getValueAt(int rowIndex, int columnIndex) { X509Certificate cert = ucm.certs.get(rowIndex); Columns col = Columns.values()[columnIndex]; switch (col) { case Status: return ucm.getStatus(cert); case SerialNumber: return cert.getSerialNumber(); case Issued: return cert.getNotBefore(); case Expires: return cert.getNotAfter(); case Reason: if (ucm.getStatus(cert).equals(CertificateStatus.revoked)) { RevocationReason r = ucm.getRevocationReason(cert.getSerialNumber()); return Translation.get("gb." + r.name()); } } return null; } public X509Certificate get(int modelRow) { return ucm.certs.get(modelRow); } public void setUserCertificateModel(UserCertificateModel ucm) { this.ucm = ucm; if (ucm == null) { return; } Collections.sort(ucm.certs, new Comparator<X509Certificate>() { @Override public int compare(X509Certificate o1, X509Certificate o2) { // sort by issue date in reverse chronological order int result = o2.getNotBefore().compareTo(o1.getNotBefore()); if (result == 0) { // same issue date, show expiring first boolean r1 = CertificatesTableModel.this.ucm.isRevoked(o1.getSerialNumber()); boolean r2 = CertificatesTableModel.this.ucm.isRevoked(o2.getSerialNumber()); if ((r1 && r2) || (!r1 && !r2)) { // both revoked or both not revoked // chronlogical order by expiration dates result = o1.getNotAfter().compareTo(o2.getNotAfter()); } else if (r1) { // r1 is revoked, r2 first return 1; } else { // r2 is revoked, r1 first return -1; } } return result; } }); } } src/main/java/com/gitblit/authority/DefaultOidsPanel.java
src/main/java/com/gitblit/authority/GitblitAuthority.java
New file @@ -0,0 +1,923 @@ /* * 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.authority; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Desktop; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.net.URI; import java.security.PrivateKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.mail.Message; import javax.mail.Multipart; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import javax.swing.ImageIcon; import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.RowFilter; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableRowSorter; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.slf4j.LoggerFactory; import com.gitblit.ConfigUserService; import com.gitblit.Constants; import com.gitblit.FileSettings; import com.gitblit.IStoredSettings; import com.gitblit.IUserService; import com.gitblit.Keys; import com.gitblit.MailExecutor; import com.gitblit.client.HeaderPanel; import com.gitblit.client.Translation; import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FileUtils; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; import com.gitblit.utils.X509Utils; import com.gitblit.utils.X509Utils.RevocationReason; import com.gitblit.utils.X509Utils.X509Log; import com.gitblit.utils.X509Utils.X509Metadata; /** * Simple GUI tool for administering Gitblit client certificates. * * @author James Moger * */ public class GitblitAuthority extends JFrame implements X509Log { private static final long serialVersionUID = 1L; private final UserCertificateTableModel tableModel; private UserCertificatePanel userCertificatePanel; private File folder; private IStoredSettings gitblitSettings; private IUserService userService; private String caKeystorePassword; private JTable table; private int defaultDuration; private TableRowSorter<UserCertificateTableModel> defaultSorter; private MailExecutor mail; private JButton certificateDefaultsButton; private JButton newSSLCertificate; public static void main(String... args) { // filter out the baseFolder parameter String folder = "data"; for (int i = 0; i< args.length; i++) { String arg = args[i]; if (arg.equals("--baseFolder")) { if (i + 1 == args.length) { System.out.println("Invalid --baseFolder parameter!"); System.exit(-1); } else if (args[i + 1] != ".") { folder = args[i+1]; } break; } } final String baseFolder = folder; EventQueue.invokeLater(new Runnable() { public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { } GitblitAuthority authority = new GitblitAuthority(); authority.initialize(baseFolder); authority.setLocationRelativeTo(null); authority.setVisible(true); } }); } public GitblitAuthority() { super(); tableModel = new UserCertificateTableModel(); defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel); } public void initialize(String baseFolder) { setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); setTitle("Gitblit Certificate Authority v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")"); setContentPane(getUI()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { saveSizeAndPosition(); } @Override public void windowOpened(WindowEvent event) { } }); File folder = new File(baseFolder).getAbsoluteFile(); load(folder); setSizeAndPosition(); } private void setSizeAndPosition() { String sz = null; String pos = null; try { StoredConfig config = getConfig(); sz = config.getString("ui", null, "size"); pos = config.getString("ui", null, "position"); defaultDuration = config.getInt("new", "duration", 365); } catch (Throwable t) { t.printStackTrace(); } // try to restore saved window size if (StringUtils.isEmpty(sz)) { setSize(900, 600); } else { String[] chunks = sz.split("x"); int width = Integer.parseInt(chunks[0]); int height = Integer.parseInt(chunks[1]); setSize(width, height); } // try to restore saved window position if (StringUtils.isEmpty(pos)) { setLocationRelativeTo(null); } else { String[] chunks = pos.split(","); int x = Integer.parseInt(chunks[0]); int y = Integer.parseInt(chunks[1]); setLocation(x, y); } } private void saveSizeAndPosition() { try { // save window size and position StoredConfig config = getConfig(); Dimension sz = GitblitAuthority.this.getSize(); config.setString("ui", null, "size", MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height)); Point pos = GitblitAuthority.this.getLocationOnScreen(); config.setString("ui", null, "position", MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y)); config.save(); } catch (Throwable t) { Utils.showException(GitblitAuthority.this, t); } } private StoredConfig getConfig() throws IOException, ConfigInvalidException { File configFile = new File(folder, X509Utils.CA_CONFIG); FileBasedConfig config = new FileBasedConfig(configFile, FS.detect()); config.load(); return config; } private IUserService loadUsers(File folder) { File file = new File(folder, "gitblit.properties"); if (!file.exists()) { return null; } gitblitSettings = new FileSettings(file.getAbsolutePath()); mail = new MailExecutor(gitblitSettings); String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf"); String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase(); IUserService service = null; if (!ext.equals("conf") && !ext.equals("properties")) { if (us.equals("com.gitblit.LdapUserService")) { us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf"); } else if (us.equals("com.gitblit.LdapUserService")) { us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf"); } } if (us.endsWith(".conf")) { service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us)); } else { throw new RuntimeException("Unsupported user service: " + us); } service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us)); return service; } private void load(File folder) { this.folder = folder; this.userService = loadUsers(folder); System.out.println(Constants.baseFolder$ + " set to " + folder); if (userService == null) { JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder)); } else { // build empty certificate model for all users Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>(); for (String user : userService.getAllUsernames()) { UserModel model = userService.getUserModel(user); UserCertificateModel ucm = new UserCertificateModel(model); map.put(user, ucm); } File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); if (certificatesConfigFile.exists()) { try { config.load(); // replace user certificate model with actual data List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list; for (UserCertificateModel ucm : list) { ucm.user = userService.getUserModel(ucm.user.username); map.put(ucm.user.username, ucm); } } catch (IOException e) { e.printStackTrace(); } catch (ConfigInvalidException e) { e.printStackTrace(); } } tableModel.list = new ArrayList<UserCertificateModel>(map.values()); Collections.sort(tableModel.list); tableModel.fireTableDataChanged(); Utils.packColumns(table, Utils.MARGIN); File caKeystore = new File(folder, X509Utils.CA_KEY_STORE); if (!caKeystore.exists()) { if (!X509Utils.unlimitedStrength) { // prompt to confirm user understands JCE Standard Strength encryption int res = JOptionPane.showConfirmDialog(GitblitAuthority.this, Translation.get("gb.jceWarning"), Translation.get("gb.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (res != JOptionPane.YES_OPTION) { if (Desktop.isDesktopSupported()) { if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { try { Desktop.getDesktop().browse(URI.create("http://www.oracle.com/technetwork/java/javase/downloads/index.html")); } catch (IOException e) { } } } System.exit(1); } } // show certificate defaults dialog certificateDefaultsButton.doClick(); // create "localhost" ssl certificate prepareX509Infrastructure(); } } } private boolean prepareX509Infrastructure() { if (caKeystorePassword == null) { JPasswordField pass = new JPasswordField(10); pass.setText(caKeystorePassword); pass.addAncestorListener(new RequestFocusListener()); JPanel panel = new JPanel(new BorderLayout()); panel.add(new JLabel(Translation.get("gb.enterKeystorePassword")), BorderLayout.NORTH); panel.add(pass, BorderLayout.CENTER); int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.password"), JOptionPane.OK_CANCEL_OPTION); if (result == JOptionPane.OK_OPTION) { caKeystorePassword = new String(pass.getPassword()); } else { return false; } } X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword); setMetadataDefaults(metadata); metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR); X509Utils.prepareX509Infrastructure(metadata, folder, this); return true; } private List<X509Certificate> findCerts(File folder, String username) { List<X509Certificate> list = new ArrayList<X509Certificate>(); File userFolder = new File(folder, X509Utils.CERTS + File.separator + username); if (!userFolder.exists()) { return list; } File [] certs = userFolder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt"); } }); try { CertificateFactory factory = CertificateFactory.getInstance("X.509"); for (File cert : certs) { BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert)); X509Certificate x509 = (X509Certificate) factory.generateCertificate(is); is.close(); list.add(x509); } } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } return list; } private Container getUI() { userCertificatePanel = new UserCertificatePanel(this) { private static final long serialVersionUID = 1L; @Override public Insets getInsets() { return Utils.INSETS; } @Override public boolean isAllowEmail() { return mail.isReady(); } @Override public Date getDefaultExpiration() { Calendar c = Calendar.getInstance(); c.add(Calendar.DATE, defaultDuration); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return c.getTime(); } @Override public boolean saveUser(String username, UserCertificateModel ucm) { return userService.updateUserModel(username, ucm.user); } @Override public boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail) { if (!prepareX509Infrastructure()) { return false; } Date notAfter = metadata.notAfter; setMetadataDefaults(metadata); metadata.notAfter = notAfter; // set user's specified OID values UserModel user = ucm.user; if (!StringUtils.isEmpty(user.organizationalUnit)) { metadata.oids.put("OU", user.organizationalUnit); } if (!StringUtils.isEmpty(user.organization)) { metadata.oids.put("O", user.organization); } if (!StringUtils.isEmpty(user.locality)) { metadata.oids.put("L", user.locality); } if (!StringUtils.isEmpty(user.stateProvince)) { metadata.oids.put("ST", user.stateProvince); } if (!StringUtils.isEmpty(user.countryCode)) { metadata.oids.put("C", user.countryCode); } File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this); // save latest expiration date if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) { ucm.expires = metadata.notAfter; } updateAuthorityConfig(ucm); // refresh user ucm.certs = null; int selectedIndex = table.getSelectedRow(); tableModel.fireTableDataChanged(); table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex); if (sendEmail) { sendEmail(user, metadata, zip); } return true; } @Override public boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) { if (!prepareX509Infrastructure()) { return false; } File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST); File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword, GitblitAuthority.this)) { File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); if (certificatesConfigFile.exists()) { try { config.load(); } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } } // add serial to revoked list ucm.revoke(cert.getSerialNumber(), reason); ucm.update(config); try { config.save(); } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } // refresh user ucm.certs = null; int modelIndex = table.convertRowIndexToModel(table.getSelectedRow()); tableModel.fireTableDataChanged(); table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex); return true; } return false; } }; table = Utils.newTable(tableModel, Utils.DATE_FORMAT); table.setRowSorter(defaultSorter); table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer()); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } int row = table.getSelectedRow(); if (row < 0) { return; } int modelIndex = table.convertRowIndexToModel(row); UserCertificateModel ucm = tableModel.get(modelIndex); if (ucm.certs == null) { ucm.certs = findCerts(folder, ucm.user.username); } userCertificatePanel.setUserCertificateModel(ucm); } }); JPanel usersPanel = new JPanel(new BorderLayout()) { private static final long serialVersionUID = 1L; @Override public Insets getInsets() { return Utils.INSETS; } }; usersPanel.add(new HeaderPanel(Translation.get("gb.users"), "users_16x16.png"), BorderLayout.NORTH); usersPanel.add(new JScrollPane(table), BorderLayout.CENTER); usersPanel.setMinimumSize(new Dimension(400, 10)); certificateDefaultsButton = new JButton(new ImageIcon(getClass().getResource("/settings_16x16.png"))); certificateDefaultsButton.setFocusable(false); certificateDefaultsButton.setToolTipText(Translation.get("gb.newCertificateDefaults")); certificateDefaultsButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { X509Metadata metadata = new X509Metadata("whocares", "whocares"); File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); NewCertificateConfig certificateConfig = null; if (certificatesConfigFile.exists()) { try { config.load(); } catch (Exception x) { Utils.showException(GitblitAuthority.this, x); } certificateConfig = NewCertificateConfig.KEY.parse(config); certificateConfig.update(metadata); } InputVerifier verifier = new InputVerifier() { public boolean verify(JComponent comp) { boolean returnValue; JTextField textField = (JTextField) comp; try { Integer.parseInt(textField.getText()); returnValue = true; } catch (NumberFormatException e) { returnValue = false; } return returnValue; } }; JTextField siteNameTF = new JTextField(20); siteNameTF.setText(gitblitSettings.getString(Keys.web.siteName, "Gitblit")); JPanel siteNamePanel = Utils.newFieldPanel(Translation.get("gb.siteName"), siteNameTF, Translation.get("gb.siteNameDescription")); JTextField validityTF = new JTextField(4); validityTF.setInputVerifier(verifier); validityTF.setVerifyInputWhenFocusTarget(true); validityTF.setText("" + certificateConfig.duration); JPanel validityPanel = Utils.newFieldPanel(Translation.get("gb.validity"), validityTF, Translation.get("gb.duration.days").replace("{0}", "").trim()); JPanel p1 = new JPanel(new GridLayout(0, 1, 5, 2)); p1.add(siteNamePanel); p1.add(validityPanel); DefaultOidsPanel oids = new DefaultOidsPanel(metadata); JPanel panel = new JPanel(new BorderLayout()); panel.add(p1, BorderLayout.NORTH); panel.add(oids, BorderLayout.CENTER); int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png"))); if (result == JOptionPane.OK_OPTION) { try { oids.update(metadata); certificateConfig.duration = Integer.parseInt(validityTF.getText()); certificateConfig.store(config, metadata); config.save(); Map<String, String> updates = new HashMap<String, String>(); updates.put(Keys.web.siteName, siteNameTF.getText()); gitblitSettings.saveSettings(updates); } catch (Exception e1) { Utils.showException(GitblitAuthority.this, e1); } } } }); newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png"))); newSSLCertificate.setFocusable(false); newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate")); newSSLCertificate.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Date defaultExpiration = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR); NewSSLCertificateDialog dialog = new NewSSLCertificateDialog(GitblitAuthority.this, defaultExpiration); dialog.setModal(true); dialog.setVisible(true); if (dialog.isCanceled()) { return; } final Date expires = dialog.getExpiration(); final String hostname = dialog.getHostname(); final boolean serveCertificate = dialog.isServeCertificate(); AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) { @Override protected Boolean doRequest() throws IOException { if (!prepareX509Infrastructure()) { return false; } // read CA private key and certificate File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE); PrivateKey caPrivateKey = X509Utils.getPrivateKey(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword); X509Certificate caCert = X509Utils.getCertificate(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword); // generate new SSL certificate X509Metadata metadata = new X509Metadata(hostname, caKeystorePassword); setMetadataDefaults(metadata); metadata.notAfter = expires; File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE); X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this); boolean hasCert = cert != null; if (hasCert && serveCertificate) { // update Gitblit https connector alias Map<String, String> updates = new HashMap<String, String>(); updates.put(Keys.server.certificateAlias, metadata.commonName); gitblitSettings.saveSettings(updates); } return hasCert; } @Override protected void onSuccess() { if (serveCertificate) { JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname), Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.sslCertificateGenerated"), hostname), Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE); } } }; worker.execute(); } }); JButton emailBundle = new JButton(new ImageIcon(getClass().getResource("/mail_16x16.png"))); emailBundle.setFocusable(false); emailBundle.setToolTipText(Translation.get("gb.emailCertificateBundle")); emailBundle.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int row = table.getSelectedRow(); if (row < 0) { return; } int modelIndex = table.convertRowIndexToModel(row); final UserCertificateModel ucm = tableModel.get(modelIndex); if (ArrayUtils.isEmpty(ucm.certs)) { JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.pleaseGenerateClientCertificate"), ucm.user.getDisplayName())); } final File zip = new File(folder, X509Utils.CERTS + File.separator + ucm.user.username + File.separator + ucm.user.username + ".zip"); if (!zip.exists()) { return; } AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) { @Override protected Boolean doRequest() throws IOException { X509Metadata metadata = new X509Metadata(ucm.user.username, "whocares"); metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME); if (StringUtils.isEmpty(metadata.serverHostname)) { metadata.serverHostname = Constants.NAME; } metadata.userDisplayname = ucm.user.getDisplayName(); return sendEmail(ucm.user, metadata, zip); } @Override protected void onSuccess() { JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"), ucm.user.getDisplayName())); } }; worker.execute(); } }); JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png"))); logButton.setFocusable(false); logButton.setToolTipText(Translation.get("gb.log")); logButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { File log = new File(folder, X509Utils.CERTS + File.separator + "log.txt"); if (log.exists()) { String content = FileUtils.readContent(log, "\n"); JTextArea textarea = new JTextArea(content); JScrollPane scrollPane = new JScrollPane(textarea); scrollPane.setPreferredSize(new Dimension(700, 400)); JOptionPane.showMessageDialog(GitblitAuthority.this, scrollPane, log.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE); } } }); final JTextField filterTextfield = new JTextField(15); filterTextfield.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { filterUsers(filterTextfield.getText()); } }); filterTextfield.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { filterUsers(filterTextfield.getText()); } }); JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL); buttonControls.setFloatable(false); buttonControls.add(certificateDefaultsButton); buttonControls.add(newSSLCertificate); buttonControls.add(emailBundle); buttonControls.add(logButton); JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, Utils.MARGIN, Utils.MARGIN)); userControls.add(new JLabel(Translation.get("gb.filter"))); userControls.add(filterTextfield); JPanel topPanel = new JPanel(new BorderLayout(0, 0)); topPanel.add(buttonControls, BorderLayout.WEST); topPanel.add(userControls, BorderLayout.EAST); JPanel leftPanel = new JPanel(new BorderLayout()); leftPanel.add(topPanel, BorderLayout.NORTH); leftPanel.add(usersPanel, BorderLayout.CENTER); userCertificatePanel.setMinimumSize(new Dimension(375, 10)); JLabel statusLabel = new JLabel(); statusLabel.setHorizontalAlignment(SwingConstants.RIGHT); if (X509Utils.unlimitedStrength) { statusLabel.setText("JCE Unlimited Strength Jurisdiction Policy"); } else { statusLabel.setText("JCE Standard Encryption Policy"); } JPanel root = new JPanel(new BorderLayout()) { private static final long serialVersionUID = 1L; public Insets getInsets() { return Utils.INSETS; } }; JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel); splitPane.setDividerLocation(1d); root.add(splitPane, BorderLayout.CENTER); root.add(statusLabel, BorderLayout.SOUTH); return root; } private void filterUsers(final String fragment) { table.clearSelection(); userCertificatePanel.setUserCertificateModel(null); if (StringUtils.isEmpty(fragment)) { table.setRowSorter(defaultSorter); return; } RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() { public boolean include(Entry<? extends UserCertificateTableModel, ? extends Object> entry) { for (int i = entry.getValueCount() - 1; i >= 0; i--) { if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) { return true; } } return false; } }; TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>( tableModel); sorter.setRowFilter(containsFilter); table.setRowSorter(sorter); } @Override public void log(String message) { BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true)); writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message)); writer.newLine(); writer.flush(); } catch (Exception e) { LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { } } } } private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) { // send email try { if (mail.isReady()) { Message message = mail.createMessage(user.emailAddress); message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname); // body of email String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata); if (StringUtils.isEmpty(body)) { body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName()); } Multipart mp = new MimeMultipart(); MimeBodyPart messagePart = new MimeBodyPart(); messagePart.setText(body); mp.addBodyPart(messagePart); // attach zip MimeBodyPart filePart = new MimeBodyPart(); FileDataSource fds = new FileDataSource(zip); filePart.setDataHandler(new DataHandler(fds)); filePart.setFileName(fds.getName()); mp.addBodyPart(filePart); message.setContent(mp); mail.sendNow(message); return true; } else { JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE); } } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } return false; } private void setMetadataDefaults(X509Metadata metadata) { metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME); if (StringUtils.isEmpty(metadata.serverHostname)) { metadata.serverHostname = Constants.NAME; } // set default values from config file File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); if (certificatesConfigFile.exists()) { try { config.load(); } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config); certificateConfig.update(metadata); } } private void updateAuthorityConfig(UserCertificateModel ucm) { File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG); FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect()); if (certificatesConfigFile.exists()) { try { config.load(); } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } } ucm.update(config); try { config.save(); } catch (Exception e) { Utils.showException(GitblitAuthority.this, e); } } } Diff truncated after the above file
src/main/java/com/gitblit/authority/Launcher.java src/main/java/com/gitblit/authority/NewCertificateConfig.java src/main/java/com/gitblit/authority/NewClientCertificateDialog.java src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java src/main/java/com/gitblit/authority/RequestFocusListener.java src/main/java/com/gitblit/authority/UserCertificateConfig.java src/main/java/com/gitblit/authority/UserCertificateModel.java src/main/java/com/gitblit/authority/UserCertificatePanel.java src/main/java/com/gitblit/authority/UserCertificateTableModel.java src/main/java/com/gitblit/authority/UserOidsPanel.java src/main/java/com/gitblit/authority/Utils.java src/main/java/com/gitblit/authority/X509CertificateViewer.java src/main/java/com/gitblit/client/BooleanCellRenderer.java src/main/java/com/gitblit/client/BranchRenderer.java src/main/java/com/gitblit/client/ClosableTabComponent.java src/main/java/com/gitblit/client/DateCellRenderer.java src/main/java/com/gitblit/client/EditRegistrationDialog.java src/main/java/com/gitblit/client/EditRepositoryDialog.java src/main/java/com/gitblit/client/EditTeamDialog.java src/main/java/com/gitblit/client/EditUserDialog.java src/main/java/com/gitblit/client/FeedEntryTableModel.java src/main/java/com/gitblit/client/FeedsPanel.java src/main/java/com/gitblit/client/FeedsTableModel.java src/main/java/com/gitblit/client/GitblitClient.java src/main/java/com/gitblit/client/GitblitManager.java src/main/java/com/gitblit/client/GitblitManagerLauncher.java src/main/java/com/gitblit/client/GitblitPanel.java src/main/java/com/gitblit/client/GitblitRegistration.java src/main/java/com/gitblit/client/GitblitWorker.java src/main/java/com/gitblit/client/HeaderPanel.java src/main/java/com/gitblit/client/IndicatorsRenderer.java src/main/java/com/gitblit/client/JPalette.java src/main/java/com/gitblit/client/MessageRenderer.java src/main/java/com/gitblit/client/NameRenderer.java src/main/java/com/gitblit/client/PropertiesTableModel.java src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java src/main/java/com/gitblit/client/RegistrationsDialog.java src/main/java/com/gitblit/client/RegistrationsTableModel.java src/main/java/com/gitblit/client/RepositoriesPanel.java src/main/java/com/gitblit/client/RepositoriesTableModel.java src/main/java/com/gitblit/client/SearchDialog.java src/main/java/com/gitblit/client/SettingCellRenderer.java src/main/java/com/gitblit/client/SettingPanel.java src/main/java/com/gitblit/client/SettingsPanel.java src/main/java/com/gitblit/client/SettingsTableModel.java src/main/java/com/gitblit/client/StatusPanel.java src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java src/main/java/com/gitblit/client/SubscriptionsDialog.java src/main/java/com/gitblit/client/TeamsPanel.java src/main/java/com/gitblit/client/TeamsTableModel.java src/main/java/com/gitblit/client/Translation.java src/main/java/com/gitblit/client/UsersPanel.java src/main/java/com/gitblit/client/UsersTableModel.java src/main/java/com/gitblit/client/Utils.java src/main/java/com/gitblit/client/splash.png src/main/java/com/gitblit/fanout/FanoutClient.java src/main/java/com/gitblit/fanout/FanoutConstants.java src/main/java/com/gitblit/fanout/FanoutNioService.java src/main/java/com/gitblit/fanout/FanoutService.java src/main/java/com/gitblit/fanout/FanoutServiceConnection.java src/main/java/com/gitblit/fanout/FanoutSocketService.java src/main/java/com/gitblit/fanout/FanoutStats.java src/main/java/com/gitblit/git/GitDaemon.java src/main/java/com/gitblit/git/GitDaemonClient.java src/main/java/com/gitblit/git/GitDaemonService.java src/main/java/com/gitblit/git/GitServlet.java src/main/java/com/gitblit/git/GitblitReceivePackFactory.java src/main/java/com/gitblit/git/GitblitUploadPackFactory.java src/main/java/com/gitblit/git/ReceiveHook.java src/main/java/com/gitblit/git/RepositoryResolver.java src/main/java/com/gitblit/models/Activity.java src/main/java/com/gitblit/models/AnnotatedLine.java src/main/java/com/gitblit/models/DailyLogEntry.java src/main/java/com/gitblit/models/FederationModel.java src/main/java/com/gitblit/models/FederationProposal.java src/main/java/com/gitblit/models/FederationSet.java src/main/java/com/gitblit/models/FeedEntryModel.java src/main/java/com/gitblit/models/FeedModel.java src/main/java/com/gitblit/models/ForkModel.java src/main/java/com/gitblit/models/GitClientApplication.java src/main/java/com/gitblit/models/GitNote.java src/main/java/com/gitblit/models/GravatarProfile.java src/main/java/com/gitblit/models/IssueModel.java src/main/java/com/gitblit/models/Metric.java src/main/java/com/gitblit/models/PathModel.java src/main/java/com/gitblit/models/ProjectModel.java src/main/java/com/gitblit/models/RefLogEntry.java src/main/java/com/gitblit/models/RefModel.java src/main/java/com/gitblit/models/RegistrantAccessPermission.java src/main/java/com/gitblit/models/RepositoryCommit.java src/main/java/com/gitblit/models/RepositoryModel.java src/main/java/com/gitblit/models/RepositoryUrl.java src/main/java/com/gitblit/models/SearchResult.java src/main/java/com/gitblit/models/ServerSettings.java src/main/java/com/gitblit/models/ServerStatus.java src/main/java/com/gitblit/models/SettingModel.java src/main/java/com/gitblit/models/SubmoduleModel.java src/main/java/com/gitblit/models/TeamModel.java src/main/java/com/gitblit/models/TicketModel.java src/main/java/com/gitblit/models/UserModel.java src/main/java/com/gitblit/models/UserPreferences.java src/main/java/com/gitblit/models/UserRepositoryPreferences.java src/main/java/com/gitblit/utils/ActivityUtils.java src/main/java/com/gitblit/utils/ArrayUtils.java src/main/java/com/gitblit/utils/Base64.java src/main/java/com/gitblit/utils/ByteFormat.java src/main/java/com/gitblit/utils/ClientLogger.java src/main/java/com/gitblit/utils/CommitCache.java src/main/java/com/gitblit/utils/CompressionUtils.java src/main/java/com/gitblit/utils/ConnectionUtils.java src/main/java/com/gitblit/utils/ContainerUtils.java src/main/java/com/gitblit/utils/DeepCopier.java src/main/java/com/gitblit/utils/DiffUtils.java src/main/java/com/gitblit/utils/FederationUtils.java src/main/java/com/gitblit/utils/FileUtils.java src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java src/main/java/com/gitblit/utils/GitWebDiffFormatter.java src/main/java/com/gitblit/utils/HttpUtils.java src/main/java/com/gitblit/utils/IssueUtils.java src/main/java/com/gitblit/utils/JGitUtils.java src/main/java/com/gitblit/utils/JsonUtils.java src/main/java/com/gitblit/utils/MarkdownUtils.java src/main/java/com/gitblit/utils/MetricUtils.java src/main/java/com/gitblit/utils/ObjectCache.java src/main/java/com/gitblit/utils/PatchFormatter.java src/main/java/com/gitblit/utils/RefLogUtils.java src/main/java/com/gitblit/utils/RpcUtils.java src/main/java/com/gitblit/utils/StringUtils.java src/main/java/com/gitblit/utils/SyndicationUtils.java src/main/java/com/gitblit/utils/TicgitUtils.java src/main/java/com/gitblit/utils/TimeUtils.java src/main/java/com/gitblit/utils/X509Utils.java src/main/java/com/gitblit/wicket/AuthorizationStrategy.java src/main/java/com/gitblit/wicket/CacheControl.java src/main/java/com/gitblit/wicket/ExternalImage.java src/main/java/com/gitblit/wicket/GitBlitWebApp.java src/main/java/com/gitblit/wicket/GitBlitWebApp.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_es.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_ja.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_ko.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_pl.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties src/main/java/com/gitblit/wicket/GitBlitWebSession.java src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java src/main/java/com/gitblit/wicket/GitblitRedirectException.java src/main/java/com/gitblit/wicket/GitblitWicketFilter.java src/main/java/com/gitblit/wicket/PageRegistration.java src/main/java/com/gitblit/wicket/RequiresAdminRole.java src/main/java/com/gitblit/wicket/SessionlessForm.java src/main/java/com/gitblit/wicket/StringChoiceRenderer.java src/main/java/com/gitblit/wicket/WicketUtils.java src/main/java/com/gitblit/wicket/charting/GoogleChart.java src/main/java/com/gitblit/wicket/charting/GoogleCharts.java src/main/java/com/gitblit/wicket/charting/GoogleLineChart.java src/main/java/com/gitblit/wicket/charting/GooglePieChart.java src/main/java/com/gitblit/wicket/charting/SecureChart.java src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java src/main/java/com/gitblit/wicket/freemarker/Freemarker.java src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm src/main/java/com/gitblit/wicket/ng/NgController.java src/main/java/com/gitblit/wicket/ng/angular.js src/main/java/com/gitblit/wicket/pages/ActivityPage.html src/main/java/com/gitblit/wicket/pages/ActivityPage.java src/main/java/com/gitblit/wicket/pages/BasePage.html src/main/java/com/gitblit/wicket/pages/BasePage.java src/main/java/com/gitblit/wicket/pages/BlamePage.html src/main/java/com/gitblit/wicket/pages/BlamePage.java src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java src/main/java/com/gitblit/wicket/pages/BlobPage.html src/main/java/com/gitblit/wicket/pages/BlobPage.java src/main/java/com/gitblit/wicket/pages/BranchesPage.html src/main/java/com/gitblit/wicket/pages/BranchesPage.java src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.html src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java src/main/java/com/gitblit/wicket/pages/CommitPage.html src/main/java/com/gitblit/wicket/pages/CommitPage.java src/main/java/com/gitblit/wicket/pages/ComparePage.html src/main/java/com/gitblit/wicket/pages/ComparePage.java src/main/java/com/gitblit/wicket/pages/DashboardPage.java src/main/java/com/gitblit/wicket/pages/DocsPage.html src/main/java/com/gitblit/wicket/pages/DocsPage.java src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java src/main/java/com/gitblit/wicket/pages/EditTeamPage.html src/main/java/com/gitblit/wicket/pages/EditTeamPage.java src/main/java/com/gitblit/wicket/pages/EditUserPage.html src/main/java/com/gitblit/wicket/pages/EditUserPage.java src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html src/main/java/com/gitblit/wicket/pages/FederationPage.html src/main/java/com/gitblit/wicket/pages/FederationPage.java src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.html src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java src/main/java/com/gitblit/wicket/pages/ForkPage.html src/main/java/com/gitblit/wicket/pages/ForkPage.java src/main/java/com/gitblit/wicket/pages/ForksPage.html src/main/java/com/gitblit/wicket/pages/ForksPage.java src/main/java/com/gitblit/wicket/pages/GitSearchPage.html src/main/java/com/gitblit/wicket/pages/GitSearchPage.java src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.html src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.java src/main/java/com/gitblit/wicket/pages/HistoryPage.html src/main/java/com/gitblit/wicket/pages/HistoryPage.java src/main/java/com/gitblit/wicket/pages/LogPage.html src/main/java/com/gitblit/wicket/pages/LogPage.java src/main/java/com/gitblit/wicket/pages/LogoutPage.html src/main/java/com/gitblit/wicket/pages/LogoutPage.java src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java src/main/java/com/gitblit/wicket/pages/MarkdownPage.html src/main/java/com/gitblit/wicket/pages/MarkdownPage.java src/main/java/com/gitblit/wicket/pages/MetricsPage.html src/main/java/com/gitblit/wicket/pages/MetricsPage.java src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java src/main/java/com/gitblit/wicket/pages/OverviewPage.html src/main/java/com/gitblit/wicket/pages/OverviewPage.java src/main/java/com/gitblit/wicket/pages/PatchPage.html src/main/java/com/gitblit/wicket/pages/PatchPage.java src/main/java/com/gitblit/wicket/pages/ProjectPage.html src/main/java/com/gitblit/wicket/pages/ProjectPage.java src/main/java/com/gitblit/wicket/pages/ProjectsPage.html src/main/java/com/gitblit/wicket/pages/ProjectsPage.java src/main/java/com/gitblit/wicket/pages/RawPage.java src/main/java/com/gitblit/wicket/pages/ReflogPage.html src/main/java/com/gitblit/wicket/pages/ReflogPage.java src/main/java/com/gitblit/wicket/pages/RepositoriesPage.html src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java src/main/java/com/gitblit/wicket/pages/RepositoryPage.html src/main/java/com/gitblit/wicket/pages/RepositoryPage.java src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.html src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java src/main/java/com/gitblit/wicket/pages/RootPage.html src/main/java/com/gitblit/wicket/pages/RootPage.java src/main/java/com/gitblit/wicket/pages/RootSubPage.html src/main/java/com/gitblit/wicket/pages/RootSubPage.java src/main/java/com/gitblit/wicket/pages/SendProposalPage.html src/main/java/com/gitblit/wicket/pages/SendProposalPage.java src/main/java/com/gitblit/wicket/pages/SessionPage.java src/main/java/com/gitblit/wicket/pages/SummaryPage.html src/main/java/com/gitblit/wicket/pages/SummaryPage.java src/main/java/com/gitblit/wicket/pages/TagPage.html src/main/java/com/gitblit/wicket/pages/TagPage.java src/main/java/com/gitblit/wicket/pages/TagsPage.html src/main/java/com/gitblit/wicket/pages/TagsPage.java src/main/java/com/gitblit/wicket/pages/TicketPage.html src/main/java/com/gitblit/wicket/pages/TicketPage.java src/main/java/com/gitblit/wicket/pages/TicketsPage.html src/main/java/com/gitblit/wicket/pages/TicketsPage.java src/main/java/com/gitblit/wicket/pages/TreePage.html src/main/java/com/gitblit/wicket/pages/TreePage.java src/main/java/com/gitblit/wicket/pages/UserPage.html src/main/java/com/gitblit/wicket/pages/UserPage.java src/main/java/com/gitblit/wicket/pages/UsersPage.html src/main/java/com/gitblit/wicket/pages/UsersPage.java src/main/java/com/gitblit/wicket/pages/prettify/lang-apollo.js src/main/java/com/gitblit/wicket/pages/prettify/lang-basic.js src/main/java/com/gitblit/wicket/pages/prettify/lang-clj.js src/main/java/com/gitblit/wicket/pages/prettify/lang-css.js src/main/java/com/gitblit/wicket/pages/prettify/lang-dart.js src/main/java/com/gitblit/wicket/pages/prettify/lang-erlang.js src/main/java/com/gitblit/wicket/pages/prettify/lang-go.js src/main/java/com/gitblit/wicket/pages/prettify/lang-hs.js src/main/java/com/gitblit/wicket/pages/prettify/lang-lisp.js src/main/java/com/gitblit/wicket/pages/prettify/lang-llvm.js src/main/java/com/gitblit/wicket/pages/prettify/lang-lua.js src/main/java/com/gitblit/wicket/pages/prettify/lang-matlab.js src/main/java/com/gitblit/wicket/pages/prettify/lang-ml.js src/main/java/com/gitblit/wicket/pages/prettify/lang-mumps.js src/main/java/com/gitblit/wicket/pages/prettify/lang-n.js src/main/java/com/gitblit/wicket/pages/prettify/lang-pascal.js src/main/java/com/gitblit/wicket/pages/prettify/lang-proto.js src/main/java/com/gitblit/wicket/pages/prettify/lang-r.js src/main/java/com/gitblit/wicket/pages/prettify/lang-rd.js src/main/java/com/gitblit/wicket/pages/prettify/lang-scala.js src/main/java/com/gitblit/wicket/pages/prettify/lang-sql.js src/main/java/com/gitblit/wicket/pages/prettify/lang-tcl.js src/main/java/com/gitblit/wicket/pages/prettify/lang-tex.js src/main/java/com/gitblit/wicket/pages/prettify/lang-vb.js src/main/java/com/gitblit/wicket/pages/prettify/lang-vhdl.js src/main/java/com/gitblit/wicket/pages/prettify/lang-wiki.js src/main/java/com/gitblit/wicket/pages/prettify/lang-xq.js src/main/java/com/gitblit/wicket/pages/prettify/lang-yaml.js src/main/java/com/gitblit/wicket/pages/prettify/prettify.css src/main/java/com/gitblit/wicket/pages/prettify/prettify.js src/main/java/com/gitblit/wicket/pages/prettify/run_prettify.js src/main/java/com/gitblit/wicket/panels/ActivityPanel.html src/main/java/com/gitblit/wicket/panels/ActivityPanel.java src/main/java/com/gitblit/wicket/panels/BasePanel.java src/main/java/com/gitblit/wicket/panels/BranchesPanel.html src/main/java/com/gitblit/wicket/panels/BranchesPanel.java src/main/java/com/gitblit/wicket/panels/BulletListPanel.html src/main/java/com/gitblit/wicket/panels/BulletListPanel.java src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.html src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.html src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.java src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.html src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java src/main/java/com/gitblit/wicket/panels/DigestsPanel.html src/main/java/com/gitblit/wicket/panels/DigestsPanel.java src/main/java/com/gitblit/wicket/panels/DropDownMenu.html src/main/java/com/gitblit/wicket/panels/DropDownMenu.java src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.html src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.java src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.html src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.java src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.html src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.java src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java src/main/java/com/gitblit/wicket/panels/GravatarImage.html src/main/java/com/gitblit/wicket/panels/GravatarImage.java src/main/java/com/gitblit/wicket/panels/HistoryPanel.html src/main/java/com/gitblit/wicket/panels/HistoryPanel.java src/main/java/com/gitblit/wicket/panels/LinkPanel.html src/main/java/com/gitblit/wicket/panels/LinkPanel.java src/main/java/com/gitblit/wicket/panels/LogPanel.html src/main/java/com/gitblit/wicket/panels/LogPanel.java src/main/java/com/gitblit/wicket/panels/NavigationPanel.html src/main/java/com/gitblit/wicket/panels/NavigationPanel.java src/main/java/com/gitblit/wicket/panels/ObjectContainer.java src/main/java/com/gitblit/wicket/panels/PagerPanel.html src/main/java/com/gitblit/wicket/panels/PagerPanel.java src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.html src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java src/main/java/com/gitblit/wicket/panels/ReflogPanel.html src/main/java/com/gitblit/wicket/panels/ReflogPanel.java src/main/java/com/gitblit/wicket/panels/RefsPanel.html src/main/java/com/gitblit/wicket/panels/RefsPanel.java src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java src/main/java/com/gitblit/wicket/panels/SearchPanel.html src/main/java/com/gitblit/wicket/panels/SearchPanel.java src/main/java/com/gitblit/wicket/panels/ShockWaveComponent.java src/main/java/com/gitblit/wicket/panels/TagsPanel.html src/main/java/com/gitblit/wicket/panels/TagsPanel.java src/main/java/com/gitblit/wicket/panels/TeamsPanel.html src/main/java/com/gitblit/wicket/panels/TeamsPanel.java src/main/java/com/gitblit/wicket/panels/UsersPanel.html src/main/java/com/gitblit/wicket/panels/UsersPanel.java src/main/java/log4j.properties src/main/java/login.mkd src/main/java/login_es.mkd src/main/java/login_ko.mkd src/main/java/login_nl.mkd src/main/java/login_pl.mkd src/main/java/login_pt_br.mkd src/main/java/login_zh_CN.mkd src/main/java/logo.png src/main/java/welcome.mkd src/main/java/welcome_es.mkd src/main/java/welcome_ko.mkd src/main/java/welcome_nl.mkd src/main/java/welcome_pl.mkd src/main/java/welcome_pt_br.mkd src/main/java/welcome_zh_CN.mkd src/main/resources/add_16x16.png src/main/resources/arrow_down.png src/main/resources/arrow_left.png src/main/resources/arrow_off.png src/main/resources/arrow_page.png src/main/resources/arrow_project.png src/main/resources/arrow_up.png src/main/resources/background.png src/main/resources/blank.png src/main/resources/book_16x16.png src/main/resources/bootstrap/css/bootstrap-responsive.css src/main/resources/bootstrap/css/bootstrap.css src/main/resources/bootstrap/css/iconic.css src/main/resources/bootstrap/font/iconic_fill.afm src/main/resources/bootstrap/font/iconic_fill.css src/main/resources/bootstrap/font/iconic_fill.eot src/main/resources/bootstrap/font/iconic_fill.otf src/main/resources/bootstrap/font/iconic_fill.svg src/main/resources/bootstrap/font/iconic_fill.ttf src/main/resources/bootstrap/font/iconic_fill.woff src/main/resources/bootstrap/font/iconic_stroke.afm src/main/resources/bootstrap/font/iconic_stroke.css src/main/resources/bootstrap/font/iconic_stroke.eot src/main/resources/bootstrap/font/iconic_stroke.otf src/main/resources/bootstrap/font/iconic_stroke.svg src/main/resources/bootstrap/font/iconic_stroke.ttf src/main/resources/bootstrap/font/iconic_stroke.woff src/main/resources/bootstrap/img/glyphicons-halflings-white.png src/main/resources/bootstrap/img/glyphicons-halflings.png src/main/resources/bootstrap/js/bootstrap.js src/main/resources/bootstrap/js/jquery.js src/main/resources/bug_16x16.png src/main/resources/bullet_black.png src/main/resources/bullet_blue.png src/main/resources/bullet_delete.png src/main/resources/bullet_error.png src/main/resources/bullet_feed.png src/main/resources/bullet_green.png src/main/resources/bullet_key.png src/main/resources/bullet_orange.png src/main/resources/bullet_red.png src/main/resources/bullet_white.png src/main/resources/bullet_yellow.png src/main/resources/clipboard_13x13.png src/main/resources/clipboard_16x16.png src/main/resources/clippy.png src/main/resources/clippy.swf src/main/resources/cold_16x16.png src/main/resources/commit_branch_16x16.png src/main/resources/commit_changes_16x16.png src/main/resources/commit_divide_16x16.png src/main/resources/commit_join_16x16.png src/main/resources/commit_merge_16x16.png src/main/resources/commit_up_16x16.png src/main/resources/federated_16x16.png src/main/resources/feed_16x16.png src/main/resources/file_16x16.png src/main/resources/file_acrobat_16x16.png src/main/resources/file_c_16x16.png src/main/resources/file_code_16x16.png src/main/resources/file_cpp_16x16.png src/main/resources/file_cs_16x16.png src/main/resources/file_doc_16x16.png src/main/resources/file_excel_16x16.png src/main/resources/file_h_16x16.png src/main/resources/file_java_16x16.png src/main/resources/file_php_16x16.png src/main/resources/file_ppt_16x16.png src/main/resources/file_ruby_16x16.png src/main/resources/file_settings_16x16.png src/main/resources/file_vs_16x16.png src/main/resources/file_world_16x16.png src/main/resources/file_zip_16x16.png src/main/resources/folder_16x16.png src/main/resources/folder_star_16x16.png src/main/resources/folder_star_32x32.png src/main/resources/fork-black_16x16.png src/main/resources/fork_16x16.png src/main/resources/git-black-16x16.png src/main/resources/git-black.png src/main/resources/git-black_210x210.png src/main/resources/git-black_32x32.png src/main/resources/git-orange-16x16.png src/main/resources/gitblit.css src/main/resources/gitblt-favicon.png src/main/resources/gitblt-logo.png src/main/resources/gitblt2.png src/main/resources/gitblt2_white.png src/main/resources/gitblt_25.png src/main/resources/gitblt_25_white.png src/main/resources/github_32x32.png src/main/resources/gitweb-favicon.png src/main/resources/health_16x16.png src/main/resources/heart_16x16.png src/main/resources/information_16x16.png src/main/resources/lock_16x16.png src/main/resources/lock_go_16x16.png src/main/resources/lock_pull_16x16.png src/main/resources/mail_16x16.png src/main/resources/pixel.png src/main/resources/rosette_16x16.png src/main/resources/rosette_32x32.png src/main/resources/script_16x16.png src/main/resources/search-icon.png src/main/resources/settings_16x16.png src/main/resources/settings_32x32.png src/main/resources/shield_16x16.png src/main/resources/smartgithg_32x32.png src/main/resources/sourcetree_32x32.png src/main/resources/sparkleshare_32x32.png src/main/resources/star_16x16.png src/main/resources/star_32x32.png src/main/resources/tag_16x16.png src/main/resources/tower_32x32.png src/main/resources/user_16x16.png src/main/resources/users_16x16.png src/main/resources/vcard_16x16.png src/site/.gitignore src/site/administration.mkd src/site/architecture.odg src/site/custom.less src/site/design.mkd src/site/fancybox/blank.gif src/site/fancybox/fancy_close.png src/site/fancybox/fancy_loading.png src/site/fancybox/fancy_nav_left.png src/site/fancybox/fancy_nav_right.png src/site/fancybox/fancy_shadow_e.png src/site/fancybox/fancy_shadow_n.png src/site/fancybox/fancy_shadow_ne.png src/site/fancybox/fancy_shadow_nw.png src/site/fancybox/fancy_shadow_s.png src/site/fancybox/fancy_shadow_se.png src/site/fancybox/fancy_shadow_sw.png src/site/fancybox/fancy_shadow_w.png src/site/fancybox/fancy_title_left.png src/site/fancybox/fancy_title_main.png src/site/fancybox/fancy_title_over.png src/site/fancybox/fancy_title_right.png src/site/fancybox/fancybox-x.png src/site/fancybox/fancybox-y.png src/site/fancybox/fancybox.png src/site/fancybox/jquery-1.4.3.min.js src/site/fancybox/jquery.easing-1.3.pack.js src/site/fancybox/jquery.fancybox-1.3.4.css src/site/fancybox/jquery.fancybox-1.3.4.js src/site/fancybox/jquery.fancybox-1.3.4.pack.js src/site/fancybox/jquery.mousewheel-3.0.4.pack.js src/site/faq.mkd src/site/features.mkd src/site/federation.mkd src/site/federation.odg src/site/gitblit_logo_white.xcf src/site/openshift.mkd src/site/permissions_matrix.ods src/site/properties.mkd src/site/releasecurrent.mkd src/site/releasehistory.mkd src/site/releases.mkd src/site/resources/architecture.png src/site/resources/fed_aggregation.png src/site/resources/fed_mirror.png src/site/resources/ldapSample.png src/site/resources/permissions_matrix.png src/site/resources/screenshots.js src/site/resources/stjude_150x150.gif src/site/roadmap.mkd src/site/rpc.mkd src/site/screenshots.mkd src/site/screenshots/00.png src/site/screenshots/00b.png src/site/screenshots/00c.png src/site/screenshots/00d.png src/site/screenshots/01.png src/site/screenshots/01b.png src/site/screenshots/01c.png src/site/screenshots/02.png src/site/screenshots/03.png src/site/screenshots/04.png src/site/screenshots/05.png src/site/screenshots/06.png src/site/screenshots/07.png src/site/screenshots/08.png src/site/screenshots/09.png src/site/screenshots/10.png src/site/screenshots/11.png src/site/screenshots/12.png src/site/screenshots/13.png src/site/screenshots/14.png src/site/screenshots/15.png src/site/screenshots/image_processing.txt src/site/screenshots/m00.png src/site/screenshots/m01.png src/site/screenshots/m02.png src/site/screenshots/m03.png src/site/screenshots/m04.png src/site/screenshots/m05.png src/site/screenshots/m06.png src/site/screenshots/m07.png src/site/screenshots/m08.png src/site/screenshots/m09.png src/site/screenshots/m10.png src/site/setup_authentication.mkd src/site/setup_client.mkd src/site/setup_clientmenus.mkd src/site/setup_express.mkd src/site/setup_go.mkd src/site/setup_hooks.mkd src/site/setup_lucene.mkd src/site/setup_proxy.mkd src/site/setup_viewer.mkd src/site/setup_war.mkd src/site/siteindex.mkd src/site/templates/atom.ftl src/site/templates/macros.ftl src/site/templates/releasecurrent.ftl src/site/templates/releasehistory.ftl src/site/templates/rss.ftl src/site/upgrade_express.mkd src/site/upgrade_go.mkd src/site/upgrade_war.mkd src/test/config/test-gitblit.properties src/test/config/test-ui-gitblit.properties src/test/config/test-ui-users.conf src/test/config/test-users.conf src/test/java/com/gitblit/tests/ActivityTest.java src/test/java/com/gitblit/tests/ArrayUtilsTest.java src/test/java/com/gitblit/tests/Base64Test.java src/test/java/com/gitblit/tests/ByteFormatTest.java src/test/java/com/gitblit/tests/DiffUtilsTest.java src/test/java/com/gitblit/tests/FanoutServiceTest.java src/test/java/com/gitblit/tests/FederationTests.java src/test/java/com/gitblit/tests/FileUtilsTest.java src/test/java/com/gitblit/tests/GitBlitSuite.java src/test/java/com/gitblit/tests/GitBlitTest.java src/test/java/com/gitblit/tests/GitDaemonStopTest.java src/test/java/com/gitblit/tests/GitDaemonTest.java src/test/java/com/gitblit/tests/GitServletTest.java src/test/java/com/gitblit/tests/GroovyScriptTest.java src/test/java/com/gitblit/tests/Issue0259Test.java src/test/java/com/gitblit/tests/Issue0271Test.java src/test/java/com/gitblit/tests/IssuesTest.java src/test/java/com/gitblit/tests/JGitUtilsTest.java src/test/java/com/gitblit/tests/JsonUtilsTest.java src/test/java/com/gitblit/tests/LdapUserServiceTest.java src/test/java/com/gitblit/tests/LuceneExecutorTest.java src/test/java/com/gitblit/tests/MailTest.java src/test/java/com/gitblit/tests/MarkdownUtilsTest.java src/test/java/com/gitblit/tests/MetricUtilsTest.java src/test/java/com/gitblit/tests/ObjectCacheTest.java src/test/java/com/gitblit/tests/PermissionsTest.java src/test/java/com/gitblit/tests/PushLogTest.java src/test/java/com/gitblit/tests/RedmineUserServiceTest.java src/test/java/com/gitblit/tests/RepositoryModelTest.java src/test/java/com/gitblit/tests/RpcTests.java src/test/java/com/gitblit/tests/StringUtilsTest.java src/test/java/com/gitblit/tests/SyndicationUtilsTest.java src/test/java/com/gitblit/tests/TicgitUtilsTest.java src/test/java/com/gitblit/tests/TimeUtilsTest.java src/test/java/com/gitblit/tests/UserServiceTest.java src/test/java/com/gitblit/tests/X509UtilsTest.java src/test/java/com/gitblit/tests/mock/MemorySettings.java src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif src/test/java/de/akquinet/devops/GitBlit4UITests.java src/test/java/de/akquinet/devops/GitBlitServer4UITests.java src/test/java/de/akquinet/devops/GitblitRunnable.java src/test/java/de/akquinet/devops/LaunchWithUITestConfig.java src/test/java/de/akquinet/devops/ManualUITestLaunch.java src/test/java/de/akquinet/devops/test/ui/TestUISuite.java src/test/java/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java src/test/java/de/akquinet/devops/test/ui/generic/AbstractUITest.java src/test/java/de/akquinet/devops/test/ui/view/Exp.java src/test/java/de/akquinet/devops/test/ui/view/GitblitDashboardView.java src/test/java/de/akquinet/devops/test/ui/view/GitblitPageView.java src/test/java/de/akquinet/devops/test/ui/view/RepoEditView.java src/test/java/de/akquinet/devops/test/ui/view/RepoListView.java src/test/resources/issue0259.conf src/test/resources/issue0271.conf test-gitblit.properties (deleted) tests/com/gitblit/tests/FanoutServiceTest.java (deleted) tests/com/gitblit/tests/FederationTests.java (deleted) tests/com/gitblit/tests/FileUtilsTest.java (deleted) tests/com/gitblit/tests/GitBlitSuite.java (deleted) tests/com/gitblit/tests/GitBlitTest.java (deleted) tests/com/gitblit/tests/GitServletTest.java (deleted) tests/com/gitblit/tests/JGitUtilsTest.java (deleted) tests/com/gitblit/tests/JsonUtilsTest.java (deleted) tests/com/gitblit/tests/LuceneExecutorTest.java (deleted) tests/com/gitblit/tests/MailTest.java (deleted) tests/com/gitblit/tests/PermissionsTest.java (deleted) tests/com/gitblit/tests/PushLogTest.java (deleted) tests/com/gitblit/tests/RpcTests.java (deleted) tests/com/gitblit/tests/TimeUtilsTest.java (deleted) tests/de/akquinet/devops/GitblitRunnable.java (deleted) tests/de/akquinet/devops/LaunchWithUITestConfig.java (deleted) tests/de/akquinet/devops/ManualUITestLaunch.java (deleted) tests/de/akquinet/devops/test/ui/TestUISuite.java (deleted) tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java (deleted) tests/de/akquinet/devops/test/ui/view/RepoEditView.java (deleted) tmplt.pom.xml (deleted) tools/GenJar.jar (deleted) tools/ant-googlecode-0.0.3.jar (deleted)