]> source.dussan.org Git - gitblit.git/commitdiff
Big push for first release.
authorJames Moger <james.moger@gitblit.com>
Sat, 25 Jun 2011 12:57:29 +0000 (08:57 -0400)
committerJames Moger <james.moger@gitblit.com>
Sat, 25 Jun 2011 12:57:29 +0000 (08:57 -0400)
* Build script overhaul including building & publishing GO, WAR, Docs,
and Site.
* Restored JGit 0.12.1 dependency and backported Blame.  Got tired of
waiting for JGit 1.0.0 Maven artifacts.
* Changed Summary Page layout
* Optional cookie authentication
* Added icons for log, tags, and branches panels.
* Show last commit author and short message on branches panel.
* Unit testing.
* Documentation.

67 files changed:
.classpath
NOTICE
README.MKD
build.xml
distrib/gitblit.properties
distrib/installService.cmd
distrib/installService64.cmd
docs/00_index.mkd
docs/00_setup.mkd [deleted file]
docs/01_faq.mkd [deleted file]
docs/01_features.mkd [new file with mode: 0644]
docs/01_releases.mkd [deleted file]
docs/01_setup.mkd [new file with mode: 0644]
docs/02_faq.mkd [new file with mode: 0644]
docs/02_properties.mkd [deleted file]
docs/03_properties.mkd [new file with mode: 0644]
docs/04_design.mkd [new file with mode: 0644]
docs/04_releases.mkd [new file with mode: 0644]
resources/commit_changes_16x16.png [new file with mode: 0644]
resources/gitblit.css
src/com/gitblit/Build.java
src/com/gitblit/BuildThumbnails.java [new file with mode: 0644]
src/com/gitblit/BuildWebXml.java
src/com/gitblit/Constants.java
src/com/gitblit/FileLoginService.java [deleted file]
src/com/gitblit/FileSettings.java
src/com/gitblit/FileUserService.java [new file with mode: 0644]
src/com/gitblit/GitBlit.java
src/com/gitblit/GitBlitServer.java
src/com/gitblit/ILoginService.java [deleted file]
src/com/gitblit/IStoredSettings.java
src/com/gitblit/IUserService.java [new file with mode: 0644]
src/com/gitblit/SyndicationServlet.java
src/com/gitblit/Thumbnailer.java [deleted file]
src/com/gitblit/WebXmlSettings.java
src/com/gitblit/models/UserModel.java
src/com/gitblit/utils/JGitUtils.java
src/com/gitblit/utils/StringUtils.java
src/com/gitblit/wicket/pages/BasePage.java
src/com/gitblit/wicket/pages/CommitPage.java
src/com/gitblit/wicket/pages/DocsPage.java
src/com/gitblit/wicket/pages/EditUserPage.java
src/com/gitblit/wicket/pages/LogPage.java
src/com/gitblit/wicket/pages/LoginPage.java
src/com/gitblit/wicket/pages/LogoutPage.java
src/com/gitblit/wicket/pages/RepositoryPage.java
src/com/gitblit/wicket/pages/SummaryPage.html
src/com/gitblit/wicket/pages/SummaryPage.java
src/com/gitblit/wicket/panels/BranchesPanel.html
src/com/gitblit/wicket/panels/BranchesPanel.java
src/com/gitblit/wicket/panels/LogPanel.html
src/com/gitblit/wicket/panels/SearchPanel.java
src/com/gitblit/wicket/panels/TagsPanel.html
src/com/gitblit/wicket/panels/TagsPanel.java
src/org/eclipse/jgit/api/BlameCommand.java [new file with mode: 0644]
src/org/eclipse/jgit/blame/BlameGenerator.java [new file with mode: 0644]
src/org/eclipse/jgit/blame/BlameResult.java [new file with mode: 0644]
src/org/eclipse/jgit/blame/Candidate.java [new file with mode: 0644]
src/org/eclipse/jgit/blame/Region.java [new file with mode: 0644]
src/org/eclipse/jgit/blame/ReverseWalk.java [new file with mode: 0644]
tests/com/gitblit/tests/DiffUtilsTest.java
tests/com/gitblit/tests/GitBlitSuite.java
tests/com/gitblit/tests/GitBlitTest.java
tests/com/gitblit/tests/JGitUtilsTest.java
tests/com/gitblit/tests/StringUtilsTest.java
tests/com/gitblit/tests/SyndicationUtilsTest.java [new file with mode: 0644]
tools/ant-googlecode-0.0.3.jar [new file with mode: 0644]

index 28f569548c51547f67a5a97fbb21df74f54c6c54..73cfac429753e2c2854435536242a989ad8c76d2 100644 (file)
@@ -2,6 +2,7 @@
 <classpath>\r
        <classpathentry kind="src" path="src"/>\r
        <classpathentry kind="src" path="tests"/>\r
+       <classpathentry kind="src" path="resources"/>\r
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>\r
        <classpathentry kind="lib" path="ext/log4j-1.2.16.jar" sourcepath="ext/log4j-1.2.16-sources.jar">\r
                <attributes>\r
                        <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/googlecharts-1.4.17-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
-       <classpathentry kind="lib" path="ext/junit-3.8.2.jar"/>\r
-       <classpathentry kind="lib" path="ext/org.eclipse.jgit-0.12.1.jar" sourcepath="ext/org.eclipse.jgit-0.12.1-sources.jar">\r
+       <classpathentry kind="lib" path="ext/bcprov-jdk16-1.46.jar" sourcepath="ext/bcprov-jdk16-1.46-sources.jar">\r
                <attributes>\r
-                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/org.eclipse.jgit-0.12.1-javadoc.jar!/"/>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/bcprov-jdk16-1.46-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
-       <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-0.12.1.jar" sourcepath="ext/org.eclipse.jgit.http.server-0.12.1-sources.jar">\r
+       <classpathentry kind="lib" path="ext/bcmail-jdk16-1.46.jar" sourcepath="ext/bcmail-jdk16-1.46-sources.jar">\r
                <attributes>\r
-                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/org.eclipse.jgit.http.server-0.12.1-javadoc.jar!/"/>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/bcmail-jdk16-1.46-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
-       <classpathentry kind="lib" path="ext/bcprov-jdk16-1.46.jar" sourcepath="ext/bcprov-jdk16-1.46-sources.jar">\r
+       <classpathentry kind="lib" path="ext/jsch-0.1.44-1.jar" sourcepath="ext/jsch-0.1.44-1-sources.jar">\r
                <attributes>\r
-                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/bcprov-jdk16-1.46-javadoc.jar!/"/>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jsch-0.1.44-1-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
-       <classpathentry kind="lib" path="ext/bcmail-jdk16-1.46.jar" sourcepath="ext/bcmail-jdk16-1.46-sources.jar">\r
+       <classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/rome-0.9-sources.jar">\r
                <attributes>\r
-                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/bcmail-jdk16-1.46-javadoc.jar!/"/>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/rome-0.9-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
-       <classpathentry kind="lib" path="ext/jetty-all-7.4.1.v20110513.jar" sourcepath="ext/jetty-all-7.4.1.v20110513-sources.jar">\r
+       <classpathentry kind="lib" path="ext/jdom-1.1.jar" sourcepath="ext/jdom-1.1-sources.jar">\r
                <attributes>\r
-                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jetty-all-7.2.2.v20101205-javadoc.jar!/"/>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jdom-1.1-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
-       <classpathentry kind="lib" path="ext/jsch-0.1.44-1.jar" sourcepath="ext/jsch-0.1.44-1-sources.jar">\r
+       <classpathentry kind="lib" path="ext/jetty-webapp-7.4.2.v20110526.jar" sourcepath="ext/jetty-webapp-7.4.2.v20110526-sources.jar">\r
                <attributes>\r
-                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jsch-0.1.44-1-javadoc.jar!/"/>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jetty-webapp-7.4.2.v20110526-javadoc.jar!/"/>\r
+               </attributes>\r
+       </classpathentry>\r
+       <classpathentry kind="lib" path="ext/junit-4.8.2.jar"/>\r
+       <classpathentry kind="lib" path="ext/org.eclipse.jgit-0.12.1.jar" sourcepath="ext/org.eclipse.jgit-0.12.1-sources.jar">\r
+               <attributes>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/org.eclipse.jgit-0.12.1-javadoc.jar!/"/>\r
+               </attributes>\r
+       </classpathentry>\r
+       <classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-0.12.1.jar" sourcepath="ext/org.eclipse.jgit.http.server-0.12.1-sources.jar">\r
+               <attributes>\r
+                       <attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/org.eclipse.jgit.http.server-0.12.1-javadoc.jar!/"/>\r
                </attributes>\r
        </classpathentry>\r
        <classpathentry kind="output" path="bin"/>\r
diff --git a/NOTICE b/NOTICE
index b45f30b8bd5157036dc29f6f6afc082bcd247a0d..bb9aa12c298b02d31b64c70d47996730343d2f19 100644 (file)
--- a/NOTICE
+++ b/NOTICE
@@ -119,6 +119,14 @@ JUnit
 \r
    http://junit.org\r
 \r
+---------------------------------------------------------------------------\r
+ant-googlecode\r
+---------------------------------------------------------------------------\r
+   ant-googlecode, released under the\r
+   New BSD License\r
+   \r
+   http://code.google.com/p/ant-googlecode\r
+   \r
 ---------------------------------------------------------------------------\r
 Fancybox image viewer\r
 ---------------------------------------------------------------------------\r
index bb272b38d7db065705becbe8a5d918172366ef65..a588cb4465db997c64957a29bc07f2aa71e91526 100644 (file)
@@ -1,20 +1,18 @@
 Gitblit\r
 =================\r
 \r
-Gitblit is an open source, pure Java Git solution for creating, viewing, and serving [Git](http://git-scm.com) repositories.<br/>\r
+Gitblit is an open source, pure Java Git solution for managing, viewing, and serving [Git](http://git-scm.com) repositories.<br/>\r
 More information about Gitblit can be found [here](http://gitblit.com).\r
 \r
 License\r
 -------\r
 \r
-Gitblit is distributed under the terms of the Apache Software Foundation\r
-license, version 2.0. The text of the license is included in the file LICENSE in the root\r
-of the project.\r
+Gitblit is distributed under the terms of the Apache Software Foundation license, version 2.0. The text of the license is included in the file LICENSE in the root of the project.\r
 \r
-Java/Application server requirements\r
+Java Runtime Requirement\r
 ------------------------------------\r
 \r
-Gitblit requires at least Java 1.6.\r
+Gitblit requires at Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK).\r
 \r
 Getting help\r
 ------------\r
index ee01de7da592c614aaa71cce9c2fb0133028e818..9a00822bdc9023d8d4a60720ec9c532cd2699116 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -1,14 +1,27 @@
 <?xml version="1.0" encoding="UTF-8"?>\r
-<project name="gitblit" default="main" basedir=".">\r
+<project name="gitblit" default="compile" basedir=".">\r
+\r
+       <!-- Google Code upload task -->\r
+       <taskdef classname="net.bluecow.googlecode.ant.GoogleCodeUploadTask" classpath="${basedir}/tools/ant-googlecode-0.0.3.jar" name="gcupload"/>\r
 \r
        <!-- Project Properties -->\r
        <property name="project.jar" value="gitblit.jar" />\r
        <property name="project.mainclass" value="com.gitblit.Launcher" />\r
        <property name="project.build.dir" value="${basedir}/build" />\r
-       <property name="project.resources.dir" value="${basedir}/resources" />\r
+       <property name="project.deploy.dir" value="${basedir}/deploy" />\r
+       <property name="project.war.dir" value="${basedir}/war" />\r
+       <property name="project.site.dir" value="${basedir}/site" />\r
+       <property name="project.resources.dir" value="${basedir}/resources" />  \r
 \r
+       <!-- Load publication servers, paths, and credentials --> \r
        <loadproperties srcfile="${basedir}/build.properties" />\r
        \r
+       \r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Scrape the version info from code and setup the build properties \r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
        <target name="buildinfo">\r
                <!-- build date -->\r
                <tstamp>\r
                                        <trim />\r
                                </tokenfilter>\r
                        </filterchain>\r
-               </loadfile>\r
-               <echo>Building Gitblit ${gb.version}</echo>\r
-               \r
+               </loadfile>     \r
                <property name="distribution.zipfile" value="gitblit-${gb.version}.zip" />\r
                <property name="distribution.warfile" value="gitblit-${gb.version}.war" />\r
        </target>\r
        \r
-       <!-- Build Gitblit GO -->\r
-       <target name="main" description="Compiles Gitblit from source to website" depends="buildinfo">\r
+       \r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Compile\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="compile" depends="buildinfo" description="Retrieves dependencies and compiles Gitblit from source">\r
 \r
                <!-- copy required distribution files to project folder -->\r
                <copy todir="${basedir}" overwrite="false">\r
@@ -67,8 +83,7 @@
                <delete dir="${project.build.dir}" />\r
                <mkdir dir="${project.build.dir}" />\r
                <javac srcdir="${basedir}/src" destdir="${project.build.dir}">\r
-                       <include name="com/gitblit/Build.java" />\r
-                       <include name="com/gitblit/BuildWebXml.java" />\r
+                       <include name="com/gitblit/Build.java" />                       \r
                        <include name="com/gitblit/Constants.java" />\r
                        <include name="com/gitblit/utils/StringUtils.java" />                   \r
                </javac>\r
                <copy todir="${project.build.dir}">\r
                        <fileset dir="${basedir}/src" excludes="**/*.java,**/thumbs.db" />\r
                </copy>\r
+       </target>\r
+\r
+       \r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Build Gitblit GO\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="buildGO" depends="compile" description="Build Gitblit GO distribution">\r
+               \r
+               <echo>Building Gitblit GO ${gb.version}</echo>\r
+\r
+               <!-- Delete the deploy folder -->\r
+               <delete dir="${project.deploy.dir}" />\r
+\r
+               <!-- Create deployment folder structure -->\r
+               <mkdir dir="${project.deploy.dir}" />\r
+               <copy todir="${project.deploy.dir}">\r
+                       <fileset dir="${basedir}/distrib">\r
+                               <include name="**/*" />\r
+                       </fileset>\r
+               </copy>\r
 \r
                <!-- Build jar -->\r
-               <delete file="${project.jar}" />\r
-               <jar jarfile="${project.jar}">\r
+               <jar jarfile="${project.deploy.dir}/${project.jar}">\r
                        <fileset dir="${project.build.dir}">\r
                                <include name="**/*" />\r
                        </fileset>\r
                        </manifest>\r
                </jar>\r
 \r
+               <!-- Build the docs for the deploy -->\r
+               <antcall target="buildDocs" inheritall="true" inheritrefs="true">\r
+                       <param name="docs.output.dir" value="${project.deploy.dir}/docs" />\r
+               </antcall>\r
+               \r
+               <!-- Create Zip deployment -->          \r
+               <zip destfile="${distribution.zipfile}">\r
+                       <fileset dir="${project.deploy.dir}">\r
+                               <include name="**/*" />\r
+                       </fileset>\r
+               </zip>\r
+\r
+       </target>\r
+       \r
+       \r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Build Gitblit Docs which are bundled with GO and WAR downloads\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="buildDocs">\r
+       <!-- Build Docs -->\r
+                       <mkdir dir="${docs.output.dir}" />\r
+                       <copy todir="${docs.output.dir}">\r
+                               <!-- Copy selected Gitblit resources -->\r
+                               <fileset dir="${project.resources.dir}">\r
+                                       <include name="background.png" />\r
+                                       <include name="gitblit.css" />\r
+                                       <include name="markdown.css" />\r
+                                       <include name="gitblt_25.png" />\r
+                                       <include name="gitblt-favicon.png" />\r
+                                       <include name="lock_go_16x16.png" />\r
+                                       <include name="lock_pull_16x16.png" />\r
+                                       <include name="shield_16x16.png" />\r
+                                       <include name="cold_16x16.png" />\r
+                                       <include name="bug_16x16.png" />\r
+                                       <include name="book_16x16.png" />\r
+                                       <include name="blank.png" />\r
+                               </fileset>\r
+\r
+                               <!-- Copy Doc images -->\r
+                               <fileset dir="${basedir}/docs">\r
+                                       <include name="*.png" />\r
+                               </fileset>\r
+                       </copy>\r
+\r
+                       <!-- Copy google-code-prettify -->\r
+                       <mkdir dir="${docs.output.dir}/prettify" />\r
+                       <copy todir="${docs.output.dir}/prettify">\r
+                               <fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify">\r
+                                       <exclude name="thumbs.db" />\r
+                               </fileset>\r
+                       </copy>\r
+\r
+                       <!-- Build deployment doc pages -->\r
+                       <java classpath="${project.build.dir}" classname="com.gitblit.BuildSite">\r
+                               <classpath refid="master-classpath" />\r
+                               <arg value="--sourceFolder" />\r
+                               <arg value="${basedir}/docs" />\r
+\r
+                               <arg value="--outputFolder" />\r
+                               <arg value="${docs.output.dir}" />\r
+\r
+                               <arg value="--pageHeader" />\r
+                               <arg value="${basedir}/docs/doc_header.html" />\r
+\r
+                               <arg value="--pageFooter" />\r
+                               <arg value="${basedir}/docs/doc_footer.html" />\r
+\r
+                               <arg value="--skip" />\r
+                               <arg value="screenshots" />\r
+\r
+                               <arg value="--skip" />\r
+                               <arg value="releases" />\r
+\r
+                               <arg value="--alias" />\r
+                               <arg value="index=overview" />\r
+\r
+                               <arg value="--alias" />\r
+                               <arg value="properties=gitblit.properties" />\r
+\r
+                               <arg value="--substitute" />\r
+                               <arg value="%VERSION%=${gb.version}" />\r
+\r
+                               <arg value="--substitute" />\r
+                               <arg value="%GO%=${distribution.zipfile}" />\r
+\r
+                               <arg value="--substitute" />\r
+                               <arg value="%WAR%=${distribution.warfile}" />\r
+\r
+                               <arg value="--substitute" />\r
+                               <arg value="%BUILDDATE%=${gb.buildDate}" />\r
+\r
+                               <arg value="--substitute" />\r
+                               <arg value="%JGIT%=${jgit.version}" />\r
+\r
+                               <arg value="--load" />\r
+                               <arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" />\r
+\r
+                       </java>\r
+       </target>\r
+       \r
+                               \r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Build Gitblit WAR\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="buildWAR" depends="compile" description="Build Gitblit WAR">\r
+               \r
+               <echo>Building Gitblit WAR ${gb.version}</echo>\r
+               \r
+               <delete dir="${project.war.dir}" />             \r
+\r
+               <!-- Copy web.xml and users.properties to WEB-INF -->\r
+               <copy todir="${project.war.dir}/WEB-INF">\r
+                       <fileset dir="${basedir}/distrib">\r
+                               <include name="users.properties" />\r
+                       </fileset>\r
+                       <fileset dir="${basedir}/src/WEB-INF">\r
+                               <include name="web.xml" />\r
+                       </fileset>\r
+               </copy>\r
+               \r
+               <!-- Build the docs for the WAR build -->\r
+               <antcall target="buildDocs" inheritall="true" inheritrefs="true">\r
+                       <param name="docs.output.dir" value="${project.war.dir}/WEB-INF/docs" />\r
+               </antcall>\r
+               \r
+               <!-- Build the WAR web.xml from the prototype web.xml and gitblit.properties --> \r
+               <java classpath="${project.build.dir}" classname="com.gitblit.BuildWebXml">\r
+                       <classpath refid="master-classpath" />\r
+                       \r
+                       <arg value="--sourceFile" />\r
+                       <arg value="${basedir}/src/WEB-INF/web.xml" />\r
+                                       \r
+                       <arg value="--destinationFile" />\r
+                       <arg value="${project.war.dir}/WEB-INF/web.xml" />\r
+                       \r
+                       <arg value="--propertiesFile" />\r
+                       <arg value="${basedir}/distrib/gitblit.properties" />\r
+               </java>\r
+\r
+               <!-- Gitblit resources -->\r
+               <copy todir="${project.war.dir}">\r
+                       <fileset dir="${project.resources.dir}">\r
+                               <exclude name="thumbs.db" />\r
+                       </fileset>\r
+               </copy>\r
+               \r
+               <!-- Gitblit library dependencies -->\r
+               <mkdir dir="${project.war.dir}/WEB-INF/lib"/>\r
+               <copy todir="${project.war.dir}/WEB-INF/lib">\r
+                       <fileset dir="${basedir}/ext">\r
+                               <exclude name="*-sources.jar" />\r
+                               <exclude name="*-javadoc.jar" />\r
+                               <exclude name="jcommander*.jar" />\r
+                               <exclude name="jetty*.jar" />\r
+                               <exclude name="junit*.jar" />\r
+                               <exclude name="servlet*.jar" />\r
+                       </fileset>\r
+               </copy>\r
+\r
+               <!-- Gitblit classes -->\r
+               <mkdir dir="${project.war.dir}/WEB-INF/classes"/>\r
+               <copy todir="${project.war.dir}/WEB-INF/classes">\r
+                       <fileset dir="${project.build.dir}">\r
+                               <exclude name="WEB-INF/web.xml" />\r
+                               <exclude name="com/gitblit/tests/" />\r
+                               <exclude name="com/gitblit/Build*.class" />\r
+                               <exclude name="com/gitblit/GitBlitServer*.class" />\r
+                               <exclude name="com/gitblit/Launcher*.class" />\r
+                               <exclude name="com/gitblit/MakeCertificate*.class" />                           \r
+                       </fileset>\r
+               </copy>\r
+\r
+               <!-- Build the WAR file -->\r
+               <jar basedir="${project.war.dir}" destfile="${distribution.warfile}" compress="true" />\r
+       </target>\r
+\r
+       \r
+       <!-- \r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Build the Gitblit Website\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="buildSite" depends="compile" description="Build the Gitblit website">\r
+               \r
+               <echo>Building Gitblit Website ${gb.version}</echo>\r
+\r
                <!-- Build Site -->\r
-               <delete dir="${basedir}/site" />\r
-               <mkdir dir="${basedir}/site" />\r
-               <copy todir="${basedir}/site">\r
+               <delete dir="${project.site.dir}" />\r
+               <mkdir dir="${project.site.dir}" />\r
+               <copy todir="${project.site.dir}">\r
                        <!-- Copy selected Gitblit resources -->\r
                        <fileset dir="${project.resources.dir}">\r
                                <include name="background.png" />\r
                </copy>\r
 \r
                <!-- Copy Fancybox -->\r
-               <mkdir dir="${basedir}/site/fancybox" />\r
-               <copy todir="${basedir}/site/fancybox">\r
+               <mkdir dir="${project.site.dir}/fancybox" />\r
+               <copy todir="${project.site.dir}/fancybox">\r
                        <fileset dir="${basedir}/docs/fancybox">\r
                                <exclude name="thumbs.db" />\r
                        </fileset>\r
 \r
                <!-- Copy google-code-prettify -->\r
                <mkdir dir="${basedir}/src/com/gitblit/wicket/pages/prettify" />\r
-               <copy todir="${basedir}/site/prettify">\r
+               <copy todir="${project.site.dir}/prettify">\r
                        <fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify">\r
                                <exclude name="thumbs.db" />\r
                        </fileset>\r
                </copy>\r
 \r
                <!-- Generate thumbnails of screenshots -->\r
-               <java classpath="${project.build.dir}" classname="com.gitblit.Thumbnailer">\r
+               <java classpath="${project.build.dir}" classname="com.gitblit.BuildThumbnails">\r
                        <classpath refid="master-classpath" />\r
                                \r
                        <arg value="--sourceFolder" />\r
                        <arg value="${basedir}/docs/screenshots" />\r
                \r
                        <arg value="--destinationFolder" />\r
-                       <arg value="${basedir}/site/thumbs" />\r
+                       <arg value="${project.site.dir}/thumbs" />\r
                        \r
                        <arg value="--maximumDimension" />\r
                        <arg value="250" />\r
                </java>\r
 \r
                <!-- Copy screenshots -->\r
-               <mkdir dir="${basedir}/site/screenshots" />\r
-               <copy todir="${basedir}/site/screenshots">\r
+               <mkdir dir="${project.site.dir}/screenshots" />\r
+               <copy todir="${project.site.dir}/screenshots">\r
                        <fileset dir="${basedir}/docs/screenshots">\r
                                <include name="*.png" />\r
                        </fileset>\r
                        <arg value="${basedir}/docs" />\r
 \r
                        <arg value="--outputFolder" />\r
-                       <arg value="${basedir}/site" />\r
+                       <arg value="${project.site.dir}" />\r
 \r
                        <arg value="--pageHeader" />\r
                        <arg value="${basedir}/docs/site_header.html" />\r
                        <arg value="%VERSION%=${gb.version}" />\r
 \r
                        <arg value="--substitute" />\r
-                       <arg value="%DISTRIBUTION%=${distribution.zipfile}" />\r
+                       <arg value="%GO%=${distribution.zipfile}" />\r
 \r
                        <arg value="--substitute" />\r
-                       <arg value="%BUILDDATE%=${gb.buildDate}" />\r
-\r
-                       <arg value="--substitute" />\r
-                       <arg value="%JGIT%=${jgit.version}" />\r
-\r
-                       <arg value="--load" />\r
-                       <arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" />\r
-\r
-               </java>\r
-\r
-               <!-- Delete the deploy folder -->\r
-               <delete dir="${basedir}/deploy" />\r
-\r
-               <!-- Create deployment folder structure -->\r
-               <mkdir dir="${basedir}/deploy" />\r
-               <copy todir="${basedir}/deploy" file="${project.jar}" />\r
-               <copy todir="${basedir}/deploy">\r
-                       <fileset dir="${basedir}/distrib">\r
-                               <include name="**/*" />\r
-                       </fileset>\r
-               </copy>\r
-\r
-               <!-- Build Deployment Docs -->\r
-               <mkdir dir="${basedir}/deploy/docs" />\r
-               <copy todir="${basedir}/deploy/docs">\r
-                       <!-- Copy selected Gitblit resources -->\r
-                       <fileset dir="${project.resources.dir}">\r
-                               <include name="background.png" />\r
-                               <include name="gitblit.css" />\r
-                               <include name="markdown.css" />\r
-                               <include name="gitblt_25.png" />\r
-                               <include name="gitblt-favicon.png" />\r
-                               <include name="lock_go_16x16.png" />\r
-                               <include name="lock_pull_16x16.png" />\r
-                               <include name="shield_16x16.png" />\r
-                               <include name="cold_16x16.png" />\r
-                               <include name="bug_16x16.png" />\r
-                               <include name="book_16x16.png" />\r
-                               <include name="blank.png" />\r
-                       </fileset>\r
-\r
-                       <!-- Copy Doc images -->\r
-                       <fileset dir="${basedir}/docs">\r
-                               <include name="*.png" />\r
-                       </fileset>\r
-               </copy>\r
-\r
-               <!-- Copy google-code-prettify -->\r
-               <mkdir dir="${basedir}/src/com/gitblit/wicket/pages/prettify" />\r
-               <copy todir="${basedir}/deploy/docs/prettify">\r
-                       <fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify">\r
-                               <exclude name="thumbs.db" />\r
-                       </fileset>\r
-               </copy>\r
-\r
-               <!-- Build deployment doc pages -->\r
-               <java classpath="${project.build.dir}" classname="com.gitblit.BuildSite">\r
-                       <classpath refid="master-classpath" />\r
-                       <arg value="--sourceFolder" />\r
-                       <arg value="${basedir}/docs" />\r
-\r
-                       <arg value="--outputFolder" />\r
-                       <arg value="${basedir}/deploy/docs" />\r
-\r
-                       <arg value="--pageHeader" />\r
-                       <arg value="${basedir}/docs/page_header.html" />\r
-\r
-                       <arg value="--pageFooter" />\r
-                       <arg value="${basedir}/docs/page_footer.html" />\r
-\r
-                       <arg value="--skip" />\r
-                       <arg value="screenshots" />\r
-\r
-                       <arg value="--skip" />\r
-                       <arg value="releases" />\r
-\r
-                       <arg value="--alias" />\r
-                       <arg value="index=overview" />\r
-\r
-                       <arg value="--alias" />\r
-                       <arg value="properties=gitblit.properties" />\r
-\r
-                       <arg value="--substitute" />\r
-                       <arg value="%VERSION%=${gb.version}" />\r
-\r
-                       <arg value="--substitute" />\r
-                       <arg value="%DISTRIBUTION%=${distribution.zipfile}" />\r
+                       <arg value="%WAR%=${distribution.warfile}" />\r
 \r
                        <arg value="--substitute" />\r
                        <arg value="%BUILDDATE%=${gb.buildDate}" />\r
                        <arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" />\r
 \r
                </java>\r
+       </target>\r
                \r
-               <!-- Create Zip deployment -->          \r
-               <zip destfile="${distribution.zipfile}">\r
-                       <fileset dir="${basedir}/deploy">\r
-                               <include name="**/*" />\r
-                       </fileset>\r
-               </zip>\r
 \r
-               <!-- Delete the deploy folder -->\r
-               <delete dir="${basedir}/deploy" />\r
-\r
-               <!-- Cleanup builds -->\r
-               <delete>\r
-                       <fileset dir="${basedir}">\r
-                               <include name="${project.jar}" />\r
-                       </fileset>\r
-               </delete>\r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \r
+               Compile from source, publish binaries, and build & deploy site\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="buildAll" depends="buildGO,buildWAR,buildSite">           \r
                <!-- Cleanup -->\r
                <delete dir="${project.build.dir}" />\r
+               <delete dir="${project.war.dir}" />\r
+               <delete dir="${project.deploy.dir}" />\r
        </target>\r
-               \r
+                               \r
        \r
-       <!-- Build Gitblit WAR -->\r
-       <target name="buildWAR" description="Build the Gitblit WAR" depends="buildinfo">\r
-               <path id="master-classpath">\r
-                       <fileset dir="${basedir}/ext">\r
-                               <include name="*.jar" />\r
-                       </fileset>\r
-               </path>\r
-                       \r
-               <delete dir="${basedir}/war" />\r
-               <mkdir dir="${basedir}/war/WEB-INF/lib"/>\r
+       <!-- \r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+               Publish binaries to Google Code\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="publishBinaries" depends="buildGO,buildWAR" description="Publish the Gitblit binaries to Google Code">\r
                \r
-               <!-- Gitblit web.xml --> \r
-               <java classpath="${project.build.dir}" classname="com.gitblit.BuildWebXml">\r
-                       <classpath refid="master-classpath" />\r
-               </java>\r
-\r
-               <!-- Gitblit resources -->\r
-               <copy todir="${basedir}/war">\r
-                       <fileset dir="${project.resources.dir}">\r
-                               <exclude name="thumbs.db" />\r
-                       </fileset>\r
-               </copy>\r
+               <echo>Uploading Gitblit ${gb.version} binaries</echo>\r
                \r
-               <!-- Gitblit library dependencies -->\r
-               <copy todir="${basedir}/war/WEB-INF/lib">\r
-                       <fileset dir="${basedir}/ext">\r
-                               <exclude name="*-sources.jar" />\r
-                               <exclude name="*-javadoc.jar" />\r
-                               <exclude name="jcommander*.jar" />\r
-                               <exclude name="jetty*.jar" />\r
-                               <exclude name="junit*.jar" />\r
-                               <exclude name="servlet*.jar" />\r
-                       </fileset>\r
-               </copy>\r
-\r
-               <!-- Gitblit classes -->\r
-               <mkdir dir="${basedir}/war/WEB-INF/classes"/>\r
-               <copy todir="${basedir}/war/WEB-INF/classes">\r
-                       <fileset dir="${basedir}/bin">\r
-                               <exclude name="WEB-INF/web.xml" />\r
-                               <exclude name="com/gitblit/tests/" />\r
-                               <exclude name="com/gitblit/Build*.class" />\r
-                               <exclude name="com/gitblit/GitBlitServer*.class" />\r
-                               <exclude name="com/gitblit/Launcher*.class" />\r
-                               <exclude name="com/gitblit/MakeCertificate*.class" />\r
-                               <exclude name="com/gitblit/Thumbnailer*.class" />\r
-                       </fileset>\r
-               </copy>\r
-\r
-               <!-- Build the WAR file -->\r
-               <jar basedir="${basedir}/war" destfile="${distribution.warfile}" compress="true" />\r
-       </target>\r
-\r
-       \r
-       <!-- Publish binaries to github -->\r
-       <target name="publishBinaries" description="Publish the Gitblit distribution to Github">\r
-               <!-- TODO -->\r
-               <!-- https://github.com/oyvindkinsey/GitHubUploadTask -->\r
+               <!-- Upload ZIP file -->\r
+               <gcupload \r
+                        username="${googlecode.user}" \r
+                        password="${googlecode.password}" \r
+                        projectname="gitblit" \r
+                        filename="${distribution.zipfile}" \r
+                        targetfilename="gitblit-${gb.version}.zip"\r
+                        summary="Standalone, integrated Gitblit server v${gb.version}"\r
+                        labels="Featured, Type-Package, OpSys-All" />\r
+                       \r
+               <!-- Upload WAR file -->\r
+               <gcupload \r
+                    username="${googlecode.user}" \r
+                    password="${googlecode.password}" \r
+                    projectname="gitblit" \r
+                    filename="${distribution.warfile}" \r
+                    targetfilename="gitblit-${gb.version}.war"\r
+                    summary="Gitblit WAR v${gb.version} for your servlet container"\r
+                    labels="Featured, Type-Package, OpSys-All" />\r
        </target>\r
 \r
        \r
-       <!-- Publish site to hosting service -->\r
-       <!-- You must add ext/commons-net-1.4.0.jar to your     ANT classpath. -->\r
-       <target name="publishSite" description="Publish the Gitblit site to a webserver (requires ext/commons-net-1.4.0.jar)">\r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \r
+               Publish site to hosting service\r
+               You must add ext/commons-net-1.4.0.jar to your ANT classpath.\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="publishSite" depends="buildSite" description="Publish the Gitblit site to a webserver (requires ext/commons-net-1.4.0.jar)" >\r
+               \r
+               <echo>Uploading Gitblit ${gb.version} website</echo>\r
+               \r
                <ftp server="${ftp.server}"\r
                        userid="${ftp.user}"\r
                        password="${ftp.password}"\r
                        remotedir="${ftp.dir}"\r
                        passive="true"\r
                        verbose="yes">\r
-               <fileset dir="${basedir}/site" />\r
+               <fileset dir="${project.site.dir}" />\r
                </ftp>\r
        </target>\r
+\r
        \r
+       <!--\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \r
+               Compile from source, publish binaries, and build & deploy site\r
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r
+       -->\r
+       <target name="publishAll" depends="publishBinaries,publishSite">                \r
+               <!-- Cleanup -->\r
+               <delete dir="${project.build.dir}" />\r
+               <delete dir="${project.war.dir}" />\r
+               <delete dir="${project.deploy.dir}" />\r
+       </target>\r
 </project>
\ No newline at end of file
index e41f12ce2a44f6cbae3392b9200a130f4c5053e9..4b5d3af04861ae951eb406e13aa8bc23e9dc54ca 100644 (file)
@@ -2,9 +2,6 @@
 # Git Servlet Settings\r
 #\r
 \r
-# Allow push/pull over http/https with JGit servlet.\r
-git.enableGitServlet = true\r
-\r
 # Base folder for repositories\r
 # Changing this value requires a server restart.\r
 # Use forward slashes even on Windows!!\r
@@ -18,6 +15,9 @@ git.repositoriesFolder = git
 #      c:/gitrepos/libraries/myotherlibrary.git\r
 git.searchRepositoriesSubfolders = true\r
 \r
+# Allow push/pull over http/https with JGit servlet.\r
+git.enableGitServlet = true\r
+\r
 #\r
 # Authentication Settings\r
 #\r
@@ -30,11 +30,15 @@ web.authenticateViewPages = false
 # Changing this value requires a server restart.\r
 web.authenticateAdminPages = true\r
 \r
-# Either a simple user realm file to authenticate users\r
-# OR a fully qualified class name that implements the ILoginService interface.\r
+# Allow Gitblit to store a cookie in the user's browser for automatic\r
+# authentication.  The cookie is generated by the user service.\r
+web.allowCookieAuthentication = true\r
+\r
+# Either the path to a simple user properties file\r
+# OR a fully qualified class name that implements the IUserService interface.\r
 # Any custom implementation must have a public default constructor.\r
 # Changing this value requires a server restart.\r
-realm.realmFile = users.properties\r
+realm.userService = users.properties\r
 \r
 # How to store passwords.\r
 # Valid values are plain or md5.  Default is md5. \r
@@ -60,7 +64,7 @@ web.allowAdministration = true
 # Allow dyanamic zip downloads.   \r
 web.allowZipDownloads = true\r
 \r
-# Default number of entries to include in RSS/Atom Syndication links\r
+# Default number of entries to include in RSS Syndication links\r
 web.syndicationEntries = 25\r
 \r
 # This is the message display above the repositories table.\r
@@ -69,7 +73,9 @@ web.syndicationEntries = 25
 web.repositoriesMessage = gitblit\r
 \r
 # Use the client timezone when formatting dates.\r
-# This uses AJAX to determine the browser's timezone.\r
+# This uses AJAX to determine the browser's timezone and may require more\r
+# server overhead because a Wicket session is created.  All Gitblit pages\r
+# attempt to be stateless, if possible.\r
 # Changing this value requires a server restart.\r
 web.useClientTimezone = false\r
 \r
@@ -118,8 +124,10 @@ web.generateActivityGraph = true
 # Value must exceed 0 else default of 20 is used\r
 web.summaryCommitCount = 16\r
 \r
-# The number of tags/heads to display on the summary page\r
-# Value must exceed 0 else default of 5 is used\r
+# The number of tags/branches to display on the summary page.\r
+# -1 = all tags/branches\r
+# 0 = hide tags/branches\r
+# N = N tags/branches\r
 web.summaryRefsCount = 5\r
 \r
 # The number of items to show on a page before showing the first, prev, next\r
@@ -144,7 +152,7 @@ web.binaryExtensions = jar pdf tar.gz zip
 \r
 # Aggressive heap management will run the garbage collector on every generated\r
 # page.  This slows down page generation a little but improves heap consumption. \r
-web.aggressiveHeapManagement = true\r
+web.aggressiveHeapManagement = false\r
 \r
 # Run the webapp in debug mode\r
 # Changing this value requires a server restart.\r
@@ -171,7 +179,7 @@ regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://e
 server.tempFolder = temp\r
 \r
 #\r
-# Jetty Settings\r
+# Jetty Http/Https Server Settings\r
 #\r
 \r
 # Use Jetty NIO connectors.  If false, Jetty Socket connectors will be used.\r
index 093c8773a096835c5bf5f0ec977f35ce36954a6e..72a7b3459d67c10bdb1ebb75af39eab4405954fc 100644 (file)
@@ -1,2 +1,2 @@
 set JVM=C:\Program Files\Java\jdk1.6.0_26\r
-JavaService.exe -install gitblit "%JVM%\jre\bin\server\jvm.dll" -Xmx1024M -Djava.class.path=%CD%\gitblit.jar;"%JVM%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword dosomegit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD%
\ No newline at end of file
+JavaService.exe -install gitblit "%JVM%\jre\bin\server\jvm.dll" -Xmx1024M -Djava.class.path=%CD%\gitblit.jar;"%JVM%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword gitblit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD%
\ No newline at end of file
index 42d2ca5a1a056f4b43b128bacc44c1fe5b9584f5..0090c00a0a0dfa9c6105e880705b477aa2bf800f 100644 (file)
@@ -1,2 +1,2 @@
 set JVM=C:\Program Files\Java\jdk1.6.0_26\r
-JavaService64.exe -install gitblit "%JVM%\jre\bin\server\jvm.dll" -Djava.class.path=%CD%\gitblit.jar;"%JVM%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword dosomegit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD%
\ No newline at end of file
+JavaService64.exe -install gitblit "%JVM%\jre\bin\server\jvm.dll" -Djava.class.path=%CD%\gitblit.jar;"%JVM%\lib\tools.jar" -start com.gitblit.Launcher -params --storePassword gitblit -stop com.gitblit.Launcher -params --stop -out %CD%\logs\stdout.log -err %CD%\logs\stderr.log -current %CD%
\ No newline at end of file
index 7a2bc569c7fd33f96c96aff8a3ca498302a7a8b1..d09f5f8207bcea69e435fa9d8e9b2ec02948f0fb 100644 (file)
 ## Overview\r
-Gitblit is an open-source, integrated pure Java stack for managing, viewing, and serving [Git][git] repositories.\r
-Its designed primarily as a tool for small workgroups who want to host [Git][git] repositories on a Windows machine.  Having said that, it works equally well on any standard Linux distribution.\r
\r
-### Current Release\r
-\r
-%VERSION% ([go](http://gitblit.com/%GO%)|[war](http://gitblit.com/%WAR%)) based on [%JGIT%][jgit] &nbsp; (*%BUILDDATE%*)\r
-\r
-sources @ [Github][gitbltsrc]\r
-\r
-### Design Principles\r
-1. [Keep It Simple, Stupid](http://en.wikipedia.org/wiki/KISS_principle)\r
-2. Offer useful features for serving Git repositories.  If feature is complex, refer to #1.\r
-3. All dependencies must be retrievable from a publicly accessible [Maven](http://maven.apache.org) repository.<br/>This is to ensure authenticity of dependencies and to keep the Gitblit distribution svelte.  \r
+<a href="screenshots.html" title="Screenshots"><img class="overview" src="thumbs/00.png" alt="Screenshots" /></a>\r
 \r
-### Gitblit Features\r
-- JGit SmartHTTP servlet\r
-- Browser and git client authentication\r
-- Four repository access control configurations and a Read-Only flag\r
-    <ul class='noBullets'>\r
-    <li>![anonymous](blank.png) *Anonymous View, Clone & Push*</li>\r
-    <li>![push](lock_go_16x16.png) *Authenticated Push*</li>\r
-    <li>![clone](lock_pull_16x16.png) *Authenticated Clone & Push*</li>\r
-    <li>![view](shield_16x16.png) *Authenticated View, Clone & Push*</li>\r
-    <li>![freeze](cold_16x16.png) Freeze repository (i.e. deny push, make read-only)\r
-    </ul>\r
-- Gitweb inspired UI\r
-- Administrators may create, edit, rename, or delete repositories through the web UI\r
-- Administrators may create, edit, rename, or delete users through the web UI\r
-- Repository Owners may edit repositories through the web UI\r
-- Git-notes support\r
-- Branch metrics (uses Google Charts)\r
-- HEAD and branch RSS feeds\r
-- Blame annotations view\r
-- Dates can optionally be displayed using the browser's reported timezone\r
-- Display of Author and Committer email addresses can be disabled\r
-- Case-insensitive searching of commit messages, authors, or committers\r
-- Dynamic zip downloads feature\r
-- Markdown file view support\r
-- Syntax highlighting for popular source code types\r
-- Customizable regular expression substitution for commit messages (i.e. bug or code review link integration)\r
-- Single text file for users configuration\r
-- Optional utility pages\r
-    <ul class='noBullets'>\r
-    <li>![docs](book_16x16.png) Docs page which enumerates all Markdown files within a repository</li>\r
-    <li>![tickets](bug_16x16.png) Ticgit ticket pages *(based on last MIT release bf57b032 2009-01-27)*</li>\r
-    </ul>\r
+Gitblit is an open-source, pure Java stack for managing, viewing, and serving [Git][git] repositories.<br/>\r
+Its designed primarily as a tool for small workgroups who want to host centralized repositories.\r
 \r
-### Gitblit-Go Features\r
-- Out-of-the-box integrated stack requiring minimal configuration\r
-- Automatically generates a self-signed certificate for https communications\r
-- Single text file for server configuration\r
+Gitblit is available in two variations:\r
+<ul class='noBullets'>\r
+<li>*Gitblit GO* - a complete & integrated pure Java stack<p>\r
+    This is what you should download if you want to go from zero to Git in less than 5 mins.<br/>\r
+    Gitblit GO is like a mashup of Apache httpd, [Git][git], and Gitweb with simplified configuration and maintenance.<br/>\r
+    All dependencies are downloaded on first execution.<p>\r
+<li>*Gitblit WAR* - a traditional WAR distribution<p>\r
+    This is what you should download if you want to deploy Gitblit into your own servlet container (e.g. Tomcat, Jetty, etc).<br/>\r
+    All dependencies are bundled.\r
+</ul>\r
 \r
-### Limitations\r
-- HTTP/HTTPS are the only supported protocols\r
-- Access controls are not path-based, they are repository-based\r
-- Only Administrators can create, rename or delete repositories\r
+### Java Runtime Requirement\r
 \r
-### Caveats\r
-- Gitblit may eat your data.  Use at your own risk.\r
-- Gitblit may have security holes.  Patches welcome.  :)\r
+Gitblit requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK).\r
\r
+### Current Release\r
 \r
-### Todo List\r
-- Code documentation\r
-- Unit testing\r
-- Update Build.java to JGit 1.0.0, when its released\r
+%VERSION% ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)) based on [%JGIT%][jgit] with Blame backport &nbsp; (*%BUILDDATE%*)\r
 \r
-### Idea List\r
-- Consider clone remote repository feature\r
-- Stronger Ticgit read-only integration\r
-    - activity/timeline\r
-    - query feature with paging support\r
-    - change history\r
-- Ticgit write integration\r
-- Blob page improvements\r
-    - view images\r
-    - view other binary files (pdf, doc, etc)\r
-- Markdown editing feature\r
+issues & binaries @ [Google Code][googlecode]<br/>\r
+sources @ [Github][gitbltsrc]\r
 \r
 ### License\r
 Gitblit is distributed under the terms of the [Apache Software Foundation license, version 2.0][apachelicense]\r
 \r
-### Inspirations\r
-- [Gitweb](http://www.git-scm.com)\r
-- [Fossil](http://www.fossil-scm.org) \r
-\r
-## Architecture\r
-\r
-![block diagram](architecture.png "Gitblit Architecture")\r
-\r
-### Bundled Dependencies\r
-The following dependencies are bundled with Gitblit.\r
-\r
-- [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)\r
-- [JavaService](http://forge.ow2.org/projects/javaservice) (BSD and LGPL)\r
-- magnifying glass search icon courtesy of [Gnome](http://gnome.org) (Creative Commons CC-BY)\r
-- modified Git logo originally designed by [Henrik Nyh](http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon)\r
-- other icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons) (Creative Commons CC-BY)\r
-\r
-### Downloaded Dependencies\r
-The following dependencies are automatically downloaded by Gitblit-Go (or already bundled with the WAR) from the Apache Maven repository and from the Eclipse Maven repository when Gitblit is launched for the first time.\r
-\r
-- [JGit][jgit] (EDL 1.0)\r
-- [Wicket](http://wicket.apache.org) (Apache 2.0)\r
-- [WicketStuff GoogleCharts](https://github.com/wicketstuff/core/wiki/GoogleCharts) (Apache 2.0)\r
-- [MarkdownPapers](http://markdown.tautua.org) (Apache 2.0)\r
-- [Jetty](http://eclipse.org/jetty) (Apache 2.0, EPL 1.0)\r
-- [SLF4J](http://www.slf4j.org) (MIT/X11)\r
-- [Log4j](http://logging.apache.org/log4j) (Apache 2.0) \r
-- [JCommander](http://jcommander.org) (Apache 2.0)\r
-- [BouncyCastle](http://www.bouncycastle.org) (MIT/X11)\r
-- [JSch - Java Secure Channel](http://www.jcraft.com/jsch) (BSD)\r
-- [Rome](http://rome.dev.java.net) (Apache 1.1)\r
-- [jdom](http://www.jdom.org) (Apache-style JDOM license)\r
-\r
-### Other Build Dependencies\r
-- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)\r
-- [JUnit](http://junit.org) (Common Public License)\r
-- [commons-net](http://commons.apache.org/net) (Apache 2.0)\r
-\r
-## Building from Source\r
-[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.\r
-\r
-Additionally, [Google CodePro AnalytiX](http://code.google.com/javadevtools), [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http://findbugs.sourceforge.net), and [EclEmma](http://www.eclemma.org) are recommended development tools.\r
-\r
-1. Clone the git repository from [Github][gitbltsrc].\r
-2. Import the gitblit project into your Eclipse workspace.<br/>\r
-*There will be lots of build errors.*\r
-3. Using Ant, execute the `build.xml` script in the project root.<br/>\r
-*This will download all necessary build dependencies and will also generate the Keys class for accessing settings.*\r
-4. Select your gitblit project root and **Refresh** the project, this should correct all build problems.\r
-5. Using JUnit, execute the `com.gitblit.tests.GitBlitSuite` test suite.<br/>\r
-*This will clone some repositories from the web and run through the unit tests.*\r
-5. Review the settings in `gitblit.properties` in your project root.\r
-    - By default, the *git.repositoriesFolder* points to the repositories cloned by the test suite.<br/>\r
-    - If running on Linux you may have to change the served port(s) to > 1024 unless you are developing as the root user. \r
-6. Execute the *com.gitblit.Launcher* class to start Gitblit.\r
-\r
-\r
-## Contributing\r
-Patches welcome in any form.\r
-\r
-Contributions must be your own original work and must licensed under the [Apache License, Version 2.0][apachelicense], the same license used by Gitblit.\r
-\r
 [jgit]: http://eclipse.org/jgit "Eclipse JGit Site"\r
 [git]: http://git-scm.com "Official Git Site"\r
-[gitbltsrc]: http://somewhere.com "gitblit git repository"\r
+[gitbltsrc]: http://github.com/gitblit "gitblit git repository"\r
+[googlecode]: http://code.google.com/p/gitblit "gitblit project management"\r
 [apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"
\ No newline at end of file
diff --git a/docs/00_setup.mkd b/docs/00_setup.mkd
deleted file mode 100644 (file)
index 02816e7..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-## Gitblit-Go Setup and Configuration\r
-\r
-1. Download and unzip [Gitblit-Go %VERSION%](http://gitblit.com/%GO%).<br/>\r
-*Its best to eliminate spaces in the path name as that can cause troubleshooting headaches.* \r
-2. The server itself is configured through a simple text file.<br/>\r
-Open `gitblit.properties` in your favorite text editor and make sure to review and set:\r
-    - *git.repositoryFolder*\r
-    - *server.tempFolder*\r
-    - *server.httpBindInterface* and *server.httpsBindInterface*<br/>\r
-**NOTE:** Consider using **https** exclusively because passwords for authentication are transmitted as clear text!     \r
-    - *server.storePassword*<br/>\r
-**NOTE:** If you manually generate an ssl certificate, the certificate password AND the keystore password must match!     \r
-3. Execute `gitblit.cmd` or `java -jar gitblit.jar` from a command-line\r
-4. Wait a minute or two while all dependencies are downloaded and your self-signed certificate is generated.\r
-5. Open your browser to <http://localhost> or <https://localhost> depending on your chosen configuration.\r
-6. Click the *Login* link and enter the default administrator credentials: **admin / admin**<br/>\r
-**NOTE:** Make sure to change the administrator username and/or password!! \r
-\r
-### Administering Repositories\r
-Repositories can be created, edited, renamed, and deleted through the web UI.  They may also be created, edited, and deleted from the command-line using real [Git](http://git-scm.com) or your favorite file manager and text editor.\r
-\r
-All repository settings are stored within the repository `.git/config` file under the *gitblit* section.\r
-\r
-    [gitblit]\r
-           description = master repository\r
-           owner = james\r
-           useTickets = false\r
-           useDocs = true\r
-           showRemoteBranches = false\r
-           accessRestriction = clone\r
-           isFrozen = false\r
-           showReadme = false\r
-           \r
-#### Repository Names\r
-Repository names must be unique and are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS.  The name must be composed of letters, digits, or `/ _ - .`<br/>\r
-Whitespace is illegal.\r
-\r
-Repositories can be grouped within subfolders.  e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git*\r
-\r
-All created repositories are *bare* and will automatically have *.git* appended to the name at creation time, if not already specified. \r
-\r
-#### Repository Owner\r
-The *Repository Owner* has the special permission of being able to edit a repository through the web UI.  The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.\r
-\r
-### Administering Users\r
-All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`.<br/>\r
-The format of `users.properties` follows Jetty's convention for HashRealms:\r
-\r
-    username,password,role1,role2,role3...\r
-\r
-#### Usernames\r
-Usernames must be unique and are case-insensitive.<br/>\r
-Whitespace is illegal.\r
-\r
-#### Passwords\r
-User passwords are CASE-SENSITIVE and may be *plain* or *md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*).\r
-\r
-#### User Roles\r
-There is only one actual *role* in Gitblit and that is *#admin* which grants administrative powers to that user.  Administrators automatically have access to all repositories.  All other *roles* are repository names.  If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction.  This is how users are granted access to a restricted repository.\r
-\r
-### Creating your own Self-Signed Certificate\r
-\r
-Review the contents of the `makekeystore.cmd` or `makekeystore_jdk.cmd` script and execute it.<br/>\r
-**NOTE:** If you manually generate an ssl certificate, the certificate password AND the keystore password must match!\r
-\r
-### Running as a Service\r
-Review the contents of the `installService.cmd` or `installService64.cmd`, as appropriate for your installed Java Virtual Machine.<br/>\r
-Set the *JVM* variable in the script to the location of your Java Virtual Machine, add any necessary start parameters, and execute the script.\r
-\r
-#### Command-Line Parameters\r
-    --tempFolder           Server temp folder\r
-       --repositoriesFolder   Git Repositories Folder\r
-    --realmFile            Users Realm Hash File\r
-    --useNio               Use NIO Connector else use Socket Connector.\r
-    --httpPort             HTTP port for to serve. (port <= 0 will disable this connector)\r
-    --httpsPort            HTTPS port to serve.  (port <= 0 will disable this connector)\r
-    --storePassword        Password for SSL (https) keystore.\r
-    --shutdownPort         Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)\r
-    \r
-**Example**\r
-\r
-    java -jar gitblit.jar --realmFile c:\myrealm.txt --storePassword something\r
-    \r
-## Client Setup and Configuration\r
-### Https with Self-Signed Certificates\r
-You must tell Git not to verify the self-signed certificate in order to perform any remote Git operations.\r
-\r
-- Eclipse/EGit\r
-    1. Window->Preferences->Team->Git->Configuration\r
-    2. Click the *New Entry* button\r
-    3. <pre>Key = *http.sslVerify*       \r
-       Value = *false*</pre>\r
-- Command-line Git ([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))\r
-    <pre>git config --global --bool --add http.sslVerify false</pre>\r
-\r
-### Cloning an Access Restricted Repository \r
-- Eclipse/Egit<br/>Nothing special to configure, EGit figures out everything.\r
-    <pre>https://yourserver/git/your/repository</pre>\r
-- Command-line Git<br/>*My testing indicates that your username must be embedded in the url.  YMMV.*\r
-    <pre>https://username@yourserver/git/your/repository</pre>\r
-           
\ No newline at end of file
diff --git a/docs/01_faq.mkd b/docs/01_faq.mkd
deleted file mode 100644 (file)
index 552c89d..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-## Troubleshooting\r
-\r
-### Eclipse/Egit/Git complains that it "can't open upload pack"?\r
-There are a few ways this can occur:\r
-\r
-1. You are using https with a self-signed certificate and you **did not** configure *http.sslVerify=false*\r
-    1. Window->Preferences->Team->Git->Configuration\r
-    2. Click the *New Entry* button\r
-    3. <pre>Key = *http.sslVerify*       \r
-       Value = *false*</pre>\r
-2. The repository is clone-restricted and you don't have access.\r
-3. The repository is clone-restricted and your password changed.\r
-4. A regression in Gitblit.  :(\r
-\r
-### Why can't I access Gitblit-Go from another machine?\r
-Please check *server.httpBindInterface* and *server.httpsBindInterface* in `gitblit.properties`.\r
-\r
-### How do I run Gitblit-Go on port 80 or 443 in Linux?\r
-Linux requires root permissions to serve on ports < 1024.<br/>\r
-Run the server as *root* (security concern) or change the ports you are serving to 8080 (http) and/or 8443 (https). \r
-\r
-## General Interest Questions\r
-\r
-### Gitblit?  What kind of name is that?\r
-It's a phonetic play on [bitblt][bitblt] which is an image processing operation meaning *bit-block transfer*.\r
-\r
-### Why use Gitblit?\r
-It's a small tool that allows you to easily manage shared repositories and doesn't require alot of setup or git kung-foo.\r
-\r
-### Who is the target user for Gitblit?\r
-Small workgroups that require centralized repositories.\r
-\r
-Gitblit is not meant to be a social coding resource like [Github](http://github.com) or [Bitbucket](http://bitbucket.com) with 100s or 1000s of users.  Gitblit is designed to fulfill the same function as your centralized Subversion or CVS server.\r
-\r
-### Why does Gitblit exist?\r
-As a Java developer I prefer that as much of my tooling as possible is Java.<br/>\r
-Originally, I was going to use [Mercurial](http://mercurial.selenic.com) but...\r
-\r
-- MercurialEclipse [shells to Python, writes to System.out, and captures System.in](http://mercurial.808500.n3.nabble.com/Hg4J-Mercurial-pure-Java-library-tp2693090p2694555.html)<br/>\r
-Parsing command-line output is fragile and suboptimal.<br/>Unfortunately this is necessary because Mercurial is an application, not a library.\r
-- Mercurial HTTP/HTTPS needs to run as CGI through Apache/IIS/etc, as mod_python through Apache, or served with a built-in http server.<br/>\r
-This requires setup and maintenance of multiple, mixed 3rd party components.\r
-\r
-Gitblit eliminates all that complication with its 100% Java stack and simple single configuration file.\r
-\r
-### Do I need real Git?\r
-No.  Gitblit is based on [JGit][jgit] which is a pure Java implementation of the [Git version control system][git].<br/>\r
-Everything you need for Gitblit is either in the zip distribution file or automatically downloaded on execution.\r
-\r
-### Can I run Gitblit in conjunction with my existing Git tooling?\r
-Yes.\r
-\r
-### Do I need a JDK or can I use a JRE?\r
-Gitblit will run just fine with a JRE.  Gitblit can optionally use `keytool` from the JDK to generate self-signed certificates, but normally Gitblit uses [BouncyCastle][bouncycastle] for that need.\r
-\r
-### Does Gitblit use a database to store its data?\r
-No.  Gitblit stores its repository configuration information within the `.git/config` file and its user information in `users.properties` or whatever filename is configured in `gitblit.properties`.\r
-\r
-### Can I manually edit users.properties, gitblit.properties, or .git/config?\r
-Yes.  You can manually manipulate all of them and (most) changes will be immediately available to Gitblit.<br/>Exceptions to this are noted in `gitblit.properties`.\r
-\r
-*NOTE:* Care must be taken to preserve the relationship between user roles and repository names.<br/>Please see the [setup](/setup.html) page for details.\r
-\r
-### Can I restrict access to paths within a repository?\r
-No.  Access restrictions apply to the repository as a whole.\r
-\r
-Gitblit's simple authentication and authorization mechanism can be used to facilitate one or more of the [workflows outlined here](http://progit.org/book/ch5-1.html).  Should you require more fine-grained access controls you might consider using [gitolite](https://github.com/sitaramc/gitolite).\r
-\r
-### Can I authenticate users against XYZ?\r
-Yes.  The login service is pluggable.  You may write your own authentication module by implementing the *ILoginService* interface.  Set the fully qualified classname as the *realm.realmFile* property.\r
-\r
-### Why doesn't Gitblit support SSH?\r
-Gitblit could integrate [Apache Mina][mina] to provide SSH access.  However, doing so violates Gitblit's first design principle: [KISS](http://en.wikipedia.org/wiki/KISS_principle).<br/>\r
-SSH support requires creating, exchanging, and managing SSH keys (arguably not more complicated than managing users).  While this is possible, JGit's SmartHTTP implementation is a simpler and universal transport mechanism.\r
-\r
-You might consider running [Gerrit](http://gerrit.googlecode.org) which does integrate [Apache Mina][mina] and supports SSH or you might consider serving [Git][git] on Linux which would offer real SSH support and also allow use of [many other compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools).\r
-\r
-### What types of Search does Gitblit support?\r
-Gitblit supports case-insensitive searches of *commit message* (default), *author*, and *committer*.<br/>\r
-\r
-To search by *author* or *committer* use the following syntax in the search box:\r
-\r
-    author: james\r
-    committer: james\r
-    \r
-Alternatively, you could enable the search type dropdown list in your `gitblit.properties` file.\r
-\r
-### Can Gitblit be translated?\r
-\r
-Yes.  Most messages are localized to a standard Java properties file.\r
-\r
-[bitblt]: http://en.wikipedia.org/wiki/Bit_blit "Wikipedia Bitblt"\r
-[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"\r
-[git]: http://git-scm.com "Official Git Site"\r
-[mina]: http://mina.apache.org "Apache Mina"\r
-[bouncycastle]: http://bouncycastle.org "The Legion of the Bouncy Castle"
\ No newline at end of file
diff --git a/docs/01_features.mkd b/docs/01_features.mkd
new file mode 100644 (file)
index 0000000..e31d959
--- /dev/null
@@ -0,0 +1,64 @@
+## Gitblit Features\r
+- JGit SmartHTTP servlet\r
+- Browser and git client authentication\r
+- Four repository access control configurations with a Read-Only control flag\r
+    <ul class='noBullets'>\r
+    <li>![anonymous](blank.png) *Anonymous View, Clone & Push*</li>\r
+    <li>![push](lock_go_16x16.png) *Authenticated Push*</li>\r
+    <li>![clone](lock_pull_16x16.png) *Authenticated Clone & Push*</li>\r
+    <li>![view](shield_16x16.png) *Authenticated View, Clone & Push*</li>\r
+    <li>![freeze](cold_16x16.png) Freeze repository (i.e. deny push, make read-only)\r
+    </ul>\r
+- Gitweb inspired web UI\r
+- Administrators may create, edit, rename, or delete repositories through the web UI\r
+- Administrators may create, edit, rename, or delete users through the web UI\r
+- Repository Owners may edit repositories through the web UI\r
+- Git-notes support\r
+- Branch metrics (uses Google Charts)\r
+- HEAD and Branch RSS feeds\r
+- Blame annotations view\r
+- Dates can optionally be displayed using the browser's reported timezone\r
+- Display of Author and Committer email addresses can be disabled\r
+- Case-insensitive searching of commit messages, authors, or committers\r
+- Dynamic zip downloads feature\r
+- Markdown file view support\r
+- Syntax highlighting for popular source code types\r
+- Customizable regular expression substitution for commit messages (i.e. bug or code review link integration)\r
+- Single text file for users configuration\r
+- Optional utility pages\r
+    <ul class='noBullets'>\r
+    <li>![docs](book_16x16.png) Docs page which enumerates all Markdown files within a repository</li>\r
+    <li>![tickets](bug_16x16.png) Ticgit ticket pages *(based on last MIT release bf57b032 2009-01-27)*</li>\r
+    </ul>\r
+\r
+## Gitblit GO Features\r
+- Out-of-the-box integrated stack requiring minimal configuration\r
+- Automatically generates a self-signed certificate for https communications\r
+- Single text file for configuring server and gitblit\r
+\r
+## Limitations\r
+- HTTP/HTTPS are the only supported protocols\r
+- Access controls are not path-based, they are repository-based\r
+- Only Administrators can create, rename or delete repositories\r
+- Only Administrators can create, modify or delete users\r
+\r
+### Caveats\r
+- Gitblit may eat your data.  Use at your own risk.\r
+- Gitblit may have security holes.  Patches welcome.  :)\r
+\r
+## Todo List\r
+- Code documentation\r
+- Unit testing\r
+- Update to JGit 1.0.0 when JGit team provides Maven artifacts\r
+\r
+### Under Consideration\r
+- Clone remote repository feature\r
+- Blob page improvements\r
+    - view images\r
+    - view other binary files (pdf, doc, etc)\r
+- Markdown editing feature\r
+- Stronger Ticgit read-only integration\r
+    - activity/timeline\r
+    - query feature with paging support\r
+    - change history\r
+- Ticgit write integration
\ No newline at end of file
diff --git a/docs/01_releases.mkd b/docs/01_releases.mkd
deleted file mode 100644 (file)
index 7c0c204..0000000
+++ /dev/null
@@ -1 +0,0 @@
-## Release History\r
diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd
new file mode 100644 (file)
index 0000000..8ad7c5e
--- /dev/null
@@ -0,0 +1,129 @@
+## Gitblit WAR Setup\r
+\r
+1. Download [Gitblit WAR %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) to the webapps folder of your servlet container.<br/>\r
+2. You may have to manually extract the WAR (zip file) to a folder within your webapps folder.  Manual extraction depends on if your servlet container is configured to automatically deploy WAR files.\r
+3. Copy the `WEB-INF/users.properties` file to a location outside the webapps folder but accessible by your servlet container.  \r
+4. The Gitblit webapp is configured through its `web.xml` file.<br/>\r
+Open `web.xml` in your favorite text editor and make sure to review and set:\r
+    - &lt;context-parameter&gt; *git.repositoryFolder* (set the full path to your repositories folder)\r
+    - &lt;context-parameter&gt; *realm.userService* (set the full path to `users.properties`)\r
+5. You may have to restart your servlet container. \r
+6. Open your browser to <http://localhost/gitblit> or whatever the url should be.\r
+7. Click the *Login* link and enter the default administrator credentials: **admin / admin**<br/>\r
+**NOTE:** Make sure to change the administrator username and/or password!! \r
+\r
+## Gitblit GO Setup\r
+\r
+1. Download and unzip [Gitblit GO %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%GO%).<br/>\r
+*Its best to eliminate spaces in the path name.* \r
+2. The server itself is configured through a simple text file.<br/>\r
+Open `gitblit.properties` in your favorite text editor and make sure to review and set:\r
+    - *git.repositoryFolder* (path my be relative or absolute)\r
+    - *server.tempFolder* (path my be relative or absolute)\r
+    - *server.httpBindInterface* and *server.httpsBindInterface*<br/>\r
+**NOTE:** Consider using **https** exclusively because passwords for authentication are transmitted as clear text!     \r
+    - *server.storePassword*<br/>\r
+**NOTE:** If you manually generate an ssl certificate, the certificate password AND the keystore password must match!     \r
+3. Execute `gitblit.cmd` or `java -jar gitblit.jar` from a command-line\r
+4. Wait a minute or two while all dependencies are downloaded and your self-signed certificate is generated.\r
+5. Open your browser to <http://localhost> or <https://localhost> depending on your chosen configuration.\r
+6. Click the *Login* link and enter the default administrator credentials: **admin / admin**<br/>\r
+**NOTE:** Make sure to change the administrator username and/or password!! \r
+\r
+### Creating your own Self-Signed Certificate\r
+Gitblit GO automatically generates an ssl certificate for you that contains generic, non-personalized information.\r
+\r
+Should you want to include more personal or server-specific information in your self-signed certificate you will have to generate a new one.\r
\r
+Review the contents of the `makekeystore.cmd` or `makekeystore_jdk.cmd` script and execute it.<br/>\r
+**NOTE:** If you manually generate an ssl certificate, the certificate password AND the keystore password must match!\r
+\r
+### Running as a Windows Service\r
+Review the contents of the `installService.cmd` or `installService64.cmd`, as appropriate for your installed Java Virtual Machine.<br/>\r
+Set the *JVM* variable in the script to the location of your Java Virtual Machine, add any necessary start parameters, and execute the script.\r
+\r
+#### Command-Line Parameters\r
+Command-Line parameters override the values in `gitblit.properties` at runtime.\r
+\r
+       --repositoriesFolder   Git Repositories Folder\r
+    --userService          Authentication and Authorization Service (filename or fully qualified classname)\r
+    --useNio               Use NIO Connector else use Socket Connector.\r
+    --httpPort             HTTP port for to serve. (port <= 0 will disable this connector)\r
+    --httpsPort            HTTPS port to serve.  (port <= 0 will disable this connector)\r
+    --storePassword        Password for SSL (https) keystore.\r
+    --shutdownPort         Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)\r
+    --tempFolder           Folder for server to extract built-in webapp\r
+    \r
+**Example**\r
+\r
+    java -jar gitblit.jar --userService c:\myrealm.properties --storePassword something\r
+    \r
+## Gitblit Configuration\r
+\r
+### Administering Repositories\r
+Repositories can be created, edited, renamed, and deleted through the web UI.  They may also be created, edited, and deleted from the command-line using real [Git](http://git-scm.com) or your favorite file manager and text editor.\r
+\r
+All repository settings are stored within the repository `.git/config` file under the *gitblit* section.\r
+\r
+    [gitblit]\r
+           description = master repository\r
+           owner = james\r
+           useTickets = false\r
+           useDocs = true\r
+           showRemoteBranches = false\r
+           accessRestriction = clone\r
+           isFrozen = false\r
+           showReadme = false\r
+           \r
+#### Repository Names\r
+Repository names must be unique and are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS.  The name must be composed of letters, digits, or `/ _ - .`<br/>\r
+Whitespace is illegal.\r
+\r
+Repositories can be grouped within subfolders.  e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git*\r
+\r
+All repositories created with Gitblit are *bare* and will automatically have *.git* appended to the name at creation time, if not already specified. \r
+\r
+#### Repository Owner\r
+The *Repository Owner* has the special permission of being able to edit a repository through the web UI.  The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.\r
+\r
+### Administering Users\r
+All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`.<br/>\r
+The format of `users.properties` follows Jetty's convention for HashRealms:\r
+\r
+    username,password,role1,role2,role3...\r
+\r
+#### Usernames\r
+Usernames must be unique and are case-insensitive.<br/>\r
+Whitespace is illegal.\r
+\r
+#### Passwords\r
+User passwords are CASE-SENSITIVE and may be *plain* or *md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*).\r
+\r
+#### User Roles\r
+There is only one actual *role* in Gitblit and that is *#admin* which grants administrative powers to that user.  Administrators automatically have access to all repositories.  All other *roles* are repository names.  If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction.  This is how users are granted access to a restricted repository.\r
+\r
+## Authentication and Authorization Customization\r
+Instead of maintaining a `users.properties` file, you may want to integrate Gitblit into an existing environment.\r
+\r
+You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting.<br/>\r
+\r
+Your user service class must be on Gitblit's classpath and must have a public default constructor. \r
+\r
+## Client Setup and Configuration\r
+### Https with Self-Signed Certificates\r
+You must tell Git not to verify the self-signed certificate in order to perform any remote Git operations.\r
+\r
+- Eclipse/EGit\r
+    1. Window->Preferences->Team->Git->Configuration\r
+    2. Click the *New Entry* button\r
+    3. <pre>Key = *http.sslVerify*       \r
+       Value = *false*</pre>\r
+- Command-line Git ([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))\r
+    <pre>git config --global --bool --add http.sslVerify false</pre>\r
+\r
+### Cloning an Access Restricted Repository \r
+- Eclipse/Egit<br/>Nothing special to configure, EGit figures out everything.\r
+    <pre>https://yourserver/git/your/repository</pre>\r
+- Command-line Git<br/>*My testing indicates that your username must be embedded in the url.  YMMV.*\r
+    <pre>https://username@yourserver/git/your/repository</pre>\r
+           
\ No newline at end of file
diff --git a/docs/02_faq.mkd b/docs/02_faq.mkd
new file mode 100644 (file)
index 0000000..7958f30
--- /dev/null
@@ -0,0 +1,109 @@
+## Troubleshooting\r
+\r
+### Eclipse/Egit/Git complains that it "can't open upload pack"?\r
+There are a few ways this can occur:\r
+\r
+1. You are using https with a self-signed certificate and you **did not** configure *http.sslVerify=false*\r
+    1. Window->Preferences->Team->Git->Configuration\r
+    2. Click the *New Entry* button\r
+    3. <pre>Key = *http.sslVerify*       \r
+       Value = *false*</pre>\r
+2. The repository is clone-restricted and you don't have access.\r
+3. The repository is clone-restricted and your password changed.\r
+4. A regression in Gitblit.  :(\r
+\r
+### Why can't I access Gitblit GO from another machine?\r
+Please check *server.httpBindInterface* and *server.httpsBindInterface* in `gitblit.properties`, you may be binding only to localhost.\r
+\r
+And of course ensure that any firewall you may have running either has an exception for your ports or for the running process.\r
+\r
+### How do I run Gitblit GO on port 80 or 443 in Linux?\r
+Linux requires root permissions to serve on ports < 1024.<br/>\r
+Run the server as *root* (security concern) or change the ports you are serving to 8080 (http) and/or 8443 (https). \r
+\r
+### Gitblit GO does not list my repositories?!\r
+Confirm that the value *git.repositoriesFolder* in `gitblit.properties` actually points to your repositories folder.\r
+\r
+### Gitblit WAR does not list my repositories?!\r
+Confirm that the &lt;context-param&gt; *git.repositoriesFolder* value in your `web.xml` file actually points to your repositories folder.\r
+\r
+### Gitblit WAR will not authenticate any users?!\r
+Confirm that the &lt;context-param&gt; *realm.userService* value in your `web.xml` file actually points to a `users.properties` file.\r
+\r
+## General Interest Questions\r
+\r
+### Gitblit?  What kind of name is that?\r
+It's a phonetic play on [bitblt][bitblt] which is an image processing operation meaning *bit-block transfer*.\r
+\r
+### Why use Gitblit?\r
+It's a small tool that allows you to easily manage shared repositories and doesn't require alot of setup or git kung-foo.\r
+\r
+### Who is the target user for Gitblit?\r
+Small workgroups that require centralized repositories.\r
+\r
+Gitblit is not meant to be a social coding resource like [Github](http://github.com) or [Bitbucket](http://bitbucket.com) with 100s or 1000s of users.  Gitblit is designed to fulfill the same function as your centralized Subversion or CVS server.\r
+\r
+### Why does Gitblit exist when there is Git and Gitweb?\r
+As a Java developer I prefer that as much of my tooling as possible is Java.<br/>\r
+Originally, I was going to use [Mercurial](http://mercurial.selenic.com) but...\r
+\r
+- MercurialEclipse [shells to Python, writes to System.out, and captures System.in](http://mercurial.808500.n3.nabble.com/Hg4J-Mercurial-pure-Java-library-tp2693090p2694555.html)<br/>\r
+Parsing command-line output is fragile and suboptimal.<br/>Unfortunately this is necessary because Mercurial is an application, not a library.\r
+- Mercurial HTTP/HTTPS needs to run as CGI through Apache/IIS/etc, as mod_python through Apache, or served with a built-in http server.<br/>\r
+This requires setup and maintenance of multiple, mixed 3rd party components.\r
+\r
+Gitblit eliminates all that complication with its 100% Java stack and simple single configuration file.\r
+\r
+Additionally, Git and Gitweb do not offer repository creation or user management.\r
+\r
+### Do I need real Git?\r
+No.  Gitblit is based on [JGit][jgit] which is a pure Java implementation of the [Git version control system][git].<br/>\r
+Everything you need for Gitblit (except Java) is either bundled in the distribution file or automatically downloaded on execution.\r
+\r
+### Can I run Gitblit in conjunction with my existing Git tooling?\r
+Yes.\r
+\r
+### Do I need a JDK or can I use a JRE?\r
+Gitblit will run just fine with a JRE.  Gitblit can optionally use `keytool` from the JDK to generate self-signed certificates, but normally Gitblit uses [BouncyCastle][bouncycastle] for that need.\r
+\r
+### Does Gitblit use a database to store its data?\r
+No.  Gitblit stores its repository configuration information within the `.git/config` file and its user information in `users.properties` or whatever filename is configured in `gitblit.properties`.\r
+\r
+### Can I manually edit users.properties, gitblit.properties, or .git/config?\r
+Yes.  You can manually manipulate all of them and (most) changes will be immediately available to Gitblit.<br/>Exceptions to this are noted in `gitblit.properties`.\r
+\r
+*NOTE:* Care must be taken to preserve the relationship between user roles and repository names.<br/>Please see the *User Roles* section of the [setup](/setup.html) page for details.\r
+\r
+### Can I restrict access to paths within a repository?\r
+No.  Access restrictions apply to the repository as a whole.\r
+\r
+Gitblit's simple authentication and authorization mechanism can be used to facilitate one or more of the [workflows outlined here](http://progit.org/book/ch5-1.html).  Should you require more fine-grained access controls you might consider using [gitolite](https://github.com/sitaramc/gitolite).\r
+\r
+### Can I authenticate users against XYZ?\r
+Yes.  The user service is pluggable.  You may write your own user service by implementing the *com.gitblit.IUserService* interface.  Set the fully qualified classname as the *realm.userService* property.\r
+\r
+### Why doesn't Gitblit support SSH?\r
+Gitblit could integrate [Apache Mina][mina] to provide SSH access.  However, doing so violates Gitblit's first design principle: [KISS](http://en.wikipedia.org/wiki/KISS_principle).<br/>\r
+SSH support requires creating, exchanging, and managing SSH keys (arguably not more complicated than managing users).  While this is possible, JGit's SmartHTTP implementation is a simpler and universal transport mechanism.\r
+\r
+You might consider running [Gerrit](http://gerrit.googlecode.org) which does integrate [Apache Mina][mina] and supports SSH or you might consider serving [Git][git] on Linux which would offer real SSH support and also allow use of [many other compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools).\r
+\r
+### What types of Search does Gitblit support?\r
+Gitblit supports case-insensitive searches of *commit message* (default), *author*, and *committer*.<br/>\r
+\r
+To search by *author* or *committer* use the following syntax in the search box:\r
+\r
+    author: james\r
+    committer: james\r
+    \r
+Alternatively, you could enable the search type dropdown list in your `gitblit.properties` file.\r
+\r
+### Can Gitblit be translated?\r
+\r
+Yes.  Most messages are localized to a standard Java properties file.\r
+\r
+[bitblt]: http://en.wikipedia.org/wiki/Bit_blit "Wikipedia Bitblt"\r
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"\r
+[git]: http://git-scm.com "Official Git Site"\r
+[mina]: http://mina.apache.org "Apache Mina"\r
+[bouncycastle]: http://bouncycastle.org "The Legion of the Bouncy Castle"
\ No newline at end of file
diff --git a/docs/02_properties.mkd b/docs/02_properties.mkd
deleted file mode 100644 (file)
index 3a1dec1..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-## gitblit.properties\r
-<pre class='prettyprint'>\r
-%PROPERTIES%\r
-</pre>
\ No newline at end of file
diff --git a/docs/03_properties.mkd b/docs/03_properties.mkd
new file mode 100644 (file)
index 0000000..3a1dec1
--- /dev/null
@@ -0,0 +1,4 @@
+## gitblit.properties\r
+<pre class='prettyprint'>\r
+%PROPERTIES%\r
+</pre>
\ No newline at end of file
diff --git a/docs/04_design.mkd b/docs/04_design.mkd
new file mode 100644 (file)
index 0000000..244b35e
--- /dev/null
@@ -0,0 +1,69 @@
+## Design Principles\r
+1. [Keep It Simple, Stupid](http://en.wikipedia.org/wiki/KISS_principle)\r
+2. Offer useful features for serving Git repositories.  If feature is complex, refer to #1.\r
+3. All dependencies must be retrievable from a publicly accessible [Maven](http://maven.apache.org) repository.<br/>This is to ensure authenticity of dependencies, to keep the Gitblit GO distribution svelte, and to automate the setup of developer environments.  \r
+\r
+## Architecture\r
+\r
+![block diagram](architecture.png "Gitblit Architecture")\r
+\r
+### Bundled Dependencies\r
+The following dependencies are bundled with Gitblit.\r
+\r
+- [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)\r
+- [JavaService](http://forge.ow2.org/projects/javaservice) (BSD and LGPL)\r
+- magnifying glass search icon courtesy of [Gnome](http://gnome.org) (Creative Commons CC-BY)\r
+- modified Git logo originally designed by [Henrik Nyh](http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon)\r
+- other icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons) (Creative Commons CC-BY)\r
+\r
+### Downloaded Dependencies\r
+The following dependencies are automatically downloaded by Gitblit GO (or already bundled with the WAR) from the Apache Maven repository and from the Eclipse Maven repository when Gitblit is launched for the first time.\r
+\r
+- [JGit][jgit] (EDL 1.0)\r
+- [Wicket](http://wicket.apache.org) (Apache 2.0)\r
+- [WicketStuff GoogleCharts](https://github.com/wicketstuff/core/wiki/GoogleCharts) (Apache 2.0)\r
+- [MarkdownPapers](http://markdown.tautua.org) (Apache 2.0)\r
+- [Jetty](http://eclipse.org/jetty) (Apache 2.0, EPL 1.0)\r
+- [SLF4J](http://www.slf4j.org) (MIT/X11)\r
+- [Log4j](http://logging.apache.org/log4j) (Apache 2.0) \r
+- [JCommander](http://jcommander.org) (Apache 2.0)\r
+- [BouncyCastle](http://www.bouncycastle.org) (MIT/X11)\r
+- [JSch - Java Secure Channel](http://www.jcraft.com/jsch) (BSD)\r
+- [Rome](http://rome.dev.java.net) (Apache 1.1)\r
+- [jdom](http://www.jdom.org) (Apache-style JDOM license)\r
+\r
+### Other Build Dependencies\r
+- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)\r
+- [JUnit](http://junit.org) (Common Public License)\r
+- [commons-net](http://commons.apache.org/net) (Apache 2.0)\r
+- [ant-googlecode](http://code.google.com/p/ant-googlecode) (New BSD)\r
+\r
+## Building from Source\r
+[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.\r
+\r
+Additionally, [Google CodePro AnalytiX](http://code.google.com/javadevtools), [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http://findbugs.sourceforge.net), and [EclEmma](http://www.eclemma.org) are recommended development tools.\r
+\r
+1. Clone the git repository from [Github][gitbltsrc].\r
+2. Import the gitblit project into your Eclipse workspace.<br/>\r
+*There will be lots of build errors.*\r
+3. Using Ant, execute the `build.xml` script in the project root.<br/>\r
+*This will download all necessary build dependencies and will also generate the Keys class for accessing settings.*\r
+4. Select your gitblit project root and **Refresh** the project, this should correct all build problems.\r
+5. Using JUnit, execute the `com.gitblit.tests.GitBlitSuite` test suite.<br/>\r
+*This will clone some repositories from the web and run through the unit tests.*\r
+5. Review the settings in `gitblit.properties` in your project root.\r
+    - By default, the *git.repositoriesFolder* points to the repositories cloned by the test suite.<br/>\r
+    - If running on Linux you may have to change the served port(s) to > 1024 unless you are developing as the root user. \r
+6. Execute the *com.gitblit.Launcher* class to start Gitblit.\r
+\r
+\r
+## Contributing\r
+Patches welcome in any form.\r
+\r
+Contributions must be your own original work and must licensed under the [Apache License, Version 2.0][apachelicense], the same license used by Gitblit.\r
+\r
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"\r
+[git]: http://git-scm.com "Official Git Site"\r
+[gitbltsrc]: http://github.com/gitblit "gitblit git repository"\r
+[googlecode]: http://code.google.com/p/gitblit "gitblit project management"\r
+[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"
\ No newline at end of file
diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd
new file mode 100644 (file)
index 0000000..109041d
--- /dev/null
@@ -0,0 +1,9 @@
+## Release History\r
+\r
+### Current Release\r
+%VERSION% ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)) based on [%JGIT%][jgit] with Blame backport &nbsp; (*%BUILDDATE%*)\r
+\r
+### Older Releases\r
+none\r
+\r
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
\ No newline at end of file
diff --git a/resources/commit_changes_16x16.png b/resources/commit_changes_16x16.png
new file mode 100644 (file)
index 0000000..257cfee
Binary files /dev/null and b/resources/commit_changes_16x16.png differ
index 294741882b20fdd6a01b530bb6ce3f40821c087c..08d147ac7ef50c144497ddedf7bf226ccf6bd6df 100644 (file)
@@ -56,6 +56,11 @@ img.inlineIcon {
        padding-right: 1px;\r
 }\r
 \r
+img.overview {\r
+       float:right;\r
+       border:1px solid #CCCCCC;\r
+}\r
+\r
 a {\r
        color: #0000cc;\r
 }\r
index 90224f08af71406a95f329712bb721a825e31488..b6c485a3704e7f9746302fa331013d46d0b093de 100644 (file)
@@ -86,10 +86,10 @@ public class Build {
                downloadFromApache(MavenObject.JSCH, BuildType.COMPILETIME);\r
                downloadFromApache(MavenObject.ROME, BuildType.COMPILETIME);\r
                downloadFromApache(MavenObject.JDOM, BuildType.COMPILETIME);\r
-               \r
+\r
                downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);\r
                downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);\r
-               \r
+\r
                // needed for site publishing\r
                downloadFromApache(MavenObject.COMMONSNET, BuildType.RUNTIME);\r
        }\r
@@ -401,18 +401,17 @@ public class Build {
                                "e528f593b19b04d500992606f58b87fcfded8883",\r
                                "d0ffadd0a4ab909d94a577b5aad43c13b617ddcb");\r
 \r
-               public static final MavenObject COMMONSNET = new MavenObject("commons-net", "commons-net", "commons-net",\r
-                               "1.4.0", 181000, 0, 0, "eb47e8cad2dd7f92fd7e77df1d1529cae87361f7",\r
-                               "",\r
-                               "");\r
-               \r
-               public static final MavenObject ROME = new MavenObject("rome", "rome", "rome",\r
-                               "0.9", 208000, 196000, 407000, "dee2705dd01e79a5a96a17225f5a1ae30470bb18",\r
+               public static final MavenObject COMMONSNET = new MavenObject("commons-net", "commons-net",\r
+                               "commons-net", "1.4.0", 181000, 0, 0, "eb47e8cad2dd7f92fd7e77df1d1529cae87361f7",\r
+                               "", "");\r
+\r
+               public static final MavenObject ROME = new MavenObject("rome", "rome", "rome", "0.9",\r
+                               208000, 196000, 407000, "dee2705dd01e79a5a96a17225f5a1ae30470bb18",\r
                                "226f851dc44fd94fe70b9c471881b71f88949cbf",\r
                                "8d7d867b97eeb3a9196c3926da550ad042941c1b");\r
 \r
-               public static final MavenObject JDOM = new MavenObject("jdom", "org/jdom", "jdom",\r
-                               "1.1", 153000, 235000, 445000, "1d04c0f321ea337f3661cf7ede8f4c6f653a8fdd",\r
+               public static final MavenObject JDOM = new MavenObject("jdom", "org/jdom", "jdom", "1.1",\r
+                               153000, 235000, 445000, "1d04c0f321ea337f3661cf7ede8f4c6f653a8fdd",\r
                                "a7ed425c4c46605b8f2bf2ee118c1609682f4f2c",\r
                                "f3df91edccba2f07a0fced70887c2f7b7836cb75");\r
 \r
diff --git a/src/com/gitblit/BuildThumbnails.java b/src/com/gitblit/BuildThumbnails.java
new file mode 100644 (file)
index 0000000..4f2b2ab
--- /dev/null
@@ -0,0 +1,134 @@
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit;\r
+\r
+import java.awt.Dimension;\r
+import java.awt.Image;\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.FilenameFilter;\r
+import java.io.IOException;\r
+import java.util.Iterator;\r
+\r
+import javax.imageio.ImageIO;\r
+import javax.imageio.ImageReader;\r
+import javax.imageio.stream.ImageInputStream;\r
+\r
+import com.beust.jcommander.JCommander;\r
+import com.beust.jcommander.Parameter;\r
+import com.beust.jcommander.ParameterException;\r
+import com.beust.jcommander.Parameters;\r
+\r
+public class BuildThumbnails {\r
+\r
+       public static void main(String[] args) {\r
+               Params params = new Params();\r
+               JCommander jc = new JCommander(params);\r
+               try {\r
+                       jc.parse(args);\r
+               } catch (ParameterException t) {\r
+                       System.err.println(t.getMessage());\r
+                       jc.usage();\r
+               }\r
+               createImageThumbnail(params.sourceFolder, params.destinationFolder, params.maximumDimension);\r
+       }\r
+\r
+       public static void createImageThumbnail(String sourceFolder, String destinationFolder,\r
+                       int maxDimension) {\r
+               if (maxDimension <= 0)\r
+                       return;\r
+               File source = new File(sourceFolder);\r
+               File destination = new File(destinationFolder);\r
+               destination.mkdirs();\r
+               File[] sourceFiles = source.listFiles(new FilenameFilter() {\r
+                       @Override\r
+                       public boolean accept(File dir, String name) {\r
+                               return name.toLowerCase().endsWith(".png");\r
+                       }\r
+               });\r
+\r
+               for (File sourceFile : sourceFiles) {\r
+                       File destinationFile = new File(destination, sourceFile.getName());\r
+                       try {\r
+                               Dimension sz = getImageDimensions(sourceFile);\r
+                               int w = 0;\r
+                               int h = 0;\r
+                               if (sz.width > maxDimension) {\r
+                                       // Scale to Width\r
+                                       w = maxDimension;\r
+                                       float f = maxDimension;\r
+                                       h = (int) ((f / sz.width) * sz.height); // normalize height\r
+                               } else if (sz.height > maxDimension) {\r
+                                       // Scale to Height\r
+                                       h = maxDimension;\r
+                                       float f = maxDimension;\r
+                                       w = (int) ((f / sz.height) * sz.width); // normalize width\r
+                               } else {\r
+                                       // No thumbnail\r
+                                       return;\r
+                               }\r
+                               System.out.println("Generating thumbnail for " + sourceFile.getName() + " as (" + w\r
+                                               + "," + h + ")");\r
+                               BufferedImage image = ImageIO.read(sourceFile);\r
+                               Image scaledImage = image.getScaledInstance(w, h, BufferedImage.SCALE_SMOOTH);\r
+                               BufferedImage destImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);\r
+                               destImage.createGraphics().drawImage(scaledImage, 0, 0, null);\r
+                               FileOutputStream fos = new FileOutputStream(destinationFile);\r
+                               ImageIO.write(destImage, "png", fos);\r
+                               fos.flush();\r
+                               fos.getFD().sync();\r
+                               fos.close();\r
+                       } catch (Throwable t) {\r
+                               t.printStackTrace();\r
+                       }\r
+               }\r
+       }\r
+\r
+       public static Dimension getImageDimensions(File file) throws IOException {\r
+               ImageInputStream in = ImageIO.createImageInputStream(file);\r
+               try {\r
+                       final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);\r
+                       if (readers.hasNext()) {\r
+                               ImageReader reader = readers.next();\r
+                               try {\r
+                                       reader.setInput(in);\r
+                                       return new Dimension(reader.getWidth(0), reader.getHeight(0));\r
+                               } finally {\r
+                                       reader.dispose();\r
+                               }\r
+                       }\r
+               } finally {\r
+                       if (in != null)\r
+                               in.close();\r
+               }\r
+               return null;\r
+       }\r
+\r
+       @Parameters(separators = " ")\r
+       private static class Params {\r
+\r
+               @Parameter(names = { "--sourceFolder" }, description = "Source folder for raw images", required = true)\r
+               public String sourceFolder;\r
+\r
+               @Parameter(names = { "--destinationFolder" }, description = "Destination folder for thumbnails", required = true)\r
+               public String destinationFolder;\r
+\r
+               @Parameter(names = { "--maximumDimension" }, description = "Maximum width or height for thumbnail", required = true)\r
+               public int maximumDimension;\r
+\r
+       }\r
+}\r
index 557c6a85dda23170b139b7a644feeaf071dd4b63..8e957df56182307c8b8d0877a94a7feba7577616 100644 (file)
@@ -24,6 +24,12 @@ import java.util.ArrayList;
 import java.util.List;\r
 import java.util.Vector;\r
 \r
+import com.beust.jcommander.JCommander;\r
+import com.beust.jcommander.Parameter;\r
+import com.beust.jcommander.ParameterException;\r
+import com.beust.jcommander.Parameters;\r
+import com.gitblit.utils.StringUtils;\r
+\r
 public class BuildWebXml {\r
        private static final String PARAMS = "<!-- PARAMS -->";\r
 \r
@@ -34,9 +40,21 @@ public class BuildWebXml {
        private static final String PARAM_PATTERN = "\n\t<context-param>\n\t\t<param-name>{0}</param-name>\n\t\t<param-value>{1}</param-value>\n\t</context-param>\n";\r
 \r
        public static void main(String[] args) throws Exception {\r
+               Params params = new Params();\r
+               JCommander jc = new JCommander(params);\r
+               try {\r
+                       jc.parse(args);\r
+               } catch (ParameterException t) {\r
+                       System.err.println(t.getMessage());\r
+                       jc.usage();\r
+               }\r
+               generateWebXml(params);\r
+       }\r
+\r
+       private static void generateWebXml(Params params) throws Exception {\r
                // Read the current Gitblit properties\r
                BufferedReader propertiesReader = new BufferedReader(new FileReader(new File(\r
-                               "distrib/gitblit.properties")));\r
+                               params.propertiesFile)));\r
 \r
                Vector<Setting> settings = new Vector<Setting>();\r
                List<String> comments = new ArrayList<String>();\r
@@ -68,11 +86,11 @@ public class BuildWebXml {
                        for (String comment : setting.comments) {\r
                                parameters.append(MessageFormat.format(COMMENT_PATTERN, comment));\r
                        }\r
-                       parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name, setting.value));\r
+                       parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name, StringUtils.escapeForHtml(setting.value, false)));\r
                }\r
 \r
                // Read the prototype web.xml file\r
-               File webxml = new File("src/WEB-INF/web.xml");\r
+               File webxml = new File(params.sourceFile);\r
                char[] buffer = new char[(int) webxml.length()];\r
                FileReader webxmlReader = new FileReader(webxml);\r
                webxmlReader.read(buffer);\r
@@ -90,7 +108,7 @@ public class BuildWebXml {
                sb.append(webXmlContent.substring(idx + PARAMS.length()));\r
 \r
                // Save the merged web.xml to the war build folder\r
-               FileOutputStream os = new FileOutputStream(new File("war/WEB-INF/web.xml"), false);\r
+               FileOutputStream os = new FileOutputStream(new File(params.destinationFile), false);\r
                os.write(sb.toString().getBytes());\r
                os.close();\r
        }\r
@@ -110,4 +128,18 @@ public class BuildWebXml {
                        this.comments = new ArrayList<String>(comments);\r
                }\r
        }\r
+\r
+       @Parameters(separators = " ")\r
+       private static class Params {\r
+\r
+               @Parameter(names = { "--sourceFile" }, description = "Source web.xml file", required = true)\r
+               public String sourceFile;\r
+\r
+               @Parameter(names = { "--propertiesFile" }, description = "Properties settings file", required = true)\r
+               public String propertiesFile;\r
+\r
+               @Parameter(names = { "--destinationFile" }, description = "Destination web.xml file", required = true)\r
+               public String destinationFile;\r
+\r
+       }\r
 }\r
index b874a7b09ce995a091b7cb8ac5b082a4ba98d48b..d410d35ff8dcd9d4b0ce38c730c3a3ef5046b8a2 100644 (file)
@@ -36,9 +36,9 @@ public class Constants {
        public static final String GIT_PATH = "/git/";\r
 \r
        public static final String ZIP_PATH = "/zip/";\r
-       \r
+\r
        public static final String SYNDICATION_PATH = "/feed/";\r
-       \r
+\r
        public static final String BORDER = "***********************************************************";\r
 \r
        public static enum AccessRestrictionType {\r
diff --git a/src/com/gitblit/FileLoginService.java b/src/com/gitblit/FileLoginService.java
deleted file mode 100644 (file)
index e239efc..0000000
+++ /dev/null
@@ -1,372 +0,0 @@
-/*\r
- * Copyright 2011 gitblit.com.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-package com.gitblit;\r
-\r
-import java.io.File;\r
-import java.io.FileWriter;\r
-import java.io.IOException;\r
-import java.text.MessageFormat;\r
-import java.util.ArrayList;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Properties;\r
-import java.util.Set;\r
-\r
-import org.slf4j.Logger;\r
-import org.slf4j.LoggerFactory;\r
-\r
-import com.gitblit.models.UserModel;\r
-import com.gitblit.utils.StringUtils;\r
-\r
-public class FileLoginService extends FileSettings implements ILoginService {\r
-\r
-       private final Logger logger = LoggerFactory.getLogger(FileLoginService.class);\r
-\r
-       public FileLoginService(File realmFile) {\r
-               super(realmFile.getAbsolutePath());\r
-       }\r
-\r
-       @Override\r
-       public UserModel authenticate(String username, char[] password) {\r
-               Properties allUsers = read();\r
-               String userInfo = allUsers.getProperty(username);\r
-               if (StringUtils.isEmpty(userInfo)) {\r
-                       return null;\r
-               }\r
-               UserModel returnedUser = null;\r
-               UserModel user = getUserModel(username);\r
-               if (user.password.startsWith(StringUtils.MD5_TYPE)) {\r
-                       String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));\r
-                       if (user.password.equalsIgnoreCase(md5)) {\r
-                               returnedUser = user;\r
-                       }\r
-               } else if (user.password.equals(new String(password))) {\r
-                       returnedUser = user;\r
-               }\r
-               return returnedUser;\r
-       }\r
-\r
-       @Override\r
-       public UserModel getUserModel(String username) {\r
-               Properties allUsers = read();\r
-               String userInfo = allUsers.getProperty(username);\r
-               if (userInfo == null) {\r
-                       return null;\r
-               }\r
-               UserModel model = new UserModel(username);\r
-               String[] userValues = userInfo.split(",");\r
-               model.password = userValues[0];\r
-               for (int i = 1; i < userValues.length; i++) {\r
-                       String role = userValues[i];\r
-                       switch (role.charAt(0)) {\r
-                       case '#':\r
-                               // Permissions\r
-                               if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {\r
-                                       model.canAdmin = true;\r
-                               }\r
-                               break;\r
-                       default:\r
-                               model.addRepository(role);\r
-                       }\r
-               }\r
-               return model;\r
-       }\r
-\r
-       @Override\r
-       public boolean updateUserModel(UserModel model) {\r
-               return updateUserModel(model.username, model);\r
-       }\r
-\r
-       @Override\r
-       public boolean updateUserModel(String username, UserModel model) {\r
-               try {\r
-                       Properties allUsers = read();\r
-                       ArrayList<String> roles = new ArrayList<String>(model.repositories);\r
-\r
-                       // Permissions\r
-                       if (model.canAdmin) {\r
-                               roles.add(Constants.ADMIN_ROLE);\r
-                       }\r
-\r
-                       StringBuilder sb = new StringBuilder();\r
-                       sb.append(model.password);\r
-                       sb.append(',');\r
-                       for (String role : roles) {\r
-                               sb.append(role);\r
-                               sb.append(',');\r
-                       }\r
-                       // trim trailing comma\r
-                       sb.setLength(sb.length() - 1);\r
-                       allUsers.remove(username);\r
-                       allUsers.put(model.username, sb.toString());\r
-\r
-                       write(allUsers);\r
-                       return true;\r
-               } catch (Throwable t) {\r
-                       logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),\r
-                                       t);\r
-               }\r
-               return false;\r
-       }\r
-\r
-       @Override\r
-       public boolean deleteUserModel(UserModel model) {\r
-               return deleteUser(model.username);\r
-       }\r
-\r
-       @Override\r
-       public boolean deleteUser(String username) {\r
-               try {\r
-                       // Read realm file\r
-                       Properties allUsers = read();\r
-                       allUsers.remove(username);\r
-                       write(allUsers);\r
-                       return true;\r
-               } catch (Throwable t) {\r
-                       logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);\r
-               }\r
-               return false;\r
-       }\r
-\r
-       @Override\r
-       public List<String> getAllUsernames() {\r
-               Properties allUsers = read();\r
-               List<String> list = new ArrayList<String>(allUsers.stringPropertyNames());\r
-               return list;\r
-       }\r
-\r
-       @Override\r
-       public List<String> getUsernamesForRole(String role) {\r
-               List<String> list = new ArrayList<String>();\r
-               try {\r
-                       Properties allUsers = read();\r
-                       for (String username : allUsers.stringPropertyNames()) {\r
-                               String value = allUsers.getProperty(username);\r
-                               String[] values = value.split(",");\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < values.length; i++) {\r
-                                       String r = values[i];\r
-                                       if (r.equalsIgnoreCase(role)) {\r
-                                               list.add(username);\r
-                                               break;\r
-                                       }\r
-                               }\r
-                       }\r
-               } catch (Throwable t) {\r
-                       logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);\r
-               }\r
-               return list;\r
-       }\r
-\r
-       @Override\r
-       public boolean setUsernamesForRole(String role, List<String> usernames) {\r
-               try {\r
-                       Set<String> specifiedUsers = new HashSet<String>(usernames);\r
-                       Set<String> needsAddRole = new HashSet<String>(specifiedUsers);\r
-                       Set<String> needsRemoveRole = new HashSet<String>();\r
-\r
-                       // identify users which require add and remove role\r
-                       Properties allUsers = read();\r
-                       for (String username : allUsers.stringPropertyNames()) {\r
-                               String value = allUsers.getProperty(username);\r
-                               String[] values = value.split(",");\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < values.length; i++) {\r
-                                       String r = values[i];\r
-                                       if (r.equalsIgnoreCase(role)) {\r
-                                               // user has role, check against revised user list\r
-                                               if (specifiedUsers.contains(username)) {\r
-                                                       needsAddRole.remove(username);\r
-                                               } else {\r
-                                                       // remove role from user\r
-                                                       needsRemoveRole.add(username);\r
-                                               }\r
-                                               break;\r
-                                       }\r
-                               }\r
-                       }\r
-\r
-                       // add roles to users\r
-                       for (String user : needsAddRole) {\r
-                               String userValues = allUsers.getProperty(user);\r
-                               userValues += "," + role;\r
-                               allUsers.put(user, userValues);\r
-                       }\r
-\r
-                       // remove role from user\r
-                       for (String user : needsRemoveRole) {\r
-                               String[] values = allUsers.getProperty(user).split(",");\r
-                               String password = values[0];\r
-                               StringBuilder sb = new StringBuilder();\r
-                               sb.append(password);\r
-                               sb.append(',');\r
-                               List<String> revisedRoles = new ArrayList<String>();\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < values.length; i++) {\r
-                                       String value = values[i];\r
-                                       if (!value.equalsIgnoreCase(role)) {\r
-                                               revisedRoles.add(value);\r
-                                               sb.append(value);\r
-                                               sb.append(',');\r
-                                       }\r
-                               }\r
-                               sb.setLength(sb.length() - 1);\r
-\r
-                               // update properties\r
-                               allUsers.put(user, sb.toString());\r
-                       }\r
-\r
-                       // persist changes\r
-                       write(allUsers);\r
-                       return true;\r
-               } catch (Throwable t) {\r
-                       logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);\r
-               }\r
-               return false;\r
-       }\r
-\r
-       @Override\r
-       public boolean renameRole(String oldRole, String newRole) {\r
-               try {\r
-                       Properties allUsers = read();\r
-                       Set<String> needsRenameRole = new HashSet<String>();\r
-\r
-                       // identify users which require role rename\r
-                       for (String username : allUsers.stringPropertyNames()) {\r
-                               String value = allUsers.getProperty(username);\r
-                               String[] roles = value.split(",");\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < roles.length; i++) {\r
-                                       String r = roles[i];\r
-                                       if (r.equalsIgnoreCase(oldRole)) {\r
-                                               needsRenameRole.remove(username);\r
-                                               break;\r
-                                       }\r
-                               }\r
-                       }\r
-\r
-                       // rename role for identified users\r
-                       for (String user : needsRenameRole) {\r
-                               String userValues = allUsers.getProperty(user);\r
-                               String[] values = userValues.split(",");\r
-                               String password = values[0];\r
-                               StringBuilder sb = new StringBuilder();\r
-                               sb.append(password);\r
-                               sb.append(',');\r
-                               List<String> revisedRoles = new ArrayList<String>();\r
-                               revisedRoles.add(newRole);\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < values.length; i++) {\r
-                                       String value = values[i];\r
-                                       if (!value.equalsIgnoreCase(oldRole)) {\r
-                                               revisedRoles.add(value);\r
-                                               sb.append(value);\r
-                                               sb.append(',');\r
-                                       }\r
-                               }\r
-                               sb.setLength(sb.length() - 1);\r
-\r
-                               // update properties\r
-                               allUsers.put(user, sb.toString());\r
-                       }\r
-\r
-                       // persist changes\r
-                       write(allUsers);\r
-                       return true;\r
-               } catch (Throwable t) {\r
-                       logger.error(\r
-                                       MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);\r
-               }\r
-               return false;\r
-       }\r
-\r
-       @Override\r
-       public boolean deleteRole(String role) {\r
-               try {\r
-                       Properties allUsers = read();\r
-                       Set<String> needsDeleteRole = new HashSet<String>();\r
-\r
-                       // identify users which require role rename\r
-                       for (String username : allUsers.stringPropertyNames()) {\r
-                               String value = allUsers.getProperty(username);\r
-                               String[] roles = value.split(",");\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < roles.length; i++) {\r
-                                       String r = roles[i];\r
-                                       if (r.equalsIgnoreCase(role)) {\r
-                                               needsDeleteRole.remove(username);\r
-                                               break;\r
-                                       }\r
-                               }\r
-                       }\r
-\r
-                       // delete role for identified users\r
-                       for (String user : needsDeleteRole) {\r
-                               String userValues = allUsers.getProperty(user);\r
-                               String[] values = userValues.split(",");\r
-                               String password = values[0];\r
-                               StringBuilder sb = new StringBuilder();\r
-                               sb.append(password);\r
-                               sb.append(',');\r
-                               List<String> revisedRoles = new ArrayList<String>();\r
-                               // skip first value (password)\r
-                               for (int i = 1; i < values.length; i++) {\r
-                                       String value = values[i];\r
-                                       if (!value.equalsIgnoreCase(role)) {\r
-                                               revisedRoles.add(value);\r
-                                               sb.append(value);\r
-                                               sb.append(',');\r
-                                       }\r
-                               }\r
-                               sb.setLength(sb.length() - 1);\r
-\r
-                               // update properties\r
-                               allUsers.put(user, sb.toString());\r
-                       }\r
-\r
-                       // persist changes\r
-                       write(allUsers);\r
-                       return true;\r
-               } catch (Throwable t) {\r
-                       logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);\r
-               }\r
-               return false;\r
-       }\r
-\r
-       private void write(Properties properties) throws IOException {\r
-               // Update realm file\r
-               File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp");\r
-               FileWriter writer = new FileWriter(realmFileCopy);\r
-               properties\r
-                               .store(writer,\r
-                                               "# Gitblit realm file format: username=password,\\#permission,repository1,repository2...");\r
-               writer.close();\r
-               if (realmFileCopy.exists() && realmFileCopy.length() > 0) {\r
-                       if (propertiesFile.delete()) {\r
-                               if (!realmFileCopy.renameTo(propertiesFile)) {\r
-                                       throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",\r
-                                                       realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));\r
-                               }\r
-                       } else {\r
-                               throw new IOException(MessageFormat.format("Failed to delete (0)!",\r
-                                               propertiesFile.getAbsolutePath()));\r
-                       }\r
-               } else {\r
-                       throw new IOException(MessageFormat.format("Failed to save {0}!",\r
-                                       realmFileCopy.getAbsolutePath()));\r
-               }\r
-       }\r
-}\r
index e213e80f88902a3c6ba0800a09e77f6b4e130bce..1e65422241706f9fecb57e60a566070384589fc2 100644 (file)
@@ -45,7 +45,7 @@ public class FileSettings extends IStoredSettings {
                                Properties props = new Properties();\r
                                is = new FileInputStream(propertiesFile);\r
                                props.load(is);\r
-                               \r
+\r
                                // load properties after we have successfully read file\r
                                properties.clear();\r
                                properties.putAll(props);\r
@@ -67,6 +67,10 @@ public class FileSettings extends IStoredSettings {
                return properties;\r
        }\r
 \r
+       protected long lastRead() {\r
+               return lastread;\r
+       }\r
+\r
        @Override\r
        public String toString() {\r
                return propertiesFile.getAbsolutePath();\r
diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java
new file mode 100644 (file)
index 0000000..01a50be
--- /dev/null
@@ -0,0 +1,423 @@
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit;\r
+\r
+import java.io.File;\r
+import java.io.FileWriter;\r
+import java.io.IOException;\r
+import java.text.MessageFormat;\r
+import java.util.ArrayList;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import java.util.Set;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+\r
+import com.gitblit.models.UserModel;\r
+import com.gitblit.utils.StringUtils;\r
+\r
+public class FileUserService extends FileSettings implements IUserService {\r
+\r
+       private final Logger logger = LoggerFactory.getLogger(FileUserService.class);\r
+\r
+       private final Map<String, String> cookies = new ConcurrentHashMap<String, String>();\r
+\r
+       public FileUserService(File realmFile) {\r
+               super(realmFile.getAbsolutePath());\r
+       }\r
+\r
+       @Override\r
+       public boolean supportsCookies() {\r
+               return true;\r
+       }\r
+\r
+       @Override\r
+       public char[] getCookie(UserModel model) {\r
+               Properties allUsers = super.read();\r
+               String value = allUsers.getProperty(model.username);\r
+               String[] roles = value.split(",");\r
+               String password = roles[0];\r
+               String cookie = StringUtils.getSHA1(model.username + password);\r
+               return cookie.toCharArray();\r
+       }\r
+\r
+       @Override\r
+       public UserModel authenticate(char[] cookie) {\r
+               String hash = new String(cookie);\r
+               if (StringUtils.isEmpty(hash)) {\r
+                       return null;\r
+               }\r
+               read();\r
+               UserModel model = null;\r
+               if (cookies.containsKey(hash)) {\r
+                       String username = cookies.get(hash);\r
+                       model = getUserModel(username);\r
+               }\r
+               return model;\r
+       }\r
+\r
+       @Override\r
+       public UserModel authenticate(String username, char[] password) {\r
+               Properties allUsers = read();\r
+               String userInfo = allUsers.getProperty(username);\r
+               if (StringUtils.isEmpty(userInfo)) {\r
+                       return null;\r
+               }\r
+               UserModel returnedUser = null;\r
+               UserModel user = getUserModel(username);\r
+               if (user.password.startsWith(StringUtils.MD5_TYPE)) {\r
+                       String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));\r
+                       if (user.password.equalsIgnoreCase(md5)) {\r
+                               returnedUser = user;\r
+                       }\r
+               } else if (user.password.equals(new String(password))) {\r
+                       returnedUser = user;\r
+               }\r
+               return returnedUser;\r
+       }\r
+\r
+       @Override\r
+       public UserModel getUserModel(String username) {\r
+               Properties allUsers = read();\r
+               String userInfo = allUsers.getProperty(username);\r
+               if (userInfo == null) {\r
+                       return null;\r
+               }\r
+               UserModel model = new UserModel(username);\r
+               String[] userValues = userInfo.split(",");\r
+               model.password = userValues[0];\r
+               for (int i = 1; i < userValues.length; i++) {\r
+                       String role = userValues[i];\r
+                       switch (role.charAt(0)) {\r
+                       case '#':\r
+                               // Permissions\r
+                               if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {\r
+                                       model.canAdmin = true;\r
+                               }\r
+                               break;\r
+                       default:\r
+                               model.addRepository(role);\r
+                       }\r
+               }\r
+               return model;\r
+       }\r
+\r
+       @Override\r
+       public boolean updateUserModel(UserModel model) {\r
+               return updateUserModel(model.username, model);\r
+       }\r
+\r
+       @Override\r
+       public boolean updateUserModel(String username, UserModel model) {\r
+               try {\r
+                       Properties allUsers = read();\r
+                       ArrayList<String> roles = new ArrayList<String>(model.repositories);\r
+\r
+                       // Permissions\r
+                       if (model.canAdmin) {\r
+                               roles.add(Constants.ADMIN_ROLE);\r
+                       }\r
+\r
+                       StringBuilder sb = new StringBuilder();\r
+                       sb.append(model.password);\r
+                       sb.append(',');\r
+                       for (String role : roles) {\r
+                               sb.append(role);\r
+                               sb.append(',');\r
+                       }\r
+                       // trim trailing comma\r
+                       sb.setLength(sb.length() - 1);\r
+                       allUsers.remove(username);\r
+                       allUsers.put(model.username, sb.toString());\r
+\r
+                       write(allUsers);\r
+                       return true;\r
+               } catch (Throwable t) {\r
+                       logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),\r
+                                       t);\r
+               }\r
+               return false;\r
+       }\r
+\r
+       @Override\r
+       public boolean deleteUserModel(UserModel model) {\r
+               return deleteUser(model.username);\r
+       }\r
+\r
+       @Override\r
+       public boolean deleteUser(String username) {\r
+               try {\r
+                       // Read realm file\r
+                       Properties allUsers = read();\r
+                       allUsers.remove(username);\r
+                       write(allUsers);\r
+                       return true;\r
+               } catch (Throwable t) {\r
+                       logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);\r
+               }\r
+               return false;\r
+       }\r
+\r
+       @Override\r
+       public List<String> getAllUsernames() {\r
+               Properties allUsers = read();\r
+               List<String> list = new ArrayList<String>(allUsers.stringPropertyNames());\r
+               return list;\r
+       }\r
+\r
+       @Override\r
+       public List<String> getUsernamesForRepository(String role) {\r
+               List<String> list = new ArrayList<String>();\r
+               try {\r
+                       Properties allUsers = read();\r
+                       for (String username : allUsers.stringPropertyNames()) {\r
+                               String value = allUsers.getProperty(username);\r
+                               String[] values = value.split(",");\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < values.length; i++) {\r
+                                       String r = values[i];\r
+                                       if (r.equalsIgnoreCase(role)) {\r
+                                               list.add(username);\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }\r
+               } catch (Throwable t) {\r
+                       logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);\r
+               }\r
+               return list;\r
+       }\r
+\r
+       @Override\r
+       public boolean setUsernamesForRepository(String role, List<String> usernames) {\r
+               try {\r
+                       Set<String> specifiedUsers = new HashSet<String>(usernames);\r
+                       Set<String> needsAddRole = new HashSet<String>(specifiedUsers);\r
+                       Set<String> needsRemoveRole = new HashSet<String>();\r
+\r
+                       // identify users which require add and remove role\r
+                       Properties allUsers = read();\r
+                       for (String username : allUsers.stringPropertyNames()) {\r
+                               String value = allUsers.getProperty(username);\r
+                               String[] values = value.split(",");\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < values.length; i++) {\r
+                                       String r = values[i];\r
+                                       if (r.equalsIgnoreCase(role)) {\r
+                                               // user has role, check against revised user list\r
+                                               if (specifiedUsers.contains(username)) {\r
+                                                       needsAddRole.remove(username);\r
+                                               } else {\r
+                                                       // remove role from user\r
+                                                       needsRemoveRole.add(username);\r
+                                               }\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       // add roles to users\r
+                       for (String user : needsAddRole) {\r
+                               String userValues = allUsers.getProperty(user);\r
+                               userValues += "," + role;\r
+                               allUsers.put(user, userValues);\r
+                       }\r
+\r
+                       // remove role from user\r
+                       for (String user : needsRemoveRole) {\r
+                               String[] values = allUsers.getProperty(user).split(",");\r
+                               String password = values[0];\r
+                               StringBuilder sb = new StringBuilder();\r
+                               sb.append(password);\r
+                               sb.append(',');\r
+                               List<String> revisedRoles = new ArrayList<String>();\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < values.length; i++) {\r
+                                       String value = values[i];\r
+                                       if (!value.equalsIgnoreCase(role)) {\r
+                                               revisedRoles.add(value);\r
+                                               sb.append(value);\r
+                                               sb.append(',');\r
+                                       }\r
+                               }\r
+                               sb.setLength(sb.length() - 1);\r
+\r
+                               // update properties\r
+                               allUsers.put(user, sb.toString());\r
+                       }\r
+\r
+                       // persist changes\r
+                       write(allUsers);\r
+                       return true;\r
+               } catch (Throwable t) {\r
+                       logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);\r
+               }\r
+               return false;\r
+       }\r
+\r
+       @Override\r
+       public boolean renameRepositoryRole(String oldRole, String newRole) {\r
+               try {\r
+                       Properties allUsers = read();\r
+                       Set<String> needsRenameRole = new HashSet<String>();\r
+\r
+                       // identify users which require role rename\r
+                       for (String username : allUsers.stringPropertyNames()) {\r
+                               String value = allUsers.getProperty(username);\r
+                               String[] roles = value.split(",");\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < roles.length; i++) {\r
+                                       String r = roles[i];\r
+                                       if (r.equalsIgnoreCase(oldRole)) {\r
+                                               needsRenameRole.remove(username);\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       // rename role for identified users\r
+                       for (String user : needsRenameRole) {\r
+                               String userValues = allUsers.getProperty(user);\r
+                               String[] values = userValues.split(",");\r
+                               String password = values[0];\r
+                               StringBuilder sb = new StringBuilder();\r
+                               sb.append(password);\r
+                               sb.append(',');\r
+                               List<String> revisedRoles = new ArrayList<String>();\r
+                               revisedRoles.add(newRole);\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < values.length; i++) {\r
+                                       String value = values[i];\r
+                                       if (!value.equalsIgnoreCase(oldRole)) {\r
+                                               revisedRoles.add(value);\r
+                                               sb.append(value);\r
+                                               sb.append(',');\r
+                                       }\r
+                               }\r
+                               sb.setLength(sb.length() - 1);\r
+\r
+                               // update properties\r
+                               allUsers.put(user, sb.toString());\r
+                       }\r
+\r
+                       // persist changes\r
+                       write(allUsers);\r
+                       return true;\r
+               } catch (Throwable t) {\r
+                       logger.error(\r
+                                       MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);\r
+               }\r
+               return false;\r
+       }\r
+\r
+       @Override\r
+       public boolean deleteRepositoryRole(String role) {\r
+               try {\r
+                       Properties allUsers = read();\r
+                       Set<String> needsDeleteRole = new HashSet<String>();\r
+\r
+                       // identify users which require role rename\r
+                       for (String username : allUsers.stringPropertyNames()) {\r
+                               String value = allUsers.getProperty(username);\r
+                               String[] roles = value.split(",");\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < roles.length; i++) {\r
+                                       String r = roles[i];\r
+                                       if (r.equalsIgnoreCase(role)) {\r
+                                               needsDeleteRole.remove(username);\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       // delete role for identified users\r
+                       for (String user : needsDeleteRole) {\r
+                               String userValues = allUsers.getProperty(user);\r
+                               String[] values = userValues.split(",");\r
+                               String password = values[0];\r
+                               StringBuilder sb = new StringBuilder();\r
+                               sb.append(password);\r
+                               sb.append(',');\r
+                               List<String> revisedRoles = new ArrayList<String>();\r
+                               // skip first value (password)\r
+                               for (int i = 1; i < values.length; i++) {\r
+                                       String value = values[i];\r
+                                       if (!value.equalsIgnoreCase(role)) {\r
+                                               revisedRoles.add(value);\r
+                                               sb.append(value);\r
+                                               sb.append(',');\r
+                                       }\r
+                               }\r
+                               sb.setLength(sb.length() - 1);\r
+\r
+                               // update properties\r
+                               allUsers.put(user, sb.toString());\r
+                       }\r
+\r
+                       // persist changes\r
+                       write(allUsers);\r
+                       return true;\r
+               } catch (Throwable t) {\r
+                       logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);\r
+               }\r
+               return false;\r
+       }\r
+\r
+       private void write(Properties properties) throws IOException {\r
+               // Update realm file\r
+               File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp");\r
+               FileWriter writer = new FileWriter(realmFileCopy);\r
+               properties\r
+                               .store(writer,\r
+                                               "# Gitblit realm file format: username=password,\\#permission,repository1,repository2...");\r
+               writer.close();\r
+               if (realmFileCopy.exists() && realmFileCopy.length() > 0) {\r
+                       if (propertiesFile.delete()) {\r
+                               if (!realmFileCopy.renameTo(propertiesFile)) {\r
+                                       throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",\r
+                                                       realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));\r
+                               }\r
+                       } else {\r
+                               throw new IOException(MessageFormat.format("Failed to delete (0)!",\r
+                                               propertiesFile.getAbsolutePath()));\r
+                       }\r
+               } else {\r
+                       throw new IOException(MessageFormat.format("Failed to save {0}!",\r
+                                       realmFileCopy.getAbsolutePath()));\r
+               }\r
+       }\r
+\r
+       @Override\r
+       protected synchronized Properties read() {\r
+               long lastRead = lastRead();\r
+               Properties allUsers = super.read();\r
+               if (lastRead != lastRead()) {\r
+                       // reload hash cache\r
+                       cookies.clear();\r
+                       for (String username : allUsers.stringPropertyNames()) {\r
+                               String value = allUsers.getProperty(username);\r
+                               String[] roles = value.split(",");\r
+                               String password = roles[0];\r
+                               cookies.put(StringUtils.getSHA1(username + password), username);\r
+                       }\r
+               }\r
+               return allUsers;\r
+       }\r
+}\r
index 1fa8b60fe759f09e2e9989a53f5c6560e988fb7a..9b6611718e6170314051324ab4607a1013c29efc 100644 (file)
@@ -27,7 +27,9 @@ import java.util.Map.Entry;
 \r
 import javax.servlet.ServletContextEvent;\r
 import javax.servlet.ServletContextListener;\r
+import javax.servlet.http.Cookie;\r
 \r
+import org.apache.wicket.protocol.http.WebResponse;\r
 import org.eclipse.jgit.errors.RepositoryNotFoundException;\r
 import org.eclipse.jgit.lib.Repository;\r
 import org.eclipse.jgit.lib.StoredConfig;\r
@@ -55,7 +57,7 @@ public class GitBlit implements ServletContextListener {
 \r
        private boolean exportAll = true;\r
 \r
-       private ILoginService loginService;\r
+       private IUserService userService;\r
 \r
        private IStoredSettings storedSettings;\r
 \r
@@ -105,44 +107,81 @@ public class GitBlit implements ServletContextListener {
                return cloneUrls;\r
        }\r
 \r
-       public void setLoginService(ILoginService loginService) {\r
-               logger.info("Setting up login service " + loginService.toString());\r
-               this.loginService = loginService;\r
+       public void setUserService(IUserService userService) {\r
+               logger.info("Setting up user service " + userService.toString());\r
+               this.userService = userService;\r
        }\r
 \r
        public UserModel authenticate(String username, char[] password) {\r
-               if (loginService == null) {\r
+               if (userService == null) {\r
                        return null;\r
                }\r
-               return loginService.authenticate(username, password);\r
+               return userService.authenticate(username, password);\r
+       }\r
+\r
+       public UserModel authenticate(Cookie[] cookies) {\r
+               if (userService == null) {\r
+                       return null;\r
+               }\r
+               if (userService.supportsCookies()) {\r
+                       if (cookies != null && cookies.length > 0) {\r
+                               for (Cookie cookie : cookies) {\r
+                                       if (cookie.getName().equals(Constants.NAME)) {\r
+                                               String value = cookie.getValue();\r
+                                               return userService.authenticate(value.toCharArray());\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               return null;\r
+       }\r
+\r
+       public void setCookie(WebResponse response, UserModel user) {\r
+               if (userService == null) {\r
+                       return;\r
+               }\r
+               if (userService.supportsCookies()) {\r
+                       Cookie userCookie;\r
+                       if (user == null) {\r
+                               // clear cookie for logout\r
+                               userCookie = new Cookie(Constants.NAME, "");\r
+                       } else {\r
+                               // set cookie for login\r
+                               char[] cookie = userService.getCookie(user);\r
+                               userCookie = new Cookie(Constants.NAME, new String(cookie));\r
+                               userCookie.setMaxAge(Integer.MAX_VALUE);\r
+                       }\r
+                       userCookie.setPath("/");\r
+                       response.addCookie(userCookie);\r
+               }\r
        }\r
 \r
        public List<String> getAllUsernames() {\r
-               List<String> names = new ArrayList<String>(loginService.getAllUsernames());\r
+               List<String> names = new ArrayList<String>(userService.getAllUsernames());\r
                Collections.sort(names);\r
                return names;\r
        }\r
 \r
        public boolean deleteUser(String username) {\r
-               return loginService.deleteUser(username);\r
+               return userService.deleteUser(username);\r
        }\r
 \r
        public UserModel getUserModel(String username) {\r
-               UserModel user = loginService.getUserModel(username);\r
+               UserModel user = userService.getUserModel(username);\r
                return user;\r
        }\r
 \r
        public List<String> getRepositoryUsers(RepositoryModel repository) {\r
-               return loginService.getUsernamesForRole(repository.name);\r
+               return userService.getUsernamesForRepository(repository.name);\r
        }\r
 \r
        public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {\r
-               return loginService.setUsernamesForRole(repository.name, repositoryUsers);\r
+               return userService.setUsernamesForRepository(repository.name, repositoryUsers);\r
        }\r
 \r
        public void editUserModel(String username, UserModel user, boolean isCreate)\r
                        throws GitBlitException {\r
-               if (!loginService.updateUserModel(username, user)) {\r
+               if (!userService.updateUserModel(username, user)) {\r
                        throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");\r
                }\r
        }\r
@@ -181,6 +220,9 @@ public class GitBlit implements ServletContextListener {
 \r
        public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {\r
                RepositoryModel model = getRepositoryModel(repositoryName);\r
+               if (model == null) {\r
+                       return null;\r
+               }\r
                if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {\r
                        if (user != null && user.canAccessRepository(model.name)) {\r
                                return model;\r
@@ -261,7 +303,7 @@ public class GitBlit implements ServletContextListener {
                                                        repository.name));\r
                                }\r
                                // rename the roles\r
-                               if (!loginService.renameRole(repositoryName, repository.name)) {\r
+                               if (!userService.renameRepositoryRole(repositoryName, repository.name)) {\r
                                        throw new GitBlitException(MessageFormat.format(\r
                                                        "Failed to rename repository permissions ''{0}'' to ''{1}''.",\r
                                                        repositoryName, repository.name));\r
@@ -309,7 +351,7 @@ public class GitBlit implements ServletContextListener {
                        File folder = new File(repositoriesFolder, repositoryName);\r
                        if (folder.exists() && folder.isDirectory()) {\r
                                FileUtils.delete(folder, FileUtils.RECURSIVE);\r
-                               if (loginService.deleteRole(repositoryName)) {\r
+                               if (userService.deleteRepositoryRole(repositoryName)) {\r
                                        return true;\r
                                }\r
                        }\r
@@ -360,13 +402,13 @@ public class GitBlit implements ServletContextListener {
                repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));\r
                logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());\r
                repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);\r
-               String realm = settings.getString(Keys.realm.realmFile, "users.properties");\r
-               ILoginService loginService = null;\r
+               String realm = settings.getString(Keys.realm.userService, "users.properties");\r
+               IUserService loginService = null;\r
                try {\r
                        // Check to see if this "file" is a login service class\r
                        Class<?> realmClass = Class.forName(realm);\r
-                       if (ILoginService.class.isAssignableFrom(realmClass)) {\r
-                               loginService = (ILoginService) realmClass.newInstance();\r
+                       if (IUserService.class.isAssignableFrom(realmClass)) {\r
+                               loginService = (IUserService) realmClass.newInstance();\r
                        }\r
                } catch (Throwable t) {\r
                        // Not a login service class OR other issue\r
@@ -380,9 +422,9 @@ public class GitBlit implements ServletContextListener {
                                                        MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);\r
                                }\r
                        }\r
-                       loginService = new FileLoginService(realmFile);\r
+                       loginService = new FileUserService(realmFile);\r
                }\r
-               setLoginService(loginService);\r
+               setUserService(loginService);\r
        }\r
 \r
        @Override\r
index 80a4690222a92b7d7b5892db4d3de7bac3c386c3..02cc54a09a86c7fa55c082df9d68b80b192d9266 100644 (file)
@@ -111,7 +111,7 @@ public class GitBlitServer {
         * Start Server.\r
         */\r
        private static void start(Params params) {\r
-               FileSettings settings = params.FILESETTINGS;\r
+               FileSettings settings = Params.FILESETTINGS;\r
 \r
                logger = LoggerFactory.getLogger(GitBlitServer.class);\r
                logger.info(Constants.BORDER);\r
@@ -194,15 +194,15 @@ public class GitBlitServer {
                sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0);\r
                rootContext.getSessionHandler().setSessionManager(sessionManager);\r
 \r
-               // Ensure there is a defined Login Service\r
-               String realmUsers = params.realmFile;\r
+               // Ensure there is a defined User Service\r
+               String realmUsers = params.userService;\r
                if (StringUtils.isEmpty(realmUsers)) {\r
-                       logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.realmFile));\r
+                       logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.userService));\r
                        return;\r
                }\r
-               \r
+\r
                // Override settings\r
-               settings.overrideSetting(Keys.realm.realmFile, params.realmFile);\r
+               settings.overrideSetting(Keys.realm.userService, params.userService);\r
                settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder);\r
 \r
                // Set the server's contexts\r
@@ -342,7 +342,7 @@ public class GitBlitServer {
                @Parameter(names = { "--stop" }, description = "Stop Server")\r
                public Boolean stop = false;\r
 \r
-               @Parameter(names = { "--tempFolder" }, description = "Server temp folder")\r
+               @Parameter(names = { "--tempFolder" }, description = "Folder for server to extract built-in webapp")\r
                public String temp = FILESETTINGS.getString(Keys.server.tempFolder, "temp");\r
 \r
                /*\r
@@ -355,8 +355,8 @@ public class GitBlitServer {
                /*\r
                 * Authentication Parameters\r
                 */\r
-               @Parameter(names = { "--realmFile" }, description = "Users Realm Hash File")\r
-               public String realmFile = FILESETTINGS.getString(Keys.realm.realmFile, "users.properties");\r
+               @Parameter(names = { "--userService" }, description = "Authentication and Authorization Service (filename or fully qualified classname)")\r
+               public String userService = FILESETTINGS.getString(Keys.realm.userService, "users.properties");\r
 \r
                /*\r
                 * JETTY Parameters\r
diff --git a/src/com/gitblit/ILoginService.java b/src/com/gitblit/ILoginService.java
deleted file mode 100644 (file)
index 0e706cf..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*\r
- * Copyright 2011 gitblit.com.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-package com.gitblit;\r
-\r
-import java.util.List;\r
-\r
-import com.gitblit.models.UserModel;\r
-\r
-public interface ILoginService {\r
-\r
-       UserModel authenticate(String username, char[] password);\r
-\r
-       UserModel getUserModel(String username);\r
-\r
-       boolean updateUserModel(UserModel model);\r
-\r
-       boolean updateUserModel(String username, UserModel model);\r
-\r
-       boolean deleteUserModel(UserModel model);\r
-\r
-       boolean deleteUser(String username);\r
-\r
-       List<String> getAllUsernames();\r
-\r
-       List<String> getUsernamesForRole(String role);\r
-\r
-       boolean setUsernamesForRole(String role, List<String> usernames);\r
-\r
-       boolean renameRole(String oldRole, String newRole);\r
-\r
-       boolean deleteRole(String role);\r
-       \r
-       String toString();\r
-}\r
index 6fcb437ef624eee04dcbcb8d389cd6eb6cf46c0d..e220a81c48ae3122098820c392e55751431fc11f 100644 (file)
@@ -27,7 +27,7 @@ import com.gitblit.utils.StringUtils;
 public abstract class IStoredSettings {\r
 \r
        protected final Logger logger;\r
-       \r
+\r
        protected final Properties overrides = new Properties();\r
 \r
        public IStoredSettings(Class<? extends IStoredSettings> clazz) {\r
@@ -35,7 +35,7 @@ public abstract class IStoredSettings {
        }\r
 \r
        protected abstract Properties read();\r
-       \r
+\r
        private Properties getSettings() {\r
                Properties props = read();\r
                props.putAll(overrides);\r
@@ -110,7 +110,7 @@ public abstract class IStoredSettings {
                }\r
                return strings;\r
        }\r
-       \r
+\r
        public void overrideSetting(String key, String value) {\r
                overrides.put(key, value);\r
        }\r
diff --git a/src/com/gitblit/IUserService.java b/src/com/gitblit/IUserService.java
new file mode 100644 (file)
index 0000000..d0d0105
--- /dev/null
@@ -0,0 +1,53 @@
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit;\r
+\r
+import java.util.List;\r
+\r
+import com.gitblit.models.UserModel;\r
+\r
+public interface IUserService {\r
+\r
+       boolean supportsCookies();\r
+\r
+       char[] getCookie(UserModel model);\r
+\r
+       UserModel authenticate(char[] cookie);\r
+\r
+       UserModel authenticate(String username, char[] password);\r
+\r
+       UserModel getUserModel(String username);\r
+\r
+       boolean updateUserModel(UserModel model);\r
+\r
+       boolean updateUserModel(String username, UserModel model);\r
+\r
+       boolean deleteUserModel(UserModel model);\r
+\r
+       boolean deleteUser(String username);\r
+\r
+       List<String> getAllUsernames();\r
+\r
+       List<String> getUsernamesForRepository(String role);\r
+\r
+       boolean setUsernamesForRepository(String role, List<String> usernames);\r
+\r
+       boolean renameRepositoryRole(String oldRole, String newRole);\r
+\r
+       boolean deleteRepositoryRole(String role);\r
+\r
+       String toString();\r
+}\r
index 66dc467a358d66ab3c6da65463c894505a9ed0cb..998949adfdda64122062f3da69897e8a5f3a55ab 100644 (file)
@@ -62,7 +62,7 @@ public class SyndicationServlet extends HttpServlet {
                }\r
                return url.toString();\r
        }\r
-       \r
+\r
        public static String getTitle(String repository, String objectId) {\r
                String id = objectId;\r
                if (!StringUtils.isEmpty(id)) {\r
diff --git a/src/com/gitblit/Thumbnailer.java b/src/com/gitblit/Thumbnailer.java
deleted file mode 100644 (file)
index 5976f25..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*\r
- * Copyright 2011 gitblit.com.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-package com.gitblit;\r
-\r
-import java.awt.Dimension;\r
-import java.awt.Image;\r
-import java.awt.image.BufferedImage;\r
-import java.io.File;\r
-import java.io.FileOutputStream;\r
-import java.io.FilenameFilter;\r
-import java.io.IOException;\r
-import java.util.Iterator;\r
-\r
-import javax.imageio.ImageIO;\r
-import javax.imageio.ImageReader;\r
-import javax.imageio.stream.ImageInputStream;\r
-\r
-import com.beust.jcommander.JCommander;\r
-import com.beust.jcommander.Parameter;\r
-import com.beust.jcommander.ParameterException;\r
-import com.beust.jcommander.Parameters;\r
-\r
-public class Thumbnailer {\r
-\r
-       public static void main(String[] args) {\r
-               Params params = new Params();\r
-               JCommander jc = new JCommander(params);\r
-               try {\r
-                       jc.parse(args);\r
-               } catch (ParameterException t) {\r
-                       System.err.println(t.getMessage());\r
-                       jc.usage();\r
-               }\r
-               createImageThumbnail(params.sourceFolder, params.destinationFolder, params.maximumDimension);\r
-       }\r
-\r
-       public static void createImageThumbnail(String sourceFolder, String destinationFolder,\r
-                       int maxDimension) {\r
-               if (maxDimension <= 0)\r
-                       return;\r
-               File source = new File(sourceFolder);\r
-               File destination = new File(destinationFolder);\r
-               destination.mkdirs();\r
-               File[] sourceFiles = source.listFiles(new FilenameFilter() {\r
-                       @Override\r
-                       public boolean accept(File dir, String name) {\r
-                               return name.toLowerCase().endsWith(".png");\r
-                       }\r
-               });\r
-\r
-               for (File sourceFile : sourceFiles) {\r
-                       File destinationFile = new File(destination, sourceFile.getName());\r
-                       try {\r
-                               Dimension sz = getImageDimensions(sourceFile);\r
-                               int w = 0;\r
-                               int h = 0;\r
-                               if (sz.width > maxDimension) {\r
-                                       // Scale to Width\r
-                                       w = maxDimension;\r
-                                       float f = maxDimension;\r
-                                       h = (int) ((f / sz.width) * sz.height); // normalize height\r
-                               } else if (sz.height > maxDimension) {\r
-                                       // Scale to Height\r
-                                       h = maxDimension;\r
-                                       float f = maxDimension;\r
-                                       w = (int) ((f / sz.height) * sz.width); // normalize width\r
-                               } else {\r
-                                       // No thumbnail\r
-                                       return;\r
-                               }\r
-                               System.out.println("Generating thumbnail for " + sourceFile.getName() + " as (" + w\r
-                                               + "," + h + ")");\r
-                               BufferedImage image = ImageIO.read(sourceFile);\r
-                               Image scaledImage = image.getScaledInstance(w, h, BufferedImage.SCALE_SMOOTH);\r
-                               BufferedImage destImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);\r
-                               destImage.createGraphics().drawImage(scaledImage, 0, 0, null);\r
-                               FileOutputStream fos = new FileOutputStream(destinationFile);\r
-                               ImageIO.write(destImage, "png", fos);\r
-                               fos.flush();\r
-                               fos.getFD().sync();\r
-                               fos.close();\r
-                       } catch (Throwable t) {\r
-                               t.printStackTrace();\r
-                       }\r
-               }\r
-       }\r
-\r
-       public static Dimension getImageDimensions(File file) throws IOException {\r
-               ImageInputStream in = ImageIO.createImageInputStream(file);\r
-               try {\r
-                       final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);\r
-                       if (readers.hasNext()) {\r
-                               ImageReader reader = readers.next();\r
-                               try {\r
-                                       reader.setInput(in);\r
-                                       return new Dimension(reader.getWidth(0), reader.getHeight(0));\r
-                               } finally {\r
-                                       reader.dispose();\r
-                               }\r
-                       }\r
-               } finally {\r
-                       if (in != null)\r
-                               in.close();\r
-               }\r
-               return null;\r
-       }\r
-\r
-       @Parameters(separators = " ")\r
-       private static class Params {\r
-\r
-               @Parameter(names = { "--sourceFolder" }, description = "Source folder for raw images", required = true)\r
-               public String sourceFolder;\r
-\r
-               @Parameter(names = { "--destinationFolder" }, description = "Destination folder for thumbnails", required = true)\r
-               public String destinationFolder;\r
-\r
-               @Parameter(names = { "--maxDimension" }, description = "Maximum width or height for thumbnail", required = true)\r
-               public int maximumDimension;\r
-\r
-       }\r
-}\r
index 0ff2a3e76ceddb587b06a2f5344f2f3583dc1e9f..dff5700e217bbfa511672da845d15583ac00959d 100644 (file)
@@ -20,20 +20,28 @@ import java.util.Properties;
 \r
 import javax.servlet.ServletContext;\r
 \r
+import com.gitblit.utils.StringUtils;\r
+\r
 public class WebXmlSettings extends IStoredSettings {\r
 \r
        private final Properties properties = new Properties();\r
-       \r
+\r
        public WebXmlSettings(ServletContext context) {\r
                super(WebXmlSettings.class);\r
                Enumeration<?> keys = context.getInitParameterNames();\r
                while (keys.hasMoreElements()) {\r
                        String key = keys.nextElement().toString();\r
                        String value = context.getInitParameter(key);\r
-                       properties.put(key, value);\r
+                       properties.put(key, decodeValue(value));\r
+                       logger.debug(key + "=" + properties.getProperty(key));\r
                }\r
        }\r
        \r
+       private String decodeValue(String value) {\r
+               // Decode escaped backslashes and HTML entities\r
+               return StringUtils.decodeFromHtml(value).replace("\\\\", "\\");\r
+       }\r
+\r
        @Override\r
        protected Properties read() {\r
                return properties;\r
index 29647088df234c00e3f89c7e7a491dc83f9c8f10..fd355fbb9441b251589e768ea514d74f83d82245 100644 (file)
@@ -43,7 +43,7 @@ public class UserModel implements Principal, Serializable {
        }\r
 \r
        @Override\r
-       public String getName() {       \r
+       public String getName() {\r
                return username;\r
        }\r
 \r
index 5656efb3d9595e807d4dc5d2a7a05e5269612578..1c607ca794c7695e7b1741e61badd8661286a5f4 100644 (file)
@@ -402,12 +402,12 @@ public class JGitUtils {
 \r
        public static List<PathChangeModel> getFilesInCommit(Repository r, RevCommit commit) {\r
                List<PathChangeModel> list = new ArrayList<PathChangeModel>();\r
-               RevWalk rw = new RevWalk(r);            \r
+               RevWalk rw = new RevWalk(r);\r
                try {\r
                        if (commit == null) {\r
                                ObjectId object = r.resolve(Constants.HEAD);\r
                                commit = rw.parseCommit(object);\r
-                       }                       \r
+                       }\r
 \r
                        if (commit.getParentCount() == 0) {\r
                                TreeWalk tw = new TreeWalk(r);\r
@@ -441,7 +441,7 @@ public class JGitUtils {
                } catch (Throwable t) {\r
                        LOGGER.error("failed to determine files in commit!", t);\r
                } finally {\r
-                       rw.dispose();                   \r
+                       rw.dispose();\r
                }\r
                return list;\r
        }\r
@@ -526,6 +526,9 @@ public class JGitUtils {
        public static List<RevCommit> getRevLog(Repository r, String objectId, String path, int offset,\r
                        int maxCount) {\r
                List<RevCommit> list = new ArrayList<RevCommit>();\r
+               if (maxCount == 0) {\r
+                       return list;\r
+               }\r
                if (!hasCommits(r)) {\r
                        return list;\r
                }\r
@@ -591,6 +594,9 @@ public class JGitUtils {
                        final SearchType type, int offset, int maxCount) {\r
                final String lcValue = value.toLowerCase();\r
                List<RevCommit> list = new ArrayList<RevCommit>();\r
+               if (maxCount == 0) {\r
+                       return list;\r
+               }\r
                if (!hasCommits(r)) {\r
                        return list;\r
                }\r
@@ -677,6 +683,9 @@ public class JGitUtils {
 \r
        private static List<RefModel> getRefs(Repository r, String refs, boolean fullName, int maxCount) {\r
                List<RefModel> list = new ArrayList<RefModel>();\r
+               if (maxCount == 0) {\r
+                       return list;\r
+               }\r
                try {\r
                        Map<String, Ref> map = r.getRefDatabase().getRefs(refs);\r
                        RevWalk rw = new RevWalk(r);\r
index b53b5e1580f0dd9f698632e0d0baa5d6f784ba3b..219699fc3e36a955e00653c1d84c88444374ed03 100644 (file)
@@ -58,6 +58,11 @@ public class StringUtils {
                return retStr.toString();\r
        }\r
 \r
+       public static String decodeFromHtml(String inStr) {\r
+               return inStr.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">")\r
+                               .replace("&quot;", "\"").replace("&nbsp;", " ");\r
+       }\r
+\r
        public static String encodeURL(String inStr) {\r
                StringBuffer retStr = new StringBuffer();\r
                int i = 0;\r
@@ -165,7 +170,7 @@ public class StringUtils {
                }\r
                return sb.toString();\r
        }\r
-       \r
+\r
        public static String getRootPath(String path) {\r
                if (path.indexOf('/') > -1) {\r
                        return path.substring(0, path.lastIndexOf('/'));\r
index 5a0eb90fc3f26f554d20a5e6f17f71637c94e121..06d5483737b65acd060a89a383d68bfa6d2ebe00 100644 (file)
@@ -19,13 +19,17 @@ import java.util.LinkedHashMap;
 import java.util.Map;\r
 import java.util.TimeZone;\r
 \r
+import javax.servlet.http.Cookie;\r
 import javax.servlet.http.HttpServletRequest;\r
 \r
 import org.apache.wicket.PageParameters;\r
 import org.apache.wicket.RestartResponseAtInterceptPageException;\r
+import org.apache.wicket.RestartResponseException;\r
 import org.apache.wicket.markup.html.WebPage;\r
 import org.apache.wicket.markup.html.basic.Label;\r
 import org.apache.wicket.markup.html.panel.FeedbackPanel;\r
+import org.apache.wicket.protocol.http.WebRequest;\r
+import org.apache.wicket.protocol.http.WebResponse;\r
 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;\r
 import org.slf4j.Logger;\r
 import org.slf4j.LoggerFactory;\r
@@ -34,6 +38,7 @@ import com.gitblit.Constants;
 import com.gitblit.Constants.AccessRestrictionType;\r
 import com.gitblit.GitBlit;\r
 import com.gitblit.Keys;\r
+import com.gitblit.models.UserModel;\r
 import com.gitblit.wicket.GitBlitWebSession;\r
 import com.gitblit.wicket.WicketUtils;\r
 import com.gitblit.wicket.panels.LinkPanel;\r
@@ -45,14 +50,40 @@ public abstract class BasePage extends WebPage {
        public BasePage() {\r
                super();\r
                logger = LoggerFactory.getLogger(getClass());\r
+               loginByCookie();\r
        }\r
 \r
        public BasePage(PageParameters params) {\r
                super(params);\r
                logger = LoggerFactory.getLogger(getClass());\r
+               loginByCookie();\r
+       }\r
+\r
+       private void loginByCookie() {\r
+               if (!GitBlit.getBoolean(Keys.web.allowCookieAuthentication, false)) {\r
+                       return;\r
+               }\r
+               UserModel user = null;\r
+\r
+               // Grab cookie from Browser Session\r
+               Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();\r
+               if (cookies != null && cookies.length > 0) {\r
+                       user = GitBlit.self().authenticate(cookies);\r
+               }\r
+\r
+               // Login the user\r
+               if (user != null) {\r
+                       // Set the user into the session\r
+                       GitBlitWebSession.get().setUser(user);\r
+\r
+                       // Set Cookie\r
+                       WebResponse response = (WebResponse) getRequestCycle().getResponse();\r
+                       GitBlit.self().setCookie(response, user);\r
+               }\r
        }\r
 \r
        protected void setupPage(String repositoryName, String pageName) {\r
+\r
                if (repositoryName != null && repositoryName.trim().length() > 0) {\r
                        add(new Label("title", getServerName() + " - " + repositoryName));\r
                } else {\r
@@ -122,7 +153,7 @@ public abstract class BasePage extends WebPage {
                HttpServletRequest req = servletWebRequest.getHttpServletRequest();\r
                return req.getServerName();\r
        }\r
-       \r
+\r
        public void warn(String message, Throwable t) {\r
                logger.warn(message, t);\r
        }\r
@@ -131,7 +162,7 @@ public abstract class BasePage extends WebPage {
                logger.error(message);\r
                if (redirect) {\r
                        GitBlitWebSession.get().cacheErrorMessage(message);\r
-                       throw new RestartResponseAtInterceptPageException(getApplication().getHomePage());\r
+                       throw new RestartResponseException(getApplication().getHomePage());\r
                } else {\r
                        super.error(message);\r
                }\r
@@ -141,9 +172,18 @@ public abstract class BasePage extends WebPage {
                logger.error(message, t);\r
                if (redirect) {\r
                        GitBlitWebSession.get().cacheErrorMessage(message);\r
-                       throw new RestartResponseAtInterceptPageException(getApplication().getHomePage());\r
+                       throw new RestartResponseException(getApplication().getHomePage());\r
                } else {\r
                        super.error(message);\r
                }\r
        }\r
+\r
+       public void authenticationError(String message) {\r
+               logger.error(message);\r
+               if (GitBlitWebSession.get().isLoggedIn()) {\r
+                       error(message, true);\r
+               } else {\r
+                       throw new RestartResponseAtInterceptPageException(LoginPage.class);\r
+               }\r
+       }\r
 }\r
index a34917b62a0e06a680307794eabc3a899ff0a1ea..3e3dcb8bbb622768418049f33446814cb0e95d39 100644 (file)
@@ -128,8 +128,8 @@ public class CommitPage extends RepositoryPage {
                                                SearchType.AUTHOR));\r
                                item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef\r
                                                .getAuthorIdent().getWhen(), getTimeZone()));\r
-                               item.add(new Label("noteContent", GitBlit.self().processCommitMessage(repositoryName, entry.content))\r
-                                               .setEscapeModelStrings(false));\r
+                               item.add(new Label("noteContent", GitBlit.self().processCommitMessage(\r
+                                               repositoryName, entry.content)).setEscapeModelStrings(false));\r
                        }\r
                };\r
                add(notesView.setVisible(notes.size() > 0));\r
index 2f899bbeb701217a56950a57eb224d79ddaebdaa..40518b5cf1979919a2e78df2fdc8aac249fa0ca7 100644 (file)
@@ -64,8 +64,8 @@ public class DocsPage extends RepositoryPage {
                                                .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
                                item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils\r
                                                .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
-                               item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class,\r
-                                               WicketUtils.newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                               item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
+                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
                                item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
                                                .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
                                WicketUtils.setAlternatingBackground(item, counter);\r
index 63916276725bd32d6d44a90816270a03feee6962..8f68ac28ef945b3eb328555f3e2ee11733e10f65 100644 (file)
@@ -130,7 +130,8 @@ public class EditUserPage extends BasePage {
                                        String type = GitBlit.getString(Keys.realm.passwordStorage, "md5");\r
                                        if (type.equalsIgnoreCase("md5")) {\r
                                                // store MD5 digest of password\r
-                                               userModel.password = StringUtils.MD5_TYPE + StringUtils.getMD5(userModel.password);\r
+                                               userModel.password = StringUtils.MD5_TYPE\r
+                                                               + StringUtils.getMD5(userModel.password);\r
                                        }\r
                                }\r
 \r
index 2cd787c7af9358042b5bee90cf056c4454dd0f22..c012538f1c96c6349abe289c9be67fad9ba834d2 100644 (file)
@@ -27,7 +27,7 @@ public class LogPage extends RepositoryPage {
                super(params);\r
 \r
                addSyndicationDiscoveryLink();\r
-               \r
+\r
                int pageNumber = WicketUtils.getPage(params);\r
                int prevPage = Math.max(0, pageNumber - 1);\r
                int nextPage = pageNumber + 1;\r
index 971ba3270b182104c40aba4a231598a4ba01a424..6ee72db39bbf6d89140bb5e10f9586d2287cdfab 100644 (file)
  */\r
 package com.gitblit.wicket.pages;\r
 \r
+import javax.servlet.http.Cookie;\r
+\r
 import org.apache.wicket.PageParameters;\r
+import org.apache.wicket.RestartResponseException;\r
 import org.apache.wicket.markup.html.WebPage;\r
 import org.apache.wicket.markup.html.basic.Label;\r
 import org.apache.wicket.markup.html.form.PasswordTextField;\r
@@ -24,6 +27,8 @@ import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.panel.FeedbackPanel;\r
 import org.apache.wicket.model.IModel;\r
 import org.apache.wicket.model.Model;\r
+import org.apache.wicket.protocol.http.WebRequest;\r
+import org.apache.wicket.protocol.http.WebResponse;\r
 \r
 import com.gitblit.Constants;\r
 import com.gitblit.GitBlit;\r
@@ -42,8 +47,11 @@ public class LoginPage extends WebPage {
                // If we are already logged in because user directly accessed\r
                // the login url, redirect to the home page\r
                if (GitBlitWebSession.get().isLoggedIn()) {\r
-                       setRedirect(true);\r
-                       setResponsePage(getApplication().getHomePage());\r
+                       throw new RestartResponseException(getApplication().getHomePage());\r
+               }\r
+\r
+               if (GitBlit.getBoolean(Keys.web.allowCookieAuthentication, false)) {\r
+                       loginByCookie();\r
                }\r
 \r
                add(new Label("title", GitBlit.getString(Keys.web.siteName, Constants.NAME)));\r
@@ -72,11 +80,30 @@ public class LoginPage extends WebPage {
                add(loginForm);\r
        }\r
 \r
+       private void loginByCookie() {\r
+               UserModel user = null;\r
+\r
+               // Grab cookie from Browser Session\r
+               Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();\r
+               if (cookies != null && cookies.length > 0) {\r
+                       user = GitBlit.self().authenticate(cookies);\r
+               }\r
+\r
+               // Login the user\r
+               loginUser(user);\r
+       }\r
+\r
        private void loginUser(UserModel user) {\r
                if (user != null) {\r
                        // Set the user into the session\r
                        GitBlitWebSession.get().setUser(user);\r
 \r
+                       // Set Cookie\r
+                       if (GitBlit.getBoolean(Keys.web.allowCookieAuthentication, false)) {\r
+                               WebResponse response = (WebResponse) getRequestCycle().getResponse();\r
+                               GitBlit.self().setCookie(response, user);\r
+                       }\r
+\r
                        if (!continueToOriginalDestination()) {\r
                                // Redirect to home page\r
                                setResponsePage(getApplication().getHomePage());\r
index 05beab3c6068ee817238a7cbce6699c7e0bd1623..b049e8e05a857a5021ba376e0f156452d42836bf 100644 (file)
 package com.gitblit.wicket.pages;\r
 \r
 import org.apache.wicket.markup.html.WebPage;\r
+import org.apache.wicket.protocol.http.WebResponse;\r
+\r
+import com.gitblit.GitBlit;\r
+import com.gitblit.wicket.GitBlitWebSession;\r
 \r
 public class LogoutPage extends WebPage {\r
 \r
        public LogoutPage() {\r
-               getSession().invalidate();\r
+               GitBlitWebSession.get().invalidate();\r
+               GitBlit.self().setCookie(((WebResponse) getResponse()), null);\r
                setRedirect(true);\r
                setResponsePage(getApplication().getHomePage());\r
        }\r
index 00ed7554e6f7aea04834bbdc82a8b1dee56a977c..22d3323acb2a20916f78c6d0ce098963e4aa8a51 100644 (file)
@@ -15,6 +15,7 @@
  */\r
 package com.gitblit.wicket.pages;\r
 \r
+import java.io.Serializable;\r
 import java.text.MessageFormat;\r
 import java.util.ArrayList;\r
 import java.util.Arrays;\r
@@ -205,7 +206,7 @@ public abstract class RepositoryPage extends BasePage {
                        RepositoryModel model = GitBlit.self().getRepositoryModel(\r
                                        GitBlitWebSession.get().getUser(), repositoryName);\r
                        if (model == null) {\r
-                               error("Unauthorized access for repository " + repositoryName, true);\r
+                               authenticationError("Unauthorized access for repository " + repositoryName);\r
                                return null;\r
                        }\r
                        m = model;\r
@@ -333,7 +334,9 @@ public abstract class RepositoryPage extends BasePage {
                return WicketUtils.newPathParameter(repositoryName, objectId, path);\r
        }\r
 \r
-       private static class PageRegistration {\r
+       private static class PageRegistration implements Serializable {\r
+               private static final long serialVersionUID = 1L;\r
+\r
                final String translationKey;\r
                final Class<? extends BasePage> pageClass;\r
 \r
@@ -343,7 +346,7 @@ public abstract class RepositoryPage extends BasePage {
                }\r
        }\r
 \r
-       private static class SearchForm extends StatelessForm<Void> {\r
+       private static class SearchForm extends StatelessForm<Void> implements Serializable {\r
                private static final long serialVersionUID = 1L;\r
 \r
                private final String repositoryName;\r
index bbf8979819b20d0b6cd5baa7238b896713a6bb6d..35ad3477fd5ac1b84c7b8b3fba4ffdb64529e9f5 100644 (file)
        <!-- commits -->\r
        <div style="padding-bottom:10px;" wicket:id="commitsPanel">[commits panel]</div>        \r
 \r
-       <!-- branches -->\r
-       <div style="padding-bottom:10px;width:400px; float:left;">\r
-               <div wicket:id="branchesPanel">[branches panel]</div>\r
-       </div>\r
-\r
        <!-- tags -->\r
-       <div style="padding-bottom:10px;margin-left:405px;">\r
-               <div wicket:id="tagsPanel">[tags panel]</div>\r
-       </div>\r
+       <div style="padding-bottom:10px;" wicket:id="tagsPanel">[tags panel]</div>\r
+\r
+       <!-- branches -->\r
+       <div style="padding-bottom:10px;" wicket:id="branchesPanel">[branches panel]</div>\r
        \r
        <!-- markdown readme -->\r
        <div wicket:id="readme" class="markdown" style="clear:both;padding-bottom:5px;"></div>\r
index e31375c080df6e5c46c25a57a2274aceea4b2443..39b8a97e9c923132d66c1ff7bf13c990d31003bf 100644 (file)
@@ -58,18 +58,11 @@ public class SummaryPage extends RepositoryPage {
        public SummaryPage(PageParameters params) {\r
                super(params);\r
 \r
-               int numCommitsDef = 20;\r
-               int numRefsDef = 5;\r
-\r
-               int numberCommits = GitBlit.getInteger(Keys.web.summaryCommitCount, numCommitsDef);\r
+               int numberCommits = GitBlit.getInteger(Keys.web.summaryCommitCount, 20);\r
                if (numberCommits <= 0) {\r
-                       numberCommits = numCommitsDef;\r
-               }\r
-\r
-               int numberRefs = GitBlit.getInteger(Keys.web.summaryRefsCount, numRefsDef);\r
-               if (numberRefs <= 0) {\r
-                       numberRefs = numRefsDef;\r
+                       numberCommits = 20;\r
                }\r
+               int numberRefs = GitBlit.getInteger(Keys.web.summaryRefsCount, 5);\r
 \r
                Repository r = getRepository();\r
                List<Metric> metrics = null;\r
@@ -78,7 +71,7 @@ public class SummaryPage extends RepositoryPage {
                        metrics = MetricUtils.getDateMetrics(r, null, true, null);\r
                        metricsTotal = metrics.remove(0);\r
                }\r
-               \r
+\r
                addSyndicationDiscoveryLink();\r
 \r
                // repository description\r
@@ -121,7 +114,7 @@ public class SummaryPage extends RepositoryPage {
                                add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));\r
                        }\r
                        StringBuilder sb = new StringBuilder();\r
-                       sb.append(WicketUtils.getHostURL(getRequestCycle().getRequest()));                                      \r
+                       sb.append(WicketUtils.getHostURL(getRequestCycle().getRequest()));\r
                        sb.append(Constants.GIT_PATH);\r
                        sb.append(repositoryName);\r
                        repositoryUrls.add(sb.toString());\r
index 7e87067a61fbf030a3768d6090bfd0a6570dc7c1..c58f42ef304c9f50dbee8cfaaebbf60c1440fbd7 100644 (file)
@@ -8,14 +8,15 @@
 <wicket:panel>\r
 \r
        <!-- header -->\r
-       <div class="header" wicket:id="branches">[branches header]</div>        \r
+       <div class="header"><img style="vertical-align: top;" src="commit_branch_16x16.png"></img><span wicket:id="branches">[branches header]</span></div>     \r
        \r
        <table class="pretty">\r
                <tbody>\r
                        <tr wicket:id="branch">\r
                        <td class="date"><span wicket:id="branchDate">[branch date]</span></td>\r
                        <td><span wicket:id="branchName">[branch name]</span></td>\r
-                       <td><span wicket:id="branchType">[branch type]</span></td>\r
+                       <td class="author"><span wicket:id="branchAuthor">[branch author]</span></td>\r
+                       <td><span wicket:id="branchLog">[branch log]</span></td>\r
                        <td class="rightAlign">\r
                                <span wicket:id="branchLinks"></span>\r
                                </td>\r
index 302b48dd160a83fe62283efdc8862fe8c55ec57f..8e58d67350ecb8b7e14602c0c988a37121a7755b 100644 (file)
@@ -27,18 +27,20 @@ import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;\r
 import org.apache.wicket.markup.repeater.data.ListDataProvider;\r
 import org.apache.wicket.model.StringResourceModel;\r
-import org.eclipse.jgit.lib.Constants;\r
 import org.eclipse.jgit.lib.Repository;\r
 \r
 import com.gitblit.SyndicationServlet;\r
 import com.gitblit.models.RefModel;\r
 import com.gitblit.models.RepositoryModel;\r
 import com.gitblit.utils.JGitUtils;\r
+import com.gitblit.utils.JGitUtils.SearchType;\r
 import com.gitblit.utils.StringUtils;\r
 import com.gitblit.wicket.WicketUtils;\r
 import com.gitblit.wicket.pages.BranchesPage;\r
+import com.gitblit.wicket.pages.CommitPage;\r
 import com.gitblit.wicket.pages.LogPage;\r
 import com.gitblit.wicket.pages.MetricsPage;\r
+import com.gitblit.wicket.pages.SearchPage;\r
 import com.gitblit.wicket.pages.SummaryPage;\r
 import com.gitblit.wicket.pages.TreePage;\r
 \r
@@ -90,11 +92,24 @@ public class BranchesPanel extends BasePanel {
                                                entry.displayName, 28), LogPage.class, WicketUtils.newObjectParameter(\r
                                                model.name, entry.getName())));\r
 \r
-                               // only show branch type on the branches page\r
-                               boolean remote = entry.getName().startsWith(Constants.R_REMOTES);\r
-                               item.add(new Label("branchType", remote ? getString("gb.remote")\r
-                                               : getString("gb.local")).setVisible(maxCount <= 0));\r
-\r
+                               String author = entry.getAuthorIdent().getName();\r
+                               LinkPanel authorLink = new LinkPanel("branchAuthor", "list", author,\r
+                                               SearchPage.class, WicketUtils.newSearchParameter(model.name, entry.getName(),\r
+                                                               author, SearchType.AUTHOR));\r
+                               setPersonSearchTooltip(authorLink, author, SearchType.AUTHOR);\r
+                               item.add(authorLink);\r
+                               \r
+                               // short message\r
+                               String shortMessage = entry.getShortMessage();\r
+                               String trimmedMessage = StringUtils.trimShortLog(shortMessage);\r
+                               LinkPanel shortlog = new LinkPanel("branchLog", "list subject",\r
+                                               trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(\r
+                                                               model.name, entry.getName()));\r
+                               if (!shortMessage.equals(trimmedMessage)) {\r
+                                       WicketUtils.setHtmlTooltip(shortlog, shortMessage);\r
+                               }\r
+                               item.add(shortlog);\r
+                               \r
                                if (maxCount <= 0) {\r
                                        Fragment fragment = new Fragment("branchLinks", "branchPageLinks", this);\r
                                        fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils\r
@@ -103,8 +118,9 @@ public class BranchesPanel extends BasePanel {
                                                        .newObjectParameter(model.name, entry.getName())));\r
                                        fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,\r
                                                        WicketUtils.newObjectParameter(model.name, entry.getName())));\r
-                                       fragment.add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()\r
-                                                       .getRelativePathPrefixToContextRoot(), model.name, entry.getName(), 0)));\r
+                                       fragment.add(new ExternalLink("syndication", SyndicationServlet.asLink(\r
+                                                       getRequest().getRelativePathPrefixToContextRoot(), model.name,\r
+                                                       entry.getName(), 0)));\r
                                        item.add(fragment);\r
                                } else {\r
                                        Fragment fragment = new Fragment("branchLinks", "branchPanelLinks", this);\r
index 1ca92851a5db49123f34aa899a175576167bba00..712a66286e6ed166a1d61e540155b8e9cf3c69bd 100644 (file)
@@ -8,8 +8,7 @@
 <wicket:panel>\r
 \r
        <!-- header --> \r
-       <div class="header" wicket:id="header">[log header]</div>\r
-               \r
+       <div class="header"><img style="vertical-align: top;" src="commit_changes_16x16.png"></img><span wicket:id="header">[log header]</span></div>\r
        <table class="pretty">\r
                <tbody>\r
                        <tr wicket:id="commit">\r
index 759040e672585ec08bb469f745f56cc41cc91d5b..d118790c033259b25c2077f172d1169643375f20 100644 (file)
@@ -74,7 +74,7 @@ public class SearchPanel extends BasePanel {
 \r
                // header\r
                add(new CommitHeaderPanel("commitHeader", repositoryName, commit));\r
-               \r
+\r
                add(new Label("searchString", value));\r
                add(new Label("searchType", searchType.toString()));\r
 \r
index 481d8e812c3603d3b12c972429a9cead66e1967b..86eedd60d45437a414d1a199070e3b7900258abd 100644 (file)
@@ -8,7 +8,7 @@
 <wicket:panel>\r
 \r
        <!-- tags -->\r
-       <div class="header" wicket:id="header">[tags header]</div>      \r
+       <div class="header"><img style="vertical-align: top;" src="tag_16x16.png"></img><span wicket:id="header">[tags header]</span></div>     \r
        <table class="pretty">\r
                <tbody>\r
                <tr wicket:id="tag">\r
index 95bc8575b994956bafd3ae84b12726ae9ad2849a..58cb458645d3a1836ddb443f948cbdfbf3a42d1a 100644 (file)
@@ -90,13 +90,10 @@ public class TagsPanel extends BasePanel {
                                item.add(new LinkPanel("tagName", "list name", entry.displayName, linkClass,\r
                                                WicketUtils.newObjectParameter(repositoryName, entry\r
                                                                .getReferencedObjectId().getName())));\r
-                               String message;\r
-                               if (maxCount > 0) {\r
-                                       message = StringUtils.trimString(entry.getShortMessage(), 40);\r
-                               } else {\r
-                                       // workaround for RevTag returning a lengthy shortlog. :(\r
-                                       message = StringUtils.trimShortLog(entry.getShortMessage());\r
-                               }\r
+                               \r
+                               // workaround for RevTag returning a lengthy shortlog. :(\r
+                               String message = StringUtils.trimShortLog(entry.getShortMessage());\r
+\r
                                if (linkClass.equals(BlobPage.class)) {\r
                                        // Blob Tag Object\r
                                        item.add(WicketUtils.newImage("tagIcon", "file_16x16.png"));\r
diff --git a/src/org/eclipse/jgit/api/BlameCommand.java b/src/org/eclipse/jgit/api/BlameCommand.java
new file mode 100644 (file)
index 0000000..400d94b
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2011, GitHub Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.api;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.blame.BlameGenerator;
+import org.eclipse.jgit.blame.BlameResult;
+import org.eclipse.jgit.diff.DiffAlgorithm;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Blame command for building a {@link BlameResult} for a file path.
+ */
+public class BlameCommand extends GitCommand<BlameResult> {
+
+       private String path;
+
+       private DiffAlgorithm diffAlgorithm;
+
+       private RawTextComparator textComparator;
+
+       private ObjectId startCommit;
+
+       private Collection<ObjectId> reverseEndCommits;
+
+       private Boolean followFileRenames;
+
+       /**
+        * @param repo
+        */
+       public BlameCommand(Repository repo) {
+               super(repo);
+       }
+
+       /**
+        * Set file path
+        *
+        * @param filePath
+        * @return this command
+        */
+       public BlameCommand setFilePath(String filePath) {
+               this.path = filePath;
+               return this;
+       }
+
+       /**
+        * Set diff algorithm
+        *
+        * @param diffAlgorithm
+        * @return this command
+        */
+       public BlameCommand setDiffAlgorithm(DiffAlgorithm diffAlgorithm) {
+               this.diffAlgorithm = diffAlgorithm;
+               return this;
+       }
+
+       /**
+        * Set raw text comparator
+        *
+        * @param textComparator
+        * @return this command
+        */
+       public BlameCommand setTextComparator(RawTextComparator textComparator) {
+               this.textComparator = textComparator;
+               return this;
+       }
+
+       /**
+        * Set start commit id
+        *
+        * @param commit
+        * @return this command
+        */
+       public BlameCommand setStartCommit(AnyObjectId commit) {
+               this.startCommit = commit.toObjectId();
+               return this;
+       }
+
+       /**
+        * Enable (or disable) following file renames.
+        * <p>
+        * If true renames are followed using the standard FollowFilter behavior
+        * used by RevWalk (which matches {@code git log --follow} in the C
+        * implementation). This is not the same as copy/move detection as
+        * implemented by the C implementation's of {@code git blame -M -C}.
+        *
+        * @param follow
+        *            enable following.
+        * @return {@code this}
+        */
+       public BlameCommand setFollowFileRenames(boolean follow) {
+               followFileRenames = Boolean.valueOf(follow);
+               return this;
+       }
+
+       /**
+        * Configure the command to compute reverse blame (history of deletes).
+        *
+        * @param start
+        *            oldest commit to traverse from. The result file will be loaded
+        *            from this commit's tree.
+        * @param end
+        *            most recent commit to stop traversal at. Usually an active
+        *            branch tip, tag, or HEAD.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameCommand reverse(AnyObjectId start, AnyObjectId end)
+                       throws IOException {
+               return reverse(start, Collections.singleton(end.toObjectId()));
+       }
+
+       /**
+        * Configure the generator to compute reverse blame (history of deletes).
+        *
+        * @param start
+        *            oldest commit to traverse from. The result file will be loaded
+        *            from this commit's tree.
+        * @param end
+        *            most recent commits to stop traversal at. Usually an active
+        *            branch tip, tag, or HEAD.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameCommand reverse(AnyObjectId start, Collection<ObjectId> end)
+                       throws IOException {
+               startCommit = start.toObjectId();
+               reverseEndCommits = new ArrayList<ObjectId>(end);
+               return this;
+       }
+
+       /**
+        * Generate a list of lines with information about when the lines were
+        * introduced into the file path.
+        *
+        * @return list of lines
+        */
+       public BlameResult call() throws JGitInternalException {
+               checkCallable();
+               BlameGenerator gen = new BlameGenerator(repo, path);
+               try {
+                       if (diffAlgorithm != null)
+                               gen.setDiffAlgorithm(diffAlgorithm);
+                       if (textComparator != null)
+                               gen.setTextComparator(textComparator);
+                       if (followFileRenames != null)
+                               gen.setFollowFileRenames(followFileRenames.booleanValue());
+
+                       if (reverseEndCommits != null)
+                               gen.reverse(startCommit, reverseEndCommits);
+                       else if (startCommit != null)
+                               gen.push(null, startCommit);
+                       else {
+                               gen.push(null, repo.resolve(Constants.HEAD));
+                               if (!repo.isBare()) {
+                                       DirCache dc = repo.readDirCache();
+                                       int entry = dc.findEntry(path);
+                                       if (0 <= entry)
+                                               gen.push(null, dc.getEntry(entry).getObjectId());
+
+                                       File inTree = new File(repo.getWorkTree(), path);
+                                       if (inTree.isFile())
+                                               gen.push(null, new RawText(inTree));
+                               }
+                       }
+                       return gen.computeBlameResult();
+               } catch (IOException e) {
+                       throw new JGitInternalException(e.getMessage(), e);
+               } finally {
+                       gen.release();
+               }
+       }
+}
diff --git a/src/org/eclipse/jgit/blame/BlameGenerator.java b/src/org/eclipse/jgit/blame/BlameGenerator.java
new file mode 100644 (file)
index 0000000..286f4c1
--- /dev/null
@@ -0,0 +1,961 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.blame;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jgit.blame.Candidate.BlobCandidate;
+import org.eclipse.jgit.blame.Candidate.ReverseCandidate;
+import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
+import org.eclipse.jgit.diff.DiffAlgorithm;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.diff.EditList;
+import org.eclipse.jgit.diff.HistogramDiff;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.diff.RenameDetector;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/**
+ * Generate author information for lines based on introduction to the file.
+ * <p>
+ * Applications that want a simple one-shot computation of blame for a file
+ * should use {@link #computeBlameResult()} to prepare the entire result in one
+ * method call. This may block for significant time as the history of the
+ * repository must be traversed until information is gathered for every line.
+ * <p>
+ * Applications that want more incremental update behavior may use either the
+ * raw {@link #next()} streaming approach supported by this class, or construct
+ * a {@link BlameResult} using {@link BlameResult#create(BlameGenerator)} and
+ * incrementally construct the result with {@link BlameResult#computeNext()}.
+ * <p>
+ * This class is not thread-safe.
+ * <p>
+ * An instance of BlameGenerator can only be used once. To blame multiple files
+ * the application must create a new BlameGenerator.
+ * <p>
+ * During blame processing there are two files involved:
+ * <ul>
+ * <li>result - The file whose lines are being examined. This is the revision
+ * the user is trying to view blame/annotation information alongside of.</li>
+ * <li>source - The file that was blamed with supplying one or more lines of
+ * data into result. The source may be a different file path (due to copy or
+ * rename). Source line numbers may differ from result line numbers due to lines
+ * being added/removed in intermediate revisions.</li>
+ * </ul>
+ * <p>
+ * The blame algorithm is implemented by initially assigning responsibility for
+ * all lines of the result to the starting commit. A difference against the
+ * commit's ancestor is computed, and responsibility is passed to the ancestor
+ * commit for any lines that are common. The starting commit is blamed only for
+ * the lines that do not appear in the ancestor, if any. The loop repeats using
+ * the ancestor, until there are no more lines to acquire information on, or the
+ * file's creation point is discovered in history.
+ */
+public class BlameGenerator {
+       private final Repository repository;
+
+       private final PathFilter resultPath;
+
+       private final MutableObjectId idBuf;
+
+       /** Revision pool used to acquire commits from. */
+       private RevWalk revPool;
+
+       /** Indicates the commit has already been processed. */
+       private RevFlag SEEN;
+
+       private ObjectReader reader;
+
+       private TreeWalk treeWalk;
+
+       private DiffAlgorithm diffAlgorithm = new HistogramDiff();
+
+       private RawTextComparator textComparator = RawTextComparator.DEFAULT;
+
+       private RenameDetector renameDetector;
+
+       /** Potential candidates, sorted by commit time descending. */
+       private Candidate queue;
+
+       /** Number of lines that still need to be discovered. */
+       private int remaining;
+
+       /** Blame is currently assigned to this source. */
+       private Candidate currentSource;
+
+       /**
+        * Create a blame generator for the repository and path
+        *
+        * @param repository
+        *            repository to access revision data from.
+        * @param path
+        *            initial path of the file to start scanning.
+        */
+       public BlameGenerator(Repository repository, String path) {
+               this.repository = repository;
+               this.resultPath = PathFilter.create(path);
+
+               idBuf = new MutableObjectId();
+               setFollowFileRenames(true);
+               initRevPool(false);
+
+               remaining = -1;
+       }
+
+       private void initRevPool(boolean reverse) {
+               if (queue != null)
+                       throw new IllegalStateException();
+
+               if (revPool != null)
+                       revPool.release();
+
+               if (reverse)
+                       revPool = new ReverseWalk(getRepository());
+               else
+                       revPool = new RevWalk(getRepository());
+
+               revPool.setRetainBody(true);
+               SEEN = revPool.newFlag("SEEN");
+               reader = revPool.getObjectReader();
+               treeWalk = new TreeWalk(reader);
+       }
+
+       /** @return repository being scanned for revision history. */
+       public Repository getRepository() {
+               return repository;
+       }
+
+       /** @return path file path being processed. */
+       public String getResultPath() {
+               return resultPath.getPath();
+       }
+
+       /**
+        * Difference algorithm to use when comparing revisions.
+        *
+        * @param algorithm
+        * @return {@code this}
+        */
+       public BlameGenerator setDiffAlgorithm(DiffAlgorithm algorithm) {
+               diffAlgorithm = algorithm;
+               return this;
+       }
+
+       /**
+        * Text comparator to use when comparing revisions.
+        *
+        * @param comparator
+        * @return {@code this}
+        */
+       public BlameGenerator setTextComparator(RawTextComparator comparator) {
+               textComparator = comparator;
+               return this;
+       }
+
+       /**
+        * Enable (or disable) following file renames, on by default.
+        * <p>
+        * If true renames are followed using the standard FollowFilter behavior
+        * used by RevWalk (which matches {@code git log --follow} in the C
+        * implementation). This is not the same as copy/move detection as
+        * implemented by the C implementation's of {@code git blame -M -C}.
+        *
+        * @param follow
+        *            enable following.
+        * @return {@code this}
+        */
+       public BlameGenerator setFollowFileRenames(boolean follow) {
+               if (follow)
+                       renameDetector = new RenameDetector(getRepository());
+               else
+                       renameDetector = null;
+               return this;
+       }
+
+       /**
+        * Obtain the RenameDetector if {@code setFollowFileRenames(true)}.
+        *
+        * @return the rename detector, allowing the application to configure its
+        *         settings for rename score and breaking behavior.
+        */
+       public RenameDetector getRenameDetector() {
+               return renameDetector;
+       }
+
+       /**
+        * Push a candidate blob onto the generator's traversal stack.
+        * <p>
+        * Candidates should be pushed in history order from oldest-to-newest.
+        * Applications should push the starting commit first, then the index
+        * revision (if the index is interesting), and finally the working tree
+        * copy (if the working tree is interesting).
+        *
+        * @param description
+        *            description of the blob revision, such as "Working Tree".
+        * @param contents
+        *            contents of the file.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameGenerator push(String description, byte[] contents)
+                       throws IOException {
+               return push(description, new RawText(contents));
+       }
+
+       /**
+        * Push a candidate blob onto the generator's traversal stack.
+        * <p>
+        * Candidates should be pushed in history order from oldest-to-newest.
+        * Applications should push the starting commit first, then the index
+        * revision (if the index is interesting), and finally the working tree copy
+        * (if the working tree is interesting).
+        *
+        * @param description
+        *            description of the blob revision, such as "Working Tree".
+        * @param contents
+        *            contents of the file.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameGenerator push(String description, RawText contents)
+                       throws IOException {
+               if (description == null)
+                       // XXX description = JGitText.get().blameNotCommittedYet;
+                       description = "blame not committed yet";
+               BlobCandidate c = new BlobCandidate(description, resultPath);
+               c.sourceText = contents;
+               c.regionList = new Region(0, 0, contents.size());
+               remaining = contents.size();
+               push(c);
+               return this;
+       }
+
+       /**
+        * Push a candidate object onto the generator's traversal stack.
+        * <p>
+        * Candidates should be pushed in history order from oldest-to-newest.
+        * Applications should push the starting commit first, then the index
+        * revision (if the index is interesting), and finally the working tree copy
+        * (if the working tree is interesting).
+        *
+        * @param description
+        *            description of the blob revision, such as "Working Tree".
+        * @param id
+        *            may be a commit or a blob.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameGenerator push(String description, AnyObjectId id)
+                       throws IOException {
+               ObjectLoader ldr = reader.open(id);
+               if (ldr.getType() == OBJ_BLOB) {
+                       if (description == null)
+                               // XXX description = JGitText.get().blameNotCommittedYet;
+                               description = "blame not committed yet";
+                       BlobCandidate c = new BlobCandidate(description, resultPath);
+                       c.sourceBlob = id.toObjectId();
+                       c.sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
+                       c.regionList = new Region(0, 0, c.sourceText.size());
+                       remaining = c.sourceText.size();
+                       push(c);
+                       return this;
+               }
+
+               RevCommit commit = revPool.parseCommit(id);
+               if (!find(commit, resultPath))
+                       return this;
+
+               Candidate c = new Candidate(commit, resultPath);
+               c.sourceBlob = idBuf.toObjectId();
+               c.loadText(reader);
+               c.regionList = new Region(0, 0, c.sourceText.size());
+               remaining = c.sourceText.size();
+               push(c);
+               return this;
+       }
+
+       /**
+        * Configure the generator to compute reverse blame (history of deletes).
+        * <p>
+        * This method is expensive as it immediately runs a RevWalk over the
+        * history spanning the expression {@code start..end} (end being more recent
+        * than start) and then performs the equivalent operation as
+        * {@link #push(String, AnyObjectId)} to begin blame traversal from the
+        * commit named by {@code start} walking forwards through history until
+        * {@code end} blaming line deletions.
+        * <p>
+        * A reverse blame may produce multiple sources for the same result line,
+        * each of these is a descendant commit that removed the line, typically
+        * this occurs when the same deletion appears in multiple side branches such
+        * as due to a cherry-pick. Applications relying on reverse should use
+        * {@link BlameResult} as it filters these duplicate sources and only
+        * remembers the first (oldest) deletion.
+        *
+        * @param start
+        *            oldest commit to traverse from. The result file will be loaded
+        *            from this commit's tree.
+        * @param end
+        *            most recent commit to stop traversal at. Usually an active
+        *            branch tip, tag, or HEAD.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameGenerator reverse(AnyObjectId start, AnyObjectId end)
+                       throws IOException {
+               return reverse(start, Collections.singleton(end.toObjectId()));
+       }
+
+       /**
+        * Configure the generator to compute reverse blame (history of deletes).
+        * <p>
+        * This method is expensive as it immediately runs a RevWalk over the
+        * history spanning the expression {@code start..end} (end being more recent
+        * than start) and then performs the equivalent operation as
+        * {@link #push(String, AnyObjectId)} to begin blame traversal from the
+        * commit named by {@code start} walking forwards through history until
+        * {@code end} blaming line deletions.
+        * <p>
+        * A reverse blame may produce multiple sources for the same result line,
+        * each of these is a descendant commit that removed the line, typically
+        * this occurs when the same deletion appears in multiple side branches such
+        * as due to a cherry-pick. Applications relying on reverse should use
+        * {@link BlameResult} as it filters these duplicate sources and only
+        * remembers the first (oldest) deletion.
+        *
+        * @param start
+        *            oldest commit to traverse from. The result file will be loaded
+        *            from this commit's tree.
+        * @param end
+        *            most recent commits to stop traversal at. Usually an active
+        *            branch tip, tag, or HEAD.
+        * @return {@code this}
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameGenerator reverse(AnyObjectId start,
+                       Collection<? extends ObjectId> end) throws IOException {
+               initRevPool(true);
+
+               ReverseCommit result = (ReverseCommit) revPool.parseCommit(start);
+               if (!find(result, resultPath))
+                       return this;
+
+               revPool.markUninteresting(result);
+               for (ObjectId id : end)
+                       revPool.markStart(revPool.parseCommit(id));
+
+               while (revPool.next() != null) {
+                       // just pump the queue
+               }
+
+               ReverseCandidate c = new ReverseCandidate(result, resultPath);
+               c.sourceBlob = idBuf.toObjectId();
+               c.loadText(reader);
+               c.regionList = new Region(0, 0, c.sourceText.size());
+               remaining = c.sourceText.size();
+               push(c);
+               return this;
+       }
+
+       /**
+        * Execute the generator in a blocking fashion until all data is ready.
+        *
+        * @return the complete result. Null if no file exists for the given path.
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public BlameResult computeBlameResult() throws IOException {
+               try {
+                       BlameResult r = BlameResult.create(this);
+                       if (r != null)
+                               r.computeAll();
+                       return r;
+               } finally {
+                       release();
+               }
+       }
+
+       /**
+        * Step the blame algorithm one iteration.
+        *
+        * @return true if the generator has found a region's source. The getSource*
+        *         and {@link #getResultStart()}, {@link #getResultEnd()} methods
+        *         can be used to inspect the region found. False if there are no
+        *         more regions to describe.
+        * @throws IOException
+        *             repository cannot be read.
+        */
+       public boolean next() throws IOException {
+               // If there is a source still pending, produce the next region.
+               if (currentSource != null) {
+                       Region r = currentSource.regionList;
+                       Region n = r.next;
+                       remaining -= r.length;
+                       if (n != null) {
+                               currentSource.regionList = n;
+                               return true;
+                       }
+
+                       if (currentSource.queueNext != null)
+                               return result(currentSource.queueNext);
+
+                       currentSource = null;
+               }
+
+               // If there are no lines remaining, the entire result is done,
+               // even if there are revisions still available for the path.
+               if (remaining == 0)
+                       return done();
+
+               for (;;) {
+                       Candidate n = pop();
+                       if (n == null)
+                               return done();
+
+                       int pCnt = n.getParentCount();
+                       if (pCnt == 1) {
+                               if (processOne(n))
+                                       return true;
+
+                       } else if (1 < pCnt) {
+                               if (processMerge(n))
+                                       return true;
+
+                       } else if (n instanceof ReverseCandidate) {
+                               // Do not generate a tip of a reverse. The region
+                               // survives and should not appear to be deleted.
+
+                       } else /* if (pCnt == 0) */{
+                               // Root commit, with at least one surviving region.
+                               // Assign the remaining blame here.
+                               return result(n);
+                       }
+               }
+       }
+
+       private boolean done() {
+               release();
+               return false;
+       }
+
+       private boolean result(Candidate n) throws IOException {
+               if (n.sourceCommit != null)
+                       revPool.parseBody(n.sourceCommit);
+               currentSource = n;
+               return true;
+       }
+
+       private boolean reverseResult(Candidate parent, Candidate source)
+                       throws IOException {
+               // On a reverse blame present the application the parent
+               // (as this is what did the removals), however the region
+               // list to enumerate is the source's surviving list.
+               Candidate res = parent.copy(parent.sourceCommit);
+               res.regionList = source.regionList;
+               return result(res);
+       }
+
+       private Candidate pop() {
+               Candidate n = queue;
+               if (n != null) {
+                       queue = n.queueNext;
+                       n.queueNext = null;
+               }
+               return n;
+       }
+
+       private void push(BlobCandidate toInsert) {
+               Candidate c = queue;
+               if (c != null) {
+                       c.regionList = null;
+                       toInsert.parent = c;
+               }
+               queue = toInsert;
+       }
+
+       private void push(Candidate toInsert) {
+               // Mark sources to ensure they get discarded (above) if
+               // another path to the same commit.
+               toInsert.add(SEEN);
+
+               // Insert into the queue using descending commit time, so
+               // the most recent commit will pop next.
+               int time = toInsert.getTime();
+               Candidate n = queue;
+               if (n == null || time >= n.getTime()) {
+                       toInsert.queueNext = n;
+                       queue = toInsert;
+                       return;
+               }
+
+               for (Candidate p = n;; p = n) {
+                       n = p.queueNext;
+                       if (n == null || time >= n.getTime()) {
+                               toInsert.queueNext = n;
+                               p.queueNext = toInsert;
+                               return;
+                       }
+               }
+       }
+
+       private boolean processOne(Candidate n) throws IOException {
+               RevCommit parent = n.getParent(0);
+               if (parent == null)
+                       return split(n.getNextCandidate(0), n);
+               if (parent.has(SEEN))
+                       return false;
+               revPool.parseHeaders(parent);
+
+               if (find(parent, n.sourcePath)) {
+                       if (idBuf.equals(n.sourceBlob)) {
+                               // The common case of the file not being modified in
+                               // a simple string-of-pearls history. Blame parent.
+                               n.sourceCommit = parent;
+                               push(n);
+                               return false;
+                       }
+
+                       Candidate next = n.create(parent, n.sourcePath);
+                       next.sourceBlob = idBuf.toObjectId();
+                       next.loadText(reader);
+                       return split(next, n);
+               }
+
+               if (n.sourceCommit == null)
+                       return result(n);
+
+               DiffEntry r = findRename(parent, n.sourceCommit, n.sourcePath);
+               if (r == null)
+                       return result(n);
+
+               if (0 == r.getOldId().prefixCompare(n.sourceBlob)) {
+                       // A 100% rename without any content change can also
+                       // skip directly to the parent.
+                       n.sourceCommit = parent;
+                       n.sourcePath = PathFilter.create(r.getOldPath());
+                       push(n);
+                       return false;
+               }
+
+               Candidate next = n.create(parent, PathFilter.create(r.getOldPath()));
+               next.sourceBlob = r.getOldId().toObjectId();
+               next.renameScore = r.getScore();
+               next.loadText(reader);
+               return split(next, n);
+       }
+
+       private boolean split(Candidate parent, Candidate source)
+                       throws IOException {
+               EditList editList = diffAlgorithm.diff(textComparator,
+                               parent.sourceText, source.sourceText);
+               if (editList.isEmpty()) {
+                       // Ignoring whitespace (or some other special comparator) can
+                       // cause non-identical blobs to have an empty edit list. In
+                       // a case like this push the parent alone.
+                       parent.regionList = source.regionList;
+                       push(parent);
+                       return false;
+               }
+
+               parent.takeBlame(editList, source);
+               if (parent.regionList != null)
+                       push(parent);
+               if (source.regionList != null) {
+                       if (source instanceof ReverseCandidate)
+                               return reverseResult(parent, source);
+                       return result(source);
+               }
+               return false;
+       }
+
+       private boolean processMerge(Candidate n) throws IOException {
+               int pCnt = n.getParentCount();
+
+               for (int pIdx = 0; pIdx < pCnt; pIdx++) {
+                       RevCommit parent = n.getParent(pIdx);
+                       if (parent.has(SEEN))
+                               continue;
+                       revPool.parseHeaders(parent);
+               }
+
+               // If any single parent exactly matches the merge, follow only
+               // that one parent through history.
+               ObjectId[] ids = null;
+               for (int pIdx = 0; pIdx < pCnt; pIdx++) {
+                       RevCommit parent = n.getParent(pIdx);
+                       if (parent.has(SEEN))
+                               continue;
+                       if (!find(parent, n.sourcePath))
+                               continue;
+                       if (!(n instanceof ReverseCandidate) && idBuf.equals(n.sourceBlob)) {
+                               n.sourceCommit = parent;
+                               push(n);
+                               return false;
+                       }
+                       if (ids == null)
+                               ids = new ObjectId[pCnt];
+                       ids[pIdx] = idBuf.toObjectId();
+               }
+
+               // If rename detection is enabled, search for any relevant names.
+               DiffEntry[] renames = null;
+               if (renameDetector != null) {
+                       renames = new DiffEntry[pCnt];
+                       for (int pIdx = 0; pIdx < pCnt; pIdx++) {
+                               RevCommit parent = n.getParent(pIdx);
+                               if (parent.has(SEEN))
+                                       continue;
+                               if (ids != null && ids[pIdx] != null)
+                                       continue;
+
+                               DiffEntry r = findRename(parent, n.sourceCommit, n.sourcePath);
+                               if (r == null)
+                                       continue;
+
+                               if (n instanceof ReverseCandidate) {
+                                       if (ids == null)
+                                               ids = new ObjectId[pCnt];
+                                       ids[pCnt] = r.getOldId().toObjectId();
+                               } else if (0 == r.getOldId().prefixCompare(n.sourceBlob)) {
+                                       // A 100% rename without any content change can also
+                                       // skip directly to the parent. Note this bypasses an
+                                       // earlier parent that had the path (above) but did not
+                                       // have an exact content match. For performance reasons
+                                       // we choose to follow the one parent over trying to do
+                                       // possibly both parents.
+                                       n.sourceCommit = parent;
+                                       n.sourcePath = PathFilter.create(r.getOldPath());
+                                       push(n);
+                                       return false;
+                               }
+
+                               renames[pIdx] = r;
+                       }
+               }
+
+               // Construct the candidate for each parent.
+               Candidate[] parents = new Candidate[pCnt];
+               for (int pIdx = 0; pIdx < pCnt; pIdx++) {
+                       RevCommit parent = n.getParent(pIdx);
+                       if (parent.has(SEEN))
+                               continue;
+
+                       Candidate p;
+                       if (renames != null && renames[pIdx] != null) {
+                               p = n.create(parent,
+                                               PathFilter.create(renames[pIdx].getOldPath()));
+                               p.renameScore = renames[pIdx].getScore();
+                               p.sourceBlob = renames[pIdx].getOldId().toObjectId();
+                       } else if (ids != null && ids[pIdx] != null) {
+                               p = n.create(parent, n.sourcePath);
+                               p.sourceBlob = ids[pIdx];
+                       } else {
+                               continue;
+                       }
+
+                       EditList editList;
+                       if (n instanceof ReverseCandidate
+                                       && p.sourceBlob.equals(n.sourceBlob)) {
+                               // This special case happens on ReverseCandidate forks.
+                               p.sourceText = n.sourceText;
+                               editList = new EditList(0);
+                       } else {
+                               p.loadText(reader);
+                               editList = diffAlgorithm.diff(textComparator,
+                                               p.sourceText, n.sourceText);
+                       }
+
+                       if (editList.isEmpty()) {
+                               // Ignoring whitespace (or some other special comparator) can
+                               // cause non-identical blobs to have an empty edit list. In
+                               // a case like this push the parent alone.
+                               if (n instanceof ReverseCandidate) {
+                                       parents[pIdx] = p;
+                                       continue;
+                               }
+
+                               p.regionList = n.regionList;
+                               push(p);
+                               return false;
+                       }
+
+                       p.takeBlame(editList, n);
+
+                       // Only remember this parent candidate if there is at least
+                       // one region that was blamed on the parent.
+                       if (p.regionList != null) {
+                               // Reverse blame requires inverting the regions. This puts
+                               // the regions the parent deleted from us into the parent,
+                               // and retains the common regions to look at other parents
+                               // for deletions.
+                               if (n instanceof ReverseCandidate) {
+                                       Region r = p.regionList;
+                                       p.regionList = n.regionList;
+                                       n.regionList = r;
+                               }
+
+                               parents[pIdx] = p;
+                       }
+               }
+
+               if (n instanceof ReverseCandidate) {
+                       // On a reverse blame report all deletions found in the children,
+                       // and pass on to them a copy of our region list.
+                       Candidate resultHead = null;
+                       Candidate resultTail = null;
+
+                       for (int pIdx = 0; pIdx < pCnt; pIdx++) {
+                               Candidate p = parents[pIdx];
+                               if (p == null)
+                                       continue;
+
+                               if (p.regionList != null) {
+                                       Candidate r = p.copy(p.sourceCommit);
+                                       if (resultTail != null) {
+                                               resultTail.queueNext = r;
+                                               resultTail = r;
+                                       } else {
+                                               resultHead = r;
+                                               resultTail = r;
+                                       }
+                               }
+
+                               if (n.regionList != null) {
+                                       p.regionList = n.regionList.deepCopy();
+                                       push(p);
+                               }
+                       }
+
+                       if (resultHead != null)
+                               return result(resultHead);
+                       return false;
+               }
+
+               // Push any parents that are still candidates.
+               for (int pIdx = 0; pIdx < pCnt; pIdx++) {
+                       if (parents[pIdx] != null)
+                               push(parents[pIdx]);
+               }
+
+               if (n.regionList != null)
+                       return result(n);
+               return false;
+       }
+
+       /**
+        * Get the revision blamed for the current region.
+        * <p>
+        * The source commit may be null if the line was blamed to an uncommitted
+        * revision, such as the working tree copy, or during a reverse blame if the
+        * line survives to the end revision (e.g. the branch tip).
+        *
+        * @return current revision being blamed.
+        */
+       public RevCommit getSourceCommit() {
+               return currentSource.sourceCommit;
+       }
+
+       /** @return current author being blamed. */
+       public PersonIdent getSourceAuthor() {
+               return currentSource.getAuthor();
+       }
+
+       /** @return current committer being blamed. */
+       public PersonIdent getSourceCommitter() {
+               RevCommit c = getSourceCommit();
+               return c != null ? c.getCommitterIdent() : null;
+       }
+
+       /** @return path of the file being blamed. */
+       public String getSourcePath() {
+               return currentSource.sourcePath.getPath();
+       }
+
+       /** @return rename score if a rename occurred in {@link #getSourceCommit}. */
+       public int getRenameScore() {
+               return currentSource.renameScore;
+       }
+
+       /**
+        * @return first line of the source data that has been blamed for the
+        *         current region. This is line number of where the region was added
+        *         during {@link #getSourceCommit()} in file
+        *         {@link #getSourcePath()}.
+        */
+       public int getSourceStart() {
+               return currentSource.regionList.sourceStart;
+       }
+
+       /**
+        * @return one past the range of the source data that has been blamed for
+        *         the current region. This is line number of where the region was
+        *         added during {@link #getSourceCommit()} in file
+        *         {@link #getSourcePath()}.
+        */
+       public int getSourceEnd() {
+               Region r = currentSource.regionList;
+               return r.sourceStart + r.length;
+       }
+
+       /**
+        * @return first line of the result that {@link #getSourceCommit()} has been
+        *         blamed for providing. Line numbers use 0 based indexing.
+        */
+       public int getResultStart() {
+               return currentSource.regionList.resultStart;
+       }
+
+       /**
+        * @return one past the range of the result that {@link #getSourceCommit()}
+        *         has been blamed for providing. Line numbers use 0 based indexing.
+        *         Because a source cannot be blamed for an empty region of the
+        *         result, {@link #getResultEnd()} is always at least one larger
+        *         than {@link #getResultStart()}.
+        */
+       public int getResultEnd() {
+               Region r = currentSource.regionList;
+               return r.resultStart + r.length;
+       }
+
+       /**
+        * @return number of lines in the current region being blamed to
+        *         {@link #getSourceCommit()}. This is always the value of the
+        *         expression {@code getResultEnd() - getResultStart()}, but also
+        *         {@code getSourceEnd() - getSourceStart()}.
+        */
+       public int getRegionLength() {
+               return currentSource.regionList.length;
+       }
+
+       /**
+        * @return complete contents of the source file blamed for the current
+        *         output region. This is the contents of {@link #getSourcePath()}
+        *         within {@link #getSourceCommit()}. The source contents is
+        *         temporarily available as an artifact of the blame algorithm. Most
+        *         applications will want the result contents for display to users.
+        */
+       public RawText getSourceContents() {
+               return currentSource.sourceText;
+       }
+
+       /**
+        * @return complete file contents of the result file blame is annotating.
+        *         This value is accessible only after being configured and only
+        *         immediately before the first call to {@link #next()}. Returns
+        *         null if the path does not exist.
+        * @throws IOException
+        *             repository cannot be read.
+        * @throws IllegalStateException
+        *             {@link #next()} has already been invoked.
+        */
+       public RawText getResultContents() throws IOException {
+               return queue != null ? queue.sourceText : null;
+       }
+
+       /** Release the current blame session. */
+       public void release() {
+               revPool.release();
+               queue = null;
+               currentSource = null;
+       }
+
+       private boolean find(RevCommit commit, PathFilter path) throws IOException {
+               treeWalk.setFilter(path);
+               treeWalk.reset(commit.getTree());
+               while (treeWalk.next()) {
+                       if (path.isDone(treeWalk)) {
+                               if (treeWalk.getFileMode(0).getObjectType() != OBJ_BLOB)
+                                       return false;
+                               treeWalk.getObjectId(idBuf, 0);
+                               return true;
+                       }
+
+                       if (treeWalk.isSubtree())
+                               treeWalk.enterSubtree();
+               }
+               return false;
+       }
+
+       private DiffEntry findRename(RevCommit parent, RevCommit commit,
+                       PathFilter path) throws IOException {
+               if (renameDetector == null)
+                       return null;
+
+               treeWalk.setFilter(TreeFilter.ANY_DIFF);
+               treeWalk.reset(parent.getTree(), commit.getTree());
+               renameDetector.addAll(DiffEntry.scan(treeWalk));
+               for (DiffEntry ent : renameDetector.compute()) {
+                       if (isRename(ent) && ent.getNewPath().equals(path.getPath()))
+                               return ent;
+               }
+               return null;
+       }
+
+       private static boolean isRename(DiffEntry ent) {
+               return ent.getChangeType() == ChangeType.RENAME
+                               || ent.getChangeType() == ChangeType.COPY;
+       }
+}
diff --git a/src/org/eclipse/jgit/blame/BlameResult.java b/src/org/eclipse/jgit/blame/BlameResult.java
new file mode 100644 (file)
index 0000000..d7a958f
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.blame;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * Collects line annotations for inspection by applications.
+ * <p>
+ * A result is usually updated incrementally as the BlameGenerator digs back
+ * further through history. Applications that want to lay annotations down text
+ * to the original source file in a viewer may find the BlameResult structure an
+ * easy way to acquire the information, at the expense of keeping tables in
+ * memory tracking every line of the result file.
+ * <p>
+ * This class is not thread-safe.
+ * <p>
+ * During blame processing there are two files involved:
+ * <ul>
+ * <li>result - The file whose lines are being examined. This is the revision
+ * the user is trying to view blame/annotation information alongside of.</li>
+ * <li>source - The file that was blamed with supplying one or more lines of
+ * data into result. The source may be a different file path (due to copy or
+ * rename). Source line numbers may differ from result line numbers due to lines
+ * being added/removed in intermediate revisions.</li>
+ * </ul>
+ */
+public class BlameResult {
+       /**
+        * Construct a new BlameResult for a generator.
+        *
+        * @param gen
+        *            the generator the result will consume records from.
+        * @return the new result object. null if the generator cannot find the path
+        *         it starts from.
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public static BlameResult create(BlameGenerator gen) throws IOException {
+               String path = gen.getResultPath();
+               RawText contents = gen.getResultContents();
+               if (contents == null) {
+                       gen.release();
+                       return null;
+               }
+               return new BlameResult(gen, path, contents);
+       }
+
+       private final String resultPath;
+
+       private final RevCommit[] sourceCommits;
+
+       private final PersonIdent[] sourceAuthors;
+
+       private final PersonIdent[] sourceCommitters;
+
+       private final String[] sourcePaths;
+
+       /** Warning: these are actually 1-based. */
+       private final int[] sourceLines;
+
+       private RawText resultContents;
+
+       private BlameGenerator generator;
+
+       private int lastLength;
+
+       BlameResult(BlameGenerator bg, String path, RawText text) {
+               generator = bg;
+               resultPath = path;
+               resultContents = text;
+
+               int cnt = text.size();
+               sourceCommits = new RevCommit[cnt];
+               sourceAuthors = new PersonIdent[cnt];
+               sourceCommitters = new PersonIdent[cnt];
+               sourceLines = new int[cnt];
+               sourcePaths = new String[cnt];
+       }
+
+       /** @return path of the file this result annotates. */
+       public String getResultPath() {
+               return resultPath;
+       }
+
+       /** @return contents of the result file, available for display. */
+       public RawText getResultContents() {
+               return resultContents;
+       }
+
+       /** Throw away the {@link #getResultContents()}. */
+       public void discardResultContents() {
+               resultContents = null;
+       }
+
+       /**
+        * Check if the given result line has been annotated yet.
+        *
+        * @param idx
+        *            line to read data of, 0 based.
+        * @return true if the data has been annotated, false otherwise.
+        */
+       public boolean hasSourceData(int idx) {
+               return sourceLines[idx] != 0;
+       }
+
+       /**
+        * Check if the given result line has been annotated yet.
+        *
+        * @param start
+        *            first index to examine.
+        * @param end
+        *            last index to examine.
+        * @return true if the data has been annotated, false otherwise.
+        */
+       public boolean hasSourceData(int start, int end) {
+               for (; start < end; start++)
+                       if (sourceLines[start] == 0)
+                               return false;
+               return true;
+       }
+
+       /**
+        * Get the commit that provided the specified line of the result.
+        * <p>
+        * The source commit may be null if the line was blamed to an uncommitted
+        * revision, such as the working tree copy, or during a reverse blame if the
+        * line survives to the end revision (e.g. the branch tip).
+        *
+        * @param idx
+        *            line to read data of, 0 based.
+        * @return commit that provided line {@code idx}. May be null.
+        */
+       public RevCommit getSourceCommit(int idx) {
+               return sourceCommits[idx];
+       }
+
+       /**
+        * Get the author that provided the specified line of the result.
+        *
+        * @param idx
+        *            line to read data of, 0 based.
+        * @return author that provided line {@code idx}. May be null.
+        */
+       public PersonIdent getSourceAuthor(int idx) {
+               return sourceAuthors[idx];
+       }
+
+       /**
+        * Get the committer that provided the specified line of the result.
+        *
+        * @param idx
+        *            line to read data of, 0 based.
+        * @return committer that provided line {@code idx}. May be null.
+        */
+       public PersonIdent getSourceCommitter(int idx) {
+               return sourceCommitters[idx];
+       }
+
+       /**
+        * Get the file path that provided the specified line of the result.
+        *
+        * @param idx
+        *            line to read data of, 0 based.
+        * @return source file path that provided line {@code idx}.
+        */
+       public String getSourcePath(int idx) {
+               return sourcePaths[idx];
+       }
+
+       /**
+        * Get the corresponding line number in the source file.
+        *
+        * @param idx
+        *            line to read data of, 0 based.
+        * @return matching line number in the source file.
+        */
+       public int getSourceLine(int idx) {
+               return sourceLines[idx] - 1;
+       }
+
+       /**
+        * Compute all pending information.
+        *
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public void computeAll() throws IOException {
+               BlameGenerator gen = generator;
+               if (gen == null)
+                       return;
+
+               try {
+                       while (gen.next())
+                               loadFrom(gen);
+               } finally {
+                       gen.release();
+                       generator = null;
+               }
+       }
+
+       /**
+        * Compute the next available segment and return the first index.
+        * <p>
+        * Computes one segment and returns to the caller the first index that is
+        * available. After return the caller can also inspect {@link #lastLength()}
+        * to determine how many lines of the result were computed.
+        *
+        * @return index that is now available. -1 if no more are available.
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public int computeNext() throws IOException {
+               BlameGenerator gen = generator;
+               if (gen == null)
+                       return -1;
+
+               if (gen.next()) {
+                       loadFrom(gen);
+                       lastLength = gen.getRegionLength();
+                       return gen.getResultStart();
+               } else {
+                       gen.release();
+                       generator = null;
+                       return -1;
+               }
+       }
+
+       /** @return length of the last segment found by {@link #computeNext()}. */
+       public int lastLength() {
+               return lastLength;
+       }
+
+       /**
+        * Compute until the entire range has been populated.
+        *
+        * @param start
+        *            first index to examine.
+        * @param end
+        *            last index to examine.
+        * @throws IOException
+        *             the repository cannot be read.
+        */
+       public void computeRange(int start, int end) throws IOException {
+               BlameGenerator gen = generator;
+               if (gen == null)
+                       return;
+
+               while (start < end) {
+                       if (hasSourceData(start, end))
+                               return;
+
+                       if (!gen.next()) {
+                               gen.release();
+                               generator = null;
+                               return;
+                       }
+
+                       loadFrom(gen);
+
+                       // If the result contains either end of our current range bounds,
+                       // update the bounds to avoid scanning that section during the
+                       // next loop iteration.
+
+                       int resLine = gen.getResultStart();
+                       int resEnd = gen.getResultEnd();
+
+                       if (resLine <= start && start < resEnd)
+                               start = resEnd;
+
+                       if (resLine <= end && end < resEnd)
+                               end = resLine;
+               }
+       }
+
+       @Override
+       public String toString() {
+               StringBuilder r = new StringBuilder();
+               r.append("BlameResult: ");
+               r.append(getResultPath());
+               return r.toString();
+       }
+
+       private void loadFrom(BlameGenerator gen) {
+               RevCommit srcCommit = gen.getSourceCommit();
+               PersonIdent srcAuthor = gen.getSourceAuthor();
+               PersonIdent srcCommitter = gen.getSourceCommitter();
+               String srcPath = gen.getSourcePath();
+               int srcLine = gen.getSourceStart();
+               int resLine = gen.getResultStart();
+               int resEnd = gen.getResultEnd();
+
+               for (; resLine < resEnd; resLine++) {
+                       // Reverse blame can generate multiple results for the same line.
+                       // Favor the first one selected, as this is the oldest and most
+                       // likely to be nearest to the inquiry made by the user.
+                       if (sourceLines[resLine] != 0)
+                               continue;
+
+                       sourceCommits[resLine] = srcCommit;
+                       sourceAuthors[resLine] = srcAuthor;
+                       sourceCommitters[resLine] = srcCommitter;
+                       sourcePaths[resLine] = srcPath;
+
+                       // Since sourceLines is 1-based to permit hasSourceData to use 0 to
+                       // mean the line has not been annotated yet, pre-increment instead
+                       // of the traditional post-increment when making the assignment.
+                       sourceLines[resLine] = ++srcLine;
+               }
+       }
+}
diff --git a/src/org/eclipse/jgit/blame/Candidate.java b/src/org/eclipse/jgit/blame/Candidate.java
new file mode 100644 (file)
index 0000000..5f20ce9
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.blame;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.EditList;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+
+/**
+ * A source that may have supplied some (or all) of the result file.
+ * <p>
+ * Candidates are kept in a queue by BlameGenerator, allowing the generator to
+ * perform a parallel search down the parents of any merges that are discovered
+ * during the history traversal. Each candidate retains a {@link #regionList}
+ * describing sections of the result file the candidate has taken responsibility
+ * for either directly or indirectly through its history. Actual blame from this
+ * region list will be assigned to the candidate when its ancestor commit(s) are
+ * themselves converted into Candidate objects and the ancestor's candidate uses
+ * {@link #takeBlame(EditList, Candidate)} to accept responsibility for sections
+ * of the result.
+ */
+class Candidate {
+       /** Next candidate in the candidate queue. */
+       Candidate queueNext;
+
+       /** Commit being considered (or blamed, depending on state). */
+       RevCommit sourceCommit;
+
+       /** Path of the candidate file in {@link #sourceCommit}. */
+       PathFilter sourcePath;
+
+       /** Unique name of the candidate blob in {@link #sourceCommit}. */
+       ObjectId sourceBlob;
+
+       /** Complete contents of the file in {@link #sourceCommit}. */
+       RawText sourceText;
+
+       /**
+        * Chain of regions this candidate may be blamed for.
+        * <p>
+        * This list is always kept sorted by resultStart order, making it simple to
+        * merge-join with the sorted EditList during blame assignment.
+        */
+       Region regionList;
+
+       /**
+        * Score assigned to the rename to this candidate.
+        * <p>
+        * Consider the history "A<-B<-C". If the result file S in C was renamed to
+        * R in B, the rename score for this rename will be held in this field by
+        * the candidate object for B. By storing the score with B, the application
+        * can see what the rename score was as it makes the transition from C/S to
+        * B/R. This may seem backwards since it was C that performed the rename,
+        * but the application doesn't learn about path R until B.
+        */
+       int renameScore;
+
+       Candidate(RevCommit commit, PathFilter path) {
+               sourceCommit = commit;
+               sourcePath = path;
+       }
+
+       int getParentCount() {
+               return sourceCommit.getParentCount();
+       }
+
+       RevCommit getParent(int idx) {
+               return sourceCommit.getParent(idx);
+       }
+
+       Candidate getNextCandidate(@SuppressWarnings("unused") int idx) {
+               return null;
+       }
+
+       void add(RevFlag flag) {
+               sourceCommit.add(flag);
+       }
+
+       int getTime() {
+               return sourceCommit.getCommitTime();
+       }
+
+       PersonIdent getAuthor() {
+               return sourceCommit.getAuthorIdent();
+       }
+
+       Candidate create(RevCommit commit, PathFilter path) {
+               return new Candidate(commit, path);
+       }
+
+       Candidate copy(RevCommit commit) {
+               Candidate r = create(commit, sourcePath);
+               r.sourceBlob = sourceBlob;
+               r.sourceText = sourceText;
+               r.regionList = regionList;
+               r.renameScore = renameScore;
+               return r;
+       }
+
+       void loadText(ObjectReader reader) throws IOException {
+               ObjectLoader ldr = reader.open(sourceBlob, Constants.OBJ_BLOB);
+               sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
+       }
+
+       void takeBlame(EditList editList, Candidate child) {
+               blame(editList, this, child);
+       }
+
+       private static void blame(EditList editList, Candidate a, Candidate b) {
+               Region r = b.clearRegionList();
+               Region aTail = null;
+               Region bTail = null;
+
+               for (int eIdx = 0; eIdx < editList.size();) {
+                       // If there are no more regions left, neither side has any
+                       // more responsibility for the result. Remaining edits can
+                       // be safely ignored.
+                       if (r == null)
+                               return;
+
+                       Edit e = editList.get(eIdx);
+
+                       // Edit ends before the next candidate region. Skip the edit.
+                       if (e.getEndB() <= r.sourceStart) {
+                               eIdx++;
+                               continue;
+                       }
+
+                       // Next candidate region starts before the edit. Assign some
+                       // of the blame onto A, but possibly split and also on B.
+                       if (r.sourceStart < e.getBeginB()) {
+                               int d = e.getBeginB() - r.sourceStart;
+                               if (r.length <= d) {
+                                       // Pass the blame for this region onto A.
+                                       Region next = r.next;
+                                       r.sourceStart = e.getBeginA() - d;
+                                       aTail = add(aTail, a, r);
+                                       r = next;
+                                       continue;
+                               }
+
+                               // Split the region and assign some to A, some to B.
+                               aTail = add(aTail, a, r.splitFirst(e.getBeginA() - d, d));
+                               r.slideAndShrink(d);
+                       }
+
+                       // At this point e.getBeginB() <= r.sourceStart.
+
+                       // An empty edit on the B side isn't relevant to this split,
+                       // as it does not overlap any candidate region.
+                       if (e.getLengthB() == 0) {
+                               eIdx++;
+                               continue;
+                       }
+
+                       // If the region ends before the edit, blame on B.
+                       int rEnd = r.sourceStart + r.length;
+                       if (rEnd <= e.getEndB()) {
+                               Region next = r.next;
+                               bTail = add(bTail, b, r);
+                               r = next;
+                               if (rEnd == e.getEndB())
+                                       eIdx++;
+                               continue;
+                       }
+
+                       // This region extends beyond the edit. Blame the first
+                       // half of the region on B, and process the rest after.
+                       int len = e.getEndB() - r.sourceStart;
+                       bTail = add(bTail, b, r.splitFirst(r.sourceStart, len));
+                       r.slideAndShrink(len);
+                       eIdx++;
+               }
+
+               if (r == null)
+                       return;
+
+               // For any remaining region, pass the blame onto A after shifting
+               // the source start to account for the difference between the two.
+               Edit e = editList.get(editList.size() - 1);
+               int endB = e.getEndB();
+               int d = endB - e.getEndA();
+               if (aTail == null)
+                       a.regionList = r;
+               else
+                       aTail.next = r;
+               do {
+                       if (endB <= r.sourceStart)
+                               r.sourceStart -= d;
+                       r = r.next;
+               } while (r != null);
+       }
+
+       private static Region add(Region aTail, Candidate a, Region n) {
+               // If there is no region on the list, use only this one.
+               if (aTail == null) {
+                       a.regionList = n;
+                       n.next = null;
+                       return n;
+               }
+
+               // If the prior region ends exactly where the new region begins
+               // in both the result and the source, combine these together into
+               // one contiguous region. This occurs when intermediate commits
+               // have inserted and deleted lines in the middle of a region. Try
+               // to report this region as a single region to the application,
+               // rather than in fragments.
+               if (aTail.resultStart + aTail.length == n.resultStart
+                               && aTail.sourceStart + aTail.length == n.sourceStart) {
+                       aTail.length += n.length;
+                       return aTail;
+               }
+
+               // Append the region onto the end of the list.
+               aTail.next = n;
+               n.next = null;
+               return n;
+       }
+
+       private Region clearRegionList() {
+               Region r = regionList;
+               regionList = null;
+               return r;
+       }
+
+       @Override
+       public String toString() {
+               StringBuilder r = new StringBuilder();
+               r.append("Candidate[");
+               r.append(sourcePath.getPath());
+               if (sourceCommit != null)
+                       r.append(" @ ").append(sourceCommit.abbreviate(6).name());
+               if (regionList != null)
+                       r.append(" regions:").append(regionList);
+               r.append("]");
+               return r.toString();
+       }
+
+       /**
+        * Special candidate type used for reverse blame.
+        * <p>
+        * Reverse blame inverts the commit history graph to follow from a commit to
+        * its descendant children, rather than the normal history direction of
+        * child to parent. These types require a {@link ReverseCommit} which keeps
+        * children pointers, allowing reverse navigation of history.
+        */
+       static final class ReverseCandidate extends Candidate {
+               ReverseCandidate(ReverseCommit commit, PathFilter path) {
+                       super(commit, path);
+               }
+
+               @Override
+               int getParentCount() {
+                       return ((ReverseCommit) sourceCommit).getChildCount();
+               }
+
+               @Override
+               RevCommit getParent(int idx) {
+                       return ((ReverseCommit) sourceCommit).getChild(idx);
+               }
+
+               @Override
+               int getTime() {
+                       // Invert the timestamp so newer dates sort older.
+                       return -sourceCommit.getCommitTime();
+               }
+
+               @Override
+               Candidate create(RevCommit commit, PathFilter path) {
+                       return new ReverseCandidate((ReverseCommit) commit, path);
+               }
+
+               @Override
+               public String toString() {
+                       return "Reverse" + super.toString();
+               }
+       }
+
+       /**
+        * Candidate loaded from a file source, and not a commit.
+        * <p>
+        * The {@link Candidate#sourceCommit} field is always null on this type of
+        * candidate. Instead history traversal follows the single {@link #parent}
+        * field to discover the next Candidate. Often this is a normal Candidate
+        * type that has a valid sourceCommit.
+        */
+       static final class BlobCandidate extends Candidate {
+               /**
+                * Next candidate to pass blame onto.
+                * <p>
+                * When computing the differences that this candidate introduced to the
+                * file content, the parent's sourceText is used as the base.
+                */
+               Candidate parent;
+
+               /** Author name to refer to this blob with. */
+               String description;
+
+               BlobCandidate(String name, PathFilter path) {
+                       super(null, path);
+                       description = name;
+               }
+
+               @Override
+               int getParentCount() {
+                       return parent != null ? 1 : 0;
+               }
+
+               @Override
+               RevCommit getParent(int idx) {
+                       return null;
+               }
+
+               @Override
+               Candidate getNextCandidate(int idx) {
+                       return parent;
+               }
+
+               @Override
+               void add(RevFlag flag) {
+                       // Do nothing, sourceCommit is null.
+               }
+
+               @Override
+               int getTime() {
+                       return Integer.MAX_VALUE;
+               }
+
+               @Override
+               PersonIdent getAuthor() {
+                       return new PersonIdent(description, null);
+               }
+       }
+}
diff --git a/src/org/eclipse/jgit/blame/Region.java b/src/org/eclipse/jgit/blame/Region.java
new file mode 100644 (file)
index 0000000..9ea346b
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.blame;
+
+/**
+ * Region of the result that still needs to be computed.
+ * <p>
+ * Regions are held in a singly-linked-list by {@link Candidate} using the
+ * {@link Candidate#regionList} field. The list is kept in sorted order by
+ * {@link #resultStart}.
+ */
+class Region {
+       /** Next entry in the region linked list. */
+       Region next;
+
+       /** First position of this region in the result file blame is computing. */
+       int resultStart;
+
+       /** First position in the {@link Candidate} that owns this Region. */
+       int sourceStart;
+
+       /** Length of the region, always >= 1. */
+       int length;
+
+       Region(int rs, int ss, int len) {
+               resultStart = rs;
+               sourceStart = ss;
+               length = len;
+       }
+
+       /**
+        * Copy the entire result region, but at a new source position.
+        *
+        * @param newSource
+        *            the new source position.
+        * @return the same result region, but offset for a new source.
+        */
+       Region copy(int newSource) {
+               return new Region(resultStart, newSource, length);
+       }
+
+       /**
+        * Split the region, assigning a new source position to the first half.
+        *
+        * @param newSource
+        *            the new source position.
+        * @param newLen
+        *            length of the new region.
+        * @return the first half of the region, at the new source.
+        */
+       Region splitFirst(int newSource, int newLen) {
+               return new Region(resultStart, newSource, newLen);
+       }
+
+       /**
+        * Edit this region to remove the first {@code d} elements.
+        *
+        * @param d
+        *            number of elements to remove from the start of this region.
+        */
+       void slideAndShrink(int d) {
+               resultStart += d;
+               sourceStart += d;
+               length -= d;
+       }
+
+       Region deepCopy() {
+               Region head = new Region(resultStart, sourceStart, length);
+               Region tail = head;
+               for (Region n = next; n != null; n = n.next) {
+                       Region q = new Region(n.resultStart, n.sourceStart, n.length);
+                       tail.next = q;
+                       tail = q;
+               }
+               return head;
+       }
+
+       @Override
+       public String toString() {
+               StringBuilder buf = new StringBuilder();
+               Region r = this;
+               do {
+                       if (r != this)
+                               buf.append(',');
+                       buf.append(r.resultStart);
+                       buf.append('-');
+                       buf.append(r.resultStart + r.length);
+                       r = r.next;
+               } while (r != null);
+               return buf.toString();
+       }
+}
diff --git a/src/org/eclipse/jgit/blame/ReverseWalk.java b/src/org/eclipse/jgit/blame/ReverseWalk.java
new file mode 100644 (file)
index 0000000..5b59804
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.blame;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+final class ReverseWalk extends RevWalk {
+       ReverseWalk(Repository repo) {
+               super(repo);
+       }
+
+       @Override
+       public ReverseCommit next() throws MissingObjectException,
+                       IncorrectObjectTypeException, IOException {
+               ReverseCommit c = (ReverseCommit) super.next();
+               if (c == null)
+                       return null;
+               for (int pIdx = 0; pIdx < c.getParentCount(); pIdx++)
+                       ((ReverseCommit) c.getParent(pIdx)).addChild(c);
+               return c;
+       }
+
+       @Override
+       protected RevCommit createCommit(AnyObjectId id) {
+               return new ReverseCommit(id);
+       }
+
+       static final class ReverseCommit extends RevCommit {
+               private static final ReverseCommit[] NO_CHILDREN = {};
+
+               private ReverseCommit[] children = NO_CHILDREN;
+
+               ReverseCommit(AnyObjectId id) {
+                       super(id);
+               }
+
+               void addChild(ReverseCommit c) {
+                       // Always put the most recent child onto the front of the list.
+                       // This works correctly because our ReverseWalk parent (above)
+                       // runs in COMMIT_TIME_DESC order. Older commits will be popped
+                       // later and should go in front of the children list so they are
+                       // visited first by BlameGenerator when considering candidates.
+
+                       int cnt = children.length;
+                       if (cnt == 0)
+                               children = new ReverseCommit[] { c };
+                       else if (cnt == 1)
+                               children = new ReverseCommit[] { c, children[0] };
+                       else {
+                               ReverseCommit[] n = new ReverseCommit[1 + cnt];
+                               n[0] = c;
+                               System.arraycopy(children, 0, n, 1, cnt);
+                               children = n;
+                       }
+               }
+
+               int getChildCount() {
+                       return children.length;
+               }
+
+               ReverseCommit getChild(final int nth) {
+                       return children[nth];
+               }
+       }
+}
index 84353c19333f58816184f7e9becebd92bd90e56f..ff6f2328db331af8ab19b4e49fd6d4f3e65fd236 100644 (file)
@@ -35,7 +35,7 @@ public class DiffUtilsTest extends TestCase {
                assertTrue(DiffOutputType.forName("gitblit").equals(DiffOutputType.GITBLIT));\r
                assertTrue(DiffOutputType.forName(null) == null);\r
        }\r
-       \r
+\r
        public void testParentCommitDiff() throws Exception {\r
                Repository repository = GitBlitSuite.getHelloworldRepository();\r
                RevCommit commit = JGitUtils.getCommit(repository,\r
@@ -107,10 +107,11 @@ public class DiffUtilsTest extends TestCase {
                String expected = "-            system.out.println(\"Hello World\");\n+         System.out.println(\"Hello World\"";\r
                assertTrue(patch.indexOf(expected) > -1);\r
        }\r
-       \r
+\r
        public void testBlame() throws Exception {\r
                Repository repository = GitBlitSuite.getHelloworldRepository();\r
-               List<AnnotatedLine> lines = DiffUtils.blame(repository, "java.java", "1d0c2933a4ae69c362f76797d42d6bd182d05176");\r
+               List<AnnotatedLine> lines = DiffUtils.blame(repository, "java.java",\r
+                               "1d0c2933a4ae69c362f76797d42d6bd182d05176");\r
                repository.close();\r
                assertTrue(lines.size() > 0);\r
                assertTrue(lines.get(0).commitId.equals("c6d31dccf5cc75e8e46299fc62d38f60ec6d41e0"));\r
index c9e383e793853473c6204189aa70049c0c8e8fac..31e29c962b8657d23759382c8e34199d32ae2ff7 100644 (file)
@@ -24,7 +24,7 @@ import junit.framework.TestSuite;
 import org.eclipse.jgit.lib.Repository;\r
 import org.eclipse.jgit.storage.file.FileRepository;\r
 \r
-import com.gitblit.FileLoginService;\r
+import com.gitblit.FileUserService;\r
 import com.gitblit.FileSettings;\r
 import com.gitblit.GitBlit;\r
 import com.gitblit.GitBlitException;\r
@@ -45,6 +45,7 @@ public class GitBlitSuite extends TestSetup {
                suite.addTestSuite(ByteFormatTest.class);\r
                suite.addTestSuite(MarkdownUtilsTest.class);\r
                suite.addTestSuite(JGitUtilsTest.class);\r
+               suite.addTestSuite(SyndicationUtilsTest.class);\r
                suite.addTestSuite(DiffUtilsTest.class);\r
                suite.addTestSuite(MetricUtilsTest.class);\r
                suite.addTestSuite(TicgitUtilsTest.class);\r
@@ -72,8 +73,8 @@ public class GitBlitSuite extends TestSetup {
        protected void setUp() throws Exception {\r
                FileSettings settings = new FileSettings("distrib/gitblit.properties");\r
                GitBlit.self().configureContext(settings);\r
-               FileLoginService loginService = new FileLoginService(new File("distrib/users.properties"));\r
-               GitBlit.self().setLoginService(loginService);\r
+               FileUserService loginService = new FileUserService(new File("distrib/users.properties"));\r
+               GitBlit.self().setUserService(loginService);\r
 \r
                if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {\r
                        cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git");\r
index 13705f1d0f05ec378d1b8cea3e59e90aaa8e8955..22297683aa319833e7bf63a2ea57b8edddd4c5fe 100644 (file)
@@ -55,7 +55,7 @@ public class GitBlitTest extends TestCase {
                model.addRepository(repository);\r
                assertTrue("Admin can't access repository!", model.canAccessRepository(repository));\r
        }\r
-       \r
+\r
        public void testAccessRestrictionTypes() throws Exception {\r
                assertTrue(AccessRestrictionType.PUSH.exceeds(AccessRestrictionType.NONE));\r
                assertTrue(AccessRestrictionType.CLONE.exceeds(AccessRestrictionType.PUSH));\r
@@ -72,7 +72,7 @@ public class GitBlitTest extends TestCase {
                assertFalse(AccessRestrictionType.NONE.atLeast(AccessRestrictionType.PUSH));\r
                assertFalse(AccessRestrictionType.PUSH.atLeast(AccessRestrictionType.CLONE));\r
                assertFalse(AccessRestrictionType.CLONE.atLeast(AccessRestrictionType.VIEW));\r
-               \r
+\r
                assertTrue(AccessRestrictionType.PUSH.toString().equals("PUSH"));\r
                assertTrue(AccessRestrictionType.CLONE.toString().equals("CLONE"));\r
                assertTrue(AccessRestrictionType.VIEW.toString().equals("VIEW"));\r
@@ -82,50 +82,50 @@ public class GitBlitTest extends TestCase {
                assertTrue(AccessRestrictionType.fromName("clone").equals(AccessRestrictionType.CLONE));\r
                assertTrue(AccessRestrictionType.fromName("view").equals(AccessRestrictionType.VIEW));\r
        }\r
-       \r
+\r
        public void testFileSettings() throws Exception {\r
                FileSettings settings = new FileSettings("distrib/gitblit.properties");\r
                assertTrue(settings.getBoolean("missing", true) == true);\r
                assertTrue(settings.getString("missing", "default").equals("default"));\r
                assertTrue(settings.getInteger("missing", 10) == 10);\r
                assertTrue(settings.getInteger("realm.realmFile", 5) == 5);\r
-               \r
+\r
                assertTrue(settings.getBoolean("git.enableGitServlet", false) == true);\r
-               assertTrue(settings.getString("realm.realmFile", null).equals("users.properties"));\r
+               assertTrue(settings.getString("realm.userService", null).equals("users.properties"));\r
                assertTrue(settings.getInteger("realm.minPasswordLength", 0) == 5);\r
                List<String> mdExtensions = settings.getStrings("web.markdownExtensions");\r
                assertTrue(mdExtensions.size() > 0);\r
                assertTrue(mdExtensions.contains("md"));\r
-               \r
+\r
                List<String> keys = settings.getAllKeys("server");\r
                assertTrue(keys.size() > 0);\r
                assertTrue(keys.contains("server.httpsPort"));\r
        }\r
-       \r
+\r
        public void testGitblitSettings() throws Exception {\r
                // These are already tested by above test method.\r
                assertTrue(GitBlit.getBoolean("missing", true) == true);\r
                assertTrue(GitBlit.getString("missing", "default").equals("default"));\r
                assertTrue(GitBlit.getInteger("missing", 10) == 10);\r
-               assertTrue(GitBlit.getInteger("realm.realmFile", 5) == 5);\r
-               \r
+               assertTrue(GitBlit.getInteger("realm.userService", 5) == 5);\r
+\r
                assertTrue(GitBlit.getBoolean("git.enableGitServlet", false) == true);\r
-               assertTrue(GitBlit.getString("realm.realmFile", null).equals("users.properties"));\r
+               assertTrue(GitBlit.getString("realm.userService", null).equals("users.properties"));\r
                assertTrue(GitBlit.getInteger("realm.minPasswordLength", 0) == 5);\r
                List<String> mdExtensions = GitBlit.getStrings("web.markdownExtensions");\r
                assertTrue(mdExtensions.size() > 0);\r
                assertTrue(mdExtensions.contains("md"));\r
-               \r
+\r
                List<String> keys = GitBlit.getAllKeys("server");\r
                assertTrue(keys.size() > 0);\r
                assertTrue(keys.contains("server.httpsPort"));\r
        }\r
-       \r
-       public void testAuthentication() throws Exception  {\r
+\r
+       public void testAuthentication() throws Exception {\r
                assertTrue(GitBlit.self().authenticate("admin", "admin".toCharArray()) != null);\r
        }\r
-       \r
-       public void testRepositories() throws Exception  {\r
+\r
+       public void testRepositories() throws Exception {\r
                assertTrue(GitBlit.self().getRepository("missing") == null);\r
                assertTrue(GitBlit.self().getRepositoryModel("missing") == null);\r
        }\r
index 19a4847491fbd44d2f3637723fe53be6af00c9da..daf0cfee6e130430c031d66bf0d406c2fd7d276d 100644 (file)
@@ -138,6 +138,7 @@ public class JGitUtilsTest extends TestCase {
 \r
        public void testBranches() throws Exception {\r
                Repository repository = GitBlitSuite.getJGitRepository();\r
+               assertTrue(JGitUtils.getLocalBranches(repository, true, 0).size() == 0);\r
                for (RefModel model : JGitUtils.getLocalBranches(repository, true, -1)) {\r
                        assertTrue(model.getName().startsWith(Constants.R_HEADS));\r
                        assertTrue(model.equals(model));\r
@@ -160,6 +161,7 @@ public class JGitUtilsTest extends TestCase {
 \r
        public void testTags() throws Exception {\r
                Repository repository = GitBlitSuite.getJGitRepository();\r
+               assertTrue(JGitUtils.getTags(repository, true, 5).size() == 5);\r
                for (RefModel model : JGitUtils.getTags(repository, true, -1)) {\r
                        if (model.getObjectId().getName().equals("d28091fb2977077471138fe97da1440e0e8ae0da")) {\r
                                assertTrue("Not an annotated tag!", model.isAnnotatedTag());\r
@@ -276,6 +278,7 @@ public class JGitUtilsTest extends TestCase {
        }\r
 \r
        public void testRevlog() throws Exception {\r
+               assertTrue(JGitUtils.getRevLog(null, 0).size() == 0);\r
                List<RevCommit> commits = JGitUtils.getRevLog(null, 10);\r
                assertTrue(commits.size() == 0);\r
 \r
@@ -306,6 +309,7 @@ public class JGitUtilsTest extends TestCase {
        }\r
 \r
        public void testSearchRevlogs() throws Exception {\r
+               assertTrue(JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0, 0).size() == 0);\r
                List<RevCommit> results = JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0,\r
                                3);\r
                assertTrue(results.size() == 0);\r
index b0d9a1ff5306d2a9ca75b11368aabe39ce824fd8..1bf0de4aee3296039ebbc787092d5ccf59d7edc3 100644 (file)
@@ -36,6 +36,12 @@ public class StringUtilsTest extends TestCase {
                String output = "this<br/>is<br/>a<br/>test<br/><br/>of<br/><br/>line<br/><br/>breaking";\r
                assertTrue(StringUtils.breakLinesForHtml(input).equals(output));\r
        }\r
+       \r
+       public void testEncodeUrl() throws Exception {\r
+               String input = "test /";\r
+               String output = "test%20%2F";\r
+               assertTrue(StringUtils.encodeURL(input).equals(output));\r
+       }\r
 \r
        public void testEscapeForHtml() throws Exception {\r
                String input = "& < > \" \t";\r
@@ -44,6 +50,12 @@ public class StringUtilsTest extends TestCase {
                assertTrue(StringUtils.escapeForHtml(input, false).equals(outputNoChange));\r
                assertTrue(StringUtils.escapeForHtml(input, true).equals(outputChange));\r
        }\r
+       \r
+       public void testDecodeForHtml() throws Exception {\r
+               String input = "&amp; &lt; &gt; &quot;";\r
+               String output = "& < > \"";\r
+               assertTrue(StringUtils.decodeFromHtml(input).equals(output));\r
+       }\r
 \r
        public void testFlattenStrings() throws Exception {\r
                String[] strings = { "A", "B", "C", "D" };\r
@@ -70,6 +82,11 @@ public class StringUtilsTest extends TestCase {
                assertTrue(StringUtils.getSHA1("blob 16\000what is up, doc?").equals(\r
                                "bd9dbf5aae1a3862dd1526723246b20206e5fc37"));\r
        }\r
+       \r
+       public void testMD5() throws Exception {\r
+               assertTrue(StringUtils.getMD5("blob 16\000what is up, doc?").equals(\r
+                               "77fb8d95331f0d557472f6776d3aedf6"));\r
+       }\r
 \r
        public void testRootPath() throws Exception {\r
                String input = "/nested/path/to/repository";\r
@@ -77,7 +94,7 @@ public class StringUtilsTest extends TestCase {
                assertTrue(StringUtils.getRootPath(input).equals(output));\r
                assertTrue(StringUtils.getRootPath("repository").equals(""));\r
        }\r
-       \r
+\r
        public void testStringsFromValue() throws Exception {\r
                List<String> strings = StringUtils.getStringsFromValue("A B C D");\r
                assertTrue(strings.size() == 4);\r
diff --git a/tests/com/gitblit/tests/SyndicationUtilsTest.java b/tests/com/gitblit/tests/SyndicationUtilsTest.java
new file mode 100644 (file)
index 0000000..ab51804
--- /dev/null
@@ -0,0 +1,42 @@
+/*\r
+ * Copyright 2011 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.tests;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+import java.util.List;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import org.eclipse.jgit.lib.Repository;\r
+import org.eclipse.jgit.revwalk.RevCommit;\r
+\r
+import com.gitblit.utils.JGitUtils;\r
+import com.gitblit.utils.SyndicationUtils;\r
+\r
+public class SyndicationUtilsTest extends TestCase {\r
+\r
+       public void testSyndication() throws Exception {\r
+               Repository repository = GitBlitSuite.getHelloworldRepository();\r
+               List<RevCommit> commits = JGitUtils.getRevLog(repository, 1);\r
+               ByteArrayOutputStream os = new ByteArrayOutputStream();\r
+               SyndicationUtils.toRSS("http://localhost", "Title", "Description", "Repository", commits, os);\r
+               String feed = os.toString();\r
+               os.close();\r
+               assertTrue(feed.length() > 100);\r
+               assertTrue(feed.indexOf("<title>Title</title>") > -1);\r
+               assertTrue(feed.indexOf("<description>Description</description>") > -1);\r
+       }\r
+}
\ No newline at end of file
diff --git a/tools/ant-googlecode-0.0.3.jar b/tools/ant-googlecode-0.0.3.jar
new file mode 100644 (file)
index 0000000..452fa84
Binary files /dev/null and b/tools/ant-googlecode-0.0.3.jar differ