编辑操作:提交到存储库
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 );
editor.textDeltaChunk( "nodeC/itemC1" , window );
editor.textDeltaEnd( "nodeC/itemC1" );
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( );
SVNCommitInfo info = editor.closeEdit();
try {
...
editor.addFile( "nodeC/itemC2" , null , -1 );
} catch( SVNException svne ) {
editor.abortEdit( );
}

使用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