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 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 &lt;0 if no duplicate name could exist;
117 	 *         0 if the paths have the same name;
118 	 *         &gt;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 }