View Javadoc
1   /*
2    * Copyright (C) 2022, Simeon Andreev 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.internal.diff;
12  
13  import java.io.IOException;
14  import java.util.ArrayList;
15  import java.util.Arrays;
16  import java.util.HashSet;
17  import java.util.List;
18  import java.util.Set;
19  
20  import org.eclipse.jgit.diff.DiffEntry;
21  import org.eclipse.jgit.diff.DiffEntry.ChangeType;
22  import org.eclipse.jgit.diff.RenameDetector;
23  import org.eclipse.jgit.lib.Repository;
24  import org.eclipse.jgit.treewalk.filter.PathFilter;
25  
26  /**
27   * Provides rename detection in special cases such as blame, where only a subset
28   * of the renames detected by {@link RenameDetector} is of interest.
29   */
30  public class FilteredRenameDetector {
31  
32  	private final RenameDetector renameDetector;
33  
34  	/**
35  	 * @param repository
36  	 *            The repository in which to check for renames.
37  	 */
38  	public FilteredRenameDetector(Repository repository) {
39  		this(new RenameDetector(repository));
40  	}
41  
42  	/**
43  	 * @param renameDetector
44  	 *            The {@link RenameDetector} to use when checking for renames.
45  	 */
46  	public FilteredRenameDetector(RenameDetector renameDetector) {
47  		this.renameDetector = renameDetector;
48  	}
49  
50  	/**
51  	 * @param diffs
52  	 *            The set of changes to check.
53  	 * @param pathFilter
54  	 *            Filter out changes that didn't affect this path.
55  	 * @return The subset of changes that affect only the filtered path.
56  	 * @throws IOException
57  	 */
58  	public List<DiffEntry> compute(List<DiffEntry> diffs,
59  			PathFilter pathFilter) throws IOException {
60  		return compute(diffs, Arrays.asList(pathFilter));
61  	}
62  
63  	/**
64  	 * Tries to avoid computation overhead in {@link RenameDetector#compute()}
65  	 * by filtering diffs related to the path filters only.
66  	 * <p>
67  	 * Note: current implementation only optimizes added or removed diffs,
68  	 * further optimization is possible.
69  	 *
70  	 * @param changes
71  	 *            The set of changes to check.
72  	 * @param pathFilters
73  	 *            Filter out changes that didn't affect these paths.
74  	 * @return The subset of changes that affect only the filtered paths.
75  	 * @throws IOException
76  	 * @see RenameDetector#compute()
77  	 */
78  	public List<DiffEntry> compute(List<DiffEntry> changes,
79  			List<PathFilter> pathFilters) throws IOException {
80  
81  		if (pathFilters == null) {
82  			throw new IllegalArgumentException("Must specify path filters"); //$NON-NLS-1$
83  		}
84  
85  		Set<String> paths = new HashSet<>(pathFilters.size());
86  		for (PathFilter pathFilter : pathFilters) {
87  			paths.add(pathFilter.getPath());
88  		}
89  
90  		List<DiffEntry> filtered = new ArrayList<>();
91  
92  		// For new path: skip ADD's that don't match given paths
93  		for (DiffEntry diff : changes) {
94  			ChangeType changeType = diff.getChangeType();
95  			if (changeType != ChangeType.ADD
96  					|| paths.contains(diff.getNewPath())) {
97  				filtered.add(diff);
98  			}
99  		}
100 
101 		renameDetector.reset();
102 		renameDetector.addAll(filtered);
103 		List<DiffEntry> sourceChanges = renameDetector.compute();
104 
105 		filtered.clear();
106 
107 		// For old path: skip DELETE's that don't match given paths
108 		for (DiffEntry diff : changes) {
109 			ChangeType changeType = diff.getChangeType();
110 			if (changeType != ChangeType.DELETE
111 					|| paths.contains(diff.getOldPath())) {
112 				filtered.add(diff);
113 			}
114 		}
115 
116 		renameDetector.reset();
117 		renameDetector.addAll(filtered);
118 		List<DiffEntry> targetChanges = renameDetector.compute();
119 
120 		List<DiffEntry> result = new ArrayList<>();
121 
122 		for (DiffEntry sourceChange : sourceChanges) {
123 			if (paths.contains(sourceChange.getNewPath())) {
124 				result.add(sourceChange);
125 			}
126 		}
127 		for (DiffEntry targetChange : targetChanges) {
128 			if (paths.contains(targetChange.getOldPath())) {
129 				result.add(targetChange);
130 			}
131 		}
132 
133 		renameDetector.reset();
134 		return result;
135 	}
136 }