com.gitblit.git.GitblitReceivePack.java Source code

Java tutorial

Introduction

Here is the source code for com.gitblit.git.GitblitReceivePack.java

Source

/*
 * Copyright 2013 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.git;

import static org.eclipse.jgit.transport.BasePackPushConnection.CAPABILITY_SIDE_BAND_64K;
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.ReceivePack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.Constants;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.client.Translation;
import com.gitblit.extensions.ReceiveHook;
import com.gitblit.manager.IGitblit;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TicketModel;
import com.gitblit.models.UserModel;
import com.gitblit.models.TicketModel.Change;
import com.gitblit.models.TicketModel.Field;
import com.gitblit.models.TicketModel.Patchset;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.TicketModel.TicketAction;
import com.gitblit.models.TicketModel.TicketLink;
import com.gitblit.tickets.BranchTicketService;
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.TicketNotifier;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ClientLogger;
import com.gitblit.utils.CommitCache;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.RefLogUtils;
import com.gitblit.utils.StringUtils;
import com.google.common.collect.Lists;

/**
 * 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 GitblitReceivePack extends ReceivePack implements PreReceiveHook, PostReceiveHook {

    private static final Logger LOGGER = LoggerFactory.getLogger(GitblitReceivePack.class);

    protected final RepositoryModel repository;

    protected final UserModel user;

    protected final File groovyDir;

    protected String gitblitUrl;

    protected GroovyScriptEngine gse;

    protected final IStoredSettings settings;

    protected final IGitblit gitblit;

    protected final ITicketService ticketService;

    protected final TicketNotifier ticketNotifier;

    public GitblitReceivePack(IGitblit gitblit, Repository db, RepositoryModel repository, UserModel user) {

        super(db);
        this.settings = gitblit.getSettings();
        this.gitblit = gitblit;
        this.repository = repository;
        this.user = user;
        this.groovyDir = gitblit.getHooksFolder();
        try {
            // set Grape root
            File grapeRoot = gitblit.getGrapesFolder();
            grapeRoot.mkdirs();
            System.setProperty("grape.root", grapeRoot.getAbsolutePath());
            this.gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
        } catch (IOException e) {
        }

        if (gitblit.getTicketService().isAcceptingTicketUpdates(repository)) {
            this.ticketService = gitblit.getTicketService();
            this.ticketNotifier = this.ticketService.createNotifier();
        } else {
            this.ticketService = null;
            this.ticketNotifier = null;
        }

        // set advanced ref permissions
        setAllowCreates(user.canCreateRef(repository));
        setAllowDeletes(user.canDeleteRef(repository));
        setAllowNonFastForwards(user.canRewindRef(repository));

        int maxObjectSz = settings.getInteger(Keys.git.maxObjectSizeLimit, -1);
        if (maxObjectSz >= 0) {
            setMaxObjectSizeLimit(maxObjectSz);
        }
        int maxPackSz = settings.getInteger(Keys.git.maxPackSizeLimit, -1);
        if (maxPackSz >= 0) {
            setMaxPackSizeLimit(maxPackSz);
        }
        setCheckReceivedObjects(settings.getBoolean(Keys.git.checkReceivedObjects, true));
        setCheckReferencedObjectsAreReachable(
                settings.getBoolean(Keys.git.checkReferencedObjectsAreReachable, true));

        // setup pre and post receive hook
        setPreReceiveHook(this);
        setPostReceiveHook(this);
    }

    /**
     * Returns true if the user is permitted to apply the receive commands to
     * the repository.
     *
     * @param commands
     * @return true if the user may push these commands
     */
    protected boolean canPush(Collection<ReceiveCommand> commands) {
        // TODO Consider supporting branch permissions here (issue-36)
        // Not sure if that should be Gerrit-style, refs/meta/config, or
        // gitolite-style, permissions in users.conf
        //
        // How could commands be empty?
        //
        // Because a subclass, like PatchsetReceivePack, filters receive
        // commands before this method is called.  This makes it possible for
        // this method to test an empty list.  In this case, we assume that the
        // subclass receive pack properly enforces push restrictions. for the
        // ref.
        //
        // The empty test is not explicitly required, it's written here to
        // clarify special-case behavior.

        return commands.isEmpty() ? true : user.canPush(repository);
    }

    /**
     * Instrumentation point where the incoming push event has been parsed,
     * validated, objects created BUT refs have not been updated. You might
     * use this to enforce a branch-write permissions model.
     */
    @Override
    public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {

        if (commands.size() == 0) {
            // no receive commands to process
            // this can happen if receive pack subclasses intercept and filter
            // the commands
            LOGGER.debug("skipping pre-receive processing, no refs created, updated, or removed");
            return;
        }

        if (repository.isMirror) {
            // repository is a mirror
            for (ReceiveCommand cmd : commands) {
                sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it is a mirror!",
                        repository.name);
            }
            return;
        }

        if (repository.isFrozen) {
            // repository is frozen/readonly
            for (ReceiveCommand cmd : commands) {
                sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it is frozen!",
                        repository.name);
            }
            return;
        }

        if (!repository.isBare) {
            // repository has a working copy
            for (ReceiveCommand cmd : commands) {
                sendRejection(cmd, "Gitblit does not allow pushes to \"{0}\" because it has a working copy!",
                        repository.name);
            }
            return;
        }

        if (!canPush(commands)) {
            // user does not have push permissions
            for (ReceiveCommand cmd : commands) {
                sendRejection(cmd, "User \"{0}\" does not have push permissions for \"{1}\"!", user.username,
                        repository.name);
            }
            return;
        }

        if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
            // enforce committer verification
            if (StringUtils.isEmpty(user.emailAddress)) {
                // reject the push because the pushing account does not have an email address
                for (ReceiveCommand cmd : commands) {
                    sendRejection(cmd,
                            "Sorry, the account \"{0}\" does not have an email address set for committer verification!",
                            user.username);
                }
                return;
            }

            // 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 first parents has the commit
            // identity of the merging user.
            boolean allRejected = false;
            for (ReceiveCommand cmd : commands) {
                String firstParent = null;
                try {
                    List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(),
                            cmd.getNewId().name());
                    for (RevCommit commit : commits) {

                        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) {
                            firstParent = null;
                        } else {
                            firstParent = commit.getParents()[0].getId().getName();
                        }

                        PersonIdent committer = commit.getCommitterIdent();
                        if (!user.is(committer.getName(), committer.getEmailAddress())) {
                            // verification failed
                            String 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);
                            cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
                            allRejected &= true;
                            break;
                        } else {
                            allRejected = false;
                        }
                    }
                } catch (Exception e) {
                    LOGGER.error("Failed to verify commits were made by pushing user", e);
                }
            }

            if (allRejected) {
                // all ref updates rejected, abort
                return;
            }
        }

        for (ReceiveCommand cmd : commands) {
            String ref = cmd.getRefName();
            if (ref.startsWith(Constants.R_HEADS)) {
                switch (cmd.getType()) {
                case UPDATE_NONFASTFORWARD:
                case DELETE:
                    // reset branch commit cache on REWIND and DELETE
                    CommitCache.instance().clear(repository.name, ref);
                    break;
                default:
                    break;
                }
            } else if (ref.equals(BranchTicketService.BRANCH)) {
                // ensure pushing user is an administrator OR an owner
                // i.e. prevent ticket tampering
                boolean permitted = user.canAdmin() || repository.isOwner(user.username);
                if (!permitted) {
                    sendRejection(cmd, "{0} is not permitted to push to {1}", user.username, ref);
                }
            } else if (ref.startsWith(Constants.R_FOR)) {
                // prevent accidental push to refs/for
                sendRejection(cmd, "{0} is not configured to receive patchsets", repository.name);
            }
        }

        // call pre-receive plugins
        for (ReceiveHook hook : gitblit.getExtensions(ReceiveHook.class)) {
            try {
                hook.onPreReceive(this, commands);
            } catch (Exception e) {
                LOGGER.error("Failed to execute extension", e);
            }
        }

        Set<String> scripts = new LinkedHashSet<String>();
        scripts.addAll(gitblit.getPreReceiveScriptsInherited(repository));
        if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) {
            scripts.addAll(repository.preReceiveScripts);
        }
        runGroovy(commands, scripts);
        for (ReceiveCommand cmd : commands) {
            if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
                LOGGER.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId().getName(),
                        cmd.getResult(), cmd.getMessage()));
            }
        }
    }

    /**
     * Instrumentation point where the incoming push has been applied to the
     * repository. This is the point where we would trigger a Jenkins build
     * or send an email.
     */
    @Override
    public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
        if (commands.size() == 0) {
            LOGGER.debug("skipping post-receive processing, no refs created, updated, or removed");
            return;
        }

        logRefChange(commands);
        updateIncrementalPushTags(commands);
        updateGitblitRefLog(commands);

        // check for updates pushed to the BranchTicketService branch
        // if the BranchTicketService is active it will reindex, as appropriate
        for (ReceiveCommand cmd : commands) {
            if (Result.OK.equals(cmd.getResult()) && BranchTicketService.BRANCH.equals(cmd.getRefName())) {
                rp.getRepository().fireEvent(new ReceiveCommandEvent(repository, cmd));
            }
        }

        // call post-receive plugins
        for (ReceiveHook hook : gitblit.getExtensions(ReceiveHook.class)) {
            try {
                hook.onPostReceive(this, commands);
            } catch (Exception e) {
                LOGGER.error("Failed to execute extension", e);
            }
        }

        // run Groovy hook scripts
        Set<String> scripts = new LinkedHashSet<String>();
        scripts.addAll(gitblit.getPostReceiveScriptsInherited(repository));
        if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
            scripts.addAll(repository.postReceiveScripts);
        }
        runGroovy(commands, scripts);
    }

    /**
     * Log the ref changes in the container log.
     *
     * @param commands
     */
    protected void logRefChange(Collection<ReceiveCommand> commands) {
        boolean isRefCreationOrDeletion = false;

        // 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()));
                    isRefCreationOrDeletion = true;
                    break;
                case CREATE:
                    LOGGER.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(),
                            repository.name));
                    isRefCreationOrDeletion = true;
                    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()));
                    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()));
                    break;
                default:
                    break;
                }
            }
        }

        if (isRefCreationOrDeletion) {
            gitblit.resetRepositoryCache(repository.name);
        }
    }

    /**
     * Optionally update the incremental push tags.
     *
     * @param commands
     */
    protected void updateIncrementalPushTags(Collection<ReceiveCommand> commands) {
        if (!repository.useIncrementalPushTags) {
            return;
        }

        // tag each pushed branch tip
        String emailAddress = user.emailAddress == null ? getRefLogIdent().getEmailAddress() : user.emailAddress;
        PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);

        for (ReceiveCommand cmd : commands) {
            if (!cmd.getRefName().startsWith(Constants.R_HEADS)) {
                // only tag branch ref changes
                continue;
            }

            if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
                    && ReceiveCommand.Result.OK.equals(cmd.getResult())) {
                String objectId = cmd.getNewId().getName();
                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);
                String prefix;
                if (StringUtils.isEmpty(repository.incrementalPushTagPrefix)) {
                    prefix = settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r");
                } else {
                    prefix = repository.incrementalPushTagPrefix;
                }

                JGitUtils.createIncrementalRevisionTag(getRepository(), objectId, userIdent, prefix, "0", msg);
            }
        }
    }

    /**
     * Update Gitblit's internal reflog.
     *
     * @param commands
     */
    protected void updateGitblitRefLog(Collection<ReceiveCommand> commands) {
        try {
            RefLogUtils.updateRefLog(user, getRepository(), commands);
            LOGGER.debug(MessageFormat.format("{0} reflog updated", repository.name));
        } catch (Exception e) {
            LOGGER.error(MessageFormat.format("Failed to update {0} reflog", repository.name), e);
        }
    }

    /** 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());
                    }
                }
            }
        }

        //
        // if there are ref update receive commands that were
        // successfully processed and there is an active ticket service for the repository
        // then process any referenced tickets
        //
        if (ticketService != null) {
            List<ReceiveCommand> allUpdates = ReceiveCommand.filter(batch.getCommands(), Result.OK);
            if (!allUpdates.isEmpty()) {
                int ticketsProcessed = 0;
                for (ReceiveCommand cmd : allUpdates) {
                    switch (cmd.getType()) {
                    case CREATE:
                    case UPDATE:
                        if (cmd.getRefName().startsWith(Constants.R_HEADS)) {
                            Collection<TicketModel> tickets = processReferencedTickets(cmd);
                            ticketsProcessed += tickets.size();
                            for (TicketModel ticket : tickets) {
                                ticketNotifier.queueMailing(ticket);
                            }
                        }
                        break;

                    case UPDATE_NONFASTFORWARD:
                        if (cmd.getRefName().startsWith(Constants.R_HEADS)) {
                            String base = JGitUtils.getMergeBase(getRepository(), cmd.getOldId(), cmd.getNewId());
                            List<TicketLink> deletedRefs = JGitUtils.identifyTicketsBetweenCommits(getRepository(),
                                    settings, base, cmd.getOldId().name());
                            for (TicketLink link : deletedRefs) {
                                link.isDelete = true;
                            }
                            Change deletion = new Change(user.username);
                            deletion.pendingLinks = deletedRefs;
                            ticketService.updateTicket(repository, 0, deletion);

                            Collection<TicketModel> tickets = processReferencedTickets(cmd);
                            ticketsProcessed += tickets.size();
                            for (TicketModel ticket : tickets) {
                                ticketNotifier.queueMailing(ticket);
                            }
                        }
                        break;
                    case DELETE:
                        //Identify if the branch has been merged 
                        SortedMap<Integer, String> bases = new TreeMap<Integer, String>();
                        try {
                            ObjectId dObj = cmd.getOldId();
                            Collection<Ref> tips = getRepository().getRefDatabase().getRefs(Constants.R_HEADS)
                                    .values();
                            for (Ref ref : tips) {
                                ObjectId iObj = ref.getObjectId();
                                String mergeBase = JGitUtils.getMergeBase(getRepository(), dObj, iObj);
                                if (mergeBase != null) {
                                    int d = JGitUtils.countCommits(getRepository(), getRevWalk(), mergeBase,
                                            dObj.name());
                                    bases.put(d, mergeBase);
                                    //All commits have been merged into some other branch
                                    if (d == 0) {
                                        break;
                                    }
                                }
                            }

                            if (bases.isEmpty()) {
                                //TODO: Handle orphan branch case
                            } else {
                                if (bases.firstKey() > 0) {
                                    //Delete references from the remaining commits that haven't been merged
                                    String mergeBase = bases.get(bases.firstKey());
                                    List<TicketLink> deletedRefs = JGitUtils.identifyTicketsBetweenCommits(
                                            getRepository(), settings, mergeBase, dObj.name());

                                    for (TicketLink link : deletedRefs) {
                                        link.isDelete = true;
                                    }
                                    Change deletion = new Change(user.username);
                                    deletion.pendingLinks = deletedRefs;
                                    ticketService.updateTicket(repository, 0, deletion);
                                }
                            }

                        } catch (IOException e) {
                            LOGGER.error(null, e);
                        }
                        break;

                    default:
                        break;
                    }
                }

                if (ticketsProcessed == 1) {
                    sendInfo("1 ticket updated");
                } else if (ticketsProcessed > 1) {
                    sendInfo("{0} tickets updated", ticketsProcessed);
                }
            }

            // reset the ticket caches for the repository
            ticketService.resetCaches(repository);
        }
    }

    protected void setGitblitUrl(String url) {
        this.gitblitUrl = url;
    }

    public 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 + ")");
    }

    public void sendHeader(String msg, Object... objects) {
        sendInfo("--> ", msg, objects);
    }

    public void sendInfo(String msg, Object... objects) {
        sendInfo("    ", msg, objects);
    }

    private void sendInfo(String prefix, String msg, Object... objects) {
        String text;
        if (ArrayUtils.isEmpty(objects)) {
            text = msg;
            super.sendMessage(prefix + msg);
        } else {
            text = MessageFormat.format(msg, objects);
            super.sendMessage(prefix + text);
        }
        if (!StringUtils.isEmpty(msg)) {
            LOGGER.info(text + " (" + user.username + ")");
        }
    }

    public 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);
        }
        if (!StringUtils.isEmpty(msg)) {
            LOGGER.error(text + " (" + user.username + ")");
        }
    }

    /**
     * Runs the specified Groovy hook scripts.
     *
     * @param repository
     * @param user
     * @param commands
     * @param scripts
     */
    private void runGroovy(Collection<ReceiveCommand> commands, Set<String> scripts) {
        if (scripts == null || scripts.size() == 0) {
            // no Groovy scripts to execute
            return;
        }

        Binding binding = new Binding();
        binding.setVariable("gitblit", gitblit);
        binding.setVariable("repository", repository);
        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(this));
        for (String script : scripts) {
            if (StringUtils.isEmpty(script)) {
                continue;
            }
            // allow script to be specified without .groovy extension
            // this is easier to read in the settings
            File file = new File(groovyDir, script);
            if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {
                file = new File(groovyDir, script + ".groovy");
                if (file.exists()) {
                    script = file.getName();
                }
            }
            try {
                Object result = gse.run(script, binding);
                if (result instanceof Boolean) {
                    if (!((Boolean) result)) {
                        LOGGER.error(MessageFormat.format("Groovy script {0} has failed!  Hook scripts aborted.",
                                script));
                        break;
                    }
                }
            } catch (Exception e) {
                LOGGER.error(MessageFormat.format("Failed to execute Groovy script {0}", script), e);
            }
        }
    }

    public IGitblit getGitblit() {
        return gitblit;
    }

    public RepositoryModel getRepositoryModel() {
        return repository;
    }

    public UserModel getUserModel() {
        return user;
    }

    /**
     * Automatically closes open tickets and adds references to tickets if made in the commit message.
     *
     * @param cmd
     */
    private Collection<TicketModel> processReferencedTickets(ReceiveCommand cmd) {
        Map<Long, TicketModel> changedTickets = new LinkedHashMap<Long, TicketModel>();

        final RevWalk rw = getRevWalk();
        try {
            rw.reset();
            rw.markStart(rw.parseCommit(cmd.getNewId()));
            if (!ObjectId.zeroId().equals(cmd.getOldId())) {
                rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
            }

            RevCommit c;
            while ((c = rw.next()) != null) {
                rw.parseBody(c);
                List<TicketLink> ticketLinks = JGitUtils.identifyTicketsFromCommitMessage(getRepository(), settings,
                        c);
                if (ticketLinks == null) {
                    continue;
                }

                for (TicketLink link : ticketLinks) {

                    TicketModel ticket = ticketService.getTicket(repository, link.targetTicketId);
                    if (ticket == null) {
                        continue;
                    }

                    Change change = null;
                    String commitSha = c.getName();
                    String branchName = Repository.shortenRefName(cmd.getRefName());

                    switch (link.action) {
                    case Commit: {
                        //A commit can reference a ticket in any branch even if the ticket is closed.
                        //This allows developers to identify and communicate related issues
                        change = new Change(user.username);
                        change.referenceCommit(commitSha);
                    }
                        break;

                    case Close: {
                        // As this isn't a patchset theres no merging taking place when closing a ticket
                        if (ticket.isClosed()) {
                            continue;
                        }

                        change = new Change(user.username);
                        change.setField(Field.status, Status.Fixed);

                        if (StringUtils.isEmpty(ticket.responsible)) {
                            // unassigned tickets are assigned to the closer
                            change.setField(Field.responsible, user.username);
                        }
                    }

                    default: {
                        //No action
                    }
                        break;
                    }

                    if (change != null) {
                        ticket = ticketService.updateTicket(repository, ticket.number, change);
                    }

                    if (ticket != null) {
                        sendInfo("");
                        sendHeader("#{0,number,0}: {1}", ticket.number,
                                StringUtils.trimString(ticket.title, Constants.LEN_SHORTLOG));

                        switch (link.action) {
                        case Commit: {
                            sendInfo("referenced by push of {0} to {1}", commitSha, branchName);
                            changedTickets.put(ticket.number, ticket);
                        }
                            break;

                        case Close: {
                            sendInfo("closed by push of {0} to {1}", commitSha, branchName);
                            changedTickets.put(ticket.number, ticket);
                        }
                            break;

                        default: {
                        }
                        }

                        sendInfo(ticketService.getTicketUrl(ticket));
                        sendInfo("");
                    } else {
                        switch (link.action) {
                        case Commit: {
                            sendError("FAILED to reference ticket {0} by push of {1}", link.targetTicketId,
                                    commitSha);
                        }
                            break;

                        case Close: {
                            sendError("FAILED to close ticket {0} by push of {1}", link.targetTicketId, commitSha);
                        }
                            break;

                        default: {
                        }
                        }
                    }
                }
            }

        } catch (IOException e) {
            LOGGER.error("Can't scan for changes to reference or close", e);
        } finally {
            rw.reset();
        }

        return changedTickets.values();
    }
}