diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java index 7084da5b6a7b..bba70e1f1ac8 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java @@ -109,6 +109,25 @@ public class GenericPatchApplier { } } + public int weightContextMatch(final int maxWalk, final int maxPartsToCheck) { + final List hunks = new ArrayList(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()) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java index 5e7246955838..abc059d4c686 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java @@ -218,7 +218,7 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } private void init(List patches, final LocalChangeList localChangeList) { - final List matchedPathes = new AutoMatchIterator(myProject).execute(patches); + final List 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 matchedPathes = patchReader == null ? Collections.emptyList() : - new AutoMatchIterator(myProject).execute(patchReader.getPatches()); + new MatchPatchPaths(myProject).execute(patchReader.getPatches()); SwingUtilities.invokeLater(new Runnable() { public void run() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/MatchPatchPaths.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/MatchPatchPaths.java new file mode 100644 index 000000000000..7f8b2e821d7a --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/MatchPatchPaths.java @@ -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 execute(final List list) { + final PatchBaseDirectoryDetector directoryDetector = PatchBaseDirectoryDetector.getInstance(myProject); + final Application application = ApplicationManager.getApplication(); + + final List candidates = new ArrayList(list.size()); + final List newOrWithoutMatches = new ArrayList(); + findCandidates(list, directoryDetector, application, candidates, newOrWithoutMatches); + + final MultiMap result = new MultiMap(); + // 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(result.values()); + } + + private void workWithNotExisting(PatchBaseDirectoryDetector directoryDetector, + List newOrWithoutMatches, + MultiMap result) { + for (TextFilePatch patch : newOrWithoutMatches) { + final String[] strings = patch.getAfterName().replace('\\', '/').split("/"); + Pair best = null; + for (int i = strings.length - 2; i >= 0; -- i) { + final String name = strings[i]; + final Collection files = directoryDetector.findFiles(name); + if (! files.isEmpty()) { + // check all candidates + for (VirtualFile file : files) { + Pair 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 candidates, MultiMap result) { + for (Iterator 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 candidates, MultiMap result) { + for (Iterator 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 exact = new ArrayList(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 list, + final PatchBaseDirectoryDetector directoryDetector, + Application application, + List candidates, List newOrWithoutMatches) { + for (final TextFilePatch patch : list) { + final String fileName = patch.getBeforeFileName(); + if (patch.isNewFile() || (patch.getBeforeName() == null)) { + newOrWithoutMatches.add(patch); + continue; + } + final Collection files = application.runReadAction(new Computable>() { + public Collection 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 variants = ObjectsConvertor.convert(files, new Convertor() { + @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 result, + final List variants, + FilePatchInProgress patchInProgress) { + patchInProgress.setAutoBases(ObjectsConvertor.convert(variants, new Convertor() { + @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 myVariants; + + private PatchAndVariants(TextFilePatch patch, List variants) { + myPatch = patch; + myVariants = variants; + } + + public TextFilePatch getPatch() { + return myPatch; + } + + public List getVariants() { + return myVariants; + } + } + + private Pair 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 compareNamesImpl(String[] parts, VirtualFile parent, int idx) { + VirtualFile previous = parent; + while ((parent != null) && (idx >= 0)) { + if (! parent.getName().equals(parts[idx])) { + return new Pair(parent, idx + 1); + } + previous = parent; + parent = parent.getParent(); + -- idx; + } + return new Pair(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 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; + } +}