org.netbeans.modules.subversion.util.SvnUtils Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.subversion.util;
import java.awt.Color;
import java.awt.EventQueue;
import java.net.MalformedURLException;
import org.netbeans.modules.subversion.client.SvnClient;
import org.openide.nodes.Node;
import org.openide.windows.TopComponent;
import org.openide.util.Lookup;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.FileObject;
import org.netbeans.api.project.*;
import org.netbeans.api.queries.SharabilityQuery;
import org.netbeans.modules.subversion.FileStatusCache;
import org.netbeans.modules.subversion.Subversion;
import org.netbeans.modules.subversion.FileInformation;
import java.io.*;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.*;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.modules.subversion.*;
import org.netbeans.modules.subversion.client.PropertiesClient;
import org.netbeans.modules.subversion.client.SvnClientExceptionHandler;
import org.netbeans.modules.subversion.client.SvnProgressSupport;
import org.netbeans.modules.subversion.options.AnnotationExpression;
import org.netbeans.modules.subversion.ui.commit.CommitOptions;
import org.netbeans.modules.subversion.ui.diff.Setup;
import org.netbeans.modules.subversion.ui.history.SearchHistoryAction;
import org.netbeans.modules.versioning.spi.VCSContext;
import org.netbeans.modules.versioning.spi.VersioningSupport;
import org.netbeans.modules.versioning.util.FileSelector;
import org.netbeans.modules.versioning.util.IndexingBridge;
import org.netbeans.modules.versioning.util.ProjectUtilities;
import org.netbeans.modules.versioning.util.Utils;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.SaveCookie;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.tigris.subversion.svnclientadapter.*;
import org.tigris.subversion.svnclientadapter.utils.SVNUrlUtils;
/**
* Subversion-specific utilities.
*
* @author Maros Sandor
*/
public class SvnUtils {
public static final String SVN_ADMIN_DIR;
public static final String SVN_ENTRIES_DIR;
public static final String SVN_WC_DB;
private static final Pattern metadataPattern;
public static final HashSet autoEscapedCharacters = new HashSet(6);
static {
autoEscapedCharacters.add(';');
autoEscapedCharacters.add('?');
autoEscapedCharacters.add('#');
autoEscapedCharacters.add('%');
autoEscapedCharacters.add('[');
autoEscapedCharacters.add(']');
autoEscapedCharacters.add(' ');
}
static {
if (Utilities.isWindows()) {
String env = System.getenv("SVN_ASP_DOT_NET_HACK");
if (env != null) {
SVN_ADMIN_DIR = "_svn";
} else {
SVN_ADMIN_DIR = ".svn";
}
} else {
SVN_ADMIN_DIR = ".svn";
}
SVN_ENTRIES_DIR = SVN_ADMIN_DIR + "/entries";
SVN_WC_DB = SVN_ADMIN_DIR + "/wc.db";
metadataPattern = Pattern.compile(".*\\" + File.separatorChar + SVN_ADMIN_DIR.replace(".", "\\.") + "(\\" + File.separatorChar + ".*|$)");
}
private static Reference contextCached = new WeakReference(null);
private static Reference contextNodesCached = new WeakReference(null);
private static final FileFilter svnFileFilter = new FileFilter() {
@Override
public boolean accept(File pathname) {
if (isAdministrative(pathname)) return false;
return SharabilityQuery.getSharability(pathname) != SharabilityQuery.NOT_SHARABLE;
}
};
/**
* Creates annotation format string.
* @param format format specified by the user, e.g. [{status}]
* @return modified format, e.g. [{0}]
*/
public static String createAnnotationFormat(final String format) {
String string = format;
string = Utils.skipUnsupportedVariables(string, new String[]{"{status}", "{lock}", "{folder}", "{revision}", "{mime_type}", //NOI18N
"{commit_revision}", "{date}", "{author}" }); //NOI18N
string = string.replaceAll("\\{revision\\}", "\\{0\\}"); // NOI18N
string = string.replaceAll("\\{status\\}", "\\{1\\}"); // NOI18N
string = string.replaceAll("\\{folder\\}", "\\{2\\}"); // NOI18N
string = string.replaceAll("\\{lock\\}", "\\{3\\}"); // NOI18N
string = string.replaceAll("\\{mime_type\\}", "\\{4\\}"); // NOI18N
string = string.replaceAll("\\{commit_revision\\}", "\\{5\\}"); // NOI18N
string = string.replaceAll("\\{date\\}", "\\{6\\}"); // NOI18N
string = string.replaceAll("\\{author\\}", "\\{7\\}"); // NOI18N
return string;
}
/**
* Decodes and encodes given URL (e.g. xxx[] -> xxx%5B%5D)
* @param url url to be encoded
* @return encoded URL
* @throws java.net.MalformedURLException
*/
public static SVNUrl decodeAndEncodeUrl(SVNUrl url) throws MalformedURLException {
return encodeUrl(decodeToString(url));
}
/**
* Encodes the SVN url to an acceptable format. It encodes all non-standard characters to a '%XX' form.
* Unescaped characters: '/', ':', '@' (peg revision).
* @param url url to be encoded
* @return encoded URL
* @throws java.net.MalformedURLException encoded URL is of a bad format anyway
*/
private static SVNUrl encodeUrl(String url) throws MalformedURLException {
url = url.replace("%20", " "); //NOI18N
StringBuilder sb = new StringBuilder(url.length());
for (int i = 0; i < url.length(); ++i) {
Character c = url.charAt(i);
if (autoEscapedCharacters.contains(c)) {
char[] chars = Character.toChars(c);
for (int j = 0; j < chars.length; ++j) {
sb.append('%');
sb.append(Integer.toHexString(chars[j]).toUpperCase());
}
} else {
sb.append(c);
}
}
return new SVNUrl(sb.toString());
}
/**
* Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
* method returns File objects instead od Nodes. Every node is examined for Files it represents. File and Folder
* nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
* logical nodes must provide FileObjects in their Lookup.
*
* @return File [] array of activated files
* @param nodes or null (then taken from windowsystem, it may be wrong on editor tabs #66700).
*/
public static Context getCurrentContext(Node[] nodes) {
if (nodes == null) {
nodes = TopComponent.getRegistry().getActivatedNodes();
}
if (Arrays.equals(contextNodesCached.get(), nodes)) {
Context ctx = contextCached.get();
if (ctx != null) return ctx;
}
VCSContext vcsCtx = VCSContext.forNodes(nodes);
Context ctx = new Context(new ArrayList(vcsCtx.computeFiles(svnFileFilter)), new ArrayList(vcsCtx.getRootFiles()), new ArrayList(vcsCtx.getExclusions()));
contextCached = new WeakReference(ctx);
contextNodesCached = new WeakReference(nodes);
return ctx;
}
/**
* Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
* method returns File objects instead od Nodes. Every node is examined for Files it represents. File and Folder
* nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
* logical nodes must provide FileObjects in their Lookup.
*
* @param nodes null (then taken from windowsystem, it may be wrong on editor tabs #66700).
* @param includingFileStatus if any activated file does not have this CVS status, an empty array is returned
* @param includingFolderStatus if any activated folder does not have this CVS status, an empty array is returned
* @param fromCache if set to true
reads status from cache otherwise uses I/O operations.
* @return File [] array of activated files, or an empty array if any of examined files/folders does not have given status
*/
public static Context getCurrentContext(Node[] nodes, int includingFileStatus, int includingFolderStatus, boolean fromCache) {
Context context = getCurrentContext(nodes);
FileStatusCache cache = Subversion.getInstance().getStatusCache();
File [] files = context.getRootFiles();
for (int i = 0; i < files.length; i++) {
File file = files[i];
FileInformation fi = fromCache ? cache.getCachedStatus(file) : cache.getStatus(file);
int status;
Boolean isDirectory = null;
if (fi != null) {
// status got from the cache and is known or got through I/O
status = fi.getStatus();
isDirectory = fi.isDirectory();
} else {
// tried to get the cached value but it was not present in the cache
status = FileInformation.STATUS_VERSIONED_UPTODATE;
}
if (Boolean.TRUE.equals(isDirectory) || (isDirectory == null && file.isDirectory())) {
if ((status & includingFolderStatus) == 0) return Context.Empty;
} else {
if ((status & includingFileStatus) == 0) return Context.Empty;
}
}
return context;
}
/**
* Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
* method returns File objects instead od Nodes. Every node is examined for Files it represents. File and Folder
* nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
* logical nodes must provide FileObjects in their Lookup.
* Does not read statuses from cache, but through I/O, might take a long time to finish.
*
* @param nodes null (then taken from windowsystem, it may be wrong on editor tabs #66700).
* @param includingFileStatus if any activated file does not have this CVS status, an empty array is returned
* @param includingFolderStatus if any activated folder does not have this CVS status, an empty array is returned
* @return File [] array of activated files, or an empty array if any of examined files/folders does not have given status
*/
public static Context getCurrentContext(Node[] nodes, int includingFileStatus, int includingFolderStatus) {
return getCurrentContext(nodes, includingFileStatus, includingFolderStatus, false);
}
/**
* Validates annotation format text
* @param format format to be validatet
* @return true
if the format is correct, false
otherwise.
*/
public static boolean isAnnotationFormatValid(final String format) {
boolean retval = true;
if (format != null) {
try {
new MessageFormat(format);
} catch (IllegalArgumentException ex) {
Subversion.LOG.log(Level.FINER, "Bad user input - annotation format", ex);
retval = false;
}
}
return retval;
}
/**
* @return true
if
*
* - the node contains a project in its lookup and
*
- the project contains at least one SVN versioned source group
*
* otherwise false
.
* @param project
* @param checkStatus if set to true, cache.getStatus is called and can take significant amount of time
*/
public static boolean isVersionedProject(Node node, boolean checkStatus) {
Lookup lookup = node.getLookup();
Project project = (Project) lookup.lookup(Project.class);
return isVersionedProject(project, checkStatus);
}
/**
* @return true
if
*
* - the project != null and
*
- the project contains at least one SVN versioned source group
*
* otherwise false
.
* @param project
* @param checkStatus if set to true, cache.getStatus is called and can take significant amount of time
*/
public static boolean isVersionedProject(Project project, boolean checkStatus) {
if (project != null) {
FileStatusCache cache = Subversion.getInstance().getStatusCache();
Sources sources = ProjectUtils.getSources(project);
SourceGroup [] sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC);
for (int j = 0; j < sourceGroups.length; j++) {
SourceGroup sourceGroup = sourceGroups[j];
File f = FileUtil.toFile(sourceGroup.getRootFolder());
if (f != null) {
if (checkStatus && (cache.getStatus(f).getStatus() & FileInformation.STATUS_MANAGED) != 0) {
return true;
} else if (!checkStatus && SvnUtils.isManaged(f)) {
return true;
}
}
}
}
return false;
}
/**
* Determines all files and folders that belong to a given project and adds them to the supplied Collection.
*
* @param filteredFiles destination collection of Files
* @param project project to examine
*/
public static void addProjectFiles(Collection filteredFiles, Collection rootFiles, Collection rootFilesExclusions, Project project) {
FileStatusCache cache = Subversion.getInstance().getStatusCache();
Sources sources = ProjectUtils.getSources(project);
SourceGroup [] sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC);
for (int j = 0; j < sourceGroups.length; j++) {
SourceGroup sourceGroup = sourceGroups[j];
FileObject srcRootFo = sourceGroup.getRootFolder();
File rootFile = FileUtil.toFile(srcRootFo);
if (rootFile == null || (cache.getStatus(rootFile).getStatus() & FileInformation.STATUS_MANAGED) == 0) continue;
rootFiles.add(rootFile);
boolean containsSubprojects = false;
FileObject [] rootChildren = srcRootFo.getChildren();
Set projectFiles = new HashSet(rootChildren.length);
for (int i = 0; i < rootChildren.length; i++) {
FileObject rootChildFo = rootChildren[i];
if (isAdministrative(rootChildFo.getNameExt())) continue;
File child = FileUtil.toFile(rootChildFo);
if (child == null) {
continue;
}
if (sourceGroup.contains(rootChildFo)) {
// TODO: #60516 deep scan is required here but not performed due to performace reasons
projectFiles.add(child);
} else {
int status = cache.getStatus(child).getStatus();
if (status != FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
rootFilesExclusions.add(child);
containsSubprojects = true;
}
}
}
if (containsSubprojects) {
filteredFiles.addAll(projectFiles);
} else {
filteredFiles.add(rootFile);
}
}
}
/**
* May take a long time for many projects, consider making the call from worker threads.
*
* @param projects projects to examine
* @return Context context that defines list of supplied projects
*/
public static Context getProjectsContext(Project [] projects) {
List filtered = new ArrayList();
List roots = new ArrayList();
List exclusions = new ArrayList();
for (int i = 0; i < projects.length; i++) {
addProjectFiles(filtered, roots, exclusions, projects[i]);
}
return new Context(filtered, roots, exclusions);
}
public static File [] toFileArray(Collection fileObjects) {
Set files = new HashSet(fileObjects.size()*4/3+1);
for (Iterator i = fileObjects.iterator(); i.hasNext();) {
File f = FileUtil.toFile(i.next());
if (f != null) {
files.add(f);
}
}
files.remove(null);
return files.toArray(new File[0]);
}
/**
* Tests parent/child relationship of files.
*
* @param parent file to be parent of the second parameter
* @param file file to be a child of the first parameter
* @return true if the second parameter represents the same file as the first parameter OR is its descendant (child)
*/
public static boolean isParentOrEqual(File parent, File file) {
for (; file != null; file = file.getParentFile()) {
if (file.equals(parent)) return true;
}
return false;
}
/**
* Evaluates if the given file is a svn administrative folder - [.svn|_svn]
* @param file
* @return true if the given file is a svn administrative folder, otherwise false
*/
public static boolean isAdministrative(File file) {
String name = file.getName();
boolean administrative = isAdministrative(name);
return ( administrative && !file.exists() ) ||
( administrative && file.exists() && file.isDirectory() ); // lets suppose it's administrative if file doesnt exist
}
/**
* Evaluates if the given fileName is a svn administrative folder name - [.svn|_svn]
* @param fileName
* @return true if the given fileName is a svn administrative folder name, otherwise false
*/
public static boolean isAdministrative(String fileName) {
return fileName.equals(SVN_ADMIN_DIR); // NOI18N
}
/**
* Tests whether a file or directory should receive the STATUS_NOTVERSIONED_NOTMANAGED status.
* All files and folders that have a parent with either .svn/entries or _svn/entries file are
* considered versioned.
*
* @param file a file or directory
* @return false if the file should receive the STATUS_NOTVERSIONED_NOTMANAGED status, true otherwise
*/
public static boolean isManaged(File file) {
return VersioningSupport.getOwner(file) instanceof SubversionVCS && !isPartOfSubversionMetadata(file);
}
/**
* Computes previous revision or null
* for initial.
*
* @param revision num.dot revision or null
*/
public static String previousRevision(String revision) {
return revision == null ? null : Long.toString(Long.parseLong(revision) - 1);
}
/**
* Compute relative path to repository root.
* For not yet versioned files guess the URL
* from parent context.
*
* I/O intensive avoid calling it frnm AWT.
*
* @return the repository url or null for unknown
*/
public static String getRelativePath(File file) throws SVNClientException {
String repositoryPath = null;
List path = new ArrayList();
SVNUrl repositoryURL = null;
boolean fileIsManaged = false;
File lastManaged = file;
SvnClient client = Subversion.getInstance().getClient(false);
while (isManaged(file)) {
fileIsManaged = true;
ISVNInfo info = null;
try {
info = getInfoFromWorkingCopy(client, file, true);
} catch (SVNClientException ex) {
if (SvnClientExceptionHandler.isUnversionedResource(ex.getMessage()) == false) {
if (WorkingCopyAttributesCache.getInstance().isSuppressed(ex)) {
// log this exception if needed and break the execution
WorkingCopyAttributesCache.getInstance().logSuppressed(ex, file);
} else {
SvnClientExceptionHandler.notifyException(ex, false, false);
}
}
}
if (info != null && info.getUrl() != null) {
String fileLink = decodeToString(info.getUrl());
repositoryURL = info.getRepository();
String repositoryLink = decodeToString(repositoryURL);
if (fileLink != null && repositoryLink != null) {
try {
repositoryPath = fileLink.substring(repositoryLink.length());
} catch (StringIndexOutOfBoundsException ex) {
// XXX delete try-catch after bugfix verification
Subversion.LOG.log(Level.INFO, "repoUrl: " + repositoryURL.toString() + "\nfileURL: " + fileLink, ex);
throw ex;
}
Iterator it = path.iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) {
String segment = (String) it.next();
sb.append("/"); // NOI18N
sb.append(segment);
}
repositoryPath += sb.toString();
break;
}
}
path.add(0, file.getName());
File parent = file.getParentFile();
lastManaged = file;
if (parent == null) {
// .svn in root folder
break;
} else {
file = parent;
}
}
if(repositoryURL == null && fileIsManaged) {
Subversion.LOG.log(Level.WARNING, "no repository url found for managed file {0}", new Object[] { lastManaged });
// The file is managed but we haven't found the repository URL in it's metadata -
// this looks like the WC was created with a client < 1.3.0. I wouldn't mind for myself and
// get the URL from the server, it's just that it could be quite a performance killer.
// XXX and now i'm just currious how we will handle this if there will be some javahl or
// pure java client suport -> without dispatching to our metadata parser
if (new File(lastManaged, SvnUtils.SVN_WC_DB).canRead()) {
throw new SVNClientException(NbBundle.getMessage(SvnUtils.class, "MSG_too_old_client", lastManaged));
} else {
throw new SVNClientException(NbBundle.getMessage(SvnUtils.class, "MSG_too_old_WC"));
}
} else if(!fileIsManaged) {
Subversion.LOG.log(Level.INFO, "no repository url found for not managed file {0}", new Object[] { lastManaged });
}
return repositoryPath;
}
/**
* Returns the repository root for the given file.
* For not yet versioned files guess the URL
* from parent context.
*
* I/O intensive avoid calling it frnm AWT.
*
* @return the repository url or null for unknown
*/
public static SVNUrl getRepositoryRootUrl(File file) throws SVNClientException {
SvnClient client = Subversion.getInstance().getClient(false);
SVNUrl repositoryURL = null;
boolean fileIsManaged = false;
File lastManaged = file;
SVNClientException e = null;
while (isManaged(file)) {
fileIsManaged = true;
ISVNInfo info = null;
try {
e = null;
info = getInfoFromWorkingCopy(client, file, true);
} catch (SVNClientException ex) {
if (SvnClientExceptionHandler.isUnversionedResource(ex.getMessage()) == false) {
if (WorkingCopyAttributesCache.getInstance().isSuppressed(ex)) {
// log this exception if needed and break the execution
WorkingCopyAttributesCache.getInstance().logSuppressed(ex, file);
} else {
SvnClientExceptionHandler.notifyException(ex, false, false);
e = ex;
}
}
}
if (info != null) {
repositoryURL = decode(info.getRepository());
if (repositoryURL != null) {
break;
}
}
File parent = file.getParentFile();
lastManaged = file;
if (parent == null) {
// .svn in root folder
break;
} else {
file = parent;
}
}
if(repositoryURL == null && fileIsManaged) {
if (e != null) {
throw e;
}
Subversion.LOG.log(Level.WARNING, "no repository url found for managed file {0}", new Object[] { lastManaged });
if (new File(lastManaged, SvnUtils.SVN_WC_DB).canRead()) {
throw new SVNClientException(NbBundle.getMessage(SvnUtils.class, "MSG_too_old_client", lastManaged));
} else {
throw new SVNClientException(NbBundle.getMessage(SvnUtils.class, "MSG_too_old_WC"));
}
} else if(!fileIsManaged) {
Subversion.LOG.log(Level.INFO, "no repository url found for not managed file {0}", new Object[] { lastManaged });
// XXX #168094 logging
Level oldLevel = Subversion.LOG.getLevel();
Subversion.LOG.setLevel(Level.FINE);
Subversion.LOG.log(Level.INFO, "getRepositoryRootUrl: file {0} {1}", new Object[] { lastManaged, VersioningSupport.getOwner(lastManaged) });
Subversion.LOG.setLevel(oldLevel);
if (!lastManaged.exists()) {
Subversion.LOG.log(Level.INFO, "getRepositoryRootUrl: file {0} does not exist", new Object[] { lastManaged });
}
}
return repositoryURL;
}
/**
* Returns the repository URL for the given file.
* For not yet versioned files guess the URL
* from parent context.
*
*
I/O intensive avoid calling it frnm AWT.
*
* @return the repository url or null for unknown
*/
public static SVNUrl getRepositoryUrl(File file) throws SVNClientException {
StringBuilder path = new StringBuilder();
SVNUrl fileURL = null;
SvnClient client = null;
try {
client = Subversion.getInstance().getClient(false);
} catch (SVNClientException ex) {
SvnClientExceptionHandler.notifyException(ex, false, false);
return null;
}
boolean fileIsManaged = false;
File lastManaged = file;
SVNClientException e = null;
while (isManaged(file)) {
fileIsManaged = true;
e = null;
try {
// it works with 1.3 workdirs and our .svn parser
ISVNStatus status = getSingleStatus(client, file);
if (status != null) {
fileURL = decode(status.getUrl());
if (fileURL != null) {
break;
}
}
} catch (SVNClientException ex) {
if (SvnClientExceptionHandler.isUnversionedResource(ex.getMessage()) == false) {
if (WorkingCopyAttributesCache.getInstance().isSuppressed(ex)) {
// log this exception if needed and break the execution
WorkingCopyAttributesCache.getInstance().logSuppressed(ex, file);
} else {
SvnClientExceptionHandler.notifyException(ex, false, false);
e = ex;
}
}
}
// slower fallback
ISVNInfo info = null;
try {
info = getInfoFromWorkingCopy(client, file, true);
} catch (SVNClientException ex) {
if (SvnClientExceptionHandler.isUnversionedResource(ex.getMessage()) == false) {
SvnClientExceptionHandler.notifyException(ex, false, false);
}
}
if (info != null) {
fileURL = decode(info.getUrl());
if (fileURL != null ) {
break;
}
}
path.insert(0, file.getName()).insert(0, "/");
File parent = file.getParentFile();
lastManaged = file;
if (parent == null) {
// .svn in root folder
break;
} else {
file = parent;
}
}
if(fileURL == null && fileIsManaged) {
if (e != null) {
throw e;
}
Subversion.LOG.log(Level.WARNING, "no repository url found for managed file {0}", new Object[] { lastManaged });
if (new File(lastManaged, SvnUtils.SVN_WC_DB).canRead()) {
throw new SVNClientException(NbBundle.getMessage(SvnUtils.class, "MSG_too_old_client", lastManaged));
} else {
throw new SVNClientException(NbBundle.getMessage(SvnUtils.class, "MSG_too_old_WC"));
}
} else if(!fileIsManaged) {
Subversion.LOG.log(Level.INFO, "no repository url found for not managed file {0}", new Object[] { lastManaged });
}
if (path.length() > 0) fileURL = fileURL.appendPath(path.toString());
return fileURL;
}
/**
* Returns repository urls for all versioned files underneath the given root
*
* @param root
* @return
* @throws SVNClientException
*/
public static Map getRepositoryUrls(File root) throws SVNClientException {
SVNUrl fileURL = null;
SvnClient client = null;
try {
client = Subversion.getInstance().getClient(false);
} catch (SVNClientException ex) {
SvnClientExceptionHandler.notifyException(ex, false, false);
return null;
}
Map ret = new HashMap();
try {
ISVNStatus[] statuses = client.getStatus(root, true, true);
for (ISVNStatus status : statuses) {
if (status != null) {
fileURL = decode(status.getUrl());
if (fileURL != null) {
ret.put(status.getFile(), fileURL);
}
}
}
} catch (SVNClientException ex) {
if (SvnClientExceptionHandler.isUnversionedResource(ex.getMessage()) == false) {
if (WorkingCopyAttributesCache.getInstance().isSuppressed(ex)) {
// log this exception if needed and break the execution
WorkingCopyAttributesCache.getInstance().logSuppressed(ex, root);
} else {
SvnClientExceptionHandler.notifyException(ex, false, false);
}
}
}
return ret;
}
public static ISVNStatus getSingleStatus(SvnClient client, File file) throws SVNClientException {
ISVNStatus status = null;
Map cache = statusCache.get();
if (cache != null) {
status = cache.get(file);
if (status != null) {
return status;
}
}
try {
status = client.getSingleStatus(file);
} catch (SVNClientException ex) {
if (SvnClientExceptionHandler.isUnversionedResource(ex.getMessage())) {
status = new SVNStatusUnversioned(file);
} else {
throw ex;
}
}
if (cache != null) {
cache.put(file, status);
}
return status;
}
/**
* Decodes svn URI by decoding %XX escape sequences.
*
* @param url url to decode
* @return decoded url
*/
public static SVNUrl decode(SVNUrl url) {
try {
String decoded = decodeToString(url);
return decoded == null ? null : new SVNUrl(decoded);
} catch (java.net.MalformedURLException e) {
throw new RuntimeException(e);
}
}
/**
* Decodes svn URI by decoding %XX escape sequences.
*
* @param url url to decode
* @return decoded url
*/
public static String decodeToString (SVNUrl url) {
if (url == null) return null;
return decodeToString(url.toString());
}
/**
* Decodes svn URI by decoding %XX escape sequences.
*
* @param url url to decode
* @return decoded url
*/
public static String decodeToString (String url) {
if (url == null) return null;
StringBuilder sb = new StringBuilder(url.length());
boolean inQuery = false;
for (int i = 0; i < url.length(); i++) {
char c = url.charAt(i);
if (c == '?') {
inQuery = true;
} else if (c == '+' && inQuery) {
sb.append(' ');
} else if (isEncodedByte(c, url, i)) {
List byteList = new ArrayList();
do {
byteList.add((byte) Integer.parseInt(url.substring(i + 1, i + 3), 16));
i += 3;
if (i >= url.length()) break;
c = url.charAt(i);
} while(isEncodedByte(c, url, i));
if(byteList.size() > 0) {
byte[] bytes = new byte[byteList.size()];
for(int ib = 0; ib < byteList.size(); ib++) {
bytes[ib] = byteList.get(ib);
}
try {
sb.append(new String(bytes, "UTF8"));
} catch (Exception e) {
Subversion.LOG.log(Level.INFO, null, e); // oops
}
i--;
}
} else {
sb.append(c);
}
}
return sb.toString();
}
private static boolean isEncodedByte(char c, String s, int i) {
return c == '%' && i + 2 < s.length() && isHexDigit(s.charAt(i + 1)) && isHexDigit(s.charAt(i + 2));
}
private static boolean isHexDigit(char c) {
return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
}
/*
* Determines a versioned file's repository path
*
* @param file versioned file
* @return file's path in repository
*/
public static String getRepositoryPath(File file) throws SVNClientException {
SVNUrl url = getRepositoryUrl(file);
SVNUrl rootUrl = getRepositoryRootUrl(file);
return decodeToString(SVNUrlUtils.getRelativePath(rootUrl, url, true));
}
/**
* @return true if the buffer is almost certainly binary.
* Note: Non-ASCII based encoding encoded text is binary,
* newlines cannot be reliably detected.
*/
public static boolean isBinary(byte[] buffer) {
for (int i = 0; i> infoCache = new ThreadLocal