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 class Paths {
22 /**
23 * Remove trailing {@code '/'} if present.
24 *
25 * @param path
26 * input path to potentially remove trailing {@code '/'} from.
27 * @return null if {@code path == null}; {@code path} after removing a
28 * trailing {@code '/'}.
29 */
30 public static String stripTrailingSeparator(String path) {
31 if (path == null || path.isEmpty()) {
32 return path;
33 }
34
35 int i = path.length();
36 if (path.charAt(path.length() - 1) != '/') {
37 return path;
38 }
39 do {
40 i--;
41 } while (path.charAt(i - 1) == '/');
42 return path.substring(0, i);
43 }
44
45 /**
46 * Compare two paths according to Git path sort ordering rules.
47 *
48 * @param aPath
49 * first path buffer. The range {@code [aPos, aEnd)} is used.
50 * @param aPos
51 * index into {@code aPath} where the first path starts.
52 * @param aEnd
53 * 1 past last index of {@code aPath}.
54 * @param aMode
55 * mode of the first file. Trees are sorted as though
56 * {@code aPath[aEnd] == '/'}, even if aEnd does not exist.
57 * @param bPath
58 * second path buffer. The range {@code [bPos, bEnd)} is used.
59 * @param bPos
60 * index into {@code bPath} where the second path starts.
61 * @param bEnd
62 * 1 past last index of {@code bPath}.
63 * @param bMode
64 * mode of the second file. Trees are sorted as though
65 * {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
66 * @return <0 if {@code aPath} sorts before {@code bPath};
67 * 0 if the paths are the same;
68 * >0 if {@code aPath} sorts after {@code bPath}.
69 */
70 public static int compare(byte[] aPath, int aPos, int aEnd, int aMode,
71 byte[] bPath, int bPos, int bEnd, int bMode) {
72 int cmp = coreCompare(
73 aPath, aPos, aEnd, aMode,
74 bPath, bPos, bEnd, bMode);
75 if (cmp == 0) {
76 cmp = lastPathChar(aMode) - lastPathChar(bMode);
77 }
78 return cmp;
79 }
80
81 /**
82 * Compare two paths, checking for identical name.
83 * <p>
84 * Unlike {@code compare} this method returns {@code 0} when the paths have
85 * the same characters in their names, even if the mode differs. It is
86 * intended for use in validation routines detecting duplicate entries.
87 * <p>
88 * Returns {@code 0} if the names are identical and a conflict exists
89 * between {@code aPath} and {@code bPath}, as they share the same name.
90 * <p>
91 * Returns {@code <0} if all possibles occurrences of {@code aPath} sort
92 * before {@code bPath} and no conflict can happen. In a properly sorted
93 * tree there are no other occurrences of {@code aPath} and therefore there
94 * are no duplicate names.
95 * <p>
96 * Returns {@code >0} when it is possible for a duplicate occurrence of
97 * {@code aPath} to appear later, after {@code bPath}. Callers should
98 * continue to examine candidates for {@code bPath} until the method returns
99 * one of the other return values.
100 *
101 * @param aPath
102 * first path buffer. The range {@code [aPos, aEnd)} is used.
103 * @param aPos
104 * index into {@code aPath} where the first path starts.
105 * @param aEnd
106 * 1 past last index of {@code aPath}.
107 * @param bPath
108 * second path buffer. The range {@code [bPos, bEnd)} is used.
109 * @param bPos
110 * index into {@code bPath} where the second path starts.
111 * @param bEnd
112 * 1 past last index of {@code bPath}.
113 * @param bMode
114 * mode of the second file. Trees are sorted as though
115 * {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
116 * @return <0 if no duplicate name could exist;
117 * 0 if the paths have the same name;
118 * >0 other {@code bPath} should still be checked by caller.
119 */
120 public static int compareSameName(
121 byte[] aPath, int aPos, int aEnd,
122 byte[] bPath, int bPos, int bEnd, int bMode) {
123 return coreCompare(
124 aPath, aPos, aEnd, TYPE_TREE,
125 bPath, bPos, bEnd, bMode);
126 }
127
128 private static int coreCompare(
129 byte[] aPath, int aPos, int aEnd, int aMode,
130 byte[] bPath, int bPos, int bEnd, int bMode) {
131 while (aPos < aEnd && bPos < bEnd) {
132 int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff);
133 if (cmp != 0) {
134 return cmp;
135 }
136 }
137 if (aPos < aEnd) {
138 return (aPath[aPos] & 0xff) - lastPathChar(bMode);
139 }
140 if (bPos < bEnd) {
141 return lastPathChar(aMode) - (bPath[bPos] & 0xff);
142 }
143 return 0;
144 }
145
146 private static int lastPathChar(int mode) {
147 if ((mode & TYPE_MASK) == TYPE_TREE) {
148 return '/';
149 }
150 return 0;
151 }
152
153 private Paths() {
154 }
155 }