[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

subversion and scarab integration

From: Rob Clark <robdclark_at_mac.com>
Date: 2002-12-30 07:18:10 CET

All,

ScmHandler.path:
    A patch to scarab to add some methods which can be called (via
xml-rpc) by a svn pre-commit hook. These shouldn't be svn specific, so
this could probably work with cvs or other version control system.
This is based on some stuff I found in the mailing list archive from
Stephen Haberman. There are some still some things I'm not grok'ing
about scarab's caching, so this will only work with
scarab.torque.manager.cache=false (in build/default.properties).
Anyone care to give me a clue about how to not get cached comment
values after addComment()?

ChangePassword.patch:
    A patch to scarab to use htpasswd to update the password file used
by apache to control access to the repository. So user's login and
password are the same for both scarab and subversion. This patch is
pretty site specific (hard coded paths)

PreCommitHook.java & pre-commit:
    the pre-commit hook for subversion... only permits checkins on
branches (ie /branches/<ISSUE-ID>) for issues assigned to that user
which have Status=Assigned. For commits which are permitted, a comment
is added to the issue which lists the author, files changed, new
repository version, and commit log message. The pre-commit script
bypasses the check for the user "svnadmin"... change this to whatever
it needs to be so someone can merge back to the trunk. You will need
xmlrpc-1.1.jar which you can get from
http://xml.apache.org/dist/xmlrpc/release/v1.1/

import java.util.*;
import org.apache.xmlrpc.XmlRpcClient;

/**
 * This main class implements the pre-commit hook for svn. It communicates to
 * the scarab issue tracking system via xml-rpc in order to determine if the
 * user has permission to checkin. The checkin is permitted if:
 * <ul>
 * <li> it is on a branch nameed after an issue in the "Assigned" state
 * <li> the user trying to perform the checkin is assigned to that issue
 * </ul>
 * If the user has permission to make the checkin, the specified comment
 * (generated by the <i>pre-commit</i> script which invokes this program)
 * is appended to the issue, and the program returns <code>0</code>. If the
 * user does not have permission, or some other error occurs, an msg will be
 * written to stderr, and the exit code is <code>-1</code>.
 * <p>
 * NOTE: this program assumes that the repository is layed out with the
 * <i>branches directory at the toplevel, ie:
 * <pre>
 * /trunk/<PROJECT-NAME>
 * /branches/<ISSUE-ID>/<PROJECT-NAME>
 * /tags/<TAG-NAME>/<PROJECT-NAME>
 * </pre>
 * Some of the logic may need to change slightly if you use a different
 * layout.
 * <p>
 * Issue must have attribute <i>Status</i> with value <i>Assigned</i>
 * <p>
 * The division of labor between the <i>pre-commit</i> script and this class
 * is somewhat ackward... if the java bindings ever work (or if I wanted to
 * deal with Runtime#exec) I could do it all from here... or maybe I should
 * just learn enough perl to do this all in perl...
 */
public class PreCommitHook
{
    /**
     * The main program
     */
    public static void main( String[] args )
    {
        if( args.length < 3 )
            error("usage: PreCommitHook <USER> <COMMENT> [<CHANGED_DIR>]+");
        
        String user = args[0];
        String comment = fix_eols(args[1]);
        String[] dirs = new String[ args.length - 2 ];
        System.arraycopy( args, 2, dirs, 0, dirs.length );

        // generate the set of unique issue-ids
        Set issueSet = new HashSet();
        for( int i=0; i<dirs.length; i++ )
        {
            int idx;
            
            // ignore commit which create branch directory under branches/
            // (note: it would better to check that it is a legit branch directory!)
            if( dirs[i].equals("branches/") )
            {
                if( dirs.length == 1 )
                    System.exit(0); // if this is only directory, let commit procede,
                else
                    continue; // else ignore this dir, and process rest as normal
            }
            
            if( ! dirs[i].startsWith("branches/") )
                error("no permissions to checkin to: " + dirs[i]);
            
            String issueId = dirs[i].substring( "branches/".length() );
            if( (idx=issueId.indexOf('/')) != -1 )
                issueId = issueId.substring( 0, idx );
            issueSet.add(issueId);
        }

        // for each unique issue, we must verify
        for( Iterator itr=issueSet.iterator(); itr.hasNext(); )
        {
            Vector v;
            String issueId = (String)(itr.next());

            // Step 1: verify issue exists, and it's "Status" attribute is set to "Assigned"
            v = (Vector)(rpc( "scm.getAttributeValues", new Object[] {
                issueId,
                "Status"
            } ));

            if( v.size() != 1 )
                error(issueId + ": bad response to scm.getAttributeValues: " + v + " (perhaps \"" + issueId + "\" is not a valid issue-id?)");
            
            if( ! v.elementAt(0).equals("Assigned") )
                error(issueId + ": not in \"Assigned\" state");

            // Step 2: verify user is assigned to issue:
            v = (Vector)(rpc( "scm.getAssignedUsers", new Object[] {
                issueId
            } ));

            boolean userFound = false;
            for( int i=0; i<v.size() && !userFound; i++ )
                if( v.elementAt(i).equals(user) )
                    userFound = true;
            if( !userFound )
                error(issueId + ": user \"" + user + "\" not assigned to issue");

            // Step 3: if we get here, so far so good... append comment to issue
            Boolean result = (Boolean)(rpc( "scm.addComment", new Object[] {
                issueId,
                user,
                comment
            } ));
            if( ! result.booleanValue() )
                error(issueId + ": unable to add commit comment");
        }

        System.exit(0); // checkin is permitted
    }

    /**
     * exit with the specified error message.
     */
    private static void error( String msg )
    {
        System.err.println(msg);
        System.exit(-1);
    }
    
    /**
     * Hack to replace the string "\n" with EOL characters...
     */
    private static String fix_eols( String str )
    {
        int idx = str.indexOf("\\n");
        if( idx != -1 )
            return str.substring(0,idx) + "\n" + fix_eols( str.substring(idx+"\\n".length()) );
        else
            return str;
    }

    /**
     * A utility function to make a xml-rpc call, and return the result.
     */
    private static Object rpc( String methodname, Object[] args )
    {
        Vector v = new Vector();
        for( int i=0; i<args.length; i++ )
            v.add(args[i]);

        try
        {
            return (new XmlRpcClient("http://localhost:12345")).execute( methodname, v );
        }
        catch(Throwable t)
        {
            error( t.getMessage() );
            return null; // XXX never get here
        }
    }
}

-- Rob

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Received on Mon Dec 30 07:19:09 2002

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.