Package org.tmatesoft.svn.core.internal.io.fs

Source Code of org.tmatesoft.svn.core.internal.io.fs.FSHooks

/*
* ====================================================================
* Copyright (c) 2004-2009 TMate Software Ltd.  All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution.  The terms
* are also available at http://svnkit.com/license.html
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.io.fs;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.util.SVNStreamGobbler;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileType;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.util.SVNLogType;

/**
* @version 1.3
* @author  TMate Software Ltd.
*/
public class FSHooks {

    public static final String SVN_REPOS_HOOK_START_COMMIT = "start-commit";
    public static final String SVN_REPOS_HOOK_PRE_COMMIT = "pre-commit";
    public static final String SVN_REPOS_HOOK_POST_COMMIT = "post-commit";
    public static final String SVN_REPOS_HOOK_PRE_REVPROP_CHANGE = "pre-revprop-change";
    public static final String SVN_REPOS_HOOK_POST_REVPROP_CHANGE = "post-revprop-change";
    public static final String SVN_REPOS_HOOK_PRE_LOCK = "pre-lock";
    public static final String SVN_REPOS_HOOK_POST_LOCK = "post-lock";
    public static final String SVN_REPOS_HOOK_PRE_UNLOCK = "pre-unlock";
    public static final String SVN_REPOS_HOOK_POST_UNLOCK = "post-unlock";
    public static final String SVN_REPOS_HOOK_READ_SENTINEL = "read-sentinels";
    public static final String SVN_REPOS_HOOK_WRITE_SENTINEL = "write-sentinels";
    public static final String SVN_REPOS_HOOK_DESC_EXT = ".tmpl";
    public static final String SVN_REPOS_HOOKS_DIR = "hooks";
    public static final String REVPROP_DELETE = "D";
    public static final String REVPROP_ADD = "A";
    public static final String REVPROP_MODIFY = "M";
    private static final String[] winExtensions = {
            ".exe", ".bat", ".cmd"
    };
   
    private static Boolean ourIsHooksEnabled;
   
    public static void setHooksEnabled(boolean enabled) {
        ourIsHooksEnabled = enabled ? Boolean.TRUE : Boolean.FALSE;
    }
   
    public static boolean isHooksEnabled() {
        if (ourIsHooksEnabled == null) {
            ourIsHooksEnabled = Boolean.valueOf(System.getProperty("svnkit.hooksEnabled", System.getProperty("javasvn.hooksEnabled", "true")));
        }
        return ourIsHooksEnabled.booleanValue();
    }

    public static void runPreLockHook(File reposRootDir, String path, String username) throws SVNException {
        runLockHook(reposRootDir, SVN_REPOS_HOOK_PRE_LOCK, path, username, null);
    }

    public static void runPostLockHook(File reposRootDir, String[] paths, String username) throws SVNException {
        StringBuffer pathsStr = new StringBuffer();
        for (int i = 0; i < paths.length; i++) {
            pathsStr.append(paths[i]);
            pathsStr.append("\n");
        }
        runLockHook(reposRootDir, SVN_REPOS_HOOK_POST_LOCK, null, username, pathsStr.toString());
    }

    public static void runPreUnlockHook(File reposRootDir, String path, String username) throws SVNException {
        runLockHook(reposRootDir, SVN_REPOS_HOOK_PRE_UNLOCK, path, username, null);
    }

    public static void runPostUnlockHook(File reposRootDir, String[] paths, String username) throws SVNException {
        StringBuffer pathsStr = new StringBuffer();
        for (int i = 0; i < paths.length; i++) {
            pathsStr.append(paths[i]);
            pathsStr.append("\n");
        }
        runLockHook(reposRootDir, SVN_REPOS_HOOK_POST_UNLOCK, null, username, pathsStr.toString());
    }

    private static void runLockHook(File reposRootDir, String hookName, String path, String username, String paths) throws SVNException {
        username = username == null ? "" : username;
        path = path == null ? "" : path;
        byte[] bytes = null;
        try {
            bytes = paths == null ? null : paths.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            bytes = paths.getBytes();
        }
        runHook(reposRootDir, hookName, new String[] {path, username}, bytes);
    }

    public static void runPreRevPropChangeHook(File reposRootDir, String propName, byte[] propNewValue, String author, long revision, String action) throws SVNException {
        runChangeRevPropHook(reposRootDir, SVN_REPOS_HOOK_PRE_REVPROP_CHANGE, propName, propNewValue, author, revision, action, true);
    }

    public static void runPostRevPropChangeHook(File reposRootDir, String propName, byte[] propOldValue, String author, long revision, String action) throws SVNException {
        runChangeRevPropHook(reposRootDir, SVN_REPOS_HOOK_POST_REVPROP_CHANGE, propName, propOldValue, author, revision, action, false);
    }
   
    private static void runChangeRevPropHook(File reposRootDir, String hookName, String propName, byte[] propValue, String author, long revision, String action, boolean isPre) throws SVNException {
        File hookFile = getHookFile(reposRootDir, hookName);
        if (hookFile == null) {
            if (isPre) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_DISABLED_FEATURE,
                        "Repository has not been enabled to accept revision propchanges;\nask the administrator to create a pre-revprop-change hook");
                SVNErrorManager.error(err, SVNLogType.FSFS);
            }
            return;
        }
        author = author == null ? "" : author;
        runHook(reposRootDir, hookName, new String[] {String.valueOf(revision), author, propName, action}, propValue);
    }

    public static void runStartCommitHook(File reposRootDir, String author, List capabilities) throws SVNException {
        author = author == null ? "" : author;
        String capsString = getCapabilitiesAsString(capabilities);
        String[] args = capsString == null ? new String[] { author } : new String[] { author, capsString };
        runHook(reposRootDir, SVN_REPOS_HOOK_START_COMMIT, args, null);
    }

    public static void runPreCommitHook(File reposRootDir, String txnName) throws SVNException {
        runHook(reposRootDir, SVN_REPOS_HOOK_PRE_COMMIT, new String[] {txnName}, null);
    }

    public static void runPostCommitHook(File reposRootDir, long committedRevision) throws SVNException {
        runHook(reposRootDir, SVN_REPOS_HOOK_POST_COMMIT, new String[] {String.valueOf(committedRevision)}, null);
    }

    private static void runHook(File reposRootDir, String hookName, String[] args, byte[] input) throws SVNException {
        File hookFile = getHookFile(reposRootDir, hookName);
        if (hookFile == null) {
            return;
        }
        if (args == null) {
            args = new String[0];
        }
        Process hookProc = null;
        String reposPath = reposRootDir.getAbsolutePath().replace(File.separatorChar, '/');
        String executableName = hookFile.getName().toLowerCase();
        boolean useCmd = (executableName.endsWith(".bat") || executableName.endsWith(".cmd")) && SVNFileUtil.isWindows;
        String[] cmd = useCmd ? new String[4 + args.length] : new String[2 + args.length];
        int i = 0;
        if (useCmd) {
            cmd[0] = "cmd";
            cmd[1] = "/C";
            i = 2;
        }
        cmd[i] = hookFile.getAbsolutePath();
        i++;
        cmd[i] = reposPath;
        i++;
        for(int j = 0; j < args.length; j++) {
            cmd[i + j] = args[j];
        }
        try {
            hookProc = Runtime.getRuntime().exec(cmd);
        } catch (IOException ioe) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "Failed to start ''{0}'' hook: {1}", new Object[] {
                    hookFile, ioe.getLocalizedMessage()
            });
            SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
        }
        feedHook(hookFile, hookName, hookProc, input);
    }


    private static void feedHook(File hook, String hookName, Process hookProcess, byte[] stdInValue) throws SVNException {
        if (hookProcess == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "Failed to start ''{0}'' hook", hook);
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }

        SVNStreamGobbler inputGobbler = new SVNStreamGobbler(hookProcess.getInputStream());
        SVNStreamGobbler errorGobbler = new SVNStreamGobbler(hookProcess.getErrorStream());
        inputGobbler.start();
        errorGobbler.start();

        if (stdInValue != null) {
            OutputStream osToStdIn = hookProcess.getOutputStream();
            try {
                for (int i = 0; i < stdInValue.length; i += 1024) {
                    osToStdIn.write(stdInValue, i, Math.min(1024, stdInValue.length - i));
                    osToStdIn.flush();
                }
            } catch (IOException ioe) {
                //
            } finally {
                SVNFileUtil.closeFile(osToStdIn);
            }
        }

        int rc = -1;
        try {
            inputGobbler.waitFor();
            errorGobbler.waitFor();
            rc = hookProcess.waitFor();
        } catch (InterruptedException ie) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "Failed to start ''{0}'' hook: {1}", new Object[] {
                    hook, ie.getLocalizedMessage()
            });
            SVNErrorManager.error(err, ie, SVNLogType.FSFS);
        } finally {
            errorGobbler.close();
            inputGobbler.close();
            hookProcess.destroy();
        }

        if (rc == 0 ) {
            if (errorGobbler.getError() != null) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "''{0}'' hook succeeded, but error output could not be read", hookName);
                SVNErrorManager.error(err, errorGobbler.getError(), SVNLogType.FSFS);
            }
        } else {
            String actionName = null;
            if (SVN_REPOS_HOOK_START_COMMIT.equals(hookName) || SVN_REPOS_HOOK_PRE_COMMIT.equals(hookName)) {
                actionName = "Commit";
            } else if (SVN_REPOS_HOOK_PRE_REVPROP_CHANGE.equals(hookName)) {
                actionName = "Revprop change";
            } else if (SVN_REPOS_HOOK_PRE_LOCK.equals(hookName)) {
                actionName = "Lock";
            } else if (SVN_REPOS_HOOK_PRE_UNLOCK.equals(hookName)) {
                actionName = "Unlock";
            }
            String stdErrMessage = errorGobbler.getError() != null ? "[Error output could not be read.]" : errorGobbler.getResult();
            String errorMessage = actionName != null ?
                    actionName + " blocked by {0} hook (exit code {1})" : "{0} hook failed (exit code {1})";
            if (stdErrMessage != null && stdErrMessage.length() > 0) {
                errorMessage += " with output:\n{2}";
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, errorMessage, new Object[] {hookName, new Integer(rc), stdErrMessage});
                SVNErrorManager.error(err, SVNLogType.FSFS);
            } else {
                errorMessage += " with no output.";
            }
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, errorMessage, new Object[] {hookName, new Integer(rc)});
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
    }

    private static File getHookFile(File reposRootDir, String hookName) throws SVNException {
        if (!isHooksEnabled()) {
            return null;
        }
        File hookFile = null;
        if (SVNFileUtil.isWindows) {
            for (int i = 0; i < winExtensions.length; i++) {
                hookFile = new File(getHooksDir(reposRootDir), hookName + winExtensions[i]);
                SVNFileType type = SVNFileType.getType(hookFile);
                if (type == SVNFileType.FILE) {
                    return hookFile;
                }
            }
        } else {
            hookFile = new File(getHooksDir(reposRootDir), hookName);
            SVNFileType type = SVNFileType.getType(hookFile);
            if (type == SVNFileType.FILE) {
                return hookFile;
            } else if (type == SVNFileType.SYMLINK) {
                // should first resolve the symlink and then decide if it's
                // broken and
                // throw an exception
                File realFile = SVNFileUtil.resolveSymlinkToFile(hookFile);
                if (realFile == null) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_HOOK_FAILURE, "Failed to run ''{0}'' hook; broken symlink", hookFile);
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                }
                return hookFile;
            }
        }
        return null;
    }

    private static File getHooksDir(File reposRootDir) {
        return new File(reposRootDir, SVN_REPOS_HOOKS_DIR);
    }
   
    private static String getCapabilitiesAsString(List capabilities) {
        if (capabilities == null || capabilities.isEmpty()) {
            return "";
        }
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < capabilities.size(); i++) {
            Object cap = capabilities.get(i);
            buffer.append(cap);
            if (i < capabilities.size() - 1) {
                buffer.append(":");
            }
        }
        return buffer.toString();
    }
}
TOP

Related Classes of org.tmatesoft.svn.core.internal.io.fs.FSHooks

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.