package org.tmatesoft.svn.test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc2.SvnCheckout;
import org.tmatesoft.svn.core.wc2.SvnCommit;
import org.tmatesoft.svn.core.wc2.SvnCopy;
import org.tmatesoft.svn.core.wc2.SvnCopySource;
import org.tmatesoft.svn.core.wc2.SvnGetStatus;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnRevert;
import org.tmatesoft.svn.core.wc2.SvnScheduleForAddition;
import org.tmatesoft.svn.core.wc2.SvnScheduleForRemoval;
import org.tmatesoft.svn.core.wc2.SvnSetProperty;
import org.tmatesoft.svn.core.wc2.SvnStatus;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.core.wc2.SvnUpdate;
public class WorkingCopy {
private final TestOptions testOptions;
private final File workingCopyDirectory;
private SvnOperationFactory clientManager;
private long currentRevision;
private PrintWriter logger;
private SVNURL repositoryUrl;
private SvnWcGeneration wcGeneration;
public WorkingCopy(TestOptions testOptions, File workingCopyDirectory) {
this.testOptions = testOptions;
this.workingCopyDirectory = workingCopyDirectory;
this.currentRevision = -1;
}
public void setWcGeneration(SvnWcGeneration generation) {
this.wcGeneration = generation;
if (clientManager != null) {
clientManager.setPrimaryWcGeneration(wcGeneration);
}
}
public void setRepositoryUrl(SVNURL repositoryUrl) {
this.repositoryUrl = repositoryUrl;
}
public File deleteFile(String relativePath) throws SVNException {
SVNFileUtil.deleteAll(getFile(relativePath), true);
return getFile(relativePath);
}
public File changeFileContents(String relativePath, String contents) throws SVNException {
TestUtil.writeFileContentsString(getFile(relativePath), contents);
return getFile(relativePath);
}
public File getFile(String relativePath) {
return new File(getWorkingCopyDirectory(), relativePath);
}
public long checkoutLatestRevision(SVNURL repositoryUrl) throws SVNException {
return checkoutRevision(repositoryUrl, SVNRepository.INVALID_REVISION);
}
public long checkoutRevision(SVNURL repositoryUrl, long revision) throws SVNException {
return checkoutRevision(repositoryUrl, revision, true);
}
public long checkoutRevision(SVNURL repositoryUrl, long revision, boolean ignoreExternals) throws SVNException {
disposeLogger();
SVNFileUtil.deleteFile(getLogFile());
beforeOperation();
log("Checking out " + repositoryUrl);
final SvnCheckout checkout = getOperationFactory().createCheckout();
checkout.setIgnoreExternals(ignoreExternals);
this.repositoryUrl = repositoryUrl;
final boolean isWcExists = getWorkingCopyDirectory().isDirectory();
try {
checkout.setSingleTarget(SvnTarget.fromFile(getWorkingCopyDirectory()));
checkout.setSource(SvnTarget.fromURL(repositoryUrl, revision >= 0 ? SVNRevision.create(revision) : SVNRevision.HEAD));
checkout.setRevision(revision >= 0 ? SVNRevision.create(revision) : SVNRevision.HEAD);
checkout.setDepth(SVNDepth.INFINITY);
checkout.setAllowUnversionedObstructions(true);
currentRevision = checkout.run();
} catch (Throwable th) {
if (isWcExists) {
SVNFileUtil.deleteAll(getWorkingCopyDirectory(), true);
checkout.run();
} else {
wrapThrowable(th);
}
}
log("Checked out " + repositoryUrl);
checkNativeStatusShowsNoChanges();
afterOperation();
return currentRevision;
}
public void updateToRevision(long revision) throws SVNException {
beforeOperation();
log("Updating to revision " + revision);
SvnUpdate up = getOperationFactory().createUpdate();
up.setIgnoreExternals(true);
up.setSingleTarget(SvnTarget.fromFile(getWorkingCopyDirectory()));
up.setRevision(revision >= 0 ? SVNRevision.create(revision) : SVNRevision.HEAD);
up.setDepth(SVNDepth.INFINITY);
up.setAllowUnversionedObstructions(true);
try {
currentRevision = up.run()[0];
} catch (Throwable th) {
wrapThrowable(th);
}
log("Updated to revision " + currentRevision);
checkNativeStatusShowsNoChanges();
afterOperation();
}
public File findAnyDirectory() {
final File workingCopyDirectory = getWorkingCopyDirectory();
final File[] files = SVNFileListUtil.listFiles(workingCopyDirectory);
if (files != null) {
for (File file : files) {
if (file.isDirectory() && !file.getName().equals(SVNFileUtil.getAdminDirectoryName())) {
return file;
}
}
}
throwException("The repository contains no directories, run the tests with another repository");
return null;
}
public File findAnotherDirectory(File directory) {
final File workingCopyDirectory = getWorkingCopyDirectory();
final File[] files = SVNFileListUtil.listFiles(workingCopyDirectory);
if (files != null) {
for (File file : files) {
if (file.isDirectory() && !file.getName().equals(SVNFileUtil.getAdminDirectoryName()) && !file.getAbsolutePath().equals(directory.getAbsolutePath())) {
return file;
}
}
}
throwException("The repository root should contain at least two directories, please run the tests with another repository");
return null;
}
public void copyAsChild(File directory, File anotherDirectory) throws SVNException {
beforeOperation();
log("Copying " + directory + " as a child of " + anotherDirectory);
final SvnCopy copyClient = getOperationFactory().createCopy();
copyClient.addCopySource(SvnCopySource.create(SvnTarget.fromFile(directory), SVNRevision.WORKING));
copyClient.setSingleTarget(SvnTarget.fromFile(anotherDirectory));
copyClient.setFailWhenDstExists(false);
try {
copyClient.run();
} catch (Throwable th) {
wrapThrowable(th);
}
log("Copied " + directory + " as a child of " + anotherDirectory);
afterOperation();
}
public long commit(String commitMessage) throws SVNException {
beforeOperation();
log("Committing ");
final SvnCommit commitClient = getOperationFactory().createCommit();
commitClient.setSingleTarget(SvnTarget.fromFile(getWorkingCopyDirectory()));
commitClient.setCommitMessage(commitMessage);
commitClient.setDepth(SVNDepth.INFINITY);
commitClient.setForce(true);
SVNCommitInfo commitInfo = null;
try {
commitInfo = commitClient.run();
} catch (Throwable th) {
wrapThrowable(th);
}
log("Committed revision " + (commitInfo == null ? "none" : commitInfo.getNewRevision()));
checkNativeStatusShowsNoChanges();
afterOperation();
return commitInfo == null ? -1 : commitInfo.getNewRevision();
}
public void add(File file) throws SVNException {
beforeOperation();
log("Adding " + file);
SvnScheduleForAddition add = getOperationFactory().createScheduleForAddition();
add.setSingleTarget(SvnTarget.fromFile(file));
add.setDepth(SVNDepth.INFINITY);
add.setIncludeIgnored(true);
add.setAddParents(true);
try {
add.run();
} catch (Throwable th) {
wrapThrowable(th);
}
log("Added " + file);
afterOperation();
}
public void revert() throws SVNException {
beforeOperation();
log("Reverting working copy");
SvnRevert revert = getOperationFactory().createRevert();
revert.setSingleTarget(SvnTarget.fromFile(getWorkingCopyDirectory()));
revert.setDepth(SVNDepth.INFINITY);
try {
revert.run();
} catch (Throwable th) {
wrapThrowable(th);
}
log("Reverted working copy");
checkNativeStatusShowsNoChanges();
afterOperation();
}
public List<File> getChildren() {
final List<File> childrenList = new ArrayList<File>();
final File[] children = SVNFileListUtil.listFiles(getWorkingCopyDirectory());
if (children != null) {
for (File child : children) {
if (!child.getName().equals(SVNFileUtil.getAdminDirectoryName())) {
childrenList.add(child);
}
}
}
return childrenList;
}
public void setProperty(File file, String propertyName, SVNPropertyValue propertyValue) throws SVNException {
beforeOperation();
log("Setting property " + propertyName + " on " + file);
SvnSetProperty ps = getOperationFactory().createSetProperty();
ps.setSingleTarget(SvnTarget.fromFile(file));
ps.setPropertyName(propertyName);
ps.setPropertyValue(propertyValue);
ps.setDepth(SVNDepth.EMPTY);
ps.setForce(true);
try {
ps.run();
} catch (Throwable th) {
wrapThrowable(th);
}
log("Set property " + propertyName + " on " + file);
afterOperation();
}
public void delete(File file) throws SVNException {
beforeOperation();
log("Deleting " + file);
SvnScheduleForRemoval rm = getOperationFactory().createScheduleForRemoval();
rm.setSingleTarget(SvnTarget.fromFile(file));
rm.setForce(true);
try {
rm.run();
} catch (Throwable th) {
wrapThrowable(th);
}
log("Deleted " + file);
afterOperation();
}
public long getCurrentRevision() {
return currentRevision;
}
public SVNURL getRepositoryUrl() {
return repositoryUrl;
}
private void checkWorkingCopyConsistency() {
final File wcDbFile = getWCDbFile();
if (!wcDbFile.exists()) {
log("No wc.db, maybe running on the old SVN working copy format, integrity check skipped");
return;
}
final String wcDbPath = wcDbFile.getAbsolutePath().replace('/', File.separatorChar);
final ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(getSqlite3Command(), wcDbPath, "pragma integrity_check;");
BufferedReader bufferedReader = null;
try {
final Process process = processBuilder.start();
if (process != null) {
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
final String line = bufferedReader.readLine();
if (line == null || !"ok".equals(line.trim())) {
final String notConsistentMessage = "SVN working copy database is not consistent.";
throwException(notConsistentMessage);
}
process.waitFor();
}
} catch (IOException e) {
log("failed to start sqlite3");
} catch (InterruptedException e) {
log("failed to start sqlite3");
} finally {
SVNFileUtil.closeFile(bufferedReader);
}
}
public File getWCDbFile() {
return new File(getAdminDirectory(), ISVNWCDb.SDB_FILE).getAbsoluteFile();
}
private File getAdminDirectory() {
return new File(getWorkingCopyDirectory(), SVNFileUtil.getAdminDirectoryName()).getAbsoluteFile();
}
public void dispose() {
if (clientManager != null) {
clientManager.dispose();
clientManager = null;
}
disposeLogger();
}
private void disposeLogger() {
SVNFileUtil.closeFile(logger);
logger = null;
}
public SvnOperationFactory getOperationFactory() {
if (clientManager == null) {
clientManager = new SvnOperationFactory();
clientManager.setPrimaryWcGeneration(wcGeneration);
}
return clientManager;
}
private TestOptions getTestOptions() {
return testOptions;
}
public File getWorkingCopyDirectory() {
return workingCopyDirectory.getAbsoluteFile();
}
private String getSqlite3Command() {
return getTestOptions().getSqlite3Command();
}
public String getSvnCommand() {
return getTestOptions().getSvnCommand();
}
public PrintWriter getLogger() {
if (logger == null) {
final File adminDirectory = getAdminDirectory();
if (!adminDirectory.exists()) {
//not checked out yet
return null;
}
final File testLogFile = getLogFile();
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(testLogFile, true);
} catch (IOException e) {
SVNFileUtil.closeFile(fileWriter);
throw new RuntimeException(e);
}
logger = new PrintWriter(fileWriter);
}
return logger;
}
private File getLogFile() {
return new File(getAdminDirectory(), "test.log");
}
private void log(String message) {
final PrintWriter logger = getLogger();
if (logger != null) {
logger.println("[" + new Date() + "]" + message);
logger.flush();
}
}
private void beforeOperation() throws SVNException {
log("");
// backupWcDbFile();
}
private void afterOperation() {
checkWorkingCopyConsistency();
log("Checked for consistency");
disposeLogger();
}
private void checkNativeStatusShowsNoChanges() {
log("Checking for local changes with native svn");
final ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command(getSvnCommand(), "status", "--ignore-externals", "-q");
processBuilder.directory(getWorkingCopyDirectory());
BufferedReader bufferedReader = null;
try {
final Process process = processBuilder.start();
if (process != null) {
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
final List<String> lines = new ArrayList<String>();
while (true) {
String line = bufferedReader.readLine();
if (line == null) {
break;
}
if (line.endsWith("\n")) {
line = line.substring(0, line.length() - 1);
}
if (line.endsWith("\r")) {
line = line.substring(0, line.length() - 1);
}
if (line.length() > 0) {
lines.add(line);
}
}
if (lines.size() > 0) {
final StringBuilder stringBuilder = new StringBuilder("SVN status showed:").append('\n');
for (String line : lines) {
stringBuilder.append(line).append('\n');
}
log(stringBuilder.toString());
log("");
}
final String localChangesMessage = "SVN status shows local changes for the working copy.";
for (String line : lines) {
if (line.charAt(0) != ' ' && line.charAt(0) != 'M') {
throwException(localChangesMessage);
} else if (line.charAt(1) != ' ') {
throwException(localChangesMessage);
}
}
process.waitFor();
}
} catch (IOException e) {
wrapThrowable(e);
} catch (InterruptedException e) {
wrapThrowable(e);
} finally {
SVNFileUtil.closeFile(bufferedReader);
}
log("No local changes");
}
private void throwException(String message) {
final RuntimeException runtimeException = new RuntimeException(message);
runtimeException.printStackTrace(getLogger());
throw runtimeException;
}
private void wrapThrowable(Throwable th) {
th.printStackTrace(getLogger());
throw new RuntimeException(th);
}
public SvnStatus getStatus(String path) throws SVNException {
SvnGetStatus st = new SvnOperationFactory().createGetStatus();
st.setDepth(SVNDepth.EMPTY);
st.setSingleTarget(SvnTarget.fromFile(getFile(path)));
return st.run();
}
public void copy(String srcPath, String dstPath) throws SVNException {
SvnCopy cp = new SvnOperationFactory().createCopy();
cp.addCopySource(SvnCopySource.create(SvnTarget.fromFile(getFile(srcPath)), SVNRevision.WORKING));
cp.setSingleTarget(SvnTarget.fromFile(getFile(dstPath)));
cp.run();
}
}