diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAny.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAny.java new file mode 100644 index 000000000000..43625f357b96 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAny.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2010 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD}) +public @interface CalledInAny { +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RunBackgroundable.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RunBackgroundable.java new file mode 100644 index 000000000000..22f4a7bfd081 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RunBackgroundable.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2010 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.committed; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import org.jetbrains.annotations.NotNull; + +public class RunBackgroundable { + private RunBackgroundable() { + } + + public static void run(@NotNull final Task task) { + final ProgressManager pm = ProgressManager.getInstance(); + if (ApplicationManager.getApplication().isDispatchThread()) { + pm.run(task); + } else { + task.run(pm.getProgressIndicator()); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java index 2f17758f35d1..209325d8c928 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java @@ -68,6 +68,7 @@ import org.jetbrains.idea.svn.actions.ShowPropertiesDiffWithLocalAction; import org.jetbrains.idea.svn.actions.SvnMergeProvider; import org.jetbrains.idea.svn.annotate.SvnAnnotationProvider; import org.jetbrains.idea.svn.checkin.SvnCheckinEnvironment; +import org.jetbrains.idea.svn.dialogs.SvnBranchPointsCalculator; import org.jetbrains.idea.svn.dialogs.SvnFormatWorker; import org.jetbrains.idea.svn.dialogs.WCInfo; import org.jetbrains.idea.svn.history.*; @@ -144,6 +145,8 @@ public class SvnVcs extends AbstractVcs { public static final Topic ROOTS_RELOADED = new Topic("ROOTS_RELOADED", Runnable.class); private VcsListener myVcsListener; + private SvnBranchPointsCalculator mySvnBranchPointsCalculator; + private final RootsToWorkingCopies myRootsToWorkingCopies; private final SvnAuthenticationNotifier myAuthNotifier; private static RareLogger.LogFilter[] ourLogFilters; @@ -365,6 +368,7 @@ public class SvnVcs extends AbstractVcs { FrameStateManager.getInstance().addListener(myFrameStateListener); myAuthNotifier.init(); + mySvnBranchPointsCalculator = new SvnBranchPointsCalculator(myProject); // do one time after project loaded StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new DumbAwareRunnable() { @@ -466,6 +470,8 @@ public class SvnVcs extends AbstractVcs { myAuthNotifier.stop(); myAuthNotifier.clear(); + + mySvnBranchPointsCalculator = null; } public VcsShowConfirmationOption getAddConfirmation() { @@ -1038,4 +1044,8 @@ public class SvnVcs extends AbstractVcs { public CommittedChangeList getRevisionChanges(VcsFileRevision revision, VirtualFile file) throws VcsException { return ShowAllSubmittedFilesAction.loadRevisions(getProject(), (SvnFileRevision)revision, file, false); } + + public SvnBranchPointsCalculator getSvnBranchPointsCalculator() { + return mySvnBranchPointsCalculator; + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java index 90d53fca5770..e3ba46c21b03 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java @@ -55,7 +55,7 @@ public class BranchMerger implements IMerger { } public String getComment() { - return "Merge from " + myBranchName; + return "Merge all from " + myBranchName; } public boolean hasNext() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java index 05363a1327f8..b0e488d1133b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java @@ -17,56 +17,29 @@ package org.jetbrains.idea.svn.dialogs; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.vcs.AbstractVcsHelper; -import com.intellij.openapi.vcs.CommittedChangesProvider; import com.intellij.openapi.vcs.ObjectsConvertor; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; -import com.intellij.openapi.vcs.changes.ui.ChangesViewBalloonProblemNotifier; -import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; -import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.DottedBorder; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.ui.components.labels.LinkListener; -import com.intellij.util.AsynchConsumer; -import com.intellij.util.Consumer; import com.intellij.util.containers.Convertor; import com.intellij.util.io.EqualityPolicy; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.NestedCopyType; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.WorkingCopyFormat; -import org.jetbrains.idea.svn.actions.ChangeListsMergerFactory; import org.jetbrains.idea.svn.actions.SelectBranchPopup; import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew; import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider; -import org.jetbrains.idea.svn.history.FirstInBranch; -import org.jetbrains.idea.svn.history.SvnChangeList; -import org.jetbrains.idea.svn.history.SvnRepositoryLocation; -import org.jetbrains.idea.svn.integrate.IMerger; -import org.jetbrains.idea.svn.integrate.MergerFactory; -import org.jetbrains.idea.svn.integrate.SvnIntegrateChangesTask; -import org.jetbrains.idea.svn.integrate.WorkingCopyInfo; -import org.jetbrains.idea.svn.mergeinfo.BranchInfo; -import org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache; -import org.jetbrains.idea.svn.update.UpdateEventHandler; import org.tmatesoft.svn.core.SVNDepth; -import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import javax.swing.*; @@ -77,8 +50,6 @@ import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; -import java.util.Collections; -import java.util.LinkedList; import java.util.List; public class CopiesPanel { @@ -291,149 +262,11 @@ public class CopiesPanel { private void mergeFrom(final WCInfo wcInfo, final VirtualFile root, final LinkLabel mergeLabel) { SelectBranchPopup.showForBranchRoot(myProject, root, new SelectBranchPopup.BranchSelectedCallback() { public void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision) { - if (url.equals(wcInfo.getRootUrl())) { - showErrorBalloon("Cannot merge from self"); - return; - } - ProgressManager.getInstance().run(new MergeCalculator(wcInfo, root, url, configuration.getBaseName(url))); + new QuickMerge(project, url, wcInfo, configuration.getBaseName(url)).execute(); } }, "Select branch", mergeLabel); } - private void showErrorBalloon(final String s) { - ChangesViewBalloonProblemNotifier.showMe(myProject, s, MessageType.ERROR); - } - - private class MergeCalculator extends Task.Backgroundable { - private final WCInfo myWcInfo; - private final VirtualFile myRoot; - private final String mySourceUrl; - private final String myBranchName; - private boolean myIsReintegrate; - - private final List myNotMerged; - private String myMergeTitle; - private BranchInfo myBranchInfo; - - private MergeCalculator(WCInfo wcInfo, VirtualFile root, String sourceUrl, String branchName) { - super(CopiesPanel.this.myProject, "Calculating not merged revisions", true, BackgroundFromStartOption.getInstance()); - myWcInfo = wcInfo; - myRoot = root; - mySourceUrl = sourceUrl; - myBranchName = branchName; - myNotMerged = new LinkedList(); - myMergeTitle = "Merge from " + branchName; - } - - - public void run(@NotNull ProgressIndicator indicator) { - // branch is info holder - new FirstInBranch(myVcs, myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl, - new Consumer() { - public void consume(FirstInBranch.CopyData copyData) { - if (copyData == null) { - showErrorBalloon("Merge start wasn't found"); - return; - } - - myIsReintegrate = ! copyData.isTrunkSupposedCorrect(); - if (! myWcInfo.getFormat().supportsMergeInfo()) return; - final long localLatest = Math.max(copyData.getCopyTargetRevision(), copyData.getCopySourceRevision()); - myBranchInfo = new BranchInfo(myVcs, myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl, mySourceUrl, myVcs.createWCClient()); - - final CommittedChangesProvider committedChangesProvider = - myVcs.getCommittedChangesProvider(); - final ChangeBrowserSettings settings = new ChangeBrowserSettings(); - settings.CHANGE_AFTER = Long.toString(localLatest); - try { - committedChangesProvider.loadCommittedChanges(settings, new SvnRepositoryLocation(mySourceUrl), - committedChangesProvider.getUnlimitedCountValue(), new AsynchConsumer() { - public void finished() { - } - - public void consume(CommittedChangeList committedChangeList) { - final SvnChangeList svnList = (SvnChangeList)committedChangeList; - if (localLatest >= svnList.getNumber()) return; - - final SvnMergeInfoCache.MergeCheckResult checkResult = - myBranchInfo.checkList(svnList, myWcInfo.getPath()); - if (SvnMergeInfoCache.MergeCheckResult.NOT_MERGED.equals(checkResult)) { - myNotMerged.add(svnList); - } - } - }); - } - catch (VcsException e) { - AbstractVcsHelper.getInstance(myProject).showErrors(Collections.singletonList(e), "Checking revisions for merge fault"); - } - } - }).run(); - } - - @Override - public void onCancel() { - onSuccess(); - } - - @Nullable - private MergerFactory askParameters() { - final int result = Messages.showDialog(myProject, myNotMerged.size() + " not merged revision(s) found.", myMergeTitle, - new String[]{"Merge &all", "&Select revisions to merge", "Cancel"}, 0, Messages.getQuestionIcon()); - if (result == 2) return null; - - final MergerFactory factory; - if (result == 0) { - factory = createMergeAllFactory(); - } else { - final ToBeMergedDialog dialog = new ToBeMergedDialog(myProject, myNotMerged, myMergeTitle, myBranchInfo); - dialog.show(); - if (dialog.getExitCode() == DialogWrapper.CANCEL_EXIT_CODE) { - return null; - } - final List lists = dialog.getSelected(); - if (lists.isEmpty()) return null; - factory = new ChangeListsMergerFactory(lists); - } - return factory; - } - - private MergerFactory createMergeAllFactory() { - return new MergerFactory() { - public IMerger createMerger(SvnVcs vcs, File target, UpdateEventHandler handler, SVNURL currentBranchUrl) { - return new BranchMerger(vcs, currentBranchUrl, myWcInfo.getUrl(), myWcInfo.getPath(), handler, myIsReintegrate, myBranchName); - } - }; - } - - @Override - public void onSuccess() { - if (! myWcInfo.getFormat().supportsMergeInfo()) { - doMerge(createMergeAllFactory()); - return; - } - if (myNotMerged.isEmpty()) { - ChangesViewBalloonProblemNotifier.showMe(myProject, "Everything is up-to-date", MessageType.WARNING); - return; - } - final MergerFactory factory = askParameters(); - if (factory == null) return; - doMerge(factory); - } - - private void doMerge(MergerFactory factory) { - final SVNURL sourceUrlUrl; - try { - sourceUrlUrl = SVNURL.parseURIEncoded(mySourceUrl); - } catch (SVNException e) { - showErrorBalloon("Cannot merge: " + e.getMessage()); - return; - } - final SvnIntegrateChangesTask task = new SvnIntegrateChangesTask(SvnVcs.getInstance(myProject), - new WorkingCopyInfo(myWcInfo.getPath(), true), factory, sourceUrlUrl, myMergeTitle, false); - ProgressManager.getInstance().run(task); - } - } - private void setFocusableForLinks(final LinkLabel label) { final Border border = new DottedBorder(new Insets(1,2,1,1), Color.black); label.setFocusable(true); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java new file mode 100644 index 000000000000..09748bca5d70 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2010 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 org.jetbrains.idea.svn.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.util.Consumer; +import com.intellij.util.ValueHolder; +import com.intellij.util.containers.Convertor; + +// cache. persistent. by request +public class FactsCalculator { + private final Project myProject; + private final String myTaskTitle; + private final ValueHolder myCache; + private final Convertor myLive; + + public FactsCalculator(Project project, String taskTitle, ValueHolder cache, Convertor live) { + myProject = project; + myTaskTitle = taskTitle; + myCache = cache; + myLive = live; + } + + @CalledInAwt + public void get(final In in, final Consumer resultConsumer) { + new RunOrContinuation(myProject, myTaskTitle) { + @Override + protected Out calculate() { + return myCache.getValue(in); + } + @Override + protected Out calculateLong() { + final Out result = myLive.convert(in); + if (result != null) { + myCache.setValue(result, in); + } + return result; + } + @Override + protected void processResult(Out out) { + resultConsumer.consume(out); + } + }.execute(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java new file mode 100644 index 000000000000..93fecb59feb4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java @@ -0,0 +1,229 @@ +/* + * Copyright 2000-2010 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 org.jetbrains.idea.svn.dialogs; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.openapi.vcs.changes.committed.RunBackgroundable; +import com.intellij.openapi.vcs.changes.ui.ChangesViewBalloonProblemNotifier; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.AsynchConsumer; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.actions.ChangeListsMergerFactory; +import org.jetbrains.idea.svn.history.CopyData; +import org.jetbrains.idea.svn.history.FirstInBranch; +import org.jetbrains.idea.svn.history.SvnChangeList; +import org.jetbrains.idea.svn.history.SvnRepositoryLocation; +import org.jetbrains.idea.svn.integrate.IMerger; +import org.jetbrains.idea.svn.integrate.MergerFactory; +import org.jetbrains.idea.svn.integrate.SvnIntegrateChangesTask; +import org.jetbrains.idea.svn.integrate.WorkingCopyInfo; +import org.jetbrains.idea.svn.mergeinfo.BranchInfo; +import org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache; +import org.jetbrains.idea.svn.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class QuickMerge { + private final Project myProject; + private final String myBranchName; + private final WCInfo myWcInfo; + private final String mySourceUrl; + private SvnVcs myVcs; + + public QuickMerge(Project project, String sourceUrl, WCInfo wcInfo, final String branchName) { + myProject = project; + myBranchName = branchName; + myVcs = SvnVcs.getInstance(project); + mySourceUrl = sourceUrl; + myWcInfo = wcInfo; + } + + @CalledInAwt + public void execute() { + if (mySourceUrl.equals(myWcInfo.getRootUrl())) { + showErrorBalloon("Cannot merge from self"); + return; + } + + if (! myWcInfo.getFormat().supportsMergeInfo()) { + mergeAll(); + return; + } + + final int result = Messages.showDialog(myProject, "Merge all?", "Merge from " + myBranchName, + new String[]{"Merge &all", "&Select revisions to merge", "Cancel"}, 0, Messages.getQuestionIcon()); + if (result == 2) return; + if (result == 0) { + mergeAll(); + return; + } + + ProgressManager.getInstance().run(new MergeCalculator(myProject, myWcInfo, mySourceUrl, myBranchName)); + } + + @CalledInAny + private void showErrorBalloon(final String s) { + ChangesViewBalloonProblemNotifier.showMe(myProject, s, MessageType.ERROR); + } + + // continuation... continuation.. hidden continuation.. + private void mergeAll() { + // suppose we're in branch + myVcs.getSvnBranchPointsCalculator().getFirstCopyPoint(myWcInfo.getRepositoryRoot(), mySourceUrl, myWcInfo.getRootUrl(), + new Consumer>() { + public void consume(SvnBranchPointsCalculator.WrapperInvertor result) { + if (result == null) { + showErrorBalloon("Merge start wasn't found"); + return; + } + final boolean reintegrate = result.isInvertedSense(); + final MergerFactory mergerFactory = new MergerFactory() { + public IMerger createMerger(SvnVcs vcs, File target, UpdateEventHandler handler, SVNURL currentBranchUrl) { + return new BranchMerger(vcs, currentBranchUrl, myWcInfo.getUrl(), myWcInfo.getPath(), handler, reintegrate, myBranchName); + } + }; + + final String title = "Merging all from " + myBranchName + (reintegrate ? " (reintegrate)" : ""); + doMerge(mergerFactory, title); + } + }); + } + + @CalledInAny + private void doMerge(final MergerFactory factory, final String mergeTitle) { + final SVNURL sourceUrlUrl; + try { + sourceUrlUrl = SVNURL.parseURIEncoded(mySourceUrl); + } catch (SVNException e) { + showErrorBalloon("Cannot merge: " + e.getMessage()); + return; + } + final SvnIntegrateChangesTask task = new SvnIntegrateChangesTask(SvnVcs.getInstance(myProject), + new WorkingCopyInfo(myWcInfo.getPath(), true), factory, sourceUrlUrl, mergeTitle, false); + RunBackgroundable.run(task); + } + + private class MergeCalculator extends Task.Backgroundable { + private final WCInfo myWcInfo; + private final String mySourceUrl; + private final String myBranchName; + private boolean myIsReintegrate; + + private final List myNotMerged; + private String myMergeTitle; + private BranchInfo myBranchInfo; + + private MergeCalculator(Project project, WCInfo wcInfo, String sourceUrl, String branchName) { + super(project, "Calculating not merged revisions", true, BackgroundFromStartOption.getInstance()); + myWcInfo = wcInfo; + mySourceUrl = sourceUrl; + myBranchName = branchName; + myNotMerged = new LinkedList(); + myMergeTitle = "Merge from " + branchName; + } + + public void run(@NotNull ProgressIndicator indicator) { + // branch is info holder + new FirstInBranch(myVcs, myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl, + new Consumer() { + public void consume(CopyData copyData) { + if (copyData == null) { + showErrorBalloon("Merge start wasn't found"); + return; + } + + myIsReintegrate = ! copyData.isTrunkSupposedCorrect(); + if (! myWcInfo.getFormat().supportsMergeInfo()) return; + final long localLatest = Math.max(copyData.getCopyTargetRevision(), copyData.getCopySourceRevision()); + myBranchInfo = new BranchInfo(myVcs, myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl, mySourceUrl, myVcs.createWCClient()); + + final CommittedChangesProvider committedChangesProvider = + myVcs.getCommittedChangesProvider(); + final ChangeBrowserSettings settings = new ChangeBrowserSettings(); + settings.CHANGE_AFTER = Long.toString(localLatest); + try { + committedChangesProvider.loadCommittedChanges(settings, new SvnRepositoryLocation(mySourceUrl), + committedChangesProvider.getUnlimitedCountValue(), new AsynchConsumer() { + public void finished() { + } + + public void consume(CommittedChangeList committedChangeList) { + final SvnChangeList svnList = (SvnChangeList)committedChangeList; + if (localLatest >= svnList.getNumber()) return; + + final SvnMergeInfoCache.MergeCheckResult checkResult = + myBranchInfo.checkList(svnList, myWcInfo.getPath()); + if (SvnMergeInfoCache.MergeCheckResult.NOT_MERGED.equals(checkResult)) { + myNotMerged.add(svnList); + } + } + }); + } + catch (VcsException e) { + AbstractVcsHelper.getInstance(myProject).showErrors(Collections.singletonList(e), "Checking revisions for merge fault"); + } + } + }).run(); + } + + @Override + public void onCancel() { + onSuccess(); + } + + @Nullable + private MergerFactory askParameters() { + final MergerFactory factory; + final ToBeMergedDialog dialog = new ToBeMergedDialog(myProject, myNotMerged, myMergeTitle, myBranchInfo); + dialog.show(); + if (dialog.getExitCode() == DialogWrapper.CANCEL_EXIT_CODE) { + return null; + } + final List lists = dialog.getSelected(); + if (lists.isEmpty()) return null; + factory = new ChangeListsMergerFactory(lists); + return factory; + } + + @Override + public void onSuccess() { + if (myNotMerged.isEmpty()) { + ChangesViewBalloonProblemNotifier.showMe(myProject, "Everything is up-to-date", MessageType.WARNING); + return; + } + final MergerFactory factory = askParameters(); + if (factory == null) return; + doMerge(factory, myMergeTitle); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java new file mode 100644 index 000000000000..b5e91c0dc310 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2010 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 org.jetbrains.idea.svn.dialogs; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.openapi.vcs.CalledInBackground; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class RunOrContinuation { + protected final Project myProject; + private final String myTaskTitle; + private boolean myWasCanceled; + + protected RunOrContinuation(final Project project, final String taskTitle) { + myProject = project; + myTaskTitle = taskTitle; + } + + @Nullable + @CalledInAwt + protected abstract T calculate(); + @Nullable + @CalledInBackground + protected abstract T calculateLong(); + @CalledInAwt + protected abstract void processResult(final T t); + + protected void cancel() { + myWasCanceled = true; + } + + @CalledInAwt + public void execute() { + final Ref refT = new Ref(); + refT.set(calculate()); + if ((! myWasCanceled) && (! refT.isNull())) { + processResult(refT.get()); + return; + } + ProgressManager.getInstance().run(new Task.Backgroundable(myProject, myTaskTitle, true, BackgroundFromStartOption.getInstance()) { + public void run(@NotNull ProgressIndicator indicator) { + refT.set(calculateLong()); + } + @Override + public void onSuccess() { + if ((! myWasCanceled) && (! refT.isNull())) processResult(refT.get()); + } + }); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java new file mode 100644 index 000000000000..70f46a5a3571 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java @@ -0,0 +1,324 @@ +/* + * Copyright 2000-2010 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 org.jetbrains.idea.svn.dialogs; + +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.util.Consumer; +import com.intellij.util.Processor; +import com.intellij.util.ValueHolder; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.PersistentHashMap; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.history.CopyData; +import org.jetbrains.idea.svn.history.FirstInBranch; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class SvnBranchPointsCalculator { + private final FactsCalculator> myCalculator; + + private PersistentHolder myPersistentHolder; + + public SvnBranchPointsCalculator(final Project project) { + final File vcs = new File(PathManager.getSystemPath(), "vcs"); + File file = new File(vcs, "svn_copy_sources"); + file.mkdirs(); + file = new File(file, project.getLocationHash()); + + // todo when will it die? + ValueHolder, KeyData> cache = null; + + try { + myPersistentHolder = new PersistentHolder(file); + cache = new ValueHolder, KeyData>() { + public WrapperInvertor getValue(KeyData dataHolder) { + try { + return myPersistentHolder.getBestHit(dataHolder.getRepoUrl(), dataHolder.getSourceUrl(), dataHolder.getTargetUrl()); + } catch (IOException e) { + } + return null; + } + // todo carefully check + public void setValue(WrapperInvertor value, KeyData dataHolder) { + try { + myPersistentHolder.put(dataHolder.getRepoUrl(), + value.isInvertedSense() ? dataHolder.getSourceUrl() : dataHolder.getTargetUrl(), value.getWrapped()); + } + catch (IOException e) { + } + } + }; + } + catch (IOException e) { + cache = new ValueHolder, KeyData>() { + public WrapperInvertor getValue(KeyData dataHolder) { + return null; + } + public void setValue(WrapperInvertor value, KeyData dataHolder) { + } + }; + } + + myCalculator = new FactsCalculator>(project, "Looking for branch origin", cache, new Loader(project)); + } + + private static class BranchDataExternalizer implements DataExternalizer> { + public void save(DataOutput out, TreeMap value) throws IOException { + out.writeInt(value.size()); + for (Map.Entry entry : value.entrySet()) { + out.writeUTF(entry.getKey()); + final BranchCopyData entryValue = entry.getValue(); + out.writeUTF(entryValue.getSource()); + out.writeUTF(entryValue.getTarget()); + out.writeLong(entryValue.getSourceRevision()); + out.writeLong(entryValue.getTargetRevision()); + } + } + + public TreeMap read(DataInput in) throws IOException { + final TreeMap result = new TreeMap(); + + final int num = in.readInt(); + for (int i = 0; i < num; i++) { + final String key = in.readUTF(); + final String source = in.readUTF(); + final String target = in.readUTF(); + final long sourceRevision = in.readLong(); + final long targetRevision = in.readLong(); + + result.put(key, new BranchCopyData(source, sourceRevision, target, targetRevision)); + } + return result; + } + } + + public static class WrapperInvertor> { + private final T myWrapped; + private final boolean myInvertedSense; + + public WrapperInvertor(boolean invertedSense, T wrapped) { + myInvertedSense = invertedSense; + myWrapped = wrapped; + } + + public boolean isInvertedSense() { + return myInvertedSense; + } + + public T getWrapped() { + return myWrapped; + } + + public T getTrue() { + return myInvertedSense ? myWrapped.invertSelf() : myWrapped; + } + + public T inverted() { + return myWrapped.invertSelf(); + } + } + + private static class PersistentHolder { + private final PersistentHashMap> myPersistentMap; + private final MultiMap myForSearchMap; + private final Object myLock; + + PersistentHolder(final File file) throws IOException { + myLock = new Object(); + myPersistentMap = new PersistentHashMap>( + file, new EnumeratorStringDescriptor(), new BranchDataExternalizer()); + final Ref excRef = new Ref(); + // list for values by default + myForSearchMap = new MultiMap(); + myPersistentMap.iterateData(new Processor() { + public boolean process(final String s) { + try { + final TreeMap map = myPersistentMap.get(s); + myForSearchMap.put(s, new ArrayList(map.keySet())); + } + catch (IOException e) { + excRef.set(e); + return false; + } + return true; + } + }); + if (! excRef.isNull()) { + throw excRef.get(); + } + } + + public void put(final String uid, final String target, final BranchCopyData data) throws IOException { + // todo - rewrite of rather big piece; consider rewriting + synchronized (myLock) { + TreeMap map = myPersistentMap.get(uid); + if (map == null) { + map = new TreeMap(); + } + map.put(target, data); + myPersistentMap.put(uid, map); + myForSearchMap.putValue(uid, target); + } + myPersistentMap.force(); + } + + @Nullable + public WrapperInvertor getBestHit(final String repoUrl, final String source, final String target) throws IOException { + final List keys; + synchronized (myLock) { + keys = (List) myForSearchMap.get(repoUrl); + } + // keys are never removed, so we can use 2 synchronized blocks + final String sourceMatching = getMatchingUrl(keys, source); + final String targetMatching = getMatchingUrl(keys, target); + + if (sourceMatching == null && targetMatching == null) return null; + + synchronized (myLock) { + final TreeMap map = myPersistentMap.get(repoUrl); + + final boolean sourceIsOut = sourceMatching == null; + if (sourceIsOut || targetMatching == null) { + // if found by "target" url - we correctly thought that target of copy is target + return sourceIsOut ? new WrapperInvertor(false, map.get(targetMatching)) : + new WrapperInvertor(false, map.get(sourceMatching)); + } + final BranchCopyData sourceData = map.get(sourceMatching); + final BranchCopyData targetData = map.get(targetMatching); + + final boolean inverted = sourceData.getTargetRevision() > targetData.getTargetRevision(); + return new WrapperInvertor(inverted, inverted ? sourceData : targetData); + } + } + + @Nullable + private String getMatchingUrl(List keys, String source) { + final int idx = Collections.binarySearch(keys, source); + if (idx >= 0) return keys.get(idx); + final int beforeInsertionIdx = - idx - 2; + if (beforeInsertionIdx < 0) return null; + final String candidate = keys.get(beforeInsertionIdx); + if (source.startsWith(candidate)) return candidate; + return null; + } + } + + private static class Loader implements Convertor> { + private SvnVcs myVcs; + + private Loader(final Project project) { + myVcs = SvnVcs.getInstance(project); + } + + public WrapperInvertor convert(final KeyData keyData) { + final Ref> result = new Ref>(); + + new FirstInBranch(myVcs, keyData.getRepoUrl(), keyData.getTargetUrl(), keyData.getSourceUrl(), new Consumer() { + public void consume(CopyData copyData) { + if (copyData != null) { + final boolean correct = copyData.isTrunkSupposedCorrect(); + final BranchCopyData branchCopyData; + if (correct) { + branchCopyData = new BranchCopyData(keyData.getSourceUrl(), copyData.getCopySourceRevision(), keyData.getTargetUrl(), + copyData.getCopyTargetRevision()); + } else { + branchCopyData = new BranchCopyData(keyData.getTargetUrl(), copyData.getCopySourceRevision(), keyData.getSourceUrl(), + copyData.getCopyTargetRevision()); + } + result.set(new WrapperInvertor(! correct, branchCopyData)); + } + } + }).run(); + return result.get(); + } + } + + private static class KeyData { + private final String myRepoUrl; + private final String mySourceUrl; + private final String myTargetUrl; + + public KeyData(final String repoUID, final String sourceUrl, final String targetUrl) { + myRepoUrl = repoUID; + mySourceUrl = sourceUrl; + myTargetUrl = targetUrl; + } + + public String getRepoUrl() { + return myRepoUrl; + } + + public String getSourceUrl() { + return mySourceUrl; + } + + public String getTargetUrl() { + return myTargetUrl; + } + } + + public static class BranchCopyData implements Invertor { + private final String mySource; + private final String myTarget; + private final long mySourceRevision; + private final long myTargetRevision; + + public BranchCopyData(String source, long sourceRevision, String target, long targetRevision) { + mySource = source; + mySourceRevision = sourceRevision; + myTarget = target; + myTargetRevision = targetRevision; + } + + public String getSource() { + return mySource; + } + + public long getSourceRevision() { + return mySourceRevision; + } + + public String getTarget() { + return myTarget; + } + + public long getTargetRevision() { + return myTargetRevision; + } + + public BranchCopyData invertSelf() { + return new BranchCopyData(myTarget, myTargetRevision, mySource, mySourceRevision); + } + } + + private interface Invertor { + T invertSelf(); + } + + public void getFirstCopyPoint(final String repoUID, final String sourceUrl, final String targetUrl, Consumer> consumer) { + myCalculator.get(new KeyData(repoUID, sourceUrl, targetUrl), consumer); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CopyData.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CopyData.java new file mode 100644 index 000000000000..8839cc8d6864 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CopyData.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2010 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 org.jetbrains.idea.svn.history; + +public class CopyData { + private final long myCopySourceRevision; + private final long myCopyTargetRevision; + private final boolean myTrunkSupposedCorrect; + + public CopyData(long copySourceRevision, long copyTargetRevision, boolean trunkSupposedCorrect) { + myCopySourceRevision = copySourceRevision; + myCopyTargetRevision = copyTargetRevision; + myTrunkSupposedCorrect = trunkSupposedCorrect; + } + + public long getCopySourceRevision() { + return myCopySourceRevision; + } + + public long getCopyTargetRevision() { + return myCopyTargetRevision; + } + + public boolean isTrunkSupposedCorrect() { + return myTrunkSupposedCorrect; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranch.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranch.java index f17dc92aabd7..3a301876a849 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranch.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranch.java @@ -15,6 +15,7 @@ */ package org.jetbrains.idea.svn.history; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.util.Consumer; import org.jetbrains.idea.svn.SvnVcs; import org.tmatesoft.svn.core.*; @@ -65,6 +66,8 @@ public class FirstInBranch implements Runnable { logClient.doLog(SVNURL.parseURIEncoded(myFullBranchUrl), null, SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNRevision.create(0), true, true, 0, new ISVNLogEntryHandler() { public void handleLogEntry(final SVNLogEntry logEntry) throws SVNException { + ProgressManager.checkCanceled(); + final Map map = logEntry.getChangedPaths(); for (Object o : map.values()) { final SVNLogEntryPath path = (SVNLogEntryPath) o; @@ -97,27 +100,4 @@ public class FirstInBranch implements Runnable { private static class MockException extends RuntimeException {} - public static class CopyData { - private final long myCopySourceRevision; - private final long myCopyTargetRevision; - private final boolean myTrunkSupposedCorrect; - - public CopyData(long copySourceRevision, long copyTargetRevision, boolean trunkSupposedCorrect) { - myCopySourceRevision = copySourceRevision; - myCopyTargetRevision = copyTargetRevision; - myTrunkSupposedCorrect = trunkSupposedCorrect; - } - - public long getCopySourceRevision() { - return myCopySourceRevision; - } - - public long getCopyTargetRevision() { - return myCopyTargetRevision; - } - - public boolean isTrunkSupposedCorrect() { - return myTrunkSupposedCorrect; - } - } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java index d0863d83953c..93e69a85aa1f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.dialogs.WCInfoWithBranches; import org.jetbrains.idea.svn.dialogs.WCPaths; +import org.jetbrains.idea.svn.history.CopyData; import org.jetbrains.idea.svn.history.FirstInBranch; import org.jetbrains.idea.svn.history.SvnChangeList; import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; @@ -167,8 +168,8 @@ public class SvnMergeInfoCache { myRevision = -1; ApplicationManager.getApplication().executeOnPooledThread(new FirstInBranch(vcs, repositoryRoot, branchUrl, trunkUrl, - new Consumer() { - public void consume(FirstInBranch.CopyData copyData) { + new Consumer() { + public void consume(CopyData copyData) { if (copyData == null) return; myRevision = copyData.getCopySourceRevision(); if (myRevision != -1) {