blob: cd0a321d5a582e80272569234c18dab545406c21 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.idea.svn;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.AbstractVcsHelper;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.impl.status.StatusBarUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.api.Depth;
import org.jetbrains.idea.svn.api.EventAction;
import org.jetbrains.idea.svn.api.ProgressEvent;
import org.jetbrains.idea.svn.api.ProgressTracker;
import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationManager;
import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew;
import org.jetbrains.idea.svn.browse.DirectoryEntry;
import org.jetbrains.idea.svn.browse.DirectoryEntryConsumer;
import org.jetbrains.idea.svn.commandLine.SvnBindException;
import org.jetbrains.idea.svn.dialogs.LockDialog;
import org.jetbrains.idea.svn.info.Info;
import org.jetbrains.idea.svn.status.Status;
import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.table.SqlJetDb;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import java.io.File;
import java.net.URI;
import java.nio.channels.NonWritableChannelException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SvnUtil {
// TODO: ASP.NET hack behavior should be supported - http://svn.apache.org/repos/asf/subversion/trunk/notes/asp-dot-net-hack.txt
// TODO: Remember this when moving out SVNKit classes.
@NonNls public static final String SVN_ADMIN_DIR_NAME = SVNFileUtil.getAdminDirectoryName();
@NonNls public static final String ENTRIES_FILE_NAME = "entries";
@NonNls public static final String WC_DB_FILE_NAME = "wc.db";
@NonNls public static final String PATH_TO_LOCK_FILE = SVN_ADMIN_DIR_NAME + "/lock";
public static final int DEFAULT_PORT_INDICATOR = -1;
private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnUtil");
public static final Pattern ERROR_PATTERN = Pattern.compile("^svn: (E(\\d+)): (.*)$", Pattern.MULTILINE);
public static final Pattern WARNING_PATTERN = Pattern.compile("^svn: warning: (W(\\d+)): (.*)$", Pattern.MULTILINE);
private SvnUtil() { }
@Nullable
public static SVNErrorMessage parseWarning(@NotNull String text) {
Matcher matcher = WARNING_PATTERN.matcher(text);
SVNErrorMessage error = null;
// currently treating only first warning
if (matcher.find()) {
error = SVNErrorMessage
.create(SVNErrorCode.getErrorCode(Integer.parseInt(matcher.group(2))), matcher.group(3), SVNErrorMessage.TYPE_WARNING);
}
return error;
}
public static boolean isSvnVersioned(final Project project, File parent) {
return isSvnVersioned(SvnVcs.getInstance(project), parent);
}
public static boolean isSvnVersioned(final @NotNull SvnVcs vcs, File parent) {
final Info info = vcs.getInfo(parent);
return info != null;
}
public static List<File> toFiles(Iterable<String> paths) {
List<File> result = ContainerUtil.newArrayList();
for (String path : paths) {
result.add(new File(path));
}
return result;
}
public static Collection<VirtualFile> crawlWCRoots(final Project project, File path, SvnWCRootCrawler callback, ProgressIndicator progress) {
final LocalFileSystem lfs = LocalFileSystem.getInstance();
VirtualFile vf = lfs.findFileByIoFile(path);
if (vf == null) {
vf = lfs.refreshAndFindFileByIoFile(path);
}
if (vf == null) return Collections.emptyList();
return crawlWCRoots(project, vf, callback, progress);
}
private static Collection<VirtualFile> crawlWCRoots(final Project project, VirtualFile vf, SvnWCRootCrawler callback, ProgressIndicator progress) {
final Collection<VirtualFile> result = new HashSet<VirtualFile>();
final boolean isDirectory = vf.isDirectory();
VirtualFile parent = ! isDirectory || !vf.exists() ? vf.getParent() : vf;
final File parentIo = new File(parent.getPath());
if (isSvnVersioned(project, parentIo)) {
checkCanceled(progress);
File ioFile = new File(vf.getPath());
callback.handleWorkingCopyRoot(ioFile, progress);
checkCanceled(progress);
result.add(parent);
} else if (isDirectory) {
checkCanceled(progress);
final VirtualFile[] childrenVF = parent.getChildren();
for (VirtualFile file : childrenVF) {
checkCanceled(progress);
if (file.isDirectory()) {
result.addAll(crawlWCRoots(project, file, callback, progress));
}
}
}
return result;
}
private static void checkCanceled(final ProgressIndicator progress) {
if (progress != null && progress.isCanceled()) {
throw new ProcessCanceledException();
}
}
@Nullable
public static String getExactLocation(final SvnVcs vcs, File path) {
Info info = vcs.getInfo(path);
return info != null && info.getURL() != null ? info.getURL().toString() : null;
}
public static void doLockFiles(Project project, final SvnVcs activeVcs, @NotNull final File[] ioFiles) throws VcsException {
final String lockMessage;
final boolean force;
// TODO[yole]: check for shift pressed
if (activeVcs.getCheckoutOptions().getValue()) {
LockDialog dialog = new LockDialog(project, true, ioFiles.length > 1);
dialog.show();
if (!dialog.isOK()) {
return;
}
lockMessage = dialog.getComment();
force = dialog.isForce();
}
else {
lockMessage = "";
force = false;
}
final VcsException[] exception = new VcsException[1];
final Collection<String> failedLocks = new ArrayList<String>();
final int[] count = new int[]{ioFiles.length};
final ProgressTracker eventHandler = new ProgressTracker() {
public void consume(ProgressEvent event) {
if (event.getAction() == EventAction.LOCK_FAILED) {
failedLocks.add(event.getErrorMessage() != null ?
event.getErrorMessage().getFullMessage() :
event.getFile().getAbsolutePath());
count[0]--;
}
}
public void checkCancelled() {
}
};
Runnable command = new Runnable() {
public void run() {
ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
try {
if (progress != null) {
progress.setText(SvnBundle.message("progress.text.locking.files"));
}
for (File ioFile : ioFiles) {
if (progress != null) {
progress.checkCanceled();
}
if (progress != null) {
progress.setText2(SvnBundle.message("progress.text2.processing.file", ioFile.getName()));
}
activeVcs.getFactory(ioFile).createLockClient().lock(ioFile, force, lockMessage, eventHandler);
}
}
catch (VcsException e) {
exception[0] = e;
}
}
};
ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.lock.files"), false, project);
if (!failedLocks.isEmpty()) {
String[] failedFiles = ArrayUtil.toStringArray(failedLocks);
List<VcsException> exceptions = new ArrayList<VcsException>();
for (String file : failedFiles) {
exceptions.add(new VcsException(SvnBundle.message("exception.text.locking.file.failed", file)));
}
final StringBuilder sb = new StringBuilder(SvnBundle.message("message.text.files.lock.failed", failedFiles.length == 1 ? 0 : 1));
for (VcsException vcsException : exceptions) {
if (sb.length() > 0) sb.append('\n');
sb.append(vcsException.getMessage());
}
//AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("message.title.lock.failures"));
throw new VcsException(sb.toString());
}
StatusBarUtil.setStatusBarInfo(project, SvnBundle.message("message.text.files.locked", count[0]));
if (exception[0] != null) {
throw exception[0];
}
}
public static void doUnlockFiles(Project project, final SvnVcs activeVcs, final File[] ioFiles) throws VcsException {
final boolean force = true;
final VcsException[] exception = new VcsException[1];
final Collection<String> failedUnlocks = new ArrayList<String>();
final int[] count = new int[]{ioFiles.length};
final ProgressTracker eventHandler = new ProgressTracker() {
public void consume(ProgressEvent event) {
if (event.getAction() == EventAction.UNLOCK_FAILED) {
failedUnlocks.add(event.getErrorMessage() != null ?
event.getErrorMessage().getFullMessage() :
event.getFile().getAbsolutePath());
count[0]--;
}
}
public void checkCancelled() {
}
};
Runnable command = new Runnable() {
public void run() {
ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator();
try {
if (progress != null) {
progress.setText(SvnBundle.message("progress.text.unlocking.files"));
}
for (File ioFile : ioFiles) {
if (progress != null) {
progress.checkCanceled();
}
if (progress != null) {
progress.setText2(SvnBundle.message("progress.text2.processing.file", ioFile.getName()));
}
activeVcs.getFactory(ioFile).createLockClient().unlock(ioFile, force, eventHandler);
}
}
catch (VcsException e) {
exception[0] = e;
}
}
};
ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.unlock.files"), false, project);
if (!failedUnlocks.isEmpty()) {
String[] failedFiles = ArrayUtil.toStringArray(failedUnlocks);
List<VcsException> exceptions = new ArrayList<VcsException>();
for (String file : failedFiles) {
exceptions.add(new VcsException(SvnBundle.message("exception.text.failed.to.unlock.file", file)));
}
AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("message.title.unlock.failures"));
}
StatusBarUtil.setStatusBarInfo(project, SvnBundle.message("message.text.files.unlocked", count[0]));
if (exception[0] != null) {
throw new VcsException(exception[0]);
}
}
public static Collection<List<Change>> splitChangesIntoWc(final SvnVcs vcs, final List<Change> changes) {
return splitIntoRepositories(vcs, changes, new Convertor<Change, File>() {
@Override
public File convert(Change o) {
return ChangesUtil.getFilePath(o).getIOFile();
}
});
}
public static <T> Collection<List<T>> splitIntoRepositories(final SvnVcs vcs, final List<T> committables,
Convertor<T, File> convertor) {
if (committables.size() == 1) {
return Collections.singletonList(committables);
}
final MultiMap<Pair<SVNURL, WorkingCopyFormat>, T> result = splitIntoRepositoriesMap(vcs, committables, convertor);
if (result.size() == 1) {
return Collections.singletonList(committables);
}
final Collection<List<T>> result2 = new ArrayList<List<T>>();
for (Map.Entry<Pair<SVNURL, WorkingCopyFormat>, Collection<T>> entry : result.entrySet()) {
result2.add((List<T>)entry.getValue());
}
return result2;
}
public static <T> MultiMap<Pair<SVNURL, WorkingCopyFormat>, T> splitIntoRepositoriesMap(SvnVcs vcs,
List<T> committables, Convertor<T, File> convertor) {
final MultiMap<Pair<SVNURL, WorkingCopyFormat>, T> result = MultiMap.create();
for (T committable : committables) {
final RootUrlInfo path = vcs.getSvnFileUrlMapping().getWcRootForFilePath(convertor.convert(committable));
if (path == null) {
result.putValue(Pair.create((SVNURL)null, WorkingCopyFormat.UNKNOWN), committable);
} else {
result.putValue(Pair.create(path.getRepositoryUrlUrl(), path.getFormat()), committable);
}
}
return result;
}
/**
* Gets working copy internal format. Works for 1.7 and 1.8.
*
* @param path
* @return
*/
@NotNull
public static WorkingCopyFormat getFormat(final File path) {
WorkingCopyFormat result = null;
File dbFile = resolveDatabase(path);
if (dbFile != null) {
result = FileUtilRt.doIOOperation(new WorkingCopyFormatOperation(dbFile));
if (result == null) {
notifyDatabaseError();
}
}
return result != null ? result : WorkingCopyFormat.UNKNOWN;
}
private static void close(@Nullable SqlJetDb db) {
if (db != null) {
try {
db.close();
}
catch (SqlJetException e) {
notifyDatabaseError();
}
}
}
private static void notifyDatabaseError() {
VcsBalloonProblemNotifier.NOTIFICATION_GROUP
.createNotification("Some errors occurred while accessing svn working copy database.", NotificationType.ERROR).notify(null);
}
private static File resolveDatabase(final File path) {
File dbFile = getWcDb(path);
File result = null;
try {
if (dbFile.exists() && dbFile.isFile()) {
result = dbFile;
}
} catch (SecurityException e) {
LOG.error("Failed to access working copy database", e);
}
return result;
}
@Nullable
public static String getRepositoryUUID(final SvnVcs vcs, final File file) {
final Info info = vcs.getInfo(file);
return info != null ? info.getRepositoryUUID() : null;
}
@Nullable
public static String getRepositoryUUID(final SvnVcs vcs, final SVNURL url) {
try {
final Info info = vcs.getInfo(url, SVNRevision.UNDEFINED);
return (info == null) ? null : info.getRepositoryUUID();
}
catch (SvnBindException e) {
return null;
}
}
@Nullable
public static SVNURL getRepositoryRoot(final SvnVcs vcs, final File file) {
final Info info = vcs.getInfo(file);
return info != null ? info.getRepositoryRootURL() : null;
}
@Nullable
public static SVNURL getRepositoryRoot(final SvnVcs vcs, final String url) {
try {
return getRepositoryRoot(vcs, createUrl(url));
}
catch (SvnBindException e) {
return null;
}
}
@Nullable
public static SVNURL getRepositoryRoot(final SvnVcs vcs, final SVNURL url) throws SvnBindException {
Info info = vcs.getInfo(url, SVNRevision.HEAD);
return (info == null) ? null : info.getRepositoryRootURL();
}
public static boolean isWorkingCopyRoot(final File file) {
return FileUtil.filesEqual(file, getWorkingCopyRootNew(file));
}
@Nullable
public static File getWorkingCopyRoot(final File inFile) {
File file = inFile;
while ((file != null) && (file.isFile() || (! file.exists()))) {
file = file.getParentFile();
}
if (file == null) {
return null;
}
File workingCopyRoot = null;
try {
workingCopyRoot = SVNWCUtil.getWorkingCopyRoot(file, true);
} catch (SVNException e) {
//
}
if (workingCopyRoot == null) {
workingCopyRoot = getWcCopyRootIf17(file, null);
}
return workingCopyRoot;
}
public static File fileFromUrl(final File baseDir, final String baseUrl, final String fullUrl) throws SVNException {
assert fullUrl.startsWith(baseUrl);
final String part = fullUrl.substring(baseUrl.length()).replace('/', File.separatorChar).replace('\\', File.separatorChar);
return new File(baseDir, part);
}
public static VirtualFile getVirtualFile(final String filePath) {
@NonNls final String path = VfsUtilCore.pathToUrl(filePath.replace(File.separatorChar, '/'));
return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
@Nullable
public VirtualFile compute() {
return VirtualFileManager.getInstance().findFileByUrl(path);
}
});
}
@Nullable
public static SVNURL getBranchForUrl(final SvnVcs vcs, final VirtualFile vcsRoot, final String urlPath) {
final SvnBranchConfigurationNew configuration;
try {
final SVNURL url = SVNURL.parseURIEncoded(urlPath);
configuration = SvnBranchConfigurationManager.getInstance(vcs.getProject()).get(vcsRoot);
return (configuration == null) ? null : configuration.getWorkingBranch(url);
}
catch (SVNException e) {
return null;
} catch (VcsException e1) {
return null;
}
}
@Nullable
public static VirtualFile correctRoot(final Project project, final VirtualFile file) {
if (file.getPath().length() == 0) {
// project root
return project.getBaseDir();
}
return file;
}
public static boolean checkRepositoryVersion15(@NotNull SvnVcs vcs, @NotNull String url) {
// Merge info tracking is supported in repositories since svn 1.5 (June 2008) - see http://subversion.apache.org/docs/release-notes/.
// But still some users use 1.4 repositories and currently we need to know if repository supports merge info for some code flows.
boolean result = false;
try {
result = vcs.getFactory().createRepositoryFeaturesClient().supportsMergeTracking(createUrl(url));
}
catch (VcsException e) {
LOG.info(e);
// TODO: Exception is thrown when url just not exist (was deleted, for instance) => and false is returned which seems not to be correct.
}
return result;
}
@Nullable
public static Status getStatus(@NotNull final SvnVcs vcs, @NotNull final File file) {
try {
return vcs.getFactory(file).createStatusClient().doStatus(file, false);
}
catch (SvnBindException e) {
return null;
}
}
public static Depth getDepth(final SvnVcs vcs, final File file) {
Info info = vcs.getInfo(file);
return info != null && info.getDepth() != null ? info.getDepth() : Depth.UNKNOWN;
}
public static boolean seemsLikeVersionedDir(final VirtualFile file) {
final String adminName = SVNFileUtil.getAdminDirectoryName();
final VirtualFile child = file.findChild(adminName);
return child != null && child.isDirectory();
}
public static boolean isAdminDirectory(final VirtualFile file) {
return isAdminDirectory(file.getParent(), file.getName());
}
public static boolean isAdminDirectory(VirtualFile parent, String name) {
// never allow to delete admin directories by themselves (this can happen during VCS undo,
// which deletes created directories from bottom to top)
if (name.equals(SVN_ADMIN_DIR_NAME)) {
return true;
}
if (parent != null) {
if (parent.getName().equals(SVN_ADMIN_DIR_NAME)) {
return true;
}
parent = parent.getParent();
if (parent != null && parent.getName().equals(SVN_ADMIN_DIR_NAME)) {
return true;
}
}
return false;
}
@Nullable
public static SVNURL getUrl(final SvnVcs vcs, final File file) {
// todo for moved items?
final Info info = vcs.getInfo(file);
return info == null ? null : info.getURL();
}
public static boolean remoteFolderIsEmpty(final SvnVcs vcs, final String url) throws VcsException {
SvnTarget target = SvnTarget.fromURL(createUrl(url));
final Ref<Boolean> result = new Ref<Boolean>(true);
DirectoryEntryConsumer handler = new DirectoryEntryConsumer() {
@Override
public void consume(final DirectoryEntry entry) throws SVNException {
if (entry != null) {
result.set(false);
}
}
};
vcs.getFactory(target).createBrowseClient().list(target, null, Depth.IMMEDIATES, handler);
return result.get();
}
public static File getWcDb(final File file) {
return new File(file, SVN_ADMIN_DIR_NAME + "/wc.db");
}
@Nullable
public static File getWcCopyRootIf17(final File file, @Nullable final File upperBound) {
File current = getParentWithDb(file);
if (current == null) return null;
while (current != null) {
try {
final SvnWcGeneration svnWcGeneration = SvnOperationFactory.detectWcGeneration(current, false);
if (SvnWcGeneration.V17.equals(svnWcGeneration)) return current;
if (SvnWcGeneration.V16.equals(svnWcGeneration)) return null;
if (upperBound != null && FileUtil.filesEqual(upperBound, current)) return null;
current = current.getParentFile();
}
catch (SVNException e) {
return null;
}
}
return null;
}
/**
* Utility method that deals also with 1.8 working copies.
* TODO: Should be renamed when all parts updated for 1.8.
*
* @param file
* @return
*/
@Nullable
public static File getWorkingCopyRootNew(final File file) {
File current = getParentWithDb(file);
if (current == null) return getWorkingCopyRoot(file);
WorkingCopyFormat format = getFormat(current);
return format.isOrGreater(WorkingCopyFormat.ONE_DOT_SEVEN) ? current : getWorkingCopyRoot(file);
}
private static File getParentWithDb(File file) {
File current = file;
boolean wcDbFound = false;
while (current != null) {
File wcDb;
if ((wcDb = getWcDb(current)).exists() && ! wcDb.isDirectory()) {
wcDbFound = true;
break;
}
current = current.getParentFile();
}
if (! wcDbFound) return null;
return current;
}
public static String getRelativeUrl(@NotNull String parentUrl, @NotNull String childUrl) {
return FileUtilRt.getRelativePath(parentUrl, childUrl, '/', true);
}
public static String getRelativePath(@NotNull String parentPath, @NotNull String childPath) {
return FileUtilRt.getRelativePath(FileUtil.toSystemIndependentName(parentPath), FileUtil.toSystemIndependentName(childPath), '/');
}
public static String ensureStartSlash(@NotNull String path) {
return StringUtil.startsWithChar(path, '/') ? path : '/' + path;
}
@NotNull
public static String join(@NotNull final String... parts) {
return StringUtil.join(parts, "/");
}
public static String appendMultiParts(@NotNull final String base, @NotNull final String subPath) {
if (StringUtil.isEmpty(subPath)) return base;
final List<String> parts = StringUtil.split(subPath.replace('\\', '/'), "/", true);
String result = base;
for (String part : parts) {
result = SVNPathUtil.append(result, part);
}
return result;
}
public static SVNURL appendMultiParts(@NotNull final SVNURL base, @NotNull final String subPath) throws SVNException {
if (StringUtil.isEmpty(subPath)) return base;
final List<String> parts = StringUtil.split(subPath.replace('\\', '/'), "/", true);
SVNURL result = base;
for (String part : parts) {
result = result.appendPath(part, false);
}
return result;
}
@NotNull
public static SVNURL removePathTail(@NotNull SVNURL url) throws SvnBindException {
return createUrl(SVNPathUtil.removeTail(url.toDecodedString()));
}
@NotNull
public static SVNRevision getHeadRevision(@NotNull SvnVcs vcs, @NotNull SVNURL url) throws SvnBindException {
Info info = vcs.getInfo(url, SVNRevision.HEAD);
if (info == null) {
throw new SvnBindException("Could not get info for " + url);
}
if (info.getRevision() == null) {
throw new SvnBindException("Could not get revision for " + url);
}
return info.getRevision();
}
public static byte[] getFileContents(@NotNull final SvnVcs vcs,
@NotNull final SvnTarget target,
@Nullable final SVNRevision revision,
@Nullable final SVNRevision pegRevision)
throws VcsException {
return vcs.getFactory(target).createContentClient().getContent(target, revision, pegRevision);
}
public static boolean hasDefaultPort(@NotNull SVNURL result) {
return !result.hasPort() || SVNURL.getDefaultPortNumber(result.getProtocol()) == result.getPort();
}
/**
* When creating SVNURL with default port, some negative value should be specified as port number, otherwise specified port value (even
* if equals to default) will occur in toString() result.
*/
public static int resolvePort(@NotNull SVNURL url) {
return !hasDefaultPort(url) ? url.getPort() : DEFAULT_PORT_INDICATOR;
}
@NotNull
public static SVNURL createUrl(@NotNull String url) throws SvnBindException {
try {
SVNURL result = SVNURL.parseURIEncoded(url);
// explicitly check if port corresponds to default port and recreate url specifying default port indicator
if (result.hasPort() && hasDefaultPort(result)) {
result = SVNURL
.create(result.getProtocol(), result.getUserInfo(), result.getHost(), DEFAULT_PORT_INDICATOR, result.getURIEncodedPath(), true);
}
return result;
}
catch (SVNException e) {
throw new SvnBindException(e);
}
}
public static SVNURL parseUrl(@NotNull String url) {
try {
return SVNURL.parseURIEncoded(url);
}
catch (SVNException e) {
throw createIllegalArgument(e);
}
}
public static SVNURL append(@NotNull SVNURL parent, String child) {
try {
return parent.appendPath(child, false);
}
catch (SVNException e) {
throw createIllegalArgument(e);
}
}
public static IllegalArgumentException createIllegalArgument(SVNException e) {
IllegalArgumentException runtimeException = new IllegalArgumentException();
runtimeException.initCause(e);
return runtimeException;
}
@Nullable
public static String getChangelistName(@NotNull final Status status) {
// no explicit check on working copy format supports change lists as they are supported from svn 1.5
// and anyway status.getChangelistName() should just return null if change lists are not supported.
return status.getKind().isFile() ? status.getChangelistName() : null;
}
public static boolean isUnversionedOrNotFound(@NotNull SvnBindException e) {
return e.contains(SVNErrorCode.WC_PATH_NOT_FOUND) ||
e.contains(SVNErrorCode.UNVERSIONED_RESOURCE) ||
e.contains(SVNErrorCode.WC_NOT_WORKING_COPY) ||
// thrown when getting info from repository for non-existent item - like HEAD revision for deleted file
e.contains(SVNErrorCode.ILLEGAL_TARGET) ||
// for svn 1.6
StringUtil.containsIgnoreCase(e.getMessage(), "(not a versioned resource)");
}
// TODO: Create custom Target class and implement append there
@NotNull
public static SvnTarget append(@NotNull SvnTarget target, @NotNull String path) throws SvnBindException {
return append(target, path, false);
}
@NotNull
public static SvnTarget append(@NotNull SvnTarget target, @NotNull String path, boolean checkAbsolute) throws SvnBindException {
SvnTarget result;
if (target.isFile()) {
result = SvnTarget.fromFile(resolvePath(target.getFile(), path));
}
else {
try {
result = SvnTarget
.fromURL(checkAbsolute && URI.create(path).isAbsolute() ? SVNURL.parseURIEncoded(path) : target.getURL().appendPath(path, false));
}
catch (SVNException e) {
throw new SvnBindException(e);
}
}
return result;
}
@NotNull
public static File resolvePath(@NotNull File base, @NotNull String path) {
File result = new File(path);
if (!result.isAbsolute()) {
result = ".".equals(path) ? base : new File(base, path);
}
return result;
}
/**
* {@code SvnTarget.getPathOrUrlDecodedString} does not correctly work for URL targets - {@code SVNURL.toString} instead of
* {@code SVNURL.toDecodedString} is used.
* <p/>
* Current utility method fixes this case.
*/
@NotNull
public static String toDecodedString(@NotNull SvnTarget target) {
return target.isFile() ? target.getFile().getPath() : target.getURL().toDecodedString();
}
private static class WorkingCopyFormatOperation implements FileUtilRt.RepeatableIOOperation<WorkingCopyFormat, RuntimeException> {
@NotNull private final File myDbFile;
public WorkingCopyFormatOperation(@NotNull File dbFile) {
myDbFile = dbFile;
}
@Nullable
@Override
public WorkingCopyFormat execute(boolean lastAttempt) {
// TODO: rewrite it using sqlite jdbc driver
SqlJetDb db = null;
WorkingCopyFormat result = null;
try {
// "write" access is requested here for now as workaround - see some details
// in https://code.google.com/p/sqljet/issues/detail?id=25 and http://issues.tmatesoft.com/issue/SVNKIT-418.
// BUSY error is currently handled same way as others.
db = SqlJetDb.open(myDbFile, true);
result = WorkingCopyFormat.getInstance(db.getOptions().getUserVersion());
}
catch (NonWritableChannelException e) {
// Such exceptions could be thrown when db is opened in "read" mode, but the db file is readonly (for instance, locked
// by other process). See links above for some details.
// handle this exception type separately - not to break execution flow
LOG.info(e);
}
catch (SqlJetException e) {
LOG.info(e);
}
finally {
close(db);
}
return result;
}
}
}