mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Apply patch: new algorithm for candidate files search:
1) look for by file name if there are several files with that name, select between them: 2) select those with full path matches (a/b/c/d/file) 3) make a look inside a patch and check [partially] whether context could be found and weight matching if there're relative paths (it is possible when you create patch from IDEA and the root is outside the project directory), try to find by exact path starting from project directory. It will fix cases when we have versioned directories but not under project scope (can not use index to search there) will fix IDEA-56110 "Apply patch" applies changes to the wrong file. and IDEA-53865 "Failed to apply patch for file ...: couldn't find context" (wrong base directory guess case)
This commit is contained in:
+19
@@ -109,6 +109,25 @@ public class GenericPatchApplier {
|
||||
}
|
||||
}
|
||||
|
||||
public int weightContextMatch(final int maxWalk, final int maxPartsToCheck) {
|
||||
final List<SplitHunk> hunks = new ArrayList<SplitHunk>(myHunks.size());
|
||||
for (PatchHunk hunk : myHunks) {
|
||||
hunks.addAll(SplitHunk.read(hunk));
|
||||
}
|
||||
int cntPlus = 0;
|
||||
int cnt = maxPartsToCheck;
|
||||
for (SplitHunk hunk : hunks) {
|
||||
final SplitHunk copy = createWithAllContextCopy(hunk);
|
||||
if (copy.isInsertion()) continue;
|
||||
if (testForPartialContextMatch(copy, new ExactMatchSolver(copy), maxWalk)) {
|
||||
++ cntPlus;
|
||||
}
|
||||
-- cnt;
|
||||
if (cnt == 0) break;
|
||||
}
|
||||
return cntPlus;
|
||||
}
|
||||
|
||||
public boolean execute() {
|
||||
debug("GenericPatchApplier execute started");
|
||||
if (! myHunks.isEmpty()) {
|
||||
|
||||
+2
-2
@@ -218,7 +218,7 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper {
|
||||
}
|
||||
|
||||
private void init(List<TextFilePatch> patches, final LocalChangeList localChangeList) {
|
||||
final List<FilePatchInProgress> matchedPathes = new AutoMatchIterator(myProject).execute(patches);
|
||||
final List<FilePatchInProgress> matchedPathes = new MatchPatchPaths(myProject).execute(patches);
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
@@ -311,7 +311,7 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper {
|
||||
if (patchReader == null) return;
|
||||
|
||||
final List<FilePatchInProgress> matchedPathes = patchReader == null ? Collections.<FilePatchInProgress>emptyList() :
|
||||
new AutoMatchIterator(myProject).execute(patchReader.getPatches());
|
||||
new MatchPatchPaths(myProject).execute(patchReader.getPatches());
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
public void run() {
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright 2000-2012 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.openapi.vcs.changes.patch;
|
||||
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diff.impl.patch.TextFilePatch;
|
||||
import com.intellij.openapi.diff.impl.patch.apply.GenericPatchApplier;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.vcs.ObjectsConvertor;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.containers.Convertor;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created with IntelliJ IDEA.
|
||||
* User: Irina.Chernushina
|
||||
* Date: 10/25/12
|
||||
* Time: 5:51 PM
|
||||
*/
|
||||
public class MatchPatchPaths {
|
||||
private final int ourBigFileBound = 100000;
|
||||
private final Project myProject;
|
||||
private final VirtualFile myBaseDir;
|
||||
|
||||
public MatchPatchPaths(Project project) {
|
||||
myProject = project;
|
||||
myBaseDir = myProject.getBaseDir();
|
||||
}
|
||||
|
||||
public List<FilePatchInProgress> execute(final List<TextFilePatch> list) {
|
||||
final PatchBaseDirectoryDetector directoryDetector = PatchBaseDirectoryDetector.getInstance(myProject);
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
|
||||
final List<PatchAndVariants> candidates = new ArrayList<PatchAndVariants>(list.size());
|
||||
final List<TextFilePatch> newOrWithoutMatches = new ArrayList<TextFilePatch>();
|
||||
findCandidates(list, directoryDetector, application, candidates, newOrWithoutMatches);
|
||||
|
||||
final MultiMap<VirtualFile, FilePatchInProgress> result = new MultiMap<VirtualFile, FilePatchInProgress>();
|
||||
// process exact matches: if one, leave and extract. if several - leave only them
|
||||
filterExactMatches(candidates, result);
|
||||
|
||||
// partially check by context
|
||||
selectByContext(candidates, result);
|
||||
// created or no variants
|
||||
workWithNotExisting(directoryDetector, newOrWithoutMatches, result);
|
||||
return new ArrayList<FilePatchInProgress>(result.values());
|
||||
}
|
||||
|
||||
private void workWithNotExisting(PatchBaseDirectoryDetector directoryDetector,
|
||||
List<TextFilePatch> newOrWithoutMatches,
|
||||
MultiMap<VirtualFile, FilePatchInProgress> result) {
|
||||
for (TextFilePatch patch : newOrWithoutMatches) {
|
||||
final String[] strings = patch.getAfterName().replace('\\', '/').split("/");
|
||||
Pair<VirtualFile, Integer> best = null;
|
||||
for (int i = strings.length - 2; i >= 0; -- i) {
|
||||
final String name = strings[i];
|
||||
final Collection<VirtualFile> files = directoryDetector.findFiles(name);
|
||||
if (! files.isEmpty()) {
|
||||
// check all candidates
|
||||
for (VirtualFile file : files) {
|
||||
Pair<VirtualFile, Integer> pair = compareNamesImpl(strings, file, i);
|
||||
if (pair != null && pair.getSecond() < i) {
|
||||
if (best == null || pair.getSecond() > best.getSecond()) {
|
||||
best = pair;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we found some match for lower directory, lets stop with checking its parents..
|
||||
if (best != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best != null) {
|
||||
final FilePatchInProgress patchInProgress = new FilePatchInProgress(patch, null, myBaseDir);
|
||||
patchInProgress.setNewBase(best.getFirst());
|
||||
int numDown = best.getSecond();
|
||||
for (int i = 0; i < numDown; i++) {
|
||||
patchInProgress.up();
|
||||
}
|
||||
result.putValue(best.getFirst(), patchInProgress);
|
||||
} else {
|
||||
final FilePatchInProgress patchInProgress = new FilePatchInProgress(patch, null, myBaseDir);
|
||||
result.putValue(myBaseDir, patchInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void selectByContext(List<PatchAndVariants> candidates, MultiMap<VirtualFile, FilePatchInProgress> result) {
|
||||
for (Iterator<PatchAndVariants> iterator = candidates.iterator(); iterator.hasNext(); ) {
|
||||
final PatchAndVariants candidate = iterator.next();
|
||||
int maxLines = -100;
|
||||
FilePatchInProgress best = null;
|
||||
for (FilePatchInProgress variant : candidate.getVariants()) {
|
||||
final int lines = getMatchingLines(variant);
|
||||
if (lines > maxLines) {
|
||||
maxLines = lines;
|
||||
best = variant;
|
||||
}
|
||||
}
|
||||
putSelected(result, candidate.getVariants(), best);
|
||||
}
|
||||
}
|
||||
|
||||
private void filterExactMatches(List<PatchAndVariants> candidates, MultiMap<VirtualFile, FilePatchInProgress> result) {
|
||||
for (Iterator<PatchAndVariants> iterator = candidates.iterator(); iterator.hasNext(); ) {
|
||||
final PatchAndVariants candidate = iterator.next();
|
||||
if (candidate.getVariants().size() == 1) {
|
||||
final FilePatchInProgress oneCandidate = candidate.getVariants().get(0);
|
||||
result.putValue(oneCandidate.getBase(), oneCandidate);
|
||||
iterator.remove();
|
||||
} else {
|
||||
final List<FilePatchInProgress> exact = new ArrayList<FilePatchInProgress>(candidate.getVariants().size());
|
||||
for (FilePatchInProgress patch : candidate.getVariants()) {
|
||||
if (patch.getCurrentStrip() == 0) {
|
||||
exact.add(patch);
|
||||
}
|
||||
}
|
||||
if (exact.size() == 1) {
|
||||
final FilePatchInProgress patchInProgress = exact.get(0);
|
||||
putSelected(result, candidate.getVariants(), patchInProgress);
|
||||
iterator.remove();
|
||||
} else if (! exact.isEmpty()) {
|
||||
candidate.getVariants().retainAll(exact);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void findCandidates(List<TextFilePatch> list,
|
||||
final PatchBaseDirectoryDetector directoryDetector,
|
||||
Application application,
|
||||
List<PatchAndVariants> candidates, List<TextFilePatch> newOrWithoutMatches) {
|
||||
for (final TextFilePatch patch : list) {
|
||||
final String fileName = patch.getBeforeFileName();
|
||||
if (patch.isNewFile() || (patch.getBeforeName() == null)) {
|
||||
newOrWithoutMatches.add(patch);
|
||||
continue;
|
||||
}
|
||||
final Collection<VirtualFile> files = application.runReadAction(new Computable<Collection<VirtualFile>>() {
|
||||
public Collection<VirtualFile> compute() {
|
||||
return directoryDetector.findFiles(fileName);
|
||||
}
|
||||
});
|
||||
// for directories outside the project scope but under version control
|
||||
if (patch.getBeforeName() != null && patch.getBeforeName().startsWith("..")) {
|
||||
final VirtualFile relativeFile = VfsUtil.findRelativeFile(myBaseDir, patch.getBeforeName().replace('\\', '/').split("/"));
|
||||
if (relativeFile != null) {
|
||||
files.add(relativeFile);
|
||||
}
|
||||
}
|
||||
if (files.isEmpty()) {
|
||||
newOrWithoutMatches.add(patch);
|
||||
} else {
|
||||
final List<FilePatchInProgress> variants = ObjectsConvertor.convert(files, new Convertor<VirtualFile, FilePatchInProgress>() {
|
||||
@Override
|
||||
public FilePatchInProgress convert(VirtualFile o) {
|
||||
return processMatch(patch, o);
|
||||
}
|
||||
}, ObjectsConvertor.NOT_NULL);
|
||||
if (variants.isEmpty()) {
|
||||
newOrWithoutMatches.add(patch); // just to be sure
|
||||
} else {
|
||||
candidates.add(new PatchAndVariants(patch, variants));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void putSelected(MultiMap<VirtualFile, FilePatchInProgress> result,
|
||||
final List<FilePatchInProgress> variants,
|
||||
FilePatchInProgress patchInProgress) {
|
||||
patchInProgress.setAutoBases(ObjectsConvertor.convert(variants, new Convertor<FilePatchInProgress, VirtualFile>() {
|
||||
@Override
|
||||
public VirtualFile convert(FilePatchInProgress o) {
|
||||
return o.getBase();
|
||||
}
|
||||
}, ObjectsConvertor.NOT_NULL));
|
||||
result.putValue(patchInProgress.getBase(), patchInProgress);
|
||||
}
|
||||
|
||||
private int getMatchingLines(final FilePatchInProgress patch) {
|
||||
final VirtualFile base = patch.getCurrentBase();
|
||||
if (base == null) return -1;
|
||||
String text;
|
||||
try {
|
||||
if (base.getLength() > ourBigFileBound) {
|
||||
// partially
|
||||
text = VfsUtil.loadText(base, ourBigFileBound);
|
||||
} else {
|
||||
text = VfsUtil.loadText(base);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
return new GenericPatchApplier(text, patch.getPatch().getHunks()).weightContextMatch(100, 5);
|
||||
}
|
||||
|
||||
private static class PatchAndVariants {
|
||||
private final TextFilePatch myPatch;
|
||||
private final List<FilePatchInProgress> myVariants;
|
||||
|
||||
private PatchAndVariants(TextFilePatch patch, List<FilePatchInProgress> variants) {
|
||||
myPatch = patch;
|
||||
myVariants = variants;
|
||||
}
|
||||
|
||||
public TextFilePatch getPatch() {
|
||||
return myPatch;
|
||||
}
|
||||
|
||||
public List<FilePatchInProgress> getVariants() {
|
||||
return myVariants;
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<VirtualFile, Integer> compareNames(final String beforeName, final VirtualFile file) {
|
||||
if (beforeName == null) return null;
|
||||
final String[] parts = beforeName.replace('\\', '/').split("/");
|
||||
return compareNamesImpl(parts, file.getParent(), parts.length - 2);
|
||||
}
|
||||
|
||||
private Pair<VirtualFile, Integer> compareNamesImpl(String[] parts, VirtualFile parent, int idx) {
|
||||
VirtualFile previous = parent;
|
||||
while ((parent != null) && (idx >= 0)) {
|
||||
if (! parent.getName().equals(parts[idx])) {
|
||||
return new Pair<VirtualFile, Integer>(parent, idx + 1);
|
||||
}
|
||||
previous = parent;
|
||||
parent = parent.getParent();
|
||||
-- idx;
|
||||
}
|
||||
return new Pair<VirtualFile, Integer>(parent, idx + 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FilePatchInProgress processMatch(final TextFilePatch patch, final VirtualFile file) {
|
||||
final String beforeName = patch.getBeforeName();
|
||||
/*if (beforeName == null) return null;
|
||||
final String[] parts = beforeName.replace('\\', '/').split("/");
|
||||
VirtualFile parent = file.getParent();
|
||||
int idx = parts.length - 2;
|
||||
while ((parent != null) && (idx >= 0)) {
|
||||
if (! parent.getName().equals(parts[idx])) {
|
||||
break;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
-- idx;
|
||||
}*/
|
||||
final Pair<VirtualFile, Integer> pair = compareNames(beforeName, file);
|
||||
if (pair == null) return null;
|
||||
final VirtualFile parent = pair.getFirst();
|
||||
if (parent != null) {
|
||||
final FilePatchInProgress result = new FilePatchInProgress(patch, null, myBaseDir);
|
||||
result.setNewBase(parent);
|
||||
int numDown = pair.getSecond();
|
||||
for (int i = 0; i < numDown; i++) {
|
||||
result.up();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user