SVNKit Low Level API 使用(1)

编辑操作:提交到存储库

  Low Level API提供一个抽象的编辑器(editor),给开发人员以构建和改变存储库(repository)中树的层次的能力。有了这样的编辑器,你可以手动(即在你的代码中明确)编辑存储库:在这些节点下添加新节点(目录)和条目(文件),根据路径更改或删除仓库。接下来我们详细介绍这些操作。

  

 

使用ISVNEditor接口

  ISVNEditor是用于编辑操作的接口,尤其是提交更改到存储库。ISVNEditor也用来接收和应用存储库的更新,但这属于下一个例子的范畴。

试想一下,我们已经在我们的资料库中得到了以下的树结构:

 

 

我们要将它改变成以下这个结构:

 

 

换句话说,我们要:

  • 删除/nodeB/itemB1

  • 改变/nodeB/nodeC/itemC1 的内容

  • 附加一些版本属性到/nodeB/nodeC/itemC2

  • 添加目录/nodeB/nodeD

现在我们将讨论如何使用editor执行这些修改。我们假设我们正在使用本地存储库。首先,我们应该获得这样的editor来进行我们的计划。我们创建了一个绑定到节点/ nodeB上的SVNRepository驱动(因为我们所有的计划将执行该节点下)。

...
FSRepositoryFactory.setup();
String url = "file:///C:/path/to/repos/nodeB/";

SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded( url ));
...

 

在这里,我们不使用身份验证管理器,获得提交编辑器:

...
String logMessage = "log message";
ISVNEditor editor = repository.getCommitEditor( logMessage , null /*locks*/ , true /*keepLocks*/ , null /*mediator*/ );
...

 

现在,我们有了一个编辑器,在我们调用editor的closeEdit()方法之前,我们不能调用SVNRepository驱动的任何资源库访问方法。下面的代码片断演示了如何在一个单一的事务中准备我们的更改。

    ...
    //provide your local revision of nodeB
1   long r = ...;
    editor.openRoot( r );

    //provide your local revision of itemB1
2   r = ...;
    editor.deleteEntry( "itemB1" , r );

    //provide your local revision of nodeC
3   r = ...;
    editor.openDir( "nodeC" , r );

    //provide your local revision of itemC1
4   r = ...;
    editor.openFile( "nodeC/itemC1" , r );

 

应用文本增量 - 文本变化到 itemC1,baseChecksum用来确保客户端的条目和存储库中的的条目具有相同版本,防止内容冲突导致的数据不一致:

String baseChecksum = ...;
editor.applyTextDelta( "nodeC/itemC1" , baseChecksum );

 

 

使用增量生成器计算工作版本之间的数据差异。生成器会将不同之处或增量产出为固定大小的diff windows(差异窗口)序列,这意味着,样的窗口会给你不大于N个字节的输出文本。默认的窗口大小为100K字节。

InputStream baseData = ...;
InputStream workingData = ...;

//100Kb-window generator
SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator( );
String checksum = deltaGenerator.sendDelta( "nodeC/itemC1" , baseData , 0 , workingData , editor , true );

 

增量 生成器 通调用其textDeltaChunk()方法将 差异 窗口传递给编辑器 :
editor.textDeltaChunk( "nodeC/itemC1" , window );
 
当成器通完成后,它调用编辑器的textDeltaEnd()方法:
editor.textDeltaEnd( "nodeC/itemC1" );
 
sendDelta()方法返回工作版本条目的checksum。此checksum可用于验证更改被正确应用到了资源库侧。如果本地的工作条目和内资源库中的不匹配,checksum可能是不同的:
    editor.closeFile( "nodeC/itemC1" , checksum );
    
5   //the second and the third parameters are the path and revision respectively 
    //of the item's ancestor if the item is being added with history
    editor.addFile( "nodeC/itemC2" , null , -1 );

    baseChecksum = ...;
    editor.applyTextDelta( "nodeC/itemC2" , baseChecksum );

    baseData = ...;
    workingData = ...;
    checksum = deltaGenerator.sendDelta( "nodeC/itemC2" , baseData , 0 , workingData , editor , true );
    editor.closeFile( "nodeC/itemC2" , checksum );
    
6   editor.changeFileProperty( "nodeC/itemC2" , "propName1" , "propValue1" );
    editor.changeFileProperty( "nodeC/itemC2" , "propName2" , "propValue2" );
    ...
    
    //we are finished with changes under nodeC, so closing nodeC
    editor.closeDir( );

7   //now we are under nodeB again
    editor.addDir( "nodeD" , null , -1 );
    
    //close nodeD
    editor.closeDir( );

    //close root - nodeB
    editor.closeDir( );
 
在步骤5中,我们添加了一个新的条目,而不是不复制现有的条目。但是,如果我们需要做一个现有条目的副本,我们应该提供复制源的绝对路径和版本分别作为editor.addFile()方法的第二个和第三个参数。如果您只想复制(文件)不做更改,你不需要应用任何增量 - Subversion会在服务器端copy一个副本。
为了增加一个目录,ddFile()中其他的参数犹如以上所列,我们不是复制一个目录,只是增加一个没有历史的新目录。
正如你看到的每一个打开/添加目录(包括根),以及每一个打开/添加文件操作,都必须关闭编辑器。最后一点,关闭编辑器会引发资源库提交事务:
SVNCommitInfo info = editor.closeEdit();
 
如果事务成功,这个调用使得我们改变存储库中的内容。编辑器返回SVNCommitInfo对象,其中包含了新的版本信息。然而,该操作可能抛出一个异常,这意味着什么地方出错了。当我们捕获了这样一个异常,我们按照以下的方式来终止事务:
try {
    ...
    editor.addFile( "nodeC/itemC2" , null , -1 );
} catch( SVNException svne ) {
    editor.abortEdit( );
}
 
当我们关闭了编辑器后,我们可能会继续使用我们的SVNRepository驱动。下图说明了我们一步一步的改变,图中的数字对应了我们在代码中的步骤:
 
附注:
 • 通过ISVNEditor进行编辑时,可能会以分层的方式遍历一个必要的子树。如果你的驱动绑定到/a,而你要改变/a/b/c/d,你不能只是打开/d而没有依次往下打开/a/b/c,你要打开的根(/a),然后打开(/a/b),然后(/a/b/c),最后打开/a/b/c/d。
 • 所有目前在本地机器上的版本数据,你必须以某种方式保持当地的版本号。这样,当你试图改变一个条目的时候,才可以检查你的本地条目是不是已经过时。
 • 您不必在打开了一个文件时,立即应用文本增量。你可以先打开上面描述的所有必要的文件,然后以你打开时相反的次序依次关闭所打开的文件所在的目录,然后将增量应用到打开的文件,最后关闭文件。你不能改变你已经关闭的目录的属性。
 • 在操作存储库版本的提交编辑器时,你永远不需要调用以方法(他们与提交无关,但与更新有关):

使用ISVNEditor的提交操作的示例

在下面的例子中,我们将继续讨论使用ISVNEditor在低级别做提交操作。我们将按以下顺序执行一些简单的提交:

  • 添加一个包含文件的目录
  • 修改已经提交的文件
  • 在资源库内部拷贝整个目录
  • 同时删除源目录和拷贝的目录

对于这四个提交操作,我们将编写独立的方法:

1、增加:

    private static SVNCommitInfo addDir( ISVNEditor editor , String dirPath , String filePath , byte[] data ) throws SVNException {
        editor.openRoot( -1 );

        editor.addDir( dirPath , null , -1 );

        editor.addFile( filePath , null , -1 );

        editor.applyTextDelta( filePath , null );

        SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator( );
        String checksum = deltaGenerator.sendDelta( filePath , new ByteArrayInputStream( data ) , editor , true );

        editor.closeFile(filePath, checksum);

        //Closes dirPath.
        editor.closeDir();

        //Closes the root directory.
        editor.closeDir();

        return editor.closeEdit();
    }

 

 

2、文件修改:

    private static SVNCommitInfo modifyFile( ISVNEditor editor , String dirPath , String filePath , byte[] oldData , byte[] newData ) throws SVNException {
        editor.openRoot( -1 );

        editor.openDir( dirPath , -1 );

        editor.openFile( filePath , -1 );
        
        editor.applyTextDelta( filePath , null );
        
        SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator( );
        String checksum = deltaGenerator.sendDelta( filePath , new ByteArrayInputStream( oldData ) , 0 , new ByteArrayInputStream( newData ) , editor , true );

        //Closes filePath.
        editor.closeFile( filePath , checksum );

        // Closes dirPath.
        editor.closeDir( );

        //Closes the root directory.
        editor.closeDir( );

        return editor.closeEdit( );
    }

 

 

在这里,我们使用无效的版本号(-1),以简化的例子,这样也是可行的。而这也是我们现在需要的。当然,在实际系统中,他们最好是真实的(有效)版本号。对于checksum也是如此。

 

3、目录复制:

 

    private static SVNCommitInfo copyDir( ISVNEditor editor , String srcDirPath , String dstDirPath , long revision ) throws SVNException {
        editor.openRoot( -1 );
        
        editor.addDir( dstDirPath , srcDirPath , revision );

        //Closes dstDirPath.
        editor.closeDir( );

        //Closes the root directory.
        editor.closeDir( );

        return editor.closeEdit( );
    }

 

4目录删除

    private static SVNCommitInfo deleteDir( ISVNEditor editor , String dirPath ) throws SVNException {
        editor.openRoot( -1 );

        editor.deleteEntry( dirPath , -1 );

        //Closes the root directory.
        editor.closeDir( );

        return editor.closeEdit( );
    }

 

 

现在,我们已经有了这些功能,我们开始吧:

public class Commit {

    public static void main( String[] args ) {

        FSRepositoryFactory.setup( );
        SVNURL url = SVNURL.parseURIDecoded( "file:///localhost/testRepos" );
        String userName = "foo";
        String userPassword = "bar";
        
        byte[] contents = "This is a new file".getBytes( );
        byte[] modifiedContents = "This is the same file but modified a little.".getBytes( );

        SVNRepository repository = SVNRepositoryFactory.create( url );

        ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( userName, userPassword );
        repository.setAuthenticationManager( authManager );

        SVNNodeKind nodeKind = repository.checkPath( "" , -1 );

        if ( nodeKind == SVNNodeKind.NONE ) {
            System.out.println( "No entry at URL " + url );
            System.exit( 1 );
        } else if ( nodeKind == SVNNodeKind.FILE ) {
            System.out.println( "Entry at URL " + url + " is a file while directory was expected" );
            System.exit( 1 );
        }
        
        //Get exact value of the latest (HEAD) revision.
        long latestRevision = repository.getLatestRevision( );
        System.out.println( "Repository latest revision (before committing): " + latestRevision );
        
        ISVNEditor editor = repository.getCommitEditor( "directory and file added" , null );
        
        try {
            SVNCommitInfo commitInfo = addDir( editor , "test" , "test/file.txt" , contents );
            System.out.println( "The directory was added: " + commitInfo );
        } catch ( SVNException svne ) {
            editor.abortEdit( );
            throw svne;
        }
        
        editor = repository.getCommitEditor( "file contents changed" , null );
        try {
            commitInfo = modifyFile( editor , "test" , "test/file.txt" , contents , modifiedContents );
            System.out.println( "The file was changed: " + commitInfo );
        } catch ( SVNException svne ) {
            editor.abortEdit( );
            throw svne;
        }

        //converts a relative path to an absolute one
        String absoluteSrcPath = repository.getRepositoryPath( "test" );
        long srcRevision = repository.getLatestRevision( );

        editor = repository.getCommitEditor( "directory copied" , null );        
        try {
            commitInfo = copyDir( editor , absoluteSrcPath , "test2" , srcRevision );
            System.out.println( "The directory was copied: " + commitInfo );
        } catch ( SVNException svne ) {
            editor.abortEdit( );
            throw svne;
        }

        
        //Delete directory "test".
        editor = repository.getCommitEditor( "directory deleted" , null );
        try {
            commitInfo = deleteDir( editor , "test" );
            System.out.println( "The directory was deleted: " + commitInfo );
        } catch ( SVNException svne ) {
            editor.abortEdit( );
            throw svne;
        }

        //Delete directory "test2".
        editor = repository.getCommitEditor( "copied directory deleted" , null );
        try {
            commitInfo = deleteDir( editor , "test2" );
            System.out.println( "The copied directory was deleted: " + commitInfo );
        } catch ( SVNException svne ) {
            editor.abortEdit( );
            throw svne;
        }
        
        latestRevision = repository.getLatestRevision( );
        System.out.println( "Repository latest revision (after committing): " + latestRevision );
        ...
    }
    ...
}

 

如果你运行程序,将会看到以下结果:

Repository latest revision (before committing): 0
The directory was added: r1 by 'foo' at Tue Jun 27 15:46:59 NOVST 2006
The file was changed: r2 by 'foo' at Tue Jun 27 15:46:59 NOVST 2006
The directory was copied: r3 by 'foo' at Tue Jun 27 15:46:59 NOVST 2006
The directory was deleted: r4 by 'foo' at Tue Jun 27 15:46:59 NOVST 2006
The copied directory was deleted: r5 by 'foo' at Tue Jun 27 15:47:00 NOVST 2006
Repository latest revision (after committing): 5

 

下载示例源码: example program source code

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值