James Moger
2013-09-27 234933ef14ca0ff2175235bc42bf1aa40040aa5d
Merge receive processing into one class

Change-Id: I603d4524914e94ec8e02c3689b65465b42c23dd7
1 files renamed
1 files added
3 files modified
437 ■■■■ changed files
src/main/java/com/gitblit/git/GitblitReceivePack.java 244 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitReceivePackFactory.java 57 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/SideBandProgressMonitor.java 127 ●●●●● patch | view | raw | blame | history
src/test/config/test-gitblit.properties 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitReceivePack.java
File was renamed from src/main/java/com/gitblit/git/ReceiveHook.java
@@ -1,5 +1,5 @@
/*
 * Copyright 2011 gitblit.com.
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
 */
package com.gitblit.git;
import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
@@ -25,8 +26,13 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PreReceiveHook;
@@ -50,40 +56,60 @@
import com.gitblit.utils.RefLogUtils;
import com.gitblit.utils.StringUtils;
/**
 * The Gitblit receive hook allows for special processing on push events.
 * That might include rejecting writes to specific branches or executing a
 * script.
 *
 * GitblitReceivePack processes receive commands.  It also executes Groovy pre-
 * and post- receive hooks.
 *
 * The general execution flow is:
 * <ol>
 *    <li>onPreReceive()</li>
 *    <li>executeCommands()</li>
 *    <li>onPostReceive()</li>
 * </ol>
 *
 * @author Android Open Source Project
 * @author James Moger
 *
 *
 */
public class ReceiveHook implements PreReceiveHook, PostReceiveHook {
public class GitblitReceivePack extends ReceivePack implements PreReceiveHook, PostReceiveHook {
    protected final Logger logger = LoggerFactory.getLogger(ReceiveHook.class);
    private static final Logger LOGGER = LoggerFactory.getLogger(GitblitReceivePack.class);
    protected UserModel user;
    protected RepositoryModel repository;
    protected final RepositoryModel repository;
    protected final UserModel user;
    protected final File groovyDir;
    protected String gitblitUrl;
    private GroovyScriptEngine gse;
    protected String repositoryUrl;
    private File groovyDir;
    protected GroovyScriptEngine gse;
    public ReceiveHook() {
        groovyDir = GitBlit.getGroovyScriptsFolder();
    public GitblitReceivePack(Repository db, RepositoryModel repository, UserModel user) {
        super(db);
        this.repository = repository;
        this.user = user == null ? UserModel.ANONYMOUS : user;
        this.groovyDir = GitBlit.getGroovyScriptsFolder();
        try {
            // set Grape root
            File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
            grapeRoot.mkdirs();
            System.setProperty("grape.root", grapeRoot.getAbsolutePath());
            gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
            this.gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
        } catch (IOException e) {
            //throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
        }
        // set advanced ref permissions
        setAllowCreates(user.canCreateRef(repository));
        setAllowDeletes(user.canDeleteRef(repository));
        setAllowNonFastForwards(user.canRewindRef(repository));
        // setup pre and post receive hook
        setPreReceiveHook(this);
        setPostReceiveHook(this);
    }
    /**
@@ -93,32 +119,27 @@
     */
    @Override
    public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
        if (repository.isFrozen) {
            // repository is frozen/readonly
            String reason = MessageFormat.format("Gitblit does not allow pushes to \"{0}\" because it is frozen!", repository.name);
            logger.warn(reason);
            for (ReceiveCommand cmd : commands) {
                cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
                sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it is frozen!", repository.name);
            }
            return;
        }
        if (!repository.isBare) {
            // repository has a working copy
            String reason = MessageFormat.format("Gitblit does not allow pushes to \"{0}\" because it has a working copy!", repository.name);
            logger.warn(reason);
            for (ReceiveCommand cmd : commands) {
                cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
                sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it has a working copy!", repository.name);
            }
            return;
        }
        if (!user.canPush(repository)) {
            // user does not have push permissions
            String reason = MessageFormat.format("User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
            logger.warn(reason);
            for (ReceiveCommand cmd : commands) {
                cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
                sendRejection(cmd, "User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
            }
            return;
        }
@@ -126,51 +147,51 @@
        if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
            // enforce committer verification
            if (StringUtils.isEmpty(user.emailAddress)) {
                // emit warning if user does not have an email address
                logger.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
                // emit warning if user does not have an email address
                LOGGER.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
            }
            // Optionally enforce that the committer of the left parent chain
            // Optionally enforce that the committer of first parent chain
            // match the account being used to push the commits.
            //
            //
            // This requires all merge commits are executed with the "--no-ff"
            // option to force a merge commit even if fast-forward is possible.
            // This ensures that the chain of left parents has the commit
            // This ensures that the chain first parents has the commit
            // identity of the merging user.
            boolean allRejected = false;
            for (ReceiveCommand cmd : commands) {
                String linearParent = null;
                String firstParent = null;
                try {
                    List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(), cmd.getNewId().name());
                    for (RevCommit commit : commits) {
                        if (linearParent != null) {
                            if (!commit.getName().equals(linearParent)) {
                        if (firstParent != null) {
                            if (!commit.getName().equals(firstParent)) {
                                // ignore: commit is right-descendant of a merge
                                continue;
                            }
                        }
                        // update expected next commit id
                        if (commit.getParentCount() == 0) {
                            linearParent = null;
                            firstParent = null;
                        } else {
                            linearParent = commit.getParents()[0].getId().getName();
                            firstParent = commit.getParents()[0].getId().getName();
                        }
                        PersonIdent committer = commit.getCommitterIdent();
                        if (!user.is(committer.getName(), committer.getEmailAddress())) {
                            String reason;
                            if (StringUtils.isEmpty(user.emailAddress)) {
                                // account does not have an email address
                                reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})",
                                reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})",
                                        commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username);
                            } else {
                                // account has an email address
                                reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>",
                                reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>",
                                        commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
                            }
                            logger.warn(reason);
                            LOGGER.warn(reason);
                            cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
                            allRejected &= true;
                            break;
@@ -179,7 +200,7 @@
                        }
                    }
                } catch (Exception e) {
                    logger.error("Failed to verify commits were made by pushing user", e);
                    LOGGER.error("Failed to verify commits were made by pushing user", e);
                }
            }
@@ -188,7 +209,7 @@
                return;
            }
        }
        // reset branch commit cache on REWIND and DELETE
        for (ReceiveCommand cmd : commands) {
            String ref = cmd.getRefName();
@@ -209,10 +230,10 @@
        if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) {
            scripts.addAll(repository.preReceiveScripts);
        }
        runGroovy(repository, user, commands, rp, scripts);
        runGroovy(commands, scripts);
        for (ReceiveCommand cmd : commands) {
            if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
                logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
                LOGGER.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
                        .getName(), cmd.getResult(), cmd.getMessage()));
            }
        }
@@ -226,26 +247,27 @@
    @Override
    public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
        if (commands.size() == 0) {
            logger.debug("skipping post-receive hooks, no refs created, updated, or removed");
            LOGGER.debug("skipping post-receive hooks, no refs created, updated, or removed");
            return;
        }
        // log ref changes
        for (ReceiveCommand cmd : commands) {
            if (Result.OK.equals(cmd.getResult())) {
                // add some logging for important ref changes
                switch (cmd.getType()) {
                case DELETE:
                    logger.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
                    LOGGER.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
                    break;
                case CREATE:
                    logger.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
                    LOGGER.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
                    break;
                case UPDATE:
                    logger.info(MessageFormat.format("{0} UPDATED {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
                    LOGGER.info(MessageFormat.format("{0} UPDATED {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
                    break;
                case UPDATE_NONFASTFORWARD:
                    logger.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
                    LOGGER.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
                    break;
                default:
                    break;
@@ -259,7 +281,7 @@
            PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);
            for (ReceiveCommand cmd : commands) {
                if (!cmd.getRefName().startsWith("refs/heads/")) {
                if (!cmd.getRefName().startsWith(Constants.R_HEADS)) {
                    // only tag branch ref changes
                    continue;
                }
@@ -267,7 +289,7 @@
                if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
                        && ReceiveCommand.Result.OK.equals(cmd.getResult())) {
                    String objectId = cmd.getNewId().getName();
                    String branch = cmd.getRefName().substring("refs/heads/".length());
                    String branch = cmd.getRefName().substring(Constants.R_HEADS.length());
                    // get translation based on the server's locale setting
                    String template = Translation.get("gb.incrementalPushTagMessage");
                    String msg = MessageFormat.format(template, branch);
@@ -286,36 +308,120 @@
                            "0",
                            msg);
                }
            }
            }
        }
        // update push log
        try {
            RefLogUtils.updateRefLog(user, rp.getRepository(), commands);
            logger.debug(MessageFormat.format("{0} push log updated", repository.name));
            LOGGER.debug(MessageFormat.format("{0} push log updated", repository.name));
        } catch (Exception e) {
            logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
            LOGGER.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
        }
        // run Groovy hook scripts
        // run Groovy hook scripts
        Set<String> scripts = new LinkedHashSet<String>();
        scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
        if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
            scripts.addAll(repository.postReceiveScripts);
        }
        runGroovy(repository, user, commands, rp, scripts);
        runGroovy(commands, scripts);
    }
    /** Execute commands to update references. */
    @Override
    protected void executeCommands() {
        List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
        if (toApply.isEmpty()) {
            return;
        }
        ProgressMonitor updating = NullProgressMonitor.INSTANCE;
        boolean sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
        if (sideBand) {
            SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
            pm.setDelayStart(250, TimeUnit.MILLISECONDS);
            updating = pm;
        }
        BatchRefUpdate batch = getRepository().getRefDatabase().newBatchUpdate();
        batch.setAllowNonFastForwards(isAllowNonFastForwards());
        batch.setRefLogIdent(getRefLogIdent());
        batch.setRefLogMessage("push", true);
        for (ReceiveCommand cmd : toApply) {
            if (Result.NOT_ATTEMPTED != cmd.getResult()) {
                // Already rejected by the core receive process.
                continue;
            }
            batch.addCommand(cmd);
        }
        if (!batch.getCommands().isEmpty()) {
            try {
                batch.execute(getRevWalk(), updating);
            } catch (IOException err) {
                for (ReceiveCommand cmd : toApply) {
                    if (cmd.getResult() == Result.NOT_ATTEMPTED) {
                        sendRejection(cmd, "lock error: {0}", err.getMessage());
                    }
                }
            }
        }
    }
    protected void setGitblitUrl(String url) {
        this.gitblitUrl = url;
    }
    protected void setRepositoryUrl(String url) {
        this.repositoryUrl = url;
    }
    protected void sendRejection(final ReceiveCommand cmd, final String why, Object... objects) {
        String text;
        if (ArrayUtils.isEmpty(objects)) {
            text = why;
        } else {
            text = MessageFormat.format(why, objects);
        }
        cmd.setResult(Result.REJECTED_OTHER_REASON, text);
        LOGGER.error(text + " (" + user.username + ")");
    }
    protected void sendMessage(String msg, Object... objects) {
        String text;
        if (ArrayUtils.isEmpty(objects)) {
            text = msg;
            super.sendMessage(msg);
        } else {
            text = MessageFormat.format(msg, objects);
            super.sendMessage(text);
        }
        LOGGER.info(text + " (" + user.username + ")");
    }
    protected void sendError(String msg, Object... objects) {
        String text;
        if (ArrayUtils.isEmpty(objects)) {
            text = msg;
            super.sendError(msg);
        } else {
            text = MessageFormat.format(msg, objects);
            super.sendError(text);
        }
        LOGGER.error(text + " (" + user.username + ")");
    }
    /**
     * Runs the specified Groovy hook scripts.
     *
     *
     * @param repository
     * @param user
     * @param commands
     * @param scripts
     */
    protected void runGroovy(RepositoryModel repository, UserModel user,
            Collection<ReceiveCommand> commands, ReceivePack rp, Set<String> scripts) {
    protected void runGroovy(Collection<ReceiveCommand> commands, Set<String> scripts) {
        if (scripts == null || scripts.size() == 0) {
            // no Groovy scripts to execute
            return;
@@ -324,12 +430,12 @@
        Binding binding = new Binding();
        binding.setVariable("gitblit", GitBlit.self());
        binding.setVariable("repository", repository);
        binding.setVariable("receivePack", rp);
        binding.setVariable("receivePack", this);
        binding.setVariable("user", user);
        binding.setVariable("commands", commands);
        binding.setVariable("url", gitblitUrl);
        binding.setVariable("logger", logger);
        binding.setVariable("clientLogger", new ClientLogger(rp));
        binding.setVariable("logger", LOGGER);
        binding.setVariable("clientLogger", new ClientLogger(this));
        for (String script : scripts) {
            if (StringUtils.isEmpty(script)) {
                continue;
@@ -347,15 +453,15 @@
                Object result = gse.run(script, binding);
                if (result instanceof Boolean) {
                    if (!((Boolean) result)) {
                        logger.error(MessageFormat.format(
                        LOGGER.error(MessageFormat.format(
                                "Groovy script {0} has failed!  Hook scripts aborted.", script));
                        break;
                    }
                }
            } catch (Exception e) {
                logger.error(
                LOGGER.error(
                        MessageFormat.format("Failed to execute Groovy script {0}", script), e);
            }
        }
    }
}
}
src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
@@ -30,11 +30,11 @@
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.StringUtils;
/**
 * The receive pack factory creates a receive pack which accepts pushes from
 * clients.
 *
 * The receive pack factory creates the receive pack which processes pushes.
 *
 * @author James Moger
 *
 * @param <X> the connection type
@@ -42,62 +42,59 @@
public class GitblitReceivePackFactory<X> implements ReceivePackFactory<X> {
    protected final Logger logger = LoggerFactory.getLogger(GitblitReceivePackFactory.class);
    @Override
    public ReceivePack create(X req, Repository db)
            throws ServiceNotEnabledException, ServiceNotAuthorizedException {
        final ReceivePack rp = new ReceivePack(db);
        UserModel user = UserModel.ANONYMOUS;
        String repositoryName = "";
        String origin = "";
        String gitblitUrl = "";
        String repositoryUrl = "";
        int timeout = 0;
        if (req instanceof HttpServletRequest) {
            // http/https request may or may not be authenticated
            // http/https request may or may not be authenticated
            HttpServletRequest request = (HttpServletRequest) req;
            repositoryName = request.getAttribute("gitblitRepositoryName").toString();
            origin = request.getRemoteHost();
            gitblitUrl = HttpUtils.getGitblitURL(request);
            repositoryUrl = request.getRequestURI();
            // determine pushing user
            String username = request.getRemoteUser();
            if (username != null && !"".equals(username)) {
                user = GitBlit.self().getUserModel(username);
                if (user == null) {
                    // anonymous push, create a temporary usermodel
                    user = new UserModel(username);
            if (!StringUtils.isEmpty(username)) {
                UserModel u = GitBlit.self().getUserModel(username);
                if (u != null) {
                    user = u;
                }
            }
        } else if (req instanceof GitDaemonClient) {
            // git daemon request is alway anonymous
            // git daemon request is always anonymous
            GitDaemonClient client = (GitDaemonClient) req;
            repositoryName = client.getRepositoryName();
            origin = client.getRemoteAddress().getHostAddress();
            // set timeout from Git daemon
            timeout = client.getDaemon().getTimeout();
        }
        // set pushing user identity for reflog
        // TODO make this a setting
        boolean allowAnonymousPushes = true;
        if (!allowAnonymousPushes && UserModel.ANONYMOUS.equals(user)) {
            // prohibit anonymous pushes
            throw new ServiceNotEnabledException();
        }
        final RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
        final GitblitReceivePack rp = new GitblitReceivePack(db, repository, user);
        rp.setGitblitUrl(gitblitUrl);
        rp.setRepositoryUrl(repositoryUrl);
        rp.setRefLogIdent(new PersonIdent(user.username, user.username + "@" + origin));
        rp.setTimeout(timeout);
        // set advanced ref permissions
        RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
        rp.setAllowCreates(user.canCreateRef(repository));
        rp.setAllowDeletes(user.canDeleteRef(repository));
        rp.setAllowNonFastForwards(user.canRewindRef(repository));
        // setup the receive hook
        ReceiveHook hook = new ReceiveHook();
        hook.user = user;
        hook.repository = repository;
        hook.gitblitUrl = gitblitUrl;
        rp.setPreReceiveHook(hook);
        rp.setPostReceiveHook(hook);
        return rp;
    }
}
}
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
@@ -16,7 +16,6 @@
package com.gitblit.git;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -30,6 +29,7 @@
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import com.gitblit.Constants;
import com.gitblit.GitBlit;
import com.gitblit.models.UserModel;
@@ -94,7 +94,7 @@
            // JGit's RefMap is custom and does not support iterator removal :(
            List<String> toRemove = new ArrayList<String>();
            for (String ref : refs.keySet()) {
                if (ref.startsWith("refs/gitblit/")) {
                if (ref.startsWith(Constants.R_GITBLIT)) {
                    toRemove.add(ref);
                }
            }
@@ -104,4 +104,4 @@
            return refs;
        }
    }
}
}
src/main/java/com/gitblit/git/SideBandProgressMonitor.java
New file
@@ -0,0 +1,127 @@
/*
 * Copyright (C) 2008-2010, 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 com.gitblit.git;
import java.io.IOException;
import java.io.OutputStream;
import org.eclipse.jgit.lib.BatchingProgressMonitor;
import org.eclipse.jgit.lib.Constants;
/** Write progress messages out to the sideband channel. */
class SideBandProgressMonitor extends BatchingProgressMonitor {
    private final OutputStream out;
    private boolean write;
    SideBandProgressMonitor(final OutputStream os) {
        out = os;
        write = true;
    }
    @Override
    protected void onUpdate(String taskName, int workCurr) {
        StringBuilder s = new StringBuilder();
        format(s, taskName, workCurr);
        s.append("   \r"); //$NON-NLS-1$
        send(s);
    }
    @Override
    protected void onEndTask(String taskName, int workCurr) {
        StringBuilder s = new StringBuilder();
        format(s, taskName, workCurr);
        s.append(", done\n"); //$NON-NLS-1$
        send(s);
    }
    private void format(StringBuilder s, String taskName, int workCurr) {
        s.append(taskName);
        s.append(": "); //$NON-NLS-1$
        s.append(workCurr);
    }
    @Override
    protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt) {
        StringBuilder s = new StringBuilder();
        format(s, taskName, cmp, totalWork, pcnt);
        s.append("   \r"); //$NON-NLS-1$
        send(s);
    }
    @Override
    protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt) {
        StringBuilder s = new StringBuilder();
        format(s, taskName, cmp, totalWork, pcnt);
        s.append("\n"); //$NON-NLS-1$
        send(s);
    }
    private void format(StringBuilder s, String taskName, int cmp,
            int totalWork, int pcnt) {
        s.append(taskName);
        s.append(": "); //$NON-NLS-1$
        if (pcnt < 100)
            s.append(' ');
        if (pcnt < 10)
            s.append(' ');
        s.append(pcnt);
        s.append("% ("); //$NON-NLS-1$
        s.append(cmp);
        s.append("/"); //$NON-NLS-1$
        s.append(totalWork);
        s.append(")"); //$NON-NLS-1$
    }
    private void send(StringBuilder s) {
        if (write) {
            try {
                out.write(Constants.encode(s.toString()));
                out.flush();
            } catch (IOException err) {
                write = false;
            }
        }
    }
}
src/test/config/test-gitblit.properties
@@ -1,7 +1,8 @@
#
# Gitblit Unit Testing properties
#
git.allowAnonymousPushes = true
git.defaultAccessRestriction = NONE
git.repositoriesFolder = ${baseFolder}/git
git.searchRepositoriesSubfolders = true
git.enableGitServlet = true