1 /* 2 * Copyright (C) 2016, Google Inc. and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 11 package org.eclipse.jgit.util; 12 13 import static org.eclipse.jgit.lib.FileMode.TYPE_MASK; 14 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; 15 16 /** 17 * Utility functions for paths inside of a Git repository. 18 * 19 * @since 4.2 20 */ 21 public final class Paths { 22 23 /** 24 * Remove trailing {@code '/'} if present. 25 * 26 * @param path 27 * input path to potentially remove trailing {@code '/'} from. 28 * @return null if {@code path == null}; {@code path} after removing a 29 * trailing {@code '/'}. 30 */ 31 public static String stripTrailingSeparator(String path) { 32 if (path == null || path.isEmpty()) { 33 return path; 34 } 35 36 int i = path.length(); 37 if (path.charAt(path.length() - 1) != '/') { 38 return path; 39 } 40 do { 41 i--; 42 } while (path.charAt(i - 1) == '/'); 43 return path.substring(0, i); 44 } 45 46 /** 47 * Determines whether a git path {@code folder} is a prefix of another git 48 * path {@code path}, or the same as {@code path}. An empty {@code folder} 49 * is <em>not</em> not considered a prefix and matches only if {@code path} 50 * is also empty. 51 * 52 * @param folder 53 * a git path for a directory, without trailing slash 54 * @param path 55 * a git path 56 * @return {@code true} if {@code folder} is a directory prefix of 57 * {@code path}, or is equal to {@code path}, {@code false} 58 * otherwise 59 * @since 6.3 60 */ 61 public static boolean isEqualOrPrefix(String folder, String path) { 62 if (folder.isEmpty()) { 63 return path.isEmpty(); 64 } 65 boolean isPrefix = path.startsWith(folder); 66 if (isPrefix) { 67 int length = folder.length(); 68 return path.length() == length || path.charAt(length) == '/'; 69 } 70 return false; 71 } 72 73 /** 74 * Compare two paths according to Git path sort ordering rules. 75 * 76 * @param aPath 77 * first path buffer. The range {@code [aPos, aEnd)} is used. 78 * @param aPos 79 * index into {@code aPath} where the first path starts. 80 * @param aEnd 81 * 1 past last index of {@code aPath}. 82 * @param aMode 83 * mode of the first file. Trees are sorted as though 84 * {@code aPath[aEnd] == '/'}, even if aEnd does not exist. 85 * @param bPath 86 * second path buffer. The range {@code [bPos, bEnd)} is used. 87 * @param bPos 88 * index into {@code bPath} where the second path starts. 89 * @param bEnd 90 * 1 past last index of {@code bPath}. 91 * @param bMode 92 * mode of the second file. Trees are sorted as though 93 * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. 94 * @return <0 if {@code aPath} sorts before {@code bPath}; 0 if the paths 95 * are the same; >0 if {@code aPath} sorts after {@code bPath}. 96 */ 97 public static int compare(byte[] aPath, int aPos, int aEnd, int aMode, 98 byte[] bPath, int bPos, int bEnd, int bMode) { 99 int cmp = coreCompare( 100 aPath, aPos, aEnd, aMode, 101 bPath, bPos, bEnd, bMode); 102 if (cmp == 0) { 103 cmp = lastPathChar(aMode) - lastPathChar(bMode); 104 } 105 return cmp; 106 } 107 108 /** 109 * Compare two paths, checking for identical name. 110 * <p> 111 * Unlike {@code compare} this method returns {@code 0} when the paths have 112 * the same characters in their names, even if the mode differs. It is 113 * intended for use in validation routines detecting duplicate entries. 114 * <p> 115 * Returns {@code 0} if the names are identical and a conflict exists 116 * between {@code aPath} and {@code bPath}, as they share the same name. 117 * <p> 118 * Returns {@code <0} if all possibles occurrences of {@code aPath} sort 119 * before {@code bPath} and no conflict can happen. In a properly sorted 120 * tree there are no other occurrences of {@code aPath} and therefore there 121 * are no duplicate names. 122 * <p> 123 * Returns {@code >0} when it is possible for a duplicate occurrence of 124 * {@code aPath} to appear later, after {@code bPath}. Callers should 125 * continue to examine candidates for {@code bPath} until the method returns 126 * one of the other return values. 127 * 128 * @param aPath 129 * first path buffer. The range {@code [aPos, aEnd)} is used. 130 * @param aPos 131 * index into {@code aPath} where the first path starts. 132 * @param aEnd 133 * 1 past last index of {@code aPath}. 134 * @param bPath 135 * second path buffer. The range {@code [bPos, bEnd)} is used. 136 * @param bPos 137 * index into {@code bPath} where the second path starts. 138 * @param bEnd 139 * 1 past last index of {@code bPath}. 140 * @param bMode 141 * mode of the second file. Trees are sorted as though 142 * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. 143 * @return <0 if no duplicate name could exist; 144 * 0 if the paths have the same name; 145 * >0 other {@code bPath} should still be checked by caller. 146 */ 147 public static int compareSameName( 148 byte[] aPath, int aPos, int aEnd, 149 byte[] bPath, int bPos, int bEnd, int bMode) { 150 return coreCompare( 151 aPath, aPos, aEnd, TYPE_TREE, 152 bPath, bPos, bEnd, bMode); 153 } 154 155 private static int coreCompare( 156 byte[] aPath, int aPos, int aEnd, int aMode, 157 byte[] bPath, int bPos, int bEnd, int bMode) { 158 while (aPos < aEnd && bPos < bEnd) { 159 int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff); 160 if (cmp != 0) { 161 return cmp; 162 } 163 } 164 if (aPos < aEnd) { 165 return (aPath[aPos] & 0xff) - lastPathChar(bMode); 166 } 167 if (bPos < bEnd) { 168 return lastPathChar(aMode) - (bPath[bPos] & 0xff); 169 } 170 return 0; 171 } 172 173 private static int lastPathChar(int mode) { 174 if ((mode & TYPE_MASK) == TYPE_TREE) { 175 return '/'; 176 } 177 return 0; 178 } 179 180 private Paths() { 181 } 182 }