diff options
145 files changed, 8894 insertions, 881 deletions
@@ -21,9 +21,9 @@ <classpathentry kind="lib" path="ext/lucene-queries-3.6.1.jar" sourcepath="ext/src/lucene-queries-3.6.1-sources.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.1.0.201209190230-r.jar" sourcepath="ext/src/org.eclipse.jgit-2.1.0.201209190230-r-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.1.0.201209190230-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-2.1.0.201209190230-r-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" /> @@ -38,6 +38,18 @@ <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="output" path="bin/classes" /> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" /> </classpath>
\ No newline at end of file @@ -31,4 +31,5 @@ /deploy
/*.jks
/x509test
-/certs
\ No newline at end of file +/certs +/data @@ -12,10 +12,11 @@ <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.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}/site" />
+ <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" />
@@ -106,22 +107,66 @@ -->
<target name="compile" depends="buildinfo" description="Retrieves dependencies and compiles Gitblit from source">
- <!-- copy required distribution files to project folder -->
- <copy todir="${basedir}" overwrite="false">
+ <!-- cleanup old builds -->
+ <delete dir="${project.target.dir}" />
+ <mkdir dir="${project.target.dir}" />
+
+ <!-- cleanup old builds -->
+ <delete>
+ <fileset dir="${basedir}">
+ <include name="*.zip" />
+ <include name="*.war" />
+ <include name="*.jar" />
+ </fileset>
+ </delete>
+
+ <!-- copy required distribution files to data folder -->
+ <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>
</copy>
<!-- copy required distribution files to project folder -->
- <mkdir dir="${basedir}/certs" />
- <copy todir="${basedir}/certs" overwrite="false">
+ <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 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 -->
+ <move todir="${basedir}/data" overwrite="true" failonerror="false">
+ <fileset dir="${basedir}">
+ <include name="users.conf" />
+ <include name="projects.conf" />
+ <include name="gitblit.properties" />
+ <include name="serverKeyStore.jks" />
+ <include name="serverTrustStore.jks" />
+ </fileset>
+ </move>
+ <move todir="${basedir}/data/certs" overwrite="true" failonerror="false">
+ <fileset dir="${basedir}/certs" />
+ </move>
+ <move todir="${basedir}/data/git" overwrite="true" failonerror="false">
+ <fileset dir="${basedir}/git" />
+ </move>
+ <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. -->
@@ -186,18 +231,47 @@ <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}">
<include name="LICENSE" />
<include name="NOTICE" />
</fileset>
</copy>
- <copy tofile="${project.deploy.dir}/authority.jar" file="${basedir}/authority-${gb.version}.jar" />
+ <!-- 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}/certs"/>
- <copy todir="${project.deploy.dir}/certs">
+ <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" />
@@ -234,20 +308,8 @@ <param name="docs.output.dir" value="${project.deploy.dir}/docs" />
</antcall>
- <!-- Copy the supported Groovy hook scripts -->
- <mkdir dir="${project.deploy.dir}/groovy" />
- <copy todir="${project.deploy.dir}/groovy">
- <fileset dir="${basedir}/groovy">
- <include name="sendmail.groovy" />
- <include name="sendmail-html.groovy" />
- <include name="jenkins.groovy" />
- <include name="protect-refs.groovy" />
- <include name="localclone.groovy" />
- </fileset>
- </copy>
-
<!-- Create Zip deployment -->
- <zip destfile="${distribution.zipfile}">
+ <zip destfile="${project.target.dir}/${distribution.zipfile}">
<fileset dir="${project.deploy.dir}">
<include name="**/*" />
</fileset>
@@ -383,9 +445,6 @@ <!-- Copy web.xml and users.conf to WEB-INF -->
<copy todir="${project.war.dir}/WEB-INF">
- <fileset dir="${basedir}/distrib">
- <include name="users.conf" />
- </fileset>
<fileset dir="${basedir}/src/WEB-INF">
<include name="web.xml" />
</fileset>
@@ -404,19 +463,30 @@ <param name="docs.output.dir" value="${project.war.dir}/WEB-INF/docs" />
</antcall>
+ <!-- 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>
+
<!-- Copy the supported Groovy hook scripts -->
- <mkdir dir="${project.war.dir}/WEB-INF/groovy" />
- <copy todir="${project.war.dir}/WEB-INF/groovy">
- <fileset dir="${basedir}/groovy">
+ <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="localclone.groovy" />
+ <include name="fogbugz.groovy" />
+ <include name="thebuggenie.groovy" />
</fileset>
</copy>
- <!-- Build the WAR web.xml from the prototype web.xml and gitblit.properties -->
+ <!-- Build the WAR web.xml from the prototype web.xml -->
<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildWebXml">
<classpath refid="master-classpath" />
@@ -426,8 +496,6 @@ <arg value="--destinationFile" />
<arg value="${project.war.dir}/WEB-INF/web.xml" />
- <arg value="--propertiesFile" />
- <arg value="${basedir}/distrib/gitblit.properties" />
</java>
<!-- Gitblit resources -->
@@ -467,7 +535,7 @@ </copy>
<!-- Build the WAR file -->
- <jar basedir="${project.war.dir}" destfile="${distribution.warfile}" compress="true" />
+ <jar basedir="${project.war.dir}" destfile="${project.target.dir}/${distribution.warfile}" compress="true" />
</target>
@@ -554,7 +622,7 @@ <target name="buildFederationClient" depends="compile" description="Builds the stand-alone Gitblit federation client">
<echo>Building Gitblit Federation Client ${gb.version}</echo>
- <genjar jarfile="fedclient.jar">
+ <genjar jarfile="${project.target.dir}/fedclient.jar">
<class name="com.gitblit.FederationClientLauncher" />
<resource file="${project.build.dir}/log4j.properties" />
<classfilter>
@@ -575,16 +643,21 @@ </genjar>
<!-- Build the federation client zip file -->
- <zip destfile="${fedclient.zipfile}">
+ <zip destfile="${project.target.dir}/${fedclient.zipfile}">
<fileset dir="${basedir}">
- <include name="fedclient.jar" />
<include name="LICENSE" />
<include name="NOTICE" />
</fileset>
+ <fileset dir="${project.target.dir}">
+ <include name="fedclient.jar" />
+ </fileset>
<fileset dir="${basedir}/distrib">
<include name="federation.properties" />
</fileset>
</zip>
+
+ <!-- Cleanup -->
+ <delete file="${project.target.dir}/fedclient.jar" />
</target>
@@ -619,14 +692,26 @@ <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/groovy" />
- <copy todir="${deployments.root}/WEB-INF/groovy">
- <fileset dir="${basedir}/groovy">
+ <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>
@@ -663,6 +748,8 @@ <exclude name="hamcrest*.jar" />
<exclude name="servlet*.jar" />
<exclude name="javax.servlet*.jar" />
+ <exclude name="jsslutils*.jar" />
+ <exclude name="jcalendar*.jar" />
</fileset>
</copy>
@@ -680,7 +767,7 @@ </jar>
<!-- Build Express Zip file -->
- <zip destfile="${express.zipfile}">
+ <zip destfile="${project.target.dir}/${express.zipfile}">
<fileset dir="${project.express.dir}" />
</zip>
@@ -695,7 +782,7 @@ <target name="buildManager" depends="compile" description="Builds the stand-alone Gitblit Manager">
<echo>Building Gitblit Manager ${gb.version}</echo>
- <genjar jarfile="manager-${gb.version}.jar">
+ <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" />
@@ -717,12 +804,15 @@ <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" />
<class name="com.gitblit.client.GitblitManagerLauncher" />
<classfilter>
@@ -744,13 +834,18 @@ </genjar>
<!-- Build Manager Zip file -->
- <zip destfile="${manager.zipfile}">
+ <zip destfile="${project.target.dir}/${manager.zipfile}">
<fileset dir="${basedir}">
- <include name="manager-${gb.version}.jar" />
<include name="LICENSE" />
<include name="NOTICE" />
</fileset>
+ <fileset dir="${project.target.dir}">
+ <include name="manager-${gb.version}.jar" />
+ </fileset>
</zip>
+
+ <!-- Cleanup -->
+ <delete file="${project.target.dir}/manager-${gb.version}.jar" />
</target>
@@ -762,7 +857,7 @@ <target name="buildAuthority" depends="compile" description="Builds the stand-alone Gitblit Authority">
<echo>Building Gitblit Authority ${gb.version}</echo>
- <genjar jarfile="authority-${gb.version}.jar">
+ <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" />
@@ -809,13 +904,15 @@ </genjar>
<!-- Build Authority Zip file -->
- <zip destfile="${authority.zipfile}">
+ <zip destfile="${project.target.dir}/${authority.zipfile}">
<fileset dir="${basedir}">
- <include name="authority-${gb.version}.jar" />
<include name="LICENSE" />
<include name="NOTICE" />
</fileset>
- <zipfileset dir="${basedir}/distrib" prefix="certs">
+ <fileset dir="${project.target.dir}">
+ <include name="authority-${gb.version}.jar" />
+ </fileset>
+ <zipfileset dir="${basedir}/distrib" prefix="data/certs">
<include name="authority.conf" />
<include name="mail.tmpl" />
<include name="instructions.tmpl" />
@@ -832,9 +929,12 @@ <echo>Building Gitblit API Library ${gb.version}</echo>
<!-- Build API Library jar -->
- <genjar jarfile="gbapi-${gb.version}.jar">
+ <genjar jarfile="${project.target.dir}/gbapi-${gb.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." />
@@ -847,7 +947,7 @@ </genjar>
<!-- Build API sources jar -->
- <zip destfile="gbapi-${gb.version}-sources.jar">
+ <zip destfile="${project.target.dir}/gbapi-${gb.version}-sources.jar">
<fileset dir="${basedir}/src" defaultexcludes="yes">
<include name="com/gitblit/Constants.java"/>
<include name="com/gitblit/GitBlitException.java"/>
@@ -859,7 +959,7 @@ </zip>
<!-- Build API JavaDoc jar -->
- <javadoc destdir="${basedir}/javadoc">
+ <javadoc destdir="${project.target.dir}/javadoc">
<fileset dir="${basedir}/src" defaultexcludes="yes">
<include name="com/gitblit/Constants.java"/>
<include name="com/gitblit/GitBlitException.java"/>
@@ -869,18 +969,20 @@ <include name="com/gitblit/utils/**/*.java"/>
</fileset>
</javadoc>
- <zip destfile="gbapi-${gb.version}-javadoc.jar">
- <fileset dir="${basedir}/javadoc" />
+ <zip destfile="${project.target.dir}/gbapi-${gb.version}-javadoc.jar">
+ <fileset dir="${project.target.dir}/javadoc" />
</zip>
<!-- Build the API library zip file -->
- <zip destfile="${gbapi.zipfile}">
+ <zip destfile="${project.target.dir}/${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" />
- <include name="LICENSE" />
- <include name="NOTICE" />
</fileset>
<fileset dir="${basedir}/ext">
<exclude name="src/**" />
@@ -889,6 +991,16 @@ <include name="jdom*.jar" />
</fileset>
</zip>
+
+ <!-- Cleanup -->
+ <delete>
+ <fileset dir="${project.target.dir}">
+ <include name="javadoc/**" />
+ <include name="gbapi-${gb.version}.jar" />
+ <include name="gbapi-${gb.version}-sources.jar" />
+ <include name="gbapi-${gb.version}-javadoc.jar" />
+ </fileset>
+ </delete>
</target>
@@ -1065,7 +1177,7 @@ <java classpath="${project.build.dir}" classname="com.gitblit.build.BuildGhPages">
<classpath refid="master-classpath" />
<arg value="--sourceFolder" />
- <arg value="${basedir}/site" />
+ <arg value="${basedir}/target/site" />
<arg value="--repository" />
<arg value="${basedir}" />
@@ -1089,7 +1201,7 @@ username="${googlecode.user}"
password="${googlecode.password}"
projectname="gitblit"
- filename="${distribution.zipfile}"
+ 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" />
@@ -1099,7 +1211,7 @@ username="${googlecode.user}"
password="${googlecode.password}"
projectname="gitblit"
- filename="${distribution.warfile}"
+ 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" />
@@ -1109,7 +1221,7 @@ username="${googlecode.user}"
password="${googlecode.password}"
projectname="gitblit"
- filename="${fedclient.zipfile}"
+ 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" />
@@ -1119,7 +1231,7 @@ username="${googlecode.user}"
password="${googlecode.password}"
projectname="gitblit"
- filename="${manager.zipfile}"
+ 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" />
@@ -1129,7 +1241,7 @@ username="${googlecode.user}"
password="${googlecode.password}"
projectname="gitblit"
- filename="${gbapi.zipfile}"
+ 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" />
@@ -1139,7 +1251,7 @@ username="${googlecode.user}"
password="${googlecode.password}"
projectname="gitblit"
- filename="${express.zipfile}"
+ 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" />
diff --git a/checkstyle.xml b/checkstyle.xml index c09c1e94..ee45e7ef 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -53,7 +53,6 @@ </module> <module name="NeedBraces" /> <module name="RightCurly" /> - <module name="DoubleCheckedLocking" /> <module name="EmptyStatement" /> <module name="EqualsHashCode" /> <module name="IllegalInstantiation" /> diff --git a/distrib/authority.cmd b/distrib/authority.cmd index 145f5242..75cb0cf7 100644 --- a/distrib/authority.cmd +++ b/distrib/authority.cmd @@ -1 +1 @@ -@java -jar authority.jar
+@java -jar authority.jar --baseFolder data
diff --git a/distrib/gitblit b/distrib/gitblit index cd1f967e..6c74d547 100644 --- a/distrib/gitblit +++ b/distrib/gitblit @@ -3,6 +3,7 @@ set -e GITBLIT_PATH=/opt/gitblit +GITBLIT_BASE_FOLDER=/opt/gitblit/data GITBLIT_HTTP_PORT=0 GITBLIT_HTTPS_PORT=8443 source ${GITBLIT_PATH}/java-proxy-config.sh @@ -14,13 +15,13 @@ case "$1" in start) log_action_begin_msg "Starting gitblit server" cd $GITBLIT_PATH - $JAVA $GITBLIT_PATH/gitblit.jar --httpsPort $GITBLIT_HTTPS_PORT --httpPort $GITBLIT_HTTP_PORT > /dev/null & + $JAVA $GITBLIT_PATH/gitblit.jar --httpsPort $GITBLIT_HTTPS_PORT --httpPort $GITBLIT_HTTP_PORT --baseFolder $GITBLIT_BASE_FOLDER > /dev/null & log_action_end_msg $? ;; stop) log_action_begin_msg "Stopping gitblit server" cd $GITBLIT_PATH - $JAVA $GITBLIT_PATH/gitblit.jar --stop > /dev/null & + $JAVA $GITBLIT_PATH/gitblit.jar --baseFolder $GITBLIT_BASE_FOLDER --stop > /dev/null & log_action_end_msg $? ;; force-reload|restart) diff --git a/distrib/gitblit-centos b/distrib/gitblit-centos index c608097e..04c9a9b4 100644 --- a/distrib/gitblit-centos +++ b/distrib/gitblit-centos @@ -6,6 +6,7 @@ # change theses values (default values) GITBLIT_PATH=/opt/gitblit +GITBLIT_BASE_FOLDER=/opt/gitblit/data GITBLIT_HTTP_PORT=0 GITBLIT_HTTPS_PORT=8443 source ${GITBLIT_PATH}/java-proxy-config.sh @@ -19,7 +20,7 @@ case "$1" in then echo $"Starting gitblit server" cd $GITBLIT_PATH - $JAVA $GITBLIT_PATH/gitblit.jar --httpsPort $GITBLIT_HTTPS_PORT --httpPort $GITBLIT_HTTP_PORT > /dev/null & + $JAVA $GITBLIT_PATH/gitblit.jar --httpsPort $GITBLIT_HTTPS_PORT --httpPort $GITBLIT_HTTP_PORT --baseFolder $GITBLIT_BASE_FOLDER > /dev/null & echo "." exit $RETVAL fi @@ -30,7 +31,7 @@ case "$1" in then echo $"Stopping gitblit server" cd $GITBLIT_PATH - $JAVA $GITBLIT_PATH/gitblit.jar --stop > /dev/null & + $JAVA $GITBLIT_PATH/gitblit.jar --baseFolder $GITBLIT_BASE_FOLDER --stop > /dev/null & echo "." exit $RETVAL fi diff --git a/distrib/gitblit-stop.cmd b/distrib/gitblit-stop.cmd index c139d57b..5820c491 100644 --- a/distrib/gitblit-stop.cmd +++ b/distrib/gitblit-stop.cmd @@ -1 +1 @@ -@java -jar gitblit.jar --stop
+@java -jar gitblit.jar --stop --baseFolder data
diff --git a/distrib/gitblit-ubuntu b/distrib/gitblit-ubuntu index b047ed97..4ff275d0 100644 --- a/distrib/gitblit-ubuntu +++ b/distrib/gitblit-ubuntu @@ -8,9 +8,10 @@ PATH=/sbin:/bin:/usr/bin:/usr/sbin # change theses values (default values) GITBLIT_PATH=/opt/gitblit +GITBLIT_BASE_FOLDER=/opt/gitblit/data GITBLIT_USER="gitblit" source ${GITBLIT_PATH}/java-proxy-config.sh -ARGS="-server -Xmx1024M ${JAVA_PROXY_CONFIG} -Djava.awt.headless=true -jar gitblit.jar" +ARGS="-server -Xmx1024M ${JAVA_PROXY_CONFIG} -Djava.awt.headless=true -jar gitblit.jar --baseFolder $GITBLIT_BASE_FOLDER" RETVAL=0 diff --git a/distrib/gitblit.cmd b/distrib/gitblit.cmd index ce96a797..3006a687 100644 --- a/distrib/gitblit.cmd +++ b/distrib/gitblit.cmd @@ -1 +1 @@ -@java -jar gitblit.jar
+@java -jar gitblit.jar --baseFolder data
diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties index ce269d2c..f5cc19b6 100644 --- a/distrib/gitblit.properties +++ b/distrib/gitblit.properties @@ -1,4 +1,19 @@ #
+# 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
#
@@ -10,7 +25,8 @@ #
# SINCE 0.5.0
# RESTART REQUIRED
-git.repositoriesFolder = git
+# 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,
@@ -299,14 +315,16 @@ git.packedGitMmap = false #
# RESTART REQUIRED
# SINCE 0.8.0
-groovy.scriptsFolder = groovy
+# BASEFOLDER
+groovy.scriptsFolder = ${baseFolder}/groovy
# Specify the directory Grape uses for downloading libraries.
# http://groovy.codehaus.org/Grape
#
# RESTART REQUIRED
# SINCE 1.0.0
-groovy.grapeFolder = groovy/grape
+# BASEFOLDER
+groovy.grapeFolder = ${baseFolder}/groovy/grape
# Scripts to execute on Pre-Receive.
#
@@ -366,6 +384,53 @@ groovy.postReceiveScripts = 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
#
@@ -390,7 +455,8 @@ web.allowCookieAuthentication = true # Config file for storing project metadata
#
# SINCE 1.2.0
-web.projectsFile = projects.conf
+# 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)
@@ -404,7 +470,8 @@ web.projectsFile = projects.conf #
# SINCE 0.5.0
# RESTART REQUIRED
-realm.userService = users.conf
+# BASEFOLDER
+realm.userService = ${baseFolder}/users.conf
# How to store passwords.
# Valid values are plain, md5, or combined-md5. md5 is the hash of password.
@@ -463,7 +530,8 @@ web.enableRpcAdministration = false # http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
#
# SINCE 1.0.0
-web.robots.txt =
+# BASEFOLDER
+web.robots.txt = ${baseFolder}/robots.txt
# 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.
@@ -562,6 +630,7 @@ web.showFederationRegistrations = false # Specifying "gitblit" uses the internal login message.
#
# SINCE 0.7.0
+# BASEFOLDER
web.loginMessage = gitblit
# This is the message displayed above the repositories table.
@@ -569,6 +638,7 @@ web.loginMessage = gitblit # 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.
@@ -878,7 +948,8 @@ federation.allowProposals = false # Use forward slashes even on Windows!!
#
# SINCE 0.6.0
-federation.proposalsFolder = proposals
+# BASEFOLDER
+federation.proposalsFolder = ${baseFolder}/proposals
# The default pull frequency if frequency is unspecified on a registration
#
@@ -980,7 +1051,8 @@ realm.ldap.password = password #
# SINCE 1.0.0
# RESTART REQUIRED
-realm.ldap.backingUserService = users.conf
+# BASEFOLDER
+realm.ldap.backingUserService = ${baseFolder}/users.conf
# Delegate team membership control to LDAP.
#
@@ -1076,7 +1148,8 @@ realm.ldap.email = email # default: users.conf
#
# RESTART REQUIRED
-realm.redmine.backingUserService = users.conf
+# BASEFOLDER
+realm.redmine.backingUserService = ${baseFolder}/users.conf
# URL of the Redmine.
realm.redmine.url = http://example.com/redmine
@@ -1089,7 +1162,8 @@ realm.redmine.url = http://example.com/redmine #
# SINCE 0.5.0
# RESTART REQUIRED
-server.tempFolder = temp
+# BASEFOLDER
+server.tempFolder = ${baseFolder}/temp
# Use Jetty NIO connectors. If false, Jetty Socket connectors will be used.
#
diff --git a/groovy/.gitignore b/distrib/groovy/.gitignore index e58dc47f..e58dc47f 100644 --- a/groovy/.gitignore +++ b/distrib/groovy/.gitignore diff --git a/groovy/blockpush.groovy b/distrib/groovy/blockpush.groovy index caef3306..caef3306 100644 --- a/groovy/blockpush.groovy +++ b/distrib/groovy/blockpush.groovy diff --git a/distrib/groovy/fogbugz.groovy b/distrib/groovy/fogbugz.groovy new file mode 100644 index 00000000..4c19d3da --- /dev/null +++ b/distrib/groovy/fogbugz.groovy @@ -0,0 +1,167 @@ +import org.eclipse.jgit.revwalk.RevCommit;
+
+/*
+ * 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.
+ */
+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 com.sun.org.apache.xalan.internal.xsltc.compiler.Import;
+
+import java.text.SimpleDateFormat
+import org.eclipse.jgit.lib.Repository
+import org.eclipse.jgit.lib.Config
+import org.eclipse.jgit.revwalk.RevCommit
+import org.eclipse.jgit.transport.ReceiveCommand
+import org.eclipse.jgit.transport.ReceiveCommand.Result
+import org.slf4j.Logger
+
+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.lib.Constants;
+import org.eclipse.jgit.lib.IndexDiff;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.patch.FileHeader;
+import org.eclipse.jgit.revwalk.RevWalk;
+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 java.util.Set;
+import java.util.HashSet;
+
+/**
+ * Sample Gitblit Post-Receive Hook: fogbugz
+ *
+ * The purpose of this script is to invoke the Fogbugz API and update a case when
+ * push is received based.
+ *
+ * Example URL - http://bugs.yourdomain.com/fogbugz/cvsSubmit.asp?ixBug=bugID&sFile=file&sPrev=x&sNew=y&ixRepository=206
+ *
+ * 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
+ * receivePack JGit Receive Pack org.eclipse.jgit.transport.ReceivePack
+ * user Gitblit User com.gitblit.models.UserModel
+ * commands JGit commands Collection<org.eclipse.jgit.transport.ReceiveCommand>
+ * url Base url for Gitblit 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
+ *
+ * Cusom Fileds Used by This script
+ * fogbugzUrl - base URL to Fogbugz (ie. https://bugs.yourdomain.com/fogbugz/)
+ * fogbugzRepositoryId - (ixRepository value from Fogbugz Source Control configuration screen)
+ * fogbugzCommitMessageRegex - regex pattern used to match on bug id
+ */
+
+// Indicate we have started the script
+logger.info("fogbugz 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)
+
+// pull custom fields from repository specific values
+// groovy.customFields = "fogbugzUrl=Fogbugz Base URL" "fogbugzRepositoryId=Fogbugz Repository ID" "fogbugzCommitMessageRegex="Fogbugz Commit Message Regular Expression"
+def fogbugzUrl = repository.customFields.fogbugzUrl
+def fogbugzRepositoryId = repository.customFields.fogbugzRepositoryId
+def bugIdRegex = repository.customFields.fogbugzCommitMessageRegex
+
+for (command in commands) {
+
+ for( commit in JGitUtils.getRevLog(r, command.oldId.name, command.newId.name).reverse() ) {
+ // Example URL - http://bugs.salsalabs.com/fogbugz/cvsSubmit.asp?ixBug=bugID&sFile=file&sPrev=x&sNew=y&ixRepository=206
+ def bugIds = [];
+ // Grab the second matcher and then filter out each numeric ID and add it to array
+ (commit.getFullMessage() =~ bugIdRegex).each{ (it[1] =~ "\\d+").each {bugIds.add(it)} }
+
+ for( file in getFiles(r, commit) ) {
+ for( bugId in bugIds ) {
+ def url = "${fogbugzUrl}/cvsSubmit.asp?ixBug=${bugId}&sFile=${file}&sPrev=${command.oldId.name}&sNew=${command.newId.name}&ixRepository=${fogbugzRepositoryId}"
+ logger.info( url );
+ // Hit the page and make sure we get an "OK" response
+ def responseString = new URL(url).getText()
+ if( !"OK".equals(responseString) ) {
+ throw new Exception( "Problem posting ${url} - ${responseString}" );
+ }
+ }
+ }
+ }
+}
+// close the repository reference
+r.close()
+
+/**
+ * For a given commit, find all files part of it.
+ */
+def Set<String> getFiles(Repository r, RevCommit commit) {
+ DiffFormatter formatter = new DiffFormatter(DisabledOutputStream.INSTANCE)
+ formatter.setRepository(r)
+ formatter.setDetectRenames(true)
+ formatter.setDiffComparator(RawTextComparator.DEFAULT);
+
+ def diffs
+ RevWalk rw = new RevWalk(r)
+ 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()
+
+ // Grab each filepath
+ Set<String> fileNameSet = new HashSet<String>( diffs.size() );
+ for (DiffEntry entry in diffs) {
+ FileHeader header = formatter.toFileHeader(entry)
+ fileNameSet.add( header.newPath )
+ }
+ return fileNameSet;
+}
\ No newline at end of file diff --git a/groovy/jenkins.groovy b/distrib/groovy/jenkins.groovy index d76a3d66..d76a3d66 100644 --- a/groovy/jenkins.groovy +++ b/distrib/groovy/jenkins.groovy diff --git a/groovy/localclone.groovy b/distrib/groovy/localclone.groovy index 49b7f8b3..49b7f8b3 100644 --- a/groovy/localclone.groovy +++ b/distrib/groovy/localclone.groovy diff --git a/groovy/protect-refs.groovy b/distrib/groovy/protect-refs.groovy index b1b611f4..b1b611f4 100644 --- a/groovy/protect-refs.groovy +++ b/distrib/groovy/protect-refs.groovy diff --git a/groovy/sendmail-html.groovy b/distrib/groovy/sendmail-html.groovy index 16920735..16920735 100644 --- a/groovy/sendmail-html.groovy +++ b/distrib/groovy/sendmail-html.groovy diff --git a/groovy/sendmail.groovy b/distrib/groovy/sendmail.groovy index c832bc64..c832bc64 100644 --- a/groovy/sendmail.groovy +++ b/distrib/groovy/sendmail.groovy diff --git a/groovy/thebuggenie.groovy b/distrib/groovy/thebuggenie.groovy index b4385a26..b4385a26 100644 --- a/groovy/thebuggenie.groovy +++ b/distrib/groovy/thebuggenie.groovy diff --git a/distrib/installService.cmd b/distrib/installService.cmd index 77b641e8..d254d679 100644 --- a/distrib/installService.cmd +++ b/distrib/installService.cmd @@ -25,12 +25,12 @@ SET ARCH=amd64 --StartPath="%CD%" ^
--StartClass=com.gitblit.Launcher ^
--StartMethod=main ^
- --StartParams="--storePassword;gitblit" ^
+ --StartParams="--storePassword;gitblit;--baseFolder;%CD%\data" ^
--StartMode=jvm ^
--StopPath="%CD%" ^
--StopClass=com.gitblit.Launcher ^
--StopMethod=main ^
- --StopParams="--stop" ^
+ --StopParams="--stop;--baseFolder;%CD%\data" ^
--StopMode=jvm ^
--Classpath="%CD%\gitblit.jar" ^
--Jvm=auto ^
diff --git a/distrib/projects.conf b/distrib/projects.conf new file mode 100644 index 00000000..d43f4829 --- /dev/null +++ b/distrib/projects.conf @@ -0,0 +1,3 @@ +[project "main"]
+ title = Main Repositories
+ description = main group of repositories
\ No newline at end of file diff --git a/docs/01_features.mkd b/docs/01_features.mkd index 4676f755..b99aa52e 100644 --- a/docs/01_features.mkd +++ b/docs/01_features.mkd @@ -20,7 +20,7 @@ - *Experimental* built-in Garbage Collection
- Ability to federate with one or more other Gitblit instances
- RSS/JSON RPC interface
-- Java/Swing Gitblit Manager tool
+- Java/Swing Gitblit Manager tool
- Gitweb inspired web UI
- Responsive web UI that subtracts elements to be usable on phones, tablets, and desktop browsers
- Groovy pre- and post- push hook scripts, per-repository or globally for all repositories
@@ -32,9 +32,12 @@ - Repository Owners may edit repositories through the web UI
- Administrators and Repository Owners may set the default branch through the web UI or RPC interface
- LDAP authentication and optional LDAP-controlled Team memberships
+- Redmine authentication
- Gravatar integration
- Git-notes display support
- Submodule support
+- Push log based on a hidden, orphan branch refs/gitblit/pushes
+- Fanout PubSub notifications service for self-hosted [Sparkleshare](http://sparkleshare.org) use
- gh-pages display support (Jekyll is not supported)
- Branch metrics (uses Google Charts)
- HEAD and Branch RSS feeds
@@ -56,6 +59,9 @@ - Spanish
- Polish
- Korean
+ - Brazilian Portuguese
+ - Dutch
+ - Chinese (zh_CN)
## Gitblit GO Features
- Out-of-the-box integrated stack requiring minimal configuration
@@ -63,7 +69,7 @@ - Integrated GUI tool to facilitate x509 PKI including ssl and client certificate generation, client certificate revocation, and client certificate distribution
- Single text file for configuring server and gitblit
- A Windows service installation script and configuration tool
-- Built-in AJP connector for Apache httpd
+- Built-in AJP connector for Apache httpd
## Limitations
- HTTP/HTTPS are the only supported Git protocols
diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd index be0e191f..a7f75264 100644 --- a/docs/01_setup.mkd +++ b/docs/01_setup.mkd @@ -1,53 +1,55 @@ -## Gitblit WAR Setup
+## Gitblit WAR Installation & Setup
1. Download [Gitblit WAR %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) to the webapps folder of your servlet container.
2. You may have to manually extract the WAR (zip file) to a folder within your webapps folder.
-3. Copy the `WEB-INF/users.conf` file to a location outside the webapps folder that is accessible by your servlet container.
-Optionally copy the example hook scripts in `WEB-INF/groovy` to a location outside the webapps folder that is accesible by your servlet container.
-4. The Gitblit webapp is configured through its `web.xml` file.
-Open `web.xml` in your favorite text editor and make sure to review and set:
- - <context-parameter> *git.repositoryFolder* (set the full path to your repositories folder)
- - <context-parameter> *groovy.scriptsFolder* (set the full path to your Groovy hook scripts folder)
- - <context-parameter> *groovy.grapeFolder* (set the full path to your Groovy Grape artifact cache)
- - <context-parameter> *web.projectsFile* (set the full path to your projects metadata file)
- - <context-parameter> *realm.userService* (set the full path to `users.conf`)
+3. By default, the Gitblit webapp is configured through `WEB-INF/data/gitblit.properties`.<br/>
+Open `WEB-INF/data/gitblit.properties` in your favorite text editor and make sure to review and set:
- <context-parameter> *git.packedGitLimit* (set larger than the size of your largest repository)
- <context-parameter> *git.streamFileThreshold* (set larger than the size of your largest committed file)
-5. You may have to restart your servlet container.
-6. Open your browser to <http://localhost/gitblit> or whatever the url should be.
-7. Enter the default administrator credentials: **admin / admin** and click the *Login* button
+4. You may have to restart your servlet container.
+5. Open your browser to <http://localhost/gitblit> or whatever the url should be.
+6. Enter the default administrator credentials: **admin / admin** and click the *Login* button
**NOTE:** Make sure to change the administrator username and/or password!!
-## Gitblit GO Setup
+### WAR Data Location
+By default, Gitblit WAR stores all data (users, settings, repositories, etc) in `${contextFolder}/WEB-INF/data`. This is fine for a quick setup, but there are many reasons why you don't want to keep your data within the webapps folder of your servlet container. You may specify an external location for your data by editing `WEB-INF/web.xml` and manipulating the *baseFolder* context parameter. Choose a location that is writeable by your servlet container. Your servlet container may be smart enough to recognize the change and to restart Gitblit.
+
+On the next restart of Gitblit, Gitblit will copy the contents of the `WEB-INF/data` folder to your specified *baseFolder* **IF** the file `${baseFolder}/gitblit.properties` does not already exist. This allows you to get going with minimal fuss.
+
+Specifying an alternate *baseFolder* also allows for simpler upgrades in the future.
+
+## Gitblit GO Installation & Setup
1. Download and unzip [Gitblit GO %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%GO%).
*Its best to eliminate spaces in the path name.*
-2. The server itself is configured through a simple text file.
-Open `gitblit.properties` in your favorite text editor and make sure to review and set:
- - *git.repositoryFolder* (path may be relative or absolute)
- - *groovy.scriptsFolder* (path may be relative or absolute)
- - *groovy.grapeFolder* (path may be relative or absolute)
- - *server.tempFolder* (path may be relative or absolute)
+2. The server itself is configured through a simple text file.<br/>
+Open `data/gitblit.properties` in your favorite text editor and make sure to review and set:
- *server.httpPort* and *server.httpsPort*
- *server.httpBindInterface* and *server.httpsBindInterface*
- *server.storePassword*
**https** is strongly recommended because passwords are insecurely transmitted form your browser/git client using Basic authentication!
- *git.packedGitLimit* (set larger than the size of your largest repository)
- *git.streamFileThreshold* (set larger than the size of your largest committed file)
-3. Execute `authority.cmd` or `java -jar authority.jar` from a command-line
+3. Execute `authority.cmd` or `java -jar authority.jar --baseFolder data` from a command-line
1. fill out the fields in the *new certificate defaults* dialog
2. enter the store password used in *server.storePassword* when prompted. This generates an SSL certificate for **localhost**.
3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from<br/>**NOTE:** You can only have **one** SSL certificate specified for a port.
5. exit the authority app
-4. Execute `gitblit.cmd` or `java -jar gitblit.jar` from a command-line
+4. Execute `gitblit.cmd` or `java -jar gitblit.jar --baseFolder data` from a command-line
5. Open your browser to <http://localhost:8080> or <https://localhost:8443> depending on your chosen configuration.
6. Enter the default administrator credentials: **admin / admin** and click the *Login* button
**NOTE:** Make sure to change the administrator username and/or password!!
+### GO Data Location
+
+By default, Gitblit GO stores all data (users, settings, repositories, etc) in the `data` subfolder of your GO installation. You may specify an external location for your data on the command-line by setting the *--baseFolder* argument. If you relocate the data folder then you must supply the *--baseFolder* argument to both GO and the Certificate Authority.
+
+If you are deploying Gitblit to a *nix platform, you might consider moving the data folder out of the GO installation folder and then creating a symlink named "data" that points to your moved folder.
+
### Creating your own Self-Signed SSL Certificate
Gitblit GO (and Gitblit Certificate Authority) automatically generates a Certificate Authority (CA) certificate and an ssl certificate signed by this CA certificate that is bound to *localhost*.
-Remote Eclipse/EGit/JGit clients (<= 2.1.0) will fail to communicate using this certificate because JGit always verifies the hostname of the certificate, regardless of the *http.sslVerify=false* client-side setting.
+Remote Eclipse/EGit/JGit clients (<= 2.2.0) will fail to communicate using this certificate because JGit always verifies the hostname of the certificate, regardless of the *http.sslVerify=false* client-side setting.
The EGit failure message is something like:
@@ -56,7 +58,7 @@ The EGit failure message is something like: If you want to serve your repositories to another machine over https then you will want to generate a new certificate for the hostname or ip address you are serving from.
-1. `authority.cmd` or `java -jar authority.jar`
+1. `authority.cmd` or `java -jar authority.jar --baseFolder data`
2. Click the *new ssl certificate* button (red rosette in the toolbar in upper left of window)
3. Enter the hostname or ip address
4. Make sure the checkbox *serve https with this certificate* is checked
@@ -64,11 +66,11 @@ If you want to serve your repositories to another machine over https then you wi If you decide to change the value of *server.storePassword* (recommended) <u>after</u> you have already started Gitblit or Gitblit Certificate Authority, then you will have to delete the following files and then restart the Gitblit Certificate Authority app:
-1. serverKeyStore.jks
-2. serverTrustStore.jks
-3. certs/caKeyStore.jks
-4. certs/ca.crt
-5. certs/caRevocationList.crl (optional)
+1. data/serverKeyStore.jks
+2. data/serverTrustStore.jks
+3. data/certs/caKeyStore.jks
+4. data/certs/ca.crt
+5. data/certs/caRevocationList.crl (optional)
### Client SSL Certificates
SINCE 1.2.0
@@ -84,6 +86,7 @@ Gitblit must be able to map the DN of the certificate to an *existing* account u How do you make your servlet container trust a client certificate?
In the WAR variant, you will have to manually setup your servlet container to:
+
1. want/need client certificates
2. trust a CA certificate used to sign your client certificates
3. generate client certificates signed by your CA certificate
@@ -92,9 +95,9 @@ Alternatively, Gitblit GO is designed to facilitate use of client certificate au #### Creating SSL Certificates with Gitblit Certificate Authority
-When you generate a new client certificate, a zip file bundle is created which includes a P12 keystore for browsers and a PEM keystore for Git. Both of these are password-protected. Additionally, a personalized README file is generated with setup instructions for popular browsers and Git. The README is generated from `certs\instructions.tmpl` and can be modified to suit your needs.
+When you generate a new client certificate, a zip file bundle is created which includes a P12 keystore for browsers and a PEM keystore for Git. Both of these are password-protected. Additionally, a personalized README file is generated with setup instructions for popular browsers and Git. The README is generated from `data\certs\instructions.tmpl` and can be modified to suit your needs.
-1. `authority.cmd` or `java -jar authority.jar`
+1. `authority.cmd` or `java -jar authority.jar --baseFolder data`
2. Select the user for which to generate the certificate
3. Click the *new certificate* button and enter the expiration date of the certificate. You must also enter a password for the generated keystore. This password is *not* the same as the user's login password. This password is used to protect the privatekey and public certificate you will generate for the selected user. You must also enter a password hint for the user.
4. If your mail server settings are properly configured you will have a *send email* checkbox which you can use to immediately send the generated certificate bundle to the user.
@@ -130,6 +133,7 @@ C:\Program Files\Java\jre6\bin\server\jvm.dll</pre> #### Command-Line Parameters
Command-Line parameters override the values in `gitblit.properties` at runtime.
+ --baseFolder The default base folder for all relative file reference settings
--repositoriesFolder Git Repositories Folder
--userService Authentication and Authorization Service (filename or fully qualified classname)
--useNio Use NIO Connector else use Socket Connector.
@@ -143,7 +147,7 @@ Command-Line parameters override the values in `gitblit.properties` at runtime. **Example**
- java -jar gitblit.jar --userService c:\myrealm.config --storePassword something
+ java -jar gitblit.jar --userService c:/myrealm.config --storePassword something --baseFolder c:/data
#### Overriding Gitblit GO's Log4j Configuration
@@ -221,9 +225,6 @@ ProxyPreserveHost On Alternatively, you can respecify *web.forwardSlashCharacter*.
## Upgrading Gitblit
-Generally, upgrading is easy.
-
-Since Gitblit does not use a database the only files you have to worry about are your configuration file (`gitblit.properties` or `web.xml`) and possibly your `users.conf` or `users.properties` file.
Any important changes to the setting keys or default values will always be mentioned in the [release log](releases.html).
@@ -231,26 +232,60 @@ Gitblit v0.8.0 introduced a new default user service implementation which serial `users.properties` and its user service implementation are deprecated as of v0.8.0.
-### Upgrading Gitblit WAR
-1. Backup your `web.xml` file
-Backup your `web.properties` file (if you have one, these are the setting overrides from using the RPC administration service)
-2. Delete currently deployed gitblit WAR
-3. Deploy new WAR and overwrite the `web.xml` file with your backup
-4. Review and optionally apply any new settings as indicated in the [release log](releases.html).
+### Upgrading Gitblit WAR (1.2.1+)
+1. Make sure your `WEB-INF/web.xml` *baseFolder* context parameter is not `${contextFolder}/WEB-INF/data`!<br/>
+If it is, move your `WEB-INF/data` folder to a location writeable by your servlet container.
+2. Deploy new WAR
+3. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
+4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `${baseFolder}/gitblit.properties`.
-### Upgrading Gitblit GO
+### Upgrading Gitblit GO (1.2.1+)
-1. Backup your `gitblit.properties` file
-2. Backup your `users.properties` file *(if it is located in the Gitblit GO folder)*
-OR
-Backup your `users.conf` file *(if it is located in the Gitblit GO folder)*
-3. Backup your Groovy hook scripts
-4. Unzip Gitblit GO to a new folder
-5. Overwrite the `gitblit.properties` file with your backup
-6. Overwrite the `users.properties` file with your backup *(if it was located in the Gitblit GO folder)*
-OR
-Overwrite the `users.conf` file with your backup *(if it was located in the Gitblit GO folder)*
-7. Review and optionally apply any new settings as indicated in the [release log](releases.html).
+1. Unzip Gitblit GO to a new folder
+2. Copy your `data` folder from your current Gitblit installation to the new folder and overwrite any conflicts
+3. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `data/gitblit.properties`.
+
+In *nix systems, there are other tricks you can play like symlinking the `data` folder or symlinking the GO folder.
+All platforms support the *--baseFolder* command-line argument.
+
+### Upgrading Gitblit WAR (pre-1.2.1)
+
+1. Create a `data` as outlined in step 1 of *Upgrading Gitblit GO (pre-1.2.1)*
+2. Copy your existing web.xml to your data folder
+3. Deploy new WAR
+4. Copy the new WAR's `WEB-INF/data/gitblit.properties` file to your data folder
+5. Manually apply any changes you made to your original web.xml file to the gitblit.properties file you copied to your data folder
+6. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
+
+### Upgrading Gitblit GO (pre-1.2.1)
+1. Create a `data` folder and copy the following files and folders to it:
+ - **users.conf*
+ - **projects.conf** *(if you have one)*
+ - **gitblit.properties**
+ - **serverKeystore.jks**
+ - **serverTrustStore.jks**
+ - *certs** folder
+ - **git** folder
+ - **groovy** folder
+ - **proposals** folder
+ - and any other custom files (robots.txt, welcome/login markdown files, etc)
+ - then edit your `gitblit.properties` file and adjust the following settings:
+ - *git.repositoriesFolder* = ${baseFolder}/git
+ - *groovy.scriptsFolder* = ${baseFolder}/groovy
+ - *groovy.grapeFolder* = ${baseFolder}/groovy/grape
+ - *web.projectsFile* = ${baseFolder}/projects.conf
+ - *realm.userService* = ${baseFolder}/users.conf
+ - *web.robots.txt* = ${baseFolder}/robots.txt
+ - *federation.proposalsFolder* = ${baseFolder}/proposals
+ - *realm.ldap.backingUserService* = ${baseFolder}/users.conf
+ - *realm.redmine.backingUserService* = ${baseFolder}/users.conf
+ - *server.tempFolder* = ${baseFolder}/temp
+
+2. Unzip Gitblit GO to a new folder
+3. Copy your `data` folder and overwrite the folder of the same name in the just-unzipped version
+4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `data/gitblit.properties`.
+
+**NOTE:** You may need to adjust your service definitions to include the `--baseFolder data` argument.
#### Upgrading Windows Service
You may need to delete your old service definition and install a new one depending on what has changed in the release.
@@ -277,7 +312,7 @@ All repository settings are stored within the repository `.git/config` file unde federationSets =
#### Repository Names
-Repository names must be unique and are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS. The name must be composed of letters, digits, or `/ _ - . ~`<br/>
+Repository names must be case-insensitive-unique but are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS. The name must be composed of letters, digits, or `/ _ - . ~`<br/>
Whitespace is illegal.
Repositories can be grouped within subfolders. e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git*
@@ -366,6 +401,10 @@ All checks are case-insensitive. You can not use fast-forward merges on your client when using committer verification. You must specify *--no-ff* to ensure that a merge commit is created with your identity as the committer. Only the first parent chain is traversed when verifying commits.
+#### Push Log
+
+Gitblit v1.2.1 introduces an incomplete push mechanism. All pushes are logged since 1.2.1, but the log has not yet been exposed through the web ui. This will be a feature of an upcoming release.
+
### Teams
Since v0.8.0, Gitblit supports *teams* for the original `users.properties` user service and the current default user service `users.conf`. Teams have assigned users and assigned repositories. A user can be a member of multiple teams and a repository may belong to multiple teams. This allows the administrator to quickly add a user to a team without having to keep track of all the appropriate repositories.
diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd index 396b2550..097dd170 100644 --- a/docs/04_releases.mkd +++ b/docs/04_releases.mkd @@ -1,17 +1,76 @@ ## Release History
+### Current Release
+
+**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%) | [war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) | [express](http://code.google.com/p/gitblit/downloads/detail?name=%EXPRESS%) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) | [api](http://code.google.com/p/gitblit/downloads/detail?name=%API%)) based on [%JGIT%][jgit] *released %BUILDDATE%*
+
+#### fixes
+
+- Can't set reset settings with $ or { characters through Gitblit Manager because they are not properly escaped
+
+#### additions
+
+ - FogBugz post-receive hook script (github/djschny)
+ - Implemented multiple repository owners (github/akquinet)
+ - Chinese translation (github/dapengme, github/yin8086)
+
+### Older Releases
+
<div class="alert alert-info">
-<h4>Update Note</h4>
-The permissions model has changed in this release.
-<p>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.</p>
+<h4>Update Note 1.2.1</h4>
+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 see the Upgrading section of the <a href="setup.html">setup</a> page for details.</p>
+
+<b>Express Users</b> make sure to update your web.xml file with the ${baseFolder} values!
</div>
-### Current Release
+#### fixes
-**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%) | [war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) | [express](http://code.google.com/p/gitblit/downloads/detail?name=%EXPRESS%) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) | [api](http://code.google.com/p/gitblit/downloads/detail?name=%API%)) based on [%JGIT%][jgit] *released %BUILDDATE%*
+- 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 it's 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 (github/inaiat)
+
+#### additions
+
+- Fanout PubSub service for self-hosted [Sparkleshare](http://sparkleshare.org) notifications.<br/>
+This service is disabled by default.<br/>
+ **New:** *fanout.bindInterface = localhost*<br/>
+ **New:** *fanout.port = 0*<br/>
+ **New:** *fanout.useNio = true*<br/>
+ **New:** *fanout.connectionLimit = 0*
+- Implemented a simple push log based on a hidden, orphan branch refs/gitblit/pushes (issue 177)<br/>
+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 (github/kwoot)
+
+#### changes
+
+- Gitblit GO and Gitblit WAR are now both configured by `gitblit.properties`. WAR is no longer configured by `web.xml`.<br/>
+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 (github/ds5apn)
+- Updated checkstyle definition (github/mystygage)
+
+<div class="alert alert-info">
+<h4>Update Note 1.2.0</h4>
+The permissions model has changed in the 1.2.0 release.
+<p>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.</p>
+</div>
+
+**1.2.0** *released 2012-12-31*
#### 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)
@@ -27,7 +86,7 @@ The permissions model has changed in this release. #### additions
-- Implemented discrete repository permissions (issue 36)
+- Implemented discrete repository permissions (issue 36)
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
@@ -35,34 +94,34 @@ The permissions model has changed in this release. - 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)
+- Implemented *case-insensitive* regex repository permission matching (issue 36)<br/>
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.
+- Added support for personal repositories.<br/>
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 support for server-side forking of a repository to a personal repository (issue 137)<br/>
+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.<br/>
**New:** *web.allowForking=true*
-- Added optional *create-on-push* support
+- Added optional *create-on-push* support<br/>
**New:** *git.allowCreateOnPush=true*
-- Added **experimental** JGit-based garbage collection service. This service is disabled by default.
- **New:** *git.allowGarbageCollection=false*
- **New:** *git.garbageCollectionHour = 0*
- **New:** *git.defaultGarbageCollectionThreshold = 500k*
+- Added **experimental** JGit-based garbage collection service. This service is disabled by default.<br/>
+ **New:** *git.allowGarbageCollection=false*<br/>
+ **New:** *git.garbageCollectionHour = 0*<br/>
+ **New:** *git.defaultGarbageCollectionThreshold = 500k*<br/>
**New:** *git.defaultGarbageCollectionPeriod = 7 days*
-- Added support for X509 client certificate authentication (github/kevinanderson1). (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.
- **New:** *git.requireClientCertificates = false*
- **New:** *git.enforceCertificateValidity = true*
+- Added support for X509 client certificate authentication (github/kevinanderson1). (issue 106)<br/>
+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.<br/>
+ **New:** *git.requireClientCertificates = false*<br/>
+ **New:** *git.enforceCertificateValidity = true*<br/>
**New:** *git.certificateUsernameOIDs = CN*
- 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.
-This is extreme and should be considered carefully since it affects every https access. The default is to **want** client certificates. Setting this value to *true* changes that to **need** client certificates.
+- Added support for Gitblit GO to require usage of client certificates to access the entire server.<br/>
+This is extreme and should be considered carefully since it affects every https access. The default is to **want** client certificates. Setting this value to *true* changes that to **need** client certificates.<br/>
**New:** *server.requireClientCertificates = false*
- Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication.
-- Added setting to control length of shortened commit ids
+- Added setting to control length of shortened commit ids<br/>
**New:** *web.shortCommitIdLength=8*
-- Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)
+- Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)<br/>
**New:** *web.compressedDownloads = zip gz*
- 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)
@@ -71,6 +130,7 @@ This is extreme and should be considered carefully since it affects every https - Added HTML sendmail hook script and Gitblit.sendHtmlMail method (github/sauthieg)
- Added RedmineUserService (github/mallowlabs)
- Support for committer verification. Requires use of *--no-ff* when merging branches or pull requests. See setup page for details.
+- Added Brazilian Portuguese translation (github/rafaelcavazin)
#### changes
@@ -86,14 +146,14 @@ This is extreme and should be considered carefully since it affects every https - 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 (Github/jpyeron, issue 126)
-- LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list.
+- 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)
#### dependency changes
- updated to Jetty 7.6.8
-- updated to JGit 2.1.0.201209190230-r
+- updated to JGit 2.2.0.201212191850-r
- updated to Groovy 1.8.8
- updated to Wicket 1.4.21
- updated to Lucene 3.6.1
@@ -104,10 +164,8 @@ If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, th - added XZ for Java 1.0
<hr/>
-### Older Releases
-
<div class="alert alert-error">
-<h4>Update Note</h4>
+<h4>Update Note 1.1.0</h4>
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.
</div>
@@ -135,24 +193,24 @@ If you are updating from an earlier release AND you have indexed branches with t #### additions
-- Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103)
+- Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103)<br/>
**New:** *git.cacheRepositoryList=true*
-- Preliminary bare repository submodule support
+- Preliminary bare repository submodule support<br/>
**New:** *git.submoduleUrlPatterns=*
- - *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*
+ - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url.<br/>
+ For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git*<br/>
**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)
+- Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117)<br/>
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 default authorization control setting (AUTHENTICATED or NAMED)<br/>
**New:** *git.defaultAuthorizationControl=NAMED*
-- Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)
+- Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)<br/>
**New:** *git.searchRecursionDepth=-1*
-- Added setting to specify regex exclusions for repositories (issue 103)
+- Added setting to specify regex exclusions for repositories (issue 103)<br/>
**New:** *git.searchExclusions=*
- Blob page now supports displaying images (issue 6)
- Non-image binary files can now be downloaded using the RAW link
@@ -182,38 +240,38 @@ AUTHENTICATED allows restricted access for any authenticated user. This is a lo #### changes
-- **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes**
+- **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes**<br/>
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
+- IUserService interface has changed to better accomodate custom authentication and/or custom authorization<br/>
The default `users.conf` now supports persisting display names and email addresses.
- Updated Japanese translation (Github/zakki)
#### additions
-- Added setting to allow specification of a robots.txt file (issue 99)
+- Added setting to allow specification of a robots.txt file (issue 99)<br/>
**New:** *web.robots.txt =*
-- Added setting to control Responsive layout or Fixed-Width layout (issue 101)
+- Added setting to control Responsive layout or Fixed-Width layout (issue 101)<br/>
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.
**New:** *web.useResponsiveLayout = true*
-- Added setting to control charsets for blob string decoding. Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97)
+- Added setting to control charsets for blob string decoding. Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97)<br/>
**New:** *web.blobEncodings = UTF-8 ISO-8859-1*
-- Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93)
- Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.
- **New:** *git.packedGitWindowSize = 8k*
- **New:** *git.packedGitLimit = 10m*
- **New:** *git.deltaBaseCacheLimit = 10m*
- **New:** *git.packedGitOpenFiles = 128*
- **New:** *git.streamFileThreshold = 50m*
+- Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93)<br/>
+ Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.<br/>
+ **New:** *git.packedGitWindowSize = 8k*<br/>
+ **New:** *git.packedGitLimit = 10m*<br/>
+ **New:** *git.deltaBaseCacheLimit = 10m*<br/>
+ **New:** *git.packedGitOpenFiles = 128*<br/>
+ **New:** *git.streamFileThreshold = 50m*<br/>
**New:** *git.packedGitMmap = false*
-- Added default access restriction. Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88)
+- Added default access restriction. Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88)<br/>
**New:** *git.defaultAccessRestriction = NONE*
- 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 setting to control Groovy Grape root folder (location where resolved dependencies are stored)<br/>
+ [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath.<br/>
**New:** *groovy.grapeFolder = groovy/grape*
- Added LDAP User Service with many new *realm.ldap* keys (Github/jcrygier)
-- Added support for custom repository properties for Groovy hooks (Github/jcrygier)
+- Added support for custom repository properties for Groovy hooks (Github/jcrygier)<br/>
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 (Github/mragab)
- Added Polish translation (Lukasz Jader)
@@ -278,23 +336,23 @@ Make sure to properly set *web.blobEncodings* before starting Gitblit if you are #### additions
-- Added optional Lucene branch indexing (issue 16)
- **New:** *web.allowLuceneIndexing = true*
+- Added optional Lucene branch indexing (issue 16)<br/>
+ **New:** *web.allowLuceneIndexing = true*<br/>
**New:** *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*
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's 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.
+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).
+**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)
+- Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54)<br/>
**New:** *web.timezone =*
-- Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59)
- **New:** *server.ajpPort = 0*
+- Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59)<br/>
+ **New:** *server.ajpPort = 0*<br/>
**New:** *server.ajpBindInterface = localhost*
-- On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49)
+- On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49)<br/>
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
+- New setting to prevent display/serving non-bare repositories<br/>
**New:** *git.onlyAccessBareRepositories = false*
- Added *protect-refs.groovy* (Github/plm)
- Allow setting default branch (relinking HEAD) to a branch or a tag (Github/plm)
@@ -348,31 +406,31 @@ Push requests to these repositories will be rejected. #### additions
-- Platform-independent, Groovy push hook script mechanism.
-Hook scripts can be set per-repository, per-team, or globally for all repositories.
- **New:** *groovy.scriptsFolder = groovy*
- **New:** *groovy.preReceiveScripts =*
+- Platform-independent, Groovy push hook script mechanism.<br/>
+Hook scripts can be set per-repository, per-team, or globally for all repositories.<br/>
+ **New:** *groovy.scriptsFolder = groovy*<br/>
+ **New:** *groovy.preReceiveScripts =*<br/>
**New:** *groovy.postReceiveScripts =*
-- *sendmail.groovy* for optional email notifications on push.
+- *sendmail.groovy* for optional email notifications on push.<br/>
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.
+- 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.<br/>
**New:** *mail.mailingLists =*
- *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`)
+- New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`)<br/>
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 it's corresponding implementation are **deprecated**.
+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.<br/>
+The original `users.properties` file and it's corresponding implementation are **deprecated**.<br/>
**New:** *realm.userService = users.conf*
- Teams for specifying user-repository access in bulk. Teams may also specify mailing lists addresses and pre- & post- receive hook scripts.
-- Gravatar integration
+- Gravatar integration<br/>
**New:** *web.allowGravatar = true*
-- Activity page for aggregated repository activity. This is a timeline of commit activity over the last N days for one or more repositories.
- **New:** *web.activityDuration = 14*
- **New:** *web.timeFormat = HH:mm*
+- Activity page for aggregated repository activity. This is a timeline of commit activity over the last N days for one or more repositories.<br/>
+ **New:** *web.activityDuration = 14*<br/>
+ **New:** *web.timeFormat = HH:mm*<br/>
**New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
-- *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)
+- *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)<br/>
**New:** *web.customFilters=*
-- Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy
+- Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy<br/>
**New:** *web.allowFlashCopyToClipboard = true*
- 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)
@@ -382,7 +440,7 @@ The original `users.properties` file and it's corresponding implementation are * #### changes
- Dropped display of trailing .git from repository names
-- Gitblit GO is now monolithic like the WAR build. (issue 30)
+- Gitblit GO is now monolithic like the WAR build. (issue 30)<br/>
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
@@ -402,25 +460,25 @@ This change helps adoption of GO in environments without an internet connection **0.7.0** *released 2011-11-11*
- **security**: fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28)
-- improved: updated ui with Twitter's Bootstrap CSS toolkit
+- improved: updated ui with Twitter's Bootstrap CSS toolkit<br/>
**New:** *web.loginMessage = gitblit*
- improved: repositories list performance by caching repository sizes (issue 27)
- improved: summary page performance by caching metric calculations (issue 25)
-- added: authenticated JSON RPC mechanism
- **New:** *web.enableRpcServlet = true*
- **New:** *web.enableRpcManagement = false*
+- added: authenticated JSON RPC mechanism<br/>
+ **New:** *web.enableRpcServlet = true*<br/>
+ **New:** *web.enableRpcManagement = false*<br/>
**New:** *web.enableRpcAdministration = false*
- added: Gitblit API RSS/JSON RPC library
- added: Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server.
- added: per-repository setting to skip size calculation (faster repositories page loading)
- added: per-repository setting to skip summary metrics calculation (faster summary page loading)
- added: IUserService.setup(IStoredSettings) for custom user service implementations
-- added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)*
+- added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)*<br/>
**New:** *server.contextPath = /*
- added: *combined-md5* password storage option which stores the hash of username+password as the password *(Github/alyandon)*
- added: repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them *(Github/dadalar)*
- added: RSS feeds now include regex substitutions on commit messages for bug trackers, etc
-- fixed: federation protocol timestamps. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.
+- fixed: federation protocol timestamps. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.<br/>
**This breaks 0.6.0 federation clients/servers.**
- fixed: collision on rename for repositories and users
- fixed: Gitblit can now browse the Linux kernel repository (issue 25)
@@ -437,14 +495,14 @@ This change helps adoption of GO in environments without an internet connection **0.6.0** *released 2011-09-27*
-- added: 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.
- **New:** *federation.name =*
- **New:** *federation.passphrase =*
- **New:** *federation.allowProposals = false*
- **New:** *federation.proposalsFolder = proposals*
- **New:** *federation.defaultFrequency = 60 mins*
- **New:** *federation.sets =*
- **New:** *mail.* settings for sending emails
+- added: 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.<br/>
+ **New:** *federation.name =*<br/>
+ **New:** *federation.passphrase =*<br/>
+ **New:** *federation.allowProposals = false*<br/>
+ **New:** *federation.proposalsFolder = proposals*<br/>
+ **New:** *federation.defaultFrequency = 60 mins*<br/>
+ **New:** *federation.sets =*<br/>
+ **New:** *mail.* settings for sending emails<br/>
**New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
- added: google-gson dependency
- added: javamail dependency
@@ -465,9 +523,9 @@ This change helps adoption of GO in environments without an internet connection - fixed: users can now change their passwords (issue 1)
- fixed: always show root repository group first, i.e. don't sort root group with other groups
- fixed: tone-down repository group header color
-- added: optionally display repository on-disk size on repositories page
+- added: optionally display repository on-disk size on repositories page<br/>
**New:** *web.showRepositorySizes = true*
-- added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers
+- added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers<br/>
**New:** *web.forwardSlashCharacter = /*
- updated: MarkdownPapers 1.1.0
- updated: Jetty 7.4.3
diff --git a/docs/05_roadmap.mkd b/docs/05_roadmap.mkd index 14ad8c19..4ac9b478 100644 --- a/docs/05_roadmap.mkd +++ b/docs/05_roadmap.mkd @@ -21,9 +21,11 @@ This list is volatile. ### IDEAS
+* Gitblit: Pull requests
+* Gitblit: Watch/Star like github with personalized activity feed
+* Gitblit: Push database or orphan branch
* Gitblit: Re-use the EGit branch visualization table cell renderer as some sort of servlet
* Gitblit: diff should highlight inserted/removed fragment compared to original line
-* Gitblit: implement branch permission controls as Groovy pre-receive script.
-*Maintain permissions text file similar to a gitolite configuration file or svn authz file.*
+* Gitblit: respect Gerrit branch permissions
* Gitblit: Consider creating more Git model objects and exposing them via the JSON RPC interface to allow inspection/retrieval of Git commits, Git trees, etc from Gitblit.
* Gitblit: Blame coloring by author (issue 2)
diff --git a/gitblit.iml b/gitblit.iml index 7f21c49a..232f00d7 100644 --- a/gitblit.iml +++ b/gitblit.iml @@ -207,13 +207,13 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="org.eclipse.jgit-2.1.0.201209190230-r.jar"> + <library name="org.eclipse.jgit-2.2.0.201212191850-r.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-2.1.0.201209190230-r.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-2.2.0.201212191850-r.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-2.1.0.201209190230-r-sources.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-2.2.0.201212191850-r-sources.jar!/" /> </SOURCES> </library> </orderEntry> @@ -229,13 +229,13 @@ </library> </orderEntry> <orderEntry type="module-library"> - <library name="org.eclipse.jgit.http.server-2.1.0.201209190230-r.jar"> + <library name="org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar"> <CLASSES> - <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-2.1.0.201209190230-r.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-2.1.0.201209190230-r-sources.jar!/" /> + <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-2.2.0.201212191850-r-sources.jar!/" /> </SOURCES> </library> </orderEntry> diff --git a/resources/folder_star_16x16.png b/resources/folder_star_16x16.png Binary files differnew file mode 100644 index 00000000..ae8fdeda --- /dev/null +++ b/resources/folder_star_16x16.png diff --git a/resources/folder_star_32x32.png b/resources/folder_star_32x32.png Binary files differnew file mode 100644 index 00000000..d2a076a9 --- /dev/null +++ b/resources/folder_star_32x32.png diff --git a/resources/login_nl.mkd b/resources/login_nl.mkd new file mode 100644 index 00000000..38224c96 --- /dev/null +++ b/resources/login_nl.mkd @@ -0,0 +1,3 @@ +## Aanmelden aub
+
+Vul aub uw aanmeldgegevens in voor toegang tot deze Gitblit site.
diff --git a/resources/login_zh_CN.mkd b/resources/login_zh_CN.mkd new file mode 100644 index 00000000..09046231 --- /dev/null +++ b/resources/login_zh_CN.mkd @@ -0,0 +1,3 @@ +## 请登录 + +请输入身份信æ¯ä»¥ç™»é™†Gitblit。
\ No newline at end of file diff --git a/resources/star_16x16.png b/resources/star_16x16.png Binary files differnew file mode 100644 index 00000000..883e4dec --- /dev/null +++ b/resources/star_16x16.png diff --git a/resources/star_32x32.png b/resources/star_32x32.png Binary files differnew file mode 100644 index 00000000..92865b19 --- /dev/null +++ b/resources/star_32x32.png diff --git a/resources/welcome_nl.mkd b/resources/welcome_nl.mkd new file mode 100644 index 00000000..406ca1e0 --- /dev/null +++ b/resources/welcome_nl.mkd @@ -0,0 +1,3 @@ +## Welkom bij Gitblit
+
+Een snelle en makkelijke manier voor het hosten en bekijken van uw eigen [Git](http://www.git-scm.com) repositories.
diff --git a/resources/welcome_zh_CN.mkd b/resources/welcome_zh_CN.mkd new file mode 100644 index 00000000..f4a651ef --- /dev/null +++ b/resources/welcome_zh_CN.mkd @@ -0,0 +1,3 @@ +## 欢迎访问 Gitblit + +快速便æ·çš„ [Git](http://www.git-scm.com) 版本库管ç†æ–¹æ¡ˆã€‚ diff --git a/src/WEB-INF/web.xml b/src/WEB-INF/web.xml index 85b24d55..75ccf9bd 100644 --- a/src/WEB-INF/web.xml +++ b/src/WEB-INF/web.xml @@ -3,6 +3,30 @@ 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>
+
<!-- PARAMS -->
<!-- Gitblit Context Listener --><!-- STRIP
diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java index 068bbe3a..67ad0537 100644 --- a/src/com/gitblit/ConfigUserService.java +++ b/src/com/gitblit/ConfigUserService.java @@ -409,6 +409,10 @@ public class ConfigUserService implements IUserService { // 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);
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index d152651b..bcca8c72 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -34,7 +34,7 @@ public class Constants { // The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
- public static final String VERSION = "1.2.0-SNAPSHOT";
+ public static final String VERSION = "1.3.0-SNAPSHOT";
// The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
@@ -42,7 +42,7 @@ public class Constants { // The build script extracts this exact line so be careful editing it
// and only use A-Z a-z 0-9 .-_ in the string.
- public static final String JGIT_VERSION = "JGit 2.1.0 (201209190230-r)";
+ public static final String JGIT_VERSION = "JGit 2.2.0 (201212191850-r)";
public static final String ADMIN_ROLE = "#admin";
@@ -88,10 +88,18 @@ public class Constants { public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
+ public static final String R_GITBLIT = "refs/gitblit/";
+
+ public static final String baseFolder = "baseFolder";
+
+ public static final String baseFolder$ = "${" + baseFolder + "}";
+
+ public static final String contextFolder$ = "${contextFolder}";
+
public static String getGitBlitVersion() {
return NAME + " v" + VERSION;
}
-
+
/**
* Enumeration representing the four access restriction levels.
*/
@@ -405,6 +413,14 @@ public class Constants { return ordinal() <= COOKIE.ordinal();
}
}
+
+ public static enum AccountType {
+ LOCAL, LDAP, REDMINE;
+
+ public boolean isLocal() {
+ return this == LOCAL;
+ }
+ }
@Documented
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/com/gitblit/FederationClient.java b/src/com/gitblit/FederationClient.java index daa9bfbf..f6661394 100644 --- a/src/com/gitblit/FederationClient.java +++ b/src/com/gitblit/FederationClient.java @@ -76,7 +76,7 @@ public class FederationClient { }
// configure the Gitblit singleton for minimal, non-server operation
- GitBlit.self().configureContext(settings, false);
+ GitBlit.self().configureContext(settings, null, false);
FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
executor.run();
if (!params.isDaemon) {
diff --git a/src/com/gitblit/FileSettings.java b/src/com/gitblit/FileSettings.java index be1f44f2..3a42cad0 100644 --- a/src/com/gitblit/FileSettings.java +++ b/src/com/gitblit/FileSettings.java @@ -104,7 +104,7 @@ public class FileSettings extends IStoredSettings { }
private String regExEscape(String input) {
- return input.replace(".", "\\.");
+ return input.replace(".", "\\.").replace("$", "\\$").replace("{", "\\{");
}
/**
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index 46b0d406..6bf75d75 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -85,6 +85,9 @@ 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.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
@@ -154,8 +157,14 @@ public class GitBlit implements ServletContextListener { 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;
@@ -176,6 +185,8 @@ public class GitBlit implements ServletContextListener { private TimeZone timezone;
private FileBasedConfig projectConfigs;
+
+ private FanoutService fanoutService;
public GitBlit() {
if (gitblit == null) {
@@ -385,12 +396,8 @@ public class GitBlit implements ServletContextListener { * @return the file
*/
public static File getFileOrFolder(String fileOrFolder) {
- String openShift = System.getenv("OPENSHIFT_DATA_DIR");
- if (!StringUtils.isEmpty(openShift)) {
- // running on RedHat OpenShift
- return new File(openShift, fileOrFolder);
- }
- return new File(fileOrFolder);
+ return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
+ self().baseFolder, fileOrFolder);
}
/**
@@ -400,7 +407,7 @@ public class GitBlit implements ServletContextListener { * @return the repositories folder path
*/
public static File getRepositoriesFolder() {
- return getFileOrFolder(Keys.git.repositoriesFolder, "git");
+ return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
}
/**
@@ -410,7 +417,7 @@ public class GitBlit implements ServletContextListener { * @return the proposals folder path
*/
public static File getProposalsFolder() {
- return getFileOrFolder(Keys.federation.proposalsFolder, "proposals");
+ return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
}
/**
@@ -420,9 +427,9 @@ public class GitBlit implements ServletContextListener { * @return the Groovy scripts folder path
*/
public static File getGroovyScriptsFolder() {
- return getFileOrFolder(Keys.groovy.scriptsFolder, "groovy");
+ return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
}
-
+
/**
* Updates the list of server settings.
*
@@ -467,36 +474,48 @@ public class GitBlit implements ServletContextListener { 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() {
- return userService.supportsCredentialChanges();
+ public boolean supportsCredentialChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || 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() {
- return userService.supportsDisplayNameChanges();
+ 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() {
- return userService.supportsEmailAddressChanges();
+ 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() {
- return userService.supportsTeamMembershipChanges();
+ public boolean supportsTeamMembershipChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
}
/**
@@ -785,6 +804,10 @@ public class GitBlit implements ServletContextListener { * @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
@@ -916,14 +939,14 @@ public class GitBlit implements ServletContextListener { for (RepositoryModel model : getRepositoryModels(user)) {
if (model.isUsersPersonalRepository(username)) {
// personal repository
- model.owner = user.username;
+ 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.owner = user.username;
+ model.addOwner(user.username);
updateRepositoryModel(model.name, model, false);
}
}
@@ -1339,7 +1362,7 @@ public class GitBlit implements ServletContextListener { }
// check for updates
- Repository r = getRepository(repositoryName);
+ Repository r = getRepository(model.name);
if (r == null) {
// repository is missing
removeFromCachedRepositoryList(repositoryName);
@@ -1351,8 +1374,8 @@ public class GitBlit implements ServletContextListener { if (config.isOutdated()) {
// reload model
logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
- model = loadRepositoryModel(repositoryName);
- removeFromCachedRepositoryList(repositoryName);
+ model = loadRepositoryModel(model.name);
+ removeFromCachedRepositoryList(model.name);
addToCachedRepositoryList(model);
} else {
// update a few repository parameters
@@ -1402,7 +1425,30 @@ public class GitBlit implements ServletContextListener { }
project.title = projectConfigs.getString("project", name, "title");
project.description = projectConfigs.getString("project", name, "description");
- configs.put(name.toLowerCase(), project);
+
+ // project markdown
+ File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd");
+ if (pmkd.exists()) {
+ Date lm = new Date(pmkd.lastModified());
+ if (!projectMarkdownCache.hasCurrent(name, lm)) {
+ String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n");
+ projectMarkdownCache.updateObject(name, lm, mkd);
+ }
+ project.projectMarkdown = projectMarkdownCache.getObject(name);
+ }
+
+ // project repositories markdown
+ File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd");
+ if (rmkd.exists()) {
+ Date lm = new Date(rmkd.lastModified());
+ if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) {
+ String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n");
+ projectRepositoriesMarkdownCache.updateObject(name, lm, mkd);
+ }
+ project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name);
+ }
+
+ configs.put(name.toLowerCase(), project);
}
projectCache.clear();
projectCache.putAll(configs);
@@ -1526,6 +1572,49 @@ public class GitBlit implements ServletContextListener { }
/**
+ * 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
@@ -1561,7 +1650,7 @@ public class GitBlit implements ServletContextListener { }
RepositoryModel model = new RepositoryModel();
model.isBare = r.isBare();
- File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "git");
+ File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
if (model.isBare) {
model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
} else {
@@ -1576,7 +1665,7 @@ public class GitBlit implements ServletContextListener { if (config != null) {
model.description = getConfig(config, "description", "");
- model.owner = getConfig(config, "owner", "");
+ model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
model.useTickets = getConfig(config, "useTickets", false);
model.useDocs = getConfig(config, "useDocs", false);
model.allowForks = getConfig(config, "allowForks", true);
@@ -1624,6 +1713,7 @@ public class GitBlit implements ServletContextListener { }
model.HEAD = JGitUtils.getHEADRef(r);
model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
+ model.sparkleshareId = JGitUtils.getSparkleshareId(r);
r.close();
if (model.origin != null && model.origin.startsWith("file://")) {
@@ -1652,7 +1742,18 @@ public class GitBlit implements ServletContextListener { * @return true if the repository exists
*/
public boolean hasRepository(String repositoryName) {
- if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ return hasRepository(repositoryName, false);
+ }
+
+ /**
+ * Determines if this server has the requested repository.
+ *
+ * @param name
+ * @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());
@@ -1728,7 +1829,7 @@ public class GitBlit implements ServletContextListener { ProjectModel project = getProjectModel(userProject);
for (String repository : project.repositories) {
if (repository.startsWith(userProject)) {
- RepositoryModel model = repositoryListCache.get(repository);
+ RepositoryModel model = getRepositoryModel(repository);
if (model.originRepository.equalsIgnoreCase(origin)) {
// user has a fork
return model.name;
@@ -1749,24 +1850,53 @@ public class GitBlit implements ServletContextListener { */
public ForkModel getForkNetwork(String repository) {
if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
- // find the root
+ // 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;
}
- return null;
}
- private ForkModel getForkModel(String repository) {
+ 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);
- fork.forks.add(fm);
+ if (fm != null) {
+ fork.forks.add(fm);
+ }
}
}
return fork;
@@ -1937,7 +2067,7 @@ public class GitBlit implements ServletContextListener { 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()) {
+ if (hasRepository(repository.name)) {
throw new GitBlitException(MessageFormat.format(
"Can not create repository ''{0}'' because it already exists.",
repository.name));
@@ -2053,7 +2183,7 @@ public class GitBlit implements ServletContextListener { 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, "owner", repository.owner);
+ 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, "allowForks", repository.allowForks);
@@ -2911,12 +3041,15 @@ public class GitBlit implements ServletContextListener { *
* @param settings
*/
- public void configureContext(IStoredSettings settings, boolean startFederation) {
- logger.info("Reading configuration from " + settings.toString());
+ public void configureContext(IStoredSettings settings, File folder, boolean startFederation) {
this.settings = settings;
+ this.baseFolder = folder;
repositoriesFolder = getRepositoriesFolder();
- logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
+
+ 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);
@@ -2938,7 +3071,7 @@ public class GitBlit implements ServletContextListener { serverStatus = new ServerStatus(isGO());
if (this.userService == null) {
- String realm = settings.getString(Keys.realm.userService, "users.properties");
+ 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
@@ -2951,7 +3084,7 @@ public class GitBlit implements ServletContextListener { }
// load and cache the project metadata
- projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "projects.conf"), FS.detect());
+ projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
getProjectConfigs();
// schedule mail engine
@@ -3017,6 +3150,32 @@ public class GitBlit implements ServletContextListener { }
ContainerUtils.CVE_2007_0450.test();
+
+ // 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();
+ }
}
private void logTimezone(String type, TimeZone zone) {
@@ -3040,39 +3199,63 @@ public class GitBlit implements ServletContextListener { public void contextInitialized(ServletContextEvent contextEvent, InputStream referencePropertiesInputStream) {
servletContext = contextEvent.getServletContext();
if (settings == null) {
- // Gitblit WAR is running in a servlet container
+ // Gitblit is running in a servlet container
ServletContext context = contextEvent.getServletContext();
WebXmlSettings webxmlSettings = new WebXmlSettings(context);
-
- // gitblit.properties file located within the webapp
- String webProps = context.getRealPath("/WEB-INF/gitblit.properties");
- if (!StringUtils.isEmpty(webProps)) {
- File overrideFile = new File(webProps);
- webxmlSettings.applyOverrides(overrideFile);
- }
+ File contextFolder = new File(context.getRealPath("/"));
+ String openShift = System.getenv("OPENSHIFT_DATA_DIR");
- // gitblit.properties file located outside the deployed war
- // folder lie, for example, on RedHat OpenShift.
- File overrideFile = getFileOrFolder("gitblit.properties");
- if (!overrideFile.getPath().equals("gitblit.properties")) {
+ if (!StringUtils.isEmpty(openShift)) {
+ // Gitblit is running in OpenShift/JBoss
+ File base = new File(openShift);
+
+ // gitblit.properties setting overrides
+ File overrideFile = new File(base, "gitblit.properties");
webxmlSettings.applyOverrides(overrideFile);
- }
-
- configureContext(webxmlSettings, true);
-
- // Copy the included scripts to the configured groovy folder
- File localScripts = getFileOrFolder(Keys.groovy.scriptsFolder, "groovy");
- if (!localScripts.exists()) {
- File includedScripts = new File(context.getRealPath("/WEB-INF/groovy"));
- if (!includedScripts.equals(localScripts)) {
- try {
- com.gitblit.utils.FileUtils.copy(localScripts, includedScripts.listFiles());
- } catch (IOException e) {
- logger.error(MessageFormat.format(
- "Failed to copy included Groovy scripts from {0} to {1}",
- includedScripts, localScripts));
+
+ // Copy the included scripts to the configured groovy folder
+ File localScripts = new File(base, webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"));
+ 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.getAbsolutePath());
+
+ String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
+ File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
+ base.mkdirs();
+
+ // try to copy the data folder contents to the baseFolder
+ File localSettings = new File(base, "gitblit.properties");
+ if (!localSettings.exists()) {
+ File contextData = new File(contextFolder, "/WEB-INF/data");
+ if (!base.equals(contextData)) {
+ try {
+ com.gitblit.utils.FileUtils.copy(base, contextData.listFiles());
+ } catch (IOException e) {
+ logger.error(MessageFormat.format(
+ "Failed to copy included data from {0} to {1}",
+ contextData, base));
+ }
}
}
+
+ // delegate all config to baseFolder/gitblit.properties file
+ FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
+ configureContext(settings, base, true);
}
}
@@ -3090,6 +3273,9 @@ public class GitBlit implements ServletContextListener { scheduledExecutor.shutdownNow();
luceneExecutor.close();
gcExecutor.close();
+ if (fanoutService != null) {
+ fanoutService.stop();
+ }
}
/**
@@ -3134,15 +3320,17 @@ public class GitBlit implements ServletContextListener { // create a Gitblit repository model for the clone
RepositoryModel cloneModel = repository.cloneAs(cloneName);
// owner has REWIND/RW+ permissions
- cloneModel.owner = user.username;
+ cloneModel.addOwner(user.username);
updateRepositoryModel(cloneName, cloneModel, false);
// add the owner of the source repository to the clone's access list
- if (!StringUtils.isEmpty(repository.owner)) {
- UserModel originOwner = getUserModel(repository.owner);
- if (originOwner != null) {
- originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
- updateUserModel(originOwner.username, originOwner, false);
+ 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);
+ }
}
}
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java index 4c0e89f6..feddb93f 100644 --- a/src/com/gitblit/GitBlitServer.java +++ b/src/com/gitblit/GitBlitServer.java @@ -84,10 +84,29 @@ public class GitBlitServer { private static Logger logger;
public static void main(String... args) {
+ // 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(args);
+ jc.parse(filtered.toArray(new String[filtered.size()]));
if (params.help) {
usage(jc, null);
}
@@ -147,13 +166,13 @@ public class GitBlitServer { * Start Gitblit GO.
*/
private static void start(Params params) {
- FileSettings settings = Params.FILESETTINGS;
+ 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(" _____ _ _ _ _ _ _");
@@ -197,11 +216,10 @@ public class GitBlitServer { // conditionally configure the https connector
if (params.securePort > 0) {
- final File folder = new File(System.getProperty("user.dir"));
- File certificatesConf = new File(folder, X509Utils.CA_CONFIG);
- File serverKeyStore = new File(folder, X509Utils.SERVER_KEY_STORE);
- File serverTrustStore = new File(folder, X509Utils.SERVER_TRUST_STORE);
- File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
+ 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);
@@ -218,12 +236,12 @@ public class GitBlitServer { }
metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
- X509Utils.prepareX509Infrastructure(metadata, folder, new X509Log() {
+ X509Utils.prepareX509Infrastructure(metadata, baseFolder, new X509Log() {
@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 = 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();
@@ -277,7 +295,7 @@ public class GitBlitServer { // tempDir is where the embedded Gitblit web application is expanded and
// where Jetty creates any necessary temporary files
- File tempDir = new File(params.temp);
+ File tempDir = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.temp);
if (tempDir.exists()) {
try {
FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY);
@@ -361,7 +379,7 @@ public class GitBlitServer { // Setup the GitBlit context
GitBlit gitblit = GitBlit.self();
- gitblit.configureContext(settings, true);
+ gitblit.configureContext(settings, baseFolder, true);
rootContext.addEventListener(gitblit);
try {
@@ -532,7 +550,9 @@ public class GitBlitServer { @Parameters(separators = " ")
private static class Params {
- private static final FileSettings FILESETTINGS = new FileSettings(Constants.PROPERTIES_FILE);
+ public static String baseFolder;
+
+ private final FileSettings FILESETTINGS = new FileSettings(new File(baseFolder, Constants.PROPERTIES_FILE).getAbsolutePath());
/*
* Server parameters
@@ -551,14 +571,14 @@ public class GitBlitServer { */
@Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
public String repositoriesFolder = FILESETTINGS.getString(Keys.git.repositoriesFolder,
- "repos");
+ "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.properties");
+ "users.conf");
/*
* JETTY Parameters
@@ -567,10 +587,10 @@ public class GitBlitServer { 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, 80);
+ 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, 443);
+ 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);
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java index 2b769d4b..a0d395b1 100644 --- a/src/com/gitblit/GitFilter.java +++ b/src/com/gitblit/GitFilter.java @@ -222,7 +222,7 @@ public class GitFilter extends AccessRestrictionFilter { // create repository
RepositoryModel model = new RepositoryModel();
model.name = repository;
- model.owner = user.username;
+ model.addOwner(user.username);
model.projectPath = StringUtils.getFirstPathElement(repository);
if (model.isUsersPersonalRepository(user.username)) {
// personal repository, default to private for user
diff --git a/src/com/gitblit/GitServlet.java b/src/com/gitblit/GitServlet.java index 42d88c91..77be963f 100644 --- a/src/com/gitblit/GitServlet.java +++ b/src/com/gitblit/GitServlet.java @@ -18,17 +18,14 @@ package com.gitblit; import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.servlet.ServletConfig;
@@ -37,7 +34,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
+import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PostReceiveHook;
@@ -45,6 +44,8 @@ import org.eclipse.jgit.transport.PreReceiveHook; import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.RefFilter;
+import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.slf4j.Logger;
@@ -55,7 +56,9 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel;
import com.gitblit.utils.ClientLogger;
import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.IssueUtils;
import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.PushLogUtils;
import com.gitblit.utils.StringUtils;
/**
@@ -83,7 +86,7 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { groovyDir = GitBlit.getGroovyScriptsFolder();
try {
// set Grape root
- File grapeRoot = new File(GitBlit.getString(Keys.groovy.grapeFolder, "groovy/grape")).getAbsoluteFile();
+ File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
grapeRoot.mkdirs();
System.setProperty("grape.root", grapeRoot.getAbsolutePath());
@@ -124,9 +127,42 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { rp.setAllowDeletes(user.canDeleteRef(repository));
rp.setAllowNonFastForwards(user.canRewindRef(repository));
+ if (repository.isFrozen) {
+ throw new ServiceNotEnabledException();
+ }
+
return rp;
}
});
+
+ // override the default upload pack to exclude gitblit refs
+ setUploadPackFactory(new DefaultUploadPackFactory() {
+ @Override
+ public UploadPack create(final HttpServletRequest req, final Repository db)
+ throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+ UploadPack up = super.create(req, db);
+ RefFilter refFilter = new RefFilter() {
+ @Override
+ public Map<String, Ref> filter(Map<String, Ref> refs) {
+ // admin accounts can access all refs
+ UserModel user = GitBlit.self().authenticate(req);
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ if (user.canAdmin()) {
+ return refs;
+ }
+
+ // normal users can not clone gitblit refs
+ refs.remove(IssueUtils.GB_ISSUES);
+ refs.remove(PushLogUtils.GB_PUSHES);
+ return refs;
+ }
+ };
+ up.setRefFilter(refFilter);
+ return up;
+ }
+ });
super.init(new GitblitServletConfig(config));
}
@@ -244,9 +280,6 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { .getName(), cmd.getResult(), cmd.getMessage()));
}
}
-
- // Experimental
- // runNativeScript(rp, "hooks/pre-receive", commands);
}
/**
@@ -260,12 +293,11 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { logger.info("skipping post-receive hooks, no refs created, updated, or removed");
return;
}
- RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
- Set<String> scripts = new LinkedHashSet<String>();
- scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
- scripts.addAll(repository.postReceiveScripts);
+
UserModel user = getUserModel(rp);
- runGroovy(repository, user, commands, rp, scripts);
+ RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
+
+ // log ref changes
for (ReceiveCommand cmd : commands) {
if (Result.OK.equals(cmd.getResult())) {
// add some logging for important ref changes
@@ -284,9 +316,20 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { }
}
}
+
+ // update push log
+ try {
+ PushLogUtils.updatePushLog(user, rp.getRepository(), commands);
+ logger.info(MessageFormat.format("{0} push log updated", repository.name));
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
+ }
- // Experimental
- // runNativeScript(rp, "hooks/post-receive", commands);
+ // run Groovy hook scripts
+ Set<String> scripts = new LinkedHashSet<String>();
+ scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
+ scripts.addAll(repository.postReceiveScripts);
+ runGroovy(repository, user, commands, rp, scripts);
}
/**
@@ -358,76 +401,5 @@ public class GitServlet extends org.eclipse.jgit.http.server.GitServlet { }
}
}
-
- /**
- * Runs the native push hook script.
- *
- * http://book.git-scm.com/5_git_hooks.html
- * http://longair.net/blog/2011/04/09/missing-git-hooks-documentation/
- *
- * @param rp
- * @param script
- * @param commands
- */
- @SuppressWarnings("unused")
- protected void runNativeScript(ReceivePack rp, String script,
- Collection<ReceiveCommand> commands) {
-
- Repository repository = rp.getRepository();
- File scriptFile = new File(repository.getDirectory(), script);
-
- int resultCode = 0;
- if (scriptFile.exists()) {
- try {
- logger.debug("executing " + scriptFile);
- Process process = Runtime.getRuntime().exec(scriptFile.getAbsolutePath(), null,
- repository.getDirectory());
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- process.getInputStream()));
- BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
- process.getOutputStream()));
- for (ReceiveCommand command : commands) {
- switch (command.getType()) {
- case UPDATE:
- // updating a ref
- writer.append(MessageFormat.format("{0} {1} {2}\n", command.getOldId()
- .getName(), command.getNewId().getName(), command.getRefName()));
- break;
- case CREATE:
- // new ref
- // oldrev hard-coded to 40? weird.
- writer.append(MessageFormat.format("40 {0} {1}\n", command.getNewId()
- .getName(), command.getRefName()));
- break;
- }
- }
- resultCode = process.waitFor();
-
- // read and buffer stdin
- // this is supposed to be piped back to the git client.
- // not sure how to do that right now.
- StringBuilder sb = new StringBuilder();
- String line = null;
- while ((line = reader.readLine()) != null) {
- sb.append(line).append('\n');
- }
- logger.debug(sb.toString());
- } catch (Throwable e) {
- resultCode = -1;
- logger.error(
- MessageFormat.format("Failed to execute {0}",
- scriptFile.getAbsolutePath()), e);
- }
- }
-
- // reject push
- if (resultCode != 0) {
- for (ReceiveCommand command : commands) {
- command.setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format(
- "Native script {0} rejected push or failed",
- scriptFile.getAbsolutePath()));
- }
- }
- }
}
}
diff --git a/src/com/gitblit/GitblitUserService.java b/src/com/gitblit/GitblitUserService.java index 141ad8f1..37f22b01 100644 --- a/src/com/gitblit/GitblitUserService.java +++ b/src/com/gitblit/GitblitUserService.java @@ -23,9 +23,11 @@ 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
@@ -48,6 +50,8 @@ import com.gitblit.utils.DeepCopier; public class GitblitUserService implements IUserService {
protected IUserService serviceImpl;
+
+ protected final String ExternalAccount = "#externalAccount";
private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class);
@@ -56,7 +60,7 @@ public class GitblitUserService implements IUserService { @Override
public void setup(IStoredSettings settings) {
- File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "users.conf");
+ File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
serviceImpl = createUserService(realmFile);
logger.info("GUS delegating to " + serviceImpl.toString());
}
@@ -144,12 +148,16 @@ public class GitblitUserService implements IUserService { @Override
public UserModel authenticate(char[] cookie) {
- return serviceImpl.authenticate(cookie);
+ UserModel user = serviceImpl.authenticate(cookie);
+ setAccountType(user);
+ return user;
}
@Override
public UserModel authenticate(String username, char[] password) {
- return serviceImpl.authenticate(username, password);
+ UserModel user = serviceImpl.authenticate(username, password);
+ setAccountType(user);
+ return user;
}
@Override
@@ -159,7 +167,9 @@ public class GitblitUserService implements IUserService { @Override
public UserModel getUserModel(String username) {
- return serviceImpl.getUserModel(username);
+ UserModel user = serviceImpl.getUserModel(username);
+ setAccountType(user);
+ return user;
}
@Override
@@ -174,8 +184,8 @@ public class GitblitUserService implements IUserService { @Override
public boolean updateUserModel(String username, UserModel model) {
- if (supportsCredentialChanges()) {
- if (!supportsTeamMembershipChanges()) {
+ if (model.isLocalAccount() || supportsCredentialChanges()) {
+ if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
// teams are externally controlled - copy from original model
UserModel existingModel = getUserModel(username);
@@ -188,7 +198,7 @@ public class GitblitUserService implements IUserService { if (model.username.equals(username)) {
// passwords are not persisted by the backing user service
model.password = null;
- if (!supportsTeamMembershipChanges()) {
+ if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
// teams are externally controlled- copy from original model
UserModel existingModel = getUserModel(username);
@@ -218,7 +228,11 @@ public class GitblitUserService implements IUserService { @Override
public List<UserModel> getAllUsers() {
- return serviceImpl.getAllUsers();
+ List<UserModel> users = serviceImpl.getAllUsers();
+ for (UserModel user : users) {
+ setAccountType(user);
+ }
+ return users;
}
@Override
@@ -300,4 +314,25 @@ public class GitblitUserService implements IUserService { 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)
+ && !ExternalAccount.equalsIgnoreCase(user.password)
+ && !"StoredInLDAP".equalsIgnoreCase(user.password)) {
+ user.accountType = AccountType.LOCAL;
+ } else {
+ user.accountType = getAccountType();
+ }
+ }
+ }
+
+ protected AccountType getAccountType() {
+ return AccountType.LOCAL;
+ }
}
diff --git a/src/com/gitblit/LdapUserService.java b/src/com/gitblit/LdapUserService.java index 9ce18f6d..595c6589 100644 --- a/src/com/gitblit/LdapUserService.java +++ b/src/com/gitblit/LdapUserService.java @@ -25,6 +25,7 @@ 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.ArrayUtils;
@@ -50,9 +51,9 @@ import com.unboundid.util.ssl.TrustAllTrustManager; public class LdapUserService extends GitblitUserService {
public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
-
- private IStoredSettings settings;
+ private IStoredSettings settings;
+
public LdapUserService() {
super();
}
@@ -60,7 +61,7 @@ public class LdapUserService extends GitblitUserService { @Override
public void setup(IStoredSettings settings) {
this.settings = settings;
- String file = settings.getString(Keys.realm.ldap.backingUserService, "users.conf");
+ String file = settings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
File realmFile = GitBlit.getFileOrFolder(file);
serviceImpl = createUserService(realmFile);
@@ -155,9 +156,19 @@ public class LdapUserService extends GitblitUserService { 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();
@@ -239,7 +250,8 @@ public class LdapUserService extends GitblitUserService { setAdminAttribute(user);
// Don't want visibility into the real password, make up a dummy
- user.password = "StoredInLDAP";
+ user.password = ExternalAccount;
+ user.accountType = getAccountType();
// Get full name Attribute
String displayName = settings.getString(Keys.realm.ldap.displayName, "");
diff --git a/src/com/gitblit/PagesServlet.java b/src/com/gitblit/PagesServlet.java index ad9276b4..91f25b70 100644 --- a/src/com/gitblit/PagesServlet.java +++ b/src/com/gitblit/PagesServlet.java @@ -170,7 +170,7 @@ public class PagesServlet extends HttpServlet { content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes(
Constants.ENCODING);
} else {
- content = JGitUtils.getByteContent(r, tree, resource);
+ content = JGitUtils.getByteContent(r, tree, resource, false);
}
response.setContentType(contentType);
} catch (Exception e) {
diff --git a/src/com/gitblit/RedmineUserService.java b/src/com/gitblit/RedmineUserService.java index b890f21b..9d571e37 100644 --- a/src/com/gitblit/RedmineUserService.java +++ b/src/com/gitblit/RedmineUserService.java @@ -9,7 +9,9 @@ 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;
@@ -45,7 +47,7 @@ public class RedmineUserService extends GitblitUserService { public void setup(IStoredSettings settings) {
this.settings = settings;
- String file = settings.getString(Keys.realm.redmine.backingUserService, "users.conf");
+ String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
File realmFile = GitBlit.getFileOrFolder(file);
serviceImpl = createUserService(realmFile);
@@ -71,54 +73,110 @@ public class RedmineUserService extends GitblitUserService { public boolean supportsTeamMembershipChanges() {
return false;
}
+
+ @Override
+ protected AccountType getAccountType() {
+ return AccountType.REDMINE;
+ }
@Override
public UserModel authenticate(String username, char[] password) {
- String urlText = this.settings.getString(Keys.realm.redmine.url, "");
- if (!urlText.endsWith("/")) {
- urlText.concat("/");
- }
- String apiKey = String.valueOf(password);
+ if (isLocalAccount(username)) {
+ // local account, bypass Redmine authentication
+ return super.authenticate(username, password);
+ }
+ String jsonString = null;
try {
- String jsonString = getCurrentUserAsJson(urlText, apiKey);
-
- RedmineCurrent current = new Gson().fromJson(jsonString, RedmineCurrent.class);
- String login = current.user.login;
-
- boolean canAdmin = true;
- // non admin user can not get login name
- if (StringUtils.isEmpty(login)) {
- canAdmin = false;
- login = current.user.mail;
- }
-
- UserModel userModel = new UserModel(login);
- userModel.canAdmin = canAdmin;
- userModel.displayName = current.user.firstname + " " + current.user.lastname;
- userModel.emailAddress = current.user.mail;
- userModel.cookie = StringUtils.getSHA1(userModel.username + new String(password));
-
- return userModel;
- } catch (IOException e) {
- logger.error("authenticate", e);
+ // 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));
}
- return null;
+
+ // update user attributes from Redmine
+ user.accountType = getAccountType();
+ user.displayName = current.user.firstname + " " + current.user.lastname;
+ user.emailAddress = current.user.mail;
+ user.password = ExternalAccount;
+ 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 url, String apiKey) throws IOException {
+ private String getCurrentUserAsJson(String username, char [] password) throws IOException {
if (testingJson != null) { // for testing
return testingJson;
}
- String apiUrl = url + "users/current.json?key=" + apiKey;
- HttpURLConnection http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
+ String url = this.settings.getString(Keys.realm.redmine.url, "");
+ if (!url.endsWith("/")) {
+ 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
@@ -126,5 +184,4 @@ public class RedmineUserService extends GitblitUserService { public void setTestingCurrentUserAsJson(String json) {
this.testingJson = json;
}
-
}
diff --git a/src/com/gitblit/RobotsTxtServlet.java b/src/com/gitblit/RobotsTxtServlet.java index c142be0d..d66ebf43 100644 --- a/src/com/gitblit/RobotsTxtServlet.java +++ b/src/com/gitblit/RobotsTxtServlet.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.StringUtils;
/**
* Handles requests for robots.txt
@@ -55,13 +54,10 @@ public class RobotsTxtServlet extends HttpServlet { protected void processRequest(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
java.io.IOException {
- String robotstxt = GitBlit.getString(Keys.web.robots.txt, null);
+ File file = GitBlit.getFileOrFolder(Keys.web.robots.txt, null);
String content = "";
- if (!StringUtils.isEmpty(robotstxt)) {
- File robotsfile = new File(robotstxt);
- if (robotsfile.exists()) {
- content = FileUtils.readContent(robotsfile, "\n");
- }
+ if (file.exists()) {
+ content = FileUtils.readContent(file, "\n");
}
response.getWriter().append(content);
}
diff --git a/src/com/gitblit/authority/GitblitAuthority.java b/src/com/gitblit/authority/GitblitAuthority.java index 909831fe..c3d81848 100644 --- a/src/com/gitblit/authority/GitblitAuthority.java +++ b/src/com/gitblit/authority/GitblitAuthority.java @@ -138,6 +138,21 @@ public class GitblitAuthority extends JFrame implements X509Log { 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 {
@@ -145,7 +160,7 @@ public class GitblitAuthority extends JFrame implements X509Log { } catch (Exception e) {
}
GitblitAuthority authority = new GitblitAuthority();
- authority.initialize();
+ authority.initialize(baseFolder);
authority.setLocationRelativeTo(null);
authority.setVisible(true);
}
@@ -158,7 +173,7 @@ public class GitblitAuthority extends JFrame implements X509Log { defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel);
}
- public void initialize() {
+ public void initialize(String baseFolder) {
setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
setTitle("Gitblit Certificate Authority v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")");
setContentPane(getUI());
@@ -174,10 +189,10 @@ public class GitblitAuthority extends JFrame implements X509Log { }
});
- setSizeAndPosition();
-
- File folder = new File(System.getProperty("user.dir"));
+ File folder = new File(baseFolder).getAbsoluteFile();
load(folder);
+
+ setSizeAndPosition();
}
private void setSizeAndPosition() {
@@ -230,7 +245,7 @@ public class GitblitAuthority extends JFrame implements X509Log { }
private StoredConfig getConfig() throws IOException, ConfigInvalidException {
- File configFile = new File(System.getProperty("user.dir"), X509Utils.CA_CONFIG);
+ File configFile = new File(folder, X509Utils.CA_CONFIG);
FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
config.load();
return config;
@@ -243,30 +258,31 @@ public class GitblitAuthority extends JFrame implements X509Log { }
gitblitSettings = new FileSettings(file.getAbsolutePath());
mail = new MailExecutor(gitblitSettings);
- String us = gitblitSettings.getString(Keys.realm.userService, "users.conf");
+ 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, "users.conf");
+ us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
} else if (us.equals("com.gitblit.LdapUserService")) {
- us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "users.conf");
+ us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
}
}
if (us.endsWith(".conf")) {
- service = new ConfigUserService(new File(us));
+ service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
} else {
throw new RuntimeException("Unsupported user service: " + us);
}
- service = new ConfigUserService(new File(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 {
diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java index 19d80e78..3a9ed751 100644 --- a/src/com/gitblit/build/Build.java +++ b/src/com/gitblit/build/Build.java @@ -109,6 +109,20 @@ public class Build { downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.RUNTIME);
downloadFromApache(MavenObject.XZ, BuildType.RUNTIME);
+ //needed for selenium ui tests
+ downloadFromApacheToExtSelenium(MavenObject.SEL_API, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_FF, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_JAVA, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_REMOTE, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_SUPPORT, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.GUAVA, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.JSON, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_EXEC, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCLIENT, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCORE, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPMIME, BuildType.RUNTIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_LOGGING, BuildType.RUNTIME);
+
downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
}
@@ -148,6 +162,20 @@ public class Build { downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.COMPILETIME);
downloadFromApache(MavenObject.XZ, BuildType.COMPILETIME);
+ //needed for selenium ui tests
+ downloadFromApacheToExtSelenium(MavenObject.SEL_API, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_FF, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_JAVA, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_REMOTE, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.SEL_SUPPORT, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.GUAVA, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.JSON, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_EXEC, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCLIENT, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPCORE, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.HTTPMIME, BuildType.COMPILETIME);
+ downloadFromApacheToExtSelenium(MavenObject.COMMONS_LOGGING, BuildType.COMPILETIME);
+
downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
@@ -217,7 +245,7 @@ public class Build { Properties properties = new Properties();
FileInputStream is = null;
try {
- is = new FileInputStream(Constants.PROPERTIES_FILE);
+ is = new FileInputStream(new File("distrib", Constants.PROPERTIES_FILE));
properties.load(is);
} catch (Throwable t) {
t.printStackTrace();
@@ -398,7 +426,7 @@ public class Build { * the maven object to download.
* @return
*/
- private static List<File> downloadFromMaven(String mavenRoot, MavenObject mo, BuildType type) {
+ private static List<File> downloadFromMaven(String mavenRoot, MavenObject mo, BuildType type, String targetFolder) {
List<File> downloads = new ArrayList<File>();
String[] jars = { "" };
if (BuildType.RUNTIME.equals(type)) {
@@ -407,9 +435,9 @@ public class Build { jars = new String[] { "-sources" };
}
for (String jar : jars) {
- File targetFile = mo.getLocalFile("ext", jar);
+ File targetFile = mo.getLocalFile(targetFolder, jar);
if ("-sources".equals(jar)) {
- File relocated = new File("ext/src", targetFile.getName());
+ File relocated = new File(targetFolder+"/src", targetFile.getName());
if (targetFile.exists()) {
// move -sources jar to ext/src folder
targetFile.renameTo(relocated);
@@ -502,6 +530,31 @@ public class Build { return downloads;
}
+ /**
+ * Download a file from the official Apache Maven repository.
+ *
+ * @param mo
+ * the maven object to download.
+ * @return
+ */
+ private static List<File> downloadFromApacheToExtSelenium(MavenObject mo,
+ BuildType type) {
+ return downloadFromMaven("http://repo1.maven.org/maven2/", mo, type,
+ "ext/seleniumhq");
+ }
+
+ /**
+ * Download a file from the official Apache Maven repository.
+ *
+ * @param mo
+ * the maven object to download.
+ * @return
+ */
+ private static List<File> downloadFromMaven(String mavenRoot,
+ MavenObject mo, BuildType type) {
+ return downloadFromMaven(mavenRoot, mo, type, "ext");
+ }
+
private static void removeObsoleteArtifacts(final MavenObject mo, final BuildType type, File folder) {
File [] removals = folder.listFiles(new FilenameFilter() {
@Override
@@ -675,17 +728,17 @@ public class Build { "");
public static final MavenObject JGIT = new MavenObject(
- "JGit", "org/eclipse/jgit", "org.eclipse.jgit", "2.1.0.201209190230-r",
+ "JGit", "org/eclipse/jgit", "org.eclipse.jgit", "2.2.0.201212191850-r",
1600000, 1565000, 3460000,
- "5e7296d21645a479a1054fc96f3ec8469cede137",
- "5f492aaeae1beda2a31d1efa182f5d34e76d7b77",
+ "97d0761b9dd618d1f9f6c16c35c3ddf045ba536c",
+ "08dcf9546f4d61e1b8a50df5da5513006023b64b",
"");
public static final MavenObject JGIT_HTTP = new MavenObject(
- "JGit", "org/eclipse/jgit", "org.eclipse.jgit.http.server", "2.1.0.201209190230-r",
+ "JGit", "org/eclipse/jgit", "org.eclipse.jgit.http.server", "2.2.0.201212191850-r",
68000, 62000, 110000,
- "0bd9e5801c246d6f8ad9268d18c45ca9915f9a50",
- "210c434c38ddcf2126af250018d5845ea41ff502",
+ "8ad4fc4fb9529d645249bb46ad7e54d98436cb65",
+ "3385cf294957d1d34c1270b468853aea347b36ca",
"");
public static final MavenObject JSCH = new MavenObject(
@@ -796,6 +849,59 @@ public class Build { "ecff5cb8b1189514c9d1d8d68eb77ac372e000c9",
"f95e32a5d2dd8da643c4419814415b9704312993", "");
+ public static final MavenObject SEL_JAVA = new MavenObject(
+ "selenium-java", "org/seleniumhq/selenium", "selenium-java",
+ "2.28.0", 984098, 0, 0,
+ "7606286989ac9cb942cc206d975ffe187c18d605", "4ede08d293dc153989a337cd0d31d26421433af5", "");
+
+ public static final MavenObject SEL_API = new MavenObject(
+ "selenium-api", "org/seleniumhq/selenium", "selenium-api",
+ "2.28.0", 984098, 0, 0,
+ "c4044c40fff65cd25135a5f443638a2b1ccaeac5", "35fc6ec0804ae32b16a56627e69bdcb69995c515", "");
+
+ public static final MavenObject SEL_REMOTE = new MavenObject(
+ "selenium-remote-driver", "org/seleniumhq/selenium",
+ "selenium-remote-driver", "2.28.0", 984098, 0, 0,
+ "c67f97cd94e02afec92b0ac881844febb4fc90be", "51a9c30de3c8c203cb7a474a10842443005a5fb4", "");
+ public static final MavenObject SEL_SUPPORT = new MavenObject(
+ "selenium-support", "org/seleniumhq/selenium",
+ "selenium-support", "2.28.0", 984098, 0, 0,
+ "caf68d6310425f583bc592c08e43066b35eb94f6", "ce3831a601f5f50fda2f4604decde409b6c735a7", "");
+ public static final MavenObject SEL_FF = new MavenObject(
+ "selenium-firefox-driver", "org/seleniumhq/selenium",
+ "selenium-firefox-driver", "2.28.0", 984098, 0, 0,
+ "a7c34e45dba39e65467b900aa67611aaa039692d", "aa8cd5fb49ca75a53d5b143406ea3d81ab3eddfd", "");
+
+ public static final MavenObject GUAVA = new MavenObject("guava",
+ "com/google/guava", "guava", "12.0", 984098, 0, 0,
+ "5bc66dd95b79db1e437eb08adba124a3e4088dc0", "f8b98e61865bed3c39b978ee3bf5c7fb990c4032", "");
+
+ public static final MavenObject JSON = new MavenObject("json",
+ "org/json", "json", "20080701", 984098, 0, 0,
+ "d652f102185530c93b66158b1859f35d45687258", "71bd54221e701df9d112bf9ba2918e13b0671f3a", "");
+
+ public static final MavenObject COMMONS_EXEC = new MavenObject(
+ "commons-exec", "org/apache/commons", "commons-exec", "1.1",
+ 984098, 0, 0, "07dfdf16fade726000564386825ed6d911a44ba1", "f60bea898e18b308099862e8634d589b06a8b0be",
+ "");
+
+ public static final MavenObject HTTPCORE = new MavenObject("httpcore",
+ "org/apache/httpcomponents", "httpcore", "4.2.1", 984098, 0, 0,
+ "2d503272bf0a8b5f92d64db78b4ba9abbaccc6fd", "3f6caf5334fa83607b82e2f32dd128a9d8a0ea5e", "");
+
+ public static final MavenObject HTTPMIME = new MavenObject("httpmime",
+ "org/apache/httpcomponents", "httpmime", "4.2.1", 984098, 0, 0,
+ "7c772bace9aa31a728c39a88c6ff66a7cd177e89", "", "4e453843ae47f1c2d70e2eb2c13c037de4b614c4");
+
+ public static final MavenObject HTTPCLIENT = new MavenObject(
+ "httpclient", "org/apache/httpcomponents", "httpclient",
+ "4.2.1", 984098, 0, 0,
+ "b69bd03af60bf487b3ae1209a644ecac587bf6fc", "6b27312b9c28b59aaeb6c21f3490045690c703d3", "");
+ public static final MavenObject COMMONS_LOGGING = new MavenObject(
+ "commons-logging", "commons-logging", "commons-logging",
+ "1.1.1", 984098, 0, 0,
+ "5043bfebc3db072ed80fbd362e7caf00e885d8ae", "f3f156cbff0e0fb0d64bfce31a352cce4a33bc19", "");
+
public final String name;
public final String group;
public final String artifact;
diff --git a/src/com/gitblit/build/BuildWebXml.java b/src/com/gitblit/build/BuildWebXml.java index 4fcc6e97..49a12ab2 100644 --- a/src/com/gitblit/build/BuildWebXml.java +++ b/src/com/gitblit/build/BuildWebXml.java @@ -60,44 +60,44 @@ public class BuildWebXml { }
private static void generateWebXml(Params params) throws Exception {
+ StringBuilder parameters = new StringBuilder();
// Read the current Gitblit properties
- BufferedReader propertiesReader = new BufferedReader(new FileReader(new File(
- params.propertiesFile)));
-
- Vector<Setting> settings = new Vector<Setting>();
- List<String> comments = new ArrayList<String>();
- String line = null;
- while ((line = propertiesReader.readLine()) != null) {
- if (line.length() == 0) {
- comments.clear();
- } else {
- if (line.charAt(0) == '#') {
- if (line.length() > 1) {
- comments.add(line.substring(1).trim());
- }
+ if (params.propertiesFile != null) {
+ BufferedReader propertiesReader = new BufferedReader(new FileReader(new File(
+ params.propertiesFile)));
+
+ Vector<Setting> settings = new Vector<Setting>();
+ List<String> comments = new ArrayList<String>();
+ String line = null;
+ while ((line = propertiesReader.readLine()) != null) {
+ if (line.length() == 0) {
+ comments.clear();
} else {
- String[] kvp = line.split("=", 2);
- String key = kvp[0].trim();
- if (!skipKey(key)) {
- Setting s = new Setting(key, kvp[1].trim(), comments);
- settings.add(s);
+ if (line.charAt(0) == '#') {
+ if (line.length() > 1) {
+ comments.add(line.substring(1).trim());
+ }
+ } else {
+ String[] kvp = line.split("=", 2);
+ String key = kvp[0].trim();
+ if (!skipKey(key)) {
+ Setting s = new Setting(key, kvp[1].trim(), comments);
+ settings.add(s);
+ }
+ comments.clear();
}
- comments.clear();
}
}
- }
- propertiesReader.close();
+ propertiesReader.close();
- StringBuilder parameters = new StringBuilder();
-
- for (Setting setting : settings) {
- for (String comment : setting.comments) {
- parameters.append(MessageFormat.format(COMMENT_PATTERN, comment));
+ for (Setting setting : settings) {
+ for (String comment : setting.comments) {
+ parameters.append(MessageFormat.format(COMMENT_PATTERN, comment));
+ }
+ parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name,
+ StringUtils.escapeForHtml(setting.value, false)));
}
- parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name,
- StringUtils.escapeForHtml(setting.value, false)));
}
-
// Read the prototype web.xml file
File webxml = new File(params.sourceFile);
char[] buffer = new char[(int) webxml.length()];
@@ -150,11 +150,11 @@ public class BuildWebXml { @Parameter(names = { "--sourceFile" }, description = "Source web.xml file", required = true)
public String sourceFile;
- @Parameter(names = { "--propertiesFile" }, description = "Properties settings file", required = true)
+ @Parameter(names = { "--propertiesFile" }, description = "Properties settings file")
public String propertiesFile;
@Parameter(names = { "--destinationFile" }, description = "Destination web.xml file", required = true)
public String destinationFile;
-
+
}
}
diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java index 6f9ed525..8851de43 100644 --- a/src/com/gitblit/client/EditRepositoryDialog.java +++ b/src/com/gitblit/client/EditRepositoryDialog.java @@ -38,7 +38,6 @@ import java.util.Set; import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
-import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
@@ -117,7 +116,7 @@ public class EditRepositoryDialog extends JDialog { private JComboBox federationStrategy;
- private JComboBox ownerField;
+ private JPalette<String> ownersPalette;
private JComboBox headRefField;
@@ -126,7 +125,7 @@ public class EditRepositoryDialog extends JDialog { private JTextField gcThreshold;
private JComboBox maxActivityCommits;
-
+
private RegistrantPermissionsPanel usersPalette;
private JPalette<String> setsPalette;
@@ -207,7 +206,7 @@ public class EditRepositoryDialog extends JDialog { gcThreshold = new JTextField(8);
gcThreshold.setText(anRepository.gcThreshold);
- ownerField = new JComboBox();
+ ownersPalette = new JPalette<String>(true);
useTickets = new JCheckBox(Translation.get("gb.useTicketsDescription"),
anRepository.useTickets);
@@ -334,10 +333,10 @@ public class EditRepositoryDialog extends JDialog { usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
- JPanel northFieldsPanel = new JPanel(new GridLayout(0, 1, 0, 5));
- northFieldsPanel.add(newFieldPanel(Translation.get("gb.owner"), ownerField));
+ JPanel northFieldsPanel = new JPanel(new BorderLayout(0, 5));
+ northFieldsPanel.add(newFieldPanel(Translation.get("gb.owners"), ownersPalette), BorderLayout.NORTH);
northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
- accessRestriction), BorderLayout.NORTH);
+ accessRestriction), BorderLayout.CENTER);
JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
northAccessPanel.add(northFieldsPanel, BorderLayout.NORTH);
@@ -556,8 +555,8 @@ public class EditRepositoryDialog extends JDialog { repository.name = rname;
repository.description = descriptionField.getText();
- repository.owner = ownerField.getSelectedItem() == null ? null
- : ownerField.getSelectedItem().toString();
+ repository.owners.clear();
+ repository.owners.addAll(ownersPalette.getSelections());
repository.HEAD = headRefField.getSelectedItem() == null ? null
: headRefField.getSelectedItem().toString();
repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();
@@ -629,11 +628,8 @@ public class EditRepositoryDialog extends JDialog { this.allowNamed.setSelected(!authenticated);
}
- public void setUsers(String owner, List<String> all, List<RegistrantAccessPermission> permissions) {
- ownerField.setModel(new DefaultComboBoxModel(all.toArray()));
- if (!StringUtils.isEmpty(owner)) {
- ownerField.setSelectedItem(owner);
- }
+ public void setUsers(List<String> owners, List<String> all, List<RegistrantAccessPermission> permissions) {
+ ownersPalette.setObjects(all, owners);
usersPalette.setObjects(all, permissions);
}
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index 1101cd60..cc7d58a6 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -162,7 +162,7 @@ public class GitblitClient implements Serializable { }
public boolean isOwner(RepositoryModel model) {
- return account != null && account.equalsIgnoreCase(model.owner);
+ return model.isOwner(account);
}
public String getURL(String action, String repository, String objectId) {
diff --git a/src/com/gitblit/client/IndicatorsRenderer.java b/src/com/gitblit/client/IndicatorsRenderer.java index 59ce6dd1..44b39d01 100644 --- a/src/com/gitblit/client/IndicatorsRenderer.java +++ b/src/com/gitblit/client/IndicatorsRenderer.java @@ -55,6 +55,8 @@ public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Ser private final ImageIcon federatedIcon;
private final ImageIcon forkIcon;
+
+ private final ImageIcon sparkleshareIcon;
public IndicatorsRenderer() {
super(new FlowLayout(FlowLayout.RIGHT, 1, 0));
@@ -67,6 +69,7 @@ public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Ser frozenIcon = new ImageIcon(getClass().getResource("/cold_16x16.png"));
federatedIcon = new ImageIcon(getClass().getResource("/federated_16x16.png"));
forkIcon = new ImageIcon(getClass().getResource("/commit_divide_16x16.png"));
+ sparkleshareIcon = new ImageIcon(getClass().getResource("/star_16x16.png"));
}
@Override
@@ -80,6 +83,11 @@ public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Ser if (value instanceof RepositoryModel) {
StringBuilder tooltip = new StringBuilder();
RepositoryModel model = (RepositoryModel) value;
+ if (model.isSparkleshared()) {
+ JLabel icon = new JLabel(sparkleshareIcon);
+ tooltip.append(Translation.get("gb.isSparkleshared")).append("<br/>");
+ add(icon);
+ }
if (model.isFork()) {
JLabel icon = new JLabel(forkIcon);
tooltip.append(Translation.get("gb.isFork")).append("<br/>");
diff --git a/src/com/gitblit/client/JPalette.java b/src/com/gitblit/client/JPalette.java index 4ead099e..a0c2b258 100644 --- a/src/com/gitblit/client/JPalette.java +++ b/src/com/gitblit/client/JPalette.java @@ -144,7 +144,7 @@ public class JPalette<T> extends JPanel { table.getColumn(table.getColumnName(0)).setCellRenderer(nameRenderer);
JScrollPane jsp = new JScrollPane(table);
- jsp.setPreferredSize(new Dimension(225, 175));
+ jsp.setPreferredSize(new Dimension(225, 160));
JPanel panel = new JPanel(new BorderLayout());
JLabel jlabel = new JLabel(label);
jlabel.setFont(jlabel.getFont().deriveFont(Font.BOLD));
diff --git a/src/com/gitblit/client/RepositoriesPanel.java b/src/com/gitblit/client/RepositoriesPanel.java index 769d33b8..64bde9b8 100644 --- a/src/com/gitblit/client/RepositoriesPanel.java +++ b/src/com/gitblit/client/RepositoriesPanel.java @@ -49,8 +49,8 @@ import javax.swing.table.TableRowSorter; import com.gitblit.Constants;
import com.gitblit.Constants.RpcRequest;
import com.gitblit.Keys;
-import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.FeedModel;
+import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.utils.StringUtils;
@@ -453,7 +453,7 @@ public abstract class RepositoriesPanel extends JPanel { dialog.setLocationRelativeTo(RepositoriesPanel.this);
List<String> usernames = gitblit.getUsernames();
List<RegistrantAccessPermission> members = gitblit.getUserAccessPermissions(repository);
- dialog.setUsers(repository.owner, usernames, members);
+ dialog.setUsers(new ArrayList<String>(repository.owners), usernames, members);
dialog.setTeams(gitblit.getTeamnames(), gitblit.getTeamAccessPermissions(repository));
dialog.setRepositories(gitblit.getRepositories());
dialog.setFederationSets(gitblit.getFederationSets(), repository.federationSets);
diff --git a/src/com/gitblit/client/RepositoriesTableModel.java b/src/com/gitblit/client/RepositoriesTableModel.java index c3eaf6e5..6b295a4b 100644 --- a/src/com/gitblit/client/RepositoriesTableModel.java +++ b/src/com/gitblit/client/RepositoriesTableModel.java @@ -23,6 +23,7 @@ import java.util.List; import javax.swing.table.AbstractTableModel;
import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.ArrayUtils;
/**
* Table model of a list of repositories.
@@ -111,7 +112,7 @@ public class RepositoriesTableModel extends AbstractTableModel { case Description:
return model.description;
case Owner:
- return model.owner;
+ return ArrayUtils.toString(model.owners);
case Indicators:
return model;
case Last_Change:
diff --git a/src/com/gitblit/client/UsersPanel.java b/src/com/gitblit/client/UsersPanel.java index e14c0010..c53a5791 100644 --- a/src/com/gitblit/client/UsersPanel.java +++ b/src/com/gitblit/client/UsersPanel.java @@ -112,8 +112,8 @@ public abstract class UsersPanel extends JPanel { String name = table.getColumnName(UsersTableModel.Columns.Name.ordinal());
table.getColumn(name).setCellRenderer(nameRenderer);
- int w = 125;
- name = table.getColumnName(UsersTableModel.Columns.AccessLevel.ordinal());
+ int w = 130;
+ name = table.getColumnName(UsersTableModel.Columns.Type.ordinal());
table.getColumn(name).setMinWidth(w);
table.getColumn(name).setMaxWidth(w);
name = table.getColumnName(UsersTableModel.Columns.Teams.ordinal());
diff --git a/src/com/gitblit/client/UsersTableModel.java b/src/com/gitblit/client/UsersTableModel.java index b8ce45d4..439d5afb 100644 --- a/src/com/gitblit/client/UsersTableModel.java +++ b/src/com/gitblit/client/UsersTableModel.java @@ -36,7 +36,7 @@ public class UsersTableModel extends AbstractTableModel { List<UserModel> list;
enum Columns {
- Name, Display_Name, AccessLevel, Teams, Repositories;
+ Name, Display_Name, Type, Teams, Repositories;
@Override
public String toString() {
@@ -71,8 +71,8 @@ public class UsersTableModel extends AbstractTableModel { return Translation.get("gb.name");
case Display_Name:
return Translation.get("gb.displayName");
- case AccessLevel:
- return Translation.get("gb.accessLevel");
+ case Type:
+ return Translation.get("gb.type");
case Teams:
return Translation.get("gb.teamMemberships");
case Repositories:
@@ -101,11 +101,18 @@ public class UsersTableModel extends AbstractTableModel { return model.username;
case Display_Name:
return model.displayName;
- case AccessLevel:
+ case Type:
+ StringBuilder sb = new StringBuilder();
+ if (model.accountType != null) {
+ sb.append(model.accountType.name());
+ }
if (model.canAdmin()) {
- return "administrator";
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append("admin");
}
- return "";
+ return sb.toString();
case Teams:
return (model.teams == null || model.teams.size() == 0) ? "" : String
.valueOf(model.teams.size());
diff --git a/src/com/gitblit/fanout/FanoutClient.java b/src/com/gitblit/fanout/FanoutClient.java new file mode 100644 index 00000000..b9ace4be --- /dev/null +++ b/src/com/gitblit/fanout/FanoutClient.java @@ -0,0 +1,413 @@ +/*
+ * 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.fanout;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Fanout client class.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutClient implements Runnable {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutClient.class);
+
+ private final int clientTimeout = 500;
+ private final int reconnectTimeout = 2000;
+ private final String host;
+ private final int port;
+ private final List<FanoutListener> listeners;
+
+ private String id;
+ private volatile Selector selector;
+ private volatile SocketChannel socketCh;
+ private Thread clientThread;
+
+ private final AtomicBoolean isConnected;
+ private final AtomicBoolean isRunning;
+ private final AtomicBoolean isAutomaticReconnect;
+ private final ByteBuffer writeBuffer;
+ private final ByteBuffer readBuffer;
+ private final CharsetDecoder decoder;
+
+ private final Set<String> subscriptions;
+ private boolean resubscribe;
+
+ public interface FanoutListener {
+ public void pong(Date timestamp);
+ public void announcement(String channel, String message);
+ }
+
+ public static class FanoutAdapter implements FanoutListener {
+ public void pong(Date timestamp) { }
+ public void announcement(String channel, String message) { }
+ }
+
+ public static void main(String args[]) throws Exception {
+ FanoutClient client = new FanoutClient("localhost", 2000);
+ client.addListener(new FanoutAdapter() {
+
+ @Override
+ public void pong(Date timestamp) {
+ System.out.println("Pong. " + timestamp);
+ }
+
+ @Override
+ public void announcement(String channel, String message) {
+ System.out.println(MessageFormat.format("Here ye, Here ye. {0} says {1}", channel, message));
+ }
+ });
+ client.start();
+
+ Thread.sleep(5000);
+ client.ping();
+ client.subscribe("james");
+ client.announce("james", "12345");
+ client.subscribe("c52f99d16eb5627877ae957df7ce1be102783bd5");
+
+ while (true) {
+ Thread.sleep(10000);
+ client.ping();
+ }
+ }
+
+ public FanoutClient(String host, int port) {
+ this.host = host;
+ this.port = port;
+ readBuffer = ByteBuffer.allocateDirect(FanoutConstants.BUFFER_LENGTH);
+ writeBuffer = ByteBuffer.allocateDirect(FanoutConstants.BUFFER_LENGTH);
+ decoder = Charset.forName(FanoutConstants.CHARSET).newDecoder();
+ listeners = Collections.synchronizedList(new ArrayList<FanoutListener>());
+ subscriptions = new LinkedHashSet<String>();
+ isRunning = new AtomicBoolean(false);
+ isConnected = new AtomicBoolean(false);
+ isAutomaticReconnect = new AtomicBoolean(true);
+ }
+
+ public void addListener(FanoutListener listener) {
+ listeners.add(listener);
+ }
+
+ public void removeListener(FanoutListener listener) {
+ listeners.remove(listener);
+ }
+
+ public boolean isAutomaticReconnect() {
+ return isAutomaticReconnect.get();
+ }
+
+ public void setAutomaticReconnect(boolean value) {
+ isAutomaticReconnect.set(value);
+ }
+
+ public void ping() {
+ confirmConnection();
+ write("ping");
+ }
+
+ public void status() {
+ confirmConnection();
+ write("status");
+ }
+
+ public void subscribe(String channel) {
+ confirmConnection();
+ if (subscriptions.add(channel)) {
+ write("subscribe " + channel);
+ }
+ }
+
+ public void unsubscribe(String channel) {
+ confirmConnection();
+ if (subscriptions.remove(channel)) {
+ write("unsubscribe " + channel);
+ }
+ }
+
+ public void announce(String channel, String message) {
+ confirmConnection();
+ write("announce " + channel + " " + message);
+ }
+
+ private void confirmConnection() {
+ if (!isConnected()) {
+ throw new RuntimeException("Fanout client is disconnected!");
+ }
+ }
+
+ public boolean isConnected() {
+ return isRunning.get() && socketCh != null && isConnected.get();
+ }
+
+ /**
+ * Start client connection and return immediately.
+ */
+ public void start() {
+ if (isRunning.get()) {
+ logger.warn("Fanout client is already running");
+ return;
+ }
+ clientThread = new Thread(this, "Fanout client");
+ clientThread.start();
+ }
+
+ /**
+ * Start client connection and wait until it has connected.
+ */
+ public void startSynchronously() {
+ start();
+ while (!isConnected()) {
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ /**
+ * Stops client connection. This method returns when the connection has
+ * been completely shutdown.
+ */
+ public void stop() {
+ if (!isRunning.get()) {
+ logger.warn("Fanout client is not running");
+ return;
+ }
+ isRunning.set(false);
+ try {
+ if (clientThread != null) {
+ clientThread.join();
+ clientThread = null;
+ }
+ } catch (InterruptedException e1) {
+ }
+ }
+
+ @Override
+ public void run() {
+ resetState();
+
+ isRunning.set(true);
+ while (isRunning.get()) {
+ // (re)connect
+ if (socketCh == null) {
+ try {
+ InetAddress addr = InetAddress.getByName(host);
+ socketCh = SocketChannel.open(new InetSocketAddress(addr, port));
+ socketCh.configureBlocking(false);
+ selector = Selector.open();
+ id = FanoutConstants.getLocalSocketId(socketCh.socket());
+ socketCh.register(selector, SelectionKey.OP_READ);
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("failed to open client connection to {0}:{1,number,0}", host, port), e);
+ try {
+ Thread.sleep(reconnectTimeout);
+ } catch (InterruptedException x) {
+ }
+ continue;
+ }
+ }
+
+ // read/write
+ try {
+ selector.select(clientTimeout);
+
+ Iterator<SelectionKey> i = selector.selectedKeys().iterator();
+ while (i.hasNext()) {
+ SelectionKey key = i.next();
+ i.remove();
+
+ if (key.isReadable()) {
+ // read message
+ String content = read();
+ String[] lines = content.split("\n");
+ for (String reply : lines) {
+ logger.trace(MessageFormat.format("fanout client {0} received: {1}", id, reply));
+ if (!processReply(reply)) {
+ logger.error(MessageFormat.format("fanout client {0} received unknown message", id));
+ }
+ }
+ } else if (key.isWritable()) {
+ // resubscribe
+ if (resubscribe) {
+ resubscribe = false;
+ logger.info(MessageFormat.format("fanout client {0} re-subscribing to {1} channels", id, subscriptions.size()));
+ for (String subscription : subscriptions) {
+ write("subscribe " + subscription);
+ }
+ }
+ socketCh.register(selector, SelectionKey.OP_READ);
+ }
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout client {0} error: {1}", id, e.getMessage()));
+ closeChannel();
+ if (!isAutomaticReconnect.get()) {
+ isRunning.set(false);
+ continue;
+ }
+ }
+ }
+
+ closeChannel();
+ resetState();
+ }
+
+ protected void resetState() {
+ readBuffer.clear();
+ writeBuffer.clear();
+ isRunning.set(false);
+ isConnected.set(false);
+ }
+
+ private void closeChannel() {
+ try {
+ if (socketCh != null) {
+ socketCh.close();
+ socketCh = null;
+ selector.close();
+ selector = null;
+ isConnected.set(false);
+ }
+ } catch (IOException x) {
+ }
+ }
+
+ protected boolean processReply(String reply) {
+ String[] fields = reply.split("!", 2);
+ if (fields.length == 1) {
+ try {
+ long time = Long.parseLong(fields[0]);
+ Date date = new Date(time);
+ firePong(date);
+ } catch (Exception e) {
+ }
+ return true;
+ } else if (fields.length == 2) {
+ String channel = fields[0];
+ String message = fields[1];
+ if (FanoutConstants.CH_DEBUG.equals(channel)) {
+ // debug messages are for internal use
+ if (FanoutConstants.MSG_CONNECTED.equals(message)) {
+ isConnected.set(true);
+ resubscribe = subscriptions.size() > 0;
+ if (resubscribe) {
+ try {
+ // register for async resubscribe
+ socketCh.register(selector, SelectionKey.OP_WRITE);
+ } catch (Exception e) {
+ logger.error("an error occurred", e);
+ }
+ }
+ }
+ logger.debug(MessageFormat.format("fanout client {0} < {1}", id, reply));
+ } else {
+ fireAnnouncement(channel, message);
+ }
+ return true;
+ } else {
+ // unknown message
+ return false;
+ }
+ }
+
+ protected void firePong(Date timestamp) {
+ logger.info(MessageFormat.format("fanout client {0} < pong {1,date,yyyy-MM-dd HH:mm:ss}", id, timestamp));
+ for (FanoutListener listener : listeners) {
+ try {
+ listener.pong(timestamp);
+ } catch (Throwable t) {
+ logger.error("FanoutListener threw an exception!", t);
+ }
+ }
+ }
+ protected void fireAnnouncement(String channel, String message) {
+ logger.info(MessageFormat.format("fanout client {0} < announcement {1} {2}", id, channel, message));
+ for (FanoutListener listener : listeners) {
+ try {
+ listener.announcement(channel, message);
+ } catch (Throwable t) {
+ logger.error("FanoutListener threw an exception!", t);
+ }
+ }
+ }
+
+ protected synchronized String read() throws IOException {
+ readBuffer.clear();
+ long len = socketCh.read(readBuffer);
+
+ if (len == -1) {
+ logger.error(MessageFormat.format("fanout client {0} lost connection to {1}:{2,number,0}, end of stream", id, host, port));
+ socketCh.close();
+ return null;
+ } else {
+ readBuffer.flip();
+ String content = decoder.decode(readBuffer).toString();
+ readBuffer.clear();
+ return content;
+ }
+ }
+
+ protected synchronized boolean write(String message) {
+ try {
+ logger.info(MessageFormat.format("fanout client {0} > {1}", id, message));
+ byte [] bytes = message.getBytes(FanoutConstants.CHARSET);
+ writeBuffer.clear();
+ writeBuffer.put(bytes);
+ if (bytes[bytes.length - 1] != 0xa) {
+ writeBuffer.put((byte) 0xa);
+ }
+ writeBuffer.flip();
+
+ // loop until write buffer has been completely sent
+ long written = 0;
+ long toWrite = writeBuffer.remaining();
+ while (written != toWrite) {
+ written += socketCh.write(writeBuffer);
+ try {
+ Thread.sleep(10);
+ } catch (Exception x) {
+ }
+ }
+ return true;
+ } catch (IOException e) {
+ logger.error("fanout client {0} error: {1}", id, e.getMessage());
+ }
+ return false;
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutConstants.java b/src/com/gitblit/fanout/FanoutConstants.java new file mode 100644 index 00000000..6e6964c9 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutConstants.java @@ -0,0 +1,36 @@ +/*
+ * 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.fanout;
+
+import java.net.Socket;
+
+public class FanoutConstants {
+
+ public final static String CHARSET = "ISO-8859-1";
+ public final static int BUFFER_LENGTH = 512;
+ public final static String CH_ALL = "all";
+ public final static String CH_DEBUG = "debug";
+ public final static String MSG_CONNECTED = "connected...";
+ public final static String MSG_BUSY = "busy";
+
+ public static String getRemoteSocketId(Socket socket) {
+ return socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
+ }
+
+ public static String getLocalSocketId(Socket socket) {
+ return socket.getInetAddress().getHostAddress() + ":" + socket.getLocalPort();
+ }
+}
diff --git a/src/com/gitblit/fanout/FanoutNioService.java b/src/com/gitblit/fanout/FanoutNioService.java new file mode 100644 index 00000000..65d022ab --- /dev/null +++ b/src/com/gitblit/fanout/FanoutNioService.java @@ -0,0 +1,332 @@ +/*
+ * 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.fanout;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A single-thread NIO implementation of https://github.com/travisghansen/fanout
+ *
+ * This implementation uses channels and selectors, which are the Java analog of
+ * the Linux epoll mechanism used in the original fanout C code.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutNioService extends FanoutService {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutNioService.class);
+
+ private volatile ServerSocketChannel serviceCh;
+ private volatile Selector selector;
+
+ public static void main(String[] args) throws Exception {
+ FanoutNioService pubsub = new FanoutNioService(null, DEFAULT_PORT);
+ pubsub.setStrictRequestTermination(false);
+ pubsub.setAllowAllChannelAnnouncements(false);
+ pubsub.start();
+ }
+
+ /**
+ * Create a single-threaded fanout service.
+ *
+ * @param host
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutNioService(int port) {
+ this(null, port);
+ }
+
+ /**
+ * Create a single-threaded fanout service.
+ *
+ * @param bindInterface
+ * the ip address to bind for the service, may be null
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutNioService(String bindInterface, int port) {
+ super(bindInterface, port, "Fanout nio service");
+ }
+
+ @Override
+ protected boolean isConnected() {
+ return serviceCh != null;
+ }
+
+ @Override
+ protected boolean connect() {
+ if (serviceCh == null) {
+ try {
+ serviceCh = ServerSocketChannel.open();
+ serviceCh.configureBlocking(false);
+ serviceCh.socket().setReuseAddress(true);
+ serviceCh.socket().bind(host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port));
+ selector = Selector.open();
+ serviceCh.register(selector, SelectionKey.OP_ACCEPT);
+ logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
+ name, host == null ? "0.0.0.0" : host, port));
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
+ name, name, host == null ? "0.0.0.0" : host, port), e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void disconnect() {
+ try {
+ if (serviceCh != null) {
+ // close all active client connections
+ Map<String, SocketChannel> clients = getCurrentClientSockets();
+ for (Map.Entry<String, SocketChannel> client : clients.entrySet()) {
+ closeClientSocket(client.getKey(), client.getValue());
+ }
+
+ // close service socket channel
+ logger.debug(MessageFormat.format("closing {0} socket channel", name));
+ serviceCh.socket().close();
+ serviceCh.close();
+ serviceCh = null;
+ selector.close();
+ selector = null;
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to disconnect {0}", name), e);
+ }
+ }
+
+ @Override
+ protected void listen() throws IOException {
+ while (selector.select(serviceTimeout) > 0) {
+ Set<SelectionKey> keys = selector.selectedKeys();
+ Iterator<SelectionKey> keyItr = keys.iterator();
+ while (keyItr.hasNext()) {
+ SelectionKey key = (SelectionKey) keyItr.next();
+ if (key.isAcceptable()) {
+ // new fanout client connection
+ ServerSocketChannel sch = (ServerSocketChannel) key.channel();
+ try {
+ SocketChannel ch = sch.accept();
+ ch.configureBlocking(false);
+ configureClientSocket(ch.socket());
+
+ FanoutNioConnection connection = new FanoutNioConnection(ch);
+ addConnection(connection);
+
+ // register to send the queued message
+ ch.register(selector, SelectionKey.OP_WRITE, connection);
+ } catch (IOException e) {
+ logger.error("error accepting fanout connection", e);
+ }
+ } else if (key.isReadable()) {
+ // read fanout client request
+ SocketChannel ch = (SocketChannel) key.channel();
+ FanoutNioConnection connection = (FanoutNioConnection) key.attachment();
+ try {
+ connection.read(ch, isStrictRequestTermination());
+ int replies = 0;
+ Iterator<String> reqItr = connection.requestQueue.iterator();
+ while (reqItr.hasNext()) {
+ String req = reqItr.next();
+ String reply = processRequest(connection, req);
+ reqItr.remove();
+ if (reply != null) {
+ replies++;
+ }
+ }
+
+ if (replies > 0) {
+ // register to send the replies to requests
+ ch.register(selector, SelectionKey.OP_WRITE, connection);
+ } else {
+ // re-register for next read
+ ch.register(selector, SelectionKey.OP_READ, connection);
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout connection {0} error: {1}", connection.id, e.getMessage()));
+ removeConnection(connection);
+ closeClientSocket(connection.id, ch);
+ }
+ } else if (key.isWritable()) {
+ // asynchronous reply to fanout client request
+ SocketChannel ch = (SocketChannel) key.channel();
+ FanoutNioConnection connection = (FanoutNioConnection) key.attachment();
+ try {
+ connection.write(ch);
+
+ if (hasConnection(connection)) {
+ // register for next read
+ ch.register(selector, SelectionKey.OP_READ, connection);
+ } else {
+ // Connection was rejected due to load or
+ // some other reason. Close it.
+ closeClientSocket(connection.id, ch);
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout connection {0}: {1}", connection.id, e.getMessage()));
+ removeConnection(connection);
+ closeClientSocket(connection.id, ch);
+ }
+ }
+ keyItr.remove();
+ }
+ }
+ }
+
+ protected void closeClientSocket(String id, SocketChannel ch) {
+ try {
+ ch.close();
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("fanout connection {0}", id), e);
+ }
+ }
+
+ protected void broadcast(Collection<FanoutServiceConnection> connections, String channel, String message) {
+ super.broadcast(connections, channel, message);
+
+ // register queued write
+ Map<String, SocketChannel> sockets = getCurrentClientSockets();
+ for (FanoutServiceConnection connection : connections) {
+ SocketChannel ch = sockets.get(connection.id);
+ if (ch == null) {
+ logger.warn(MessageFormat.format("fanout connection {0} has been disconnected", connection.id));
+ removeConnection(connection);
+ continue;
+ }
+ try {
+ ch.register(selector, SelectionKey.OP_WRITE, connection);
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to register write op for fanout connection {0}", connection.id));
+ }
+ }
+ }
+
+ protected Map<String, SocketChannel> getCurrentClientSockets() {
+ Map<String, SocketChannel> sockets = new HashMap<String, SocketChannel>();
+ for (SelectionKey key : selector.keys()) {
+ if (key.channel() instanceof SocketChannel) {
+ SocketChannel ch = (SocketChannel) key.channel();
+ String id = FanoutConstants.getRemoteSocketId(ch.socket());
+ sockets.put(id, ch);
+ }
+ }
+ return sockets;
+ }
+
+ /**
+ * FanoutNioConnection handles reading/writing messages from a remote fanout
+ * connection.
+ *
+ * @author James Moger
+ *
+ */
+ static class FanoutNioConnection extends FanoutServiceConnection {
+ final ByteBuffer readBuffer;
+ final ByteBuffer writeBuffer;
+ final List<String> requestQueue;
+ final List<String> replyQueue;
+ final CharsetDecoder decoder;
+
+ FanoutNioConnection(SocketChannel ch) {
+ super(ch.socket());
+ readBuffer = ByteBuffer.allocate(FanoutConstants.BUFFER_LENGTH);
+ writeBuffer = ByteBuffer.allocate(FanoutConstants.BUFFER_LENGTH);
+ requestQueue = new ArrayList<String>();
+ replyQueue = new ArrayList<String>();
+ decoder = Charset.forName(FanoutConstants.CHARSET).newDecoder();
+ }
+
+ protected void read(SocketChannel ch, boolean strictRequestTermination) throws CharacterCodingException, IOException {
+ long bytesRead = 0;
+ readBuffer.clear();
+ bytesRead = ch.read(readBuffer);
+ readBuffer.flip();
+ if (bytesRead == -1) {
+ throw new IOException("lost client connection, end of stream");
+ }
+ if (readBuffer.limit() == 0) {
+ return;
+ }
+ CharBuffer cbuf = decoder.decode(readBuffer);
+ String req = cbuf.toString();
+ String [] lines = req.split(strictRequestTermination ? "\n" : "\n|\r");
+ requestQueue.addAll(Arrays.asList(lines));
+ }
+
+ protected void write(SocketChannel ch) throws IOException {
+ Iterator<String> itr = replyQueue.iterator();
+ while (itr.hasNext()) {
+ String reply = itr.next();
+ writeBuffer.clear();
+ logger.debug(MessageFormat.format("fanout reply to {0}: {1}", id, reply));
+ byte [] bytes = reply.getBytes(FanoutConstants.CHARSET);
+ writeBuffer.put(bytes);
+ if (bytes[bytes.length - 1] != 0xa) {
+ writeBuffer.put((byte) 0xa);
+ }
+ writeBuffer.flip();
+
+ // loop until write buffer has been completely sent
+ int written = 0;
+ int toWrite = writeBuffer.remaining();
+ while (written != toWrite) {
+ written += ch.write(writeBuffer);
+ try {
+ Thread.sleep(10);
+ } catch (Exception x) {
+ }
+ }
+ itr.remove();
+ }
+ writeBuffer.clear();
+ }
+
+ @Override
+ protected void reply(String content) throws IOException {
+ // queue the reply
+ // replies are transmitted asynchronously from the requests
+ replyQueue.add(content);
+ }
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutService.java b/src/com/gitblit/fanout/FanoutService.java new file mode 100644 index 00000000..cbfd8a24 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutService.java @@ -0,0 +1,563 @@ +/*
+ * 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.fanout;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class for Fanout service implementations.
+ *
+ * Subclass implementations can be used as a Sparkleshare PubSub notification
+ * server. This allows Sparkleshare to be used in conjunction with Gitblit
+ * behind a corporate firewall that restricts or prohibits client internet access
+ * to the default Sparkleshare PubSub server: notifications.sparkleshare.org
+ *
+ * @author James Moger
+ *
+ */
+public abstract class FanoutService implements Runnable {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutService.class);
+
+ public final static int DEFAULT_PORT = 17000;
+
+ protected final static int serviceTimeout = 5000;
+
+ protected final String host;
+ protected final int port;
+ protected final String name;
+
+ private Thread serviceThread;
+
+ private final Map<String, FanoutServiceConnection> connections;
+ private final Map<String, Set<FanoutServiceConnection>> subscriptions;
+
+ protected final AtomicBoolean isRunning;
+ private final AtomicBoolean strictRequestTermination;
+ private final AtomicBoolean allowAllChannelAnnouncements;
+ private final AtomicInteger concurrentConnectionLimit;
+
+ private final Date bootDate;
+ private final AtomicLong rejectedConnectionCount;
+ private final AtomicInteger peakConnectionCount;
+ private final AtomicLong totalConnections;
+ private final AtomicLong totalAnnouncements;
+ private final AtomicLong totalMessages;
+ private final AtomicLong totalSubscribes;
+ private final AtomicLong totalUnsubscribes;
+ private final AtomicLong totalPings;
+
+ protected FanoutService(String host, int port, String name) {
+ this.host = host;
+ this.port = port;
+ this.name = name;
+
+ connections = new ConcurrentHashMap<String, FanoutServiceConnection>();
+ subscriptions = new ConcurrentHashMap<String, Set<FanoutServiceConnection>>();
+ subscriptions.put(FanoutConstants.CH_ALL, new ConcurrentSkipListSet<FanoutServiceConnection>());
+
+ isRunning = new AtomicBoolean(false);
+ strictRequestTermination = new AtomicBoolean(false);
+ allowAllChannelAnnouncements = new AtomicBoolean(false);
+ concurrentConnectionLimit = new AtomicInteger(0);
+
+ bootDate = new Date();
+ rejectedConnectionCount = new AtomicLong(0);
+ peakConnectionCount = new AtomicInteger(0);
+ totalConnections = new AtomicLong(0);
+ totalAnnouncements = new AtomicLong(0);
+ totalMessages = new AtomicLong(0);
+ totalSubscribes = new AtomicLong(0);
+ totalUnsubscribes = new AtomicLong(0);
+ totalPings = new AtomicLong(0);
+ }
+
+ /*
+ * Abstract methods
+ */
+
+ protected abstract boolean isConnected();
+
+ protected abstract boolean connect();
+
+ protected abstract void listen() throws IOException;
+
+ protected abstract void disconnect();
+
+ /**
+ * Returns true if the service requires \n request termination.
+ *
+ * @return true if request requires \n termination
+ */
+ public boolean isStrictRequestTermination() {
+ return strictRequestTermination.get();
+ }
+
+ /**
+ * Control the termination of fanout requests. If true, fanout requests must
+ * be terminated with \n. If false, fanout requests may be terminated with
+ * \n, \r, \r\n, or \n\r. This is useful for debugging with a telnet client.
+ *
+ * @param isStrictTermination
+ */
+ public void setStrictRequestTermination(boolean isStrictTermination) {
+ strictRequestTermination.set(isStrictTermination);
+ }
+
+ /**
+ * Returns the maximum allowable concurrent fanout connections.
+ *
+ * @return the maximum allowable concurrent connection count
+ */
+ public int getConcurrentConnectionLimit() {
+ return concurrentConnectionLimit.get();
+ }
+
+ /**
+ * Sets the maximum allowable concurrent fanout connection count.
+ *
+ * @param value
+ */
+ public void setConcurrentConnectionLimit(int value) {
+ concurrentConnectionLimit.set(value);
+ }
+
+ /**
+ * Returns true if connections are allowed to announce on the all channel.
+ *
+ * @return true if connections are allowed to announce on the all channel
+ */
+ public boolean allowAllChannelAnnouncements() {
+ return allowAllChannelAnnouncements.get();
+ }
+
+ /**
+ * Allows/prohibits connections from announcing on the ALL channel.
+ *
+ * @param value
+ */
+ public void setAllowAllChannelAnnouncements(boolean value) {
+ allowAllChannelAnnouncements.set(value);
+ }
+
+ /**
+ * Returns the current connections
+ *
+ * @param channel
+ * @return map of current connections keyed by their id
+ */
+ public Map<String, FanoutServiceConnection> getCurrentConnections() {
+ return connections;
+ }
+
+ /**
+ * Returns all subscriptions
+ *
+ * @return map of current subscriptions keyed by channel name
+ */
+ public Map<String, Set<FanoutServiceConnection>> getCurrentSubscriptions() {
+ return subscriptions;
+ }
+
+ /**
+ * Returns the subscriptions for the specified channel
+ *
+ * @param channel
+ * @return set of subscribed connections for the specified channel
+ */
+ public Set<FanoutServiceConnection> getCurrentSubscriptions(String channel) {
+ return subscriptions.get(channel);
+ }
+
+ /**
+ * Returns the runtime statistics object for this service.
+ *
+ * @return stats
+ */
+ public FanoutStats getStatistics() {
+ FanoutStats stats = new FanoutStats();
+
+ // settings
+ stats.allowAllChannelAnnouncements = allowAllChannelAnnouncements();
+ stats.concurrentConnectionLimit = getConcurrentConnectionLimit();
+ stats.strictRequestTermination = isStrictRequestTermination();
+
+ // runtime stats
+ stats.bootDate = bootDate;
+ stats.rejectedConnectionCount = rejectedConnectionCount.get();
+ stats.peakConnectionCount = peakConnectionCount.get();
+ stats.totalConnections = totalConnections.get();
+ stats.totalAnnouncements = totalAnnouncements.get();
+ stats.totalMessages = totalMessages.get();
+ stats.totalSubscribes = totalSubscribes.get();
+ stats.totalUnsubscribes = totalUnsubscribes.get();
+ stats.totalPings = totalPings.get();
+ stats.currentConnections = connections.size();
+ stats.currentChannels = subscriptions.size();
+ stats.currentSubscriptions = subscriptions.size() * connections.size();
+ return stats;
+ }
+
+ /**
+ * Returns true if the service is ready.
+ *
+ * @return true, if the service is ready
+ */
+ public boolean isReady() {
+ if (isRunning.get()) {
+ return isConnected();
+ }
+ return false;
+ }
+
+ /**
+ * Start the Fanout service thread and immediatel return.
+ *
+ */
+ public void start() {
+ if (isRunning.get()) {
+ logger.warn(MessageFormat.format("{0} is already running", name));
+ return;
+ }
+ serviceThread = new Thread(this);
+ serviceThread.setName(MessageFormat.format("{0} {1}:{2,number,0}", name, host == null ? "all" : host, port));
+ serviceThread.start();
+ }
+
+ /**
+ * Start the Fanout service thread and wait until it is accepting connections.
+ *
+ */
+ public void startSynchronously() {
+ start();
+ while (!isReady()) {
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ /**
+ * Stop the Fanout service. This method returns when the service has been
+ * completely shutdown.
+ */
+ public void stop() {
+ if (!isRunning.get()) {
+ logger.warn(MessageFormat.format("{0} is not running", name));
+ return;
+ }
+ logger.info(MessageFormat.format("stopping {0}...", name));
+ isRunning.set(false);
+ try {
+ if (serviceThread != null) {
+ serviceThread.join();
+ serviceThread = null;
+ }
+ } catch (InterruptedException e1) {
+ logger.error("", e1);
+ }
+ logger.info(MessageFormat.format("stopped {0}", name));
+ }
+
+ /**
+ * Main execution method of the service
+ */
+ @Override
+ public final void run() {
+ disconnect();
+ resetState();
+ isRunning.set(true);
+ while (isRunning.get()) {
+ if (connect()) {
+ try {
+ listen();
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("error processing {0}", name), e);
+ isRunning.set(false);
+ }
+ } else {
+ try {
+ Thread.sleep(serviceTimeout);
+ } catch (InterruptedException x) {
+ }
+ }
+ }
+ disconnect();
+ resetState();
+ }
+
+ protected void resetState() {
+ // reset state data
+ connections.clear();
+ subscriptions.clear();
+ rejectedConnectionCount.set(0);
+ peakConnectionCount.set(0);
+ totalConnections.set(0);
+ totalAnnouncements.set(0);
+ totalMessages.set(0);
+ totalSubscribes.set(0);
+ totalUnsubscribes.set(0);
+ totalPings.set(0);
+ }
+
+ /**
+ * Configure the client connection socket.
+ *
+ * @param socket
+ * @throws SocketException
+ */
+ protected void configureClientSocket(Socket socket) throws SocketException {
+ socket.setKeepAlive(true);
+ socket.setSoLinger(true, 0); // immediately discard any remaining data
+ }
+
+ /**
+ * Add the connection to the connections map.
+ *
+ * @param connection
+ * @return false if the connection was rejected due to too many concurrent
+ * connections
+ */
+ protected boolean addConnection(FanoutServiceConnection connection) {
+ int limit = getConcurrentConnectionLimit();
+ if (limit > 0 && connections.size() > limit) {
+ logger.info(MessageFormat.format("hit {0,number,0} connection limit, rejecting fanout connection", concurrentConnectionLimit));
+ increment(rejectedConnectionCount);
+ connection.busy();
+ return false;
+ }
+
+ // add the connection to our map
+ connections.put(connection.id, connection);
+
+ // track peak number of concurrent connections
+ if (connections.size() > peakConnectionCount.get()) {
+ peakConnectionCount.set(connections.size());
+ }
+
+ logger.info("fanout new connection " + connection.id);
+ connection.connected();
+ return true;
+ }
+
+ /**
+ * Remove the connection from the connections list and from subscriptions.
+ *
+ * @param connection
+ */
+ protected void removeConnection(FanoutServiceConnection connection) {
+ connections.remove(connection.id);
+ Iterator<Map.Entry<String, Set<FanoutServiceConnection>>> itr = subscriptions.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<String, Set<FanoutServiceConnection>> entry = itr.next();
+ Set<FanoutServiceConnection> subscriptions = entry.getValue();
+ subscriptions.remove(connection);
+ if (!FanoutConstants.CH_ALL.equals(entry.getKey())) {
+ if (subscriptions.size() == 0) {
+ itr.remove();
+ logger.info(MessageFormat.format("fanout remove channel {0}, no subscribers", entry.getKey()));
+ }
+ }
+ }
+ logger.info(MessageFormat.format("fanout connection {0} removed", connection.id));
+ }
+
+ /**
+ * Tests to see if the connection is being monitored by the service.
+ *
+ * @param connection
+ * @return true if the service is monitoring the connection
+ */
+ protected boolean hasConnection(FanoutServiceConnection connection) {
+ return connections.containsKey(connection.id);
+ }
+
+ /**
+ * Reply to a connection on the specified channel.
+ *
+ * @param connection
+ * @param channel
+ * @param message
+ * @return the reply
+ */
+ protected String reply(FanoutServiceConnection connection, String channel, String message) {
+ if (channel != null && channel.length() > 0) {
+ increment(totalMessages);
+ }
+ return connection.reply(channel, message);
+ }
+
+ /**
+ * Service method to broadcast a message to all connections.
+ *
+ * @param message
+ */
+ public void broadcastAll(String message) {
+ broadcast(connections.values(), FanoutConstants.CH_ALL, message);
+ increment(totalAnnouncements);
+ }
+
+ /**
+ * Service method to broadcast a message to connections subscribed to the
+ * channel.
+ *
+ * @param message
+ */
+ public void broadcast(String channel, String message) {
+ List<FanoutServiceConnection> connections = new ArrayList<FanoutServiceConnection>(subscriptions.get(channel));
+ broadcast(connections, channel, message);
+ increment(totalAnnouncements);
+ }
+
+ /**
+ * Broadcast a message to connections subscribed to the specified channel.
+ *
+ * @param connections
+ * @param channel
+ * @param message
+ */
+ protected void broadcast(Collection<FanoutServiceConnection> connections, String channel, String message) {
+ for (FanoutServiceConnection connection : connections) {
+ reply(connection, channel, message);
+ }
+ }
+
+ /**
+ * Process an incoming Fanout request.
+ *
+ * @param connection
+ * @param req
+ * @return the reply to the request, may be null
+ */
+ protected String processRequest(FanoutServiceConnection connection, String req) {
+ logger.info(MessageFormat.format("fanout request from {0}: {1}", connection.id, req));
+ String[] fields = req.split(" ", 3);
+ String action = fields[0];
+ String channel = fields.length >= 2 ? fields[1] : null;
+ String message = fields.length >= 3 ? fields[2] : null;
+ try {
+ return processRequest(connection, action, channel, message);
+ } catch (IllegalArgumentException e) {
+ // invalid action
+ logger.error(MessageFormat.format("fanout connection {0} requested invalid action {1}", connection.id, action));
+ logger.error(asHexArray(req));
+ }
+ return null;
+ }
+
+ /**
+ * Process the Fanout request.
+ *
+ * @param connection
+ * @param action
+ * @param channel
+ * @param message
+ * @return the reply to the request, may be null
+ * @throws IllegalArgumentException
+ */
+ protected String processRequest(FanoutServiceConnection connection, String action, String channel, String message) throws IllegalArgumentException {
+ if ("ping".equals(action)) {
+ // ping
+ increment(totalPings);
+ return reply(connection, null, "" + System.currentTimeMillis());
+ } else if ("info".equals(action)) {
+ // info
+ String info = getStatistics().info();
+ return reply(connection, null, info);
+ } else if ("announce".equals(action)) {
+ // announcement
+ if (!allowAllChannelAnnouncements.get() && FanoutConstants.CH_ALL.equals(channel)) {
+ // prohibiting connection-sourced all announcements
+ logger.warn(MessageFormat.format("fanout connection {0} attempted to announce {1} on ALL channel", connection.id, message));
+ } else if ("debug".equals(channel)) {
+ // prohibiting connection-sourced debug announcements
+ logger.warn(MessageFormat.format("fanout connection {0} attempted to announce {1} on DEBUG channel", connection.id, message));
+ } else {
+ // acceptable announcement
+ List<FanoutServiceConnection> connections = new ArrayList<FanoutServiceConnection>(subscriptions.get(channel));
+ connections.remove(connection); // remove announcer
+ broadcast(connections, channel, message);
+ increment(totalAnnouncements);
+ }
+ } else if ("subscribe".equals(action)) {
+ // subscribe
+ if (!subscriptions.containsKey(channel)) {
+ logger.info(MessageFormat.format("fanout new channel {0}", channel));
+ subscriptions.put(channel, new ConcurrentSkipListSet<FanoutServiceConnection>());
+ }
+ subscriptions.get(channel).add(connection);
+ logger.debug(MessageFormat.format("fanout connection {0} subscribed to channel {1}", connection.id, channel));
+ increment(totalSubscribes);
+ } else if ("unsubscribe".equals(action)) {
+ // unsubscribe
+ if (subscriptions.containsKey(channel)) {
+ subscriptions.get(channel).remove(connection);
+ if (subscriptions.get(channel).size() == 0) {
+ subscriptions.remove(channel);
+ }
+ increment(totalUnsubscribes);
+ }
+ } else {
+ // invalid action
+ throw new IllegalArgumentException(action);
+ }
+ return null;
+ }
+
+ private String asHexArray(String req) {
+ StringBuilder sb = new StringBuilder();
+ for (char c : req.toCharArray()) {
+ sb.append(Integer.toHexString(c)).append(' ');
+ }
+ return "[ " + sb.toString().trim() + " ]";
+ }
+
+ /**
+ * Increment a long and prevent negative rollover.
+ *
+ * @param counter
+ */
+ private void increment(AtomicLong counter) {
+ long v = counter.incrementAndGet();
+ if (v < 0) {
+ counter.set(0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutServiceConnection.java b/src/com/gitblit/fanout/FanoutServiceConnection.java new file mode 100644 index 00000000..f7f2c959 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutServiceConnection.java @@ -0,0 +1,105 @@ +/*
+ * 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.fanout;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * FanoutServiceConnection handles reading/writing messages from a remote fanout
+ * connection.
+ *
+ * @author James Moger
+ *
+ */
+public abstract class FanoutServiceConnection implements Comparable<FanoutServiceConnection> {
+
+ private static final Logger logger = LoggerFactory.getLogger(FanoutServiceConnection.class);
+
+ public final String id;
+
+ protected FanoutServiceConnection(Socket socket) {
+ this.id = FanoutConstants.getRemoteSocketId(socket);
+ }
+
+ protected abstract void reply(String content) throws IOException;
+
+ /**
+ * Send the connection a debug channel connected message.
+ *
+ * @param message
+ */
+ protected void connected() {
+ reply(FanoutConstants.CH_DEBUG, FanoutConstants.MSG_CONNECTED);
+ }
+
+ /**
+ * Send the connection a debug channel busy message.
+ *
+ * @param message
+ */
+ protected void busy() {
+ reply(FanoutConstants.CH_DEBUG, FanoutConstants.MSG_BUSY);
+ }
+
+ /**
+ * Send the connection a message for the specified channel.
+ *
+ * @param channel
+ * @param message
+ * @return the reply
+ */
+ protected String reply(String channel, String message) {
+ String content;
+ if (channel != null) {
+ content = channel + "!" + message;
+ } else {
+ content = message;
+ }
+ try {
+ reply(content);
+ } catch (Exception e) {
+ logger.error("failed to reply to fanout connection " + id, e);
+ }
+ return content;
+ }
+
+ @Override
+ public int compareTo(FanoutServiceConnection c) {
+ return id.compareTo(c.id);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof FanoutServiceConnection) {
+ return id.equals(((FanoutServiceConnection) o).id);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return id;
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutSocketService.java b/src/com/gitblit/fanout/FanoutSocketService.java new file mode 100644 index 00000000..07c18f90 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutSocketService.java @@ -0,0 +1,234 @@ +/*
+ * 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.fanout;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.text.MessageFormat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A multi-threaded socket implementation of https://github.com/travisghansen/fanout
+ *
+ * This implementation creates a master acceptor thread which accepts incoming
+ * fanout connections and then spawns a daemon thread for each accepted connection.
+ * If there are 100 concurrent fanout connections, there are 101 threads.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutSocketService extends FanoutService {
+
+ private final static Logger logger = LoggerFactory.getLogger(FanoutSocketService.class);
+
+ private volatile ServerSocket serviceSocket;
+
+ public static void main(String[] args) throws Exception {
+ FanoutSocketService pubsub = new FanoutSocketService(null, DEFAULT_PORT);
+ pubsub.setStrictRequestTermination(false);
+ pubsub.setAllowAllChannelAnnouncements(false);
+ pubsub.start();
+ }
+
+ /**
+ * Create a multi-threaded fanout service.
+ *
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutSocketService(int port) {
+ this(null, port);
+ }
+
+ /**
+ * Create a multi-threaded fanout service.
+ *
+ * @param bindInterface
+ * the ip address to bind for the service, may be null
+ * @param port
+ * the port for running the fanout PubSub service
+ * @throws IOException
+ */
+ public FanoutSocketService(String bindInterface, int port) {
+ super(bindInterface, port, "Fanout socket service");
+ }
+
+ @Override
+ protected boolean isConnected() {
+ return serviceSocket != null;
+ }
+
+ @Override
+ protected boolean connect() {
+ if (serviceSocket == null) {
+ try {
+ serviceSocket = new ServerSocket();
+ serviceSocket.setReuseAddress(true);
+ serviceSocket.setSoTimeout(serviceTimeout);
+ serviceSocket.bind(host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port));
+ logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
+ name, host == null ? "0.0.0.0" : host, serviceSocket.getLocalPort()));
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
+ name, host == null ? "0.0.0.0" : host, port), e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void disconnect() {
+ try {
+ if (serviceSocket != null) {
+ logger.debug(MessageFormat.format("closing {0} server socket", name));
+ serviceSocket.close();
+ serviceSocket = null;
+ }
+ } catch (IOException e) {
+ logger.error(MessageFormat.format("failed to disconnect {0}", name), e);
+ }
+ }
+
+ /**
+ * This accepts incoming fanout connections and spawns connection threads.
+ */
+ @Override
+ protected void listen() throws IOException {
+ try {
+ Socket socket;
+ socket = serviceSocket.accept();
+ configureClientSocket(socket);
+
+ FanoutSocketConnection connection = new FanoutSocketConnection(socket);
+
+ if (addConnection(connection)) {
+ // spawn connection daemon thread
+ Thread connectionThread = new Thread(connection);
+ connectionThread.setDaemon(true);
+ connectionThread.setName("Fanout " + connection.id);
+ connectionThread.start();
+ } else {
+ // synchronously close the connection and remove it
+ removeConnection(connection);
+ connection.closeConnection();
+ connection = null;
+ }
+ } catch (SocketTimeoutException e) {
+ // ignore accept timeout exceptions
+ }
+ }
+
+ /**
+ * FanoutSocketConnection handles reading/writing messages from a remote fanout
+ * connection.
+ *
+ * @author James Moger
+ *
+ */
+ class FanoutSocketConnection extends FanoutServiceConnection implements Runnable {
+ Socket socket;
+
+ FanoutSocketConnection(Socket socket) {
+ super(socket);
+ this.socket = socket;
+ }
+
+ /**
+ * Connection thread read/write method.
+ */
+ @Override
+ public void run() {
+ try {
+ StringBuilder sb = new StringBuilder();
+ BufferedInputStream is = new BufferedInputStream(socket.getInputStream());
+ byte[] buffer = new byte[FanoutConstants.BUFFER_LENGTH];
+ int len = 0;
+ while (true) {
+ while (is.available() > 0) {
+ len = is.read(buffer);
+ for (int i = 0; i < len; i++) {
+ byte b = buffer[i];
+ if (b == 0xa || (!isStrictRequestTermination() && b == 0xd)) {
+ String req = sb.toString();
+ sb.setLength(0);
+ if (req.length() > 0) {
+ // ignore empty request strings
+ processRequest(this, req);
+ }
+ } else {
+ sb.append((char) b);
+ }
+ }
+ }
+
+ if (!isRunning.get()) {
+ // service has stopped, terminate client connection
+ break;
+ } else {
+ Thread.sleep(500);
+ }
+ }
+ } catch (Throwable t) {
+ if (t instanceof SocketException) {
+ logger.error(MessageFormat.format("fanout connection {0}: {1}", id, t.getMessage()));
+ } else if (t instanceof SocketTimeoutException) {
+ logger.error(MessageFormat.format("fanout connection {0}: {1}", id, t.getMessage()));
+ } else {
+ logger.error(MessageFormat.format("exception while handling fanout connection {0}", id), t);
+ }
+ } finally {
+ closeConnection();
+ }
+
+ logger.info(MessageFormat.format("thread for fanout connection {0} is finished", id));
+ }
+
+ @Override
+ protected void reply(String content) throws IOException {
+ // synchronously send reply
+ logger.debug(MessageFormat.format("fanout reply to {0}: {1}", id, content));
+ OutputStream os = socket.getOutputStream();
+ byte [] bytes = content.getBytes(FanoutConstants.CHARSET);
+ os.write(bytes);
+ if (bytes[bytes.length - 1] != 0xa) {
+ os.write(0xa);
+ }
+ os.flush();
+ }
+
+ protected void closeConnection() {
+ // close the connection socket
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ socket = null;
+
+ // remove this connection from the service
+ removeConnection(this);
+ }
+ }
+}
\ No newline at end of file diff --git a/src/com/gitblit/fanout/FanoutStats.java b/src/com/gitblit/fanout/FanoutStats.java new file mode 100644 index 00000000..b06884d3 --- /dev/null +++ b/src/com/gitblit/fanout/FanoutStats.java @@ -0,0 +1,98 @@ +/*
+ * 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.fanout;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Date;
+
+/**
+ * Encapsulates the runtime stats of a fanout service.
+ *
+ * @author James Moger
+ *
+ */
+public class FanoutStats implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public long concurrentConnectionLimit;
+ public boolean allowAllChannelAnnouncements;
+ public boolean strictRequestTermination;
+
+ public Date bootDate;
+ public long rejectedConnectionCount;
+ public int peakConnectionCount;
+ public long currentChannels;
+ public long currentSubscriptions;
+ public long currentConnections;
+ public long totalConnections;
+ public long totalAnnouncements;
+ public long totalMessages;
+ public long totalSubscribes;
+ public long totalUnsubscribes;
+ public long totalPings;
+
+ public String info() {
+ int i = 0;
+ StringBuilder sb = new StringBuilder();
+ sb.append(infoStr(i++, "boot date"));
+ sb.append(infoStr(i++, "strict request termination"));
+ sb.append(infoStr(i++, "allow connection \"all\" announcements"));
+ sb.append(infoInt(i++, "concurrent connection limit"));
+ sb.append(infoInt(i++, "concurrent limit rejected connections"));
+ sb.append(infoInt(i++, "peak connections"));
+ sb.append(infoInt(i++, "current connections"));
+ sb.append(infoInt(i++, "current channels"));
+ sb.append(infoInt(i++, "current subscriptions"));
+ sb.append(infoInt(i++, "user-requested subscriptions"));
+ sb.append(infoInt(i++, "total connections"));
+ sb.append(infoInt(i++, "total announcements"));
+ sb.append(infoInt(i++, "total messages"));
+ sb.append(infoInt(i++, "total subscribes"));
+ sb.append(infoInt(i++, "total unsubscribes"));
+ sb.append(infoInt(i++, "total pings"));
+ String template = sb.toString();
+
+ String info = MessageFormat.format(template,
+ bootDate.toString(),
+ Boolean.toString(strictRequestTermination),
+ Boolean.toString(allowAllChannelAnnouncements),
+ concurrentConnectionLimit,
+ rejectedConnectionCount,
+ peakConnectionCount,
+ currentConnections,
+ currentChannels,
+ currentSubscriptions,
+ currentSubscriptions == 0 ? 0 : (currentSubscriptions - currentConnections),
+ totalConnections,
+ totalAnnouncements,
+ totalMessages,
+ totalSubscribes,
+ totalUnsubscribes,
+ totalPings);
+ return info;
+ }
+
+ private String infoStr(int index, String label) {
+ return label + ": {" + index + "}\n";
+ }
+
+ private String infoInt(int index, String label) {
+ return label + ": {" + index + ",number,0}\n";
+ }
+
+}
diff --git a/src/com/gitblit/models/Activity.java b/src/com/gitblit/models/Activity.java index 771c8a1a..59405c7f 100644 --- a/src/com/gitblit/models/Activity.java +++ b/src/com/gitblit/models/Activity.java @@ -25,9 +25,9 @@ import java.util.List; import java.util.Map;
import java.util.Set;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
+import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
/**
@@ -93,8 +93,7 @@ public class Activity implements Serializable, Comparable<Activity> { }
repositoryMetrics.get(repository).count++;
- String author = commit.getAuthorIdent().getEmailAddress()
- .toLowerCase();
+ String author = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();
if (!authorMetrics.containsKey(author)) {
authorMetrics.put(author, new Metric(author));
}
@@ -127,86 +126,4 @@ public class Activity implements Serializable, Comparable<Activity> { // reverse chronological order
return o.startDate.compareTo(startDate);
}
-
- /**
- * Model class to represent a RevCommit, it's source repository, and the
- * branch. This class is used by the activity page.
- *
- * @author James Moger
- */
- public static class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> {
-
- private static final long serialVersionUID = 1L;
-
- public final String repository;
-
- public final String branch;
-
- private final RevCommit commit;
-
- private List<RefModel> refs;
-
- public RepositoryCommit(String repository, String branch, RevCommit commit) {
- this.repository = repository;
- this.branch = branch;
- this.commit = commit;
- }
-
- public void setRefs(List<RefModel> refs) {
- this.refs = refs;
- }
-
- public List<RefModel> getRefs() {
- return refs;
- }
-
- public String getName() {
- return commit.getName();
- }
-
- public String getShortName() {
- return commit.getName().substring(0, 8);
- }
-
- public String getShortMessage() {
- return commit.getShortMessage();
- }
-
- public int getParentCount() {
- return commit.getParentCount();
- }
-
- public PersonIdent getAuthorIdent() {
- return commit.getAuthorIdent();
- }
-
- public PersonIdent getCommitterIdent() {
- return commit.getCommitterIdent();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof RepositoryCommit) {
- RepositoryCommit commit = (RepositoryCommit) o;
- return repository.equals(commit.repository) && getName().equals(commit.getName());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return (repository + commit).hashCode();
- }
-
- @Override
- public int compareTo(RepositoryCommit o) {
- // reverse-chronological order
- if (commit.getCommitTime() > o.commit.getCommitTime()) {
- return -1;
- } else if (commit.getCommitTime() < o.commit.getCommitTime()) {
- return 1;
- }
- return 0;
- }
- }
}
diff --git a/src/com/gitblit/models/ProjectModel.java b/src/com/gitblit/models/ProjectModel.java index 189a409b..9e5d5233 100644 --- a/src/com/gitblit/models/ProjectModel.java +++ b/src/com/gitblit/models/ProjectModel.java @@ -39,6 +39,8 @@ public class ProjectModel implements Serializable, Comparable<ProjectModel> { public String description;
public final Set<String> repositories = new HashSet<String>();
+ public String projectMarkdown;
+ public String repositoriesMarkdown;
public Date lastChange;
public final boolean isRoot;
diff --git a/src/com/gitblit/models/PushLogEntry.java b/src/com/gitblit/models/PushLogEntry.java new file mode 100644 index 00000000..f625c2a3 --- /dev/null +++ b/src/com/gitblit/models/PushLogEntry.java @@ -0,0 +1,208 @@ +/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * Model class to represent a push into a repository.
+ *
+ * @author James Moger
+ */
+public class PushLogEntry implements Serializable, Comparable<PushLogEntry> {
+
+ private static final long serialVersionUID = 1L;
+
+ public final String repository;
+
+ public final Date date;
+
+ public final UserModel user;
+
+ private final Set<RepositoryCommit> commits;
+
+ private final Map<String, ReceiveCommand.Type> refUpdates;
+
+ /**
+ * Constructor for specified duration of push from start date.
+ *
+ * @param repository
+ * the repository that received the push
+ * @param date
+ * the date of the push
+ * @param user
+ * the user who pushed
+ */
+ public PushLogEntry(String repository, Date date, UserModel user) {
+ this.repository = repository;
+ this.date = date;
+ this.user = user;
+ this.commits = new LinkedHashSet<RepositoryCommit>();
+ this.refUpdates = new HashMap<String, ReceiveCommand.Type>();
+ }
+
+ /**
+ * Tracks the change type for the specified ref.
+ *
+ * @param ref
+ * @param type
+ */
+ public void updateRef(String ref, ReceiveCommand.Type type) {
+ if (!refUpdates.containsKey(ref)) {
+ refUpdates.put(ref, type);
+ }
+ }
+
+ /**
+ * Adds a commit to the push entry object as long as the commit is not a
+ * duplicate.
+ *
+ * @param branch
+ * @param commit
+ * @return a RepositoryCommit, if one was added. Null if this is duplicate
+ * commit
+ */
+ public RepositoryCommit addCommit(String branch, RevCommit commit) {
+ RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
+ if (commits.add(commitModel)) {
+ return commitModel;
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if this push contains a non-fastforward ref update.
+ *
+ * @return true if this is a non-fastforward push
+ */
+ public boolean isNonFastForward() {
+ for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
+ if (ReceiveCommand.Type.UPDATE_NONFASTFORWARD.equals(entry.getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the list of branches changed by the push.
+ *
+ * @return a list of branches
+ */
+ public List<String> getChangedBranches() {
+ return getChangedRefs(Constants.R_HEADS);
+ }
+
+ /**
+ * Returns the list of tags changed by the push.
+ *
+ * @return a list of tags
+ */
+ public List<String> getChangedTags() {
+ return getChangedRefs(Constants.R_TAGS);
+ }
+
+ /**
+ * Gets the changed refs in the push.
+ *
+ * @param baseRef
+ * @return the changed refs
+ */
+ protected List<String> getChangedRefs(String baseRef) {
+ Set<String> refs = new HashSet<String>();
+ for (String ref : refUpdates.keySet()) {
+ if (baseRef == null || ref.startsWith(baseRef)) {
+ refs.add(ref);
+ }
+ }
+ List<String> list = new ArrayList<String>(refs);
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * The total number of commits in the push.
+ *
+ * @return the number of commits in the push
+ */
+ public int getCommitCount() {
+ return commits.size();
+ }
+
+ /**
+ * Returns all commits in the push.
+ *
+ * @return a list of commits
+ */
+ public List<RepositoryCommit> getCommits() {
+ List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * Returns all commits that belong to a particular ref
+ *
+ * @param ref
+ * @return a list of commits
+ */
+ public List<RepositoryCommit> getCommits(String ref) {
+ List<RepositoryCommit> list = new ArrayList<RepositoryCommit>();
+ for (RepositoryCommit commit : commits) {
+ if (commit.branch.equals(ref)) {
+ list.add(commit);
+ }
+ }
+ Collections.sort(list);
+ return list;
+ }
+
+ @Override
+ public int compareTo(PushLogEntry o) {
+ // reverse chronological order
+ return o.date.compareTo(date);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1} pushed {2,number,0} commit{3} to {4} ",
+ date, user.getDisplayName(), commits.size(), commits.size() == 1 ? "":"s", repository));
+ for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
+ String ref = entry.getKey();
+ ReceiveCommand.Type type = entry.getValue();
+ sb.append("\n ").append(ref).append(' ').append(type.name()).append('\n');
+ for (RepositoryCommit commit : getCommits(ref)) {
+ sb.append(" ").append(commit.toString()).append('\n');
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/gitblit/models/RepositoryCommit.java b/src/com/gitblit/models/RepositoryCommit.java new file mode 100644 index 00000000..e68e8613 --- /dev/null +++ b/src/com/gitblit/models/RepositoryCommit.java @@ -0,0 +1,112 @@ +/* + * 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.models; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.List; + +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; + +/** + * Model class to represent a RevCommit, it's source repository, and the branch. + * This class is used by the activity page. + * + * @author James Moger + */ +public class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> { + + private static final long serialVersionUID = 1L; + + public final String repository; + + public final String branch; + + private final RevCommit commit; + + private List<RefModel> refs; + + public RepositoryCommit(String repository, String branch, RevCommit commit) { + this.repository = repository; + this.branch = branch; + this.commit = commit; + } + + public void setRefs(List<RefModel> refs) { + this.refs = refs; + } + + public List<RefModel> getRefs() { + return refs; + } + + public String getName() { + return commit.getName(); + } + + public String getShortName() { + return commit.getName().substring(0, 8); + } + + public String getShortMessage() { + return commit.getShortMessage(); + } + + public int getParentCount() { + return commit.getParentCount(); + } + + public PersonIdent getAuthorIdent() { + return commit.getAuthorIdent(); + } + + public PersonIdent getCommitterIdent() { + return commit.getCommitterIdent(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof RepositoryCommit) { + RepositoryCommit commit = (RepositoryCommit) o; + return repository.equals(commit.repository) && getName().equals(commit.getName()); + } + return false; + } + + @Override + public int hashCode() { + return (repository + commit).hashCode(); + } + + @Override + public int compareTo(RepositoryCommit o) { + // reverse-chronological order + if (commit.getCommitTime() > o.commit.getCommitTime()) { + return -1; + } else if (commit.getCommitTime() < o.commit.getCommitTime()) { + return 1; + } + return 0; + } + + @Override + public String toString() { + return MessageFormat.format("{0} {1} {2,date,yyyy-MM-dd HH:mm} {3} {4}", + getShortName(), branch, getCommitterIdent().getWhen(), getAuthorIdent().getName(), + getShortMessage()); + } +}
\ No newline at end of file diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java index 5be33a2d..a2dab3c5 100644 --- a/src/com/gitblit/models/RepositoryModel.java +++ b/src/com/gitblit/models/RepositoryModel.java @@ -17,6 +17,7 @@ package com.gitblit.models; import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -43,7 +44,7 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel // field names are reflectively mapped in EditRepository page
public String name;
public String description;
- public String owner;
+ public List<String> owners;
public Date lastChange;
public boolean hasCommits;
public boolean showRemoteBranches;
@@ -82,6 +83,7 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel public transient boolean isCollectingGarbage;
public Date lastGC;
+ public String sparkleshareId;
public RepositoryModel() {
this("", "", "", new Date(0));
@@ -90,13 +92,15 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel public RepositoryModel(String name, String description, String owner, Date lastchange) {
this.name = name;
this.description = description;
- this.owner = owner;
this.lastChange = lastchange;
this.accessRestriction = AccessRestrictionType.NONE;
this.authorizationControl = AuthorizationControl.NAMED;
this.federationSets = new ArrayList<String>();
this.federationStrategy = FederationStrategy.FEDERATE_THIS;
this.projectPath = StringUtils.getFirstPathElement(name);
+ this.owners = new ArrayList<String>();
+
+ addOwner(owner);
}
public List<String> getLocalBranches() {
@@ -161,7 +165,10 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel }
public boolean isOwner(String username) {
- return owner != null && username != null && owner.equalsIgnoreCase(username);
+ if (StringUtils.isEmpty(username) || ArrayUtils.isEmpty(owners)) {
+ return false;
+ }
+ return owners.contains(username.toLowerCase());
}
public boolean isPersonalRepository() {
@@ -176,6 +183,10 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel return !accessRestriction.atLeast(AccessRestrictionType.VIEW);
}
+ public boolean isSparkleshared() {
+ return !StringUtils.isEmpty(sparkleshareId);
+ }
+
public RepositoryModel cloneAs(String cloneName) {
RepositoryModel clone = new RepositoryModel();
clone.originRepository = name;
@@ -193,6 +204,40 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel clone.useTickets = useTickets;
clone.skipSizeCalculation = skipSizeCalculation;
clone.skipSummaryMetrics = skipSummaryMetrics;
+ clone.sparkleshareId = sparkleshareId;
return clone;
}
-}
\ No newline at end of file +
+ public void addOwner(String username) {
+ if (!StringUtils.isEmpty(username)) {
+ String name = username.toLowerCase();
+ // a set would be more efficient, but this complicates JSON
+ // deserialization so we enforce uniqueness with an arraylist
+ if (!owners.contains(name)) {
+ owners.add(name);
+ }
+ }
+ }
+
+ public void removeOwner(String username) {
+ if (!StringUtils.isEmpty(username)) {
+ owners.remove(username.toLowerCase());
+ }
+ }
+
+ public void addOwners(Collection<String> usernames) {
+ if (!ArrayUtils.isEmpty(usernames)) {
+ for (String username : usernames) {
+ addOwner(username);
+ }
+ }
+ }
+
+ public void removeOwners(Collection<String> usernames) {
+ if (!ArrayUtils.isEmpty(owners)) {
+ for (String username : usernames) {
+ removeOwner(username);
+ }
+ }
+ }
+} diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java index ac67ff78..bec011d9 100644 --- a/src/com/gitblit/models/UserModel.java +++ b/src/com/gitblit/models/UserModel.java @@ -29,6 +29,7 @@ import java.util.TreeSet; import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AccountType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
@@ -73,15 +74,22 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel> // non-persisted fields
public boolean isAuthenticated;
+ public AccountType accountType;
public UserModel(String username) {
this.username = username;
this.isAuthenticated = true;
+ this.accountType = AccountType.LOCAL;
}
private UserModel() {
this.username = "$anonymous";
this.isAuthenticated = false;
+ this.accountType = AccountType.LOCAL;
+ }
+
+ public boolean isLocalAccount() {
+ return accountType.isLocal();
}
/**
@@ -100,8 +108,7 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel> @Deprecated
@Unused
public boolean canAccessRepository(RepositoryModel repository) {
- boolean isOwner = !StringUtils.isEmpty(repository.owner)
- && repository.owner.equals(username);
+ boolean isOwner = repository.isOwner(username);
boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);
return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())
|| hasTeamAccess(repository.name) || allowAuthenticated;
diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java index ef3a55e7..732fdeb1 100644 --- a/src/com/gitblit/utils/ActivityUtils.java +++ b/src/com/gitblit/utils/ActivityUtils.java @@ -36,9 +36,9 @@ import org.eclipse.jgit.revwalk.RevCommit; import com.gitblit.GitBlit;
import com.gitblit.models.Activity;
-import com.gitblit.models.Activity.RepositoryCommit;
import com.gitblit.models.GravatarProfile;
import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryCommit;
import com.gitblit.models.RepositoryModel;
import com.google.gson.reflect.TypeToken;
diff --git a/src/com/gitblit/utils/ArrayUtils.java b/src/com/gitblit/utils/ArrayUtils.java index 41d110a3..65834673 100644 --- a/src/com/gitblit/utils/ArrayUtils.java +++ b/src/com/gitblit/utils/ArrayUtils.java @@ -15,7 +15,9 @@ */
package com.gitblit.utils;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
/**
@@ -41,4 +43,32 @@ public class ArrayUtils { public static boolean isEmpty(Collection<?> collection) {
return collection == null || collection.size() == 0;
}
+
+ public static String toString(Collection<?> collection) {
+ if (isEmpty(collection)) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (Object o : collection) {
+ sb.append(o.toString()).append(", ");
+ }
+ // trim trailing comma-space
+ sb.setLength(sb.length() - 2);
+ return sb.toString();
+ }
+
+ public static Collection<String> fromString(String value) {
+ if (StringUtils.isEmpty(value)) {
+ value = "";
+ }
+ List<String> list = new ArrayList<String>();
+ String [] values = value.split(",|;");
+ for (String v : values) {
+ String string = v.trim();
+ if (!StringUtils.isEmpty(string)) {
+ list.add(string);
+ }
+ }
+ return list;
+ }
}
diff --git a/src/com/gitblit/utils/FileUtils.java b/src/com/gitblit/utils/FileUtils.java index 0b8aeb4a..a21b5128 100644 --- a/src/com/gitblit/utils/FileUtils.java +++ b/src/com/gitblit/utils/FileUtils.java @@ -176,19 +176,17 @@ public class FileUtils { public static long folderSize(File directory) {
if (directory == null || !directory.exists()) {
return -1;
- }
- if (directory.isFile()) {
- return directory.length();
- }
- long length = 0;
- for (File file : directory.listFiles()) {
- if (file.isFile()) {
- length += file.length();
- } else {
+ }
+ if (directory.isDirectory()) {
+ long length = 0;
+ for (File file : directory.listFiles()) {
length += folderSize(file);
}
+ return length;
+ } else if (directory.isFile()) {
+ return directory.length();
}
- return length;
+ return 0;
}
/**
@@ -276,4 +274,19 @@ public class FileUtils { return path.getAbsoluteFile();
}
}
+
+ public static File resolveParameter(String parameter, File aFolder, String path) {
+ if (aFolder == null) {
+ // strip any parameter reference
+ path = path.replace(parameter, "").trim();
+ if (path.length() > 0 && path.charAt(0) == '/') {
+ // strip leading /
+ path = path.substring(1);
+ }
+ } else if (path.contains(parameter)) {
+ // replace parameter with path
+ path = path.replace(parameter, aFolder.getAbsolutePath());
+ }
+ return new File(path);
+ }
}
diff --git a/src/com/gitblit/utils/IssueUtils.java b/src/com/gitblit/utils/IssueUtils.java index 7b24ccf7..dd09235b 100644 --- a/src/com/gitblit/utils/IssueUtils.java +++ b/src/com/gitblit/utils/IssueUtils.java @@ -76,9 +76,9 @@ public class IssueUtils { public abstract boolean accept(IssueModel issue);
}
- public static final String GB_ISSUES = "refs/heads/gb-issues";
+ public static final String GB_ISSUES = "refs/gitblit/issues";
- static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
+ static final Logger LOGGER = LoggerFactory.getLogger(IssueUtils.class);
/**
* Log an error message and exception.
@@ -111,7 +111,13 @@ public class IssueUtils { * @return a refmodel for the gb-issues branch or null
*/
public static RefModel getIssuesBranch(Repository repository) {
- return JGitUtils.getBranch(repository, "gb-issues");
+ List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT);
+ for (RefModel ref : refs) {
+ if (ref.reference.getName().equals(GB_ISSUES)) {
+ return ref;
+ }
+ }
+ return null;
}
/**
@@ -374,7 +380,7 @@ public class IssueUtils { String issuePath = getIssuePath(issueId);
RevTree tree = JGitUtils.getCommit(repository, GB_ISSUES).getTree();
byte[] content = JGitUtils
- .getByteContent(repository, tree, issuePath + "/" + attachment.id);
+ .getByteContent(repository, tree, issuePath + "/" + attachment.id, false);
attachment.content = content;
attachment.size = content.length;
return attachment;
@@ -394,7 +400,7 @@ public class IssueUtils { public static IssueModel createIssue(Repository repository, Change change) {
RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
- JGitUtils.createOrphanBranch(repository, "gb-issues", null);
+ JGitUtils.createOrphanBranch(repository, GB_ISSUES, null);
}
if (StringUtils.isEmpty(change.author)) {
@@ -471,7 +477,7 @@ public class IssueUtils { RefModel issuesBranch = getIssuesBranch(repository);
if (issuesBranch == null) {
- throw new RuntimeException("gb-issues branch does not exist!");
+ throw new RuntimeException(GB_ISSUES + " does not exist!");
}
if (StringUtils.isEmpty(issueId)) {
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java index beaa27da..1f2ae943 100644 --- a/src/com/gitblit/utils/JGitUtils.java +++ b/src/com/gitblit/utils/JGitUtils.java @@ -537,7 +537,7 @@ public class JGitUtils { * @param path
* @return content as a byte []
*/
- public static byte[] getByteContent(Repository repository, RevTree tree, final String path) {
+ public static byte[] getByteContent(Repository repository, RevTree tree, final String path, boolean throwError) {
RevWalk rw = new RevWalk(repository);
TreeWalk tw = new TreeWalk(repository);
tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
@@ -572,7 +572,9 @@ public class JGitUtils { }
}
} catch (Throwable t) {
- error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
+ if (throwError) {
+ error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
+ }
} finally {
rw.dispose();
tw.release();
@@ -591,7 +593,7 @@ public class JGitUtils { * @return UTF-8 string content
*/
public static String getStringContent(Repository repository, RevTree tree, String blobPath, String... charsets) {
- byte[] content = getByteContent(repository, tree, blobPath);
+ byte[] content = getByteContent(repository, tree, blobPath, true);
if (content == null) {
return null;
}
@@ -741,11 +743,7 @@ public class JGitUtils { df.setDetectRenames(true);
List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
for (DiffEntry diff : diffs) {
- String objectId = null;
- if (FileMode.GITLINK.equals(diff.getNewMode())) {
- objectId = diff.getNewId().name();
- }
-
+ String objectId = diff.getNewId().name();
if (diff.getChangeType().equals(ChangeType.DELETE)) {
list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
.getNewMode().getBits(), objectId, commit.getId().getName(), diff
@@ -1457,6 +1455,20 @@ public class JGitUtils { int maxCount) {
return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
}
+
+ /**
+ * Returns the list of refs in the specified base ref. If repository does
+ * not exist or is empty, an empty list is returned.
+ *
+ * @param repository
+ * @param fullName
+ * if true, /refs/yadayadayada is returned. If false,
+ * yadayadayada is returned.
+ * @return list of refs
+ */
+ public static List<RefModel> getRefs(Repository repository, String baseRef) {
+ return getRefs(repository, baseRef, true, -1);
+ }
/**
* Returns a list of references in the repository matching "refs". If the
@@ -1570,7 +1582,7 @@ public class JGitUtils { */
public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
- byte [] blob = getByteContent(repository, tree, ".gitmodules");
+ byte [] blob = getByteContent(repository, tree, ".gitmodules", false);
if (blob == null) {
return list;
}
@@ -1604,6 +1616,32 @@ public class JGitUtils { }
return null;
}
+
+ public static String getSubmoduleCommitId(Repository repository, String path, RevCommit commit) {
+ String commitId = null;
+ RevWalk rw = new RevWalk(repository);
+ TreeWalk tw = new TreeWalk(repository);
+ tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
+ try {
+ tw.reset(commit.getTree());
+ while (tw.next()) {
+ if (tw.isSubtree() && !path.equals(tw.getPathString())) {
+ tw.enterSubtree();
+ continue;
+ }
+ if (FileMode.GITLINK == tw.getFileMode(0)) {
+ commitId = tw.getObjectId(0).getName();
+ break;
+ }
+ }
+ } catch (Throwable t) {
+ error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
+ } finally {
+ rw.dispose();
+ tw.release();
+ }
+ return commitId;
+ }
/**
* Returns the list of notes entered about the commit from the refs/notes
@@ -1720,4 +1758,18 @@ public class JGitUtils { }
return success;
}
+
+ /**
+ * Reads the sparkleshare id, if present, from the repository.
+ *
+ * @param repository
+ * @return an id or null
+ */
+ public static String getSparkleshareId(Repository repository) {
+ byte[] content = getByteContent(repository, null, ".sparkleshare", false);
+ if (content == null) {
+ return null;
+ }
+ return StringUtils.decodeString(content);
+ }
}
diff --git a/src/com/gitblit/utils/MetricUtils.java b/src/com/gitblit/utils/MetricUtils.java index e9e1fa52..26e4581c 100644 --- a/src/com/gitblit/utils/MetricUtils.java +++ b/src/com/gitblit/utils/MetricUtils.java @@ -210,6 +210,7 @@ public class MetricUtils { p = rev.getAuthorIdent().getEmailAddress().toLowerCase();
}
}
+ p = p.replace('\n',' ').replace('\r', ' ').trim();
if (!metricMap.containsKey(p)) {
metricMap.put(p, new Metric(p));
}
diff --git a/src/com/gitblit/utils/PushLogUtils.java b/src/com/gitblit/utils/PushLogUtils.java new file mode 100644 index 00000000..665533b3 --- /dev/null +++ b/src/com/gitblit/utils/PushLogUtils.java @@ -0,0 +1,344 @@ +/* + * 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.utils; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.models.PathModel.PathChangeModel; +import com.gitblit.models.PushLogEntry; +import com.gitblit.models.RefModel; +import com.gitblit.models.UserModel; + +/** + * Utility class for maintaining a pushlog within a git repository on an + * orphan branch. + * + * @author James Moger + * + */ +public class PushLogUtils { + + public static final String GB_PUSHES = "refs/gitblit/pushes"; + + static final Logger LOGGER = LoggerFactory.getLogger(PushLogUtils.class); + + /** + * Log an error message and exception. + * + * @param t + * @param repository + * if repository is not null it MUST be the {0} parameter in the + * pattern. + * @param pattern + * @param objects + */ + private static void error(Throwable t, Repository repository, String pattern, Object... objects) { + List<Object> parameters = new ArrayList<Object>(); + if (objects != null && objects.length > 0) { + for (Object o : objects) { + parameters.add(o); + } + } + if (repository != null) { + parameters.add(0, repository.getDirectory().getAbsolutePath()); + } + LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t); + } + + /** + * Returns a RefModel for the gb-pushes branch in the repository. If the + * branch can not be found, null is returned. + * + * @param repository + * @return a refmodel for the gb-pushes branch or null + */ + public static RefModel getPushLogBranch(Repository repository) { + List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT); + for (RefModel ref : refs) { + if (ref.reference.getName().equals(GB_PUSHES)) { + return ref; + } + } + return null; + } + + /** + * Updates a push log. + * + * @param user + * @param repository + * @param commands + * @return true, if the update was successful + */ + public static boolean updatePushLog(UserModel user, Repository repository, + Collection<ReceiveCommand> commands) { + RefModel pushlogBranch = getPushLogBranch(repository); + if (pushlogBranch == null) { + JGitUtils.createOrphanBranch(repository, GB_PUSHES, null); + } + + boolean success = false; + String message = "push"; + + try { + ObjectId headId = repository.resolve(GB_PUSHES + "^{commit}"); + ObjectInserter odi = repository.newObjectInserter(); + try { + // Create the in-memory index of the push log entry + DirCache index = createIndex(repository, headId, commands); + ObjectId indexTreeId = index.writeTree(odi); + + PersonIdent ident = new PersonIdent(user.getDisplayName(), + user.emailAddress == null ? user.username:user.emailAddress); + + // Create a commit object + CommitBuilder commit = new CommitBuilder(); + commit.setAuthor(ident); + commit.setCommitter(ident); + commit.setEncoding(Constants.CHARACTER_ENCODING); + commit.setMessage(message); + commit.setParentId(headId); + commit.setTreeId(indexTreeId); + + // Insert the commit into the repository + ObjectId commitId = odi.insert(commit); + odi.flush(); + + RevWalk revWalk = new RevWalk(repository); + try { + RevCommit revCommit = revWalk.parseCommit(commitId); + RefUpdate ru = repository.updateRef(GB_PUSHES); + ru.setNewObjectId(commitId); + ru.setExpectedOldObjectId(headId); + ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false); + Result rc = ru.forceUpdate(); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: + success = true; + break; + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, + ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, GB_PUSHES, commitId.toString(), + rc)); + } + } finally { + revWalk.release(); + } + } finally { + odi.release(); + } + } catch (Throwable t) { + error(t, repository, "Failed to commit pushlog entry to {0}"); + } + return success; + } + + /** + * Creates an in-memory index of the push log entry. + * + * @param repo + * @param headId + * @param commands + * @return an in-memory index + * @throws IOException + */ + private static DirCache createIndex(Repository repo, ObjectId headId, + Collection<ReceiveCommand> commands) throws IOException { + + DirCache inCoreIndex = DirCache.newInCore(); + DirCacheBuilder dcBuilder = inCoreIndex.builder(); + ObjectInserter inserter = repo.newObjectInserter(); + + long now = System.currentTimeMillis(); + Set<String> ignorePaths = new TreeSet<String>(); + try { + // add receive commands to the temporary index + for (ReceiveCommand command : commands) { + // use the ref names as the path names + String path = command.getRefName(); + ignorePaths.add(path); + + StringBuilder change = new StringBuilder(); + change.append(command.getType().name()).append(' '); + switch (command.getType()) { + case CREATE: + change.append(ObjectId.zeroId().getName()); + change.append(' '); + change.append(command.getNewId().getName()); + break; + case UPDATE: + case UPDATE_NONFASTFORWARD: + change.append(command.getOldId().getName()); + change.append(' '); + change.append(command.getNewId().getName()); + break; + case DELETE: + change = null; + break; + } + if (change == null) { + // ref deleted + continue; + } + String content = change.toString(); + + // create an index entry for this attachment + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setLength(content.length()); + dcEntry.setLastModified(now); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + + // insert object + dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))); + + // add to temporary in-core index + dcBuilder.add(dcEntry); + } + + // Traverse HEAD to add all other paths + TreeWalk treeWalk = new TreeWalk(repo); + int hIdx = -1; + if (headId != null) + hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId)); + treeWalk.setRecursive(true); + + while (treeWalk.next()) { + String path = treeWalk.getPathString(); + CanonicalTreeParser hTree = null; + if (hIdx != -1) + hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); + if (!ignorePaths.contains(path)) { + // add entries from HEAD for all other paths + if (hTree != null) { + // create a new DirCacheEntry with data retrieved from + // HEAD + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setObjectId(hTree.getEntryObjectId()); + dcEntry.setFileMode(hTree.getEntryFileMode()); + + // add to temporary in-core index + dcBuilder.add(dcEntry); + } + } + } + + // release the treewalk + treeWalk.release(); + + // finish temporary in-core index used for this commit + dcBuilder.finish(); + } finally { + inserter.release(); + } + return inCoreIndex; + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository) { + return getPushLog(repositoryName, repository, null, -1); + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, int maxCount) { + return getPushLog(repositoryName, repository, null, maxCount); + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, Date minimumDate) { + return getPushLog(repositoryName, repository, minimumDate, -1); + } + + public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, Date minimumDate, int maxCount) { + List<PushLogEntry> list = new ArrayList<PushLogEntry>(); + RefModel ref = getPushLogBranch(repository); + if (ref == null) { + return list; + } + List<RevCommit> pushes; + if (minimumDate == null) { + pushes = JGitUtils.getRevLog(repository, GB_PUSHES, 0, maxCount); + } else { + pushes = JGitUtils.getRevLog(repository, GB_PUSHES, minimumDate); + } + for (RevCommit push : pushes) { + if (push.getAuthorIdent().getName().equalsIgnoreCase("gitblit")) { + // skip gitblit/internal commits + continue; + } + Date date = push.getAuthorIdent().getWhen(); + UserModel user = new UserModel(push.getAuthorIdent().getEmailAddress()); + user.displayName = push.getAuthorIdent().getName(); + PushLogEntry log = new PushLogEntry(repositoryName, date, user); + list.add(log); + List<PathChangeModel> changedRefs = JGitUtils.getFilesInCommit(repository, push); + for (PathChangeModel change : changedRefs) { + switch (change.changeType) { + case DELETE: + log.updateRef(change.path, ReceiveCommand.Type.DELETE); + break; + case ADD: + log.updateRef(change.path, ReceiveCommand.Type.CREATE); + default: + String content = JGitUtils.getStringContent(repository, push.getTree(), change.path); + String [] fields = content.split(" "); + log.updateRef(change.path, ReceiveCommand.Type.valueOf(fields[0])); + String oldId = fields[1]; + String newId = fields[2]; + List<RevCommit> pushedCommits = JGitUtils.getRevLog(repository, oldId, newId); + for (RevCommit pushedCommit : pushedCommits) { + log.addCommit(change.path, pushedCommit); + } + } + } + } + Collections.sort(list); + return list; + } +} diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java index 86840048..86823db5 100644 --- a/src/com/gitblit/utils/StringUtils.java +++ b/src/com/gitblit/utils/StringUtils.java @@ -719,4 +719,18 @@ public class StringUtils { Matcher m = p.matcher(input);
return m.matches();
}
+
+ /**
+ * Removes new line and carriage return chars from a string.
+ * If input value is null an empty string is returned.
+ *
+ * @param input
+ * @return a sanitized or empty string
+ */
+ public static String removeNewlines(String input) {
+ if (input == null) {
+ return "";
+ }
+ return input.replace('\n',' ').replace('\r', ' ').trim();
+ }
}
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index 16f76411..a993f9f1 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -440,4 +440,6 @@ gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certif gb.validity = validity
gb.siteName = site name
gb.siteNameDescription = short, descriptive name of your server
-gb.excludeFromActivity = exclude from activity page
\ No newline at end of file +gb.excludeFromActivity = exclude from activity page
+gb.isSparkleshared = repository is Sparkleshared
+gb.owners = owners
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_es.properties b/src/com/gitblit/wicket/GitBlitWebApp_es.properties index d83fcef3..64c9ca13 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp_es.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp_es.properties @@ -163,7 +163,7 @@ gb.manage = Administrar gb.lastLogin = \u00DAltimo acceso
gb.skipSizeCalculation = Saltar comprobaciones de tama\u00F1o
gb.skipSizeCalculationDescription = No calcular el tama\u00F1o del repositorio (Reduce tiempo de carga de la p\u00E1gina)
-gb.skipSummaryMetrics = Saltar el resumen de estad\u00EDsticas
+gb.skipSummaryMetrics = Saltar resumen de estad\u00EDsticas
gb.skipSummaryMetricsDescription = No calcular estad\u00EDsticas (Reduce tiempo de carga de la p\u00E1gina)
gb.accessLevel = Nivel de acceso
gb.default = Predeterminado
@@ -341,8 +341,103 @@ gb.isFork = Es bifurcado gb.canCreate = Puede crear
gb.canCreateDescription = Puede crear repositorios personales
gb.illegalPersonalRepositoryLocation = Tu repositorio personal debe estar ubicado en \"{0}\"
-gb.verifyCommitter = Acreditar consignador
-gb.verifyCommitterDescription = Require que la acreditaci\u00F3n del consignador coincida con la de la cuenta del usuario en Gitblt (es necesario "--no-ff" al empujar para que consignador se acredite)
+gb.verifyCommitter = Consignador acreditado
+gb.verifyCommitterDescription = Require que la acreditaci\u00F3n del consignador coincida con la de la cuenta del usuario en Gitblt
+gb.verifyCommitterNote = es obligatorio "--no-ff" al empujar para que el consignador se acredite
gb.repositoryPermissions = Permisos del repositorio
gb.userPermissions = Permisos de usuarios
gb.teamPermissions = Permisos de equipos
+gb.add = A\u00F1adir
+gb.noPermission = BORRAR ESTE PERMISO
+gb.excludePermission = {0} (excluir)
+gb.viewPermission = {0} (ver)
+gb.clonePermission = {0} (clonar)
+gb.pushPermission = {0} (empujar)
+gb.createPermission = {0} (empujar, ref creaci\u00F3n)
+gb.deletePermission = {0} (empujar, ref creaci\u00F3n+borrado)
+gb.rewindPermission = {0} (empujar, ref creaci\u00F3n+borrado+supresi\u00F3n)
+gb.permission = Permisos
+gb.regexPermission = Estos permisos se ajustan desde la expresi\u00F3n regulare \"{0}\"
+gb.accessDenied = Acceso denegado
+gb.busyCollectingGarbage = Perd\u00F3n, Gitblit est\u00E1 ocupado quitando basura de {0}
+gb.gcPeriod = Periodo para GC
+gb.gcPeriodDescription = Duraci\u00F3n entre periodos de limpieza
+gb.gcThreshold = L\u00EDmites para GC
+gb.gcThresholdDescription = Tama\u00F1o m\u00EDnimo total de objetos sueltos para activar la recolecci\u00F3n inmediata de basura
+gb.ownerPermission = Propietario del repositorio
+gb.administrator = Admin
+gb.administratorPermission = Administrador de Gitblit
+gb.team = Equipo
+gb.teamPermission = Permisos ajustados para \"{0}\" mienbros de equipo
+gb.missing = \u00A1Omitido!
+gb.missingPermission = \u00A1Falta el repositorio de este permiso!
+gb.mutable = Alterables
+gb.specified = Espec\u00EDficos
+gb.effective = Efectivos
+gb.organizationalUnit = Unidad de organizaci\u00F3n
+gb.organization = Organizaci\u00F3n
+gb.locality = Localidad
+gb.stateProvince = Estado o provincia
+gb.countryCode = C\u00F3digo postal
+gb.properties = Propiedades
+gb.issued = Publicado
+gb.expires = Expira
+gb.expired = Expirado
+gb.expiring = Concluido
+gb.revoked = Revocado
+gb.serialNumber = N\u00FAmero de serie
+gb.certificates = Certificados
+gb.newCertificate = Nuevo certificado
+gb.revokeCertificate = Revocar certificado
+gb.sendEmail = Enviar correo
+gb.passwordHint = Recordatorio de contrase\u00F1a
+gb.ok = ok
+gb.invalidExpirationDate = \u00A1La fecha de expiraci\u00F3n no es v\u00E1lida!
+gb.passwordHintRequired = \u00A1Se requiere una pista para la contrase\u00F1a!
+gb.viewCertificate = Ver certificado
+gb.subject = Asunto
+gb.issuer = Emisor
+gb.validFrom = V\u00E1lido desde
+gb.validUntil = V\u00E1lido hasta
+gb.publicKey = Clave p\u00FAblica
+gb.signatureAlgorithm = Algoritmo de firma
+gb.sha1FingerPrint = Huella digital SHA-1
+gb.md5FingerPrint = Huella digital MD5
+gb.reason = Motivo
+gb.revokeCertificateReason = Por favor, selecciona un motivo por el que revocas el certificado
+gb.unspecified = Sin especificar
+gb.keyCompromise = Clave de compromiso
+gb.caCompromise = Compromiso CA
+gb.affiliationChanged = Afiliaci\u00F3n cambiada
+gb.superseded = Sustituida
+gb.cessationOfOperation = Cese de operaci\u00F3n
+gb.privilegeWithdrawn = Privilegios retirados
+gb.time.inMinutes = en {0} mints
+gb.time.inHours = en {0} horas
+gb.time.inDays = en {0} d\u00EDas
+gb.hostname = Nombre de host
+gb.hostnameRequired = Por favor, introduzca un nombre de host
+gb.newSSLCertificate = Nuevo certificado SSL del servidor
+gb.newCertificateDefaults = Nuevo certificado predeterminado
+gb.duration = Duraci\u00F3n
+gb.certificateRevoked = El cretificado {0,n\u00FAmero,0} ha sido revocado
+gb.clientCertificateGenerated = Nuevo certificado de cliente generado correctamente para {0}
+gb.sslCertificateGenerated = Nuevo certificado de SSL generado correctamente para {0}
+gb.newClientCertificateMessage = AVISO:\nLa 'contrase\u00F1a' no es la contrase\u00F1a del usuario, es la contrase\u00F1a para proteger el almac\u00E9n de claves del usuario. Esta contrase\u00F1a no se guarda por lo que tambi\u00E9n debe introducirse una "pista" que ser\u00E1 incluida en las instrucciones LEEME del usuario.
+gb.certificate = Certificado
+gb.emailCertificateBundle = Correo del cliente para el paquete del certificado
+gb.pleaseGenerateClientCertificate = Por favor, genera un certificado de cliente para {0}
+gb.clientCertificateBundleSent = Paquete de certificado de cliente {0} enviado
+gb.enterKeystorePassword = Por favor, introduzca la contrase\u00F1a del almac\u00E9n de claves de Gitblit
+gb.warning = Advertencia
+gb.jceWarning = Tu entorno de trabajo JAVA no contiene los archivos \"JCE Unlimited Strength Jurisdiction Policy\".\nEsto limita la longitud de la contrase\u00F1a que puedes usuar para cifrar el almac\u00E9n de claves a 7 caracteres.\nEstos archivos opcionales puedes descargarlos desde Oracle.\n\n\u00BFQuieres continuar y generar la infraestructura de certificados de todos modos?\n\nSi respondes No tu navegador te dirigir\u00E1 a la p\u00E1gina de descarga de Oracle para que pueda descargar dichos archivos.
+gb.maxActivityCommits = Actividad m\u00E1xima de consignas
+gb.maxActivityCommitsDescription = N\u00FAmero m\u00E1ximo de consignas a incluir en la p\u00E1gina de actividad
+gb.noMaximum = Sin m\u00E1ximos
+gb.attributes = Atributos
+gb.serveCertificate = Servidor https con este certificado
+gb.sslCertificateGeneratedRestart = Certificado SSL generado correctamente para {0}.\nDebes reiniciar Gitblit para usar el nuevo certificado.\n\nSi lo has iniciado con la opci\u00F3n '--alias' deber\u00E1s ajustar dicha opci\u00F3n a ''--alias {0}''.
+gb.validity = Vigencia
+gb.siteName = Nombre del sitio
+gb.siteNameDescription = Nombre corto y descriptivo de tu servidor
+gb.excludeFromActivity = Excluir de la p\u00E1gina de actividad
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties index 9582ef85..18eda26c 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties @@ -237,7 +237,7 @@ gb.passwordChanged = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uBCC0\uACBD \uC131\uACF5. gb.passwordChangeAborted = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD \uCDE8\uC18C\uB428. gb.pleaseSetRepositoryName = \uC800\uC7A5\uC18C \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694! gb.illegalLeadingSlash = \uC800\uC7A5\uC18C \uC774\uB984 \uB610\uB294 \uD3F4\uB354\uB294 (/) \uB85C \uC2DC\uC791\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. -gb.illegalRelativeSlash = \uC0C1\uB300 \uACBD\uB85C \uC9C0\uC815 (../) \uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. +gb.illegalRelativeSlash = \uC0C1\uB300 \uACBD\uB85C \uC9C0\uC815 (../) \uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.. gb.illegalCharacterRepositoryName = \uBB38\uC790 ''{0}'' \uC800\uC7A5\uC18C \uC774\uB984\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694! gb.selectAccessRestriction = \uC811\uC18D \uAD8C\uD55C\uC744 \uC120\uD0DD\uD558\uC138\uC694! gb.selectFederationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45\uC744 \uC120\uD0DD\uD558\uC138\uC694! @@ -287,7 +287,7 @@ gb.none = none gb.line = \uB77C\uC778 gb.content = \uB0B4\uC6A9 gb.empty = empty -gb.inherited = inherited +gb.inherited = \uC0C1\uC18D gb.deleteRepository = \"{0}\" \uC800\uC7A5\uC18C\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694? gb.repositoryDeleted = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C\uB428. gb.repositoryDeleteFailed = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C \uC2E4\uD328! @@ -315,3 +315,129 @@ gb.allowAuthenticatedDescription = \uBAA8\uB4E0 \uC778\uC99D\uB41C \uC720\uC800\ gb.allowNamedDescription = \uC774\uB984\uC73C\uB85C \uC720\uC800\uB098 \uD300\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC gb.markdownFailure = \uB9C8\uD06C\uB2E4\uC6B4 \uCEE8\uD150\uD2B8 \uD30C\uC2F1 \uC624\uB958! gb.clearCache = \uCE90\uC2DC \uC9C0\uC6B0\uAE30 +gb.projects = \uD504\uB85C\uC81D\uD2B8\uB4E4 +gb.project = \uD504\uB85C\uC81D\uD2B8 +gb.allProjects = \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8 +gb.copyToClipboard = \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC +gb.fork = \uD3EC\uD06C +gb.forks = \uD3EC\uD06C +gb.forkRepository = fork {0}? +gb.repositoryForked = {0} \uD3EC\uD06C\uB428 +gb.repositoryForkFailed= \uD3EC\uD06C\uC2E4\uD328 +gb.personalRepositories = \uAC1C\uC778 \uC800\uC7A5\uC18C +gb.allowForks = \uD3EC\uD06C \uD5C8\uC6A9 +gb.allowForksDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC70 \uD3EC\uD06C \uD5C8\uC6A9 +gb.forkedFrom = forked from +gb.canFork = \uD3EC\uD06C \uAC00\uB2A5 +gb.canForkDescription = \uD5C8\uC6A9\uB41C \uC800\uC7A5\uC18C\uB97C \uAC1C\uC778 \uC800\uC7A5\uC18C\uC5D0 \uD3EC\uD06C\uD560 \uC218 \uC788\uC74C +gb.myFork = \uB0B4 \uD3EC\uD06C \uBCF4\uAE30 +gb.forksProhibited = \uD3EC\uD06C \uCC28\uB2E8\uB428 +gb.forksProhibitedWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uD3EC\uD06C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC74C +gb.noForks = {0} \uB294 \uD3EC\uD06C \uC5C6\uC74C +gb.forkNotAuthorized = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. {0} \uD3EC\uD06C\uC5D0 \uC811\uC18D \uC778\uC99D\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. +gb.forkInProgress = \uD504\uD06C \uC9C4\uD589 \uC911 +gb.preparingFork = \uD3EC\uD06C \uC900\uBE44 \uC911... +gb.isFork = \uD3EC\uD06C\uD55C +gb.canCreate = \uC0DD\uC131 \uAC00\uB2A5 +gb.canCreateDescription = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4 \uC218 \uC788\uC74C +gb.illegalPersonalRepositoryLocation = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB294 \uBC18\uB4DC\uC2DC \"{0}\" \uC5D0 \uC704\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4. +gb.verifyCommitter = \uCEE4\uBBF8\uD130 \uD655\uC778 +gb.verifyCommitterDescription = \uCEE4\uBBF8\uD130 ID \uB294 Gitblit ID \uC640 \uB9E4\uCE58\uB418\uC5B4\uC57C \uD568 +gb.verifyCommitterNote = \uBAA8\uB4E0 \uBA38\uC9C0\uB294 \uCEE4\uBBF8\uD130 ID \uB97C \uC801\uC6A9\uD558\uAE30 \uC704\uD574 "--no-ff" \uC635\uC158 \uD544\uC694 +gb.repositoryPermissions = \uC800\uC7A5\uC18C \uAD8C\uD55C +gb.userPermissions = \uC720\uC800 \uAD8C\uD55C +gb.teamPermissions = \uD300 \uAD8C\uD55C +gb.add = \uCD94\uAC00 +gb.noPermission = \uC774 \uAD8C\uD55C \uC0AD\uC81C +gb.excludePermission = {0} (\uC81C\uC678) +gb.viewPermission = {0} (\uBCF4\uAE30) +gb.clonePermission = {0} (\uD074\uB860) +gb.pushPermission = {0} (\uD478\uC2DC) +gb.createPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131) +gb.deletePermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C) +gb.rewindPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C+\uB418\uB3CC\uB9AC\uAE30) +gb.permission = \uAD8C\uD55C +gb.regexPermission = \uC774 \uAD8C\uD55C\uC740 \uC815\uADDC\uC2DD \"{0}\" \uB85C\uBD80\uD130 \uC124\uC815\uB428 +gb.accessDenied = \uC811\uC18D \uAC70\uBD80 +gb.busyCollectingGarbage = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. Gitblit \uC740 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158 \uC911\uC785\uB2C8\uB2E4. {0} +gb.gcPeriod = GC \uC8FC\uAE30 +gb.gcPeriodDescription = \uAC00\uBE44\uC9C0 \uD074\uB809\uC158\uAC04\uC758 \uC2DC\uAC04 \uAC04\uACA9 +gb.gcThreshold = GC \uAE30\uC900\uC810 +gb.gcThresholdDescription = \uC870\uAE30 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158\uC744 \uBC1C\uC0DD\uC2DC\uD0A4\uAE30 \uC704\uD55C \uC624\uBE0C\uC81D\uD2B8\uB4E4\uC758 \uCD5C\uC18C \uC804\uCCB4 \uD06C\uAE30 +gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108 +gb.administrator = \uAD00\uB9AC\uC790 +gb.administratorPermission = Gitblit \uAD00\uB9AC\uC790 +gb.team = \uD300 +gb.teamPermission = \"{0}\" \uD300 \uBA64\uBC84\uC5D0 \uAD8C\uD55C \uC124\uC815\uB428 +gb.missing = \uB204\uB77D! +gb.missingPermission = \uC774 \uAD8C\uD55C\uC744 \uC704\uD55C \uC800\uC7A5\uC18C \uB204\uB77D! +gb.mutable = \uAC00\uBCC0 +gb.specified = \uC9C0\uC815\uB41C +gb.effective = \uD6A8\uACFC\uC801 +gb.organizationalUnit = \uC870\uC9C1 +gb.organization = \uAE30\uAD00 +gb.locality = \uC704\uCE58 +gb.stateProvince = \uB3C4 \uB610\uB294 \uC8FC +gb.countryCode = \uAD6D\uAC00\uCF54\uB4DC +gb.properties = \uC18D\uC131 +gb.issued = \uBC1C\uAE09\uB428 +gb.expires = \uB9CC\uB8CC +gb.expired = \uB9CC\uB8CC\uB428 +gb.expiring = \uB9CC\uB8CC\uC911 +gb.revoked = \uD3D0\uAE30\uB428 +gb.serialNumber = \uC77C\uB828\uBC88\uD638 +gb.certificates = \uC778\uC99D\uC11C +gb.newCertificate = \uC0C8 \uC778\uC99D\uC11C +gb.revokeCertificate = \uC778\uC99D\uC11C \uD3D0\uAE30 +gb.sendEmail = \uBA54\uC77C \uBCF4\uB0B4\uAE30 +gb.passwordHint = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 +gb.ok = ok +gb.invalidExpirationDate = \uB9D0\uB8CC\uC77C\uC790 \uC624\uB958! +gb.passwordHintRequired = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 \uD544\uC218! +gb.viewCertificate = \uC778\uC99D\uC11C \uBCF4\uAE30 +gb.subject = \uC774\uB984 +gb.issuer = \uBC1C\uAE09\uC790 +gb.validFrom = \uC720\uD6A8\uAE30\uAC04 (\uC2DC\uC791) +gb.validUntil = \uC720\uD6A8\uAE30\uAC04 (\uB05D) +gb.publicKey = \uACF5\uAC1C\uD0A4 +gb.signatureAlgorithm = \uC11C\uBA85 \uC54C\uACE0\uB9AC\uC998 +gb.sha1FingerPrint = SHA-1 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998 +gb.md5FingerPrint = MD5 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998 +gb.reason = \uC774\uC720 +gb.revokeCertificateReason = \uC778\uC99D\uC11C \uD574\uC9C0\uC774\uC720\uB97C \uC120\uD0DD\uD558\uC138\uC694 +gb.unspecified = \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC74C +gb.keyCompromise = \uD0A4 \uC190\uC0C1 +gb.caCompromise = CA \uC190\uC0C1 +gb.affiliationChanged = \uAD00\uACC4 \uBCC0\uACBD\uB428 +gb.superseded = \uB300\uCCB4\uB428 +gb.cessationOfOperation = \uC6B4\uC601 \uC911\uC9C0 +gb.privilegeWithdrawn = \uAD8C\uD55C \uCCA0\uD68C\uB428 +gb.time.inMinutes = {0} \uBD84 +gb.time.inHours = {0} \uC2DC\uAC04 +gb.time.inDays = {0} \uC77C +gb.hostname = \uD638\uC2A4\uD2B8\uBA85 +gb.hostnameRequired = \uD638\uC2A4\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694 +gb.newSSLCertificate = \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C +gb.newCertificateDefaults = \uC0C8 \uC778\uC99D\uC11C \uAE30\uBCF8 +gb.duration = \uAE30\uAC04 +gb.certificateRevoked = \uC778\uC99D\uC11C {0,number,0} \uB294 \uD3D0\uAE30\uB418\uC5C8\uC2B5\uB2C8\uB2E4 +gb.clientCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5 +gb.sslCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uC11C\uBC84 SSL \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5 +gb.newClientCertificateMessage = \uB178\uD2B8:\n'\uD328\uC2A4\uC6CC\uB4DC' \uB294 \uC720\uC800\uC758 \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC544\uB2C8\uB77C \uC720\uC800\uC758 \uD0A4\uC2A4\uD1A0\uC5B4 \uB97C \uBCF4\uD638\uD558\uAE30 \uC704\uD55C \uAC83\uC785\uB2C8\uB2E4. \uC774 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790 README \uC9C0\uCE68\uC5D0 \uD3EC\uD568\uB420 '\uD78C\uD2B8' \uB97C \uBC18\uB4DC\uC2DC \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4. +gb.certificate = \uC778\uC99D\uC11C +gb.emailCertificateBundle = \uC774\uBA54\uC77C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 +gb.pleaseGenerateClientCertificate = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C\uB97C \uC0DD\uC131\uD558\uC138\uC694 +gb.clientCertificateBundleSent = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 \uBC1C\uC1A1\uB428 +gb.enterKeystorePassword = Gitblit \uD0A4\uC2A4\uD1A0\uC5B4 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694 +gb.warning = \uACBD\uACE0 +gb.jceWarning = \uC790\uBC14 \uC2E4\uD589\uD658\uACBD\uC5D0 \"JCE Unlimited Strength Jurisdiction Policy\" \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC774\uAC83\uC740 \uD0A4\uC800\uC7A5\uC18C \uC554\uD638\uD654\uC5D0 \uC0AC\uC6A9\uB418\uB294 \uD328\uC2A4\uC6CC\uB4DC\uC758 \uAE38\uC774\uB294 7\uC790\uB85C \uC81C\uD55C\uD569\uB2C8\uB2E4.\n\uC774 \uC815\uCC45 \uD30C\uC77C\uC740 Oracle \uC5D0\uC11C \uC120\uD0DD\uC801\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD574\uC57C \uD569\uB2C8\uB2E4.\n\n\uBB34\uC2DC\uD558\uACE0 \uC778\uC99D\uC11C \uC778\uD504\uB77C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n\uC544\uB2C8\uC624(No) \uB77C\uACE0 \uB2F5\uD558\uBA74 \uC815\uCC45\uD30C\uC77C\uC744 \uB2E4\uC6B4\uBC1B\uC744 \uC218 \uC788\uB294 Oracle \uB2E4\uC6B4\uB85C\uB4DC \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC548\uB0B4\uD560 \uAC83\uC785\uB2C8\uB2E4. +gb.maxActivityCommits = \uCD5C\uB300 \uC561\uD2F0\uBE44\uD2F0 \uCEE4\uBC0B +gb.maxActivityCommitsDescription = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0 \uD45C\uC2DC\uD560 \uCD5C\uB300 \uCEE4\uBC0B \uC218 +gb.noMaximum = \uBB34\uC81C\uD55C +gb.attributes = \uC18D\uC131 +gb.serveCertificate = \uC774 \uC778\uC99D\uC11C\uB85C https \uC81C\uACF5 +gb.sslCertificateGeneratedRestart = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C\uB97C \uC131\uACF5\uC801\uC73C\uB85C \uC0DD\uC131\uD558\uC600\uC2B5\uB2C8\uB2E4. \n\uC0C8 \uC778\uC99D\uC11C\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 Gitblit \uC744 \uC7AC\uC2DC\uC791 \uD574\uC57C \uD569\uB2C8\uB2E4.\n\n'--alias' \uD30C\uB77C\uBBF8\uD130\uB85C \uC2E4\uD589\uD55C\uB2E4\uBA74 ''--alias {0}'' \uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. +gb.validity = \uC720\uD6A8\uC131 +gb.siteName = \uC0AC\uC774\uD2B8 \uC774\uB984 +gb.siteNameDescription = \uC11C\uBC84\uC758 \uC9E6\uC740 \uC124\uBA85\uC774 \uD3EC\uD568\uB41C \uC774\uB984 +gb.excludeFromActivity = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0\uC11C \uC81C\uC678
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_nl.properties b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties new file mode 100644 index 00000000..5471ad8a --- /dev/null +++ b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties @@ -0,0 +1,443 @@ +gb.repository = repositorie
+gb.owner = eigenaar
+gb.description = omschrijving
+gb.lastChange = laatste wijziging
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = auteur
+gb.committer = committer
+gb.commit = commit
+gb.tree = tree
+gb.parent = parent
+gb.url = URL
+gb.history = historie
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = toegewezen
+gb.ticketOpenDate = open datum
+gb.ticketState = status
+gb.ticketComments = commentaar
+gb.view = view
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = meer commits...
+gb.allTags = alle tags...
+gb.allBranches = alle branches...
+gb.summary = samenvatting
+gb.ticket = ticket
+gb.newRepository = nieuwe repositorie
+gb.newUser = nieuwe gebruiker
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = eerste
+gb.pagePrevious = vorige
+gb.pageNext = volgende
+gb.head = HEAD
+gb.blame = blame
+gb.login = aanmelden
+gb.logout = afmelden
+gb.username = gebruikersnaam
+gb.password = wachtwoord
+gb.tagger = tagger
+gb.moreHistory = meer historie...
+gb.difftocurrent = diff naar current
+gb.search = zoeken
+gb.searchForAuthor = Zoeken naar commits authored door
+gb.searchForCommitter = Zoeken naar commits committed door
+gb.addition = additie
+gb.modification = wijziging
+gb.deletion = verwijdering
+gb.rename = hernoem
+gb.metrics = metrieken
+gb.stats = stats
+gb.markdown = markdown
+gb.changedFiles = gewijzigde bestanden
+gb.filesAdded = {0} bestanden toegevoegd
+gb.filesModified = {0} bestanden gewijzigd
+gb.filesDeleted = {0} bestanden verwijderd
+gb.filesCopied = {0} bestanden gekopieerd
+gb.filesRenamed = {0} bestanden hernoemd
+gb.missingUsername = Ontbrekende Gebruikersnaam
+gb.edit = edit
+gb.searchTypeTooltip = Selecteer Zoek Type
+gb.searchTooltip = Zoek {0}
+gb.delete = verwijder
+gb.docs = docs
+gb.accessRestriction = toegangsbeperking
+gb.name = naam
+gb.enableTickets = enable tickets
+gb.enableDocs = enable docs
+gb.save = opslaan
+gb.showRemoteBranches = toon remote branches
+gb.editUsers = wijzig gebruikers
+gb.confirmPassword = bevestig wachtwoord
+gb.restrictedRepositories = restricted repositories
+gb.canAdmin = kan beheren
+gb.notRestricted = anoniem view, clone, & push
+gb.pushRestricted = geauthenticeerde push
+gb.cloneRestricted = geauthenticeerde clone & push
+gb.viewRestricted = geauthenticeerde view, clone, & push
+gb.useTicketsDescription = readonly, gedistribueerde Ticgit issues
+gb.useDocsDescription = enumereer Markdown documentatie in repositorie
+gb.showRemoteBranchesDescription = toon remote branches
+gb.canAdminDescription = kan Gitblit server beheren
+gb.permittedUsers = toegestane gebruikers
+gb.isFrozen = is bevroren
+gb.isFrozenDescription = weiger push operaties
+gb.zip = zip
+gb.showReadme = toon readme
+gb.showReadmeDescription = toon een \"readme\" Markdown bestand in de samenvattingspagina
+gb.nameDescription = gebruik '/' voor het groeperen van repositories. bijv. libraries/mycoollib.git
+gb.ownerDescription = de eigenaar mag repository instellingen wijzigen
+gb.blob = blob
+gb.commitActivityTrend = commit activiteit trend
+gb.commitActivityDOW = commit activiteit per dag van de week
+gb.commitActivityAuthors = primaire auteurs op basis van commit activiteit
+gb.feed = feed
+gb.cancel = afbreken
+gb.changePassword = wijzig wachtwoord
+gb.isFederated = is gefedereerd
+gb.federateThis = federeer deze repositorie
+gb.federateOrigin = federeer deze origin
+gb.excludeFromFederation = uitsluiten van federatie
+gb.excludeFromFederationDescription = sluit gefedereerde Gitblit instances uit van het pullen van dit account
+gb.tokens = federatie tokens
+gb.tokenAllDescription = alle repositories, gebruikers, & instellingen
+gb.tokenUnrDescription = alle repositories & gebruikers
+gb.tokenJurDescription = alle repositories
+gb.federatedRepositoryDefinitions = repositorie definities
+gb.federatedUserDefinitions = gebruikersdefinities
+gb.federatedSettingDefinitions = instellingendefinities
+gb.proposals = federatie voorstellen
+gb.received = ontvangen
+gb.type = type
+gb.token = token
+gb.repositories = repositories
+gb.proposal = voorstel
+gb.frequency = frequentie
+gb.folder = map
+gb.lastPull = laatste pull
+gb.nextPull = volgende pull
+gb.inclusions = inclusies
+gb.exclusions = exclusies
+gb.registration = registratie
+gb.registrations = federatie registraties
+gb.sendProposal = voorstel
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = wijzig de ref waar HEAD naar linkt naar bijv. refs/heads/master
+gb.federationStrategy = federatie strategie
+gb.federationRegistration = federatie registratie
+gb.federationResults = federatie pull resultaten
+gb.federationSets = federatie sets
+gb.message = melding
+gb.myUrlDescription = de publiek toegankelijke url voor uw Gitblit instantie
+gb.destinationUrl = zend naar
+gb.destinationUrlDescription = de url van de Gitblit instantie voor het verzenden van uw voorstel
+gb.users = gebruikers
+gb.federation = federatie
+gb.error = fout
+gb.refresh = ververs
+gb.browse = blader
+gb.clone = clone
+gb.filter = filter
+gb.create = maak
+gb.servers = servers
+gb.recent = recent
+gb.available = beschikbaar
+gb.selected = geselecteerd
+gb.size = grootte
+gb.downloading = downloading
+gb.loading = laden
+gb.starting = starten
+gb.general = algemeen
+gb.settings = instellingen
+gb.manage = beheer
+gb.lastLogin = laatste login
+gb.skipSizeCalculation = geen berekening van de omvang
+gb.skipSizeCalculationDescription = geen berekening van de repositoriegrootte (beperkt laadtijd pagina)
+gb.skipSummaryMetrics = geen metrieken samenvatting
+gb.skipSummaryMetricsDescription = geen berekening van metrieken op de samenvattingspagina (beperkt laadtijd pagina)
+gb.accessLevel = toegangsniveau
+gb.default = standaard
+gb.setDefault = instellen als standaard
+gb.since = sinds
+gb.status = status
+gb.bootDate = boot datum
+gb.servletContainer = servlet container
+gb.heapMaximum = maximum heap
+gb.heapAllocated = toegewezen heap
+gb.heapUsed = gebruikte heap
+gb.free = beschikbaar
+gb.version = versie
+gb.releaseDate = release datum
+gb.date = datum
+gb.activity = activiteit
+gb.subscribe = aboneer
+gb.branch = branch
+gb.maxHits = max hits
+gb.recentActivity = recente activiteit
+gb.recentActivityStats = laatste {0} dagen / {1} commits door {2} auteurs
+gb.recentActivityNone = laatste {0} dagen / geen
+gb.dailyActivity = dagelijkse activiteit
+gb.activeRepositories = actieve repositories
+gb.activeAuthors = actieve auteurs
+gb.commits = commits
+gb.teams = teams
+gb.teamName = teamnaam
+gb.teamMembers = teamleden
+gb.teamMemberships = teamlidmaatschappen
+gb.newTeam = nieuw team
+gb.permittedTeams = toegestane teams
+gb.emptyRepository = lege repositorie
+gb.repositoryUrl = repositorie url
+gb.mailingLists = mailing lijsten
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = custom velden
+gb.customFieldsDescription = custom velden beschikbaar voor Groovy hooks
+gb.accessPermissions = toegangsrechten
+gb.filters = filters
+gb.generalDescription = algemene instellingen
+gb.accessPermissionsDescription = beperk toegang voor gebruikers en teams
+gb.accessPermissionsForUserDescription = stel teamlidmaatschappen in of geef toegang tot specifieke besloten repositories
+gb.accessPermissionsForTeamDescription = stel teamlidmaatschappen in en geef toegang tot specifieke besloten repositories
+gb.federationRepositoryDescription = deel deze repositorie met andere Gitblit servers
+gb.hookScriptsDescription = run Groovy scripts bij pushes naar deze Gitblit server
+gb.reset = reset
+gb.pages = paginas
+gb.workingCopy = werkkopie
+gb.workingCopyWarning = deze repositorie heeft een werkkopie en kan geen pushes ontvangen
+gb.query = query
+gb.queryHelp = Standaard query syntax wordt ondersteund.<p/><p/>Zie aub <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> voor informatie.
+gb.queryResults = resultaten {0} - {1} ({2} hits)
+gb.noHits = geen hits
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = geïndexeerde branches
+gb.indexedBranchesDescription = kies de branches voor opname in uw Lucene index
+gb.noIndexedRepositoriesWarning = geen van uw repositories is geconfigureerd voor Lucene indexering
+gb.undefinedQueryWarning = query is niet gedefinieerd!
+gb.noSelectedRepositoriesWarning = kies aub één of meerdere repositories!
+gb.luceneDisabled = Lucene indexering staat uit
+gb.failedtoRead = Lezen is mislukt
+gb.isNotValidFile = is geen valide bestand
+gb.failedToReadMessage = Het lezen van de standaard boodschap van {0} is mislukt!
+gb.passwordsDoNotMatch = Wachtwoorden komen niet overeen!
+gb.passwordTooShort = Wachtwoord is te kort. Minimum lengte is {0} karakters.
+gb.passwordChanged = Wachtwoord succesvol gewijzigd.
+gb.passwordChangeAborted = Wijziging wachtwoord afgebroken.
+gb.pleaseSetRepositoryName = Vul aub een repositorie naam in!
+gb.illegalLeadingSlash = Leidende root folder referenties (/) zijn niet toegestaan.
+gb.illegalRelativeSlash = Relatieve folder referenties (../) zijn niet toegestaan.
+gb.illegalCharacterRepositoryName = Illegaal karakter ''{0}'' in repositorie naam!
+gb.selectAccessRestriction = Stel aub een toegangsbeperking in!
+gb.selectFederationStrategy = Selecteer aub een federatie strategie!
+gb.pleaseSetTeamName = Vul aub een teamnaam in!
+gb.teamNameUnavailable = Teamnaam ''{0}'' is niet beschikbaar.
+gb.teamMustSpecifyRepository = Een team moet minimaal één repositorie specificeren.
+gb.teamCreated = Nieuw team ''{0}'' successvol aangemaakt.
+gb.pleaseSetUsername = Vul aub een gebruikersnaam in!
+gb.usernameUnavailable = Gebruikersnaam ''{0}'' is niet beschikbaar.
+gb.combinedMd5Rename = Gitblit is geconfigureerd voor combined-md5 wachtwoord hashing. U moet een nieuw wachtwoord opgeven bij het hernoemen van een account.
+gb.userCreated = Nieuwe gebruiker ''{0}'' succesvol aangemaakt.
+gb.couldNotFindFederationRegistration = Kon de federatie registratie niet vinden!
+gb.failedToFindGravatarProfile = Kon het Gravatar profiel voor {0} niet vinden
+gb.branchStats = {0} commits en {1} tags in {2}
+gb.repositoryNotSpecified = Repositorie niet gespecificeerd!
+gb.repositoryNotSpecifiedFor = Repositorie niet gespecificeerd voor {0}!
+gb.canNotLoadRepository = Kan repositorie niet laden
+gb.commitIsNull = Commit is null
+gb.unauthorizedAccessForRepository = Niet toegestane toegang tot repositorie
+gb.failedToFindCommit = Het vinden van commit \"{0}\" in {1} voor {2} pagina is mislukt!
+gb.couldNotFindFederationProposal = Kon federatievoorstel niet vinden!
+gb.invalidUsernameOrPassword = Onjuiste gebruikersnaam of wachtwoord!
+gb.OneProposalToReview = Er is 1 federatie voorstel dat wacht op review.
+gb.nFederationProposalsToReview = Er zijn {0} federatie verzoeken die wachten op review.
+gb.couldNotFindTag = Kon tag {0} niet vinden
+gb.couldNotCreateFederationProposal = Kon geen federatie voorstel maken!
+gb.pleaseSetGitblitUrl = Vul aub uw Gitblit url in!
+gb.pleaseSetDestinationUrl = Vul aub een bestemmings-url in voor uw voorstel!
+gb.proposalReceived = Voorstel correct ontvangen door {0}.
+gb.noGitblitFound = Sorry, {0} kon geen Gitblit instance vinden op {1}.
+gb.noProposals = Sorry, {0} accepteert geen voorstellen op dit moment.
+gb.noFederation = Sorry, {0} is niet geconfigureerd voor het federeren met een Gitblit instance.
+gb.proposalFailed = Sorry, {0} ontving geen voorstelgegevens!
+gb.proposalError = Sorry, {0} rapporteert dat een onverwachte fout is opgetreden!
+gb.failedToSendProposal = Voorstel verzenden is niet gelukt!
+gb.userServiceDoesNotPermitAddUser = {0} staat het toevoegen van een gebruikersaccount niet toe!
+gb.userServiceDoesNotPermitPasswordChanges = {0} staat wachtwoord wijzigingen niet toe!
+gb.displayName = display naam
+gb.emailAddress = emailadres
+gb.errorAdminLoginRequired = Aanmelden vereist voor beheerwerk
+gb.errorOnlyAdminMayCreateRepository = Alleen een beheerder kan een repositorie maken
+gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of de eigenaar kan een repositorie wijzigen
+gb.errorAdministrationDisabled = Beheer is uitgeschakeld
+gb.lastNDays = laatste {0} dagen
+gb.completeGravatarProfile = Completeer profiel op Gravatar.com
+gb.none = geen
+gb.line = regel
+gb.content = inhoud
+gb.empty = leeg
+gb.inherited = geërfd
+gb.deleteRepository = Verwijder repositorie \"{0}\"?
+gb.repositoryDeleted = Repositorie ''{0}'' verwijderd.
+gb.repositoryDeleteFailed = Verwijdering van repositorie ''{0}'' mislukt!
+gb.deleteUser = Verwijder gebruiker \"{0}\"?
+gb.userDeleted = Gebruiker ''{0}'' verwijderd.
+gb.userDeleteFailed = Verwijdering van gebruiker ''{0}'' mislukt!
+gb.time.justNow = net
+gb.time.today = vandaag
+gb.time.yesterday = gisteren
+gb.time.minsAgo = {0} minuten geleden
+gb.time.hoursAgo = {0} uren geleden
+gb.time.daysAgo = {0} dagen geleden
+gb.time.weeksAgo = {0} weken geleden
+gb.time.monthsAgo = {0} maanden geleden
+gb.time.oneYearAgo = 1 jaar geleden
+gb.time.yearsAgo = {0} jaren geleden
+gb.duration.oneDay = 1 dag
+gb.duration.days = {0} dagen
+gb.duration.oneMonth = 1 maand
+gb.duration.months = {0} maanden
+gb.duration.oneYear = 1 jaar
+gb.duration.years = {0} jaren
+gb.authorizationControl = authorisatiebeheer
+gb.allowAuthenticatedDescription = ken RW+ rechten toe aan alle geautoriseerde gebruikers
+gb.allowNamedDescription = ken verfijnde rechten toe aan genoemde gebruikers of teams
+gb.markdownFailure = Het parsen van Markdown content is mislukt!
+gb.clearCache = maak cache leeg
+gb.projects = projecten
+gb.project = project
+gb.allProjects = alle projecten
+gb.copyToClipboard = kopieer naar clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} is geforked
+gb.repositoryForkFailed= fork is mislukt
+gb.personalRepositories = personlijke repositories
+gb.allowForks = sta forks toe
+gb.allowForksDescription = sta geauthoriseerde gebruikers toe om deze repositorie te forken
+gb.forkedFrom = geforked vanaf
+gb.canFork = kan geforked worden
+gb.canForkDescription = kan geauthoriseerde repositories forken naar persoonlijke repositories
+gb.myFork = toon mijn fork
+gb.forksProhibited = forks niet toegestaan
+gb.forksProhibitedWarning = deze repositorie staat forken niet toe
+gb.noForks = {0} heeft geen forks
+gb.forkNotAuthorized = sorry, u bent niet geautoriseerd voor het forken van {0}
+gb.forkInProgress = bezig met forken
+gb.preparingFork = bezig met het maken van uw fork...
+gb.isFork = is een fork
+gb.canCreate = mag maken
+gb.canCreateDescription = mag persoonlijke repositories maken
+gb.illegalPersonalRepositoryLocation = uw persoonlijke repositorie moet te vinden zijn op \"{0}\"
+gb.verifyCommitter = controleer committer
+gb.verifyCommitterDescription = vereis dat committer identiteit overeen komt met pushing Gitblt gebruikersaccount
+gb.verifyCommitterNote = alle merges vereisen "--no-ff" om committer identiteit af te dwingen
+gb.repositoryPermissions = repository rechten
+gb.userPermissions = gebruikersrechten
+gb.teamPermissions = teamrechten
+gb.add = toevoegen
+gb.noPermission = VERWIJDER DIT RECHT
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creëer)
+gb.deletePermission = {0} (push, ref creëer+verwijdering)
+gb.rewindPermission = {0} (push, ref creëer+verwijdering+rewind)
+gb.permission = recht
+gb.regexPermission = dit recht is gezet vanaf de reguliere expressie \"{0}\"
+gb.accessDenied = toegang geweigerd
+gb.busyCollectingGarbage = sorry, Gitblit is bezig met opruimen in {0}
+gb.gcPeriod = opruim periode
+gb.gcPeriodDescription = tijdsduur tussen opruimacties
+gb.gcThreshold = opruim drempel
+gb.gcThresholdDescription = minimum totaalomvang van losse objecten voor het starten van opruimactie
+gb.ownerPermission = repositorie eigenaar
+gb.administrator = beheer
+gb.administratorPermission = Gitblit beheerder
+gb.team = team
+gb.teamPermission = permissie ingesteld via \"{0}\" teamlidmaatschap
+gb.missing = ontbrekend!
+gb.missingPermission = de repositorie voor deze permissie ontbreekt!
+gb.mutable = te wijzigen
+gb.specified = gespecificeerd
+gb.effective = geldig
+gb.organizationalUnit = organisatie eenheid
+gb.organization = organisatie
+gb.locality = localiteit
+gb.stateProvince = staat of provincie
+gb.countryCode = landcode
+gb.properties = eigenschappen
+gb.issued = uitgegeven
+gb.expires = verloopt op
+gb.expired = verlopen
+gb.expiring = verloopt
+gb.revoked = ingetrokken
+gb.serialNumber = serie nummer
+gb.certificates = certificaten
+gb.newCertificate = nieuwe certificaten
+gb.revokeCertificate = trek certificaat in
+gb.sendEmail = zend email
+gb.passwordHint = wachtwoord hint
+gb.ok = ok
+gb.invalidExpirationDate = ongeldige verloopdatum!
+gb.passwordHintRequired = wachtwoord hint vereist!
+gb.viewCertificate = toon certificaat
+gb.subject = onderwerp
+gb.issuer = issuer
+gb.validFrom = geldig vanaf
+gb.validUntil = geldig tot
+gb.publicKey = publieke sleutel
+gb.signatureAlgorithm = signature algoritme
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = reden
+gb.revokeCertificateReason = Kies aub een reden voor het intrekken van het certificaat
+gb.unspecified = niet gespecificeerd
+gb.keyCompromise = sleutel gecompromitteerd
+gb.caCompromise = CA gecompromitteerd
+gb.affiliationChanged = affiliatie gewijzigd
+gb.superseded = opgevolgd
+gb.cessationOfOperation = gestaakt
+gb.privilegeWithdrawn = privilege ingetrokken
+gb.time.inMinutes = in {0} minuten
+gb.time.inHours = in {0} uren
+gb.time.inDays = in {0} dagen
+gb.hostname = hostnaam
+gb.hostnameRequired = Vul aub een hostnaam in
+gb.newSSLCertificate = nieuw server SSL certificaat
+gb.newCertificateDefaults = nieuw certificaat defaults
+gb.duration = duur
+gb.certificateRevoked = Certificaat {0,number,0} is ingetrokken
+gb.clientCertificateGenerated = Nieuw client certificaat voor {0} succesvol gegenereerd
+gb.sslCertificateGenerated = Nieuw server SSL certificaat voor {0} succesvol gegenereerd
+gb.newClientCertificateMessage = MERK OP:\nHet 'wachtwoord' is niet het wachtwoord van de gebruiker. Het is het wachtwoord voor het afschermen van de sleutelring van de gebruiker. Dit wachtwoord wordt niet opgeslagen dus moet u ook een 'hint' invullen die zal worden opgenomen in de README instructies van de gebruiker.
+gb.certificate = certificaat
+gb.emailCertificateBundle = email client certificaat bundel
+gb.pleaseGenerateClientCertificate = Genereer aub een client certificaat voor {0}
+gb.clientCertificateBundleSent = Client certificaat bundel voor {0} verzonden
+gb.enterKeystorePassword = Vul aub het Gitblit keystore wachtwoord in
+gb.warning = waarschuwing
+gb.jceWarning = Uw Java Runtime Environment heeft geen \"JCE Unlimited Strength Jurisdiction Policy\" bestanden.\nDit zal de lengte van wachtwoorden voor het eventueel versleutelen van uw keystores beperken tot 7 karakters.\nDeze policy bestanden zijn een optionele download van Oracle.\n\nWilt u toch doorgaan en de certificaat infrastructuur genereren?\n\nNee antwoorden zal uw browser doorsturen naar de downloadpagina van Oracle zodat u de policybestanden kunt downloaden.
+gb.maxActivityCommits = maximum activiteit commits
+gb.maxActivityCommitsDescription = maximum aantal commits om bij te dragen aan de Activiteitspagina
+gb.noMaximum = geen maximum
+gb.attributes = attributen
+gb.serveCertificate = gebruik deze certificaten voor https
+gb.sslCertificateGeneratedRestart = Nieuwe SSL certificaten voor {0} succesvol gegenereerd.\nU dient Gitblit te herstarten om de nieuwe certificaten te gebruiken.\n\nAls u opstart met de '--alias' parameter moet u die wijzigen naar ''--alias {0}''.
+gb.validity = geldigheid
+gb.siteName = site naam
+gb.siteNameDescription = korte, verduidelijkende naam van deze server
+gb.excludeFromActivity = sluit uit van activiteitspagina
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties new file mode 100644 index 00000000..469d2055 --- /dev/null +++ b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties @@ -0,0 +1,442 @@ +gb.repository = repositório
+gb.owner = proprietário
+gb.description = descrição
+gb.lastChange = última alteração
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = autor
+gb.committer = committer
+gb.commit = commit
+gb.tree = árvore
+gb.parent = parent
+gb.url = URL
+gb.history = histórico
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = atribuído
+gb.ticketOpenDate = data da abertura
+gb.ticketState = estado
+gb.ticketComments = comentários
+gb.view = visualizar
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = mais commits...
+gb.allTags = todas as tags...
+gb.allBranches = todos os branches...
+gb.summary = resumo
+gb.ticket = ticket
+gb.newRepository = novo repositório
+gb.newUser = novo usuário
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = primeira
+gb.pagePrevious anterior
+gb.pageNext = próxima
+gb.head = HEAD
+gb.blame = blame
+gb.login = login
+gb.logout = logout
+gb.username = username
+gb.password = password
+gb.tagger = tagger
+gb.moreHistory = mais histórico...
+gb.difftocurrent = diff para a atual
+gb.search = pesquisar
+gb.searchForAuthor = Procurar por commits cujo autor é
+gb.searchForCommitter = Procurar por commits commitados por é
+gb.addition = adicionados
+gb.modification = modificados
+gb.deletion = apagados
+gb.rename = renomear
+gb.metrics = métricas
+gb.stats = estatísticas
+gb.markdown = markdown
+gb.changedFiles = arquivos alterados
+gb.filesAdded = {0} arquivos adicionados
+gb.filesModified = {0} arquivos modificados
+gb.filesDeleted = {0} arquivos deletados
+gb.filesCopied = {0} arquivos copiados
+gb.filesRenamed = {0} arquivos renomeados
+gb.missingUsername = Username desconhecido
+gb.edit = editar
+gb.searchTypeTooltip = Selecione o Tipo de Pesquisa
+gb.searchTooltip = Pesquisar {0}
+gb.delete = deletar
+gb.docs = documentos
+gb.accessRestriction = restrição de acesso
+gb.name = nome
+gb.enableTickets = ativar tickets
+gb.enableDocs = ativar documentação
+gb.save = salvar
+gb.showRemoteBranches = mostrar branches remotos
+gb.editUsers = editar usuários
+gb.confirmPassword = confirmar password
+gb.restrictedRepositories = repositórios restritos
+gb.canAdmin = pode administrar
+gb.notRestricted = visualização anônima, clone, & push
+gb.pushRestricted = push autênticado
+gb.cloneRestricted = clone & push autênticados
+gb.viewRestricted = view, clone, & push autênticados
+gb.useTicketsDescription = somente leitura, issues do Ticgit distribuídos
+gb.useDocsDescription = enumerar documentação Markdown no repositório
+gb.showRemoteBranchesDescription = mostrar branches remotos
+gb.canAdminDescription = pode administrar o server Gitblit
+gb.permittedUsers = usuários autorizados
+gb.isFrozen = congelar
+gb.isFrozenDescription = proibir fazer push
+gb.zip = zip
+gb.showReadme = mostrar readme
+gb.showReadmeDescription = mostrar um arquivo \"leia-me\" na página de resumo
+gb.nameDescription = usar '/' para agrupar repositórios. e.g. libraries/mycoollib.git
+gb.ownerDescription = o proprietário pode editar configurações do repositório
+gb.blob = blob
+gb.commitActivityTrend = tendência dos commits
+gb.commitActivityDOW = commits diários
+gb.commitActivityAuthors = principais committers
+gb.feed = feed
+gb.cancel = cancelar
+gb.changePassword = alterar password
+gb.isFederated = está federado
+gb.federateThis = federar este repositório
+gb.federateOrigin = federar o origin
+gb.excludeFromFederation = excluir da federação
+gb.excludeFromFederationDescription = bloquear instâncias federadas do GitBlit de fazer pull desta conta
+gb.tokens = tokens de federação
+gb.tokenAllDescription = todos repositórios, usuários & configurações
+gb.tokenUnrDescription = todos repositórios & usuários
+gb.tokenJurDescription = todos repositórios
+gb.federatedRepositoryDefinitions = definições de repositório
+gb.federatedUserDefinitions = definições de usuários
+gb.federatedSettingDefinitions = definições de configurações
+gb.proposals = propostas de federações
+gb.received = recebidos
+gb.type = tipo
+gb.token = token
+gb.repositories = repositórios
+gb.proposal = propostas
+gb.frequency = frequência
+gb.folder = pasta
+gb.lastPull = último pull
+gb.nextPull = próximo pull
+gb.inclusions = inclusões
+gb.exclusions = excluões
+gb.registration = cadastro
+gb.registrations = cadastro de federações
+gb.sendProposal = enviar proposta
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = alterar a ref o qual a HEAD aponta. e.g. refs/heads/master
+gb.federationStrategy = estratégia de federação
+gb.federationRegistration = cadastro de federações
+gb.federationResults = resultados dos pulls de federações
+gb.federationSets = ajustes de federações
+gb.message = mensagem
+gb.myUrlDescription = a url de acesso público para a instância Gitblit
+gb.destinationUrl = enviar para
+gb.destinationUrlDescription = a url da intância do Gitblit para enviar sua proposta
+gb.users = usuários
+gb.federation = federação
+gb.error = erro
+gb.refresh = atualizar
+gb.browse = navegar
+gb.clone = clonar
+gb.filter = filtrar
+gb.create = criar
+gb.servers = servidores
+gb.recent = recente
+gb.available = disponível
+gb.selected = selecionado
+gb.size = tamanho
+gb.downloading = downloading
+gb.loading = loading
+gb.starting = inciando
+gb.general = geral
+gb.settings = configurações
+gb.manage = administrar
+gb.lastLogin = último login
+gb.skipSizeCalculation = ignorar cálculo do tamanho
+gb.skipSizeCalculationDescription = não calcular o tamanho do repositório (reduz o tempo de load da página)
+gb.skipSummaryMetrics = ignorar resumo das métricas
+gb.skipSummaryMetricsDescription = não calcular métricas na página de resumo
+gb.accessLevel = acesso
+gb.default = default
+gb.setDefault = tornar default
+gb.since = desde
+gb.status = status
+gb.bootDate = data do boot
+gb.servletContainer = servlet container
+gb.heapMaximum = heap máximo
+gb.heapAllocated = alocar heap
+gb.heapUsed = usar heap
+gb.free = free
+gb.version = versão
+gb.releaseDate = data de release
+gb.date = data
+gb.activity = atividade
+gb.subscribe = inscrever
+gb.branch = branch
+gb.maxHits = hits máximos
+gb.recentActivity = atividade recente
+gb.recentActivityStats = últimos {0} dias / {1} commits por {2} autores
+gb.recentActivityNone = últimos {0} dias / nenhum
+gb.dailyActivity = atividade diária
+gb.activeRepositories = repositórios ativos
+gb.activeAuthors = autores ativos
+gb.commits = commits
+gb.teams = equipes
+gb.teamName = nome da equipe
+gb.teamMembers = membros
+gb.teamMemberships = filiações em equipes
+gb.newTeam = nova equipe
+gb.permittedTeams = equipes permitidas
+gb.emptyRepository = repositório vazio
+gb.repositoryUrl = url do repositório
+gb.mailingLists = listas de e-mails
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = campos customizados
+gb.customFieldsDescription = campos customizados disponíveis para Groovy hooks
+gb.accessPermissions = permissões de acesso
+gb.filters = filtros
+gb.generalDescription = configurações comuns
+gb.accessPermissionsDescription = restringir acesso por usuários e equipes
+gb.accessPermissionsForUserDescription = ajustar filiações em equipes ou garantir acesso a repositórios específicos
+gb.accessPermissionsForTeamDescription = ajustar membros da equipe e garantir acesso a repositórios específicos
+gb.federationRepositoryDescription = compartilhar este repositório com outros servidores Gitblit
+gb.hookScriptsDescription = rodar scripts Groovy nos pushes pushes para este servidor Gitblit
+gb.reset = reset
+gb.pages = páginas
+gb.workingCopy = working copy
+gb.workingCopyWarning = this repository has a working copy and can not receive pushes
+gb.query = query
+gb.queryHelp = Standard query syntax é suportada.<p/><p/>Por favor veja <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> para mais detalhes.
+gb.queryResults = resultados {0} - {1} ({2} hits)
+gb.noHits = sem hits
+gb.authored = foi autor do
+gb.committed = committed
+gb.indexedBranches = branches indexados
+gb.indexedBranchesDescription = selecione os branches para incluir nos seus índices Lucene
+gb.noIndexedRepositoriesWarning = nenhum dos seus repositórios foram configurados para indexação do Lucene
+gb.undefinedQueryWarning = a query não foi definida!
+gb.noSelectedRepositoriesWarning = por favor selecione um ou mais repositórios!
+gb.luceneDisabled = indexação do Lucene está desabilitada
+gb.failedtoRead = leitura falhou
+gb.isNotValidFile = não é um arquivo válido
+gb.failedToReadMessage = Falhou em ler mensagens default de {0}!
+gb.passwordsDoNotMatch = Passwords não conferem!
+gb.passwordTooShort = Password é muito curto. Tamanho mínimo são {0} caracteres.
+gb.passwordChanged = Password alterado com sucesso.
+gb.passwordChangeAborted = alteração do Password foi abortada.
+gb.pleaseSetRepositoryName = Por favor ajuste o nome do repositório!
+gb.illegalLeadingSlash = Referências a diretórios raiz começando com (/) são proibidas.
+gb.illegalRelativeSlash = Referências a diretórios relativos (../) são proibidas.
+gb.illegalCharacterRepositoryName = Caractere ilegal ''{0}'' no nome do repositório!
+gb.selectAccessRestriction = Please select access restriction!
+gb.selectFederationStrategy = Por favor selecione a estratégia de federação!
+gb.pleaseSetTeamName = Por favor insira um nome de equipe!
+gb.teamNameUnavailable = O nome de equipe ''{0}'' está indisponível.
+gb.teamMustSpecifyRepository = Uma equipe deve especificar pelo menos um repositório.
+gb.teamCreated = Nova equipe ''{0}'' criada com sucesso.
+gb.pleaseSetUsername = Por favor entre com um username!
+gb.usernameUnavailable = Username ''{0}'' está indisponível.
+gb.combinedMd5Rename = Gitblit está configurado para usar um hash combinado-md5. Você deve inserir um novo password ao renamear a conta.
+gb.userCreated = Novo usuário ''{0}'' criado com sucesso.
+gb.couldNotFindFederationRegistration = Não foi possível localizar o registro da federação!
+gb.failedToFindGravatarProfile = Falhou em localizar um perfil Gravatar para {0}
+gb.branchStats = {0} commits e {1} tags em {2}
+gb.repositoryNotSpecified = Repositório não específicado!
+gb.repositoryNotSpecifiedFor = Repositório não específicado para {0}!
+gb.canNotLoadRepository = Não foi possível carregar o repositório
+gb.commitIsNull = Commit está nulo
+gb.unauthorizedAccessForRepository = Acesso não autorizado para o repositório
+gb.failedToFindCommit = Não foi possível achar o commit \"{0}\" em {1} para {2} página!
+gb.couldNotFindFederationProposal = Não foi possível localizar propostas de federação!
+gb.invalidUsernameOrPassword = username ou password inválido!
+gb.OneProposalToReview = Existe uma proposta de federação aguardando revisão.
+gb.nFederationProposalsToReview = Existem {0} propostas de federação aguardando revisão.
+gb.couldNotFindTag = Não foi possível localizar a tag {0}
+gb.couldNotCreateFederationProposal = Não foi possível criar uma proposta de federation!
+gb.pleaseSetGitblitUrl = Por favor insira sua url do Gitblit!
+gb.pleaseSetDestinationUrl = Por favor insira a url de destino para sua proposta!
+gb.proposalReceived = Proposta recebida com sucesso por {0}.
+gb.noGitblitFound = Desculpe, {0} não localizou uma instância do Gitblit em {1}.
+gb.noProposals = Desculpe, {0} não está aceitando propostas agora.
+gb.noFederation = Desculpe, {0} não está configurado com nenhuma intância do Gitblit.
+gb.proposalFailed = Desculpe, {0} não recebeu nenhum dado de proposta!
+gb.proposalError = Desculpe, {0} reportou que um erro inesperado ocorreu!
+gb.failedToSendProposal = Não foi possível enviar a proposta!
+gb.userServiceDoesNotPermitAddUser = {0} não permite adicionar uma conta de usuário!
+gb.userServiceDoesNotPermitPasswordChanges = {0} não permite alterações no password!
+gb.displayName = nome
+gb.emailAddress = e-mail
+gb.errorAdminLoginRequired = Administração requer um login
+gb.errorOnlyAdminMayCreateRepository = Somente umadministrador pode criar um repositório
+gb.errorOnlyAdminOrOwnerMayEditRepository = Somente umadministrador pode editar um repositório
+gb.errorAdministrationDisabled = Administração está desabilitada
+gb.lastNDays = últimos {0} dias
+gb.completeGravatarProfile = Profile completo em Gravatar.com
+gb.none = nenhum
+gb.line = linha
+gb.content = conteúdo
+gb.empty = vazio
+gb.inherited = herdado
+gb.deleteRepository = Deletar repositório \"{0}\"?
+gb.repositoryDeleted = Repositório ''{0}'' deletado.
+gb.repositoryDeleteFailed = Não foi possível apagar o repositório ''{0}''!
+gb.deleteUser = Deletar usuário \"{0}\"?
+gb.userDeleted = Usuário ''{0}'' deletado.
+gb.userDeleteFailed = Não foi possível apagar o usuário ''{0}''!
+gb.time.justNow = agora mesmo
+gb.time.today = hoje
+gb.time.yesterday = ontem
+gb.time.minsAgo = há {0} minutos
+gb.time.hoursAgo = há {0} horas
+gb.time.daysAgo = há {0} dias
+gb.time.weeksAgo = há {0} semanas
+gb.time.monthsAgo = há {0} meses
+gb.time.oneYearAgo = há 1 ano
+gb.time.yearsAgo = há {0} anos
+gb.duration.oneDay = 1 dia
+gb.duration.days = {0} dias
+gb.duration.oneMonth = 1 mês
+gb.duration.months = {0} meses
+gb.duration.oneYear = 1 ano
+gb.duration.years = {0} anos
+gb.authorizationControl = controle de autorização
+gb.allowAuthenticatedDescription = conceder permissão RW+ para todos os usuários autênticados
+gb.allowNamedDescription = conceder permissões refinadas para usuários escolhidos ou equipes
+gb.markdownFailure = Não foi possível converter conteúdo Markdown!
+gb.clearCache = limpar o cache
+gb.projects = projetos
+gb.project = projeto
+gb.allProjects = todos projetos
+gb.copyToClipboard = copiar para o clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = fork feito em {0}
+gb.repositoryForkFailed= não foi possível fazer fork
+gb.personalRepositories = repositórios pessoais
+gb.allowForks = permitir forks
+gb.allowForksDescription = permitir usuários autorizados a fazer fork deste repositório
+gb.forkedFrom = forked de
+gb.canFork = pode fazer fork
+gb.canForkDescription = pode fazer fork de repositórios autorizados para repositórios pessoais
+gb.myFork = visualizar meu fork
+gb.forksProhibited = forks proibidos
+gb.forksProhibitedWarning = este repositório proíbe forks
+gb.noForks = {0} não possui forks
+gb.forkNotAuthorized = desculpe, você não está autorizado a fazer fork de {0}
+gb.forkInProgress = fork em progresso
+gb.preparingFork = preparando seu fork...
+gb.isFork = é fork
+gb.canCreate = pode criar
+gb.canCreateDescription = pode criar repositórios pessoais
+gb.illegalPersonalRepositoryLocation = seu repositório pessoal deve estar localizado em \"{0}\"
+gb.verifyCommitter = verificar committer
+gb.verifyCommitterDescription = requer a identidade do committer para combinar com uma conta do Gitblt
+gb.verifyCommitterNote = todos os merges requerem "--no-ff" para impor a identidade do committer
+gb.repositoryPermissions = permissões de repositório
+gb.userPermissions = permissões de usuário
+gb.teamPermissions = permissões de equipe
+gb.add = add
+gb.noPermission = APAGAR ESTA PERMISSÃO
+gb.excludePermission = {0} (excluir)
+gb.viewPermission = {0} (visualizar)
+gb.clonePermission = {0} (clonar)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = permissão
+gb.regexPermission = esta permissão foi configurada através da expressão regular \"{0}\"
+gb.accessDenied = acesso negado
+gb.busyCollectingGarbage = desculpe, o Gitblit está ocupado coletando lixo em {0}
+gb.gcPeriod = período do GC
+gb.gcPeriodDescription = duração entre as coletas de lixo
+gb.gcThreshold = limite do GC
+gb.gcThresholdDescription = tamanho total mínimo de objetos \"soltos\" que ativam a coleta de lixo
+gb.ownerPermission = proprietário do repositório
+gb.administrator = administrador
+gb.administratorPermission = administrador do Gitblit
+gb.team = equipe
+gb.teamPermission = permissão concedida pela filiação a equipe \"{0}\"
+gb.missing = faltando!
+gb.missingPermission = o repositório para esta permissão está faltando!
+gb.mutable = mutável
+gb.specified = específico
+gb.effective = efetivo
+gb.organizationalUnit = unidade organizacional
+gb.organization = organização
+gb.locality = localidade
+gb.stateProvince = estado ou província
+gb.countryCode = código do país
+gb.properties = propriedades
+gb.issued = emitido
+gb.expires = expira
+gb.expired = expirado
+gb.expiring = expirando
+gb.revoked = revogado
+gb.serialNumber = número serial
+gb.certificates = certificados
+gb.newCertificate = novo certificado
+gb.revokeCertificate = revogar certificado
+gb.sendEmail = enviar email
+gb.passwordHint = dica de password
+gb.ok = ok
+gb.invalidExpirationDate = data de expiração inválida!
+gb.passwordHintRequired = dica de password requerida!
+gb.viewCertificate = visualizar certificado
+gb.subject = assunto
+gb.issuer = emissor
+gb.validFrom = válido a partir de
+gb.validUntil = válido até
+gb.publicKey = chave pública
+gb.signatureAlgorithm = algoritmo de assinatura
+gb.sha1FingerPrint = digital SHA-1
+gb.md5FingerPrint = digital MD5
+gb.reason = razão
+gb.revokeCertificateReason = Por selecione a razão da revogação do certificado
+gb.unspecified = não específico
+gb.keyCompromise = comprometimento de chave
+gb.caCompromise = compromisso CA
+gb.affiliationChanged = afiliação foi alterada
+gb.superseded = substituídas
+gb.cessationOfOperation = cessação de funcionamento
+gb.privilegeWithdrawn = privilégio retirado
+gb.time.inMinutes = em {0} minutos
+gb.time.inHours = em {0} horas
+gb.time.inDays = em {0} dias
+gb.hostname = hostname
+gb.hostnameRequired = Por favor insira um hostname
+gb.newSSLCertificate = novo servidor de certificado SSL
+gb.newCertificateDefaults = novos padrões de certificação
+gb.duration = duração
+gb.certificateRevoked = Certificado {0, número, 0} foi revogado
+gb.clientCertificateGenerated = Novo certificado cliente para {0} foi gerado com sucesso
+gb.sslCertificateGenerated = Novo servidor de certificado SSL gerado com sucesso para {0}
+gb.newClientCertificateMessage = OBSERVAÇÃO:\nO 'password' não é o password do usuário mas sim o password usado para proteger a keystore. Este password não será salvo então você também inserir uma dica que será incluída nas instruções de LEIA-ME do usuário.
+gb.certificate = certificado
+gb.emailCertificateBundle = pacote certificado de cliente de email
+gb.pleaseGenerateClientCertificate = Por favor gere um certificado cliente para {0}
+gb.clientCertificateBundleSent = Pacote de certificado de cliente para {0} enviada
+gb.enterKeystorePassword = Por favor insira uma chave para keystore do Gitblit
+gb.warning = warning
+gb.jceWarning = Seu Java Runtime Environment não tem os arquivos \"JCE Unlimited Strength Jurisdiction Policy\".\nIsto irá limitar o tamanho dos passwords que você usará para encriptar suas keystores para 7 caracteres.\nEstes arquivos de políticas são um download opcional da Oracle.\n\nVocê gostaria de continuar e gerar os certificados de infraestrutura de qualquer forma?\n\nRespondendo "Não" irá redirecionar o seu browser para a página de downloads da Oracle, de onde você poderá fazer download desses arquivos.
+gb.maxActivityCommits = limitar exibição de commits
+gb.maxActivityCommitsDescription = quantidade máxima de commits para contribuir para a página de atividade
+gb.noMaximum = ilimitado
+gb.attributes = atributos
+gb.serveCertificate = servir https com este certificado
+gb.sslCertificateGeneratedRestart = Novo certificado SSL de servidor gerado com sucesso para {0}.\nVocê deve reiniciar o Gitblit para usar o novo certificado.\n\nSe você estiver executando com o parâmetro '--alias', você precisará alterá-lo para ''--alias {0}''.
+gb.validity = validade
+gb.siteName = nome do site
+gb.siteNameDescription = breve mas um nome descritivo para seu servidor
\ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties b/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties new file mode 100644 index 00000000..b5715850 --- /dev/null +++ b/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties @@ -0,0 +1,444 @@ +gb.repository = \u7248\u672c\u5e93 +gb.owner = \u7ba1\u7406\u5458 +gb.description = \u63cf\u8ff0 +gb.lastChange = \u6700\u8fd1\u4fee\u6539 +gb.refs = refs +gb.tag = \u6807\u7b7e +gb.tags = \u6807\u7b7e +gb.author = \u7528\u6237 +gb.committer = \u63d0\u4ea4\u8005 +gb.commit = \u63d0\u4ea4 +gb.tree = \u76ee\u5f55 +gb.parent = parent +gb.url = URL +gb.history = \u5386\u53f2\u4fe1\u606f +gb.raw = raw +gb.object = object +gb.ticketId = ticket id +gb.ticketAssigned = assigned +gb.ticketOpenDate = \u5f00\u542f\u65e5\u671f +gb.ticketState = \u72b6\u6001 +gb.ticketComments = \u8bc4\u8bba +gb.view = \u67e5\u770b +gb.local = \u672c\u5730 +gb.remote = \u8fdc\u7a0b +gb.branches = \u5206\u652f +gb.patch = patch +gb.diff = \u5bf9\u6bd4 +gb.log = \u65e5\u5fd7 +gb.moreLogs = \u66f4\u591a\u63d0\u4ea4... +gb.allTags = \u6240\u6709\u6807\u7b7e... +gb.allBranches = \u6240\u6709\u5206\u652f... +gb.summary = \u6982\u51b5 +gb.ticket = ticket +gb.newRepository = \u521b\u5efa\u7248\u672c\u5e93 +gb.newUser = \u6dfb\u52a0\u7528\u6237 +gb.commitdiff = \u5bf9\u6bd4\u63d0\u4ea4\u7684\u5185\u5bb9 +gb.tickets = tickets +gb.pageFirst = \u9996\u9875 +gb.pagePrevious = \u524d\u4e00\u9875 +gb.pageNext = \u4e0b\u4e00\u9875 +gb.head = HEAD +gb.blame = blame +gb.login = \u767b\u5f55 +gb.logout = \u6ce8\u9500 +gb.username = \u7528\u6237\u540d +gb.password = \u5bc6\u7801 +gb.tagger = \u6807\u8bb0\u8005 +gb.moreHistory = \u66f4\u591a\u7684\u5386\u53f2\u4fe1\u606f... +gb.difftocurrent = \u5bf9\u6bd4\u5f53\u524d +gb.search = \u641c\u7d22 +gb.searchForAuthor = \u6309\u4f5c\u8005\u641c\u7d22 commits +gb.searchForCommitter = \u6309\u63d0\u4ea4\u8005\u641c\u7d22 commits +gb.addition = \u6dfb\u52a0 +gb.modification = \u4fee\u6539 +gb.deletion = \u5220\u9664 +gb.rename = \u91cd\u547d\u540d +gb.metrics = metrics +gb.stats = \u7edf\u8ba1 +gb.markdown = markdown +gb.changedFiles = \u5df2\u4fee\u6539\u6587\u4ef6 +gb.filesAdded = {0}\u4e2a\u6587\u4ef6\u5df2\u6dfb\u52a0 +gb.filesModified = {0}\u4e2a\u6587\u4ef6\u5df2\u4fee\u6539 +gb.filesDeleted = {0}\u4e2a\u6587\u4ef6\u5df2\u5220\u9664 +gb.filesCopied = {0} \u6587\u4ef6\u5df2\u590d\u5236 +gb.filesRenamed = {0} \u6587\u4ef6\u5df2\u91cd\u547d\u540d +gb.missingUsername = \u7528\u6237\u540d\u4e0d\u5b58\u5728 +gb.edit = \u7f16\u8f91 +gb.searchTypeTooltip = \u9009\u62e9\u641c\u7d22\u7c7b\u578b +gb.searchTooltip = \u641c\u7d22 {0} +gb.delete = \u5220\u9664 +gb.docs = \u6587\u6863 +gb.accessRestriction = \u8bbf\u95ee\u9650\u5236 +gb.name = \u540d\u79f0 +gb.enableTickets = \u5141\u8bb8 tickets +gb.enableDocs = \u5141\u8bb8\u6587\u6863 +gb.save = \u4fdd\u5b58 +gb.showRemoteBranches = \u663e\u793a\u8fdc\u7a0b\u5206\u652f +gb.editUsers = \u7f16\u8f91\u7528\u6237 +gb.confirmPassword = \u786e\u8ba4\u5bc6\u7801 +gb.restrictedRepositories = \u7248\u672c\u5e93\u8bbe\u7f6e +gb.canAdmin = \u7ba1\u7406\u6743\u9650 +gb.notRestricted = anonymous view, clone, & push +gb.pushRestricted = authenticated push +gb.cloneRestricted = authenticated clone & push +gb.viewRestricted = authenticated view, clone, & push +gb.useTicketsDescription = distributed Ticgit issues +gb.useDocsDescription = \u5217\u51fa\u7248\u672c\u5e93\u5185\u6240\u6709 Markdown \u6587\u6863 +gb.showRemoteBranchesDescription = \u663e\u793a\u8fdc\u7a0b\u5206\u652f +gb.canAdminDescription = Gitblit \u670d\u52a1\u5668\u7ba1\u7406\u5458 +gb.permittedUsers = \u5141\u8bb8\u7528\u6237 +gb.isFrozen = \u88ab\u51bb\u7ed3 +gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001\u64cd\u4f5c +gb.zip = zip +gb.showReadme = \u663e\u793areadme +gb.showReadmeDescription = \u5728\u6982\u51b5\u9875\u9762\u663e\u793a \\"readme\\" Markdown \u6587\u4ef6 +gb.nameDescription = \u4f7f\u7528 '/' \u5bf9\u7248\u672c\u5e93\u8fdb\u884c\u5206\u7ec4 \u4f8b\u5982. libraries/mycoollib.git +gb.ownerDescription = \u521b\u5efa\u8005\u53ef\u4ee5\u7f16\u8f91\u7248\u672c\u5e93\u5c5e\u6027 +gb.blob = blob +gb.commitActivityTrend = commit \u6d3b\u52a8\u8d8b\u52bf +gb.commitActivityDOW = \u6bcf\u5468 commit \u6d3b\u52a8 +gb.commitActivityAuthors = commit \u6d3b\u52a8\u4e3b\u8981\u7528\u6237 +gb.feed = feed +gb.cancel = \u53d6\u6d88 +gb.changePassword = \u4fee\u6539\u5bc6\u7801 +gb.isFederated = is federated +gb.federateThis = federate this repository +gb.federateOrigin = federate the origin +gb.excludeFromFederation = exclude from federation +gb.excludeFromFederationDescription = \u7981\u6b62\u5df2 federated \u7684 Gitblit \u5b9e\u4f8b\u4ece\u672c\u8d26\u6237\u62c9\u53d6 +gb.tokens = federation tokens +gb.tokenAllDescription = all repositories, users, & settings +gb.tokenUnrDescription = all repositories & users +gb.tokenJurDescription = all repositories +gb.federatedRepositoryDefinitions = \u7248\u672c\u5e93\u5b9a\u4e49 +gb.federatedUserDefinitions = \u7528\u6237\u5b9a\u4e49 +gb.federatedSettingDefinitions = \u8bbe\u7f6e\u5b9a\u4e49 +gb.proposals = federation proposals +gb.received = \u5df2\u63a5\u53d7 +gb.type = type +gb.token = token +gb.repositories = \u7248\u672c\u5e93 +gb.proposal = proposal +gb.frequency = \u9891\u7387 +gb.folder = \u6587\u4ef6\u5939 +gb.lastPull = \u4e0a\u4e00\u6b21\u62c9\u53d6 +gb.nextPull = \u4e0b\u4e00\u6b21\u62c9\u53d6 +gb.inclusions = \u5305\u542b\u5185\u5bb9 +gb.exclusions = \u4f8b\u5916 +gb.registration = \u6ce8\u518c +gb.registrations = federation \u6ce8\u518c +gb.sendProposal = propose +gb.status = \u72b6\u6001 +gb.origin = origin +gb.headRef = \u9ed8\u8ba4\u5206\u652f (HEAD) +gb.headRefDescription = \u4fee\u6539 HEAD \u6240\u6307\u5411\u7684 ref\u3002 \u4f8b\u5982: refs/heads/master +gb.federationStrategy = federation \u7b56\u7565 +gb.federationRegistration = federation \u6ce8\u518c +gb.federationResults = federation \u62c9\u53d6\u7ed3\u679c +gb.federationSets = federation \u96c6 +gb.message = \u6d88\u606f +gb.myUrlDescription = \u60a8\u7684 Gitblit \u5b9e\u4f8b\u7684\u516c\u5171\u8bbf\u95ee\u7f51\u5740 +gb.destinationUrl = \u53d1\u9001\u81f3 +gb.destinationUrlDescription = \u4f60\u6240\u8981\u53d1\u9001proposal\u7684 Gitblit \u5b9e\u4f8b\u7f51\u5740 +gb.users = \u7528\u6237 +gb.federation = federation +gb.error = \u9519\u8bef +gb.refresh = \u5237\u65b0 +gb.browse = \u6d4f\u89c8 +gb.clone = \u514b\u9686 +gb.filter = \u8fc7\u6ee4 +gb.create = \u521b\u5efa +gb.servers = \u670d\u52a1\u5668 +gb.recent = \u6700\u8fd1 +gb.available = \u53ef\u7528 +gb.selected = \u5df2\u9009\u4e2d +gb.size = \u5927\u5c0f +gb.downloading = \u4e0b\u8f7d\u4e2d +gb.loading = \u8f7d\u5165\u4e2d +gb.starting = \u542f\u52a8\u4e2d +gb.general = \u5e38\u89c4 +gb.settings = \u8bbe\u7f6e +gb.manage = \u7ba1\u7406 +gb.lastLogin = \u4e0a\u6b21\u767b\u5f55 +gb.skipSizeCalculation = \u5ffd\u7565\u5927\u5c0f\u4f30\u8ba1 +gb.skipSizeCalculationDescription = \u4e0d\u8ba1\u7b97\u7248\u672c\u5e93\u5927\u5c0f\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09 +gb.skipSummaryMetrics = \u5ffd\u7565\u6982\u51b5\u5904 metrics +gb.skipSummaryMetricsDescription = \u6982\u51b5\u9875\u9762\u4e0d\u8ba1\u7b97metrics\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09 +gb.accessLevel = \u8bbf\u95ee\u7ea7\u522b +gb.default = \u9ed8\u8ba4 +gb.setDefault = \u9ed8\u8ba4\u8bbe\u7f6e +gb.since = \u81ea\u4ece +gb.status = \u72b6\u6001 +gb.bootDate = \u542f\u52a8\u65e5\u671f +gb.servletContainer = servlet container +gb.heapMaximum = \u6700\u5927\u5806 +gb.heapAllocated = \u5df2\u5206\u914d\u5806 +gb.heapUsed = \u5df2\u4f7f\u7528\u5806 +gb.free = \u7a7a\u95f2 +gb.version = \u7248\u672c +gb.releaseDate = \u53d1\u884c\u65e5\u671f +gb.date = \u65e5\u671f +gb.activity = \u6d3b\u52a8 +gb.subscribe = \u8ba2\u9605 +gb.branch = \u5206\u652f +gb.maxHits = \u6700\u5927\u547d\u4e2d\u6570 +gb.recentActivity = \u6700\u8fd1\u6d3b\u52a8 +gb.recentActivityStats = \u6700\u8fd1{0}\u5929 / {2}\u4f4d\u7528\u6237\u505a\u4e86{1}\u6b21\u63d0\u4ea4 +gb.recentActivityNone = \u6700\u8fd1{0}\u5929 / \u6ca1\u6709\u6d3b\u52a8 +gb.dailyActivity = \u65e5\u5e38\u6d3b\u52a8 +gb.activeRepositories = \u6d3b\u8dc3\u7684\u7248\u672c\u5e93 +gb.activeAuthors = \u6d3b\u8dc3\u7528\u6237 +gb.commits = \u63d0\u4ea4\u6b21\u6570 +gb.teams = \u56e2\u961f +gb.teamName = \u56e2\u961f\u540d\u79f0 +gb.teamMembers = \u56e2\u961f\u6210\u5458 +gb.teamMemberships = \u56e2\u961f\u6210\u5458 +gb.newTeam = \u6dfb\u52a0\u56e2\u961f +gb.permittedTeams = \u5141\u8bb8\u56e2\u961f +gb.emptyRepository = \u7a7a\u7248\u672c\u5e93 +gb.repositoryUrl = \u7248\u672c\u5e93\u5730\u5740 +gb.mailingLists = \u90ae\u4ef6\u5217\u8868 +gb.preReceiveScripts = pre-receive \u811a\u672c +gb.postReceiveScripts = post-receive \u811a\u672c +gb.hookScripts = hook \u811a\u672c +gb.customFields = \u81ea\u5b9a\u4e49\u57df +gb.customFieldsDescription = Groovy\u811a\u672c\u652f\u6301\u7684\u81ea\u5b9a\u4e49\u57df +gb.accessPermissions = \u8bbf\u95ee\u6743\u9650 +gb.filters = \u8fc7\u6ee4 +gb.generalDescription = \u4e00\u822c\u8bbe\u7f6e +gb.accessPermissionsDescription = \u6309\u7167\u7528\u6237\u548c\u56e2\u961f\u9650\u5236\u8bbf\u95ee +gb.accessPermissionsForUserDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u6216\u8005\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650 +gb.accessPermissionsForTeamDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u5e76\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650 +gb.federationRepositoryDescription = \u4e0e\u5176\u4ed6Gitblit\u670d\u52a1\u5668\u5206\u4eab\u7248\u672c\u5e93 +gb.hookScriptsDescription = \u5728\u670d\u52a1\u5668\u4e0a\u8fd0\u884cGroovy\u811a\u672c +gb.reset = \u91cd\u7f6e +gb.pages = \u9875\u9762 +gb.workingCopy = \u5de5\u4f5c\u526f\u672c +gb.workingCopyWarning = \u6b64\u7248\u672c\u5e93\u5b58\u5728\u4e00\u4efd\u5de5\u4f5c\u526f\u672c\uff0c\u65e0\u6cd5\u8fdb\u884c\u63a8\u9001 +gb.query = \u67e5\u8be2 +gb.queryHelp = \u652f\u6301\u6807\u51c6\u67e5\u8be2\u683c\u5f0f.<p/><p/>\u8bf7\u67e5\u770b <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene \u67e5\u8be2\u5904\u7406\u5668\u683c\u5f0f</a> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u5185\u5bb9\u3002 +gb.queryResults = \u7ed3\u679c {0} - {1} ({2} \u6b21\u547d\u4e2d) +gb.noHits = \u672a\u547d\u4e2d +gb.authored = authored +gb.committed = committed +gb.indexedBranches = \u5df2\u7d22\u5f15\u5206\u652f +gb.indexedBranchesDescription = \u9009\u62e9\u8981\u653e\u5165\u4f60\u7684 Lucene \u7d22\u5f15\u7684\u5206\u652f +gb.noIndexedRepositoriesWarning = \u60a8\u7684\u6240\u6709\u7248\u672c\u5e93\u90fd\u6ca1\u6709\u7ecf\u8fc7Lucene\u7d22\u5f15 +gb.undefinedQueryWarning = \u67e5\u8be2\u672a\u5b9a\u4e49! +gb.noSelectedRepositoriesWarning = \u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u7248\u672c\u5e93! +gb.luceneDisabled = Lucene\u7d22\u5f15\u5df2\u88ab\u7981\u6b62 +gb.failedtoRead = \u8bfb\u53d6\u5931\u8d25 +gb.isNotValidFile = \u4e0d\u662f\u5408\u6cd5\u6587\u4ef6 +gb.failedToReadMessage = \u5728 {0} \u4e2d\u8bfb\u53d6\u9ed8\u8ba4\u6d88\u606f\u5931\u8d25! +gb.passwordsDoNotMatch = \u5bc6\u7801\u4e0d\u5339\u914d! +gb.passwordTooShort = \u5bc6\u7801\u957f\u5ea6\u592a\u77ed\u3002\u6700\u77ed\u957f\u5ea6 {0} \u4e2a\u5b57\u7b26\u3002 +gb.passwordChanged = \u5bc6\u7801\u4fee\u6539\u6210\u529f\u3002 +gb.passwordChangeAborted = \u5bc6\u7801\u4fee\u6539\u7ec8\u6b62 +gb.pleaseSetRepositoryName = \u8bf7\u8bbe\u7f6e\u4e00\u4e2a\u7248\u672c\u5e93\u540d\u79f0! +gb.illegalLeadingSlash = \u7981\u6b62\u4f7f\u7528\u6839\u76ee\u5f55\u5f15\u7528 (/) \u3002 +gb.illegalRelativeSlash = \u76f8\u5bf9\u6587\u4ef6\u5939\u8def\u5f84(../)\u7981\u6b62\u4f7f\u7528 +gb.illegalCharacterRepositoryName = \u7248\u672c\u5e93\u4e2d\u542b\u6709\u4e0d\u5408\u6cd5\u5b57\u7b26 ''{0}'' ! +gb.selectAccessRestriction = \u8bf7\u9009\u62e9\u8bbf\u95ee\u6743\u9650\uff01 +gb.selectFederationStrategy = \u8bf7\u9009\u62e9federation\u7b56\u7565! +gb.pleaseSetTeamName = \u8bf7\u8f93\u5165\u4e00\u4e2a\u56e2\u961f\u540d\u79f0\uff01 +gb.teamNameUnavailable = \u56e2\u961f\u540d ''{0}'' \u4e0d\u5408\u6cd5. +gb.teamMustSpecifyRepository = \u56e2\u961f\u5fc5\u987b\u62e5\u6709\u81f3\u5c11\u4e00\u4e2a\u7248\u672c\u5e93\u3002 +gb.teamCreated = \u6210\u529f\u521b\u5efa\u65b0\u56e2\u961f ''{0}'' . +gb.pleaseSetUsername = \u8bf7\u8f93\u5165\u7528\u6237\u540d\uff01 +gb.usernameUnavailable = \u7528\u6237\u540d ''{0}'' \u4e0d\u53ef\u7528.. +gb.combinedMd5Rename = Gitblit\u91c7\u7528\u6df7\u5408md5\u5bc6\u7801\u54c8\u5e0c\u3002\u56e0\u6b64\u5fc5\u987b\u5728\u4fee\u6539\u7528\u6237\u540d\u540e\u4fee\u6539\u5bc6\u7801\u3002 +gb.userCreated = \u6210\u529f\u521b\u5efa\u65b0\u7528\u6237 \\"{0}\\"\u3002 +gb.couldNotFindFederationRegistration = \u65e0\u6cd5\u627e\u5230federation registration! +gb.failedToFindGravatarProfile = \u52a0\u8f7d {0} \u7684Gravatar\u4fe1\u606f\u5931\u8d25 +gb.branchStats = {0} \u4e2a\u63d0\u4ea4\u548c {1} \u4e2a\u6807\u7b7e\u5728 {2} \u5185 +gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5e93! +gb.repositoryNotSpecifiedFor = \u6ca1\u6709\u4e3a {0} \u8bbe\u7f6e\u7248\u672c\u5e93! +gb.canNotLoadRepository = \u65e0\u6cd5\u8f7d\u5165\u7248\u672c\u5e93 +gb.commitIsNull = \u63d0\u4ea4\u5185\u5bb9\u4e3a\u7a7a +gb.unauthorizedAccessForRepository = \u672a\u6388\u6743\u8bbf\u95ee\u7248\u672c\u5e93 +gb.failedToFindCommit = \u5728 {1} \u4e2d {2} \u4e2a\u9875\u9762\u5185\u67e5\u627e\u63d0\u4ea4 \\"{0}\\"\u5931\u8d25! +gb.couldNotFindFederationProposal = \u65e0\u6cd5\u627e\u5230federation proposal! +gb.invalidUsernameOrPassword = \u7528\u6237\u540d\u6216\u8005\u5bc6\u7801\u9519\u8bef\uff01 +gb.OneProposalToReview = 1\u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5\u3002 +gb.nFederationProposalsToReview = {0} \u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5 +gb.couldNotFindTag = \u65e0\u6cd5\u627e\u5230\u6807\u7b7e {0} +gb.couldNotCreateFederationProposal = \u65e0\u6cd5\u521b\u5efafederation proposal! +gb.pleaseSetGitblitUrl = \u8bf7\u8f93\u5165\u4f60\u7684Gitblit\u7f51\u5740! +gb.pleaseSetDestinationUrl = \u8bf7\u4e3a\u4f60\u7684proposal\u8f93\u5165\u4e00\u4e2a\u76ee\u6807\u5730\u5740! +gb.proposalReceived = \u6210\u529f\u4ece {0} \u63a5\u6536Proposal. +gb.noGitblitFound = \u62b1\u6b49, {0} \u65e0\u6cd5\u5728{1} \u4e2d\u627e\u5230Gitblit\u5b9e\u4f8b\u3002 +gb.noProposals = \u62b1\u6b49, {0} \u5f53\u524d\u4e0d\u63a5\u53d7proposals\u3002 +gb.noFederation = \u62b1\u6b49, {0} \u6ca1\u6709\u4e0e\u4efb\u4f55Gitblit\u5b9e\u4f8b\u8bbe\u7f6efederate\u3002. +gb.proposalFailed = \u62b1\u6b49, {0} \u65e0\u6cd5\u63a5\u53d7\u4efb\u4f55proposal\u6570\u636e! +gb.proposalError = \u62b1\u6b49\uff0c{0} \u62a5\u544a\u4e2d\u53d1\u73b0\u672a\u9884\u671f\u7684\u9519\u8bef\uff01 +gb.failedToSendProposal = \u53d1\u9001proposal\u5931\u8d25! +gb.userServiceDoesNotPermitAddUser = {0} \u4e0d\u5141\u8bb8\u6dfb\u52a0\u7528\u6237! +gb.userServiceDoesNotPermitPasswordChanges = {0} \u4e0d\u5141\u8bb8\u8fdb\u884c\u5bc6\u7801\u4fee\u6539! +gb.displayName = \u663e\u793a\u540d\u79f0 +gb.emailAddress = \u90ae\u7bb1 +gb.errorAdminLoginRequired = \u9700\u8981\u7ba1\u7406\u5458\u767b\u9646 +gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u5458\u624d\u53ef\u4ee5\u521b\u5efa\u7248\u672c\u5e93 +gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u5458\u6216\u8005\u6240\u6709\u8005\u624d\u53ef\u4ee5\u7f16\u8f91\u4ee3\u7801\u5e93 +gb.errorAdministrationDisabled = \u7ba1\u7406\u6743\u9650\u88ab\u7981\u6b62\u3002 +gb.lastNDays = \u6700\u8fd1 {0} \u5929 +gb.completeGravatarProfile = \u5728Gravatar.com\u4e0a\u5b8c\u6210\u4e2a\u4eba\u8bbe\u5b9a +gb.none = \u65e0 +gb.line = \u884c +gb.content = \u5185\u5bb9 +gb.empty = \u7a7a\u767d\u7248\u672c\u5e93 +gb.inherited = \u7ee7\u627f +gb.deleteRepository = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \uff1f +gb.repositoryDeleted = \u7248\u672c\u5e93 ''{0}'' \u5df2\u5220\u9664\u3002 +gb.repositoryDeleteFailed = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \u5931\u8d25\uff01 +gb.deleteUser = \u5220\u9664\u7528\u6237 \\"{0}\\" \uff1f +gb.userDeleted = \u7528\u6237 ''{0}'' \u5df2\u5220\u9664\uff01 +gb.userDeleteFailed = \u5220\u9664\u7528\u6237''{0}''\u5931\u8d25\uff01 +gb.time.justNow = \u521a\u521a +gb.time.today = \u4eca\u5929 +gb.time.yesterday = \u6628\u5929 +gb.time.minsAgo = {0} \u5206\u949f\u4ee5\u524d +gb.time.hoursAgo = {0} \u5c0f\u65f6\u4ee5\u524d +gb.time.daysAgo = {0} \u5929\u4ee5\u524d +gb.time.weeksAgo = {0} \u5468\u4ee5\u524d +gb.time.monthsAgo = {0} \u4e2a\u6708\u4ee5\u524d +gb.time.oneYearAgo = 1 \u5e74\u4ee5\u524d +gb.time.yearsAgo = {0} \u5e74\u4ee5\u524d +gb.duration.oneDay = 1 \u5929 +gb.duration.days = {0} \u5929 +gb.duration.oneMonth = 1 \u6708 +gb.duration.months = {0} \u6708 +gb.duration.oneYear = 1 \u5e74 +gb.duration.years = {0} \u5e74 +gb.authorizationControl = \u6388\u6743\u63a7\u5236 +gb.allowAuthenticatedDescription = \u6388\u4e88\u6240\u6709\u8ba4\u8bc1\u7528\u6237\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650 +gb.allowNamedDescription = \u6388\u4e88\u6307\u5b9a\u540d\u79f0\u7684\u7528\u6237\u6216\u56e2\u961f\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650 +gb.markdownFailure = \u8bfb\u53d6 Markdown \u5185\u5bb9\u5931\u8d25\uff01 +gb.clearCache = \u6e05\u9664\u7f13\u5b58 +gb.projects = \u9879\u76ee +gb.project = \u9879\u76ee +gb.allProjects = \u6240\u6709\u9879\u76ee +gb.copyToClipboard = \u590d\u5236\u5230\u526a\u8d34\u677f +gb.fork = \u6d3e\u751f +gb.forks = \u6d3e\u751f +gb.forkRepository = \u6d3e\u751f {0} ? +gb.repositoryForked = {0} \u5df2\u88ab\u6d3e\u751f +gb.repositoryForkFailed = \u6d3e\u751f\u5931\u8d25 +gb.personalRepositories = \u79c1\u4eba\u7248\u672c\u5e93 +gb.allowForks = \u5141\u8bb8\u6d3e\u751f +gb.allowForksDescription = \u5141\u8bb8\u8ba4\u8bc1\u7528\u6237\u6d3e\u751f\u6b64\u7248\u672c\u5e93 +gb.forkedFrom = \u6d3e\u751f\u81ea +gb.canFork = \u5141\u8bb8\u6d3e\u751f +gb.canForkDescription = \u5141\u8bb8\u6d3e\u751f\u8ba4\u8bc1\u7248\u672c\u5e93\u5230\u79c1\u4eba\u7248\u672c\u5e93 +gb.myFork = \u67e5\u770b\u6211\u7684\u6d3e\u751f +gb.forksProhibited = \u7981\u6b62\u6d3e\u751f +gb.forksProhibitedWarning = \u5f53\u524d\u7248\u672c\u5e93\u7981\u6b62\u6d3e\u751f +gb.noForks = {0} \u6ca1\u6709\u6d3e\u751f +gb.forkNotAuthorized = \u62b1\u6b49\uff0c\u4f60\u65e0\u6743\u6d3e\u751f {0} +gb.forkInProgress = \u6b63\u5728\u6d3e\u751f +gb.preparingFork = \u6b63\u5728\u4e3a\u60a8\u51c6\u5907\u6d3e\u751f... +gb.isFork = \u5df2\u6d3e\u751f +gb.canCreate = \u5141\u8bb8\u521b\u5efa +gb.canCreateDescription = \u5141\u8bb8\u521b\u5efa\u79c1\u4eba\u7248\u672c\u5e93 +gb.illegalPersonalRepositoryLocation = \u60a8\u7684\u79c1\u4eba\u7248\u672c\u5e93\u5fc5\u987b\u4f4d\u4e8e \\"{0}\\" +gb.verifyCommitter = \u9a8c\u8bc1\u63d0\u4ea4\u8005 +gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7684\u8eab\u4efd\u4e0e Gitblit \u7528\u6237\u8eab\u4efd\u76f8\u7b26 +gb.verifyCommitterNote = \u6240\u6709\u5408\u5e76\u9009\u9879\u9700\u8981\u4f7f\u7528 \\"--no-ff\\" \u6765\u6267\u884c\u63d0\u4ea4\u8005\u9a8c\u8bc1 +gb.repositoryPermissions = \u7248\u672c\u5e93\u6743\u9650 +gb.userPermissions = \u7528\u6237\u6743\u9650 +gb.teamPermissions = \u56e2\u961f\u6743\u9650 +gb.add = \u6dfb\u52a0 +gb.noPermission = \u5220\u9664\u6b64\u6743\u9650 +gb.excludePermission = {0} (exclude) +gb.viewPermission = {0} (view) +gb.clonePermission = {0} (clone) +gb.pushPermission = {0} (push) +gb.createPermission = {0} (push, ref creation) +gb.deletePermission = {0} (push, ref creation+deletion) +gb.rewindPermission = {0} (push, ref creation+deletion+rewind) +gb.permission = \u6743\u9650 +gb.regexPermission = \u6b64\u6743\u9650\u662f\u901a\u8fc7\u6b63\u5219\u8868\u8fbe\u5f0f \\"{0}\\" \u8bbe\u7f6e +gb.accessDenied = \u8bbf\u95ee\u88ab\u62d2\u7edd +gb.busyCollectingGarbage = \u62b1\u6b49\uff0cGitblit\u6b63\u5728 {0} \u5185\u6e05\u7406\u5783\u573e +gb.gcPeriod = GC \u65f6\u95f4 +gb.gcPeriodDescription = \u5783\u573e\u6e05\u7406\u7684\u6301\u7eed\u65f6\u95f4 +gb.gcThreshold = GC \u9600\u503c +gb.gcThresholdDescription = \u6fc0\u53d1\u5783\u573e\u6e05\u7406\u7684\u6700\u5c0f objects \u5927\u5c0f +gb.ownerPermission = \u7248\u672c\u5e93\u521b\u5efa\u8005 +gb.administrator = \u7ba1\u7406\u5458 +gb.administratorPermission = Gitblit \u7ba1\u7406\u5458 +gb.team = \u56e2\u961f +gb.teamPermission = \u901a\u8fc7 \\"{0}\\" \u56e2\u961f\u6210\u5458\u8bbe\u7f6e\u6743\u9650 +gb.missing = \u4e0d\u5b58\u5728! +gb.missingPermission = \u6b64\u6743\u9650\u7684\u7248\u672c\u5e93\u4e0d\u5b58\u5728! +gb.mutable = mutable +gb.specified = specified +gb.effective = effective +gb.organizationalUnit = \u7ec4\u7ec7\u90e8\u5206 +gb.organization = \u7ec4\u7ec7 +gb.locality = \u5730\u533a +gb.stateProvince = \u5dde\u6216\u7701 +gb.countryCode = \u56fd\u5bb6\u4ee3\u7801 +gb.properties = \u5c5e\u6027 +gb.issued = issued +gb.expires = \u5230\u671f +gb.expired = \u5df2\u5230\u671f +gb.expiring = \u5373\u5c06\u8fc7\u671f +gb.revoked = \u5df2\u64a4\u9500 +gb.serialNumber = \u5e8f\u5217\u53f7 +gb.certificates = \u8bc1\u4e66 +gb.newCertificate = \u521b\u5efa\u8bc1\u4e66 +gb.revokeCertificate = \u64a4\u9500\u8bc1\u4e66 +gb.sendEmail = \u53d1\u9001\u90ae\u4ef6 +gb.passwordHint = \u5bc6\u7801\u63d0\u793a +gb.ok = \u786e\u5b9a +gb.invalidExpirationDate = \u65e0\u6548\u7684\u8fc7\u671f\u65f6\u95f4! +gb.passwordHintRequired = \u9700\u8981\u586b\u5199\u5bc6\u7801\u63d0\u793a! +gb.viewCertificate = \u67e5\u770b\u8bc1\u4e66 +gb.subject = \u4e3b\u9898 +gb.issuer = \u63d0\u4ea4\u8005 +gb.validFrom = \u6709\u6548\u671f\u5f00\u59cb\u81ea +gb.validUntil = \u6709\u6548\u671f\u622a\u6b62\u4e8e +gb.publicKey = \u516c\u94a5 +gb.signatureAlgorithm = \u7b7e\u540d\u7b97\u6cd5 +gb.sha1FingerPrint = SHA-1 \u6307\u7eb9\u7b97\u6cd5 +gb.md5FingerPrint = MD5 \u6307\u7eb9\u7b97\u6cd5 +gb.reason = \u7406\u7531 +gb.revokeCertificateReason = \u8bf7\u9009\u62e9\u64a4\u9500\u8bc1\u4e66\u7684\u7406\u7531 +gb.unspecified = \u672a\u6307\u5b9a +gb.keyCompromise = key compromise +gb.caCompromise = CA compromise +gb.affiliationChanged = \u96b6\u5c5e\u5173\u7cfb\u5df2\u4fee\u6539 +gb.superseded = \u5df2\u53d6\u4ee3 +gb.cessationOfOperation = \u505c\u6b62\u64cd\u4f5c +gb.privilegeWithdrawn = \u7279\u6743\u5df2\u64a4\u56de +gb.time.inMinutes = {0} \u5206\u949f\u4e4b\u5185 +gb.time.inHours = {0} \u5c0f\u65f6\u4e4b\u5185 +gb.time.inDays = {0} \u5929\u4e4b\u5185 +gb.hostname = hostname +gb.hostnameRequired = \u8bf7\u8f93\u5165 hostname +gb.newSSLCertificate = \u521b\u5efa\u670d\u52a1\u5668 SSL \u8bc1\u4e66 +gb.newCertificateDefaults = \u521b\u5efa\u8bc1\u4e66\u9ed8\u8ba4\u8bbe\u7f6e +gb.duration = \u6301\u7eed\u65f6\u95f4 +gb.certificateRevoked = \u8bc1\u4e66 {0,number,0} \u5df2\u88ab\u64a4\u9500 +gb.clientCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u5ba2\u6237\u7aef\u8bc1\u4e66 +gb.sslCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u670d\u52a1\u5668 SSL \u8bc1\u4e66 +gb.newClientCertificateMessage = \u6ce8\u610f:\\n\u6b64\u5bc6\u7801\u5e76\u975e\u7528\u6237\u5bc6\u7801, \u8fd9\u662f\u4fdd\u5b58\u7528\u6237 keystore \u7684\u5bc6\u7801\u3002 \u7531\u4e8e\u672c\u5bc6\u7801\u672a\u5b58\u50a8\uff0c\u56e0\u6b64\u4f60\u5fc5\u987b\u4e00\u4e2a\u5bc6\u7801\u63d0\u793a\uff0c\u8fd9\u4e2a\u63d0\u793a\u4f1a\u8bb0\u5f55\u5728\u7528\u6237\u7684 README \u6587\u6863\u5185\u3002 +gb.certificate = \u8bc1\u4e66 +gb.emailCertificateBundle = \u53d1\u9001\u5ba2\u6237\u7aef\u8bc1\u4e66 +gb.pleaseGenerateClientCertificate = \u8bf7\u4e3a {0} \u751f\u6210\u4e00\u4e2a\u5ba2\u6237\u7aef\u8bc1\u4e66 +gb.clientCertificateBundleSent = {0} \u7684\u5ba2\u6237\u7aef\u8bc1\u4e66\u5df2\u53d1\u9001 +gb.enterKeystorePassword = \u8bf7\u8f93\u5165 Gitblit keystore \u5bc6\u7801 +gb.warning = \u8b66\u544a +gb.jceWarning = \u60a8\u7684 JAVA \u8fd0\u884c\u73af\u5883\u4e0d\u5305\u542b \\"JCE Unlimited Strength Jurisdiction Policy\\" \u6587\u4ef6\u3002\\n\u8fd9\u5c06\u5bfc\u81f4\u60a8\u6700\u591a\u53ea\u80fd\u75287\u4e2a\u5b57\u7b26\u7684\u5bc6\u7801\u4fdd\u62a4\u60a8\u7684 keystore\u3002 \\n\u8fd9\u4e9b\u662f\u4e00\u4e9b\u53ef\u9009\u4e0b\u8f7d\u7684\u653f\u7b56\u6587\u4ef6\u3002\\n\\n\u4f60\u662f\u5426\u8981\u7ee7\u7eed\u751f\u6210\u8bc1\u4e66\uff1f\\n\\n\u9009\u62e9\u5426\u7684\u8bdd\uff0c\u5c06\u4f1a\u6253\u5f00\u4e00\u4e2a\u6d4f\u89c8\u5668\u754c\u9762\u4f9b\u60a8\u4e0b\u8f7d\u76f8\u5173\u6587\u4ef6\u3002 +gb.maxActivityCommits = \u6700\u5927\u6d3b\u52a8\u63d0\u4ea4\u6570 +gb.maxActivityCommitsDescription = \u6d3b\u52a8\u9875\u9762\u663e\u793a\u7684\u6700\u5927\u63d0\u4ea4\u6570 +gb.noMaximum = \u65e0\u4e0a\u9650 +gb.attributes = \u5c5e\u6027 +gb.serveCertificate = \u4f7f\u7528\u6b64\u8bc1\u4e66\u63d0\u4f9b https \u652f\u6301 +gb.sslCertificateGeneratedRestart = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684 SSL \u8bc1\u4e66.\\n\u4f60\u5fc5\u987b\u91cd\u65b0\u542f\u52a8 Gitblit \u4ee5\u4f7f\u7528\u6b64\u8bc1\u4e66\u3002\\n\\n\u5982\u679c\u60a8\u4f7f\u7528 '--alias' \u53c2\u6570\u542f\u52a8\uff0c\u4f60\u5fc5\u987b\u4e5f\u8981\u8bbe\u7f6e ''--alias {0}''\u3002 +gb.validity = \u5408\u6cd5\u6027 +gb.siteName = \u7f51\u7ad9\u540d\u79f0 +gb.siteNameDescription = \u60a8\u7684\u670d\u52a1\u5668\u7684\u7b80\u8981\u63cf\u8ff0 +gb.excludeFromActivity = \u4ece\u6d3b\u52a8\u9875\u9762\u6392\u9664 +gb.isSparkleshared = repository is Sparkleshared
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java index d1ee2710..c733c992 100644 --- a/src/com/gitblit/wicket/pages/BasePage.java +++ b/src/com/gitblit/wicket/pages/BasePage.java @@ -98,6 +98,10 @@ public abstract class BasePage extends WebPage { return GitBlitWebSession.get().getLocale().getLanguage();
}
+ protected String getCountryCode() {
+ return GitBlitWebSession.get().getLocale().getCountry().toLowerCase();
+ }
+
protected TimeUtils getTimeUtils() {
if (timeUtils == null) {
ResourceBundle bundle;
@@ -132,7 +136,10 @@ public abstract class BasePage extends WebPage { private void login() {
GitBlitWebSession session = GitBlitWebSession.get();
if (session.isLoggedIn() && !session.isSessionInvalidated()) {
- // already have a session
+ // already have a session, refresh usermodel to pick up
+ // any changes to permissions or roles (issue-186)
+ UserModel user = GitBlit.self().getUserModel(session.getUser().username);
+ session.setUser(user);
return;
}
@@ -429,7 +436,7 @@ public abstract class BasePage extends WebPage { GitBlitWebSession session = GitBlitWebSession.get();
if (session.isLoggedIn()) {
UserModel user = session.getUser();
- boolean editCredentials = GitBlit.self().supportsCredentialChanges();
+ boolean editCredentials = GitBlit.self().supportsCredentialChanges(user);
boolean standardLogin = session.authenticationType.isStandard();
// username, logout, and change password
diff --git a/src/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/com/gitblit/wicket/pages/ChangePasswordPage.java index 5e663006..c4014208 100644 --- a/src/com/gitblit/wicket/pages/ChangePasswordPage.java +++ b/src/com/gitblit/wicket/pages/ChangePasswordPage.java @@ -51,12 +51,13 @@ public class ChangePasswordPage extends RootSubPage { throw new RestartResponseException(getApplication().getHomePage());
}
- if (!GitBlit.self().supportsCredentialChanges()) {
+ UserModel user = GitBlitWebSession.get().getUser();
+ if (!GitBlit.self().supportsCredentialChanges(user)) {
error(MessageFormat.format(getString("gb.userServiceDoesNotPermitPasswordChanges"),
- GitBlit.getString(Keys.realm.userService, "users.conf")), true);
+ GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
}
- setupPage(getString("gb.changePassword"), GitBlitWebSession.get().getUsername());
+ setupPage(getString("gb.changePassword"), user.username);
StatelessForm<Void> form = new StatelessForm<Void>("passwordForm") {
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.java b/src/com/gitblit/wicket/pages/CommitDiffPage.java index 585ce8e3..3ad70742 100644 --- a/src/com/gitblit/wicket/pages/CommitDiffPage.java +++ b/src/com/gitblit/wicket/pages/CommitDiffPage.java @@ -15,14 +15,14 @@ */ package com.gitblit.wicket.pages; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import java.text.MessageFormat; - import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.markup.repeater.data.ListDataProvider; @@ -150,15 +150,12 @@ public class CommitDiffPage extends RepositoryPage { // quick links if (entry.isSubmodule()) { // submodule - item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils - .newPathParameter(submodulePath, entry.objectId, entry.path)).setEnabled(false)); + item.add(new ExternalLink("patch", "").setEnabled(false)); item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils .newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule)); - item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils - .newPathParameter(submodulePath, entry.objectId, entry.path)).setEnabled(false)); + item.add(new ExternalLink("blame", "").setEnabled(false)); item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils - .newPathParameter(submodulePath, entry.objectId, entry.path)) - .setEnabled(hasSubmodule)); + .newPathParameter(repositoryName, entry.commitId, entry.path))); } else { // tree or blob item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java index 17621ad0..c5a24c8d 100644 --- a/src/com/gitblit/wicket/pages/CommitPage.java +++ b/src/com/gitblit/wicket/pages/CommitPage.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
@@ -184,16 +185,14 @@ public class CommitPage extends RepositoryPage { if (entry.isSubmodule()) {
// submodule
item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
- .newPathParameter(submodulePath, entry.objectId, entry.path))
- .setEnabled(false));
+ .newPathParameter(repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
- item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
- .newPathParameter(submodulePath, entry.objectId, entry.path))
- .setEnabled(false));
+ item.add(new ExternalLink("blame", "").setEnabled(false));
item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
- .newPathParameter(submodulePath, entry.objectId, entry.path))
- .setEnabled(hasSubmodule));
+ .newPathParameter(repositoryName, entry.commitId, entry.path))
+ .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
} else {
// tree or blob
item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html index 60893f44..7fc0de23 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -50,7 +50,7 @@ <div class="tab-pane" id="permissions">
<table class="plain">
<tbody class="settings">
- <tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select class="span2" wicket:id="owner" tabindex="15" /> <span class="help-inline"><wicket:message key="gb.ownerDescription"></wicket:message></span></td></tr>
+ <tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="15" /> </td></tr>
<tr><th colspan="2"><hr/></th></tr>
<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr>
<tr><th colspan="2"><hr/></th></tr>
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java index a071b69e..d68d6550 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java @@ -94,7 +94,7 @@ public class EditRepositoryPage extends RootSubPage { // personal create permissions, inject personal repository path
model.name = user.getPersonalPath() + "/";
model.projectPath = user.getPersonalPath();
- model.owner = user.username;
+ model.addOwner(user.username);
// personal repositories are private by default
model.accessRestriction = AccessRestrictionType.VIEW;
model.authorizationControl = AuthorizationControl.NAMED;
@@ -164,6 +164,12 @@ public class EditRepositoryPage extends RootSubPage { final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams",
RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
+ // owners palette
+ List<String> owners = new ArrayList<String>(repositoryModel.owners);
+ List<String> persons = GitBlit.self().getAllUsernames();
+ final Palette<String> ownersPalette = new Palette<String>("owners", new ListModel<String>(owners), new CollectionModel<String>(
+ persons), new StringChoiceRenderer(), 12, true);
+
// indexed local branches palette
List<String> allLocalBranches = new ArrayList<String>();
allLocalBranches.add(Constants.DEFAULT_BRANCH);
@@ -326,6 +332,13 @@ public class EditRepositoryPage extends RootSubPage { }
repositoryModel.indexedBranches = indexedBranches;
+ // owners
+ repositoryModel.owners.clear();
+ Iterator<String> owners = ownersPalette.getSelectedChoices();
+ while (owners.hasNext()) {
+ repositoryModel.addOwner(owners.next());
+ }
+
// pre-receive scripts
List<String> preReceiveScripts = new ArrayList<String>();
Iterator<String> pres = preReceivePalette.getSelectedChoices();
@@ -377,8 +390,7 @@ public class EditRepositoryPage extends RootSubPage { // field names reflective match RepositoryModel fields
form.add(new TextField<String>("name").setEnabled(allowEditName));
form.add(new TextField<String>("description"));
- form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames())
- .setEnabled(GitBlitWebSession.get().canAdmin() && !repositoryModel.isPersonalRepository()));
+ form.add(ownersPalette);
form.add(new CheckBox("allowForks").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
@@ -559,7 +571,7 @@ public class EditRepositoryPage extends RootSubPage { isAdmin = true;
return;
} else {
- if (!model.owner.equalsIgnoreCase(user.username)) {
+ if (!model.isOwner(user.username)) {
// User is not an Admin nor Owner
error(getString("gb.errorOnlyAdminOrOwnerMayEditRepository"), true);
}
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.java b/src/com/gitblit/wicket/pages/EditTeamPage.java index 1991c02a..8344d387 100644 --- a/src/com/gitblit/wicket/pages/EditTeamPage.java +++ b/src/com/gitblit/wicket/pages/EditTeamPage.java @@ -212,7 +212,7 @@ public class EditTeamPage extends RootSubPage { form.add(new SimpleAttributeModifier("autocomplete", "off"));
// not all user services support manipulating team memberships
- boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges();
+ boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges(null);
// field names reflective match TeamModel fields
form.add(new TextField<String>("name"));
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index 7a01fb68..c060f237 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -55,9 +55,9 @@ public class EditUserPage extends RootSubPage { public EditUserPage() {
// create constructor
super();
- if (!GitBlit.self().supportsCredentialChanges()) {
+ if (!GitBlit.self().supportsAddUser()) {
error(MessageFormat.format(getString("gb.userServiceDoesNotPermitAddUser"),
- GitBlit.getString(Keys.realm.userService, "users.conf")), true);
+ GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
}
isCreate = true;
setupPage(new UserModel(""));
@@ -134,7 +134,7 @@ public class EditUserPage extends RootSubPage { }
boolean rename = !StringUtils.isEmpty(oldName)
&& !oldName.equalsIgnoreCase(username);
- if (GitBlit.self().supportsCredentialChanges()) {
+ if (GitBlit.self().supportsCredentialChanges(userModel)) {
if (!userModel.password.equals(confirmPassword.getObject())) {
error(getString("gb.passwordsDoNotMatch"));
return;
@@ -210,16 +210,16 @@ public class EditUserPage extends RootSubPage { form.add(new SimpleAttributeModifier("autocomplete", "off"));
// not all user services support manipulating username and password
- boolean editCredentials = GitBlit.self().supportsCredentialChanges();
+ boolean editCredentials = GitBlit.self().supportsCredentialChanges(userModel);
// not all user services support manipulating display name
- boolean editDisplayName = GitBlit.self().supportsDisplayNameChanges();
+ boolean editDisplayName = GitBlit.self().supportsDisplayNameChanges(userModel);
// not all user services support manipulating email address
- boolean editEmailAddress = GitBlit.self().supportsEmailAddressChanges();
+ boolean editEmailAddress = GitBlit.self().supportsEmailAddressChanges(userModel);
// not all user services support manipulating team memberships
- boolean editTeams = GitBlit.self().supportsTeamMembershipChanges();
+ boolean editTeams = GitBlit.self().supportsTeamMembershipChanges(userModel);
// field names reflective match UserModel fields
form.add(new TextField<String>("username").setEnabled(editCredentials));
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html new file mode 100644 index 00000000..a8ee2e25 --- /dev/null +++ b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html @@ -0,0 +1,53 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="nl"
+ lang="nl">
+
+<body>
+<wicket:extend>
+
+ <h2>Empty Repository</h2>
+ <p></p>
+ <div class="row">
+ <div class="span10">
+ <div class="alert alert-success">
+ <span wicket:id="repository" style="font-weight: bold;">[repository]</span> is een lege repositorie en kan niet bekeken worden door Gitblit.
+ <p></p>
+ Push aub een paar commitsome commits naar <span wicket:id="pushurl"></span>
+ <p></p>
+ <hr/>
+ Nadat u een paar commits gepushed hebt kunt u deze pagina <b>verversen</b> om de repository te bekijken.
+ </div>
+ </div>
+ </div>
+
+ <h3>Git Command-Line Syntax</h3>
+ <span style="padding-bottom:5px;">Als u geen lokale Git repositorie heeft, kunt u deze repository clonen, er een paar bestanden naar committen en deze commits teug pushen naar Gitblit.</span>
+ <p></p>
+ <pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+ <p></p>
+ <span style="padding-bottom:5px;">Als u al een lokale Git repositorie heeft met commits kunt u deze repository als een remote toevoegen en er naar toe pushen.</span>
+ <p></p>
+ <pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+ <p></p>
+ <h3>Learn Git</h3>
+ Als u niet goed weet wat u met deze informatie aan moet raden we aan om het <a href="http://book.git-scm.com">Git Community Book</a> of <a href="http://progit.org/book" target="_blank">Pro Git</a> te bestuderen voor een betere begrip van hoe u Git kunt gebruiken.
+ <p></p>
+ <h4>Open Source Git Clients</h4>
+ <ul>
+ <li><a href="http://git-scm.com">Git</a> - de officiele, command-line Git</li>
+ <li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Windows bestandsverkenner ingetratie (officiele command-line Git is wel nodig)</li>
+ <li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git voor de Eclipse IDE (gebaseerd op JGit, zoals Gitblit)</li>
+ <li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend voor Git met Windows Explorer en Visual Studio integratie</li>
+ <li><a href="http://gitx.laullon.com/">GitX (L)</a> - een Mac OS X Git client</li>
+ </ul>
+ <p></p>
+ <h4>Commercial/Closed-Source Git Clients</h4>
+ <ul>
+ <li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Een Java Git, Mercurial, en SVN client applicatie (officiele command-line Git is wel nodig)</li>
+ <li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Een gratis Mac Client voor Git, Mercurial, en SVN</li>
+ </ul>
+</wicket:extend>
+</body>
+</html>
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html new file mode 100644 index 00000000..351ef879 --- /dev/null +++ b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html @@ -0,0 +1,53 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
+ xml:lang="pt-br"
+ lang="pt-br">
+
+<body>
+<wicket:extend>
+
+ <h2>Repositório Vazio</h2>
+ <p></p>
+ <div class="row">
+ <div class="span10">
+ <div class="alert alert-success">
+ <span wicket:id="repository" style="font-weight: bold;">[repository]</span> é um repositório vazio e não pode ser visualizado pelo Gitblit.
+ <p></p>
+ Por favor faça o push de alguns commits para <span wicket:id="pushurl"></span>
+ <p></p>
+ <hr/>
+ Depois de ter feito push você poderá <b>atualizar</b> esta página para visualizar seu repositório.
+ </div>
+ </div>
+ </div>
+
+ <h3>Sintaxe dos comandos do Git</h3>
+ <span style="padding-bottom:5px;">Se você ainda não tem um repositório local do Git, então você deve primeiro clonar este repositório, fazer commit de alguns arquivos e então fazer push desses commits para o Gitblit.</span>
+ <p></p>
+ <pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+ <p></p>
+ <span style="padding-bottom:5px;">Se você já tem um repositório Git local com alguns commits, então você deve adicionar este repositório como uma referência remota e então fazer push.</span>
+ <p></p>
+ <pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+ <p></p>
+ <h3>Aprenda Git</h3>
+ Se você estiver com dúvidas sobre como ultilizar essas informações, uma sugestão seria dar uma olhada no livro <a href="http://book.git-scm.com">Git Community Book</a> ou <a href="http://progit.org/book" target="_blank">Pro Git</a> para entender melhor como usar o Git.
+ <p></p>
+ <h4>Alguns clients do Git que são Open Source</h4>
+ <ul>
+ <li><a href="http://git-scm.com">Git</a> - o Git oficial através de linhas de comando</li>
+ <li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Faz integração do Explorer do Windows com o Git (por isso requer o Git Oficial)</li>
+ <li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git para a IDE Eclipse (baseada no JGit, como o Gitblit)</li>
+ <li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - Interface (em C#) para o Git cuja a caracterÃstica é a integração com o Windows Explorer e o Visual Studio</li>
+ <li><a href="http://gitx.laullon.com/">GitX (L)</a> - um Cliente do Git para Mac OS X</li>
+ </ul>
+ <p></p>
+ <h4>Clients do Git proprietários ou com Código Fechado</h4>
+ <ul>
+ <li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Aplicação Client (em Java) para Git, Mercurial, e SVN (por isso requer o Git Oficial)</li>
+ <li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Client gratuito para o Mac que suporta Git, Mercurial e SVN</li>
+ </ul>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html new file mode 100644 index 00000000..4b21800e --- /dev/null +++ b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html @@ -0,0 +1,55 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" + xml:lang="zh-CN" + lang="zh-CN"> + +<body> +<wicket:extend> + + <h2>空版本库</h2> + <p></p> + <div class="row"> + <div class="span10"> + <div class="alert alert-success"> + <span wicket:id="repository" style="font-weight: bold;">[repository]</span> 版本库目å‰ä¸ºç©ºã€‚ + Gitblit æ— æ³•æŸ¥çœ‹ã€‚ + <p></p> + 请往æ¤ç½‘å€è¿›è¡ŒæŽ¨é€ <span wicket:id="pushurl"></span> + <p></p> + <hr/> + å½“ä½ æŽ¨é€å®Œæ¯•åŽä½ å¯ä»¥ <b>刷新</b> æ¤é¡µé¢é‡æ–°æŸ¥çœ‹æ‚¨çš„版本库。 + </div> + </div> + </div> + + <h3>Git å‘½ä»¤è¡Œæ ¼å¼</h3> + <span style="padding-bottom:5px;">如果您没有本地 Git 版本库, 您å¯ä»¥å…‹éš†æ¤ç‰ˆæœ¬åº“, æ交一些文件, 然åŽå°†æ‚¨çš„æ交推é€å›žGitblit。</span> + <p></p> + <pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre> + <p></p> + <span style="padding-bottom:5px;">如果您已ç»æœ‰ä¸€ä¸ªæœ¬åœ°çš„æ交过的版本库, 那么您å¯ä»¥å°†æ¤ç‰ˆæœ¬åº“åŠ ä¸ºè¿œç¨‹ + 版本库,并进行推é€ã€‚</span> + <p></p> + <pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre> + <p></p> + <h3>å¦ä¹ Git</h3> + 如果您ä¸æ˜Žç™½è¿™äº›ä¿¡æ¯ä»€ä¹ˆæ„æ€, 您å¯ä»¥å‚考 <a href="http://book.git-scm.com">Git Community Book</a> 或者 <a href="http://progit.org/book" target="_blank">Pro Git</a> åŽ»æ›´åŠ æ·±å…¥çš„å¦ä¹ Git 的用法。 + <p></p> + <h4>å¼€æº Git 客户端</h4> + <ul> + <li><a href="http://git-scm.com">Git</a> - 官方, 命令行版本 Git</li> + <li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - 与 Windows 资æºç®¡ç†å™¨é›†æˆ (需è¦å®˜æ–¹, 命令行 Git 的支æŒ)</li> + <li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git for the Eclipse IDE (基于 JGit, 类似 Gitblit)</li> + <li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# 版本的 Git å‰ç«¯ï¼Œä¸Ž Windows 资æºç®¡ç†å™¨å’Œ Visual Studio 集æˆ</li> + <li><a href="http://gitx.laullon.com/">GitX (L)</a> - Mac OS X Git 客户端</li> + </ul> + <p></p> + <h4>商业/é—æº Git 客户端</h4> + <ul> + <li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Java ç‰ˆæœ¬çš„æ”¯æŒ Git, Mercurial å’Œ SVN 客户端应用 (需è¦å®˜æ–¹, 命令行 Git 的支æŒ)</li> + <li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - å…费的 Mac Git Mercurial ä»¥åŠ SVN 客户端, Mercurial, and SVN</li> + </ul> +</wicket:extend> +</body> +</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/ForksPage.java b/src/com/gitblit/wicket/pages/ForksPage.java index 6155f3ed..cc483878 100644 --- a/src/com/gitblit/wicket/pages/ForksPage.java +++ b/src/com/gitblit/wicket/pages/ForksPage.java @@ -58,7 +58,7 @@ public class ForksPage extends RepositoryPage { if (repository.isPersonalRepository()) {
UserModel user = GitBlit.self().getUserModel(repository.projectPath.substring(1));
- PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress);
+ PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
item.add(new GravatarImage("anAvatar", ident, 20));
if (pageRepository.equals(repository)) {
// do not link to self
diff --git a/src/com/gitblit/wicket/pages/ProjectPage.java b/src/com/gitblit/wicket/pages/ProjectPage.java index bc546dfc..7eba0331 100644 --- a/src/com/gitblit/wicket/pages/ProjectPage.java +++ b/src/com/gitblit/wicket/pages/ProjectPage.java @@ -15,9 +15,6 @@ */
package com.gitblit.wicket.pages;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -37,7 +34,6 @@ import org.apache.wicket.markup.html.link.ExternalLink; import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Constants;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
@@ -46,7 +42,6 @@ import com.gitblit.models.Activity; import com.gitblit.models.Metric;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
import com.gitblit.utils.ActivityUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
@@ -111,23 +106,14 @@ public class ProjectPage extends RootPage { add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(project.getDisplayName(),
null), feedLink));
- final String projectPath;
- if (project.isRoot) {
- projectPath = "";
- } else {
- projectPath = projectName + "/";
- }
-
// project markdown message
- File pmkd = new File(GitBlit.getRepositoriesFolder(), projectPath + "project.mkd");
- String pmessage = readMarkdown(projectName, pmkd);
+ String pmessage = transformMarkdown(project.projectMarkdown);
Component projectMessage = new Label("projectMessage", pmessage)
.setEscapeModelStrings(false).setVisible(pmessage.length() > 0);
add(projectMessage);
// markdown message above repositories list
- File rmkd = new File(GitBlit.getRepositoriesFolder(), projectPath + "repositories.mkd");
- String rmessage = readMarkdown(projectName, rmkd);
+ String rmessage = transformMarkdown(project.repositoriesMarkdown);
Component repositoriesMessage = new Label("repositoriesMessage", rmessage)
.setEscapeModelStrings(false).setVisible(rmessage.length() > 0);
add(repositoriesMessage);
@@ -300,8 +286,8 @@ public class ProjectPage extends RootPage { @Override
protected List<ProjectModel> getProjectModels() {
if (projectModels.isEmpty()) {
- final UserModel user = GitBlitWebSession.get().getUser();
- List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false);
+ List<RepositoryModel> repositories = getRepositoryModels();
+ List<ProjectModel> projects = GitBlit.self().getProjectModels(repositories, false);
projectModels.addAll(projects);
}
return projectModels;
@@ -352,20 +338,15 @@ public class ProjectPage extends RootPage { }
return menu;
}
-
-
- private String readMarkdown(String projectName, File projectMessage) {
+
+ private String transformMarkdown(String markdown) {
String message = "";
- if (projectMessage.exists()) {
+ if (!StringUtils.isEmpty(markdown)) {
// Read user-supplied message
try {
- FileInputStream fis = new FileInputStream(projectMessage);
- InputStreamReader reader = new InputStreamReader(fis,
- Constants.CHARACTER_ENCODING);
- message = MarkdownUtils.transformMarkdown(reader);
- reader.close();
+ message = MarkdownUtils.transformMarkdown(markdown);
} catch (Throwable t) {
- message = getString("gb.failedToRead") + " " + projectMessage;
+ message = getString("gb.failedToRead") + " " + markdown;
warn(message, t);
}
}
diff --git a/src/com/gitblit/wicket/pages/ProjectsPage.java b/src/com/gitblit/wicket/pages/ProjectsPage.java index 7161d0f3..7f0b002e 100644 --- a/src/com/gitblit/wicket/pages/ProjectsPage.java +++ b/src/com/gitblit/wicket/pages/ProjectsPage.java @@ -36,7 +36,6 @@ import org.eclipse.jgit.lib.Constants; import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.ProjectModel;
-import com.gitblit.models.UserModel;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
@@ -48,8 +47,6 @@ import com.gitblit.wicket.panels.LinkPanel; public class ProjectsPage extends RootPage {
- List<ProjectModel> projectModels = new ArrayList<ProjectModel>();
-
public ProjectsPage() {
super();
setup(null);
@@ -67,9 +64,7 @@ public class ProjectsPage extends RootPage { @Override
protected List<ProjectModel> getProjectModels() {
- final UserModel user = GitBlitWebSession.get().getUser();
- List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false);
- return projects;
+ return GitBlit.self().getProjectModels(getRepositoryModels(), false);
}
private void setup(PageParameters params) {
@@ -194,39 +189,47 @@ public class ProjectsPage extends RootPage { }
private String readDefaultMarkdown(String file) {
- String content = readDefaultMarkdown(file, getLanguageCode());
- if (StringUtils.isEmpty(content)) {
- content = readDefaultMarkdown(file, null);
- }
- return content;
- }
+ String base = file.substring(0, file.lastIndexOf('.'));
+ String ext = file.substring(file.lastIndexOf('.'));
+ String lc = getLanguageCode();
+ String cc = getCountryCode();
- private String readDefaultMarkdown(String file, String lc) {
+ // try to read file_en-us.ext, file_en.ext, file.ext
+ List<String> files = new ArrayList<String>();
if (!StringUtils.isEmpty(lc)) {
- // convert to file_lc.mkd
- file = file.substring(0, file.lastIndexOf('.')) + "_" + lc
- + file.substring(file.lastIndexOf('.'));
+ if (!StringUtils.isEmpty(cc)) {
+ files.add(base + "_" + lc + "-" + cc + ext);
+ files.add(base + "_" + lc + "_" + cc + ext);
+ }
+ files.add(base + "_" + lc + ext);
}
- String message;
- try {
- ContextRelativeResource res = WicketUtils.getResource(file);
- InputStream is = res.getResourceStream().getInputStream();
- InputStreamReader reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
- message = MarkdownUtils.transformMarkdown(reader);
- reader.close();
- } catch (ResourceStreamNotFoundException t) {
- if (lc == null) {
- // could not find default language resource
+ files.add(file);
+
+ for (String name : files) {
+ String message;
+ InputStreamReader reader = null;
+ try {
+ ContextRelativeResource res = WicketUtils.getResource(name);
+ InputStream is = res.getResourceStream().getInputStream();
+ reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
+ message = MarkdownUtils.transformMarkdown(reader);
+ reader.close();
+ return message;
+ } catch (ResourceStreamNotFoundException t) {
+ continue;
+ } catch (Throwable t) {
message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
error(message, t, false);
- } else {
- // ignore so we can try default language resource
- message = null;
- }
- } catch (Throwable t) {
- message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
- error(message, t, false);
+ return message;
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (Exception e) {
+ }
+ }
+ }
}
- return message;
+ return MessageFormat.format(getString("gb.failedToReadMessage"), file);
}
}
diff --git a/src/com/gitblit/wicket/pages/RawPage.java b/src/com/gitblit/wicket/pages/RawPage.java index 7f6ed139..28e8bae2 100644 --- a/src/com/gitblit/wicket/pages/RawPage.java +++ b/src/com/gitblit/wicket/pages/RawPage.java @@ -109,7 +109,7 @@ public class RawPage extends WebPage { switch (type) {
case 2:
// image blobs
- byte[] image = JGitUtils.getByteContent(r, commit.getTree(), blobPath);
+ byte[] image = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
response.setContentType("image/" + extension.toLowerCase());
response.setContentLength(image.length);
try {
@@ -120,7 +120,7 @@ public class RawPage extends WebPage { break;
case 3:
// binary blobs (download)
- byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath);
+ byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
response.setContentLength(binary.length);
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java index 97c6aa23..4bce77f5 100644 --- a/src/com/gitblit/wicket/pages/RepositoriesPage.java +++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java @@ -20,6 +20,7 @@ import java.io.FileInputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Component;
@@ -118,7 +119,7 @@ public class RepositoriesPage extends RootPage { } else {
// Read user-supplied message
if (!StringUtils.isEmpty(messageSource)) {
- File file = new File(messageSource);
+ File file = GitBlit.getFileOrFolder(messageSource);
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
@@ -139,37 +140,47 @@ public class RepositoriesPage extends RootPage { }
private String readDefaultMarkdown(String file) {
- String content = readDefaultMarkdown(file, getLanguageCode());
- if (StringUtils.isEmpty(content)) {
- content = readDefaultMarkdown(file, null);
- }
- return content;
- }
-
- private String readDefaultMarkdown(String file, String lc) {
+ String base = file.substring(0, file.lastIndexOf('.'));
+ String ext = file.substring(file.lastIndexOf('.'));
+ String lc = getLanguageCode();
+ String cc = getCountryCode();
+
+ // try to read file_en-us.ext, file_en.ext, file.ext
+ List<String> files = new ArrayList<String>();
if (!StringUtils.isEmpty(lc)) {
- // convert to file_lc.mkd
- file = file.substring(0, file.lastIndexOf('.')) + "_" + lc + file.substring(file.lastIndexOf('.'));
+ if (!StringUtils.isEmpty(cc)) {
+ files.add(base + "_" + lc + "-" + cc + ext);
+ files.add(base + "_" + lc + "_" + cc + ext);
+ }
+ files.add(base + "_" + lc + ext);
}
- String message;
- try {
- InputStream is = GitBlit.self().getResourceAsStream(file);
- InputStreamReader reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
- message = MarkdownUtils.transformMarkdown(reader);
- reader.close();
- } catch (ResourceStreamNotFoundException t) {
- if (lc == null) {
- // could not find default language resource
+ files.add(file);
+
+ for (String name : files) {
+ String message;
+ InputStreamReader reader = null;
+ try {
+ ContextRelativeResource res = WicketUtils.getResource(name);
+ InputStream is = res.getResourceStream().getInputStream();
+ reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
+ message = MarkdownUtils.transformMarkdown(reader);
+ reader.close();
+ return message;
+ } catch (ResourceStreamNotFoundException t) {
+ continue;
+ } catch (Throwable t) {
message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
error(message, t, false);
- } else {
- // ignore so we can try default language resource
- message = null;
- }
- } catch (Throwable t) {
- message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
- error(message, t, false);
+ return message;
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (Exception e) {
+ }
+ }
+ }
}
- return message;
+ return MessageFormat.format(getString("gb.failedToReadMessage"), file);
}
}
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.html b/src/com/gitblit/wicket/pages/RepositoryPage.html index 63a894da..d49f0188 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.html +++ b/src/com/gitblit/wicket/pages/RepositoryPage.html @@ -52,7 +52,7 @@ </div>
</div>
<div class="span7">
- <div><span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span> <span class="hidden-phone"><span wicket:id="pageName">[page name]</span></span></div>
+ <div><span class="project" wicket:id="projectTitle">[project title]</span>/<img wicket:id="repositoryIcon" style="padding-left: 10px;"></img><span class="repository" wicket:id="repositoryName">[repository name]</span> <span class="hidden-phone"><span wicket:id="pageName">[page name]</span></span></div>
<span wicket:id="originRepository">[origin repository]</span>
</div>
</div>
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java index b4e1a0e3..a477b741 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/com/gitblit/wicket/pages/RepositoryPage.java @@ -184,7 +184,7 @@ public abstract class RepositoryPage extends BasePage { showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
}
isOwner = GitBlitWebSession.get().isLoggedIn()
- && (model.owner != null && model.owner.equalsIgnoreCase(GitBlitWebSession.get()
+ && (model.isOwner(GitBlitWebSession.get()
.getUsername()));
if (showAdmin || isOwner) {
pages.put("edit", new PageRegistration("gb.edit", EditRepositoryPage.class, params));
@@ -246,6 +246,14 @@ public abstract class RepositoryPage extends BasePage { }
}
+ // show sparkleshare folder icon
+ if (model.isSparkleshared()) {
+ add(WicketUtils.newImage("repositoryIcon", "folder_star_32x32.png",
+ getString("gb.isSparkleshared")));
+ } else {
+ add(WicketUtils.newClearPixel("repositoryIcon").setVisible(false));
+ }
+
if (getRepositoryModel().isBare) {
add(new Label("workingCopyIndicator").setVisible(false));
} else {
@@ -326,7 +334,7 @@ public abstract class RepositoryPage extends BasePage { RepositoryModel model = GitBlit.self().getRepositoryModel(
GitBlitWebSession.get().getUser(), repositoryName);
if (model == null) {
- if (GitBlit.self().hasRepository(repositoryName)) {
+ if (GitBlit.self().hasRepository(repositoryName, true)) {
// has repository, but unauthorized
authenticationError(getString("gb.unauthorizedAccessForRepository") + " " + repositoryName);
} else {
@@ -360,10 +368,6 @@ public abstract class RepositoryPage extends BasePage { return submodules;
}
- protected Map<String, SubmoduleModel> getSubmodules() {
- return submodules;
- }
-
protected SubmoduleModel getSubmodule(String path) {
SubmoduleModel model = submodules.get(path);
if (model == null) {
@@ -450,6 +454,8 @@ public abstract class RepositoryPage extends BasePage { Constants.SearchType searchType) {
String name = identity == null ? "" : identity.getName();
String address = identity == null ? "" : identity.getEmailAddress();
+ name = StringUtils.removeNewlines(name);
+ address = StringUtils.removeNewlines(address);
boolean showEmail = GitBlit.getBoolean(Keys.web.showEmailAddresses, false);
if (!showEmail || StringUtils.isEmpty(name) || StringUtils.isEmpty(address)) {
String value = name;
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html index 45ffddfb..3e85df99 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.html +++ b/src/com/gitblit/wicket/pages/SummaryPage.html @@ -16,7 +16,7 @@ <div class="hidden-phone" style="padding-bottom: 10px;">
<table class="plain">
<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>
- <tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr>
+ <tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></span></td></tr>
<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
<tr><th style="vertical-align:top;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message> <img style="vertical-align: top;padding-left:3px;" wicket:id="accessRestrictionIcon" /></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span><div wicket:id="otherUrls"></div></td></tr>
@@ -44,7 +44,11 @@ <div style="border:1px solid #ddd;border-radius: 0 0 3px 3px;padding: 20px;">
<div wicket:id="readmeContent" class="markdown"></div>
</div>
- </wicket:fragment>
+ </wicket:fragment>
+
+ <wicket:fragment wicket:id="ownersFragment">
+
+ </wicket:fragment>
</wicket:extend>
</body>
</html>
\ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java index 8df2cebc..bd40a1b7 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.java +++ b/src/com/gitblit/wicket/pages/SummaryPage.java @@ -27,6 +27,9 @@ import org.apache.wicket.PageParameters; import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.wicketstuff.googlecharts.Chart;
@@ -82,18 +85,29 @@ public class SummaryPage extends RepositoryPage { // repository description
add(new Label("repositoryDescription", getRepositoryModel().description));
- String owner = getRepositoryModel().owner;
- if (StringUtils.isEmpty(owner)) {
- add(new Label("repositoryOwner").setVisible(false));
- } else {
- UserModel ownerModel = GitBlit.self().getUserModel(owner);
- if (ownerModel != null) {
- add(new LinkPanel("repositoryOwner", null, ownerModel.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(owner)));
- } else {
- add(new Label("repositoryOwner", owner));
+
+ // owner links
+ final List<String> owners = new ArrayList<String>(getRepositoryModel().owners);
+ ListDataProvider<String> ownersDp = new ListDataProvider<String>(owners);
+ DataView<String> ownersView = new DataView<String>("repositoryOwners", ownersDp) {
+ private static final long serialVersionUID = 1L;
+ int counter = 0;
+ public void populateItem(final Item<String> item) {
+ UserModel ownerModel = GitBlit.self().getUserModel(item.getModelObject());
+ if (ownerModel != null) {
+ item.add(new LinkPanel("owner", null, ownerModel.getDisplayName(), UserPage.class,
+ WicketUtils.newUsernameParameter(ownerModel.username)).setRenderBodyOnly(true));
+ } else {
+ item.add(new Label("owner").setVisible(false));
+ }
+ counter++;
+ item.add(new Label("comma", ",").setVisible(counter < owners.size()));
+ item.setRenderBodyOnly(true);
}
- }
-
+ };
+ ownersView.setRenderBodyOnly(true);
+ add(ownersView);
+
add(WicketUtils.createTimestampLabel("repositoryLastChange",
JGitUtils.getLastChange(r), getTimeZone(), getTimeUtils()));
if (metricsTotal == null) {
diff --git a/src/com/gitblit/wicket/pages/TreePage.java b/src/com/gitblit/wicket/pages/TreePage.java index b8ce7b48..bc27f0c2 100644 --- a/src/com/gitblit/wicket/pages/TreePage.java +++ b/src/com/gitblit/wicket/pages/TreePage.java @@ -137,8 +137,8 @@ public class TreePage extends RepositoryPage { WicketUtils.newPathParameter(submodulePath, submoduleId,
"")).setEnabled(hasSubmodule));
links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
- WicketUtils.newPathParameter(submodulePath, submoduleId,
- "")).setEnabled(hasSubmodule));
+ WicketUtils.newPathParameter(repositoryName, entry.commitId,
+ entry.path)));
links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
submodulePath, submoduleId, "").setEnabled(hasSubmodule));
item.add(links);
diff --git a/src/com/gitblit/wicket/pages/UserPage.java b/src/com/gitblit/wicket/pages/UserPage.java index d3e93c61..f4331dd1 100644 --- a/src/com/gitblit/wicket/pages/UserPage.java +++ b/src/com/gitblit/wicket/pages/UserPage.java @@ -97,7 +97,7 @@ public class UserPage extends RootPage { email.setRenderBodyOnly(true);
add(email.setVisible(GitBlit.getBoolean(Keys.web.showEmailAddresses, true) && !StringUtils.isEmpty(user.emailAddress)));
- PersonIdent person = new PersonIdent(user.getDisplayName(), user.emailAddress);
+ PersonIdent person = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
add(new GravatarImage("gravatar", person, 210));
UserModel sessionUser = GitBlitWebSession.get().getUser();
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java index 6caee3e7..669c36be 100644 --- a/src/com/gitblit/wicket/panels/ActivityPanel.java +++ b/src/com/gitblit/wicket/panels/ActivityPanel.java @@ -27,7 +27,7 @@ import com.gitblit.Constants; import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.Activity;
-import com.gitblit.models.Activity.RepositoryCommit;
+import com.gitblit.models.RepositoryCommit;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.CommitDiffPage;
diff --git a/src/com/gitblit/wicket/panels/HistoryPanel.java b/src/com/gitblit/wicket/panels/HistoryPanel.java index 0f586031..e5878635 100644 --- a/src/com/gitblit/wicket/panels/HistoryPanel.java +++ b/src/com/gitblit/wicket/panels/HistoryPanel.java @@ -15,10 +15,14 @@ */
package com.gitblit.wicket.panels;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -38,6 +42,7 @@ import com.gitblit.Constants; import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel;
+import com.gitblit.models.SubmoduleModel;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.utils.JGitUtils;
@@ -69,6 +74,11 @@ public class HistoryPanel extends BasePanel { RevCommit commit = JGitUtils.getCommit(r, objectId);
List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, commit);
+ Map<String, SubmoduleModel> submodules = new HashMap<String, SubmoduleModel>();
+ for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
+ submodules.put(model.path, model);
+ }
+
PathModel matchingPath = null;
for (PathModel p : paths) {
if (p.path.equals(path)) {
@@ -99,7 +109,20 @@ public class HistoryPanel extends BasePanel { }
final boolean isTree = matchingPath == null ? true : matchingPath.isTree();
+ final boolean isSubmodule = matchingPath == null ? true : matchingPath.isSubmodule();
+ // submodule
+ SubmoduleModel submodule = getSubmodule(submodules, repositoryName, matchingPath.path);
+ final String submodulePath;
+ final boolean hasSubmodule;
+ if (submodule != null) {
+ submodulePath = submodule.gitblitPath;
+ hasSubmodule = submodule.hasSubmodule;
+ } else {
+ submodulePath = "";
+ hasSubmodule = false;
+ }
+
final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs);
List<RevCommit> commits;
if (pageResults) {
@@ -179,6 +202,23 @@ public class HistoryPanel extends BasePanel { links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
WicketUtils.newObjectParameter(repositoryName, entry.getName())));
item.add(links);
+ } else if (isSubmodule) {
+ // submodule
+ item.add(new Label("hashLabel", submodulePath + "@"));
+ Repository repository = GitBlit.self().getRepository(repositoryName);
+ String submoduleId = JGitUtils.getSubmoduleCommitId(repository, path, entry);
+ repository.close();
+ LinkPanel commitHash = new LinkPanel("hashLink", null, submoduleId.substring(0, hashLen),
+ TreePage.class, WicketUtils.newObjectParameter(
+ submodulePath, submoduleId));
+ WicketUtils.setCssClass(commitHash, "shortsha1");
+ WicketUtils.setHtmlTooltip(commitHash, submoduleId);
+ item.add(commitHash.setEnabled(hasSubmodule));
+
+ Fragment links = new Fragment("historyLinks", "treeLinks", this);
+ links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
+ WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+ item.add(links);
} else {
// commit
item.add(new Label("hashLabel", getString("gb.blob") + "@"));
@@ -230,4 +270,66 @@ public class HistoryPanel extends BasePanel { public boolean hasMore() {
return hasMore;
}
+
+ protected SubmoduleModel getSubmodule(Map<String, SubmoduleModel> submodules, String repositoryName, String path) {
+ SubmoduleModel model = submodules.get(path);
+ if (model == null) {
+ // undefined submodule?!
+ model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
+ model.hasSubmodule = false;
+ model.gitblitPath = model.name;
+ return model;
+ } else {
+ // extract the repository name from the clone url
+ List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
+ String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
+
+ // determine the current path for constructing paths relative
+ // to the current repository
+ String currentPath = "";
+ if (repositoryName.indexOf('/') > -1) {
+ currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
+ }
+
+ // try to locate the submodule repository
+ // prefer bare to non-bare names
+ List<String> candidates = new ArrayList<String>();
+
+ // relative
+ candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
+ candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+ // relative, no subfolder
+ if (submoduleName.lastIndexOf('/') > -1) {
+ String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+ candidates.add(currentPath + StringUtils.stripDotGit(name));
+ candidates.add(currentPath + candidates.get(candidates.size() - 1) + ".git");
+ }
+
+ // absolute
+ candidates.add(StringUtils.stripDotGit(submoduleName));
+ candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+ // absolute, no subfolder
+ if (submoduleName.lastIndexOf('/') > -1) {
+ String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+ candidates.add(StringUtils.stripDotGit(name));
+ candidates.add(candidates.get(candidates.size() - 1) + ".git");
+ }
+
+ // create a unique, ordered set of candidate paths
+ Set<String> paths = new LinkedHashSet<String>(candidates);
+ for (String candidate : paths) {
+ if (GitBlit.self().hasRepository(candidate)) {
+ model.hasSubmodule = true;
+ model.gitblitPath = candidate;
+ return model;
+ }
+ }
+
+ // we do not have a copy of the submodule, but we need a path
+ model.gitblitPath = candidates.get(0);
+ return model;
+ }
+ }
}
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html index 46781536..9b621d5a 100644 --- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html +++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html @@ -38,6 +38,7 @@ <div class="pull-right" style="text-align:right;padding-right:15px;">
<span wicket:id="repositoryLinks"></span>
<div>
+ <img class="inlineIcon" wicket:id="sparkleshareIcon" />
<img class="inlineIcon" wicket:id="frozenIcon" />
<img class="inlineIcon" wicket:id="federatedIcon" />
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java index 50f0d52d..7b4ee9f0 100644 --- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java +++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java @@ -87,6 +87,12 @@ public class ProjectRepositoryPanel extends BasePanel { add(forkFrag);
}
+ if (entry.isSparkleshared()) {
+ add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png", localizer.getString("gb.isSparkleshared", parent)));
+ } else {
+ add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
+ }
+
add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets));
add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs));
@@ -121,16 +127,24 @@ public class ProjectRepositoryPanel extends BasePanel { add(WicketUtils.newBlankImage("accessRestrictionIcon"));
}
- if (StringUtils.isEmpty(entry.owner)) {
+ if (ArrayUtils.isEmpty(entry.owners)) {
add(new Label("repositoryOwner").setVisible(false));
} else {
- UserModel ownerModel = GitBlit.self().getUserModel(entry.owner);
- String owner = entry.owner;
- if (ownerModel != null) {
- owner = ownerModel.getDisplayName();
+ String owner = "";
+ for (String username : entry.owners) {
+ UserModel ownerModel = GitBlit.self().getUserModel(username);
+
+ if (ownerModel != null) {
+ owner = ownerModel.getDisplayName();
+ }
+ }
+ if (entry.owners.size() > 1) {
+ owner += ", ...";
}
- add(new Label("repositoryOwner", owner + " (" +
+ Label ownerLabel = (new Label("repositoryOwner", owner + " (" +
localizer.getString("gb.owner", parent) + ")"));
+ WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
+ add(ownerLabel);
}
UserModel user = GitBlitWebSession.get().getUser();
diff --git a/src/com/gitblit/wicket/panels/RefsPanel.java b/src/com/gitblit/wicket/panels/RefsPanel.java index b4676427..3ba22c0b 100644 --- a/src/com/gitblit/wicket/panels/RefsPanel.java +++ b/src/com/gitblit/wicket/panels/RefsPanel.java @@ -129,8 +129,14 @@ public class RefsPanel extends Panel { name = name.substring(Constants.R_TAGS.length());
cssClass = "tagRef";
} else if (name.startsWith(Constants.R_NOTES)) {
+ // codereview refs
linkClass = CommitPage.class;
cssClass = "otherRef";
+ } else if (name.startsWith(com.gitblit.Constants.R_GITBLIT)) {
+ // gitblit refs
+ linkClass = LogPage.class;
+ cssClass = "otherRef";
+ name = name.substring(com.gitblit.Constants.R_GITBLIT.length());
}
Component c = new LinkPanel("refName", null, name, linkClass,
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java index 689ee571..4156cd19 100644 --- a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java +++ b/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java @@ -138,10 +138,10 @@ public class RegistrantPermissionsPanel extends BasePanel { } } else if (RegistrantType.USER.equals(entry.registrantType)) { // user - PersonIdent ident = new PersonIdent(entry.registrant, null); + PersonIdent ident = new PersonIdent(entry.registrant, ""); UserModel user = GitBlit.self().getUserModel(entry.registrant); if (user != null) { - ident = new PersonIdent(user.getDisplayName(), user.emailAddress); + ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress); } Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this); diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html index 42f9f1f2..81a4c6eb 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html @@ -89,7 +89,7 @@ <td class="left" style="padding-left:3px;" ><b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
<td class="hidden-phone"><span class="list" wicket:id="repositoryDescription">[repository description]</span></td>
<td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
- <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="forkIcon" /><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+ <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="forkIcon" /><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
<td><span wicket:id="repositoryLastChange">[last change]</span></td>
<td class="hidden-phone" style="text-align: right;padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span></td>
<td class="rightAlign">
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java index d3b8ddbe..726af61d 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -49,6 +49,7 @@ import com.gitblit.SyndicationServlet; import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
@@ -123,22 +124,18 @@ public class RepositoriesPanel extends BasePanel { if (rootRepositories.size() > 0) {
// inject the root repositories at the top of the page
- String rootPath = GitBlit.getString(Keys.web.repositoryRootGroupName, " ");
- roots.add(0, rootPath);
- groups.put(rootPath, rootRepositories);
+ roots.add(0, "");
+ groups.put("", rootRepositories);
}
- Map<String, ProjectModel> projects = new HashMap<String, ProjectModel>();
- for (ProjectModel project : GitBlit.self().getProjectModels(user, true)) {
- projects.put(project.name, project);
- }
List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
for (String root : roots) {
List<RepositoryModel> subModels = groups.get(root);
- GroupRepositoryModel group = new GroupRepositoryModel(root, subModels.size());
- if (projects.containsKey(root)) {
- group.title = projects.get(root).title;
- group.description = projects.get(root).description;
+ ProjectModel project = GitBlit.self().getProjectModel(root);
+ GroupRepositoryModel group = new GroupRepositoryModel(project.name, subModels.size());
+ if (project != null) {
+ group.title = project.title;
+ group.description = project.description;
}
groupedModels.add(group);
Collections.sort(subModels);
@@ -237,6 +234,13 @@ public class RepositoriesPanel extends BasePanel { .setEscapeModelStrings(false));
}
+ if (entry.isSparkleshared()) {
+ row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png",
+ getString("gb.isSparkleshared")));
+ } else {
+ row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
+ }
+
if (entry.isFork()) {
row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
getString("gb.isFork")));
@@ -291,14 +295,23 @@ public class RepositoriesPanel extends BasePanel { row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
}
- String owner = entry.owner;
- if (!StringUtils.isEmpty(owner)) {
- UserModel ownerModel = GitBlit.self().getUserModel(owner);
- if (ownerModel != null) {
- owner = ownerModel.getDisplayName();
+ String owner = "";
+ if (!ArrayUtils.isEmpty(entry.owners)) {
+ // display first owner
+ for (String username : entry.owners) {
+ UserModel ownerModel = GitBlit.self().getUserModel(username);
+ if (ownerModel != null) {
+ owner = ownerModel.getDisplayName();
+ break;
+ }
+ }
+ if (entry.owners.size() > 1) {
+ owner += ", ...";
}
}
- row.add(new Label("repositoryOwner", owner));
+ Label ownerLabel = new Label("repositoryOwner", owner);
+ WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
+ row.add(ownerLabel);
String lastChange;
if (entry.lastChange.getTime() == 0) {
@@ -519,10 +532,12 @@ public class RepositoriesPanel extends BasePanel { Collections.sort(list, new Comparator<RepositoryModel>() {
@Override
public int compare(RepositoryModel o1, RepositoryModel o2) {
+ String own1 = ArrayUtils.toString(o1.owners);
+ String own2 = ArrayUtils.toString(o2.owners);
if (asc) {
- return o1.owner.compareTo(o2.owner);
+ return own1.compareTo(own2);
}
- return o2.owner.compareTo(o1.owner);
+ return own2.compareTo(own1);
}
});
} else if (prop.equals(SortBy.description.name())) {
diff --git a/src/com/gitblit/wicket/panels/TeamsPanel.java b/src/com/gitblit/wicket/panels/TeamsPanel.java index cc37c519..b76388b3 100644 --- a/src/com/gitblit/wicket/panels/TeamsPanel.java +++ b/src/com/gitblit/wicket/panels/TeamsPanel.java @@ -40,7 +40,7 @@ public class TeamsPanel extends BasePanel { Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
adminLinks.add(new BookmarkablePageLink<Void>("newTeam", EditTeamPage.class));
- add(adminLinks.setVisible(showAdmin && GitBlit.self().supportsTeamMembershipChanges()));
+ add(adminLinks.setVisible(showAdmin && GitBlit.self().supportsTeamMembershipChanges(null)));
final List<TeamModel> teams = GitBlit.self().getAllTeams();
DataView<TeamModel> teamsView = new DataView<TeamModel>("teamRow",
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.html b/src/com/gitblit/wicket/panels/UsersPanel.html index aed985c1..80159610 100644 --- a/src/com/gitblit/wicket/panels/UsersPanel.html +++ b/src/com/gitblit/wicket/panels/UsersPanel.html @@ -17,7 +17,7 @@ </th>
<th class="hidden-phone hidden-tablet left"><wicket:message key="gb.displayName">[display name]</wicket:message></th>
<th class="hidden-phone hidden-tablet left"><wicket:message key="gb.emailAddress">[email address]</wicket:message></th>
- <th class="hidden-phone" style="width:120px;"><wicket:message key="gb.accessLevel">[access level]</wicket:message></th>
+ <th class="hidden-phone" style="width:140px;"><wicket:message key="gb.type">[type]</wicket:message></th>
<th class="hidden-phone" style="width:140px;"><wicket:message key="gb.teamMemberships">[team memberships]</wicket:message></th>
<th class="hidden-phone" style="width:100px;"><wicket:message key="gb.repositories">[repositories]</wicket:message></th>
<th style="width:80px;" class="right"></th>
@@ -27,7 +27,7 @@ <td class="left" ><span class="list" wicket:id="username">[username]</span></td>
<td class="hidden-phone hidden-tablet left" ><span class="list" wicket:id="displayName">[display name]</span></td>
<td class="hidden-phone hidden-tablet left" ><span class="list" wicket:id="emailAddress">[email address]</span></td>
- <td class="hidden-phone left" ><span class="list" wicket:id="accesslevel">[access level]</span></td>
+ <td class="hidden-phone left" ><span style="font-size: 0.8em;" wicket:id="accountType">[account type]</span></td>
<td class="hidden-phone left" ><span class="list" wicket:id="teams">[team memberships]</span></td>
<td class="hidden-phone left" ><span class="list" wicket:id="repositories">[repositories]</span></td>
<td class="rightAlign"><span wicket:id="userLinks"></span></td>
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.java b/src/com/gitblit/wicket/panels/UsersPanel.java index 46c502e5..f5b95e20 100644 --- a/src/com/gitblit/wicket/panels/UsersPanel.java +++ b/src/com/gitblit/wicket/panels/UsersPanel.java @@ -41,7 +41,7 @@ public class UsersPanel extends BasePanel { Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class)
- .setVisible(GitBlit.self().supportsCredentialChanges()));
+ .setVisible(GitBlit.self().supportsAddUser()));
add(adminLinks.setVisible(showAdmin));
final List<UserModel> users = GitBlit.self().getAllUsers();
@@ -81,7 +81,7 @@ public class UsersPanel extends BasePanel { item.add(editLink);
}
- item.add(new Label("accesslevel", entry.canAdmin() ? "administrator" : ""));
+ item.add(new Label("accountType", entry.accountType.name() + (entry.canAdmin() ? ", admin":"")));
item.add(new Label("teams", entry.teams.size() > 0 ? ("" + entry.teams.size()) : ""));
item.add(new Label("repositories",
entry.permissions.size() > 0 ? ("" + entry.permissions.size()) : ""));
diff --git a/test-gitblit.properties b/test-gitblit.properties index 6997dde7..f16f5c5a 100644 --- a/test-gitblit.properties +++ b/test-gitblit.properties @@ -2,10 +2,10 @@ # Gitblit Unit Testing properties # -git.repositoriesFolder = git +git.repositoriesFolder = ${baseFolder}/git git.searchRepositoriesSubfolders = true git.enableGitServlet = true -groovy.scriptsFolder = groovy +groovy.scriptsFolder = ${baseFolder}/groovy groovy.preReceiveScripts = blockpush groovy.postReceiveScripts = sendmail web.authenticateViewPages = false @@ -77,7 +77,7 @@ federation.sets = animal mineral vegetable #federation.example1.mirror = true #federation.example1.mergeAccounts = true -server.tempFolder = temp +server.tempFolder = ${baseFolder}/temp server.useNio = true server.contextPath = / server.httpPort = 0 diff --git a/test-ui-gitblit.properties b/test-ui-gitblit.properties new file mode 100644 index 00000000..c77de1a2 --- /dev/null +++ b/test-ui-gitblit.properties @@ -0,0 +1,1203 @@ +#
+# 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
+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/(.*)
+
+# 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
+
+# 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
+groovy.scriptsFolder = ${baseFolder}/groovy
+
+# Specify the directory Grape uses for downloading libraries.
+# http://groovy.codehaus.org/Grape
+#
+# RESTART REQUIRED
+# SINCE 1.0.0
+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 =
+
+#
+# Authentication Settings
+#
+
+# Require authentication to see everything but the admin pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateViewPages = 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
+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
+#
+# Any custom user service implementation must have a public default constructor.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+realm.userService = test-ui-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 =
+
+# 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
+web.robots.txt =
+
+# 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 = false
+
+# 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
+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
+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.
+# e.g.
+# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0}
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.otherUrls =
+
+# 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 number of days to show on the activity page.
+# Value must exceed 0 else default of 14 is used
+#
+# SINCE 0.8.0
+web.activityDuration = 14
+
+# 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 if 50 is used for any invalid value.
+#
+# SINCE 0.5.0
+web.itemsPerPage = 50
+
+# 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 = c cpp cs css frm groovy htm html java js php pl prefs properties py rb scala sh sql xml vb
+
+# 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
+
+# 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
+
+# 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
+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
+#
+
+# 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
+realm.ldap.backingUserService = test-ui-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
+
+# The RedmineUserService must be backed by another user service for standard user
+# and team management.
+# default: users.conf
+#
+# RESTART REQUIRED
+realm.redmine.backingUserService = test-ui-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
+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
+
+# 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
diff --git a/test-ui-users.conf b/test-ui-users.conf new file mode 100644 index 00000000..5bf35f3b --- /dev/null +++ b/test-ui-users.conf @@ -0,0 +1,44 @@ +[user "admin"] + password = admin + cookie = dd94709528bb1c83d08f3088d4043f4742891f4f + role = "#admin" + role = "#notfederated" +[user "userthree"] + password = StoredInLDAP + cookie = d7d3894fc517612aa6c595555b6e1ab8e147e597 + displayName = User Three + emailAddress = userthree@gitblit.com + role = "#admin" +[user "userone"] + password = StoredInLDAP + cookie = c97cd38e50858cd0b389ec61b18fb9a89b4da54c + displayName = User One + emailAddress = User.One@gitblit.com + role = "#admin" +[user "usertwo"] + password = StoredInLDAP + cookie = 498ca9bd2841d39050fa45d1d737b9f9f767858d + displayName = User Two + emailAddress = usertwo@gitblit.com + role = "#admin" +[user "basic"] + password = MD5:f17aaabc20bfe045075927934fed52d2 + cookie = dd94709528bb1c83d08f3088d4043f4742891f4f + role = "#fork" + repository = RW:~repocreator/shb.git + repository = V:test/gitective.git +[user "repocreator"] + password = MD5:b77e53bb561c47368d133b22e285f60b + cookie = dd94709528bb1c83d08f3088d4043f4742891f4f + role = "#create" +[team "Git_Admins"] + role = "#none" + user = userone +[team "Git_Users"] + role = "#none" + user = userone + user = usertwo + user = userthree +[team "Git Admins"] + role = "#none" + user = usertwo diff --git a/tests/com/gitblit/tests/FanoutServiceTest.java b/tests/com/gitblit/tests/FanoutServiceTest.java new file mode 100644 index 00000000..28e5d82d --- /dev/null +++ b/tests/com/gitblit/tests/FanoutServiceTest.java @@ -0,0 +1,172 @@ +/*
+ * 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.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+
+import com.gitblit.fanout.FanoutService;
+import com.gitblit.fanout.FanoutClient;
+import com.gitblit.fanout.FanoutClient.FanoutAdapter;
+import com.gitblit.fanout.FanoutNioService;
+import com.gitblit.fanout.FanoutService;
+import com.gitblit.fanout.FanoutSocketService;
+
+public class FanoutServiceTest {
+
+ int fanoutPort = FanoutService.DEFAULT_PORT;
+
+ @Test
+ public void testNioPubSub() throws Exception {
+ testPubSub(new FanoutNioService(fanoutPort));
+ }
+
+ @Test
+ public void testSocketPubSub() throws Exception {
+ testPubSub(new FanoutSocketService(fanoutPort));
+ }
+
+ @Test
+ public void testNioDisruptionAndRecovery() throws Exception {
+ testDisruption(new FanoutNioService(fanoutPort));
+ }
+
+ @Test
+ public void testSocketDisruptionAndRecovery() throws Exception {
+ testDisruption(new FanoutSocketService(fanoutPort));
+ }
+
+ protected void testPubSub(FanoutService service) throws Exception {
+ System.out.println(MessageFormat.format("\n\n========================================\nPUBSUB TEST {0}\n========================================\n\n", service.toString()));
+ service.startSynchronously();
+
+ final Map<String, String> announcementsA = new ConcurrentHashMap<String, String>();
+ FanoutClient clientA = new FanoutClient("localhost", fanoutPort);
+ clientA.addListener(new FanoutAdapter() {
+
+ @Override
+ public void announcement(String channel, String message) {
+ announcementsA.put(channel, message);
+ }
+ });
+
+ clientA.startSynchronously();
+
+ final Map<String, String> announcementsB = new ConcurrentHashMap<String, String>();
+ FanoutClient clientB = new FanoutClient("localhost", fanoutPort);
+ clientB.addListener(new FanoutAdapter() {
+ @Override
+ public void announcement(String channel, String message) {
+ announcementsB.put(channel, message);
+ }
+ });
+ clientB.startSynchronously();
+
+
+ // subscribe clients A and B to the channels
+ clientA.subscribe("a");
+ clientA.subscribe("b");
+ clientA.subscribe("c");
+
+ clientB.subscribe("a");
+ clientB.subscribe("b");
+ clientB.subscribe("c");
+
+ // give async messages a chance to be delivered
+ Thread.sleep(1000);
+
+ clientA.announce("a", "apple");
+ clientA.announce("b", "banana");
+ clientA.announce("c", "cantelope");
+
+ clientB.announce("a", "avocado");
+ clientB.announce("b", "beet");
+ clientB.announce("c", "carrot");
+
+ // give async messages a chance to be delivered
+ Thread.sleep(2000);
+
+ // confirm that client B received client A's announcements
+ assertEquals("apple", announcementsB.get("a"));
+ assertEquals("banana", announcementsB.get("b"));
+ assertEquals("cantelope", announcementsB.get("c"));
+
+ // confirm that client A received client B's announcements
+ assertEquals("avocado", announcementsA.get("a"));
+ assertEquals("beet", announcementsA.get("b"));
+ assertEquals("carrot", announcementsA.get("c"));
+
+ clientA.stop();
+ clientB.stop();
+ service.stop();
+ }
+
+ protected void testDisruption(FanoutService service) throws Exception {
+ System.out.println(MessageFormat.format("\n\n========================================\nDISRUPTION TEST {0}\n========================================\n\n", service.toString()));
+ service.startSynchronously();
+
+ final AtomicInteger pongCount = new AtomicInteger(0);
+ FanoutClient client = new FanoutClient("localhost", fanoutPort);
+ client.addListener(new FanoutAdapter() {
+ @Override
+ public void pong(Date timestamp) {
+ pongCount.incrementAndGet();
+ }
+ });
+ client.startSynchronously();
+
+ // ping and wait for pong
+ client.ping();
+ Thread.sleep(500);
+
+ // restart client
+ client.stop();
+ Thread.sleep(1000);
+ client.startSynchronously();
+
+ // ping and wait for pong
+ client.ping();
+ Thread.sleep(500);
+
+ assertEquals(2, pongCount.get());
+
+ // now disrupt service
+ service.stop();
+ Thread.sleep(2000);
+ service.startSynchronously();
+
+ // wait for reconnect
+ Thread.sleep(2000);
+
+ // ping and wait for pong
+ client.ping();
+ Thread.sleep(500);
+
+ // kill all
+ client.stop();
+ service.stop();
+
+ // confirm expected pong count
+ assertEquals(3, pongCount.get());
+ }
+}
\ No newline at end of file diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java index c8f686ad..ced500a5 100644 --- a/tests/com/gitblit/tests/FederationTests.java +++ b/tests/com/gitblit/tests/FederationTests.java @@ -72,7 +72,7 @@ public class FederationTests { model.accessRestriction = AccessRestrictionType.VIEW;
model.description = "cloneable repository " + i;
model.lastChange = new Date();
- model.owner = "adminuser";
+ model.addOwner("adminuser");
model.name = "repo" + i + ".git";
model.size = "5 MB";
model.hasCommits = true;
diff --git a/tests/com/gitblit/tests/GitBlitSuite.java b/tests/com/gitblit/tests/GitBlitSuite.java index bb734eb7..b0179c37 100644 --- a/tests/com/gitblit/tests/GitBlitSuite.java +++ b/tests/com/gitblit/tests/GitBlitSuite.java @@ -59,10 +59,11 @@ import com.gitblit.utils.JGitUtils; MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class,
DiffUtilsTest.class, MetricUtilsTest.class, TicgitUtilsTest.class, X509UtilsTest.class,
GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class,
- GroovyScriptTest.class, LuceneExecutorTest.class, IssuesTest.class, RepositoryModelTest.class })
+ GroovyScriptTest.class, LuceneExecutorTest.class, IssuesTest.class, RepositoryModelTest.class,
+ FanoutServiceTest.class })
public class GitBlitSuite {
- public static final File REPOSITORIES = new File("git");
+ public static final File REPOSITORIES = new File("data/git");
static int port = 8280;
static int shutdownPort = 8281;
@@ -116,7 +117,8 @@ public class GitBlitSuite { GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort",
"" + shutdownPort, "--repositoriesFolder",
"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
- "test-users.conf", "--settings", "test-gitblit.properties");
+ "test-users.conf", "--settings", "test-gitblit.properties",
+ "--baseFolder", "data");
}
});
diff --git a/tests/com/gitblit/tests/GitBlitTest.java b/tests/com/gitblit/tests/GitBlitTest.java index 1c9bbd00..786614f8 100644 --- a/tests/com/gitblit/tests/GitBlitTest.java +++ b/tests/com/gitblit/tests/GitBlitTest.java @@ -138,7 +138,7 @@ public class GitBlitTest { assertEquals(5, settings.getInteger("realm.realmFile", 5));
assertTrue(settings.getBoolean("git.enableGitServlet", false));
- assertEquals("users.conf", settings.getString("realm.userService", null));
+ assertEquals("${baseFolder}/users.conf", settings.getString("realm.userService", null));
assertEquals(5, settings.getInteger("realm.minPasswordLength", 0));
List<String> mdExtensions = settings.getStrings("web.markdownExtensions");
assertTrue(mdExtensions.size() > 0);
diff --git a/tests/com/gitblit/tests/GitServletTest.java b/tests/com/gitblit/tests/GitServletTest.java index e65c61cb..a05b3650 100644 --- a/tests/com/gitblit/tests/GitServletTest.java +++ b/tests/com/gitblit/tests/GitServletTest.java @@ -7,9 +7,11 @@ import static org.junit.Assert.assertTrue; import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.MessageFormat;
import java.util.Date;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.api.CloneCommand;
@@ -18,6 +20,7 @@ import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepository;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
@@ -34,9 +37,12 @@ import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
+import com.gitblit.models.PushLogEntry;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.PushLogUtils;
public class GitServletTest {
@@ -88,6 +94,11 @@ public class GitServletTest { @Test
public void testClone() throws Exception {
+ GitBlitSuite.close(ticgitFolder);
+ if (ticgitFolder.exists()) {
+ FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+ }
+
CloneCommand clone = Git.cloneRepository();
clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
clone.setDirectory(ticgitFolder);
@@ -187,6 +198,20 @@ public class GitServletTest { @Test
public void testAnonymousPush() throws Exception {
+ GitBlitSuite.close(ticgitFolder);
+ if (ticgitFolder.exists()) {
+ FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+ }
+
+ CloneCommand clone = Git.cloneRepository();
+ clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
+ clone.setDirectory(ticgitFolder);
+ clone.setBare(false);
+ clone.setCloneAllBranches(true);
+ clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+ GitBlitSuite.close(clone.call());
+ assertTrue(true);
+
Git git = Git.open(ticgitFolder);
File file = new File(ticgitFolder, "TODO");
OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
@@ -201,6 +226,11 @@ public class GitServletTest { @Test
public void testSubfolderPush() throws Exception {
+ GitBlitSuite.close(jgitFolder);
+ if (jgitFolder.exists()) {
+ FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+ }
+
CloneCommand clone = Git.cloneRepository();
clone.setURI(MessageFormat.format("{0}/git/test/jgit.git", url));
clone.setDirectory(jgitFolder);
@@ -223,6 +253,51 @@ public class GitServletTest { }
@Test
+ public void testPushToFrozenRepo() throws Exception {
+ GitBlitSuite.close(jgitFolder);
+ if (jgitFolder.exists()) {
+ FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+ }
+
+ CloneCommand clone = Git.cloneRepository();
+ clone.setURI(MessageFormat.format("{0}/git/test/jgit.git", url));
+ clone.setDirectory(jgitFolder);
+ clone.setBare(false);
+ clone.setCloneAllBranches(true);
+ clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+ GitBlitSuite.close(clone.call());
+ assertTrue(true);
+
+ // freeze repo
+ RepositoryModel model = GitBlit.self().getRepositoryModel("test/jgit.git");
+ model.isFrozen = true;
+ GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+ Git git = Git.open(jgitFolder);
+ File file = new File(jgitFolder, "TODO");
+ OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+ BufferedWriter w = new BufferedWriter(os);
+ w.write("// " + new Date().toString() + "\n");
+ w.close();
+ git.add().addFilepattern(file.getName()).call();
+ git.commit().setMessage("test commit").call();
+
+ try {
+ git.push().setPushAll().call();
+ assertTrue(false);
+ } catch (Exception e) {
+ assertTrue(e.getCause().getMessage().contains("access forbidden"));
+ }
+
+ // unfreeze repo
+ model.isFrozen = false;
+ GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+ git.push().setPushAll().call();
+ GitBlitSuite.close(git);
+ }
+
+ @Test
public void testPushToNonBareRepository() throws Exception {
CloneCommand clone = Git.cloneRepository();
clone.setURI(MessageFormat.format("{0}/git/working/jgit", url));
@@ -651,7 +726,7 @@ public class GitServletTest { // confirm default personal repository permissions
RepositoryModel model = GitBlit.self().getRepositoryModel(MessageFormat.format("~{0}/ticgit.git", user.username));
- assertEquals("Unexpected owner", user.username, model.owner);
+ assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));
assertEquals("Unexpected authorization control", AuthorizationControl.NAMED, model.authorizationControl);
assertEquals("Unexpected access restriction", AccessRestrictionType.VIEW, model.accessRestriction);
@@ -675,7 +750,7 @@ public class GitServletTest { // confirm default project repository permissions
RepositoryModel model = GitBlit.self().getRepositoryModel("project/ticgit.git");
- assertEquals("Unexpected owner", user.username, model.owner);
+ assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));
assertEquals("Unexpected authorization control", AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, "NAMED")), model.authorizationControl);
assertEquals("Unexpected access restriction", AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, "NONE")), model.accessRestriction);
@@ -687,4 +762,14 @@ public class GitServletTest { GitBlitSuite.close(git);
GitBlit.self().deleteUser(user.username);
}
+
+ @Test
+ public void testPushLog() throws IOException {
+ String name = "refchecks/ticgit.git";
+ File refChecks = new File(GitBlitSuite.REPOSITORIES, name);
+ FileRepository repository = new FileRepository(refChecks);
+ List<PushLogEntry> pushes = PushLogUtils.getPushLog(name, repository);
+ GitBlitSuite.close(repository);
+ assertTrue("Repository has an empty push log!", pushes.size() > 0);
+ }
}
diff --git a/tests/com/gitblit/tests/GroovyScriptTest.java b/tests/com/gitblit/tests/GroovyScriptTest.java index 47d20a4c..2954fa1d 100644 --- a/tests/com/gitblit/tests/GroovyScriptTest.java +++ b/tests/com/gitblit/tests/GroovyScriptTest.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -70,6 +71,28 @@ public class GroovyScriptTest { }
@Test
+ public void testFogbugz() throws Exception {
+ MockGitblit gitblit = new MockGitblit();
+ MockLogger logger = new MockLogger();
+ MockClientLogger clientLogger = new MockClientLogger();
+ List<ReceiveCommand> commands = new ArrayList<ReceiveCommand>();
+ commands.add(new ReceiveCommand(ObjectId
+ .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId
+ .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master"));
+ commands.add(new ReceiveCommand(ObjectId
+ .fromString("c18877690322dfc6ae3e37bb7f7085a24e94e887"), ObjectId
+ .fromString("3fa7c46d11b11d61f1cbadc6888be5d0eae21969"), "refs/heads/master2"));
+
+ RepositoryModel repository = GitBlit.self().getRepositoryModel("helloworld.git");
+ repository.customFields = new HashMap<String,String>();
+ repository.customFields.put( "fogbugzUrl", "http://bugs.test.com" );
+ repository.customFields.put( "fogbugzRepositoryId", "1" );
+ repository.customFields.put( "fogbugzCommitMessageRegex", "\\s*[Bb][Uu][Gg][(Zz)(Ss)]*\\s*[(IDs)]*\\s*[#:; ]+((\\d+[ ,:;#]*)+)" );
+
+ test("fogbugz.groovy", gitblit, logger, clientLogger, commands, repository);
+ }
+
+ @Test
public void testSendHtmlMail() throws Exception {
MockGitblit gitblit = new MockGitblit();
MockLogger logger = new MockLogger();
diff --git a/tests/com/gitblit/tests/LdapUserServiceTest.java b/tests/com/gitblit/tests/LdapUserServiceTest.java index ffe82640..a928f4a5 100644 --- a/tests/com/gitblit/tests/LdapUserServiceTest.java +++ b/tests/com/gitblit/tests/LdapUserServiceTest.java @@ -31,6 +31,7 @@ import org.junit.Test; import com.gitblit.LdapUserService; import com.gitblit.models.UserModel; import com.gitblit.tests.mock.MemorySettings; +import com.gitblit.utils.StringUtils; import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; @@ -154,5 +155,20 @@ public class LdapUserServiceTest { UserModel userOneModel = ldapUserService.authenticate("*)(userPassword=userOnePassword", "userOnePassword".toCharArray()); assertNull(userOneModel); } + + @Test + public void testLocalAccount() { + UserModel localAccount = new UserModel("bruce"); + localAccount.displayName = "Bruce Campbell"; + localAccount.password = StringUtils.MD5_TYPE + StringUtils.getMD5("gimmesomesugar"); + ldapUserService.deleteUser(localAccount.username); + assertTrue("Failed to add local account", + ldapUserService.updateUserModel(localAccount)); + assertEquals("Accounts are not equal!", + localAccount, + ldapUserService.authenticate(localAccount.username, "gimmesomesugar".toCharArray())); + assertTrue("Failed to delete local account!", + ldapUserService.deleteUser(localAccount.username)); + } } diff --git a/tests/com/gitblit/tests/PermissionsTest.java b/tests/com/gitblit/tests/PermissionsTest.java index b6ffa626..5a951042 100644 --- a/tests/com/gitblit/tests/PermissionsTest.java +++ b/tests/com/gitblit/tests/PermissionsTest.java @@ -2327,7 +2327,7 @@ public class PermissionsTest extends Assert { repository.accessRestriction = AccessRestrictionType.VIEW; UserModel user = new UserModel("test"); - repository.owner = user.username; + repository.addOwner(user.username); assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name)); assertTrue("owner CAN NOT view!", user.canView(repository)); @@ -2345,13 +2345,58 @@ public class PermissionsTest extends Assert { } @Test + public void testMultipleOwners() throws Exception { + RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date()); + repository.authorizationControl = AuthorizationControl.NAMED; + repository.accessRestriction = AccessRestrictionType.VIEW; + + UserModel user = new UserModel("test"); + repository.addOwner(user.username); + UserModel user2 = new UserModel("test2"); + repository.addOwner(user2.username); + + // first owner + assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name)); + assertTrue("owner CAN NOT view!", user.canView(repository)); + assertTrue("owner CAN NOT clone!", user.canClone(repository)); + assertTrue("owner CAN NOT push!", user.canPush(repository)); + + assertTrue("owner CAN NOT create ref!", user.canCreateRef(repository)); + assertTrue("owner CAN NOT delete ref!", user.canDeleteRef(repository)); + assertTrue("owner CAN NOT rewind ref!", user.canRewindRef(repository)); + + assertTrue("owner CAN NOT fork!", user.canFork(repository)); + + assertFalse("owner CAN NOT delete!", user.canDelete(repository)); + assertTrue("owner CAN NOT edit!", user.canEdit(repository)); + + // second owner + assertFalse("user SHOULD NOT HAVE a repository permission!", user2.hasRepositoryPermission(repository.name)); + assertTrue("owner CAN NOT view!", user2.canView(repository)); + assertTrue("owner CAN NOT clone!", user2.canClone(repository)); + assertTrue("owner CAN NOT push!", user2.canPush(repository)); + + assertTrue("owner CAN NOT create ref!", user2.canCreateRef(repository)); + assertTrue("owner CAN NOT delete ref!", user2.canDeleteRef(repository)); + assertTrue("owner CAN NOT rewind ref!", user2.canRewindRef(repository)); + + assertTrue("owner CAN NOT fork!", user2.canFork(repository)); + + assertFalse("owner CAN NOT delete!", user2.canDelete(repository)); + assertTrue("owner CAN NOT edit!", user2.canEdit(repository)); + + assertTrue(repository.isOwner(user.username)); + assertTrue(repository.isOwner(user2.username)); + } + + @Test public void testOwnerPersonalRepository() throws Exception { RepositoryModel repository = new RepositoryModel("~test/myrepo.git", null, null, new Date()); repository.authorizationControl = AuthorizationControl.NAMED; repository.accessRestriction = AccessRestrictionType.VIEW; UserModel user = new UserModel("test"); - repository.owner = user.username; + repository.addOwner(user.username); assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name)); assertTrue("user CAN NOT view!", user.canView(repository)); @@ -2375,7 +2420,7 @@ public class PermissionsTest extends Assert { repository.accessRestriction = AccessRestrictionType.VIEW; UserModel user = new UserModel("visitor"); - repository.owner = "test"; + repository.addOwner("test"); assertFalse("user HAS a repository permission!", user.hasRepositoryPermission(repository.name)); assertFalse("user CAN view!", user.canView(repository)); diff --git a/tests/com/gitblit/tests/PushLogTest.java b/tests/com/gitblit/tests/PushLogTest.java new file mode 100644 index 00000000..aa4cf418 --- /dev/null +++ b/tests/com/gitblit/tests/PushLogTest.java @@ -0,0 +1,37 @@ +/*
+ * 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.tests;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.junit.Test;
+
+import com.gitblit.models.PushLogEntry;
+import com.gitblit.utils.PushLogUtils;
+
+public class PushLogTest {
+
+ @Test
+ public void testPushLog() throws IOException {
+ String name = "~james/helloworld.git";
+ FileRepository repository = new FileRepository(new File(GitBlitSuite.REPOSITORIES, name));
+ List<PushLogEntry> pushes = PushLogUtils.getPushLog(name, repository);
+ GitBlitSuite.close(repository);
+ }
+}
\ No newline at end of file diff --git a/tests/com/gitblit/tests/RedmineUserServiceTest.java b/tests/com/gitblit/tests/RedmineUserServiceTest.java index 30a8fb20..12fa73ff 100644 --- a/tests/com/gitblit/tests/RedmineUserServiceTest.java +++ b/tests/com/gitblit/tests/RedmineUserServiceTest.java @@ -1,9 +1,10 @@ package com.gitblit.tests;
import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import java.util.HashMap;
@@ -12,6 +13,7 @@ import org.junit.Test; import com.gitblit.RedmineUserService;
import com.gitblit.models.UserModel;
import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.utils.StringUtils;
public class RedmineUserServiceTest {
@@ -28,8 +30,8 @@ public class RedmineUserServiceTest { RedmineUserService redmineUserService = new RedmineUserService();
redmineUserService.setup(new MemorySettings(new HashMap<String, Object>()));
redmineUserService.setTestingCurrentUserAsJson(JSON);
- UserModel userModel = redmineUserService.authenticate("RedmineUserId", "RedmineAPIKey".toCharArray());
- assertThat(userModel.getName(), is("RedmineUserId"));
+ UserModel userModel = redmineUserService.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray());
+ assertThat(userModel.getName(), is("redmineadminid"));
assertThat(userModel.getDisplayName(), is("baz foo"));
assertThat(userModel.emailAddress, is("baz@example.com"));
assertNotNull(userModel.cookie);
@@ -42,11 +44,29 @@ public class RedmineUserServiceTest { redmineUserService.setup(new MemorySettings(new HashMap<String, Object>()));
redmineUserService.setTestingCurrentUserAsJson(NOT_ADMIN_JSON);
UserModel userModel = redmineUserService.authenticate("RedmineUserId", "RedmineAPIKey".toCharArray());
- assertThat(userModel.getName(), is("baz@example.com"));
+ assertThat(userModel.getName(), is("redmineuserid"));
assertThat(userModel.getDisplayName(), is("baz foo"));
assertThat(userModel.emailAddress, is("baz@example.com"));
assertNotNull(userModel.cookie);
assertThat(userModel.canAdmin, is(false));
}
+
+ @Test
+ public void testLocalAccount() {
+ RedmineUserService redmineUserService = new RedmineUserService();
+ redmineUserService.setup(new MemorySettings(new HashMap<String, Object>()));
+
+ UserModel localAccount = new UserModel("bruce");
+ localAccount.displayName = "Bruce Campbell";
+ localAccount.password = StringUtils.MD5_TYPE + StringUtils.getMD5("gimmesomesugar");
+ redmineUserService.deleteUser(localAccount.username);
+ assertTrue("Failed to add local account",
+ redmineUserService.updateUserModel(localAccount));
+ assertEquals("Accounts are not equal!",
+ localAccount,
+ redmineUserService.authenticate(localAccount.username, "gimmesomesugar".toCharArray()));
+ assertTrue("Failed to delete local account!",
+ redmineUserService.deleteUser(localAccount.username));
+ }
}
diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java index 62d87bf9..3241a8ab 100644 --- a/tests/com/gitblit/tests/RpcTests.java +++ b/tests/com/gitblit/tests/RpcTests.java @@ -167,7 +167,7 @@ public class RpcTests { RepositoryModel model = new RepositoryModel();
model.name = "garbagerepo.git";
model.description = "created by RpcUtils";
- model.owner = "garbage";
+ model.addOwner("garbage");
model.accessRestriction = AccessRestrictionType.VIEW;
model.authorizationControl = AuthorizationControl.AUTHENTICATED;
diff --git a/tests/de/akquinet/devops/GitblitRunnable.java b/tests/de/akquinet/devops/GitblitRunnable.java new file mode 100644 index 00000000..fc08f5af --- /dev/null +++ b/tests/de/akquinet/devops/GitblitRunnable.java @@ -0,0 +1,134 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops; + +import java.net.InetAddress; +import java.net.ServerSocket; + +import com.gitblit.GitBlitServer; +import com.gitblit.tests.GitBlitSuite; + +/** + * This is a runnable implementation, that is used to run a gitblit server in a + * separate thread (e.g. alongside test cases) + * + * @author saheba + * + */ +public class GitblitRunnable implements Runnable { + + private int httpPort, httpsPort, shutdownPort; + private String userPropertiesPath, gitblitPropertiesPath; + private boolean startFailed = false; + + /** + * constructor with reduced set of start params + * + * @param httpPort + * @param httpsPort + * @param shutdownPort + * @param gitblitPropertiesPath + * @param userPropertiesPath + */ + public GitblitRunnable(int httpPort, int httpsPort, int shutdownPort, + String gitblitPropertiesPath, String userPropertiesPath) { + this.httpPort = httpPort; + this.httpsPort = httpsPort; + this.shutdownPort = shutdownPort; + this.userPropertiesPath = userPropertiesPath; + this.gitblitPropertiesPath = gitblitPropertiesPath; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + public void run() { + boolean portsFree = false; + long lastRun = -1; + while (!portsFree) { + long current = System.currentTimeMillis(); + if (lastRun == -1 || lastRun + 100 < current) { + portsFree = areAllPortsFree(new int[] { httpPort, httpsPort, + shutdownPort }, "127.0.0.1"); + } + lastRun = current; + + } + try { + GitBlitServer.main("--httpPort", "" + httpPort, "--httpsPort", "" + + httpsPort, "--shutdownPort", "" + shutdownPort, + "--repositoriesFolder", + "\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", + "--userService", userPropertiesPath, "--settings", + gitblitPropertiesPath); + setStartFailed(false); + } catch (Exception iex) { + System.out.println("Gitblit server start failed"); + setStartFailed(true); + } + } + + /** + * Method used to ensure that all ports are free, if the runnable is used + * JUnit test classes. Be aware that JUnit's setUpClass and tearDownClass + * methods, which are executed before and after a test class (consisting of + * several test cases), may be executed parallely if they are part of a test + * suite consisting of several test classes. Therefore the run method of + * this class calls areAllPortsFree to check port availability before + * starting another gitblit instance. + * + * @param ports + * @param inetAddress + * @return + */ + public static boolean areAllPortsFree(int[] ports, String inetAddress) { + System.out + .println("\n" + + System.currentTimeMillis() + + " ----------------------------------- testing if all ports are free ..."); + String blockedPorts = ""; + for (int i = 0; i < ports.length; i++) { + ServerSocket s; + try { + s = new ServerSocket(ports[i], 1, + InetAddress.getByName(inetAddress)); + s.close(); + } catch (Exception e) { + if (!blockedPorts.equals("")) { + blockedPorts += ", "; + } + } + } + if (blockedPorts.equals("")) { + System.out + .println(" ----------------------------------- ... verified"); + return true; + } + System.out.println(" ----------------------------------- ... " + + blockedPorts + " are still blocked"); + return false; + } + + private void setStartFailed(boolean startFailed) { + this.startFailed = startFailed; + } + + public boolean isStartFailed() { + return startFailed; + } +} diff --git a/tests/de/akquinet/devops/LaunchWithUITestConfig.java b/tests/de/akquinet/devops/LaunchWithUITestConfig.java new file mode 100644 index 00000000..594d7fc7 --- /dev/null +++ b/tests/de/akquinet/devops/LaunchWithUITestConfig.java @@ -0,0 +1,126 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.gitblit.Constants; +import com.gitblit.GitBlitServer; +import com.gitblit.tests.GitBlitSuite; + +/** + * This test checks if it is possible to run two server instances in the same + * JVM sequentially + * + * @author saheba + * + */ +public class LaunchWithUITestConfig { + + @Test + public void testSequentialLaunchOfSeveralInstances() + throws InterruptedException { + // different ports than in testParallelLaunchOfSeveralInstances to + // ensure that both test cases do not affect each others test results + int httpPort = 9191, httpsPort = 9292, shutdownPort = 9393; + String gitblitPropertiesPath = "test-ui-gitblit.properties", usersPropertiesPath = "test-ui-users.conf"; + + GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort, + httpsPort, shutdownPort, gitblitPropertiesPath, + usersPropertiesPath); + Thread serverThread = new Thread(gitblitRunnable); + serverThread.start(); + Thread.sleep(2000); + Assert.assertFalse(gitblitRunnable.isStartFailed()); + LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort); + + Thread.sleep(5000); + + GitblitRunnable gitblitRunnable2 = new GitblitRunnable(httpPort, + httpsPort, shutdownPort, gitblitPropertiesPath, + usersPropertiesPath); + Thread serverThread2 = new Thread(gitblitRunnable2); + serverThread2.start(); + Thread.sleep(2000); + Assert.assertFalse(gitblitRunnable2.isStartFailed()); + LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort); + } + + @Test + public void testParallelLaunchOfSeveralInstances() + throws InterruptedException { + // different ports than in testSequentialLaunchOfSeveralInstances to + // ensure that both test cases do not affect each others test results + int httpPort = 9797, httpsPort = 9898, shutdownPort = 9999; + int httpPort2 = 9494, httpsPort2 = 9595, shutdownPort2 = 9696; + String gitblitPropertiesPath = "test-ui-gitblit.properties", usersPropertiesPath = "test-ui-users.conf"; + + GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort, + httpsPort, shutdownPort, gitblitPropertiesPath, + usersPropertiesPath); + Thread serverThread = new Thread(gitblitRunnable); + serverThread.start(); + Thread.sleep(2000); + Assert.assertFalse(gitblitRunnable.isStartFailed()); + + GitblitRunnable gitblitRunnable2 = new GitblitRunnable(httpPort2, + httpsPort2, shutdownPort2, gitblitPropertiesPath, + usersPropertiesPath); + Thread serverThread2 = new Thread(gitblitRunnable2); + serverThread2.start(); + Thread.sleep(2000); + Assert.assertFalse(gitblitRunnable2.isStartFailed()); + + LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort); + LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort2); + } + + /** + * main runs the tests without assert checks. You have to check the console + * output manually. + * + * @param args + * @throws InterruptedException + */ + public static void main(String[] args) throws InterruptedException { + new LaunchWithUITestConfig().testSequentialLaunchOfSeveralInstances(); + new LaunchWithUITestConfig().testParallelLaunchOfSeveralInstances(); + } + + private static void shutdownGitBlitServer(int shutdownPort) { + try { + Socket s = new Socket(InetAddress.getByName("127.0.0.1"), + 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(); + } + } +} diff --git a/tests/de/akquinet/devops/ManualUITestLaunch.java b/tests/de/akquinet/devops/ManualUITestLaunch.java new file mode 100644 index 00000000..2eff4918 --- /dev/null +++ b/tests/de/akquinet/devops/ManualUITestLaunch.java @@ -0,0 +1,14 @@ +package de.akquinet.devops; + +public class ManualUITestLaunch { +public static void main(String[] args) { + int httpPort = 8080, httpsPort = 8443, shutdownPort = 8081; + String gitblitPropertiesPath = "test-ui-gitblit.properties", usersPropertiesPath = "test-ui-users.conf"; + + GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort, + httpsPort, shutdownPort, gitblitPropertiesPath, + usersPropertiesPath); + Thread serverThread = new Thread(gitblitRunnable); + serverThread.start(); +} +} diff --git a/tests/de/akquinet/devops/test/ui/TestUISuite.java b/tests/de/akquinet/devops/test/ui/TestUISuite.java new file mode 100644 index 00000000..97bd9037 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/TestUISuite.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import de.akquinet.devops.test.ui.cases.UI_MultiAdminSupportTest; + +/** + * the test suite including all selenium-based ui-tests. + * + * @author saheba + * + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ UI_MultiAdminSupportTest.class }) +public class TestUISuite { + +} diff --git a/tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java b/tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java new file mode 100644 index 00000000..9cdad160 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.cases; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import de.akquinet.devops.test.ui.generic.AbstractUITest; +import de.akquinet.devops.test.ui.view.RepoEditView; +import de.akquinet.devops.test.ui.view.RepoListView; + +/** + * tests the multi admin per repo feature. + * + * @author saheba + * + */ +public class UI_MultiAdminSupportTest extends AbstractUITest { + + String baseUrl = "https://localhost:8443"; + RepoListView view; + RepoEditView editView; + private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_NAME = "testmultiadminsupport"; + private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_PATH = "~repocreator/" + + TEST_MULTI_ADMIN_SUPPORT_REPO_NAME + ".git"; + private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX = "~repocreator/" + + TEST_MULTI_ADMIN_SUPPORT_REPO_NAME; + + @Before + public void before() { + System.out.println("IN BEFORE"); + this.view = new RepoListView(AbstractUITest.getDriver(), baseUrl); + this.editView = new RepoEditView(AbstractUITest.getDriver()); + AbstractUITest.getDriver().navigate().to(baseUrl); + } + + @Test + public void test_MultiAdminSelectionInStandardRepo() { + // login + view.login("repocreator", "repocreator"); + + // create new repo + view.navigateToNewRepo(1); + editView.changeName(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH); + Assert.assertTrue(editView.navigateToPermissionsTab()); + + Assert.assertTrue(editView + .changeAccessRestriction(RepoEditView.RESTRICTION_AUTHENTICATED_VCP)); + Assert.assertTrue(editView + .changeAuthorizationControl(RepoEditView.AUTHCONTROL_RWALL)); + + // with a second admin + editView.addRepoAdministrator("admin"); + Assert.assertTrue(editView.save()); + // user is automatically forwarded to repo list view + Assert.assertTrue(view.isEmptyRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH)); + Assert.assertTrue(view + .isEditableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH)); + Assert.assertTrue(view + .isDeletableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX)); + // logout repocreator + view.logout(); + + // check with admin account if second admin has the same rights + view.login("admin", "admin"); + Assert.assertTrue(view.isEmptyRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH)); + Assert.assertTrue(view + .isEditableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH)); + Assert.assertTrue(view + .isDeletableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX)); + // delete repo to reach state as before test execution + view.navigateToDeleteRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX); + view.acceptAlertDialog(); + view.logout(); + + Assert.assertTrue(view.isLoginPartVisible()); + } + +} diff --git a/tests/de/akquinet/devops/test/ui/generic/AbstractUITest.java b/tests/de/akquinet/devops/test/ui/generic/AbstractUITest.java new file mode 100644 index 00000000..bb7b3da2 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/generic/AbstractUITest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.generic; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxProfile; + +import com.gitblit.GitBlitServer; + +import de.akquinet.devops.GitblitRunnable; + +/** + * This abstract class implements the setUpClass and tearDownClass for + * selenium-based UITests. They require a running gitblit server instance and a + * webdriver instance, which are managed by the setUpClass and tearDownClass + * method. Write a separate test class derived from this abstract class for each + * scenario consisting of one or more test cases, which can share the same + * server instance. + * + * @author saheba + * + */ +public abstract class AbstractUITest { + + private static Thread serverThread; + private static WebDriver driver; + + private static final int HTTP_PORT = 8080, HTTPS_PORT = 8443, + SHUTDOWN_PORT = 8081; + private static final String GITBLIT_PROPERTIES_PATH = "test-ui-gitblit.properties", + USERS_PROPERTIES_PATH = "test-ui-users.conf"; + + /** + * starts a gitblit server instance in a separate thread before test cases + * of concrete, non-abstract child-classes are executed + */ + @BeforeClass + public static void setUpClass() { + Runnable gitblitRunnable = new GitblitRunnable(HTTP_PORT, HTTPS_PORT, + SHUTDOWN_PORT, GITBLIT_PROPERTIES_PATH, USERS_PROPERTIES_PATH); + + serverThread = new Thread(gitblitRunnable); + serverThread.start(); + FirefoxProfile firefoxProfile = new FirefoxProfile(); + firefoxProfile.setPreference("startup.homepage_welcome_url", + "https://www.google.de"); + + firefoxProfile.setPreference("browser.download.folderList", 2); + firefoxProfile.setPreference( + "browser.download.manager.showWhenStarting", false); + String downloadDir = System.getProperty("java.io.tmpdir"); + firefoxProfile.setPreference("browser.download.dir", downloadDir); + firefoxProfile.setPreference("browser.helperApps.neverAsk.saveToDisk", + "text/csv,text/plain,application/zip,application/pdf"); + firefoxProfile.setPreference("browser.helperApps.alwaysAsk.force", + false); + System.out.println("Saving all attachments to: " + downloadDir); + + driver = new FirefoxDriver(firefoxProfile); + } + + /** + * stops the gitblit server instance running in a separate thread after test + * cases of concrete, non-abstract child-classes have been executed + */ + @AfterClass + public static void tearDownClass() throws InterruptedException { + driver.close(); + // Stop Gitblit + GitBlitServer.main("--stop", "--shutdownPort", "" + SHUTDOWN_PORT); + + // Wait a few seconds for it to be running completely including thread + // destruction + Thread.sleep(1000); + } + + public static WebDriver getDriver() { + return AbstractUITest.driver; + } +} diff --git a/tests/de/akquinet/devops/test/ui/view/Exp.java b/tests/de/akquinet/devops/test/ui/view/Exp.java new file mode 100644 index 00000000..3433bbb6 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/view/Exp.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.view; + +import java.util.List; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; + +/** + * container class for selenium conditions + * + * @author saheba + * + */ +public class Exp { + public static class EditRepoViewLoaded implements ExpectedCondition<Boolean> { + public Boolean apply(WebDriver d) { + List<WebElement> findElements = d.findElements(By.partialLinkText("general")); + return findElements.size() == 1; + } + } + public static class RepoListViewLoaded implements ExpectedCondition<Boolean> { + public Boolean apply(WebDriver d) { + String xpath = "//img[@src=\"git-black-16x16.png\"]"; + List<WebElement> findElements = d.findElements(By.xpath(xpath )); + return findElements.size() == 1; + } + } +} diff --git a/tests/de/akquinet/devops/test/ui/view/GitblitDashboardView.java b/tests/de/akquinet/devops/test/ui/view/GitblitDashboardView.java new file mode 100644 index 00000000..0908d7c9 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/view/GitblitDashboardView.java @@ -0,0 +1,100 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.view; + +import java.util.List; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * class representing the view componenents and possible user interactions, you + * can see and do on most screens when you are logged in. + * + * @author saheba + * + */ +public class GitblitDashboardView extends GitblitPageView { + + public static final String TITLE_STARTS_WITH = "localhost"; + + public GitblitDashboardView(WebDriver driver, String baseUrl) { + super(driver, baseUrl); + } + + public boolean isLoginPartVisible() { + List<WebElement> found = getDriver().findElements( + By.partialLinkText("logout")); + return found == null || found.size() == 0; + } + + public void logout() { + // String pathLogout = "//a[@href =\"?" + WICKET_HREF_PAGE_PATH + // + ".LogoutPage\"]"; + // List<WebElement> logout = + // getDriver().findElements(By.xpath(pathLogout)); + // logout.get(0).click(); + // replaced by url call because click hangs sometimes if the clicked + // object is not a button or selenium ff driver does not notice the + // change for any other reason + getDriver().navigate().to( + getBaseUrl() + "?" + WICKET_HREF_PAGE_PATH + ".LogoutPage"); + } + + public static final String LOGIN_AREA_SELECTOR = "//span[@class = \"form-search\" ]"; + public static final String WICKET_PAGES_PACKAGE_NAME = "com.gitblit.wicket.pages"; + public static final String WICKET_HREF_PAGE_PATH = "wicket:bookmarkablePage=:" + + WICKET_PAGES_PACKAGE_NAME; + + synchronized public void waitToLoadFor(int sec) { + WebDriverWait webDriverWait = new WebDriverWait(getDriver(), sec); + webDriverWait.until(new ExpectedCondition<Boolean>() { + public Boolean apply(WebDriver d) { + return d.getTitle().toLowerCase() + .startsWith(GitblitDashboardView.TITLE_STARTS_WITH); + } + }); + } + + public void login(String id, String pw) { + String pathID = LOGIN_AREA_SELECTOR + "/input[@name = \"username\" ]"; + String pathPW = LOGIN_AREA_SELECTOR + "/input[@name = \"password\" ]"; + String pathSubmit = LOGIN_AREA_SELECTOR + + "/button[@type = \"submit\" ]"; + // System.out.println("DRIVER:"+getDriver()); + // List<WebElement> findElement = + // getDriver().findElements(By.xpath("//span[@class = \"form-search\" ]")); + // + // System.out.println("ELEM: "+findElement); + // System.out.println("SIZE: "+findElement.size()); + // System.out.println("XPath: "+pathID); + WebElement idField = getDriver().findElement(By.xpath(pathID)); + // System.out.println("IDFIELD:"+idField); + idField.sendKeys(id); + WebElement pwField = getDriver().findElement(By.xpath(pathPW)); + // System.out.println(pwField); + pwField.sendKeys(pw); + WebElement submit = getDriver().findElement(By.xpath(pathSubmit)); + submit.click(); + } + + public void acceptAlertDialog() { + getDriver().switchTo().alert().accept(); + } +} diff --git a/tests/de/akquinet/devops/test/ui/view/GitblitPageView.java b/tests/de/akquinet/devops/test/ui/view/GitblitPageView.java new file mode 100644 index 00000000..43716434 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/view/GitblitPageView.java @@ -0,0 +1,73 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.view; + +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +/** + * general basic class representing a gitblit webpage and offering basic methods + * used in selenium tests. + * + * @author saheba + * + */ +public class GitblitPageView { + private WebDriver driver; + private String baseUrl; + + public GitblitPageView(WebDriver driver, String baseUrl) { + this.driver = driver; + this.baseUrl = baseUrl; + } + + public void sleep(int miliseconds) { + try { + Thread.sleep(miliseconds); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public WebElement getElementWithFocus() { + String elScript = "return document.activeElement;"; + + WebElement focuseedEl = (WebElement) ((JavascriptExecutor) getDriver()) + .executeScript(elScript); + return focuseedEl; + } + + public void navigateToPreviousPageOfBrowserHistory() { + driver.navigate().back(); + } + + public void setDriver(WebDriver driver) { + this.driver = driver; + } + + public WebDriver getDriver() { + return driver; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getBaseUrl() { + return baseUrl; + } +} diff --git a/tests/de/akquinet/devops/test/ui/view/RepoEditView.java b/tests/de/akquinet/devops/test/ui/view/RepoEditView.java new file mode 100644 index 00000000..ef0a3171 --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/view/RepoEditView.java @@ -0,0 +1,158 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.view; + +import java.util.List; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * class representing the tabs you can access when you edit a repo. + * + * @author saheba + * + */ +public class RepoEditView extends GitblitDashboardView { + + public static final String PERMISSION_VIEW_USERS_NAME_PREFIX = "users:"; + public static final String PERMISSION_VIEW_TEAMS_NAME_PREFIX = "teams:"; + + public static final String PERMISSION_VIEW_MUTABLE = "permissionToggleForm:showMutable"; + public static final String PERMISSION_VIEW_SPECIFIED = "permissionToggleForm:showSpecified"; + public static final String PERMISSION_VIEW_EFFECTIVE = "permissionToggleForm:showEffective"; + + public static final int RESTRICTION_ANONYMOUS_VCP = 0; + public static final int RESTRICTION_AUTHENTICATED_P = 1; + public static final int RESTRICTION_AUTHENTICATED_CP = 2; + public static final int RESTRICTION_AUTHENTICATED_VCP = 3; + + public static final int AUTHCONTROL_RWALL = 0; + public static final int AUTHOCONTROL_FINE = 1; + + public RepoEditView(WebDriver driver) { + super(driver, null); + } + + public void changeName(String newName) { + String pathName = "//input[@id = \"name\" ]"; + WebElement field = getDriver().findElement(By.xpath(pathName)); + field.clear(); + field.sendKeys(newName); + } + + public boolean navigateToPermissionsTab() { + String linkText = "access permissions"; + List<WebElement> found = getDriver().findElements( + By.partialLinkText(linkText)); + System.out.println("PERM TABS found =" + found.size()); + if (found != null && found.size() == 1) { + found.get(0).click(); + return true; + } + return false; + } + + private void changeRepoAdministrators(String action, + String affectedSelection, String username) { + String xpath = "//select[@name=\"" + affectedSelection + + "\"]/option[@value = \"" + username + "\" ]"; + WebElement option = getDriver().findElement(By.xpath(xpath)); + option.click(); + String buttonPath = "//button[@class=\"button " + action + "\"]"; + WebElement button = getDriver().findElement(By.xpath(buttonPath)); + button.click(); + } + + public void removeRepoAdministrator(String username) { + changeRepoAdministrators("remove", "repoAdministrators:selection", + username); + } + + public void addRepoAdministrator(String username) { + changeRepoAdministrators("add", "repoAdministrators:choices", username); + } + + public WebElement getAccessRestrictionSelection() { + String xpath = "//select[@name =\"accessRestriction\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(xpath)); + if (found != null && found.size() == 1) { + return found.get(0); + } + return null; + } + + public boolean changeAccessRestriction(int option) { + WebElement accessRestrictionSelection = getAccessRestrictionSelection(); + if (accessRestrictionSelection == null) { + return false; + } + accessRestrictionSelection.click(); + sleep(100); + String xpath = "//select[@name =\"accessRestriction\"]/option[@value=\"" + + option + "\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(xpath)); + if (found == null || found.size() == 0 || found.size() > 1) { + return false; + } + found.get(0).click(); + return true; + } + + public boolean changeAuthorizationControl(int option) { + System.out.println("try to change auth control"); + String xpath = "//input[@name =\"authorizationControl\" and @value=\"" + + option + "\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(xpath)); + System.out.println("found auth CONTROL options " + found.size()); + if (found == null || found.size() == 0 || found.size() > 1) { + return false; + } + found.get(0).click(); + return true; + } + + private boolean isPermissionViewDisabled(String prefix, String view) { + String xpath = "//[@name =\"" + prefix + view + "\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(xpath)); + if (found == null || found.size() == 0 || found.size() > 1) { + return false; + } + String attrValue = found.get(0).getAttribute("disabled"); + return (attrValue != null) && (attrValue.equals("disabled")); + } + + public boolean isPermissionViewSectionDisabled(String prefix) { + return isPermissionViewDisabled(prefix, PERMISSION_VIEW_MUTABLE) + && isPermissionViewDisabled(prefix, PERMISSION_VIEW_SPECIFIED) + && isPermissionViewDisabled(prefix, PERMISSION_VIEW_EFFECTIVE); + } + + public boolean save() { + String xpath = "//div[@class=\"form-actions\"]/input[@name =\"" + + "save" + "\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(xpath)); + if (found == null || found.size() == 0 || found.size() > 1) { + return false; + } + found.get(0).click(); + WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 1); + webDriverWait.until(new Exp.RepoListViewLoaded()); + return true; + } +} diff --git a/tests/de/akquinet/devops/test/ui/view/RepoListView.java b/tests/de/akquinet/devops/test/ui/view/RepoListView.java new file mode 100644 index 00000000..6ec6203f --- /dev/null +++ b/tests/de/akquinet/devops/test/ui/view/RepoListView.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013 akquinet tech@spree GmbH + * + * 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 de.akquinet.devops.test.ui.view; + +import java.util.List; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; + +/** + * class representing the repo list view, which you see e.g. right after you + * logged in. + * + * @author saheba + * + */ +public class RepoListView extends GitblitDashboardView { + + public RepoListView(WebDriver driver, String baseUrl) { + super(driver, baseUrl); + } + + public boolean isEmptyRepo(String fullyQualifiedRepoName) { + String pathToLink = "//a[@href = \"?" + WICKET_HREF_PAGE_PATH + + ".EmptyRepositoryPage&r=" + fullyQualifiedRepoName + "\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(pathToLink)); + return found != null && found.size() > 0; + } + + private String getEditRepoPath(String fullyQualifiedRepoName) { + return "//a[@href =\"?" + WICKET_HREF_PAGE_PATH + + ".EditRepositoryPage&r=" + fullyQualifiedRepoName + "\"]"; + } + + private String getDeleteRepoOnclickIdentifier( + String fullyQualifiedRepoPathAndName) { + return "var conf = confirm('Delete repository \"" + + fullyQualifiedRepoPathAndName + + "\"?'); if (!conf) return false; "; + } + + public boolean navigateToNewRepo(long waitSecToLoad) { + String pathToLink = "//a[@href =\"?" + WICKET_HREF_PAGE_PATH + + ".EditRepositoryPage\"]"; + List<WebElement> found = getDriver().findElements(By.xpath(pathToLink)); + if (found == null || found.size() == 0 || found.size() > 1) { + return false; + } + found.get(0).click(); + WebDriverWait webDriverWait = new WebDriverWait(getDriver(), + waitSecToLoad); + webDriverWait.until(new Exp.EditRepoViewLoaded()); + return true; + } + + private boolean checkOrDoEditRepo(String fullyQualifiedRepoName, + boolean doEdit) { + List<WebElement> found = getDriver().findElements( + By.xpath(getEditRepoPath(fullyQualifiedRepoName))); + if (found == null || found.size() == 0 || found.size() > 1) { + return false; + } + if (doEdit) { + found.get(0).click(); + } + return true; + } + + public boolean navigateToEditRepo(String fullyQualifiedRepoName, + int waitSecToLoad) { + boolean result = checkOrDoEditRepo(fullyQualifiedRepoName, true); + WebDriverWait webDriverWait = new WebDriverWait(getDriver(), + waitSecToLoad); + webDriverWait.until(new Exp.EditRepoViewLoaded()); + return result; + } + + public boolean isEditableRepo(String fullyQualifiedRepoName) { + return checkOrDoEditRepo(fullyQualifiedRepoName, false); + } + + private boolean checkOrDoDeleteRepo(String fullyQualifiedRepoPathAndName, + boolean doDelete) { + List<WebElement> found = getDriver().findElements( + By.partialLinkText("delete")); + String onclickIdentifier = getDeleteRepoOnclickIdentifier(fullyQualifiedRepoPathAndName); + WebElement result = null; + for (WebElement webElement : found) { + if (webElement.getAttribute("onclick") != null + && webElement.getAttribute("onclick").equals( + onclickIdentifier)) { + result = webElement; + break; + } + } + System.out.println("result ? " + result); + if (result == null) { + return false; + } + if (doDelete) { + System.out.println(".............. DO DELETE .... "); + result.click(); + } + return true; + } + + public boolean isDeletableRepo(String fullyQualifiedRepoPathAndName) { + return checkOrDoDeleteRepo(fullyQualifiedRepoPathAndName, false); + } + + public boolean navigateToDeleteRepo(String fullyQualifiedRepoPathAndName) { + return checkOrDoDeleteRepo(fullyQualifiedRepoPathAndName, true); + + } +} |