From ddd98909c6ce950a9b8acf78532b0b2eab3aad68 Mon Sep 17 00:00:00 2001 From: irengrig Date: Tue, 3 Nov 2009 18:26:37 +0300 Subject: [PATCH] new Apply Patch UI --- .../src/com/intellij/psi/search/FilenameIndex.java | 4 + .../com/intellij/openapi/vcs/changes/Change.java | 2 +- .../vcs-api/src/com/intellij/vcsUtil/VcsUtil.java | 10 + .../openapi/diff/impl/patch/FilePatch.java | 6 +- .../diff/impl/patch/formove/PatchApplier.java | 124 ++- .../diff/impl/patch/formove/PathMerger.java | 210 +++++ .../diff/impl/patch/formove/PathsVerifier.java | 17 +- .../com/intellij/openapi/vcs}/ZipperUpdater.java | 13 +- .../vcs/changes/patch/ApplyPatchAction.java | 48 +- .../vcs/changes/patch/ApplyPatchDialog.java | 6 +- .../patch/ApplyPatchDifferentiatedDialog.java | 845 +++++++++++++++++++++ .../vcs/changes/patch/FilePatchInProgress.java | 196 +++++ .../openapi/vcs/changes/patch/FilePatchStatus.java | 23 + .../changes/patch/LazyPatchContentRevision.java | 82 ++ .../openapi/vcs/changes/ui/ChangesTreeList.java | 18 +- .../vcs/changes/ui/CommitChangeListDialog.java | 7 +- .../openapi/vcs/changes/ui/CommitLegendPanel.java | 139 ++-- platform/vcs-impl/vcs-impl.iml | 1 + .../idea/svn/history/MergeInfoUpdatesListener.java | 5 +- 19 files changed, 1613 insertions(+), 143 deletions(-) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathMerger.java rename {plugins/svn4idea/src/org/jetbrains/idea/svn/history => platform/vcs-impl/src/com/intellij/openapi/vcs}/ZipperUpdater.java (82%) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchStatus.java create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java diff --git a/platform/lang-impl/src/com/intellij/psi/search/FilenameIndex.java b/platform/lang-impl/src/com/intellij/psi/search/FilenameIndex.java index ad47b6df32..137bad286b 100644 --- a/platform/lang-impl/src/com/intellij/psi/search/FilenameIndex.java +++ b/platform/lang-impl/src/com/intellij/psi/search/FilenameIndex.java @@ -70,6 +70,10 @@ public class FilenameIndex extends ScalarIndexExtension { return ArrayUtil.toStringArray(allKeys); } + public static Collection getVirtualFilesByName(final Project project, final String name, final GlobalSearchScope scope) { + return FileBasedIndex.getInstance().getContainingFiles(NAME, name, scope); + } + public static PsiFile[] getFilesByName(final Project project, final String name, final GlobalSearchScope scope) { final Collection files = FileBasedIndex.getInstance().getContainingFiles(NAME, name, scope); if (files.isEmpty()) return PsiFile.EMPTY_ARRAY; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/Change.java b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/Change.java index 266bfca263..f7ab7dfc9c 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/Change.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/Change.java @@ -57,7 +57,7 @@ public class Change { assert beforeRevision != null || afterRevision != null; myBeforeRevision = beforeRevision; myAfterRevision = afterRevision; - myFileStatus = fileStatus; + myFileStatus = fileStatus == null ? convertStatus(beforeRevision, afterRevision) : fileStatus; } private static FileStatus convertStatus(ContentRevision beforeRevision, ContentRevision afterRevision) { diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java index 45f418eae4..c8204edca6 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java +++ b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java @@ -258,6 +258,16 @@ public class VcsUtil { }); } + @Nullable + public static VirtualFile getVirtualFileWithRefresh(final File file) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + VirtualFile result = lfs.findFileByIoFile(file); + if (result == null) { + result = lfs.refreshAndFindFileByIoFile(file); + } + return result; + } + public static String getFileContent(final String path) { return ApplicationManager.getApplication().runReadAction(new Computable() { public String compute() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java index 57939a5a02..d33a87f8c2 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/FilePatch.java @@ -95,6 +95,10 @@ public abstract class FilePatch { public ApplyPatchStatus apply(final VirtualFile fileToPatch, final ApplyPatchContext context, final Project project) throws IOException, ApplyPatchException { context.addAffectedFile(getTarget(fileToPatch)); + return applyImpl(fileToPatch, project); + } + + public ApplyPatchStatus applyImpl(final VirtualFile fileToPatch, final Project project) throws IOException, ApplyPatchException { if (isNewFile()) { applyCreate(fileToPatch); } @@ -216,4 +220,4 @@ public abstract class FilePatch { public abstract boolean isNewFile(); public abstract boolean isDeletedFile(); -} \ No newline at end of file +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java index bcd16202cd..47a2c014d5 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PatchApplier.java @@ -27,13 +27,13 @@ import com.intellij.openapi.fileTypes.ex.FileTypeChooser; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.patch.ApplyPatchAction; -import com.intellij.openapi.vcs.changes.patch.RelativePathCalculator; import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; @@ -45,6 +45,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -68,16 +69,63 @@ public class PatchApplier { myTargetChangeList = targetChangeList; myCustomForBinaries = customForBinaries; myRemainingPatches = new ArrayList(); - myVerifier = new PathsVerifier(myProject, myBaseDirectory, myPatches); + myVerifier = new PathsVerifier(myProject, myBaseDirectory, myPatches, new PathsVerifier.BaseMapper() { + @Nullable + public VirtualFile getFile(FilePatch patch, String path) { + return PathMerger.getFile(myBaseDirectory, path); + } + }); } - // todo progress public ApplyPatchStatus execute() { myRemainingPatches.addAll(myPatches); - final Ref refStatus = new Ref(ApplyPatchStatus.FAILURE); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { + final ApplyPatchStatus status = ApplicationManager.getApplication().runWriteAction(new Computable() { + public ApplyPatchStatus compute() { + final Ref refStatus = new Ref(ApplyPatchStatus.FAILURE); + CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { + public void run() { + refStatus.set(executeWritable()); + } + }, VcsBundle.message("patch.apply.command"), null); + return refStatus.get(); + } + }); + showApplyStatus(myProject, status); + refreshFiles(); + return status; + } + + public static ApplyPatchStatus executePatchGroup(final Collection group) { + if (group.isEmpty()) return ApplyPatchStatus.SUCCESS; //? + final Project project = group.iterator().next().myProject; + + ApplyPatchStatus result = ApplicationManager.getApplication().runWriteAction(new Computable() { + public ApplyPatchStatus compute() { + final Ref refStatus = new Ref(null); + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + public void run() { + for (PatchApplier applier : group) { + refStatus.set(ApplyPatchStatus.and(refStatus.get(), applier.executeWritable())); + } + } + }, VcsBundle.message("patch.apply.command"), null); + return refStatus.get(); + } + }); + result = result == null ? ApplyPatchStatus.FAILURE : result; + + for (PatchApplier applier : group) { + applier.refreshFiles(); + } + showApplyStatus(project, result); + return result; + } + + protected ApplyPatchStatus executeWritable() { + return ApplicationManager.getApplication().runWriteAction(new Computable() { + public ApplyPatchStatus compute() { + final Ref refStatus = new Ref(ApplyPatchStatus.FAILURE); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { public void run() { if (! myVerifier.execute()) { @@ -100,16 +148,12 @@ public class PatchApplier { } } // end of Command run }, VcsBundle.message("patch.apply.command"), null); + return refStatus.get(); } }); - showApplyStatus(refStatus.get()); - - refreshFiles(); - - return refStatus.get(); } - private void refreshFiles() { + protected void refreshFiles() { final List directlyAffected = myVerifier.getDirectlyAffected(); final List indirectlyAffected = myVerifier.getAllAffected(); @@ -192,14 +236,14 @@ public class PatchApplier { return status; } - private void showApplyStatus(final ApplyPatchStatus status) { + protected static void showApplyStatus(final Project project, final ApplyPatchStatus status) { if (status == ApplyPatchStatus.ALREADY_APPLIED) { - showError(myProject, VcsBundle.message("patch.apply.already.applied"), false); + showError(project, VcsBundle.message("patch.apply.already.applied"), false); } else if (status == ApplyPatchStatus.PARTIAL) { - showError(myProject, VcsBundle.message("patch.apply.partially.applied"), false); + showError(project, VcsBundle.message("patch.apply.partially.applied"), false); } else if (ApplyPatchStatus.SUCCESS.equals(status)) { - ToolWindowManager.getInstance(myProject).notifyByBalloon(ChangesViewContentManager.TOOLWINDOW_ID, MessageType.INFO, + ToolWindowManager.getInstance(project).notifyByBalloon(ChangesViewContentManager.TOOLWINDOW_ID, MessageType.INFO, VcsBundle.message("patch.apply.success.applied.text")); } } @@ -258,54 +302,6 @@ public class PatchApplier { } } - @Nullable - public static VirtualFile getFile(final VirtualFile baseDir, final String path) { - if (path == null) { - return null; - } - final List tail = new ArrayList(); - final VirtualFile file = getFile(baseDir, path, tail); - if (tail.isEmpty()) { - return file; - } - return null; - } - - @Nullable - public static VirtualFile getFile(final VirtualFile baseDir, final String path, final List tail) { - VirtualFile child = baseDir; - - final String[] pieces = RelativePathCalculator.split(path); - - for (int i = 0; i < pieces.length; i++) { - final String piece = pieces[i]; - if (child == null) { - return null; - } - if ("".equals(piece) || ".".equals(piece)) { - continue; - } - if ("..".equals(piece)) { - child = child.getParent(); - continue; - } - - VirtualFile nextChild = child.findChild(piece); - if (nextChild == null) { - if (tail != null) { - for (int j = i; j < pieces.length; j++) { - final String pieceInner = pieces[j]; - tail.add(pieceInner); - } - } - return child; - } - child = nextChild; - } - - return child; - } - private class FilesMover implements Runnable { private final ChangeListManager myChangeListManager; private final List myDirectlyAffected; diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathMerger.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathMerger.java new file mode 100644 index 0000000000..13979a7180 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathMerger.java @@ -0,0 +1,210 @@ +/* + * Copyright 2000-2009 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.diff.impl.patch.formove; + +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.vcs.changes.patch.RelativePathCalculator; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public class PathMerger { + private PathMerger() { + } + + @Nullable + public static VirtualFile getFile(final VirtualFile base, final String path) { + return getFile(new VirtualFilePathMerger(base), path); + } + + @Nullable + public static VirtualFile getFile(final VirtualFile base, final String path, final List tail) { + return getFile(new VirtualFilePathMerger(base), path, tail); + } + + @Nullable + public static File getFile(final File base, final String path) { + return getFile(new IoFilePathMerger(base), path); + } + + @Nullable + public static File getFile(final File base, final String path, final List tail) { + return getFile(new IoFilePathMerger(base), path, tail); + } + + @Nullable + public static T getFile(final FilePathMerger merger, final String path) { + if (path == null) { + return null; + } + final List tail = new ArrayList(); + final T file = getFile(merger, path, tail); + if (tail.isEmpty()) { + return file; + } + return null; + } + + @Nullable + public static T getFile(final FilePathMerger merger, final String path, final List tail) { + final String[] pieces = RelativePathCalculator.split(path); + + for (int i = 0; i < pieces.length; i++) { + final String piece = pieces[i]; + if ("".equals(piece) || ".".equals(piece)) { + continue; + } + if ("..".equals(piece)) { + final boolean upResult = merger.up(); + if (! upResult) return null; + continue; + } + + final boolean downResult = merger.down(piece); + if (! downResult) { + if (tail != null) { + for (int j = i; j < pieces.length; j++) { + final String pieceInner = pieces[j]; + tail.add(pieceInner); + } + } + return merger.getResult(); + } + } + + return merger.getResult(); + } + + @Nullable + public static VirtualFile getBase(final VirtualFile base, final String path) { + return getBase(new VirtualFilePathMerger(base), path); + } + + @Nullable + public static T getBase(final FilePathMerger merger, final String path) { + final boolean caseSensitive = SystemInfo.isFileSystemCaseSensitive; + final String[] parts = path.replace("\\", "/").split("/"); + for (int i = parts.length - 1; i >=0; --i) { + final String part = parts[i]; + if ("".equals(part) || ".".equals(part)) { + continue; + } else if ("..".equals(part)) { + if (! merger.up()) return null; + continue; + } + final String vfName = merger.getCurrentName(); + if (vfName == null) return null; + if ((caseSensitive && vfName.equals(part)) || ((! caseSensitive) && vfName.equalsIgnoreCase(part))) { + if (! merger.up()) return null; + } else { + return null; + } + } + return merger.getResult(); + } + + public static class VirtualFilePathMerger implements FilePathMerger { + private VirtualFile myCurrent; + + public VirtualFilePathMerger(final VirtualFile current) { + myCurrent = current; + } + + public boolean up() { + myCurrent = myCurrent.getParent(); + return myCurrent != null; + } + + public boolean down(final String name) { + final VirtualFile nextChild = myCurrent.findChild(name); + if (nextChild != null) { + myCurrent = nextChild; + return true; + } + return false; + } + + public VirtualFile getResult() { + return myCurrent; + } + + public String getCurrentName() { + return myCurrent == null ? null : myCurrent.getName(); + } + } + + // ! does not check result for existence! + public static class IoFilePathMerger implements FilePathMerger { + private File myBase; + private final List myChildPathElements; + + public IoFilePathMerger(final File base) { + myBase = base; + myChildPathElements = new LinkedList(); + } + + public boolean up() { + if (! myChildPathElements.isEmpty()) { + myChildPathElements.remove(myChildPathElements.size() - 1); + return true; + } + myBase = myBase.getParentFile(); + return myBase != null; + } + + public boolean down(String name) { + myChildPathElements.add(name); + return true; + } + + public File getResult() { + final StringBuilder sb = new StringBuilder(); + for (String element : myChildPathElements) { + if (sb.length() > 0) { + sb.append(File.separatorChar); + } + sb.append(element); + } + return new File(myBase, sb.toString()); + } + + @Nullable + public String getCurrentName() { + if (! myChildPathElements.isEmpty()) { + return myChildPathElements.get(myChildPathElements.size() - 1); + } + return myBase == null ? null : myBase.getName(); + } + } + + public interface FilePathMerger { + boolean up(); + + /** + * !!! should not go down (to null state), if can't find corresponding child + * @param name + * @return + */ + boolean down(final String name); + T getResult(); + @Nullable + String getCurrentName(); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java index faa9ec7c94..c5bf4a9972 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/PathsVerifier.java @@ -47,11 +47,13 @@ public class PathsVerifier { private final List> myTextPatches; private final List> myBinaryPatches; private final List myWritableFiles; + private final BaseMapper myBaseMapper; - public PathsVerifier(final Project project, final VirtualFile baseDirectory, final List patches) { + public PathsVerifier(final Project project, final VirtualFile baseDirectory, final List patches, BaseMapper baseMapper) { myProject = project; myBaseDirectory = baseDirectory; myPatches = patches; + myBaseMapper = baseMapper; myMovedFiles = new HashMap(); myBeforePaths = new ArrayList(); @@ -161,7 +163,7 @@ public class PathsVerifier { } protected boolean check() throws IOException { - final VirtualFile beforeFile = PatchApplier.getFile(myBaseDirectory, myBeforeName); + final VirtualFile beforeFile = myBaseMapper.getFile(myPatch, myBeforeName); // todo maybe deletion may be ok, just warning if (! checkExistsAndValid(beforeFile, myBeforeName)) { return false; @@ -223,7 +225,7 @@ public class PathsVerifier { setErrorMessage(fileNotFoundMessage(myAfterName)); return false; } - final VirtualFile beforeFile = PatchApplier.getFile(myBaseDirectory, myBeforeName); + final VirtualFile beforeFile = myBaseMapper.getFile(myPatch, myBeforeName); if (! checkExistsAndValid(beforeFile, myBeforeName)) { return false; } @@ -254,8 +256,8 @@ public class PathsVerifier { } public boolean canBeApplied() { - final VirtualFile beforeFile = PatchApplier.getFile(myBaseDirectory, myBeforeName); - final VirtualFile afterFile = PatchApplier.getFile(myBaseDirectory, myAfterName); + final VirtualFile beforeFile = myBaseMapper.getFile(myPatch, myBeforeName); + final VirtualFile afterFile = myBaseMapper.getFile(myPatch, myAfterName); return precheck(beforeFile, afterFile); } @@ -467,4 +469,9 @@ public class PathsVerifier { return afterFile; } } + + public interface BaseMapper { + @Nullable + VirtualFile getFile(final FilePatch patch, final String path); + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/ZipperUpdater.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ZipperUpdater.java similarity index 82% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/ZipperUpdater.java rename to platform/vcs-impl/src/com/intellij/openapi/vcs/ZipperUpdater.java index 82db8ca8d3..1d379e0261 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/ZipperUpdater.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ZipperUpdater.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.idea.svn.history; +package com.intellij.openapi.vcs; import com.intellij.openapi.progress.SomeQueue; import com.intellij.util.Alarm; @@ -23,13 +23,18 @@ public class ZipperUpdater { private final Alarm myAlarm; private boolean myRaised; private final Object myLock = new Object(); - private final static int DELAY = 300; + private final int myDelay; - public ZipperUpdater() { + public ZipperUpdater(final int delay) { + myDelay = delay; myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); } public void queue(final Runnable runnable) { + queue(runnable, false); + } + + public void queue(final Runnable runnable, final boolean urgent) { synchronized (myLock) { myRaised = true; } @@ -41,6 +46,6 @@ public class ZipperUpdater { } runnable.run(); } - }, DELAY); + }, urgent ? 0 : myDelay); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java index 52592aaff2..ea5fc30710 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java @@ -39,15 +39,17 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.FilePathImpl; -import com.intellij.openapi.vcs.VcsBundle; -import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; import com.intellij.util.Processor; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.LinkedList; import java.util.List; public class ApplyPatchAction extends AnAction { @@ -55,22 +57,34 @@ public class ApplyPatchAction extends AnAction { public void actionPerformed(AnActionEvent e) { final Project project = e.getData(PlatformDataKeys.PROJECT); - final ApplyPatchDialog dialog = new ApplyPatchDialog(project); - final VirtualFile vFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); - if (vFile != null && vFile.getFileType() == StdFileTypes.PATCH) { - dialog.setFileName(vFile.getPresentableUrl()); - } - dialog.show(); - if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return; - } - final List patches = dialog.getPatches(); - final ApplyPatchContext context = dialog.getApplyPatchContext(); + final Consumer callback = new Consumer() { + public void consume(ApplyPatchDifferentiatedDialog newDia) { + if (newDia.getExitCode() != DialogWrapper.OK_EXIT_CODE) { + return; + } - applySkipDirs(patches, context.getSkipTopDirs()); + final Collection included = newDia.getIncluded(); + final MultiMap patchGroups = new MultiMap(); + for (FilePatchInProgress patchInProgress : included) { + patchGroups.putValue(patchInProgress.getBase(), patchInProgress); + } - new PatchApplier(project, context.getBaseDir(), patches, dialog.getSelectedChangeList(), null).execute(); + final Collection appliers = new LinkedList(); + for (VirtualFile base : patchGroups.keySet()) { + final PatchApplier patchApplier = + new PatchApplier(project, base, ObjectsConvertor.convert(patchGroups.get(base), new Convertor() { + public FilePatch convert(FilePatchInProgress o) { + return o.getPatch(); + } + }), newDia.getSelectedChangeList(), null); + appliers.add(patchApplier); + } + PatchApplier.executePatchGroup(appliers); + } + }; + final ApplyPatchDifferentiatedDialog dialog = new ApplyPatchDifferentiatedDialog(project, callback); + dialog.show(); } public static void applySkipDirs(final List patches, final int skipDirs) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java index 4ca46cf15d..905e45c537 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDialog.java @@ -23,7 +23,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.patch.*; -import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; +import com.intellij.openapi.diff.impl.patch.formove.PathMerger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -200,11 +200,11 @@ public class ApplyPatchDialog extends DialogWrapper { (! patch.getBeforeName().equals(patch.getAfterName()))) { final VirtualFile baseDirectory = getBaseDirectory(); - final VirtualFile beforeFile = PatchApplier.getFile(baseDirectory, patch.getBeforeName()); + final VirtualFile beforeFile = PathMerger.getFile(baseDirectory, patch.getBeforeName()); if (beforeFile != null) { final List tail = new ArrayList(); - final VirtualFile partFile = PatchApplier.getFile(baseDirectory, patch.getAfterName(), tail); + final VirtualFile partFile = PathMerger.getFile(baseDirectory, patch.getAfterName(), tail); final StringBuilder sb = new StringBuilder(partFile.getPath()); for (String s : tail) { if (sb.charAt(sb.length() - 1) != '/') { 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 new file mode 100644 index 0000000000..17b2412569 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java @@ -0,0 +1,845 @@ +/* + * Copyright 2000-2009 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.actionSystem.*; +import com.intellij.openapi.diff.impl.patch.FilePatch; +import com.intellij.openapi.diff.impl.patch.PatchReader; +import com.intellij.openapi.diff.impl.patch.PatchSyntaxException; +import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.PopupStep; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.ZipperUpdater; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.changes.actions.ShowDiffAction; +import com.intellij.openapi.vcs.changes.ui.*; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.ProjectScope; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.SimpleColoredComponent; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Consumer; +import com.intellij.util.Icons; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.tree.DefaultTreeModel; +import java.awt.*; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class ApplyPatchDifferentiatedDialog extends DialogWrapper { + private final ZipperUpdater myLoadQueue; + private TextFieldWithBrowseButton myPatchFile; + + private final List myPatches; + private final MyChangeTreeList myChangesTreeList; + + private JComponent myCenterPanel; + private JComponent mySouthPanel; + private final Project myProject; + + private AtomicReference myRecentPathFileChange; + private ApplyPatchDifferentiatedDialog.MyUpdater myUpdater; + private Runnable myReset; + private ChangeListChooserPanel myChangeListChooser; + private ChangesLegendCalculator myInfoCalculator; + private CommitLegendPanel myCommitLegendPanel; + private final Consumer myCallback; + + public ApplyPatchDifferentiatedDialog(final Project project, final Consumer callback) { + super(project, true); + myCallback = callback; + setModal(false); + setTitle(VcsBundle.message("patch.apply.dialog.title")); + + final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) { + @Override + public boolean isFileSelectable(VirtualFile file) { + return file.getFileType() == StdFileTypes.PATCH || file.getFileType() == FileTypes.PLAIN_TEXT; + } + }; + myUpdater = new MyUpdater(); + myPatchFile = new TextFieldWithBrowseButton(); + myPatchFile.addBrowseFolderListener(VcsBundle.message("patch.apply.select.title"), "", project, descriptor); + myPatchFile.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(DocumentEvent e) { + setPathFileChangeDefault(); + myLoadQueue.queue(myUpdater); + } + }); + + myProject = project; + myLoadQueue = new ZipperUpdater(500); + myPatches = new LinkedList(); + myRecentPathFileChange = new AtomicReference(); + myChangesTreeList = new MyChangeTreeList(project, Collections.emptyList(), + new Runnable() { + public void run() { + final NamedTrinity includedTrinity = new NamedTrinity(); + final Collection includedChanges = myChangesTreeList.getIncludedChanges(); + final Set> set = new HashSet>(); + for (FilePatchInProgress.PatchChange change : includedChanges) { + final TextFilePatch patch = change.getPatchInProgress().getPatch(); + final Pair pair = new Pair(patch.getBeforeName(), patch.getAfterName()); + if (set.contains(pair)) continue; + set.add(pair); + acceptChange(includedTrinity, change); + } + myInfoCalculator.setIncluded(includedTrinity); + myCommitLegendPanel.update(); + } + }, new MyChangeNodeDecorator()); + myReset = new Runnable() { + public void run() { + reset(); + } + }; + + myChangeListChooser = new ChangeListChooserPanel(null, new Consumer() { + public void consume(final Boolean aBoolean) { + setOKActionEnabled(aBoolean); + } + }); + ChangeListManager changeListManager = ChangeListManager.getInstance(project); + myChangeListChooser.setChangeLists(changeListManager.getChangeListsCopy()); + myChangeListChooser.setDefaultSelection(changeListManager.getDefaultChangeList()); + myChangeListChooser.init(project); + + myInfoCalculator = new ChangesLegendCalculator(); + myCommitLegendPanel = new CommitLegendPanel(myInfoCalculator); + + init(); + } + + @Override + @NonNls + protected String getDimensionServiceKey() { + return "vcs.ApplyPatchDifferentiatedDialog"; + } + + private void setPathFileChangeDefault() { + myRecentPathFileChange.set(new FilePresentation(myPatchFile.getText())); + } + + public void init(final VirtualFile patchFile) { + myPatchFile.setText(patchFile.getPath()); + myRecentPathFileChange.set(new FilePresentation(patchFile)); + myLoadQueue.queue(myUpdater); + } + + private class MyUpdater implements Runnable { + public void run() { + final FilePresentation filePresentation = myRecentPathFileChange.get(); + if ((filePresentation == null) || (filePresentation.getVf() == null)) { + SwingUtilities.invokeLater(myReset); + return; + } + final VirtualFile file = filePresentation.getVf(); + + final List patches = loadPatches(file); + final List matchedPathes = autoMatch(patches); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + myChangeListChooser.setDefaultName(file.getNameWithoutExtension().replace('_', ' ').trim()); + myPatches.clear(); + myPatches.addAll(matchedPathes); + + updateTree(true); + } + }); + } + } + + private List autoMatch(final List list) { + final VirtualFile baseDir = myProject.getBaseDir(); + + final List result = new ArrayList(list.size()); + final GlobalSearchScope scope = ProjectScope.getProjectScope(myProject); + final List creations = new LinkedList(); + final MultiMap foldersDecisions = new MultiMap() { + @Override + protected Collection createCollection() { + return new HashSet(); + } + @Override + protected Collection createEmptyCollection() { + return Collections.emptySet(); + } + }; + + for (TextFilePatch patch : list) { + if (patch.isNewFile() || (patch.getBeforeName() == null)) { + creations.add(patch); + continue; + } + final String fileName = patch.getBeforeFileName(); + final Collection variants = filterVariants(patch, FilenameIndex.getVirtualFilesByName(myProject, fileName, scope)); + + final FilePatchInProgress filePatchInProgress = new FilePatchInProgress(patch, variants, baseDir); + result.add(filePatchInProgress); + final String path = extractPathWithoutName(patch.getBeforeName()); + if (path != null) { + foldersDecisions.putValue(path, filePatchInProgress.getBase()); + } + } + // then try to match creations + for (TextFilePatch creation : creations) { + final String newFileParentPath = extractPathWithoutName(creation.getAfterName()); + if (newFileParentPath == null) { + result.add(new FilePatchInProgress(creation, null, baseDir)); + } else { + final Collection variants = filterVariants(creation, foldersDecisions.get(newFileParentPath)); + result.add(new FilePatchInProgress(creation, variants, baseDir)); + } + } + + return result; + } + + private Collection filterVariants(final TextFilePatch patch, final Collection in) { + String path = patch.getBeforeName() == null ? patch.getAfterName() : patch.getBeforeName(); + path = path.replace("\\", "/"); + + final boolean caseSensitive = SystemInfo.isFileSystemCaseSensitive; + final Collection result = new LinkedList(); + for (VirtualFile vf : in) { + final String vfPath = vf.getPath(); + if ((caseSensitive && vfPath.endsWith(path)) || ((! caseSensitive) && StringUtil.endsWithIgnoreCase(vfPath, path))) { + result.add(vf); + } + } + return result; + } + + @Nullable + private String extractPathWithoutName(final String path) { + final String replaced = path.replace("\\", "/"); + final int idx = replaced.lastIndexOf('/'); + if (idx == -1) return null; + return replaced.substring(0, idx); + } + + private List loadPatches(final VirtualFile patchFile) { + if (! patchFile.isValid()) { + //todo + //queueUpdateStatus("Cannot find patch file"); + return Collections.emptyList(); + } + PatchReader reader; + try { + reader = new PatchReader(patchFile); + } + catch (IOException e) { + //todo + //queueUpdateStatus(VcsBundle.message("patch.apply.open.error", e.getMessage())); + return Collections.emptyList(); + } + final List result = new LinkedList(); + while(true) { + FilePatch patch; + try { + patch = reader.readNextPatch(); + } + catch (PatchSyntaxException e) { + // todo + if (e.getLine() >= 0) { + //queueUpdateStatus(VcsBundle.message("patch.apply.load.error.line", e.getMessage(), e.getLine())); + } + else { + //queueUpdateStatus(VcsBundle.message("patch.apply.load.error", e.getMessage())); + } + return Collections.emptyList(); + } + if (patch == null) { + break; + } + + result.add((TextFilePatch) patch); + } + if (myPatches.isEmpty()) { + // todo + //queueUpdateStatus(VcsBundle.message("patch.apply.no.patches.found")); + } + return result; + } + + private static class FilePresentation { + private final VirtualFile myVf; + private final String myPath; + + private FilePresentation(VirtualFile vf) { + myVf = vf; + myPath = null; + } + + private FilePresentation(String path) { + myPath = path; + myVf = null; + } + + @Nullable + public VirtualFile getVf() { + if (myVf != null) { + return myVf; + } + final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(myPath); + return (file != null) && (! file.isDirectory()) ? file : null; + } + } + + public void reset() { + myPatches.clear(); + myChangesTreeList.setChangesToDisplay(Collections.emptyList()); + myChangesTreeList.repaint(); + } + + @Override + protected JComponent createCenterPanel() { + if (myCenterPanel == null) { + myCenterPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(1, 1, 1, 1), 0, 0); + + final JLabel label = new JLabel(VcsBundle.message("create.patch.file.name.field")); + myCenterPanel.add(label, gb); + + ++ gb.gridx; + gb.fill = GridBagConstraints.HORIZONTAL; + gb.weightx = 1; + myCenterPanel.add(myPatchFile, gb); + + gb.gridx = 0; + ++ gb.gridy; + gb.weightx = 1; + gb.weighty = 0; + gb.fill = GridBagConstraints.HORIZONTAL; + gb.gridwidth = 2; + + final DefaultActionGroup group = new DefaultActionGroup(); + final AnAction[] treeActions = myChangesTreeList.getTreeActions(); + group.addAll(treeActions); + group.add(new MapDirectory()); + + final MyShowDiff diffAction = new MyShowDiff(); + diffAction.registerCustomShortcutSet(CommonShortcuts.getDiff(), myCenterPanel); + group.add(diffAction); + + final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar("APPLY_PATCH", group, true); + myCenterPanel.add(toolbar.getComponent(), gb); + + gb.gridx = 0; + ++ gb.gridy; + gb.weighty = 1; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.BOTH; + myCenterPanel.add(myChangesTreeList, gb); + + final JPanel wrapper = new JPanel(new GridBagLayout()); + final GridBagConstraints gb1 = + new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(1, 1, 1, 1), 0, 0); + wrapper.add(myChangeListChooser, gb1); + ++ gb1.gridx; + gb1.fill = GridBagConstraints.NONE; + gb1.weightx = 0; + gb1.insets.left = 10; + wrapper.add(myCommitLegendPanel.getComponent(), gb1); + + gb.gridx = 0; + ++ gb.gridy; + gb.weightx = 1; + gb.weighty = 0; + gb.fill = GridBagConstraints.HORIZONTAL; + myCenterPanel.add(wrapper, gb); + } + return myCenterPanel; + } + + // create change + /*private void showDiff() { + List changes = new ArrayList(); + ApplyPatchContext context = getApplyPatchContext().getPrepareContext(); + Object[] selection = myPatchContentsList.getSelectedValues(); + if (selection.length == 0) { + if (myPatches == null) return; + selection = ArrayUtil.toObjectArray(myPatches); + } + for(Object o: selection) { + final TextFilePatch patch = (TextFilePatch) o; + try { + if (patch.isNewFile()) { + final FilePath newFilePath = FilePathImpl.createNonLocal(patch.getAfterName(), false); + final String content = patch.getNewFileText(); + ContentRevision revision = new SimpleContentRevision(content, newFilePath, patch.getAfterVersionId()); + changes.add(new Change(null, revision)); + } else if ((! patch.isDeletedFile()) && (patch.getBeforeName() != null) && (patch.getAfterName() != null) && + (! patch.getBeforeName().equals(patch.getAfterName()))) { + + final VirtualFile baseDirectory = getBaseDirectory(); + final VirtualFile beforeFile = PatchApplier.getFile(baseDirectory, patch.getBeforeName()); + + if (beforeFile != null) { + final List tail = new ArrayList(); + final VirtualFile partFile = PatchApplier.getFile(baseDirectory, patch.getAfterName(), tail); + final StringBuilder sb = new StringBuilder(partFile.getPath()); + for (String s : tail) { + if (sb.charAt(sb.length() - 1) != '/') { + sb.append('/'); + } + sb.append(s); + } + + final Change change = + changeForPath(beforeFile, patch, FilePathImpl.createNonLocal(FileUtil.toSystemIndependentName(sb.toString()), false)); + if (change != null) { + changes.add(change); + } + } else { + Messages.showErrorDialog(myProject, "Cannot show difference: cannot find file " + patch.getBeforeName(), + VcsBundle.message("patch.apply.dialog.title")); + } + } + else { + final VirtualFile fileToPatch = patch.findFileToPatch(context); + if (fileToPatch != null) { + final FilePathImpl filePath = new FilePathImpl(fileToPatch); + final CurrentContentRevision currentRevision = new CurrentContentRevision(filePath); + if (patch.isDeletedFile()) { + changes.add(new Change(currentRevision, null)); + } + else { + final Change change = changeForPath(fileToPatch, patch, null); + if (change != null) { + changes.add(change); + } + } + } + } + } + catch (Exception e) { + Messages.showErrorDialog(myProject, "Error loading changes for " + patch.getAfterFileName() + ": " + e.getMessage(), + VcsBundle.message("patch.apply.dialog.title")); + return; + } + } + ShowDiffAction.showDiffForChange(changes.toArray(new Change[changes.size()]), 0, myProject, + ShowDiffAction.DiffExtendUIFactory.NONE, false); + } + + @Nullable + private Change changeForPath(final VirtualFile fileToPatch, final TextFilePatch patch, final FilePath newFilePath) { + try { + final FilePathImpl filePath = new FilePathImpl(fileToPatch); + final CurrentContentRevision currentRevision = new CurrentContentRevision(filePath); + final Document doc = FileDocumentManager.getInstance().getDocument(fileToPatch); + String baseContent = doc.getText(); + StringBuilder newText = new StringBuilder(); + patch.applyModifications(baseContent, newText); + ContentRevision revision = new SimpleContentRevision(newText.toString(), (newFilePath == null) ? filePath : newFilePath, patch.getAfterVersionId()); + return new Change(currentRevision, revision); + } catch (ApplyPatchException e) { + ApplyPatchContext context = new ApplyPatchContext(getBaseDirectory(), 0, false, false); + // just show diff here. maybe refactor further.. + ApplyPatchAction.mergeAgainstBaseVersion(myProject, fileToPatch, context, patch, ApplyPatchAction.ApplyPatchMergeRequestFactory.INSTANCE_READ_ONLY); + return null; + } + } */ + + + private static class MyChangeTreeList extends ChangesTreeList { + private MyChangeTreeList(Project project, + Collection initiallyIncluded, + @Nullable Runnable inclusionListener, + @Nullable ChangeNodeDecorator decorator) { + super(project, initiallyIncluded, true, false, inclusionListener, decorator); + } + + @Override + protected DefaultTreeModel buildTreeModel(List changes, ChangeNodeDecorator changeNodeDecorator) { + TreeModelBuilder builder = new TreeModelBuilder(myProject, false); + return builder.buildModel(ObjectsConvertor.convert(changes, + new Convertor() { + public Change convert(FilePatchInProgress.PatchChange o) { + return o; + } + }), changeNodeDecorator); + } + + @Override + protected List getSelectedObjects(ChangesBrowserNode node) { + final List under = node.getAllChangesUnder(); + return ObjectsConvertor.convert(under, new Convertor() { + public FilePatchInProgress.PatchChange convert(Change o) { + return (FilePatchInProgress.PatchChange) o; + } + }); + } + + @Override + protected FilePatchInProgress.PatchChange getLeadSelectedObject(ChangesBrowserNode node) { + final Object o = node.getUserObject(); + if (o instanceof FilePatchInProgress.PatchChange) { + return (FilePatchInProgress.PatchChange) o; + } + return null; + } + } + + private class MapDirectory extends AnAction { + private final NewBaseSelector myNewBaseSelector; + + private MapDirectory() { + super("Map base directory", "Map base directory", IconLoader.getIcon("/actions/browser-externalJavaDoc.png")); + myNewBaseSelector = new NewBaseSelector(); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + if ((selectedChanges.size() >= 1) && (sameBase(selectedChanges))) { + final FilePatchInProgress.PatchChange patchChange = selectedChanges.get(0); + final FilePatchInProgress patch = patchChange.getPatchInProgress(); + final List autoBases = patch.getAutoBasesCopy(); + if (autoBases.isEmpty() || (autoBases.size() == 1 && autoBases.get(0).equals(patch.getBase()))) { + myNewBaseSelector.run(); + } else { + autoBases.add(null); + final MapPopup step = new MapPopup(autoBases, myNewBaseSelector); + JBPopupFactory.getInstance().createListPopup(step).showCenteredInCurrentWindow(myProject); + } + } + } + + @Override + public void update(AnActionEvent e) { + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + e.getPresentation().setEnabled((selectedChanges.size() >= 1) && (sameBase(selectedChanges))); + } + } + + private boolean sameBase(final List selectedChanges) { + VirtualFile base = null; + for (FilePatchInProgress.PatchChange change : selectedChanges) { + final VirtualFile changeBase = change.getPatchInProgress().getBase(); + if (base == null) { + base = changeBase; + } else if (! base.equals(changeBase)) { + return false; + } + } + return true; + } + + private void updateTree(boolean doInitCheck) { + final List changes = getAllChanges(); + final Collection included = getIncluded(doInitCheck, changes); + myChangesTreeList.setChangesToDisplay(changes); + myChangesTreeList.setIncludedChanges(included); + myChangesTreeList.repaint(); + } + + private List getAllChanges() { + return ObjectsConvertor.convert(myPatches, + new Convertor() { + public FilePatchInProgress.PatchChange convert(FilePatchInProgress o) { + return o.getChange(); + } + }); + } + + private void acceptChange(final NamedTrinity trinity, final FilePatchInProgress.PatchChange change) { + final FilePatchInProgress patchInProgress = change.getPatchInProgress(); + if (FilePatchStatus.ADDED.equals(patchInProgress.getStatus())) { + trinity.plusAdded(); + } else if (FilePatchStatus.DELETED.equals(patchInProgress.getStatus())) { + trinity.plusDeleted(); + } else { + trinity.plusModified(); + } + } + + private Collection getIncluded(boolean doInitCheck, List changes) { + final NamedTrinity totalTrinity = new NamedTrinity(); + final NamedTrinity includedTrinity = new NamedTrinity(); + + final Collection included = new LinkedList(); + if (doInitCheck) { + for (FilePatchInProgress.PatchChange change : changes) { + acceptChange(totalTrinity, change); + final FilePatchInProgress filePatchInProgress = change.getPatchInProgress(); + if (filePatchInProgress.baseExistsOrAdded()) { + acceptChange(includedTrinity, change); + included.add(change); + } + } + } else { + // todo maybe written pretty + final Collection includedNow = myChangesTreeList.getIncludedChanges(); + final Set> toBeIncluded = new HashSet>(); + for (FilePatchInProgress.PatchChange change : includedNow) { + final FilePatchInProgress patch = change.getPatchInProgress(); + toBeIncluded.add(new Pair(patch.getPatch().getBeforeName(), patch.getPatch().getAfterName())); + } + for (FilePatchInProgress.PatchChange change : changes) { + final FilePatchInProgress patch = change.getPatchInProgress(); + final Pair pair = new Pair(patch.getPatch().getBeforeName(), patch.getPatch().getAfterName()); + acceptChange(totalTrinity, change); + if (toBeIncluded.contains(pair) && patch.baseExistsOrAdded()) { + acceptChange(includedTrinity, change); + included.add(change); + } + } + } + myInfoCalculator.setTotal(totalTrinity); + myInfoCalculator.setIncluded(includedTrinity); + myCommitLegendPanel.update(); + return included; + } + + private class NewBaseSelector implements Runnable { + public void run() { + final FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false); + VirtualFile[] selectedFiles = FileChooser.chooseFiles(myProject, descriptor); + if (selectedFiles.length != 1 || selectedFiles[0] == null) { + return; + } + final VirtualFile selectedValue = selectedFiles[0]; + + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + if (selectedChanges.size() >= 1) { + for (FilePatchInProgress.PatchChange patchChange : selectedChanges) { + final FilePatchInProgress patch = patchChange.getPatchInProgress(); + patch.setNewBase(selectedValue); + } + updateTree(false); + } + } + } + + private class MapPopup extends BaseListPopupStep { + private final Runnable myNewBaseSelector; + + private MapPopup(final @NotNull List aValues, Runnable newBaseSelector) { + super("Select base directory for a path", aValues); + myNewBaseSelector = newBaseSelector; + } + + @Override + public boolean isSpeedSearchEnabled() { + return true; + } + + @Override + public PopupStep onChosen(final VirtualFile selectedValue, boolean finalChoice) { + if (selectedValue == null) { + myNewBaseSelector.run(); + return null; + } + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + if (selectedChanges.size() >= 1) { + for (FilePatchInProgress.PatchChange patchChange : selectedChanges) { + final FilePatchInProgress patch = patchChange.getPatchInProgress(); + patch.setNewBase(selectedValue); + } + updateTree(false); + } + return null; + } + + @NotNull + @Override + public String getTextFor(VirtualFile value) { + return value == null ? "Select base for a path" : value.getPath(); + } + } + + private static class NamedTrinity { + private int myAdded; + private int myModified; + private int myDeleted; + + public NamedTrinity() { + myAdded = 0; + myModified = 0; + myDeleted = 0; + } + + public NamedTrinity(int added, int modified, int deleted) { + myAdded = added; + myModified = modified; + myDeleted = deleted; + } + + public void plusAdded() { + ++ myAdded; + } + + public void plusModified() { + ++ myModified; + } + + public void plusDeleted() { + ++ myDeleted; + } + + public int getAdded() { + return myAdded; + } + + public int getModified() { + return myModified; + } + + public int getDeleted() { + return myDeleted; + } + } + + private static class ChangesLegendCalculator implements CommitLegendPanel.InfoCalculator { + private NamedTrinity myTotal; + private NamedTrinity myIncluded; + + private ChangesLegendCalculator() { + myTotal = new NamedTrinity(); + myIncluded = new NamedTrinity(); + } + + public void setTotal(final NamedTrinity trinity) { + myTotal = trinity; + } + + public void setIncluded(final NamedTrinity trinity) { + myIncluded = trinity; + } + + public int getNew() { + return myTotal.getAdded(); + } + + public int getModified() { + return myTotal.getModified(); + } + + public int getDeleted() { + return myTotal.getDeleted(); + } + + public int getIncludedNew() { + return myIncluded.getAdded(); + } + + public int getIncludedModified() { + return myIncluded.getModified(); + } + + public int getIncludedDeleted() { + return myIncluded.getDeleted(); + } + } + + private static class MyChangeNodeDecorator implements ChangeNodeDecorator { + public void decorate(Change change, SimpleColoredComponent component) { + if (change instanceof FilePatchInProgress.PatchChange) { + final FilePatchInProgress.PatchChange patchChange = (FilePatchInProgress.PatchChange) change; + final String text; + if (FilePatchStatus.ADDED.equals(patchChange.getPatchInProgress().getStatus())) { + text = "(Added)"; + } else if (FilePatchStatus.DELETED.equals(patchChange.getPatchInProgress().getStatus())) { + text = "(Deleted)"; + } else { + text = "(Modified)"; + } + component.append(" "); + component.append(text, SimpleTextAttributes.GRAY_ATTRIBUTES); + } + } + } + + public Collection getIncluded() { + return ObjectsConvertor.convert(myChangesTreeList.getIncludedChanges(), + new Convertor() { + public FilePatchInProgress convert(FilePatchInProgress.PatchChange o) { + return o.getPatchInProgress(); + } + }); + } + + public LocalChangeList getSelectedChangeList() { + return myChangeListChooser.getSelectedList(myProject); + } + + @Override + protected void doOKAction() { + super.doOKAction(); + myCallback.consume(this); + } + + private class MyShowDiff extends AnAction { + private MyShowDiff() { + super("Show Diff", "Show Diff", IconLoader.getIcon("/actions/diff.png")); + } + + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(! myPatches.isEmpty()); + } + + public void actionPerformed(AnActionEvent e) { + if (myPatches.isEmpty()) return; + final List changes = getAllChanges(); + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + int idx = 0; + if (! selectedChanges.isEmpty()) { + final FilePatchInProgress.PatchChange c = selectedChanges.get(0); + for (FilePatchInProgress.PatchChange change : changes) { + if (change.equals(c)) { + break; + } + ++ idx; + } + } + idx = (idx == changes.size()) ? 0 : idx; + ShowDiffAction.showDiffForChange(changes.toArray(new Change[changes.size()]), idx, myProject, + ShowDiffAction.DiffExtendUIFactory.NONE, false); + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java new file mode 100644 index 0000000000..630135204f --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java @@ -0,0 +1,196 @@ +/* + * Copyright 2000-2009 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.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.diff.impl.patch.formove.PathMerger; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.CurrentContentRevision; +import com.intellij.openapi.vcs.changes.SimpleContentRevision; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class FilePatchInProgress { + private final TextFilePatch myPatch; + private final FilePatchStatus myStatus; + + private VirtualFile myBase; + private File myIoCurrentBase; + private VirtualFile myCurrentBase; + private boolean myBaseExists; + private ContentRevision myNewContentRevision; + private ContentRevision myCurrentRevision; + private final List myAutoBases; + + private File myAfterFile; + + public FilePatchInProgress(final TextFilePatch patch, final Collection autoBases, final VirtualFile baseDir) { + myPatch = patch; + if (autoBases != null) { + myAutoBases = new ArrayList(); + final String path = myPatch.getBeforeName() == null ? myPatch.getAfterName() : myPatch.getBeforeName(); + for (VirtualFile autoBase : autoBases) { + final VirtualFile willBeBase = PathMerger.getBase(autoBase, path); + if (willBeBase != null) { + myAutoBases.add(willBeBase); + } + } + } else { + myAutoBases = Collections.emptyList(); + } + myStatus = getStatus(myPatch); + if (myAutoBases.isEmpty()) { + setNewBase(baseDir); + } else { + setNewBase(myAutoBases.get(0)); + } + } + + private static FilePatchStatus getStatus(final TextFilePatch patch) { + final String beforeName = patch.getBeforeName().replace("\\", "/"); + final String afterName = patch.getAfterName().replace("\\", "/"); + + if (patch.isNewFile() || (beforeName == null)) { + return FilePatchStatus.ADDED; + } else if (patch.isDeletedFile() || (afterName == null)) { + return FilePatchStatus.DELETED; + } + + if (beforeName.equals(afterName)) return FilePatchStatus.MODIFIED; + return FilePatchStatus.MOVED_OR_RENAMED; + } + + public PatchChange getChange() { + return new PatchChange(getCurrentRevision(), getNewContentRevision(), this); + } + + public void setNewBase(final VirtualFile base) { + myBase = base; + myNewContentRevision = null; + myCurrentRevision = null; + myAfterFile = null; + + final String beforeName = myPatch.getBeforeName(); + if (beforeName != null) { + myIoCurrentBase = PathMerger.getFile(new File(myBase.getPath()), beforeName); + myCurrentBase = VcsUtil.getVirtualFileWithRefresh(myIoCurrentBase); + myBaseExists = (myCurrentBase != null) && myCurrentBase.exists(); + } else { + // creation + final String afterName = myPatch.getAfterName(); + myBaseExists = true; + myIoCurrentBase = PathMerger.getFile(new File(myBase.getPath()), afterName); + myCurrentBase = VcsUtil.getVirtualFileWithRefresh(myIoCurrentBase); + } + } + + public void setCreatedCurrentBase(final VirtualFile vf) { + myCurrentBase = vf; + } + + public FilePatchStatus getStatus() { + return myStatus; + } + + public File getIoCurrentBase() { + return myIoCurrentBase; + } + + public VirtualFile getCurrentBase() { + return myCurrentBase; + } + + public VirtualFile getBase() { + return myBase; + } + + public TextFilePatch getPatch() { + return myPatch; + } + + public boolean isBaseExists() { + return myBaseExists; + } + + public boolean baseExistsOrAdded() { + return myBaseExists || FilePatchStatus.ADDED.equals(myStatus); + } + + public ContentRevision getNewContentRevision() { + if (FilePatchStatus.DELETED.equals(myStatus)) return null; + + if (myNewContentRevision == null) { + if (FilePatchStatus.ADDED.equals(myStatus)) { + final FilePath newFilePath = FilePathImpl.createNonLocal(myIoCurrentBase.getAbsolutePath(), false); + final String content = myPatch.getNewFileText(); + myNewContentRevision = new SimpleContentRevision(content, newFilePath, myPatch.getAfterVersionId()); + } else { + final FilePath newFilePath; + if (FilePatchStatus.MOVED_OR_RENAMED.equals(myStatus)) { + newFilePath = new FilePathImpl(PathMerger.getFile(new File(myBase.getPath()), myPatch.getAfterName()), false); + } else { + newFilePath = (myCurrentBase != null) ? new FilePathImpl(myCurrentBase) : new FilePathImpl(myIoCurrentBase, false); + } + myNewContentRevision = new LazyPatchContentRevision(myCurrentBase, newFilePath, myPatch.getAfterVersionId(), myPatch); + } + } + return myNewContentRevision; + } + + public ContentRevision getCurrentRevision() { + if (FilePatchStatus.ADDED.equals(myStatus)) return null; + if (myCurrentRevision == null) { + final FilePathImpl filePath = (myCurrentBase != null) ? new FilePathImpl(myCurrentBase) : new FilePathImpl(myIoCurrentBase, false); + myCurrentRevision = new CurrentContentRevision(filePath); + } + return myCurrentRevision; + } + + public static class PatchChange extends Change { + private final FilePatchInProgress myPatchInProgress; + + public PatchChange(ContentRevision beforeRevision, ContentRevision afterRevision, FilePatchInProgress patchInProgress) { + super(beforeRevision, afterRevision, + patchInProgress.isBaseExists() || FilePatchStatus.ADDED.equals(patchInProgress.getStatus()) ? null : FileStatus.MERGED_WITH_CONFLICTS); + myPatchInProgress = patchInProgress; + } + + public FilePatchInProgress getPatchInProgress() { + return myPatchInProgress; + } + } + + public List getAutoBasesCopy() { + final ArrayList result = new ArrayList(myAutoBases.size() + 1); + result.addAll(myAutoBases); + return result; + } + + public Pair getKey() { + return new Pair(myPatch.getBeforeName(), myPatch.getAfterName()); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchStatus.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchStatus.java new file mode 100644 index 0000000000..b6e503d7d2 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchStatus.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2009 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; + +public enum FilePatchStatus { + ADDED, + MODIFIED, + DELETED, + MOVED_OR_RENAMED +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java new file mode 100644 index 0000000000..83d0829342 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/LazyPatchContentRevision.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2009 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.diff.impl.patch.ApplyPatchException; +import com.intellij.openapi.diff.impl.patch.TextFilePatch; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; + +public class LazyPatchContentRevision implements ContentRevision { + private volatile String myContent; + private final VirtualFile myVf; + private final FilePath myNewFilePath; + private final String myRevision; + private final TextFilePatch myPatch; + private volatile boolean myPatchApplyFailed; + + public LazyPatchContentRevision(final VirtualFile vf, final FilePath newFilePath, final String revision, final TextFilePatch patch) { + myVf = vf; + myNewFilePath = newFilePath; + myRevision = revision; + myPatch = patch; + } + + public String getContent() throws VcsException { + if (myContent == null) { + try { + final Document doc = FileDocumentManager.getInstance().getDocument(myVf); + final String baseContent = doc.getText(); + final StringBuilder newText = new StringBuilder(); + myPatch.applyModifications(baseContent, newText); + + myContent = newText.toString(); + } catch (ApplyPatchException e) { + myPatchApplyFailed = true; + return null; + } + } + return myContent; + } + + public boolean isPatchApplyFailed() { + return myPatchApplyFailed; + } + + @NotNull + public FilePath getFile() { + return myNewFilePath; + } + + @NotNull + public VcsRevisionNumber getRevisionNumber() { + return new VcsRevisionNumber() { + public String asString() { + return myRevision; + } + + public int compareTo(final VcsRevisionNumber o) { + return 0; + } + }; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java index ddc4972096..b1650802e7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java @@ -55,7 +55,7 @@ import java.util.List; public abstract class ChangesTreeList extends JPanel { private final Tree myTree; private final JList myList; - private final Project myProject; + protected final Project myProject; private final boolean myShowCheckboxes; private final boolean myHighlightProblems; private boolean myShowFlatten; @@ -330,6 +330,14 @@ public abstract class ChangesTreeList extends JPanel { repaint(); } + public int getSelectionCount() { + if (myShowFlatten) { + return myList.getSelectedIndices().length; + } else { + return myTree.getSelectionCount(); + } + } + @NotNull public List getSelectedChanges() { if (myShowFlatten) { @@ -399,6 +407,14 @@ public abstract class ChangesTreeList extends JPanel { } } + // no listener supposed to be called + public void setIncludedChanges(final Collection changes) { + myIncludedChanges.clear(); + myIncludedChanges.addAll(changes); + myTree.repaint(); + myList.repaint(); + } + public void includeChange(final T change) { myIncludedChanges.add(change); notifyInclusionListener(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java index 84e44c1031..d44d123f24 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java @@ -88,6 +88,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj private final Map myListComments; private String myLastSelectedListName; + private CommitLegendPanel.ChangeInfoCalculator myChangesInfoCalculator; private static class MyUpdateButtonsRunnable implements Runnable { private CommitChangeListDialog myDialog; @@ -748,7 +749,8 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj rootPane.add(browserHeader, BorderLayout.NORTH); JPanel infoPanel = new JPanel(new BorderLayout()); - myLegend = new CommitLegendPanel(); + myChangesInfoCalculator = new CommitLegendPanel.ChangeInfoCalculator(); + myLegend = new CommitLegendPanel(myChangesInfoCalculator); infoPanel.add(myLegend.getComponent(), BorderLayout.NORTH); infoPanel.add(myAdditionalOptionsPanel, BorderLayout.CENTER); rootPane.add(infoPanel, BorderLayout.EAST); @@ -899,7 +901,8 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj private void updateLegend() { if (myDisposed) return; - myLegend.update(myBrowser.getCurrentDisplayedChanges(), myBrowserExtender.getCurrentIncludedChanges()); + myChangesInfoCalculator.update(myBrowser.getCurrentDisplayedChanges(), myBrowserExtender.getCurrentIncludedChanges()); + myLegend.update(); } @NotNull diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitLegendPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitLegendPanel.java index c85cdb8b00..6c0613ccf4 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitLegendPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitLegendPanel.java @@ -24,6 +24,7 @@ import com.intellij.util.ui.UIUtil; import javax.swing.*; import java.awt.*; +import java.util.Collections; import java.util.List; /** @@ -47,8 +48,10 @@ public class CommitLegendPanel { private JLabel myDeletedLabel; private JPanel myHeadingPanel; + private final InfoCalculator myInfoCalculator; - public CommitLegendPanel() { + public CommitLegendPanel(InfoCalculator infoCalculator) { + myInfoCalculator = infoCalculator; final Color background = UIUtil.getListBackground(); myModifiedPanel.setBackground(background); myNewPanel.setBackground(background); @@ -66,15 +69,23 @@ public class CommitLegendPanel { boldLabel(myDeletedLabel, true); } - public JComponent getComponent() { + public JPanel getComponent() { return myRootPanel; } - public void update(final List displayedChanges, final List includedChanges) { - updateCategory(myTotalShown, myTotalIncluded, displayedChanges, includedChanges, ALL_FILTER); - updateCategory(myModifiedShown, myModifiedIncluded, displayedChanges, includedChanges, MODIFIED_FILTER); - updateCategory(myNewShown, myNewIncluded, displayedChanges, includedChanges, NEW_FILTER); - updateCategory(myDeletedShown, myDeletedIncluded, displayedChanges, includedChanges, DELETED_FILTER); + public void update() { + final int deleted = myInfoCalculator.getDeleted(); + final int modified = myInfoCalculator.getModified(); + final int cntNew = myInfoCalculator.getNew(); + + final int includedDeleted = myInfoCalculator.getIncludedDeleted(); + final int includedModified = myInfoCalculator.getIncludedModified(); + final int includedNew = myInfoCalculator.getIncludedNew(); + + updateCategory(myTotalShown, myTotalIncluded, deleted + modified + cntNew, includedDeleted + includedModified + includedNew); + updateCategory(myModifiedShown, myModifiedIncluded, modified, includedModified); + updateCategory(myNewShown, myNewIncluded, cntNew, includedNew); + updateCategory(myDeletedShown, myDeletedIncluded, deleted, includedDeleted); } private void createUIComponents() { @@ -85,45 +96,12 @@ public class CommitLegendPanel { boolean matches(T item); } - private static final Filter MODIFIED_FILTER = new Filter() { - public boolean matches(final Change item) { - return item.getType() == Change.Type.MODIFICATION || item.getType() == Change.Type.MOVED; - } - }; - private static final Filter NEW_FILTER = new Filter() { - public boolean matches(final Change item) { - return item.getType() == Change.Type.NEW; - } - }; - private static final Filter DELETED_FILTER = new Filter() { - public boolean matches(final Change item) { - return item.getType() == Change.Type.DELETED; - } - }; - private static final Filter ALL_FILTER = new Filter() { - public boolean matches(final Change item) { - return true; - } - }; - - private static int countMatchingItems(List items, Filter filter) { - int count = 0; - for (T item : items) { - if (filter.matches(item)) count++; - } - - return count; - } - private static void updateCategory(JLabel totalLabel, JLabel includedLabel, - List totalList, - List includedList, - Filter filter) { - int totalCount = countMatchingItems(totalList, filter); - int includedCount = countMatchingItems(includedList, filter); - updateLabel(totalLabel, totalCount, false); - updateLabel(includedLabel, includedCount, totalCount != includedCount); + int totalCnt, + int includedCnt) { + updateLabel(totalLabel, totalCnt, false); + updateLabel(includedLabel, includedCnt, totalCnt != includedCnt); } private static void updateLabel(JLabel label, int count, boolean bold) { @@ -135,4 +113,77 @@ public class CommitLegendPanel { private static void boldLabel(final JLabel label, final boolean bold) { label.setFont(label.getFont().deriveFont(bold ? Font.BOLD : Font.PLAIN)); } + + public interface InfoCalculator { + int getNew(); + int getModified(); + int getDeleted(); + int getIncludedNew(); + int getIncludedModified(); + int getIncludedDeleted(); + } + + public static class ChangeInfoCalculator implements InfoCalculator { + private List myDisplayedChanges; + private List myIncludedChanges; + + public ChangeInfoCalculator() { + myDisplayedChanges = Collections.emptyList(); + myIncludedChanges = Collections.emptyList(); + } + + public void update(final List displayedChanges, final List includedChanges) { + myDisplayedChanges = displayedChanges; + myIncludedChanges = includedChanges; + } + + public int getNew() { + return countMatchingItems(myDisplayedChanges, NEW_FILTER); + } + + public int getModified() { + return countMatchingItems(myDisplayedChanges, MODIFIED_FILTER); + } + + public int getDeleted() { + return countMatchingItems(myDisplayedChanges, DELETED_FILTER); + } + + public int getIncludedNew() { + return countMatchingItems(myIncludedChanges, NEW_FILTER); + } + + public int getIncludedModified() { + return countMatchingItems(myIncludedChanges, MODIFIED_FILTER); + } + + public int getIncludedDeleted() { + return countMatchingItems(myIncludedChanges, DELETED_FILTER); + } + + private static final Filter MODIFIED_FILTER = new Filter() { + public boolean matches(final Change item) { + return item.getType() == Change.Type.MODIFICATION || item.getType() == Change.Type.MOVED; + } + }; + private static final Filter NEW_FILTER = new Filter() { + public boolean matches(final Change item) { + return item.getType() == Change.Type.NEW; + } + }; + private static final Filter DELETED_FILTER = new Filter() { + public boolean matches(final Change item) { + return item.getType() == Change.Type.DELETED; + } + }; + + private static int countMatchingItems(List items, Filter filter) { + int count = 0; + for (T item : items) { + if (filter.matches(item)) count++; + } + + return count; + } + } } diff --git a/platform/vcs-impl/vcs-impl.iml b/platform/vcs-impl/vcs-impl.iml index 871014a7c4..2c00db69ea 100644 --- a/platform/vcs-impl/vcs-impl.iml +++ b/platform/vcs-impl/vcs-impl.iml @@ -10,6 +10,7 @@ + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java index 2e023f981b..6c5ece3f69 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java @@ -19,6 +19,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsListener; +import com.intellij.openapi.vcs.ZipperUpdater; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; import com.intellij.openapi.vcs.changes.committed.VcsConfigurationChangeListener; @@ -34,6 +35,8 @@ import java.util.ArrayList; import java.util.List; public class MergeInfoUpdatesListener { + private final static int DELAY = 300; + private final Project myProject; private final MessageBusConnection myConnection; private List myMergeInfoRefreshActions; @@ -42,7 +45,7 @@ public class MergeInfoUpdatesListener { public MergeInfoUpdatesListener(final Project project, final MessageBusConnection connection) { myConnection = connection; myProject = project; - myUpdater = new ZipperUpdater(); + myUpdater = new ZipperUpdater(DELAY); } public void addPanel(final RootsAndBranches action) { -- 2.11.4.GIT