diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java new file mode 100644 index 000000000..12f4dcc0c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandSymlinkTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.gitrepo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; +import org.junit.Before; +import org.junit.Test; + +public class RepoCommandSymlinkTest extends RepositoryTestCase { + @Before + public void beforeMethod() { + // If this assumption fails the tests are skipped. When running on a + // filesystem not supporting symlinks I don't want this tests + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + } + + private Repository defaultDb; + + private String rootUri; + private String defaultUri; + + @Override + public void setUp() throws Exception { + super.setUp(); + + defaultDb = createWorkRepository(); + try (Git git = new Git(defaultDb)) { + JGitTestUtil.writeTrashFile(defaultDb, "hello.txt", "hello world"); + git.add().addFilepattern("hello.txt").call(); + git.commit().setMessage("Initial commit").call(); + addRepoToClose(defaultDb); + } + + defaultUri = defaultDb.getDirectory().toURI().toString(); + int root = defaultUri.lastIndexOf("/", + defaultUri.lastIndexOf("/.git") - 1) + + 1; + rootUri = defaultUri.substring(0, root) + + "manifest"; + defaultUri = defaultUri.substring(root); + } + + @Test + public void testLinkFileBare() throws Exception { + try ( + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository()) { + StringBuilder xmlContent = new StringBuilder(); + xmlContent + .append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("").append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", + xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath( + tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri).call(); + // Clone it + File directory = createTempDirectory("testCopyFileBare"); + Repository localDb = Git.cloneRepository().setDirectory(directory) + .setURI(remoteDb.getDirectory().toURI().toString()).call() + .getRepository(); + + // The LinkedHello symlink should exist. + File linkedhello = new File(localDb.getWorkTree(), "LinkedHello"); + assertTrue("The LinkedHello file should exist", + localDb.getFS().exists(linkedhello)); + assertTrue("The LinkedHello file should be a symlink", + localDb.getFS().isSymLink(linkedhello)); + assertEquals("foo/hello.txt", + localDb.getFS().readSymLink(linkedhello)); + + // The foo/LinkedHello file should be skipped. + File linkedfoohello = new File(localDb.getWorkTree(), "foo/LinkedHello"); + assertFalse("The foo/LinkedHello file should be skipped", + localDb.getFS().exists(linkedfoohello)); + + // The subdir/LinkedHello file should use a relative ../ + File linkedsubdirhello = new File(localDb.getWorkTree(), + "subdir/LinkedHello"); + assertTrue("The subdir/LinkedHello file should exist", + localDb.getFS().exists(linkedsubdirhello)); + assertTrue("The subdir/LinkedHello file should be a symlink", + localDb.getFS().isSymLink(linkedsubdirhello)); + assertEquals("../foo/hello.txt", + localDb.getFS().readSymLink(linkedsubdirhello)); + + // The bar/foo/LinkedHello file should use a single relative ../ + File linkedbarfoohello = new File(localDb.getWorkTree(), + "bar/foo/LinkedHello"); + assertTrue("The bar/foo/LinkedHello file should exist", + localDb.getFS().exists(linkedbarfoohello)); + assertTrue("The bar/foo/LinkedHello file should be a symlink", + localDb.getFS().isSymLink(linkedbarfoohello)); + assertEquals("../baz/hello.txt", + localDb.getFS().readSymLink(linkedbarfoohello)); + + localDb.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java index 109d0e6ee..c0f496566 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilsTest.java @@ -465,12 +465,12 @@ public void testCreateSymlinkOverrideExisting() throws IOException { @Test public void testRelativize_doc() { - // This is the javadoc example + // This is the example from the javadoc String base = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\project"); String other = toOSPathString("c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"); String expected = toOSPathString("..\\another_project\\pom.xml"); - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expected, actual); } @@ -483,13 +483,13 @@ public void testRelativize_mixedCase() { String expectedCaseSensitive = toOSPathString("..\\..\\Git\\test\\d\\f.txt"); if (systemReader.isWindows()) { - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expectedCaseInsensitive, actual); } else if (systemReader.isMacOS()) { - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expectedCaseInsensitive, actual); } else { - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expectedCaseSensitive, actual); } } @@ -501,7 +501,7 @@ public void testRelativize_scheme() { // 'file.java' is treated as a folder String expected = toOSPathString("../../project"); - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expected, actual); } @@ -511,7 +511,7 @@ public void testRelativize_equalPaths() { String other = toOSPathString("file:/home/eclipse/runtime-New_configuration/project_1"); String expected = ""; - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expected, actual); } @@ -521,7 +521,7 @@ public void testRelativize_whitespaces() { String other = toOSPathString("/home/eclipse 3.4/runtime New_configuration/project_1/file"); String expected = "file"; - String actual = FileUtils.relativize(base, other); + String actual = FileUtils.relativizeNativePath(base, other); assertEquals(expected, actual); } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index da076dcb7..225cb5371 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -431,6 +431,7 @@ noHEADExistsAndNoExplicitStartingRevisionWasSpecified=No HEAD exists and no expl noHMACsupport=No {0} support: {1} noMergeBase=No merge base could be determined. Reason={0}. {1} noMergeHeadSpecified=No merge head specified +nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos noSuchRef=no such ref notABoolean=Not a boolean: {0} notABundle=not a bundle diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java index 94c8e437c..ddc6addbc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java @@ -60,6 +60,8 @@ import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; +import org.eclipse.jgit.gitrepo.RepoProject.ReferenceFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; @@ -209,6 +211,15 @@ public void startElement( currentProject.getPath(), attributes.getValue("src"), //$NON-NLS-1$ attributes.getValue("dest"))); //$NON-NLS-1$ + } else if ("linkfile".equals(qName)) { //$NON-NLS-1$ + if (currentProject == null) { + throw new SAXException(RepoText.get().invalidManifest); + } + currentProject.addLinkFile(new LinkFile( + rootRepo, + currentProject.getPath(), + attributes.getValue("src"), //$NON-NLS-1$ + attributes.getValue("dest"))); //$NON-NLS-1$ } else if ("include".equals(qName)) { //$NON-NLS-1$ String name = attributes.getValue("name"); //$NON-NLS-1$ if (includedReader != null) { @@ -359,19 +370,25 @@ void removeOverlaps() { else last = p; } - removeNestedCopyfiles(); + removeNestedCopyAndLinkfiles(); } - /** Remove copyfiles that sit in a subdirectory of any other project. */ - void removeNestedCopyfiles() { + private void removeNestedCopyAndLinkfiles() { for (RepoProject proj : filteredProjects) { List copyfiles = new ArrayList<>(proj.getCopyFiles()); proj.clearCopyFiles(); for (CopyFile copyfile : copyfiles) { - if (!isNestedCopyfile(copyfile)) { + if (!isNestedReferencefile(copyfile)) { proj.addCopyFile(copyfile); } } + List linkfiles = new ArrayList<>(proj.getLinkFiles()); + proj.clearLinkFiles(); + for (LinkFile linkfile : linkfiles) { + if (!isNestedReferencefile(linkfile)) { + proj.addLinkFile(linkfile); + } + } } } @@ -393,18 +410,18 @@ boolean inGroups(RepoProject proj) { return false; } - private boolean isNestedCopyfile(CopyFile copyfile) { - if (copyfile.dest.indexOf('/') == -1) { - // If the copyfile is at root level then it won't be nested. + private boolean isNestedReferencefile(ReferenceFile referencefile) { + if (referencefile.dest.indexOf('/') == -1) { + // If the referencefile is at root level then it won't be nested. return false; } for (RepoProject proj : filteredProjects) { - if (proj.getPath().compareTo(copyfile.dest) > 0) { + if (proj.getPath().compareTo(referencefile.dest) > 0) { // Early return as remaining projects can't be ancestor of this - // copyfile config (filteredProjects is sorted). + // referencefile config (filteredProjects is sorted). return false; } - if (proj.isAncestorOf(copyfile.dest)) { + if (proj.isAncestorOf(referencefile.dest)) { return true; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index 8f5f15e8d..6669c9cfb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -49,6 +49,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.UnsupportedOperationException; import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; @@ -69,6 +70,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.gitrepo.ManifestParser.IncludedFileReader; import org.eclipse.jgit.gitrepo.RepoProject.CopyFile; +import org.eclipse.jgit.gitrepo.RepoProject.LinkFile; import org.eclipse.jgit.gitrepo.internal.RepoText; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; @@ -506,6 +508,7 @@ public RevCommit call() throws GitAPIException { proj.getPath(), proj.getRevision(), proj.getCopyFiles(), + proj.getLinkFiles(), proj.getGroups(), proj.getRecommendShallow()); } @@ -593,6 +596,25 @@ public RevCommit call() throws GitAPIException { dcEntry.setFileMode(FileMode.REGULAR_FILE); builder.add(dcEntry); } + for (LinkFile linkfile : proj.getLinkFiles()) { + String link; + if (linkfile.dest.contains("/")) { //$NON-NLS-1$ + link = FileUtils.relativizeGitPath( + linkfile.dest.substring(0, + linkfile.dest.lastIndexOf('/')), + proj.getPath() + "/" + linkfile.src); //$NON-NLS-1$ + } else { + link = proj.getPath() + "/" + linkfile.src; //$NON-NLS-1$ + } + + objectId = inserter.insert(Constants.OBJ_BLOB, + link.getBytes( + Constants.CHARACTER_ENCODING)); + dcEntry = new DirCacheEntry(linkfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.SYMLINK); + builder.add(dcEntry); + } } String content = cfg.toText(); @@ -667,13 +689,20 @@ public RevCommit call() throws GitAPIException { } private void addSubmodule(String url, String path, String revision, - List copyfiles, Set groups, String recommendShallow) + List copyfiles, List linkfiles, + Set groups, String recommendShallow) throws GitAPIException, IOException { if (repo.isBare()) { RepoProject proj = new RepoProject(url, path, revision, null, groups, recommendShallow); proj.addCopyFiles(copyfiles); + proj.addLinkFiles(linkfiles); bareProjects.add(proj); } else { + if (!linkfiles.isEmpty()) { + throw new UnsupportedOperationException( + JGitText.get().nonBareLinkFilesNotSupported); + } + SubmoduleAddCommand add = git .submoduleAdd() .setPath(path) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java index 700cf1107..00cd38d69 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java @@ -70,14 +70,17 @@ public class RepoProject implements Comparable { private final String remote; private final Set groups; private final List copyfiles; + private final List linkfiles; private String recommendShallow; private String url; private String defaultRevision; /** - * The representation of a copy file configuration. + * The representation of a reference file configuration. + * + * @since 4.8 */ - public static class CopyFile { + public static class ReferenceFile { final Repository repo; final String path; final String src; @@ -93,12 +96,31 @@ public static class CopyFile { * @param dest * the destination path relative to the super project. */ - public CopyFile(Repository repo, String path, String src, String dest) { + public ReferenceFile(Repository repo, String path, String src, String dest) { this.repo = repo; this.path = path; this.src = src; this.dest = dest; } + } + + /** + * The representation of a copy file configuration. + */ + public static class CopyFile extends ReferenceFile { + /** + * @param repo + * the super project. + * @param path + * the path of the project containing this copyfile config. + * @param src + * the source path relative to the sub repo. + * @param dest + * the destination path relative to the super project. + */ + public CopyFile(Repository repo, String path, String src, String dest) { + super(repo, path, src, dest); + } /** * Do the copy file action. @@ -125,6 +147,27 @@ public void copy() throws IOException { } } + /** + * The representation of a link file configuration. + * + * @since 4.8 + */ + public static class LinkFile extends ReferenceFile { + /** + * @param repo + * the super project. + * @param path + * the path of the project containing this linkfile config. + * @param src + * the source path relative to the sub repo. + * @param dest + * the destination path relative to the super project. + */ + public LinkFile(Repository repo, String path, String src, String dest) { + super(repo, path, src, dest); + } + } + /** * @param name * the relative path to the {@code remote} @@ -156,6 +199,7 @@ public RepoProject(String name, String path, String revision, this.groups = groups; this.recommendShallow = recommendShallow; copyfiles = new ArrayList<>(); + linkfiles = new ArrayList<>(); } /** @@ -249,6 +293,16 @@ public List getCopyFiles() { return Collections.unmodifiableList(copyfiles); } + /** + * Getter for the linkfile configurations. + * + * @return Immutable copy of {@code linkfiles} + * @since 4.8 + */ + public List getLinkFiles() { + return Collections.unmodifiableList(linkfiles); + } + /** * Get the url of the sub repo. * @@ -335,6 +389,35 @@ public void clearCopyFiles() { this.copyfiles.clear(); } + /** + * Add a link file configuration. + * + * @param linkfile + * @since 4.8 + */ + public void addLinkFile(LinkFile linkfile) { + linkfiles.add(linkfile); + } + + /** + * Add a bunch of linkfile configurations. + * + * @param linkFiles + * @since 4.8 + */ + public void addLinkFiles(Collection linkFiles) { + this.linkfiles.addAll(linkFiles); + } + + /** + * Clear all the linkfiles. + * + * @since 4.8 + */ + public void clearLinkFiles() { + this.linkfiles.clear(); + } + private String getPathWithSlash() { if (path.endsWith("/")) //$NON-NLS-1$ return path; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 0e52fccaa..b2c59a357 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -490,6 +490,7 @@ public static JGitText get() { /***/ public String noHMACsupport; /***/ public String noMergeBase; /***/ public String noMergeHeadSpecified; + /***/ public String nonBareLinkFilesNotSupported; /***/ public String noSuchRef; /***/ public String notABoolean; /***/ public String notABundle; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 68b71309b..229355c50 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -777,7 +777,7 @@ public void createSymLink(File path, String target) throws IOException { } /** - * See {@link FileUtils#relativize(String, String)}. + * See {@link FileUtils#relativizePath(String, String, String, boolean)}. * * @param base * The path against which other should be @@ -786,11 +786,11 @@ public void createSymLink(File path, String target) throws IOException { * The path that will be made relative to base. * @return A relative path that, when resolved against base, * will yield the original other. - * @see FileUtils#relativize(String, String) + * @see FileUtils#relativizePath(String, String, String, boolean) * @since 3.7 */ public String relativize(String base, String other) { - return FileUtils.relativize(base, other); + return FileUtils.relativizePath(base, other, File.separator, this.isCaseSensitive()); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 1f20e9700..76dbb8756 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -468,10 +468,71 @@ public static File createTempDir(String prefix, String suffix, File dir) throw new IOException(JGitText.get().cannotCreateTempDir); } + /** - * This will try and make a given path relative to another. + * @deprecated Use the more-clearly-named + * {@link FileUtils#relativizeNativePath(String, String)} + * instead, or directly call + * {@link FileUtils#relativizePath(String, String, String, boolean)} + * + * Expresses other as a relative file path from + * base. File-separator and case sensitivity are + * based on the current file system. + * + * See also + * {@link FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from base to other + * @since 3.7 + */ + @Deprecated + public static String relativize(String base, String other) { + return relativizeNativePath(base, other); + } + + /** + * Expresses other as a relative file path from base. + * File-separator and case sensitivity are based on the current file system. + * + * See also {@link FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from base to other + * @since 4.8 + */ + public static String relativizeNativePath(String base, String other) { + return FS.DETECTED.relativize(base, other); + } + + /** + * Expresses other as a relative file path from base. + * File-separator and case sensitivity are based on Git's internal representation of files (which matches Unix). + * + * See also {@link FileUtils#relativizePath(String, String, String, boolean)}. + * + * @param base + * Base path + * @param other + * Destination path + * @return Relative path from base to other + * @since 4.8 + */ + public static String relativizeGitPath(String base, String other) { + return relativizePath(base, other, "/", false); //$NON-NLS-1$ + } + + + /** + * Expresses other as a relative file path from base *

- * For example, if this is called with the two following paths : + * For example, if called with the two following paths : * *

 	 * base = "c:\\Users\\jdoe\\eclipse\\git\\project"
@@ -480,9 +541,7 @@ public static File createTempDir(String prefix, String suffix, File dir)
 	 *
 	 * This will return "..\\another_project\\pom.xml".
 	 * 

- *

- * This method uses {@link File#separator} to split the paths into segments. - *

+ * *

* Note that this will return the empty String if base * and other are equal. @@ -494,41 +553,44 @@ public static File createTempDir(String prefix, String suffix, File dir) * folder and not a file. * @param other * The path that will be made relative to base. + * @param dirSeparator + * A string that separates components of the path. In practice, this is "/" or "\\". + * @param caseSensitive + * Whether to consider differently-cased directory names as distinct * @return A relative path that, when resolved against base, * will yield the original other. - * @since 3.7 + * @since 4.8 */ - public static String relativize(String base, String other) { + public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) { if (base.equals(other)) return ""; //$NON-NLS-1$ - final boolean ignoreCase = !FS.DETECTED.isCaseSensitive(); - final String[] baseSegments = base.split(Pattern.quote(File.separator)); + final String[] baseSegments = base.split(Pattern.quote(dirSeparator)); final String[] otherSegments = other.split(Pattern - .quote(File.separator)); + .quote(dirSeparator)); int commonPrefix = 0; while (commonPrefix < baseSegments.length && commonPrefix < otherSegments.length) { - if (ignoreCase + if (caseSensitive + && baseSegments[commonPrefix] + .equals(otherSegments[commonPrefix])) + commonPrefix++; + else if (!caseSensitive && baseSegments[commonPrefix] .equalsIgnoreCase(otherSegments[commonPrefix])) commonPrefix++; - else if (!ignoreCase - && baseSegments[commonPrefix] - .equals(otherSegments[commonPrefix])) - commonPrefix++; else break; } final StringBuilder builder = new StringBuilder(); for (int i = commonPrefix; i < baseSegments.length; i++) - builder.append("..").append(File.separator); //$NON-NLS-1$ + builder.append("..").append(dirSeparator); //$NON-NLS-1$ for (int i = commonPrefix; i < otherSegments.length; i++) { builder.append(otherSegments[i]); if (i < otherSegments.length - 1) - builder.append(File.separator); + builder.append(dirSeparator); } return builder.toString(); }