SVN: one-click merge: first ask whether to merge all; copy source persistent cache

This commit is contained in:
irengrig
2010-05-19 18:56:59 +04:00
parent 0635ef4878
commit 79262ced7f
12 changed files with 801 additions and 194 deletions
@@ -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 {
}
@@ -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());
}
}
}
@@ -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<CommittedChangeList> {
public static final Topic<Runnable> ROOTS_RELOADED = new Topic<Runnable>("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<CommittedChangeList> {
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<CommittedChangeList> {
myAuthNotifier.stop();
myAuthNotifier.clear();
mySvnBranchPointsCalculator = null;
}
public VcsShowConfirmationOption getAddConfirmation() {
@@ -1038,4 +1044,8 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> {
public CommittedChangeList getRevisionChanges(VcsFileRevision revision, VirtualFile file) throws VcsException {
return ShowAllSubmittedFilesAction.loadRevisions(getProject(), (SvnFileRevision)revision, file, false);
}
public SvnBranchPointsCalculator getSvnBranchPointsCalculator() {
return mySvnBranchPointsCalculator;
}
}
@@ -55,7 +55,7 @@ public class BranchMerger implements IMerger {
}
public String getComment() {
return "Merge from " + myBranchName;
return "Merge all from " + myBranchName;
}
public boolean hasNext() {
@@ -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<CommittedChangeList> 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<CommittedChangeList>();
myMergeTitle = "Merge from " + branchName;
}
public void run(@NotNull ProgressIndicator indicator) {
// branch is info holder
new FirstInBranch(myVcs, myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl,
new Consumer<FirstInBranch.CopyData>() {
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<SvnChangeList,ChangeBrowserSettings> 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<CommittedChangeList>() {
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<CommittedChangeList> 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);
@@ -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<In, Out> {
private final Project myProject;
private final String myTaskTitle;
private final ValueHolder<Out, In> myCache;
private final Convertor<In, Out> myLive;
public FactsCalculator(Project project, String taskTitle, ValueHolder<Out, In> cache, Convertor<In, Out> live) {
myProject = project;
myTaskTitle = taskTitle;
myCache = cache;
myLive = live;
}
@CalledInAwt
public void get(final In in, final Consumer<Out> resultConsumer) {
new RunOrContinuation<Out>(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();
}
}
@@ -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<SvnBranchPointsCalculator.WrapperInvertor<SvnBranchPointsCalculator.BranchCopyData>>() {
public void consume(SvnBranchPointsCalculator.WrapperInvertor<SvnBranchPointsCalculator.BranchCopyData> 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<CommittedChangeList> 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<CommittedChangeList>();
myMergeTitle = "Merge from " + branchName;
}
public void run(@NotNull ProgressIndicator indicator) {
// branch is info holder
new FirstInBranch(myVcs, myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl,
new Consumer<CopyData>() {
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<SvnChangeList,ChangeBrowserSettings> 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<CommittedChangeList>() {
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<CommittedChangeList> 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);
}
}
}
@@ -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<T> {
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<T> refT = new Ref<T>();
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());
}
});
}
}
@@ -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<KeyData, WrapperInvertor<BranchCopyData>> 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<WrapperInvertor<BranchCopyData>, KeyData> cache = null;
try {
myPersistentHolder = new PersistentHolder(file);
cache = new ValueHolder<WrapperInvertor<BranchCopyData>, KeyData>() {
public WrapperInvertor<BranchCopyData> 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<BranchCopyData> 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<WrapperInvertor<BranchCopyData>, KeyData>() {
public WrapperInvertor<BranchCopyData> getValue(KeyData dataHolder) {
return null;
}
public void setValue(WrapperInvertor<BranchCopyData> value, KeyData dataHolder) {
}
};
}
myCalculator = new FactsCalculator<KeyData, WrapperInvertor<BranchCopyData>>(project, "Looking for branch origin", cache, new Loader(project));
}
private static class BranchDataExternalizer implements DataExternalizer<TreeMap<String,BranchCopyData>> {
public void save(DataOutput out, TreeMap<String,BranchCopyData> value) throws IOException {
out.writeInt(value.size());
for (Map.Entry<String, BranchCopyData> 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<String,BranchCopyData> read(DataInput in) throws IOException {
final TreeMap<String,BranchCopyData> result = new TreeMap<String, BranchCopyData>();
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<T extends Invertor<T>> {
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<String, TreeMap<String, BranchCopyData>> myPersistentMap;
private final MultiMap<String, String> myForSearchMap;
private final Object myLock;
PersistentHolder(final File file) throws IOException {
myLock = new Object();
myPersistentMap = new PersistentHashMap<String, TreeMap<String, BranchCopyData>>(
file, new EnumeratorStringDescriptor(), new BranchDataExternalizer());
final Ref<IOException> excRef = new Ref<IOException>();
// list for values by default
myForSearchMap = new MultiMap<String, String>();
myPersistentMap.iterateData(new Processor<String>() {
public boolean process(final String s) {
try {
final TreeMap<String, BranchCopyData> map = myPersistentMap.get(s);
myForSearchMap.put(s, new ArrayList<String>(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<String, BranchCopyData> map = myPersistentMap.get(uid);
if (map == null) {
map = new TreeMap<String, BranchCopyData>();
}
map.put(target, data);
myPersistentMap.put(uid, map);
myForSearchMap.putValue(uid, target);
}
myPersistentMap.force();
}
@Nullable
public WrapperInvertor<BranchCopyData> getBestHit(final String repoUrl, final String source, final String target) throws IOException {
final List<String> keys;
synchronized (myLock) {
keys = (List<String>) 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<String, BranchCopyData> 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<BranchCopyData>(false, map.get(targetMatching)) :
new WrapperInvertor<BranchCopyData>(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<BranchCopyData>(inverted, inverted ? sourceData : targetData);
}
}
@Nullable
private String getMatchingUrl(List<String> 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<KeyData, WrapperInvertor<BranchCopyData>> {
private SvnVcs myVcs;
private Loader(final Project project) {
myVcs = SvnVcs.getInstance(project);
}
public WrapperInvertor<BranchCopyData> convert(final KeyData keyData) {
final Ref<WrapperInvertor<BranchCopyData>> result = new Ref<WrapperInvertor<BranchCopyData>>();
new FirstInBranch(myVcs, keyData.getRepoUrl(), keyData.getTargetUrl(), keyData.getSourceUrl(), new Consumer<CopyData>() {
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<BranchCopyData>(! 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<BranchCopyData> {
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> {
T invertSelf();
}
public void getFirstCopyPoint(final String repoUID, final String sourceUrl, final String targetUrl, Consumer<WrapperInvertor<BranchCopyData>> consumer) {
myCalculator.get(new KeyData(repoUID, sourceUrl, targetUrl), consumer);
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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<FirstInBranch.CopyData>() {
public void consume(FirstInBranch.CopyData copyData) {
new Consumer<CopyData>() {
public void consume(CopyData copyData) {
if (copyData == null) return;
myRevision = copyData.getCopySourceRevision();
if (myRevision != -1) {