/*
* ====================================================================
* Copyright (c) 2004-2012 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.wc.admin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner;
import org.tmatesoft.svn.core.internal.io.fs.FSEntry;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSID;
import org.tmatesoft.svn.core.internal.io.fs.FSNodeHistory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot;
import org.tmatesoft.svn.core.internal.io.fs.FSRoot;
import org.tmatesoft.svn.core.internal.io.fs.FSTransactionInfo;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNGNUDiffGenerator;
import org.tmatesoft.svn.core.internal.wc.SVNAdminHelper;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNNodeEditor;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNRepositoryPool;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.util.SVNLogType;
/**
* The <b>SVNLookClient</b> class provides API for examining
* different aspects of a Subversion repository. Its functionality
* is similar to the one of the Subversion command-line utility
* called <i>svnlook</i>. The following table matches methods of
* <b>SVNLookClient</b> to the corresponding commands of the
* <i>svnlook</i> utility (to make sense what its different methods
* are for):
*
* <table cellpadding="3" cellspacing="1" border="0" width="50%" bgcolor="#999933">
* <tr bgcolor="#ADB8D9" align="left">
* <td><b>SVNLookClient</b></td>
* <td><b>Subversion</b></td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doCat()</td><td>'svnlook cat'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetAuthor()</td><td>'svnlook author'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetChanged()</td><td>'svnlook changed'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetChangedDirectories()</td><td>'svnlook dirs-changed'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetDate()</td><td>'svnlook date'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetDiff()</td><td>'svnlook diff'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetHistory()</td><td>'svnlook history'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetInfo()</td><td>'svnlook info'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetLock()</td><td>'svnlook lock'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetLog()</td><td>'svnlook log'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetProperties()</td><td>'svnlook proplist'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetProperty()</td><td>'svnlook propget'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetRevisionProperties()</td><td>'svnlook proplist --revprop'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetRevisionProperty()</td><td>'svnlook propget --revprop'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetTree()</td><td>'svnlook tree'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetUUID()</td><td>'svnlook uuid'</td>
* </tr>
* <tr bgcolor="#EAEAEA" align="left">
* <td>doGetYoungestRevision()</td><td>'svnlook youngest'</td>
* </tr>
* </table>
*
* @version 1.3
* @author TMate Software Ltd.
* @since 1.2
*/
public class SVNLookClient extends SVNAdminBasicClient {
private ISVNGNUDiffGenerator myDiffGenerator;
/**
* Creates a new instance of <b>SVNLookClient</b>
* given an authentication manager and global
* options keeper.
*
* @param authManager a manager which provides authentication
* credentials
* @param options a global config options provider
*/
public SVNLookClient(ISVNAuthenticationManager authManager, ISVNOptions options) {
super(authManager, options);
}
/**
* Creates a new instance of <b>SVNLookClient</b>
* given an {@link org.tmatesoft.svn.core.io.SVNRepository}}
* drivers provider and global options keeper.
*
* @param repositoryPool a repository connectors keeper
* @param options a global config options provider
*/
public SVNLookClient(ISVNRepositoryPool repositoryPool, ISVNOptions options) {
super(repositoryPool, options);
}
public SVNLookClient(SvnOperationFactory of) {
super(of);
}
/**
* Retrieves author, timestamp and log message information from
* the repository for the given revision. This information is
* provided in a single {@link org.tmatesoft.svn.core.SVNLogEntry}
* object, that is only the following methods of <b>SVNLogEntry</b>
* return valid information:
*
* <ul>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getAuthor() getAuthor()}
* </li>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getDate() getDate()}
* </li>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getMessage() getMessage()}
* </li>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getRevision() getRevision()}
* </li>
* </ul>
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @return revision info
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public SVNLogEntry doGetInfo(File repositoryRoot, SVNRevision revision) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
SVNProperties revProps = fsfs.getRevisionProperties(revNum);
String date = revProps.getStringValue(SVNRevisionProperty.DATE);
String author = revProps.getStringValue(SVNRevisionProperty.AUTHOR);
String logMessage = revProps.getStringValue(SVNRevisionProperty.LOG);
return new SVNLogEntry(null, revNum, author, SVNDate.parseDateString(date), logMessage);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Retrieves author, timestamp and log message information from
* the repository for the given transaction name. This information is
* provided in a single {@link org.tmatesoft.svn.core.SVNLogEntry}
* object, that is only the following methods of <b>SVNLogEntry</b>
* return valid information:
* <ul>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getAuthor() getAuthor()}
* </li>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getDate() getDate()}
* </li>
* <li>
* {@link org.tmatesoft.svn.core.SVNLogEntry#getMessage() getMessage()}
* </li>
* </ul>
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @return transaction info
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code></li>
* <li>if the specified transaction is not found</li>
* </ul>
*/
public SVNLogEntry doGetInfo(File repositoryRoot, String transactionName) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
SVNProperties txnProps = fsfs.getTransactionProperties(txn.getTxnId());
String date = txnProps.getStringValue(SVNRevisionProperty.DATE);
String author = txnProps.getStringValue(SVNRevisionProperty.AUTHOR);
String logMessage = txnProps.getStringValue(SVNRevisionProperty.LOG);
return new SVNLogEntry(null, -1, author, SVNDate.parseDateString(date), logMessage);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns the latest revision of the repository.
*
* @param repositoryRoot a repository root directory path
* @return a revision number
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public long doGetYoungestRevision(File repositoryRoot) throws SVNException {
FSFS fsfs = null;
try {
fsfs = SVNAdminHelper.openRepository(repositoryRoot, true);
return fsfs.getYoungestRevision();
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns the uuid of the repository.
*
* @param repositoryRoot a repository root directory path
* @return an uuid
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public String doGetUUID(File repositoryRoot) throws SVNException {
FSFS fsfs = null;
try {
fsfs = SVNAdminHelper.openRepository(repositoryRoot, true);
return fsfs.getUUID();
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns author information for the given revision.
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @return a revision author
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public String doGetAuthor(File repositoryRoot, SVNRevision revision) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
SVNProperties revProps = fsfs.getRevisionProperties(revNum);
return revProps.getStringValue(SVNRevisionProperty.AUTHOR);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns author information for the given transaction.
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @return a transaction owner
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code></li>
* <li>if the specified transaction is not found</li>
* </ul>
*/
public String doGetAuthor(File repositoryRoot, String transactionName) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
SVNProperties txnProps = fsfs.getTransactionProperties(txn.getTxnId());
return txnProps.getStringValue(SVNRevisionProperty.AUTHOR);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Fetches file contents for the specified revision and path.
* <code>path</code> must be absolute, that is it must
* start with <code>'/'</code>. The provided output stream is
* not closed within this method.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute file path
* @param revision a revision number
* @param out an output stream to write contents to
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found or
* is not a file
* </li>
* </ul>
*/
public void doCat(File repositoryRoot, String path, SVNRevision revision, OutputStream out) throws SVNException {
if (path == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS,
"Missing repository path argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
FSRoot root = fsfs.createRevisionRoot(revNum);
catFile(root, path, out);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Fetches file contents for the specified path in the given
* transaction. <code>path</code> must be absolute, that is it
* must start with <code>'/'</code>. The provided output stream
* is not closed within this method.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute file path
* @param transactionName a transaction name
* @param out an output stream to write contents to
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found or
* is not a file
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public void doCat(File repositoryRoot, String path, String transactionName, OutputStream out) throws SVNException {
if (path == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing repository path argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
FSRoot root = fsfs.createTransactionRoot(txn);
catFile(root, path, out);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns datestamp information for the given revision.
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @return a datestamp
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public Date doGetDate(File repositoryRoot, SVNRevision revision) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
SVNProperties revProps = fsfs.getRevisionProperties(revNum);
String date = revProps.getStringValue(SVNRevisionProperty.DATE);
if (date != null) {
return SVNDate.parseDate(date);
}
return null;
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns datestamp information for the given transaction.
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @return a datestamp
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public Date doGetDate(File repositoryRoot, String transactionName) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
SVNProperties txnProps = fsfs.getTransactionProperties(txn.getTxnId());
String date = txnProps.getStringValue(SVNRevisionProperty.DATE);
if (date != null) {
return SVNDate.parseDate(date);
}
return null;
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns log information for the given revision.
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @return a log message
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public String doGetLog(File repositoryRoot, SVNRevision revision) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
SVNProperties revProps = fsfs.getRevisionProperties(revNum);
return revProps.getStringValue(SVNRevisionProperty.LOG);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns log information for the given transaction.
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @return a log message
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public String doGetLog(File repositoryRoot, String transactionName) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
SVNProperties txnProps = fsfs.getTransactionProperties(txn.getTxnId());
return txnProps.getStringValue(SVNRevisionProperty.LOG);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Traverses changed paths for the given revision invoking
* the passed handler on each changed path.
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @param handler a changed path handler
* @param includeCopyInfo if <span class="javakeyword">true</span> copy-from
* information is also provided for copied paths
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public void doGetChanged(File repositoryRoot, SVNRevision revision, ISVNChangeEntryHandler handler, boolean includeCopyInfo) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
FSRoot root = fsfs.createRevisionRoot(revNum);
long baseRevision = revNum - 1;
SVNNodeEditor editor = generateDeltaTree(fsfs, root, baseRevision);
editor.traverseTree(includeCopyInfo, handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Traverses changed paths for the given transaction invoking
* the passed handler on each changed path.
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @param handler a changed path handler
* @param includeCopyInfo if <span class="javakeyword">true</span> copy-from
* information is also provided for copied paths
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public void doGetChanged(File repositoryRoot, String transactionName, ISVNChangeEntryHandler handler, boolean includeCopyInfo) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
FSRoot root = fsfs.createTransactionRoot(txn);
long baseRevision = txn.getBaseRevision();
if (!SVNRevision.isValidRevisionNumber(baseRevision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Transaction ''{0}'' is not based on a revision; how odd", transactionName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
SVNNodeEditor editor = generateDeltaTree(fsfs, root, baseRevision);
editor.traverseTree(includeCopyInfo, handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Passes paths of directories changed in the given revision to the provided handler.
* Paths are absolute (start with <code>'/'</code>).
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @param handler a path handler
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public void doGetChangedDirectories(File repositoryRoot, SVNRevision revision, ISVNChangedDirectoriesHandler handler) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
FSRoot root = fsfs.createRevisionRoot(revNum);
long baseRevision = revNum - 1;
if (!SVNRevision.isValidRevisionNumber(baseRevision)) {
//r0, no changed directories
return;
}
SVNNodeEditor editor = generateDeltaTree(fsfs, root, baseRevision);
editor.traverseChangedDirs(handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Passes paths of directories changed in the given transaction to the provided handler.
* Paths are absolute (start with <code>'/'</code>).
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @param handler a path handler
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public void doGetChangedDirectories(File repositoryRoot, String transactionName, ISVNChangedDirectoriesHandler handler) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
FSRoot root = fsfs.createTransactionRoot(txn);
long baseRevision = txn.getBaseRevision();
if (!SVNRevision.isValidRevisionNumber(baseRevision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Transaction ''{0}'' is not based on a revision; how odd", transactionName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
SVNNodeEditor editor = generateDeltaTree(fsfs, root, baseRevision);
editor.traverseChangedDirs(handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Passes history information for the specified path and revision to the provided handler.
* This information is provided as {@link SVNAdminPath} objects and include the following
* pieces:
* <ul>
* <li>path (use {@link SVNAdminPath#getPath()} to retrieve it)</li>
* <li>revision (use {@link SVNAdminPath#getRevision()} to retrieve it)</li>
* <li>node id (optional, use {@link SVNAdminPath#getNodeID()} to retrieve it)</li>
* </ul>
* For history retrieval only these listed <code>get</code> methods of <b>SVNAdminPath</b> are
* relevant.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
* If <code>path</code> is <span class="javakeyword">null</span> it defaults to
* <code>"/"</code>.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute path
* @param revision a revision number
* @param includeIDs if <span class="javakeyword">true</span> a node
* revision id is also included for each path
* @param limit maximum number of history entries; if <code><=0</code>, then no limitation
* is applied and all history entries are reported
* @param handler a history handler
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* </ul>
*/
public void doGetHistory(File repositoryRoot, String path, SVNRevision revision, boolean includeIDs,
long limit, ISVNHistoryHandler handler) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
path = path == null ? "/" : path;
getHistory(fsfs, path, 0, revNum, limit, true, includeIDs, handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Retrieves lock information for the specified path.
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute path
* @return an object containing details of a lock or
* <span class="javakeyword">null</span> if the
* path is not locked
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* </ul>
*/
public SVNLock doGetLock(File repositoryRoot, String path) throws SVNException {
if (path == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing path argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSFS fsfs = open(repositoryRoot, SVNRevision.HEAD);
try {
return fsfs.getLockHelper(path, false);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Traverses repository tree starting at the specified path in the
* given revision and invoking the provided handler on each path.
* Path information is provided as {@link SVNAdminPath} objects and
* include the following pieces:
* <ul>
* <li>path (use {@link SVNAdminPath#getPath()} to retrieve it)</li>
* <li>tree depth starting from <code>0</code> at <code>path</code>
* (use {@link SVNAdminPath#getTreeDepth()} to retrieve it)</li>
* <li>node id (optional, use {@link SVNAdminPath#getNodeID()} to retrieve it)</li>
* <li>file/dir information (use {@link SVNAdminPath#isDir()} to retrieve it)</li>
* </ul>
*
* For tree retrieval only these listed <code>get</code> methods of <b>SVNAdminPath</b> are
* relevant.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
* If <code>path</code> is <span class="javakeyword">null</span> it defaults to
* <code>"/"</code>.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute path
* @param revision a revision number
* @param includeIDs if <span class="javakeyword">true</span> a node
* revision id is also included for each path
* @param recursive whether to descend recursively or operate on a single directory only
* @param handler a tree handler
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* </ul>
*/
public void doGetTree(File repositoryRoot, String path, SVNRevision revision, boolean includeIDs,
boolean recursive, ISVNTreeHandler handler) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
FSRoot root = fsfs.createRevisionRoot(revNum);
path = path == null ? "/" : path;
FSRevisionNode node = root.getRevisionNode(path);
FSID id = includeIDs ? node.getId() : null;
SVNNodeKind kind = root.checkNodeKind(path);
getTree(fsfs, root, path, kind, id, includeIDs, 0, recursive, handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Traverses repository tree starting at the specified path in the
* given transaction and invoking the provided handler on each path.
* Path information is provided as {@link SVNAdminPath} objects and
* include the following pieces:
* <ul>
* <li>path (use {@link SVNAdminPath#getPath()} to retrieve it)</li>
* <li>tree depth starting from <code>0</code> at <code>path</code>
* (use {@link SVNAdminPath#getTreeDepth()} to retrieve it)</li>
* <li>node id (optional, use {@link SVNAdminPath#getNodeID()} to retrieve it)</li>
* <li>file/dir information (use {@link SVNAdminPath#isDir()} to retrieve it)</li>
* </ul>
*
* For tree retrieval only these listed <code>get</code> methods of <b>SVNAdminPath</b> are
* relevant.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
* If <code>path</code> is <span class="javakeyword">null</span> it defaults to
* <code>"/"</code>.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute path
* @param transactionName a transaction name
* @param includeIDs if <span class="javakeyword">true</span> a node
* revision id is also included for each path
* @param recursive whether to descend recursively or operate on a single directory only
* @param handler a tree handler
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public void doGetTree(File repositoryRoot, String path, String transactionName, boolean includeIDs,
boolean recursive, ISVNTreeHandler handler) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
FSRoot root = fsfs.createTransactionRoot(txn);
path = path == null ? "/" : path;
FSRevisionNode node = root.getRevisionNode(path);
FSID id = includeIDs ? node.getId() : null;
SVNNodeKind kind = root.checkNodeKind(path);
getTree(fsfs, root, path, kind, id, includeIDs, 0, recursive, handler);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Writes differences of changed files and properties for the
* given revision to the provided output stream. If no special diff generator
* {@link #setDiffGenerator(ISVNGNUDiffGenerator) was provided} to
* this client a default GNU-style diff generator is used (which
* writes differences just like the <code>'svnlook diff'</code> command).
*
* <p>
* The provided output stream is not closed within this method.
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @param diffDeleted if <span class="javakeyword">true</span>
* differences for deleted files are included,
* otherwise not
* @param diffAdded if <span class="javakeyword">true</span>
* differences for added files are included,
* otherwise not
* @param diffCopyFrom if <span class="javakeyword">true</span>
* writes differences against the copy source
* (if any), otherwise not
* @param os an output stream to write differences to
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public void doGetDiff(File repositoryRoot, SVNRevision revision, boolean diffDeleted, boolean diffAdded, boolean diffCopyFrom, OutputStream os) throws SVNException {
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
FSRoot root = fsfs.createRevisionRoot(revNum);
long baseRevision = revNum - 1;
if (!SVNRevision.isValidRevisionNumber(baseRevision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Invalid base revision {0}", new Long(baseRevision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
SVNNodeEditor editor = generateDeltaTree(fsfs, root, baseRevision);
ISVNGNUDiffGenerator generator = getDiffGenerator();
generator.setDiffAdded(diffAdded);
generator.setDiffCopied(diffCopyFrom);
generator.setDiffDeleted(diffDeleted);
editor.diff(root, baseRevision, generator, os);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Writes differences of changed files and properties for the
* given transaction to the provided output stream. If no special diff generator
* {@link #setDiffGenerator(ISVNGNUDiffGenerator) was provided} to
* this client a default GNU-style diff generator is used (which
* writes differences just like the <code>'svnlook diff'</code> command).
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @param diffDeleted if <span class="javakeyword">true</span>
* differences for deleted files are included,
* otherwise not
* @param diffAdded if <span class="javakeyword">true</span>
* differences for added files are included,
* otherwise not
* @param diffCopyFrom if <span class="javakeyword">true</span>
* writes differences against the copy source
* (if any), otherwise not
* @param os an output stream to write differences to
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public void doGetDiff(File repositoryRoot, String transactionName, boolean diffDeleted, boolean diffAdded, boolean diffCopyFrom, OutputStream os) throws SVNException {
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
FSRoot root = fsfs.createTransactionRoot(txn);
long baseRevision = txn.getBaseRevision();
if (!SVNRevision.isValidRevisionNumber(baseRevision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Transaction ''{0}'' is not based on a revision; how odd", transactionName);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
SVNNodeEditor editor = generateDeltaTree(fsfs, root, baseRevision);
ISVNGNUDiffGenerator generator = getDiffGenerator();
generator.setDiffAdded(diffAdded);
generator.setDiffCopied(diffCopyFrom);
generator.setDiffDeleted(diffDeleted);
editor.diff(root, baseRevision, generator, os);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns the value of a versioned property for the specified path in the
* given revision.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
*
* @param repositoryRoot a repository root directory path
* @param propName a property name
* @param path an absolute path
* @param revision a revision number
* @return the value of a property
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* </ul>
*/
public SVNPropertyValue doGetProperty(File repositoryRoot, String propName, String path, SVNRevision revision) throws SVNException {
SVNProperties props = getProperties(repositoryRoot, propName, path, revision, null, true, false);
return props.getSVNPropertyValue(propName);
}
/**
* Returns versioned properties for the specified path in the
* given revision.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute path
* @param revision a revision number
* @return name (String) to value (String) mappings
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* </ul>
*/
public SVNProperties doGetProperties(File repositoryRoot, String path, SVNRevision revision) throws SVNException {
return getProperties(repositoryRoot, null, path, revision, null, false, false);
}
/**
* Returns the value of a versioned property for the specified path in the
* given transaction.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
*
* @param repositoryRoot a repository root directory path
* @param propName a property name
* @param path an absolute path
* @param transactionName a transaction name
* @return the value of a property
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public SVNPropertyValue doGetProperty(File repositoryRoot, String propName, String path, String transactionName) throws SVNException {
SVNProperties props = getProperties(repositoryRoot, propName, path, null, transactionName, true, false);
return props.getSVNPropertyValue(propName);
}
/**
* Returns versioned properties for the specified path in the
* given transaction.
*
* <p>
* <code>path</code> must be absolute, that is it must start with <code>'/'</code>.
*
* @param repositoryRoot a repository root directory path
* @param path an absolute path
* @param transactionName a transaction name
* @return name (String) to value (String) mappings
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if <code>path</code> is not found
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public SVNProperties doGetProperties(File repositoryRoot, String path, String transactionName) throws SVNException {
return getProperties(repositoryRoot, null, path, null, transactionName, false, false);
}
/**
* Returns the value of a revision property in the given revision.
*
* @param repositoryRoot a repository root directory path
* @param propName a property name
* @param revision a revision number
* @return the value of a revision property
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public SVNPropertyValue doGetRevisionProperty(File repositoryRoot, String propName, SVNRevision revision) throws SVNException {
SVNProperties revProps = getProperties(repositoryRoot, propName, null, revision, null, true, true);
return revProps.getSVNPropertyValue(propName);
}
/**
* Returns revision properties in the given revision.
*
* @param repositoryRoot a repository root directory path
* @param revision a revision number
* @return name (String) to value (String) mappings
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public SVNProperties doGetRevisionProperties(File repositoryRoot, SVNRevision revision) throws SVNException {
return getProperties(repositoryRoot, null, null, revision, null, false, true);
}
/**
* Returns the value of a revision property for the given transaction.
*
* @param repositoryRoot a repository root directory path
* @param propName a property name
* @param transactionName a transaction name
* @return the value of a revision property
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public SVNPropertyValue doGetRevisionProperty(File repositoryRoot, String propName, String transactionName) throws SVNException {
SVNProperties revProps = getProperties(repositoryRoot, propName, null, null, transactionName, true, true);
return revProps.getSVNPropertyValue(propName);
}
/**
* Returns revision properties for the given transaction.
*
* @param repositoryRoot a repository root directory path
* @param transactionName a transaction name
* @return name (String) to value (String) mappings
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public SVNProperties doGetRevisionProperties(File repositoryRoot, String transactionName) throws SVNException {
return getProperties(repositoryRoot, null, null, null, transactionName, false, true);
}
/**
* Returns the size in bytes for the specified path in given transaction
*
* @param repositoryRoot a repository root directory path
* @param path a path to the file inside the repository
* @param transactionName a transaction name
* @return file size in bytes
* @throws SVNException <ul>
* <li>no repository is found at
* <code>repositoryRoot</code>
* </li>
* <li>if the specified transaction is not found
* </li>
* </ul>
*/
public long doGetFileSize(File repositoryRoot, String path, String transactionName) throws SVNException {
if (path == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing repository path argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSFS fsfs = open(repositoryRoot, transactionName);
try {
FSTransactionInfo txn = fsfs.openTxn(transactionName);
FSRoot root = fsfs.createTransactionRoot(txn);
return getFileSize(root, path);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Returns the size in bytes for the specified path in given revision
*
* @param repositoryRoot a repository root directory path
* @param path a path to the file inside the repository
* @param revision a revision
* @return file size in bytes
* @throws SVNException no repository is found at
* <code>repositoryRoot</code>
*/
public long doGetFileSize(File repositoryRoot, String path, SVNRevision revision) throws SVNException {
if (path == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing repository path argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSFS fsfs = open(repositoryRoot, revision);
try {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
FSRoot root = fsfs.createRevisionRoot(revNum);
return getFileSize(root, path);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
/**
* Sets a diff generator to be used in <code>doGetDiff()</code> methods of this class.
*
* @param diffGenerator
* @see #getDiffGenerator()
*/
public void setDiffGenerator(ISVNGNUDiffGenerator diffGenerator) {
myDiffGenerator = diffGenerator;
}
/**
* Returns a diff generator to be used in <code>doGetDiff()</code> methods of this class.
* If no generator was provided by a caller, <b>SVNLookClient</b> uses a default one
* that prints differences in a GNU-style.
*
* @return a diff generator
* @see #setDiffGenerator(ISVNGNUDiffGenerator)
*/
public ISVNGNUDiffGenerator getDiffGenerator() {
if (myDiffGenerator == null) {
DefaultSVNGNUDiffGenerator defaultDiffGenerator = new DefaultSVNGNUDiffGenerator();
defaultDiffGenerator.setOptions(getOptions());
myDiffGenerator = defaultDiffGenerator;
}
return myDiffGenerator;
}
private void getTree(FSFS fsfs, FSRoot root, String path, SVNNodeKind kind, FSID id, boolean includeIDs,
int depth, boolean recursive, ISVNTreeHandler handler) throws SVNException {
checkCancelled();
if (handler != null) {
handler.handlePath(new SVNAdminPath(path, includeIDs ? id.toString() : null, depth,
kind == SVNNodeKind.DIR));
}
if (kind != SVNNodeKind.DIR) {
return;
}
if (recursive || depth == 0) {
FSRevisionNode node = root.getRevisionNode(path);
Map entries = node.getDirEntries(fsfs);
for (Iterator names = entries.keySet().iterator(); names.hasNext();) {
String name = (String) names.next();
FSEntry entry = (FSEntry) entries.get(name);
getTree(fsfs, root, SVNPathUtil.getAbsolutePath(SVNPathUtil.append(path, entry.getName())),
entry.getType(), includeIDs ? entry.getId() : null, includeIDs, depth + 1, recursive, handler);
}
}
}
private SVNProperties getProperties(File repositoryRoot, String propName, String path, SVNRevision revision, String txnName, boolean singleProp, boolean revProps) throws SVNException {
if (propName == null && singleProp) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing propname argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (path == null && !revProps) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing repository path argument");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSFS fsfs = txnName == null ? open(repositoryRoot, revision) : open(repositoryRoot, txnName);
try {
FSRoot root = null;
if (txnName == null) {
long revNum = SVNAdminHelper.getRevisionNumber(revision, fsfs.getYoungestRevision(), fsfs);
if (revProps) {
return fsfs.getRevisionProperties(revNum);
}
root = fsfs.createRevisionRoot(revNum);
} else {
FSTransactionInfo txn = fsfs.openTxn(txnName);
if (revProps) {
return fsfs.getTransactionProperties(txn.getTxnId());
}
root = fsfs.createTransactionRoot(txn);
}
verifyPath(root, path);
FSRevisionNode node = root.getRevisionNode(path);
return node.getProperties(fsfs);
} finally {
SVNAdminHelper.closeRepository(fsfs);
}
}
private void getHistory(FSFS fsfs, String path, long start, long end, long limit, boolean crossCopies,
boolean includeIDs, ISVNHistoryHandler handler) throws SVNException {
if (!SVNRevision.isValidRevisionNumber(start)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION,
"Invalid start revision {0}", String.valueOf(start));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (!SVNRevision.isValidRevisionNumber(end)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION,
"Invalid end revision {0}", String.valueOf(end));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (start > end) {
long tmpRev = start;
start = end;
end = tmpRev;
}
FSRevisionRoot root = fsfs.createRevisionRoot(end);
FSNodeHistory history = root.getNodeHistory(path);
long count = 0;
do {
history = history.getPreviousHistory(crossCopies);
if (history == null) {
break;
}
long revision = history.getHistoryEntry().getRevision();
if (revision < start) {
break;
}
String id = null;
if (includeIDs) {
FSRevisionRoot revRoot = fsfs.createRevisionRoot(revision);
FSRevisionNode node = revRoot.getRevisionNode(history.getHistoryEntry().getPath());
id = node.getId().toString();
}
if (handler != null) {
try {
handler.handlePath(new SVNAdminPath(history.getHistoryEntry().getPath(), id, revision));
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.CEASE_INVOCATION) {
break;
}
throw svne;
}
}
if (limit > 0) {
count++;
if (count >= limit) {
break;
}
}
} while (history != null);
}
private SVNNodeEditor generateDeltaTree(FSFS fsfs, FSRoot root, long baseRevision) throws SVNException {
FSRevisionRoot baseRoot = fsfs.createRevisionRoot(baseRevision);
SVNNodeEditor editor = new SVNNodeEditor(fsfs, baseRoot, this);
FSRepositoryUtil.replay(fsfs, root, "", -1, false, editor);
return editor;
}
private void catFile(FSRoot root, String path, OutputStream out) throws SVNException {
SVNNodeKind kind = verifyPath(root, path);
if (kind != SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Path ''{0}'' is not a file", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (out != null) {
InputStream contents = null;
try {
contents = root.getFileStreamForPath(new SVNDeltaCombiner(), path);
byte[] buffer = new byte[SVNFileUtil.STREAM_CHUNK_SIZE];
int len = 0;
do {
checkCancelled();
len = SVNFileUtil.readIntoBuffer(contents, buffer, 0, buffer.length);
if (len > 0) {
out.write(buffer, 0, len);
}
} while (len == SVNFileUtil.STREAM_CHUNK_SIZE);
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(contents);
}
}
}
private long getFileSize(FSRoot root, String path) throws SVNException {
SVNNodeKind kind = verifyPath(root, path);
if (kind != SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Path ''{0}'' is not a file", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return root.getFileSize(path);
}
private SVNNodeKind verifyPath(FSRoot root, String path) throws SVNException {
SVNNodeKind kind = root.checkNodeKind(path);
if (kind == SVNNodeKind.NONE) {
if (SVNPathUtil.isURL(path)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND,
"''{0}'' is a URL, probably should be a path", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND,
"Path ''{0}'' does not exist", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return kind;
}
private FSFS open(File repositoryRoot, SVNRevision revision) throws SVNException {
if (revision == null || !revision.isValid()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_ARG_PARSING_ERROR, "Invalid revision number supplied");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return SVNAdminHelper.openRepository(repositoryRoot, true);
}
private FSFS open(File repositoryRoot, String transactionName) throws SVNException {
if (transactionName == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CL_INSUFFICIENT_ARGS, "Missing transaction name");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return SVNAdminHelper.openRepository(repositoryRoot, true);
}
}