View Javadoc
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 &lt;0 if {@code aPath} sorts before {@code bPath}; 0 if the paths
95  	 *         are the same; &gt;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 &lt;0 if no duplicate name could exist;
144 	 *         0 if the paths have the same name;
145 	 *         &gt;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 }