0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.subversion.util;
0043:
0044: import org.netbeans.modules.subversion.client.SvnClient;
0045: import org.openide.*;
0046: import org.openide.nodes.Node;
0047: import org.openide.windows.TopComponent;
0048: import org.openide.util.Lookup;
0049: import org.openide.filesystems.FileUtil;
0050: import org.openide.filesystems.FileObject;
0051: import org.netbeans.api.project.*;
0052: import org.netbeans.api.queries.SharabilityQuery;
0053: import org.netbeans.modules.subversion.FileStatusCache;
0054: import org.netbeans.modules.subversion.Subversion;
0055: import org.netbeans.modules.subversion.FileInformation;
0056: import java.io.*;
0057: import java.util.*;
0058: import java.text.ParseException;
0059: import java.util.logging.Level;
0060: import java.util.regex.Pattern;
0061: import java.util.regex.PatternSyntaxException;
0062: import org.netbeans.modules.subversion.SvnModuleConfig;
0063: import org.netbeans.modules.subversion.client.PropertiesClient;
0064: import org.netbeans.modules.subversion.client.SvnClientExceptionHandler;
0065: import org.netbeans.modules.subversion.options.AnnotationExpression;
0066: import org.netbeans.modules.versioning.spi.VCSContext;
0067: import org.netbeans.modules.versioning.util.Utils;
0068: import org.openide.util.NbBundle;
0069: import org.tigris.subversion.svnclientadapter.*;
0070: import org.tigris.subversion.svnclientadapter.utils.SVNUrlUtils;
0071:
0072: /**
0073: * Subversion-specific utilities.
0074: * TODO: PETR Move generic methods to versioncontrol module
0075: *
0076: * @author Maros Sandor
0077: */
0078: public class SvnUtils {
0079:
0080: private static final Pattern metadataPattern = Pattern
0081: .compile(".*\\" + File.separatorChar + "(\\.|_)svn(\\"
0082: + File.separatorChar + ".*|$)");
0083:
0084: private static final FileFilter svnFileFilter = new FileFilter() {
0085: public boolean accept(File pathname) {
0086: if (Subversion.getInstance().isAdministrative(pathname))
0087: return false;
0088: return SharabilityQuery.getSharability(pathname) != SharabilityQuery.NOT_SHARABLE;
0089: }
0090: };
0091:
0092: /**
0093: * Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
0094: * method returns File objects instead od Nodes. Every node is examined for Files it represents. File and Folder
0095: * nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
0096: * logical nodes must provide FileObjects in their Lookup.
0097: *
0098: * @return File [] array of activated files
0099: * @param nodes or null (then taken from windowsystem, it may be wrong on editor tabs #66700).
0100: */
0101: public static Context getCurrentContext(Node[] nodes) {
0102: if (nodes == null) {
0103: nodes = TopComponent.getRegistry().getActivatedNodes();
0104: }
0105: VCSContext ctx = VCSContext.forNodes(nodes);
0106: return new Context(new ArrayList(ctx
0107: .computeFiles(svnFileFilter)), new ArrayList(ctx
0108: .getRootFiles()), new ArrayList(ctx.getExclusions()));
0109: }
0110:
0111: /**
0112: * Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
0113: * method returns File objects instead od Nodes. Every node is examined for Files it represents. File and Folder
0114: * nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
0115: * logical nodes must provide FileObjects in their Lookup.
0116: *
0117: * @param nodes null (then taken from windowsystem, it may be wrong on editor tabs #66700).
0118: * @param includingFileStatus if any activated file does not have this CVS status, an empty array is returned
0119: * @param includingFolderStatus if any activated folder does not have this CVS status, an empty array is returned
0120: * @return File [] array of activated files, or an empty array if any of examined files/folders does not have given status
0121: */
0122: public static Context getCurrentContext(Node[] nodes,
0123: int includingFileStatus, int includingFolderStatus) {
0124: Context context = getCurrentContext(nodes);
0125: FileStatusCache cache = Subversion.getInstance()
0126: .getStatusCache();
0127: File[] files = context.getRootFiles();
0128: for (int i = 0; i < files.length; i++) {
0129: File file = files[i];
0130: FileInformation fi = cache.getStatus(file);
0131: if (file.isDirectory()) {
0132: if ((fi.getStatus() & includingFolderStatus) == 0)
0133: return Context.Empty;
0134: } else {
0135: if ((fi.getStatus() & includingFileStatus) == 0)
0136: return Context.Empty;
0137: }
0138: }
0139: return context;
0140: }
0141:
0142: /**
0143: * @return <code>true</code> if
0144: * <ul>
0145: * <li> the node contains a project in its lookup and
0146: * <li> the project contains at least one CVS versioned source group
0147: * </ul>
0148: * otherwise <code>false</code>.
0149: */
0150: public static boolean isVersionedProject(Node node) {
0151: Lookup lookup = node.getLookup();
0152: Project project = (Project) lookup.lookup(Project.class);
0153: return isVersionedProject(project);
0154: }
0155:
0156: /**
0157: * @return <code>true</code> if
0158: * <ul>
0159: * <li> the project != null and
0160: * <li> the project contains at least one CVS versioned source group
0161: * </ul>
0162: * otherwise <code>false</code>.
0163: */
0164: public static boolean isVersionedProject(Project project) {
0165: if (project != null) {
0166: FileStatusCache cache = Subversion.getInstance()
0167: .getStatusCache();
0168: Sources sources = ProjectUtils.getSources(project);
0169: SourceGroup[] sourceGroups = sources
0170: .getSourceGroups(Sources.TYPE_GENERIC);
0171: for (int j = 0; j < sourceGroups.length; j++) {
0172: SourceGroup sourceGroup = sourceGroups[j];
0173: File f = FileUtil.toFile(sourceGroup.getRootFolder());
0174: //if (f != null) { XXX fallback if experimntal should not work
0175: // File probe = new File (f, ".svn");
0176: // File probe2 = new File (f, "_svn");
0177: // if (probe.isDirectory() || probe2.isDirectory()) {
0178: // return true;
0179: // }
0180: // }
0181: if ((cache.getStatus(f).getStatus() & FileInformation.STATUS_MANAGED) != 0)
0182: return true; // XXX experimental
0183: }
0184: }
0185: return false;
0186: }
0187:
0188: /**
0189: * Determines all files and folders that belong to a given project and adds them to the supplied Collection.
0190: *
0191: * @param filteredFiles destination collection of Files
0192: * @param project project to examine
0193: */
0194: public static void addProjectFiles(Collection<File> filteredFiles,
0195: Collection<File> rootFiles,
0196: Collection<File> rootFilesExclusions, Project project) {
0197: FileStatusCache cache = Subversion.getInstance()
0198: .getStatusCache();
0199: Sources sources = ProjectUtils.getSources(project);
0200: SourceGroup[] sourceGroups = sources
0201: .getSourceGroups(Sources.TYPE_GENERIC);
0202: for (int j = 0; j < sourceGroups.length; j++) {
0203: SourceGroup sourceGroup = sourceGroups[j];
0204: FileObject srcRootFo = sourceGroup.getRootFolder();
0205: File rootFile = FileUtil.toFile(srcRootFo);
0206: if ((cache.getStatus(rootFile).getStatus() & FileInformation.STATUS_MANAGED) == 0)
0207: continue;
0208: rootFiles.add(rootFile);
0209: boolean containsSubprojects = false;
0210: FileObject[] rootChildren = srcRootFo.getChildren();
0211: Set<File> projectFiles = new HashSet<File>(
0212: rootChildren.length);
0213: for (int i = 0; i < rootChildren.length; i++) {
0214: FileObject rootChildFo = rootChildren[i];
0215: if (Subversion.getInstance().isAdministrative(
0216: rootChildFo.getNameExt()))
0217: continue;
0218: File child = FileUtil.toFile(rootChildFo);
0219: if (sourceGroup.contains(rootChildFo)) {
0220: // TODO: #60516 deep scan is required here but not performed due to performace reasons
0221: projectFiles.add(child);
0222: } else {
0223: int status = cache.getStatus(child).getStatus();
0224: if (status != FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
0225: rootFilesExclusions.add(child);
0226: containsSubprojects = true;
0227: }
0228: }
0229: }
0230: if (containsSubprojects) {
0231: filteredFiles.addAll(projectFiles);
0232: } else {
0233: filteredFiles.add(rootFile);
0234: }
0235: }
0236: }
0237:
0238: /**
0239: * May take a long time for many projects, consider making the call from worker threads.
0240: *
0241: * @param projects projects to examine
0242: * @return Context context that defines list of supplied projects
0243: */
0244: public static Context getProjectsContext(Project[] projects) {
0245: List<File> filtered = new ArrayList<File>();
0246: List<File> roots = new ArrayList<File>();
0247: List<File> exclusions = new ArrayList<File>();
0248: for (int i = 0; i < projects.length; i++) {
0249: addProjectFiles(filtered, roots, exclusions, projects[i]);
0250: }
0251: return new Context(filtered, roots, exclusions);
0252: }
0253:
0254: public static File[] toFileArray(Collection<FileObject> fileObjects) {
0255: Set<File> files = new HashSet<File>(
0256: fileObjects.size() * 4 / 3 + 1);
0257: for (Iterator<FileObject> i = fileObjects.iterator(); i
0258: .hasNext();) {
0259: files.add(FileUtil.toFile(i.next()));
0260: }
0261: files.remove(null);
0262: return files.toArray(new File[files.size()]);
0263: }
0264:
0265: /**
0266: * Tests parent/child relationship of files.
0267: *
0268: * @param parent file to be parent of the second parameter
0269: * @param file file to be a child of the first parameter
0270: * @return true if the second parameter represents the same file as the first parameter OR is its descendant (child)
0271: */
0272: public static boolean isParentOrEqual(File parent, File file) {
0273: for (; file != null; file = file.getParentFile()) {
0274: if (file.equals(parent))
0275: return true;
0276: }
0277: return false;
0278: }
0279:
0280: /**
0281: * Computes previous revision or <code>null</code>
0282: * for initial.
0283: *
0284: * @param revision num.dot revision or <code>null</code>
0285: */
0286: public static String previousRevision(String revision) {
0287: return revision == null ? null : Long.toString(Long
0288: .parseLong(revision) - 1);
0289: }
0290:
0291: /**
0292: * Compute relative path to repository root.
0293: * For not yet versioned files guess the URL
0294: * from parent context.
0295: *
0296: * <p>I/O intensive avoid calling it frnm AWT.
0297: *
0298: * @return the repository url or null for unknown
0299: */
0300: public static String getRelativePath(File file)
0301: throws SVNClientException {
0302: String repositoryPath = null;
0303:
0304: List<String> path = new ArrayList<String>();
0305: SVNUrl repositoryURL = null;
0306: boolean fileIsManaged = false;
0307: while (Subversion.getInstance().isManaged(file)) {
0308: fileIsManaged = true;
0309:
0310: ISVNInfo info = null;
0311: try {
0312: SvnClient client = Subversion.getInstance().getClient(
0313: false);
0314: info = client.getInfoFromWorkingCopy(file);
0315: } catch (SVNClientException ex) {
0316: if (SvnClientExceptionHandler.isUnversionedResource(ex
0317: .getMessage()) == false) {
0318: SvnClientExceptionHandler.notifyException(ex,
0319: false, false);
0320: }
0321: }
0322:
0323: if (info != null && info.getUrl() != null) {
0324: SVNUrl fileURL = decode(info.getUrl());
0325: repositoryURL = info.getRepository();
0326:
0327: if (fileURL != null && repositoryURL != null) {
0328: String fileLink = fileURL.toString();
0329: String repositoryLink = repositoryURL.toString();
0330: repositoryPath = fileLink.substring(repositoryLink
0331: .length());
0332:
0333: Iterator it = path.iterator();
0334: StringBuffer sb = new StringBuffer();
0335: while (it.hasNext()) {
0336: String segment = (String) it.next();
0337: sb.append("/"); // NOI18N
0338: sb.append(segment);
0339: }
0340: repositoryPath += sb.toString();
0341: break;
0342: }
0343: }
0344:
0345: path.add(0, file.getName());
0346: file = file.getParentFile();
0347:
0348: }
0349: if (repositoryURL == null & fileIsManaged) {
0350: // The file is managed but we haven't found the repository URL in it's metadata -
0351: // this looks like the WC was created with a client < 1.3.0. I wouldn't mind for myself and
0352: // get the URL from the server, it's just that it could be quite a performance killer.
0353: // XXX and now i'm just currious how we will handle this if there will be some javahl or
0354: // pure java client suport -> without dispatching to our metadata parser
0355: throw new SVNClientException(NbBundle.getMessage(
0356: SvnUtils.class, "MSG_too_old_WC"));
0357: }
0358: return repositoryPath;
0359: }
0360:
0361: /**
0362: * Compute relative path to repository root.
0363: * For not yet versioned files guess the URL
0364: * from parent context.
0365: *
0366: * <p>I/O intensive avoid calling it frnm AWT.
0367: *
0368: * @return the repository url or null for unknown
0369: * XXX we need this until we get a local implementation for client.getInfoFromWorkingCopy(file);
0370: */
0371: public static String getRelativePath(SVNUrl repositoryURL, File file)
0372: throws SVNClientException {
0373: String repositoryPath = null;
0374:
0375: SvnClient client;
0376: try {
0377: client = Subversion.getInstance().getClient(false);
0378: } catch (SVNClientException ex) {
0379: SvnClientExceptionHandler.notifyException(ex, false, false);
0380: return null;
0381: }
0382:
0383: List<String> path = new ArrayList<String>();
0384: boolean fileIsManaged = false;
0385: while (Subversion.getInstance().isManaged(file)) {
0386: fileIsManaged = true;
0387:
0388: ISVNStatus status = null;
0389: try {
0390: status = client.getSingleStatus(file);
0391: } catch (SVNClientException ex) {
0392: if (SvnClientExceptionHandler.isUnversionedResource(ex
0393: .getMessage()) == false) {
0394: SvnClientExceptionHandler.notifyException(ex,
0395: false, false);
0396: }
0397: }
0398:
0399: if (status != null && status.getUrl() != null) {
0400: SVNUrl fileURL = status.getUrl();
0401:
0402: if (fileURL != null && repositoryURL != null) {
0403: fileURL = decode(fileURL);
0404: String fileLink = fileURL.toString();
0405: String repositoryLink = repositoryURL.toString();
0406: repositoryPath = fileLink.substring(repositoryLink
0407: .length());
0408:
0409: Iterator it = path.iterator();
0410: StringBuffer sb = new StringBuffer();
0411: while (it.hasNext()) {
0412: String segment = (String) it.next();
0413: sb.append("/"); // NOI18N
0414: sb.append(segment);
0415: }
0416: repositoryPath += sb.toString();
0417: break;
0418: }
0419: }
0420:
0421: path.add(0, file.getName());
0422: file = file.getParentFile();
0423:
0424: }
0425: if (repositoryURL == null & fileIsManaged) {
0426: // The file is managed but we haven't found the repository URL in it's metadata -
0427: // this looks like the WC was created with a client < 1.3.0. I wouldn't mind for myself and
0428: // get the URL from the server, it's just that it could be quite a performance killer.
0429: throw new SVNClientException(NbBundle.getMessage(
0430: SvnUtils.class, "MSG_too_old_WC"));
0431: }
0432: return repositoryPath.startsWith("/") ? repositoryPath
0433: .substring(1) : repositoryPath;
0434: }
0435:
0436: /**
0437: * Returns the repository root for the given file.
0438: * For not yet versioned files guess the URL
0439: * from parent context.
0440: *
0441: * <p>I/O intensive avoid calling it frnm AWT.
0442: *
0443: * @return the repository url or null for unknown
0444: */
0445: public static SVNUrl getRepositoryRootUrl(File file)
0446: throws SVNClientException {
0447: SvnClient client;
0448: try {
0449: client = Subversion.getInstance().getClient(false);
0450: } catch (SVNClientException ex) {
0451: SvnClientExceptionHandler.notifyException(ex, false, false);
0452: return null;
0453: }
0454:
0455: SVNUrl repositoryURL = null;
0456: boolean fileIsManaged = false;
0457: while (Subversion.getInstance().isManaged(file)) {
0458: fileIsManaged = true;
0459: ISVNInfo info = null;
0460: try {
0461: info = client.getInfoFromWorkingCopy(file);
0462: } catch (SVNClientException ex) {
0463: if (SvnClientExceptionHandler.isUnversionedResource(ex
0464: .getMessage()) == false) {
0465: SvnClientExceptionHandler.notifyException(ex,
0466: false, false);
0467: }
0468: }
0469:
0470: if (info != null && info.getUrl() != null) {
0471: repositoryURL = decode(info.getRepository());
0472: if (repositoryURL != null) {
0473: break;
0474: }
0475: }
0476:
0477: // path.add(0, file.getName());
0478: file = file.getParentFile();
0479:
0480: }
0481: if (repositoryURL == null & fileIsManaged) {
0482: // The file is managed but we haven't found the repository URL in it's metadata -
0483: // this looks like the WC was created with a client < 1.3.0. I wouldn't mind for myself and
0484: // get the URL from the server, it's just that it could be quite a performance killer.
0485: throw new SVNClientException(NbBundle.getMessage(
0486: SvnUtils.class, "MSG_too_old_WC"));
0487: }
0488: return repositoryURL;
0489: }
0490:
0491: /**
0492: * Returns the repository URL for the given file.
0493: * For not yet versioned files guess the URL
0494: * from parent context.
0495: *
0496: * <p>I/O intensive avoid calling it frnm AWT.
0497: *
0498: * @return the repository url or null for unknown
0499: */
0500: public static SVNUrl getRepositoryUrl(File file)
0501: throws SVNClientException {
0502:
0503: StringBuffer path = new StringBuffer();
0504: SVNUrl fileURL = null;
0505: SvnClient client = null;
0506: try {
0507: client = Subversion.getInstance().getClient(false);
0508: } catch (SVNClientException ex) {
0509: SvnClientExceptionHandler.notifyException(ex, false, false);
0510: return null;
0511: }
0512: boolean fileIsManaged = false;
0513: while (Subversion.getInstance().isManaged(file)) {
0514: fileIsManaged = true;
0515:
0516: try {
0517: // it works with 1.3 workdirs and our .svn parser
0518: ISVNStatus status = getSingleStatus(client, file);
0519: if (status != null) {
0520: fileURL = decode(status.getUrl());
0521: if (fileURL != null) {
0522: break;
0523: }
0524: }
0525: } catch (SVNClientException ex) {
0526: if (SvnClientExceptionHandler.isUnversionedResource(ex
0527: .getMessage()) == false) {
0528: SvnClientExceptionHandler.notifyException(ex,
0529: false, false);
0530: }
0531: }
0532:
0533: // slower fallback
0534:
0535: ISVNInfo info = null;
0536: try {
0537: info = client.getInfoFromWorkingCopy(file);
0538: } catch (SVNClientException ex) {
0539: if (SvnClientExceptionHandler.isUnversionedResource(ex
0540: .getMessage()) == false) {
0541: SvnClientExceptionHandler.notifyException(ex,
0542: false, false);
0543: }
0544: }
0545:
0546: if (info != null) {
0547: fileURL = decode(info.getUrl());
0548:
0549: if (fileURL != null) {
0550: break;
0551: }
0552: }
0553:
0554: path.insert(0, file.getName()).insert(0, "/");
0555: file = file.getParentFile();
0556:
0557: }
0558: if (fileURL == null & fileIsManaged) {
0559: // The file is managed but we haven't found the URL in it's metadata -
0560: // this looks like the WC was created with a client < 1.3.0. I wouldn't mind for myself and
0561: // get the URL from the server, it's just that it could be quite a performance killer.
0562: throw new SVNClientException(NbBundle.getMessage(
0563: SvnUtils.class, "MSG_too_old_WC"));
0564: }
0565: if (path.length() > 0)
0566: fileURL = fileURL.appendPath(path.toString());
0567: return fileURL;
0568: }
0569:
0570: private static ISVNStatus getSingleStatus(SvnClient client,
0571: File file) throws SVNClientException {
0572: return client.getSingleStatus(file);
0573: }
0574:
0575: /**
0576: * Decodes svn URI by decoding %XX escape sequences.
0577: *
0578: * @param url url to decode
0579: * @return decoded url
0580: */
0581: private static SVNUrl decode(SVNUrl url) {
0582: if (url == null)
0583: return null;
0584: String s = url.toString();
0585: StringBuffer sb = new StringBuffer(s.length());
0586:
0587: boolean inQuery = false;
0588: for (int i = 0; i < s.length(); i++) {
0589: char c = s.charAt(i);
0590: if (c == '?') {
0591: inQuery = true;
0592: } else if (c == '+' && inQuery) {
0593: sb.append(' ');
0594: } else if (isEncodedByte(c, s, i)) {
0595: List<Byte> byteList = new ArrayList<Byte>();
0596: do {
0597: byteList.add((byte) Integer.parseInt(s.substring(
0598: i + 1, i + 3), 16));
0599: i += 3;
0600: c = s.charAt(i);
0601: } while (isEncodedByte(c, s, i));
0602:
0603: if (byteList.size() > 0) {
0604: byte[] bytes = new byte[byteList.size()];
0605: for (int ib = 0; ib < byteList.size(); ib++) {
0606: bytes[ib] = byteList.get(ib);
0607: }
0608: try {
0609: sb.append(new String(bytes, "UTF8"));
0610: } catch (Exception e) {
0611: Subversion.LOG.log(Level.INFO, null, e); // oops
0612: }
0613: i--;
0614: }
0615: } else {
0616: sb.append(c);
0617: }
0618: }
0619: try {
0620: return new SVNUrl(sb.toString());
0621: } catch (java.net.MalformedURLException e) {
0622: throw new RuntimeException(e);
0623: }
0624: }
0625:
0626: private static boolean isEncodedByte(char c, String s, int i) {
0627: return c == '%' && i + 2 < s.length()
0628: && isHexDigit(s.charAt(i + 1))
0629: && isHexDigit(s.charAt(i + 2));
0630: }
0631:
0632: private static boolean isHexDigit(char c) {
0633: return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a'
0634: && c <= 'f';
0635: }
0636:
0637: /*
0638: * Determines a versioned file's repository path
0639: *
0640: * @param file versioned file
0641: * @return file's path in repository
0642: */
0643: public static String getRepositoryPath(File file)
0644: throws SVNClientException {
0645: SVNUrl url = getRepositoryUrl(file);
0646: SVNUrl rootUrl = getRepositoryRootUrl(file);
0647: return SVNUrlUtils.getRelativePath(rootUrl, url, true);
0648: }
0649:
0650: /**
0651: * @return true if the buffer is almost certainly binary.
0652: * Note: Non-ASCII based encoding encoded text is binary,
0653: * newlines cannot be reliably detected.
0654: */
0655: public static boolean isBinary(byte[] buffer) {
0656: for (int i = 0; i < buffer.length; i++) {
0657: int ch = buffer[i];
0658: if (ch < 32 && ch != '\t' && ch != '\n' && ch != '\r') {
0659: return true;
0660: }
0661: }
0662: return false;
0663: }
0664:
0665: /**
0666: * Compares two {@link FileInformation} objects by importance of statuses they represent.
0667: */
0668: public static class ByImportanceComparator<T> implements
0669: Comparator<FileInformation> {
0670: public int compare(FileInformation i1, FileInformation i2) {
0671: return getComparableStatus(i1.getStatus())
0672: - getComparableStatus(i2.getStatus());
0673: }
0674: }
0675:
0676: /**
0677: * Normalize flat files, Subversion treats folder as normal file
0678: * so it's necessary explicitly list direct descendants to
0679: * get classical flat behaviour.
0680: *
0681: * <p> E.g. revert on package node means:
0682: * <ul>
0683: * <li>revert package folder properties AND
0684: * <li>revert all modified (including deleted) files in the folder
0685: * </ul>
0686: *
0687: * @return files with given status and direct descendants with given status.
0688: */
0689: public static File[] flatten(File[] files, int status) {
0690: LinkedList<File> ret = new LinkedList<File>();
0691:
0692: FileStatusCache cache = Subversion.getInstance()
0693: .getStatusCache();
0694: for (int i = 0; i < files.length; i++) {
0695: File dir = files[i];
0696: FileInformation info = cache.getStatus(dir);
0697: if ((status & info.getStatus()) != 0) {
0698: ret.add(dir);
0699: }
0700: File[] entries = cache.listFiles(dir); // comparing to dir.listFiles() lists already deleted too
0701: for (int e = 0; e < entries.length; e++) {
0702: File entry = entries[e];
0703: info = cache.getStatus(entry);
0704: if ((status & info.getStatus()) != 0) {
0705: ret.add(entry);
0706: }
0707: }
0708: }
0709:
0710: return ret.toArray(new File[ret.size()]);
0711: }
0712:
0713: /**
0714: * Utility method that returns all non-excluded modified files that are
0715: * under given roots (folders) and have one of specified statuses.
0716: *
0717: * @param context context to search
0718: * @param includeStatus bit mask of file statuses to include in result
0719: * @return File [] array of Files having specified status
0720: */
0721: public static File[] getModifiedFiles(Context context,
0722: int includeStatus) {
0723: File[] all = Subversion.getInstance().getStatusCache()
0724: .listFiles(context, includeStatus);
0725: List<File> files = new ArrayList<File>();
0726: for (int i = 0; i < all.length; i++) {
0727: File file = all[i];
0728: String path = file.getAbsolutePath();
0729: if (SvnModuleConfig.getDefault().isExcludedFromCommit(path) == false) {
0730: files.add(file);
0731: }
0732: }
0733:
0734: // ensure that command roots (files that were explicitly selected by user) are included in Diff
0735: FileStatusCache cache = Subversion.getInstance()
0736: .getStatusCache();
0737: File[] rootFiles = context.getRootFiles();
0738: for (int i = 0; i < rootFiles.length; i++) {
0739: File file = rootFiles[i];
0740: if (file.isFile()
0741: && (cache.getStatus(file).getStatus() & includeStatus) != 0
0742: && !files.contains(file)) {
0743: files.add(file);
0744: }
0745: }
0746: return files.toArray(new File[files.size()]);
0747: }
0748:
0749: /**
0750: * Checks file location.
0751: *
0752: * @param file file to check
0753: * @return true if the file or folder is a part of subverion metadata, false otherwise
0754: */
0755: public static boolean isPartOfSubversionMetadata(File file) {
0756: return metadataPattern.matcher(file.getAbsolutePath())
0757: .matches();
0758: }
0759:
0760: /**
0761: * Gets integer status that can be used in comparators. The more important the status is for the user,
0762: * the lower value it has. Conflict is 0, unknown status is 100.
0763: *
0764: * @return status constant suitable for 'by importance' comparators
0765: */
0766: public static int getComparableStatus(int status) {
0767: if (0 != (status & FileInformation.STATUS_VERSIONED_CONFLICT)) {
0768: return 0;
0769: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MERGE)) {
0770: return 1;
0771: } else if (0 != (status & FileInformation.STATUS_VERSIONED_DELETEDLOCALLY)) {
0772: return 10;
0773: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY)) {
0774: return 11;
0775: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY)) {
0776: return 12;
0777: } else if (0 != (status & FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)) {
0778: return 13;
0779: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY)) {
0780: return 14;
0781: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY)) {
0782: return 30;
0783: } else if (0 != (status & FileInformation.STATUS_VERSIONED_NEWINREPOSITORY)) {
0784: return 31;
0785: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY)) {
0786: return 32;
0787: } else if (0 != (status & FileInformation.STATUS_VERSIONED_UPTODATE)) {
0788: return 50;
0789: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_EXCLUDED)) {
0790: return 100;
0791: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)) {
0792: return 101;
0793: } else if (status == FileInformation.STATUS_UNKNOWN) {
0794: return 102;
0795: } else {
0796: throw new IllegalArgumentException("Uncomparable status: "
0797: + status); // NOI18N
0798: }
0799: }
0800:
0801: /**
0802: * Returns a symbolic branch/tag name if the given file lives
0803: * in a location specified by an AnnotationExpression
0804: *
0805: * @param file
0806: * @return name or null
0807: */
0808: public static String getCopy(File file) {
0809: SVNUrl url;
0810: try {
0811: url = getRepositoryUrl(file);
0812: } catch (SVNClientException ex) {
0813: SvnClientExceptionHandler.notifyException(ex, false, false);
0814: return null;
0815: }
0816: return getCopy(url, SvnModuleConfig.getDefault()
0817: .getAnnotationExpresions());
0818: }
0819:
0820: /**
0821: * Returns a symbolic branch/tag name if the given url represents
0822: * a location specified by an AnnotationExpression
0823: *
0824: * @param url
0825: * @return name or null
0826: */
0827: public static String getCopy(SVNUrl url) {
0828: return getCopy(url, SvnModuleConfig.getDefault()
0829: .getAnnotationExpresions());
0830: }
0831:
0832: /**
0833: * Returns a symbolic branch/tag name if the given url represents
0834: * a location specified by an AnnotationExpression
0835: *
0836: * @param url
0837: * @param annotationExpressions
0838: * @return name or null
0839: */
0840: private static String getCopy(SVNUrl url,
0841: List<AnnotationExpression> annotationExpressions) {
0842: if (url != null) {
0843: String urlString = url.toString();
0844: for (Iterator<AnnotationExpression> it = annotationExpressions
0845: .iterator(); it.hasNext();) {
0846: String name = it.next().getCopyName(urlString);
0847: if (name != null) {
0848: return name;
0849: }
0850: }
0851: }
0852: return null;
0853: }
0854:
0855: /**
0856: * Refreshes statuses of this folder and all its parent folders up to filesystem root.
0857: *
0858: * @param folder folder to refresh
0859: */
0860: public static void refreshParents(File folder) {
0861: if (folder == null)
0862: return;
0863: refreshParents(folder.getParentFile());
0864: Subversion.getInstance().getStatusCache().refresh(folder,
0865: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0866: }
0867:
0868: /**
0869: * Refreshes the status for the given file and all its children
0870: *
0871: * @param file
0872: */
0873: public static void refreshRecursively(File file) {
0874: FileStatusCache cache = Subversion.getInstance()
0875: .getStatusCache();
0876: cache.refresh(file, FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0877: File[] files = file.listFiles();
0878: if (files != null) {
0879: for (int i = 0; i < files.length; i++) {
0880: if (!(SvnUtils.isPartOfSubversionMetadata(files[i]) || Subversion
0881: .getInstance().isAdministrative(files[i]))) {
0882: cache.refresh(files[i],
0883: FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
0884: if (files[i].isDirectory()) {
0885: refreshRecursively(files[i]);
0886: }
0887: }
0888: }
0889: }
0890: }
0891:
0892: /**
0893: * Rips an eventual username off - e.g. user@svn.host.org
0894: *
0895: * @param host - hostname with a userneame
0896: * @return host - hostname without the username
0897: */
0898: public static String ripUserFromHost(String host) {
0899: int idx = host.indexOf('@');
0900: if (idx < 0) {
0901: return host;
0902: } else {
0903: return host.substring(idx + 1);
0904: }
0905: }
0906:
0907: public static SVNRevision getSVNRevision(String revisionString) {
0908: try {
0909: // HEAD, PREV, BASE, COMMITED, ...
0910: return SVNRevision.getRevision(revisionString);
0911: } catch (ParseException ex) {
0912: return new SVNRevision.Number(Long
0913: .parseLong(revisionString));
0914: }
0915: }
0916:
0917: /*
0918: * Returns the first pattern from the list which matches with the given value.
0919: * The patterns are interpreted as shell paterns.
0920: *
0921: * @param patterns
0922: * @param value
0923: * @return the first pattern matching with the given value
0924: */
0925: public static List<String> getMatchinIgnoreParterns(
0926: List<String> patterns, String value, boolean onlyFirstMatch) {
0927: List<String> ret = new ArrayList<String>();
0928: for (Iterator<String> i = patterns.iterator(); i.hasNext();) {
0929: try {
0930: // may contain shell patterns (almost identical to RegExp)
0931: String patternString = i.next();
0932: String shellPatternString = regExpToFilePatterns(patternString);
0933: Pattern pattern = Pattern.compile(shellPatternString);
0934: if (pattern.matcher(value).matches()) {
0935: ret.add(patternString);
0936: if (onlyFirstMatch) {
0937: return ret;
0938: }
0939: }
0940: } catch (PatternSyntaxException e) {
0941: // it's difference between shell and regexp
0942: // or user error (set invalid property), rethrow?
0943: Subversion.LOG.log(Level.INFO, null, e);
0944: }
0945: }
0946: return ret;
0947: }
0948:
0949: private static String regExpToFilePatterns(String exp) {
0950: exp = exp.replaceAll("\\.", "\\\\."); // NOI18N
0951: exp = exp.replaceAll("\\*", ".*"); // NOI18N
0952: exp = exp.replaceAll("\\?", "."); // NOI18N
0953:
0954: exp = exp.replaceAll("\\$", "\\\\\\$"); // NOI18N
0955: exp = exp.replaceAll("\\^", "\\\\^"); // NOI18N
0956: exp = exp.replaceAll("\\<", "\\\\<"); // NOI18N
0957: exp = exp.replaceAll("\\>", "\\\\>"); // NOI18N
0958: exp = exp.replaceAll("\\[", "\\\\["); // NOI18N
0959: exp = exp.replaceAll("\\]", "\\\\]"); // NOI18N
0960: exp = exp.replaceAll("\\{", "\\\\{"); // NOI18N
0961: exp = exp.replaceAll("\\}", "\\\\}"); // NOI18N
0962: exp = exp.replaceAll("\\(", "\\\\("); // NOI18N
0963: exp = exp.replaceAll("\\)", "\\\\)"); // NOI18N
0964: exp = exp.replaceAll("\\+", "\\\\+"); // NOI18N
0965: exp = exp.replaceAll("\\|", "\\\\|"); // NOI18N
0966:
0967: return exp;
0968: }
0969:
0970: /**
0971: * Reads the svn:mime-type property or uses content analysis for unversioned files.
0972: *
0973: * @param file file to examine
0974: * @return String mime type of the file (or best guess)
0975: */
0976: public static String getMimeType(File file) {
0977: FileObject fo = FileUtil.toFileObject(file);
0978: String foMime;
0979: if (fo == null) {
0980: foMime = "content/unknown";
0981: } else {
0982: foMime = fo.getMIMEType();
0983: }
0984: FileStatusCache cache = Subversion.getInstance()
0985: .getStatusCache();
0986: if ((cache.getStatus(file).getStatus() & FileInformation.STATUS_VERSIONED) == 0) {
0987: if (foMime.startsWith("text")) {
0988: return foMime;
0989: }
0990: return Utils.isFileContentText(file) ? "text/plain"
0991: : "application/octet-stream";
0992: } else {
0993: PropertiesClient client = new PropertiesClient(file);
0994: try {
0995: byte[] mimeProperty = client.getProperties().get(
0996: "svn:mime-type");
0997: if (mimeProperty == null) {
0998: return foMime;
0999: }
1000: return new String(mimeProperty);
1001: } catch (IOException e) {
1002: return foMime;
1003: }
1004: }
1005: }
1006:
1007: public static <T> boolean equals(List<T> l1, List<T> l2) {
1008:
1009: if (l1 == null && l2 == null) {
1010: return true;
1011: }
1012:
1013: if ((l1 == null && l2 != null && l2.size() > 0)
1014: || (l2 == null && l1 != null && l1.size() > 0)) {
1015: return false;
1016: }
1017:
1018: if (l1.size() != l2.size()) {
1019: return false;
1020: }
1021:
1022: for (T t : l1) {
1023: if (!l2.contains(t)) {
1024: return false;
1025: }
1026: }
1027:
1028: return true;
1029: }
1030: }
|