mschaefers
2012-12-03 ba6ae959b8e21c714c69f66254e82837d45a3ed2
Merge branch 'master' of https://github.com/gitblit/gitblit into enhancedLdap

Conflicts:
distrib/gitblit.properties
4 files added
29 files modified
908 ■■■■ changed files
.classpath 2 ●●●●● patch | view | raw | blame | history
NOTICE 16 ●●●●● patch | view | raw | blame | history
build.xml 7 ●●●●● patch | view | raw | blame | history
distrib/gitblit.properties 13 ●●●●● patch | view | raw | blame | history
docs/04_design.mkd 2 ●●●●● patch | view | raw | blame | history
docs/04_releases.mkd 6 ●●●● patch | view | raw | blame | history
resources/gitblit.css 6 ●●●● patch | view | raw | blame | history
resources/script_16x16.png patch | view | raw | blame | history
src/com/gitblit/Constants.java 8 ●●●●● patch | view | raw | blame | history
src/com/gitblit/DownloadZipServlet.java 54 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 46 ●●●● patch | view | raw | blame | history
src/com/gitblit/authority/GitblitAuthority.java 21 ●●●●● patch | view | raw | blame | history
src/com/gitblit/build/Build.java 16 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/CompressionUtils.java 315 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/HttpUtils.java 40 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/JGitUtils.java 73 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/X509Utils.java 68 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebSession.java 4 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/BasePage.java 38 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.java 10 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoryPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TreePage.html 6 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/TreePage.java 21 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/BranchesPanel.java 21 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/CompressedDownloadsPanel.html 12 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/CompressedDownloadsPanel.java 77 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/HistoryPanel.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/HistoryPanel.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/LogPanel.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/LogPanel.java 2 ●●● patch | view | raw | blame | history
tests/com/gitblit/tests/JGitUtilsTest.java 7 ●●●●● patch | view | raw | blame | history
tests/com/gitblit/tests/X509UtilsTest.java 7 ●●●● patch | view | raw | blame | history
.classpath
@@ -34,6 +34,8 @@
    <classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0-sources.jar" />
    <classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0-sources.jar" />
    <classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" />
    <classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1-sources.jar" />
    <classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0-sources.jar" />
    <classpathentry kind="lib" path="ext/junit-4.10.jar" sourcepath="ext/src/junit-4.10-sources.jar" />
    <classpathentry kind="lib" path="ext/hamcrest-core-1.1.jar" />
    <classpathentry kind="output" path="bin/classes" />
NOTICE
@@ -239,3 +239,19 @@
   http://www.toedter.com/en/jcalendar
   
---------------------------------------------------------------------------
Commons-Compress
---------------------------------------------------------------------------
   Commons-Compress, released under the
   Apache Software License, Version 2.0.
   http://commons.apache.org/compress
---------------------------------------------------------------------------
XZ for Java
---------------------------------------------------------------------------
   XZ for Java, released under the
   Public Domain
   http://tukaani.org/xz/java.html
build.xml
@@ -95,6 +95,7 @@
        <property name="authority.zipfile" value="authority-${gb.version}.zip" />
        <property name="gbapi.zipfile" value="gbapi-${gb.version}.zip" />
        <property name="express.zipfile" value="express-${gb.version}.zip" />
        <property name="distribution.pomfileTmplt" value="tmplt.pom.xml" />
    </target>
    
    
@@ -495,6 +496,11 @@
                <exclude name="com/gitblit/Launcher*.class" />
            </fileset>
        </copy>
        <copy todir="${project.jar.dir}/static">
            <fileset dir="${project.resources.dir}">
                <exclude name="thumbs.db" />
            </fileset>
        </copy>
        <!-- Build the JAR file -->
        <jar basedir="${project.jar.dir}" destfile="${distribution.jarfile}" compress="true" />
@@ -767,6 +773,7 @@
            <resource file="${basedir}/resources/settings_32x32.png" />
            <resource file="${basedir}/resources/search-icon.png" />
            <resource file="${basedir}/resources/mail_16x16.png" />
            <resource file="${basedir}/resources/script_16x16.png" />
            <resource file="${basedir}/resources/blank.png" />
            <resource file="${basedir}/resources/bullet_green.png" />
            <resource file="${basedir}/resources/bullet_orange.png" />
distrib/gitblit.properties
@@ -482,6 +482,19 @@
# SINCE 0.5.0
web.allowZipDownloads = true
# If *web.allowZipDownloads=true* the following formats will be displayed for
# download compressed archive links:
#
# zip   = standard .zip
# tar   = standard tar format (preserves *nix permissions and symlinks)
# gz    = gz-compressed tar
# xz    = xz-compressed tar
# bzip2 = bzip2-compressed tar
#
# SPACE-DELIMITED
# SINCE 1.2.0
web.compressedDownloads = zip gz
# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
# A repository may specify branches to index with Lucene instead of using Git
# commit traversal. There are scenarios where you may want to completely disable
docs/04_design.mkd
@@ -43,6 +43,8 @@
- [UnboundID](http://www.unboundid.com) (LGPL 2.1)
- [Ivy](http://ant.apache.org/ivy) (Apache 2.0)
- [JCalendar](http://www.toedter.com/en/jcalendar) (LGPL 2.1)
- [Commons-Compress](http://commons.apache.org/compress) (Apache 2.0)
- [XZ for Java](http://tukaani.org/xz/java.html) (Public Domain)
### Other Build Dependencies
- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
docs/04_releases.mkd
@@ -60,6 +60,8 @@
- Added Gitblit Certificate Authority, an X509 certificate generation tool for Gitblit GO to encourage use of client certificate authentication.
- Added setting to control length of shortened commit ids  
    **New:** *web.shortCommitIdLength=8*  
- Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)
    **New:** *web.compressedDownloads = zip gz*
- Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.
- Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135)
- Delete branch feature (issue 121, Github/ajermakovics)
@@ -70,6 +72,7 @@
#### changes
- Access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate any Gitblit cookie found in the request before resorting to BASIC authentication.
- Added *groovy* and *scala* to *web.prettyPrintExtensions*
- Added short commit id column to log and history tables (issue 168)
- Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration
@@ -90,7 +93,8 @@
- updated to Lucene 3.6.1
- updated to BouncyCastle 1.47
- added JCalendar 1.3.2
- added Commons-Compress 1.4.1
- added XZ for Java 1.0
<hr/>
### Older Releases
resources/gitblit.css
@@ -984,11 +984,15 @@
    white-space: nowrap;
}
span.sha1, span.sha1 a, span.sha1 a span, .commit_message {
span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1 {
    font-family: consolas, monospace;
    font-size: 13px;
}
span.shortsha1 {
    font-size: 12px;
}
td.mode {
    text-align: right;
    font-family: monospace;
resources/script_16x16.png
src/com/gitblit/Constants.java
@@ -398,6 +398,14 @@
        }
    }
    
    public static enum AuthenticationType {
        CREDENTIALS, COOKIE, CERTIFICATE;
        public boolean isStandard() {
            return ordinal() <= COOKIE.ordinal();
        }
    }
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Unused {
src/com/gitblit/DownloadZipServlet.java
@@ -29,6 +29,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.utils.CompressionUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
@@ -46,6 +47,25 @@
    private transient Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class);
    public static enum Format {
        zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2");
        public final String extension;
        Format(String ext) {
            this.extension = ext;
        }
        public static Format fromName(String name) {
            for (Format format : values()) {
                if (format.name().equalsIgnoreCase(name)) {
                    return format;
                }
            }
            return zip;
        }
    }
    public DownloadZipServlet() {
        super();
    }
@@ -57,15 +77,17 @@
     * @param repository
     * @param objectId
     * @param path
     * @param format
     * @return an url
     */
    public static String asLink(String baseURL, String repository, String objectId, String path) {
    public static String asLink(String baseURL, String repository, String objectId, String path, Format format) {
        if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
            baseURL = baseURL.substring(0, baseURL.length() - 1);
        }
        return baseURL + Constants.ZIP_PATH + "?r=" + repository
                + (path == null ? "" : ("&p=" + path))
                + (objectId == null ? "" : ("&h=" + objectId));
                + (objectId == null ? "" : ("&h=" + objectId))
                + (format == null ? "" : ("&format=" + format.name()));
    }
    /**
@@ -85,15 +107,21 @@
            return;
        }
        Format format = Format.zip;
        String repository = request.getParameter("r");
        String basePath = request.getParameter("p");
        String objectId = request.getParameter("h");
        String f = request.getParameter("format");
        if (!StringUtils.isEmpty(f)) {
            format = Format.fromName(f);
        }
        try {
            String name = repository;
            if (name.indexOf('/') > -1) {
                name = name.substring(name.lastIndexOf('/') + 1);
            }
            name = StringUtils.stripDotGit(name);
            if (!StringUtils.isEmpty(basePath)) {
                name += "-" + basePath.replace('/', '_');
@@ -122,15 +150,31 @@
            String contentType = "application/octet-stream";
            response.setContentType(contentType + "; charset=" + response.getCharacterEncoding());
            response.setHeader("Content-Disposition", "attachment; filename=\"" + name + ".zip"
                    + "\"");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\"");
            response.setDateHeader("Last-Modified", date.getTime());
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);
            try {
                JGitUtils.zip(r, basePath, objectId, response.getOutputStream());
                switch (format) {
                case zip:
                    CompressionUtils.zip(r, basePath, objectId, response.getOutputStream());
                    break;
                case tar:
                    CompressionUtils.tar(r, basePath, objectId, response.getOutputStream());
                    break;
                case gz:
                    CompressionUtils.gz(r, basePath, objectId, response.getOutputStream());
                    break;
                case xz:
                    CompressionUtils.xz(r, basePath, objectId, response.getOutputStream());
                    break;
                case bzip2:
                    CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream());
                    break;
                }
                response.flushBuffer();
            } catch (Throwable t) {
                logger.error("Failed to write attachment to client", t);
src/com/gitblit/GitBlit.java
@@ -58,6 +58,7 @@
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.protocol.http.WebResponse;
import org.apache.wicket.resource.ContextRelativeResource;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
@@ -75,6 +76,7 @@
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthenticationType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationStrategy;
@@ -107,6 +109,8 @@
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.X509Utils.X509Metadata;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
/**
@@ -537,7 +541,7 @@
     * @param cookies
     * @return a user object or null
     */
    public UserModel authenticate(Cookie[] cookies) {
    protected UserModel authenticate(Cookie[] cookies) {
        if (userService == null) {
            return null;
        }
@@ -555,22 +559,52 @@
    }
    /**
     * Authenticate a user based on HTTP request paramters.
     * This method is inteded to be used as fallback when other
     * means of authentication are failing (username / password or cookies).
     * Authenticate a user based on HTTP request parameters.
     *
     * Authentication by X509Certificate is tried first and then by cookie.
     *
     * @param httpRequest
     * @return a user object or null
     */
    public UserModel authenticate(HttpServletRequest httpRequest) {
        // try to authenticate by certificate
        boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
        String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
        UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
        if (model != null) {
            UserModel user = GitBlit.self().getUserModel(model.username);
            logger.info(MessageFormat.format("{0} authenticated by client certificate from {1}",
            // grab real user model and preserve certificate serial number
            UserModel user = getUserModel(model.username);
            if (user != null) {
                RequestCycle requestCycle = RequestCycle.get();
                if (requestCycle != null) {
                    // flag the Wicket session, if this is a Wicket request
                    GitBlitWebSession session = GitBlitWebSession.get();
                    session.authenticationType = AuthenticationType.CERTIFICATE;
                }
                X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
                logger.info(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
                        user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
                return user;
            }
        }
        // try to authenticate by cookie
        Cookie[] cookies = httpRequest.getCookies();
        if (allowCookieAuthentication() && cookies != null && cookies.length > 0) {
            // Grab cookie from Browser Session
            UserModel user = authenticate(cookies);
            if (user != null) {
                RequestCycle requestCycle = RequestCycle.get();
                if (requestCycle != null) {
                    // flag the Wicket session, if this is a Wicket request
                    GitBlitWebSession session = GitBlitWebSession.get();
                    session.authenticationType = AuthenticationType.COOKIE;
                }
                logger.info(MessageFormat.format("{0} authenticated by cookie from {1}",
                    user.username, httpRequest.getRemoteAddr()));
            return user;
        }
        }
        return null;
    }
src/com/gitblit/authority/GitblitAuthority.java
@@ -67,6 +67,7 @@
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.RowFilter;
import javax.swing.SwingConstants;
@@ -92,6 +93,7 @@
import com.gitblit.client.Translation;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.X509Utils;
@@ -343,6 +345,7 @@
        X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword);
        setMetadataDefaults(metadata);
        metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
        X509Utils.prepareX509Infrastructure(metadata, folder, this);
        return true;
    }
@@ -681,6 +684,23 @@
            }
        });
        
        JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png")));
        logButton.setFocusable(false);
        logButton.setToolTipText(Translation.get("gb.log"));
        logButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                File log = new File(folder, X509Utils.CERTS + File.separator + "log.txt");
                if (log.exists()) {
                    String content = FileUtils.readContent(log,  "\n");
                    JTextArea textarea = new JTextArea(content);
                    JScrollPane scrollPane = new JScrollPane(textarea);
                    scrollPane.setPreferredSize(new Dimension(700, 400));
                    JOptionPane.showMessageDialog(GitblitAuthority.this, scrollPane, log.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE);
                }
            }
        });
        final JTextField filterTextfield = new JTextField(15);
        filterTextfield.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
@@ -697,6 +717,7 @@
        buttonControls.add(certificateDefaultsButton);
        buttonControls.add(newSSLCertificate);
        buttonControls.add(emailBundle);
        buttonControls.add(logButton);
        JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, Utils.MARGIN, Utils.MARGIN));
        userControls.add(new JLabel(Translation.get("gb.filter")));
src/com/gitblit/build/Build.java
@@ -106,6 +106,8 @@
        downloadFromApache(MavenObject.UNBOUND_ID, BuildType.RUNTIME);
        downloadFromApache(MavenObject.IVY, BuildType.RUNTIME);
        downloadFromApache(MavenObject.JCALENDAR, BuildType.RUNTIME);
        downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.RUNTIME);
        downloadFromApache(MavenObject.XZ, BuildType.RUNTIME);
        downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
        downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
@@ -143,6 +145,8 @@
        downloadFromApache(MavenObject.UNBOUND_ID, BuildType.COMPILETIME);
        downloadFromApache(MavenObject.IVY, BuildType.COMPILETIME);
        downloadFromApache(MavenObject.JCALENDAR, BuildType.COMPILETIME);
        downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.COMPILETIME);
        downloadFromApache(MavenObject.XZ, BuildType.COMPILETIME);
        
        downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
        downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
@@ -780,6 +784,18 @@
                "323a672aeacb5f5f4461be3b7f7d9d3e4bda80d4",
                null, "");
        
        public static final MavenObject COMMONS_COMPRESS = new MavenObject(
                "commons-compress", "org/apache/commons", "commons-compress", "1.4.1",
                242000, 265000, 0,
                "b02e84a993d88568417536240e970c4b809126fd",
                "277d39267403965a7a192474794a29bac6760a25", "");
        public static final MavenObject XZ = new MavenObject(
                "xz", "org/tukaani", "xz", "1.0",
                95000, 120000, 0,
                "ecff5cb8b1189514c9d1d8d68eb77ac372e000c9",
                "f95e32a5d2dd8da643c4419814415b9704312993", "");
        public final String name;
        public final String group;
        public final String artifact;
src/com/gitblit/utils/CompressionUtils.java
New file
@@ -0,0 +1,315 @@
/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.utils.IOUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * Collection of static methods for retrieving information from a repository.
 *
 * @author James Moger
 *
 */
public class CompressionUtils {
    static final Logger LOGGER = LoggerFactory.getLogger(CompressionUtils.class);
    /**
     * Log an error message and exception.
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
     *            pattern.
     * @param pattern
     * @param objects
     */
    private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
        List<Object> parameters = new ArrayList<Object>();
        if (objects != null && objects.length > 0) {
            for (Object o : objects) {
                parameters.add(o);
            }
        }
        if (repository != null) {
            parameters.add(0, repository.getDirectory().getAbsolutePath());
        }
        LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
    }
    /**
     * Zips the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    public static boolean zip(Repository repository, String basePath, String objectId,
            OutputStream os) {
        RevCommit commit = JGitUtils.getCommit(repository, objectId);
        if (commit == null) {
            return false;
        }
        boolean success = false;
        RevWalk rw = new RevWalk(repository);
        TreeWalk tw = new TreeWalk(repository);
        try {
            tw.addTree(commit.getTree());
            ZipOutputStream zos = new ZipOutputStream(os);
            zos.setComment("Generated by Gitblit");
            if (!StringUtils.isEmpty(basePath)) {
                PathFilter f = PathFilter.create(basePath);
                tw.setFilter(f);
            }
            tw.setRecursive(true);
            while (tw.next()) {
                if (tw.getFileMode(0) == FileMode.GITLINK) {
                    continue;
                }
                ZipEntry entry = new ZipEntry(tw.getPathString());
                entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0),
                        Constants.OBJ_BLOB));
                entry.setComment(commit.getName());
                zos.putNextEntry(entry);
                ObjectId entid = tw.getObjectId(0);
                FileMode entmode = tw.getFileMode(0);
                RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType());
                rw.parseBody(blob);
                ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
                byte[] tmp = new byte[4096];
                InputStream in = ldr.openStream();
                int n;
                while ((n = in.read(tmp)) > 0) {
                    zos.write(tmp, 0, n);
                }
                in.close();
            }
            zos.finish();
            success = true;
        } catch (IOException e) {
            error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
        } finally {
            tw.release();
            rw.dispose();
        }
        return success;
    }
    /**
     * tar the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    public static boolean tar(Repository repository, String basePath, String objectId,
            OutputStream os) {
        return tar(null, repository, basePath, objectId, os);
    }
    /**
     * tar.gz the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    public static boolean gz(Repository repository, String basePath, String objectId,
            OutputStream os) {
        return tar(CompressorStreamFactory.GZIP, repository, basePath, objectId, os);
    }
    /**
     * tar.xz the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    public static boolean xz(Repository repository, String basePath, String objectId,
            OutputStream os) {
        return tar(CompressorStreamFactory.XZ, repository, basePath, objectId, os);
    }
    /**
     * tar.bzip2 the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    public static boolean bzip2(Repository repository, String basePath, String objectId,
            OutputStream os) {
        return tar(CompressorStreamFactory.BZIP2, repository, basePath, objectId, os);
    }
    /**
     * Compresses/archives the contents of the tree at the (optionally)
     * specified revision and the (optionally) specified basepath to the
     * supplied outputstream.
     *
     * @param algorithm
     *            compression algorithm for tar (optional)
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    private static boolean tar(String algorithm, Repository repository, String basePath, String objectId,
            OutputStream os) {
        RevCommit commit = JGitUtils.getCommit(repository, objectId);
        if (commit == null) {
            return false;
        }
        OutputStream cos = os;
        if (!StringUtils.isEmpty(algorithm)) {
            try {
                cos = new CompressorStreamFactory().createCompressorOutputStream(algorithm, os);
            } catch (CompressorException e1) {
                error(e1, repository, "{0} failed to open {1} stream", algorithm);
            }
        }
        boolean success = false;
        RevWalk rw = new RevWalk(repository);
        TreeWalk tw = new TreeWalk(repository);
        try {
            tw.addTree(commit.getTree());
            TarArchiveOutputStream tos = new TarArchiveOutputStream(cos);
            tos.setAddPaxHeadersForNonAsciiNames(true);
            tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            if (!StringUtils.isEmpty(basePath)) {
                PathFilter f = PathFilter.create(basePath);
                tw.setFilter(f);
            }
            tw.setRecursive(true);
            while (tw.next()) {
                FileMode mode = tw.getFileMode(0);
                if (mode == FileMode.GITLINK) {
                    continue;
                }
                ObjectId id = tw.getObjectId(0);
                // new entry
                TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString());
                entry.setSize(tw.getObjectReader().getObjectSize(id, Constants.OBJ_BLOB));
                if (FileMode.SYMLINK.equals(mode)) {
                    // symlink
                    entry.setMode(mode.getBits());
                    // read the symlink target
                    ByteArrayOutputStream bs = new ByteArrayOutputStream();
                    RevBlob blob = (RevBlob) rw.lookupAny(id, mode.getObjectType());
                    rw.parseBody(blob);
                    ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
                    IOUtils.copy(ldr.openStream(), bs);
                    entry.setLinkName(bs.toString("UTF-8"));
                } else {
                    // regular file or executable file
                    entry.setMode(mode.getBits());
                }
                entry.setModTime(commit.getAuthorIdent().getWhen());
                tos.putArchiveEntry(entry);
                if (!FileMode.SYMLINK.equals(mode)) {
                    // write the blob
                    RevBlob blob = (RevBlob) rw.lookupAny(id, mode.getObjectType());
                    rw.parseBody(blob);
                    ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
                    IOUtils.copy(ldr.openStream(), tos);
                }
                // close entry
                tos.closeArchiveEntry();
            }
            tos.finish();
            tos.close();
            cos.close();
            success = true;
        } catch (IOException e) {
            error(e, repository, "{0} failed to {1} stream files from commit {2}", algorithm, commit.getName());
        } finally {
            tw.release();
            rw.dispose();
        }
        return success;
    }
}
src/com/gitblit/utils/HttpUtils.java
@@ -20,14 +20,13 @@
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.LoggerFactory;
import com.gitblit.models.UserModel;
import com.gitblit.utils.X509Utils.X509Metadata;
/**
 * Collection of utility methods for http requests.
@@ -145,21 +144,11 @@
     * @return
     */
    public static UserModel getUserModelFromCertificate(X509Certificate cert, String... usernameOIDs) {
        UserModel user = new UserModel(null);
        user.isAuthenticated = false;
        X509Metadata metadata = X509Utils.getMetadata(cert);
        
        // manually split DN into OID components
        // this is instead of parsing with LdapName which:
        // (1) I don't trust the order of values
        // (2) it filters out values like EMAILADDRESS
        String dn = cert.getSubjectDN().getName();
        Map<String, String> oids = new HashMap<String, String>();
        for (String kvp : dn.split(",")) {
            String [] val = kvp.trim().split("=");
            String oid = val[0].toUpperCase().trim();
            String data = val[1].trim();
            oids.put(oid, data);
        }
        UserModel user = new UserModel(metadata.commonName);
        user.emailAddress = metadata.emailAddress;
        user.isAuthenticated = false;
        
        if (usernameOIDs == null || usernameOIDs.length == 0) {
            // use default usename<->CN mapping
@@ -169,24 +158,23 @@
        // determine username from OID fingerprint
        StringBuilder an = new StringBuilder();
        for (String oid : usernameOIDs) {
            String val = getOIDValue(oid.toUpperCase(), oids);
            String val = metadata.getOID(oid.toUpperCase(), null);
            if (val != null) {
                an.append(val).append(' ');
            }
        }
        user.username = an.toString().trim();
        // extract email address, if available
        user.emailAddress = getOIDValue("E", oids);
        if (user.emailAddress == null) {
            user.emailAddress = getOIDValue("EMAILADDRESS", oids);
        }
        return user;
    }
    
    private static String getOIDValue(String oid, Map<String, String> oids) {
        if (oids.containsKey(oid)) {
            return oids.get(oid);
    public static X509Metadata getCertificateMetadata(HttpServletRequest httpRequest) {
        if (httpRequest.getAttribute("javax.servlet.request.X509Certificate") != null) {
            X509Certificate[] certChain = (X509Certificate[]) httpRequest
                    .getAttribute("javax.servlet.request.X509Certificate");
            if (certChain != null) {
                X509Certificate cert = certChain[0];
                return X509Utils.getMetadata(cert);
            }
        }
        return null;
    }
src/com/gitblit/utils/JGitUtils.java
@@ -19,7 +19,6 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -30,8 +29,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.FetchCommand;
@@ -1531,7 +1528,7 @@
        try {
            // search for the branch in local heads
            for (RefModel ref : JGitUtils.getLocalBranches(repository, false, -1)) {
                if (ref.displayName.endsWith(name)) {
                if (ref.reference.getName().endsWith(name)) {
                    branch = ref;
                    break;
                }
@@ -1540,7 +1537,7 @@
            // search for the branch in remote heads
            if (branch == null) {
                for (RefModel ref : JGitUtils.getRemoteBranches(repository, false, -1)) {
                    if (ref.displayName.endsWith(name)) {
                    if (ref.reference.getName().endsWith(name)) {
                        branch = ref;
                        break;
                    }
@@ -1720,72 +1717,6 @@
            }
        } catch (Throwable t) {
            error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
        }
        return success;
    }
    /**
     * Zips the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
     * @param objectId
     *            if unspecified, HEAD is assumed.
     * @param os
     * @return true if repository was successfully zipped to supplied output
     *         stream
     */
    public static boolean zip(Repository repository, String basePath, String objectId,
            OutputStream os) {
        RevCommit commit = getCommit(repository, objectId);
        if (commit == null) {
            return false;
        }
        boolean success = false;
        RevWalk rw = new RevWalk(repository);
        TreeWalk tw = new TreeWalk(repository);
        try {
            tw.addTree(commit.getTree());
            ZipOutputStream zos = new ZipOutputStream(os);
            zos.setComment("Generated by Gitblit");
            if (!StringUtils.isEmpty(basePath)) {
                PathFilter f = PathFilter.create(basePath);
                tw.setFilter(f);
            }
            tw.setRecursive(true);
            while (tw.next()) {
                if (tw.getFileMode(0) == FileMode.GITLINK) {
                    continue;
                }
                ZipEntry entry = new ZipEntry(tw.getPathString());
                entry.setSize(tw.getObjectReader().getObjectSize(tw.getObjectId(0),
                        Constants.OBJ_BLOB));
                entry.setComment(commit.getName());
                zos.putNextEntry(entry);
                ObjectId entid = tw.getObjectId(0);
                FileMode entmode = tw.getFileMode(0);
                RevBlob blob = (RevBlob) rw.lookupAny(entid, entmode.getObjectType());
                rw.parseBody(blob);
                ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
                byte[] tmp = new byte[4096];
                InputStream in = ldr.openStream();
                int n;
                while ((n = in.read(tmp)) > 0) {
                    zos.write(tmp, 0, n);
                }
                in.close();
            }
            zos.finish();
            success = true;
        } catch (IOException e) {
            error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
        } finally {
            tw.release();
            rw.dispose();
        }
        return success;
    }
src/com/gitblit/utils/X509Utils.java
@@ -111,6 +111,12 @@
    private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
    
    private static final int KEY_LENGTH = 2048;
    private static final String KEY_ALGORITHM = "RSA";
    private static final String SIGNING_ALGORITHM = "SHA512withRSA";
    public static final boolean unlimitedStrength;
    
    private static final Logger logger = LoggerFactory.getLogger(X509Utils.class);
@@ -182,6 +188,9 @@
        
        // displayname of user for README in bundle
        public String userDisplayname;
        // serialnumber of generated or read certificate
        public String serialNumber;
        public X509Metadata(String cn, String pwd) {
            if (StringUtils.isEmpty(cn)) {
@@ -472,8 +481,8 @@
     * @throws Exception
     */
    private static KeyPair newKeyPair() throws Exception {
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC);
        kpGen.initialize(2048, new SecureRandom());
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance(KEY_ALGORITHM, BC);
        kpGen.initialize(KEY_LENGTH, new SecureRandom());
        return kpGen.generateKeyPair();
    }
    
@@ -547,7 +556,7 @@
            certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
            certBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caCert.getPublicKey()));
            ContentSigner caSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
            ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM)
                    .setProvider(BC).build(caPrivateKey);
            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
                    .getCertificate(certBuilder.build(caSigner));
@@ -562,6 +571,10 @@
            saveKeyStore(targetStoreFile, serverStore, sslMetadata.password);
            
            x509log.log(MessageFormat.format("New SSL certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));
            // update serial number in metadata object
            sslMetadata.serialNumber = cert.getSerialNumber().toString();
            return cert;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to generate SSL certificate!", t);
@@ -582,7 +595,7 @@
        try {
            KeyPair caPair = newKeyPair();
            
            ContentSigner caSigner = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(caPair.getPrivate());
            ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPair.getPrivate());
            
            // clone metadata
            X509Metadata caMetadata = metadata.clone(CA_CN, metadata.password);
@@ -623,6 +636,9 @@
            
            x509log.log(MessageFormat.format("New CA certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getIssuerDN().getName()));
            // update serial number in metadata object
            caMetadata.serialNumber = cert.getSerialNumber().toString();
            return cert;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to generate Gitblit CA certificate!", t);
@@ -649,7 +665,7 @@
            X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
            
            // build and sign CRL with CA private key
            ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(caPrivateKey);
            ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
            X509CRLHolder crl = crlBuilder.build(signer);
            File tmpFile = new File(caRevocationList.getParentFile(), Long.toHexString(System.currentTimeMillis()) + ".tmp");
@@ -743,6 +759,17 @@
                    zos.write(FileUtils.readContent(pemFile));
                    zos.closeEntry();
                }
                // include user's public certificate
                zos.putNextEntry(new ZipEntry(clientMetadata.commonName + ".cer"));
                zos.write(cert.getEncoded());
                zos.closeEntry();
                // include CA public certificate
                zos.putNextEntry(new ZipEntry("ca.cer"));
                zos.write(caCert.getEncoded());
                zos.closeEntry();
                if (readme != null) {
                    zos.putNextEntry(new ZipEntry("README.TXT"));
                    zos.write(readme.getBytes("UTF-8"));
@@ -799,7 +826,7 @@
                certBuilder.addExtension(X509Extension.subjectAlternativeName, false, subjectAltName);
            }
            ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(caPrivateKey);
            ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
            X509Certificate userCert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certBuilder.build(signer));
            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)pair.getPrivate();
@@ -851,6 +878,9 @@
            
            // save certificate after successfully creating the key stores
            saveCertificate(userCert, certFile);
            // update serial number in metadata object
            clientMetadata.serialNumber = userCert.getSerialNumber().toString();
            
            return userCert;
        } catch (Throwable t) {
@@ -1065,4 +1095,30 @@
        }
        return false;
    }
    public static X509Metadata getMetadata(X509Certificate cert) {
        // manually split DN into OID components
        // this is instead of parsing with LdapName which:
        // (1) I don't trust the order of values
        // (2) it filters out values like EMAILADDRESS
        String dn = cert.getSubjectDN().getName();
        Map<String, String> oids = new HashMap<String, String>();
        for (String kvp : dn.split(",")) {
            String [] val = kvp.trim().split("=");
            String oid = val[0].toUpperCase().trim();
            String data = val[1].trim();
            oids.put(oid, data);
        }
        X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
        metadata.oids.putAll(oids);
        metadata.serialNumber = cert.getSerialNumber().toString();
        metadata.notAfter = cert.getNotAfter();
        metadata.notBefore = cert.getNotBefore();
        metadata.emailAddress = metadata.getOID("E", null);
        if (metadata.emailAddress == null) {
            metadata.emailAddress = metadata.getOID("EMAILADDRESS", null);
        }
        return metadata;
    }
}
src/com/gitblit/wicket/GitBlitWebSession.java
@@ -29,6 +29,7 @@
import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.protocol.http.request.WebClientInfo;
import com.gitblit.Constants.AuthenticationType;
import com.gitblit.models.UserModel;
public final class GitBlitWebSession extends WebSession {
@@ -45,9 +46,12 @@
    
    private AtomicBoolean isForking;
    
    public AuthenticationType authenticationType;
    public GitBlitWebSession(Request request) {
        super(request);
        isForking = new AtomicBoolean();
        authenticationType = AuthenticationType.CREDENTIALS;
    }
    public void invalidate() {
src/com/gitblit/wicket/pages/BasePage.java
@@ -29,7 +29,6 @@
import java.util.TimeZone;
import java.util.regex.Pattern;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.Application;
@@ -131,22 +130,18 @@
    }    
    private void login() {
        // try to authenticate by servlet request
        UserModel user = GitBlit.self().authenticate(((WebRequest) getRequestCycle().getRequest()).getHttpServletRequest());
        GitBlitWebSession session = GitBlitWebSession.get();
        if (session.isLoggedIn() && !session.isSessionInvalidated()) {
            // already have a session
            return;
        }
        if (user == null) {
            // try to authenticate by cookie
            Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();
            if (GitBlit.self().allowCookieAuthentication() && cookies != null && cookies.length > 0) {
                // Grab cookie from Browser Session
                user = GitBlit.self().authenticate(cookies);
            }
        }
        // try to authenticate by servlet request
        HttpServletRequest httpRequest = ((WebRequest) getRequestCycle().getRequest()).getHttpServletRequest();
        UserModel user = GitBlit.self().authenticate(httpRequest);
        // Login the user
        if (user != null) {
            // Set the user into the session
            GitBlitWebSession session = GitBlitWebSession.get();
            // issue 62: fix session fixation vulnerability
            session.replaceSession();
            session.setUser(user);
@@ -431,14 +426,19 @@
        public UserFragment(String id, String markupId, MarkupContainer markupProvider) {
            super(id, markupId, markupProvider);
            if (GitBlitWebSession.get().isLoggedIn()) {
                // username, logout, and change password
                add(new Label("username", GitBlitWebSession.get().getUser().getDisplayName() + ":"));
                add(new LinkPanel("loginLink", null, markupProvider.getString("gb.logout"),
                        LogoutPage.class));
            GitBlitWebSession session = GitBlitWebSession.get();
            if (session.isLoggedIn()) {
                UserModel user = session.getUser();
                boolean editCredentials = GitBlit.self().supportsCredentialChanges();
                boolean standardLogin = session.authenticationType.isStandard();
                // username, logout, and change password
                add(new Label("username", user.getDisplayName() + ":"));
                add(new LinkPanel("loginLink", null, markupProvider.getString("gb.logout"),
                        LogoutPage.class).setVisible(standardLogin));
                // quick and dirty hack for showing a separator
                add(new Label("separator", "|").setVisible(editCredentials));
                add(new Label("separator", "|").setVisible(standardLogin && editCredentials));
                add(new BookmarkablePageLink<Void>("changePasswordLink", 
                        ChangePasswordPage.class).setVisible(editCredentials));
            } else {
src/com/gitblit/wicket/pages/CommitPage.html
@@ -30,7 +30,7 @@
        <tr class="hidden-phone"><th><wicket:message key="gb.tree">tree</wicket:message></th>
            <td><span class="sha1" wicket:id="commitTree">[commit tree]</span>
                <span class="link">
                    <a wicket:id="treeLink"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="zipLink"><wicket:message key="gb.zip"></wicket:message></a>
                    <a wicket:id="treeLink"><wicket:message key="gb.tree"></wicket:message></a> | <span wicket:id="compressedLinks"></span>
                </span>
            </td></tr>
        <tr class="hidden-phone"><th valign="top"><wicket:message key="gb.parent">parent</wicket:message></th>
src/com/gitblit/wicket/pages/CommitPage.java
@@ -22,7 +22,6 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
@@ -32,16 +31,15 @@
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.Constants;
import com.gitblit.DownloadZipServlet;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.GitNote;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.CommitLegendPanel;
import com.gitblit.wicket.panels.CompressedDownloadsPanel;
import com.gitblit.wicket.panels.GravatarImage;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.RefsPanel;
@@ -95,8 +93,8 @@
                newCommitParameter()));
        add(new BookmarkablePageLink<Void>("treeLink", TreePage.class, newCommitParameter()));
        final String baseUrl = WicketUtils.getGitblitURL(getRequest());
        add(new ExternalLink("zipLink", DownloadZipServlet.asLink(baseUrl, repositoryName,
                objectId, null)).setVisible(GitBlit.getBoolean(Keys.web.allowZipDownloads, true)));
        add(new CompressedDownloadsPanel("compressedLinks", baseUrl, repositoryName, objectId, null));
        // Parent Commits
        ListDataProvider<String> parentsDp = new ListDataProvider<String>(parents);
src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -115,7 +115,7 @@
                boolean canAccess = user.hasBranchPermission(repositoryName,
                                branch.reference.getName());
                if (!canAccess) {
                    error(getString("gb.accessDeined"), true);
                    error(getString("gb.accessDenied"), true);
                }
            }
        }
src/com/gitblit/wicket/pages/TreePage.html
@@ -9,7 +9,7 @@
    <!-- blob nav links -->    
    <div class="page_nav2">
        <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a> | <a wicket:id="zipLink"><wicket:message key="gb.zip"></wicket:message></a>
        <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a> | <span wicket:id="compressedLinks"></span>
    </div>    
    
    <!-- commit header -->
@@ -32,14 +32,14 @@
    <!--  submodule links -->
    <wicket:fragment wicket:id="submoduleLinks">
        <span class="link">
            <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <span class="hidden-phone"><a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | </span><a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="zip"><wicket:message key="gb.zip"></wicket:message></a>
            <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <span class="hidden-phone"><a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | </span><a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | <span wicket:id="compressedLinks"></span>
        </span>
    </wicket:fragment>
    <!--  tree links -->
    <wicket:fragment wicket:id="treeLinks">
        <span class="link">
            <span class="hidden-phone"><a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | </span><a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="zip"><wicket:message key="gb.zip"></wicket:message></a>
            <span class="hidden-phone"><a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | </span><a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | <span wicket:id="compressedLinks"></span>
        </span>
    </wicket:fragment>
    
src/com/gitblit/wicket/pages/TreePage.java
@@ -20,7 +20,6 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
@@ -30,15 +29,13 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.DownloadZipServlet;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.CompressedDownloadsPanel;
import com.gitblit.wicket.panels.LinkPanel;
import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@@ -58,9 +55,8 @@
                WicketUtils.newPathParameter(repositoryName, objectId, path)));
        add(new BookmarkablePageLink<Void>("headLink", TreePage.class,
                WicketUtils.newPathParameter(repositoryName, Constants.HEAD, path)));
        add(new ExternalLink("zipLink", DownloadZipServlet.asLink(getRequest()
                .getRelativePathPrefixToContextRoot(), repositoryName, objectId, path))
                .setVisible(GitBlit.getBoolean(Keys.web.allowZipDownloads, true)));
        add(new CompressedDownloadsPanel("compressedLinks", getRequest()
                .getRelativePathPrefixToContextRoot(), repositoryName, objectId, path));
        add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
@@ -115,9 +111,9 @@
                        links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
                                WicketUtils.newPathParameter(repositoryName, entry.commitId,
                                        entry.path)));
                        links.add(new ExternalLink("zip", DownloadZipServlet.asLink(baseUrl,
                                repositoryName, objectId, entry.path)).setVisible(GitBlit
                                .getBoolean(Keys.web.allowZipDownloads, true)));
                        links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
                                repositoryName, objectId, entry.path));
                        item.add(links);
                    } else if (entry.isSubmodule()) {
                        // submodule
@@ -143,9 +139,8 @@
                        links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
                                WicketUtils.newPathParameter(submodulePath, submoduleId,
                                        "")).setEnabled(hasSubmodule));
                        links.add(new ExternalLink("zip", DownloadZipServlet.asLink(baseUrl,
                                submodulePath, submoduleId, "")).setVisible(GitBlit
                                .getBoolean(Keys.web.allowZipDownloads, true)).setEnabled(hasSubmodule));
                        links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
                                submodulePath, submoduleId, "").setEnabled(hasSubmodule));
                        item.add(links);                        
                    } else {
                        // blob link
src/com/gitblit/wicket/panels/BranchesPanel.java
@@ -36,8 +36,10 @@
import com.gitblit.SyndicationServlet;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.pages.BranchesPage;
import com.gitblit.wicket.pages.CommitPage;
@@ -58,9 +60,24 @@
        // branches
        List<RefModel> branches = new ArrayList<RefModel>();
        branches.addAll(JGitUtils.getLocalBranches(r, false, maxCount));
        UserModel user = GitBlitWebSession.get().getUser();
        if (user == null) {
            user = UserModel.ANONYMOUS;
        }
        List<RefModel> localBranches = JGitUtils.getLocalBranches(r, false, -1);
        for (RefModel refModel : localBranches) {
            if (user.hasBranchPermission(model.name, refModel.reference.getName())) {
                branches.add(refModel);
            }
        }
        if (model.showRemoteBranches) {
            branches.addAll(JGitUtils.getRemoteBranches(r, false, maxCount));
            List<RefModel> remoteBranches = JGitUtils.getRemoteBranches(r, false, -1);
            for (RefModel refModel : remoteBranches) {
                if (user.hasBranchPermission(model.name, refModel.reference.getName())) {
                    branches.add(refModel);
                }
            }
        }
        Collections.sort(branches);
        Collections.reverse(branches);
src/com/gitblit/wicket/panels/CompressedDownloadsPanel.html
New file
@@ -0,0 +1,12 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"
      xml:lang="en"
      lang="en">
<wicket:panel>
    <span wicket:id="compressedLinks">
        <span wicket:id="linkSep">|</span><span wicket:id="compressedLink">ref</span>
    </span>
</wicket:panel>
</html>
src/com/gitblit/wicket/panels/CompressedDownloadsPanel.java
New file
@@ -0,0 +1,77 @@
/*
 * Copyright 2012 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import com.gitblit.DownloadZipServlet;
import com.gitblit.DownloadZipServlet.Format;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
public class CompressedDownloadsPanel extends Panel {
    private static final long serialVersionUID = 1L;
    public CompressedDownloadsPanel(String id, final String baseUrl, final String repositoryName, final String objectId, final String path) {
        super(id);
        List<String> types = GitBlit.getStrings(Keys.web.compressedDownloads);
        if (types.isEmpty()) {
            types.add(Format.zip.name());
            types.add(Format.gz.name());
        }
        ListDataProvider<String> refsDp = new ListDataProvider<String>(types);
        DataView<String> refsView = new DataView<String>("compressedLinks", refsDp) {
            private static final long serialVersionUID = 1L;
            int counter;
            @Override
            protected void onBeforeRender() {
                super.onBeforeRender();
                counter = 0;
            }
            @Override
            public void populateItem(final Item<String> item) {
                String compressionType = item.getModelObject();
                Format format = Format.fromName(compressionType);
                String href = DownloadZipServlet.asLink(baseUrl, repositoryName,
                        objectId, path, format);
                Component c = new LinkPanel("compressedLink", null, format.name(), href);
                item.add(c);
                Label lb = new Label("linkSep", "|");
                lb.setVisible(counter > 0);
                lb.setRenderBodyOnly(true);
                item.add(lb.setEscapeModelStrings(false));
                item.setRenderBodyOnly(true);
                counter++;
            }
        };
        add(refsView);
        setVisible(GitBlit.getBoolean(Keys.web.allowZipDownloads, true));
    }
}
src/com/gitblit/wicket/panels/HistoryPanel.html
@@ -20,7 +20,7 @@
                 <td class="icon"><img wicket:id="commitIcon" /></td>
                 <td class="hidden-phone author"><span wicket:id="commitAuthor">[commit author]</span></td>
                 <td class="message"><table class="nestedTable"><tr><td><span style="vertical-align:middle;" wicket:id="commitShortMessage">[commit short message]</span></td><td><div style="text-align:right;" wicket:id="commitRefs">[commit refs]</div></td></tr></table></td>
                 <td class="hidden-phone hidden-tablet"><span class="link" wicket:id="hashLabel">[hash label]</span><span wicket:id="hashLink">[hash link]</span></td>
                 <td class="hidden-phone hidden-tablet rightAlign"><span class="link" wicket:id="hashLabel">[hash label]</span><span wicket:id="hashLink">[hash link]</span></td>
                 <td class="hidden-phone rightAlign">
                     <span wicket:id="historyLinks">[history links]</span>
                </td>
src/com/gitblit/wicket/panels/HistoryPanel.java
@@ -171,7 +171,7 @@
                    LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
                            TreePage.class, WicketUtils.newObjectParameter(
                                    repositoryName, entry.getName()));
                    WicketUtils.setCssClass(commitHash, "sha1");
                    WicketUtils.setCssClass(commitHash, "shortsha1");
                    WicketUtils.setHtmlTooltip(commitHash, entry.getName());                    
                    item.add(commitHash);
                    
src/com/gitblit/wicket/panels/LogPanel.html
@@ -16,7 +16,7 @@
                 <td class="hidden-phone author"><span wicket:id="commitAuthor">[commit author]</span></td>
                 <td class="hidden-phone icon"><img wicket:id="commitIcon" /></td>
                 <td class="message"><table class="nestedTable"><tr><td><span style="vertical-align:middle;" wicket:id="commitShortMessage">[commit short message]</span></td><td><div style="text-align:right;" wicket:id="commitRefs">[commit refs]</div></td></tr></table></td>
                 <td class="hidden-phone hidden-tablet"><span wicket:id="hashLink">[hash link]</span></td>
                 <td class="hidden-phone hidden-tablet rightAlign"><span wicket:id="hashLink">[hash link]</span></td>
                 <td class="hidden-phone hidden-tablet rightAlign">
                     <span class="link">
                        <a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
src/com/gitblit/wicket/panels/LogPanel.java
@@ -131,7 +131,7 @@
                LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
                        CommitPage.class, WicketUtils.newObjectParameter(
                                repositoryName, entry.getName()));
                WicketUtils.setCssClass(commitHash, "sha1");
                WicketUtils.setCssClass(commitHash, "shortsha1");
                WicketUtils.setHtmlTooltip(commitHash, entry.getName());
                item.add(commitHash);
                
tests/com/gitblit/tests/JGitUtilsTest.java
@@ -50,6 +50,7 @@
import com.gitblit.models.PathModel;
import com.gitblit.models.PathModel.PathChangeModel;
import com.gitblit.models.RefModel;
import com.gitblit.utils.CompressionUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
@@ -446,16 +447,16 @@
    @Test
    public void testZip() throws Exception {
        assertFalse(JGitUtils.zip(null, null, null, null));
        assertFalse(CompressionUtils.zip(null, null, null, null));
        Repository repository = GitBlitSuite.getHelloworldRepository();
        File zipFileA = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip");
        FileOutputStream fosA = new FileOutputStream(zipFileA);
        boolean successA = JGitUtils.zip(repository, null, Constants.HEAD, fosA);
        boolean successA = CompressionUtils.zip(repository, null, Constants.HEAD, fosA);
        fosA.close();
        File zipFileB = new File(GitBlitSuite.REPOSITORIES, "helloworld-java.zip");
        FileOutputStream fosB = new FileOutputStream(zipFileB);
        boolean successB = JGitUtils.zip(repository, "java.java", Constants.HEAD, fosB);
        boolean successB = CompressionUtils.zip(repository, "java.java", Constants.HEAD, fosB);
        fosB.close();
        repository.close();
tests/com/gitblit/tests/X509UtilsTest.java
@@ -118,7 +118,12 @@
        File zip = X509Utils.newClientBundle(userMetadata, storeFile, caPassword, log);
        assertTrue(zip.exists());
        
        List<String> expected = Arrays.asList(userMetadata.commonName + ".pem", userMetadata.commonName + ".p12", "README.TXT");
        List<String> expected = Arrays.asList(
                userMetadata.commonName + ".pem",
                userMetadata.commonName + ".p12",
                userMetadata.commonName + ".cer",
                "ca.cer",
                "README.TXT");
        
        ZipInputStream zis = new ZipInputStream(new FileInputStream(zip));
        ZipEntry entry = null;