diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties index e113fad95cc5..25af980f994f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties @@ -631,3 +631,5 @@ svn.option.ignore.whitespace.in.annotate=Ignore whitespace differences in annota annotation.show.merge.sources.default.text=Show merge source in annotations svn.cannot.save.credentials.store-auth-creds=Cannot store credentials: forbidden by "store-auth-creds = no" annotation.original.revision=Original revision +confirmation.resolve.tree.conflict.merge.moved=Apply theirs changes to {0} moved to {1}? +confirmation.resolve.tree.conflict.merge.renamed=Apply theirs changes to {0} renamed to {1}? diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java index 347dae1fabc6..7a5bec33e780 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java @@ -38,7 +38,7 @@ import java.io.IOException; /** * @author yole */ -class SvnContentRevision implements ContentRevision { +public class SvnContentRevision implements ContentRevision { private final SvnVcs myVcs; protected final FilePath myFile; private final SVNRevision myRevision; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java index 5d4a2785d5b7..565937944504 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java @@ -16,24 +16,36 @@ package org.jetbrains.idea.svn.treeConflict; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diff.impl.patch.BinaryFilePatch; +import com.intellij.openapi.diff.impl.patch.FilePatch; +import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder; +import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; import com.intellij.openapi.progress.BackgroundTaskQueue; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.history.*; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.BeforeAfter; +import com.intellij.util.Consumer; import com.intellij.util.containers.Convertor; +import com.intellij.util.continuation.*; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.VcsBackgroundTask; -import org.jetbrains.idea.svn.ConflictedSvnChange; -import org.jetbrains.idea.svn.SvnRevisionNumber; -import org.jetbrains.idea.svn.SvnVcs; +import com.intellij.vcsUtil.VcsUtil; +import gnu.trove.TLongArrayList; +import org.jetbrains.idea.svn.*; +import org.jetbrains.idea.svn.history.SvnFileRevision; import org.jetbrains.idea.svn.history.SvnHistoryProvider; import org.jetbrains.idea.svn.history.SvnHistorySession; import org.tmatesoft.svn.core.SVNException; @@ -46,6 +58,7 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -62,6 +75,7 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { private SvnRevisionNumber myCommittedRevision; private FilePath myPath; private final List myChildDisposables; + private final TLongArrayList myRightRevisionsList; public TreeConflictRefreshablePanel(Project project, String loadingTitle, BackgroundTaskQueue queue, Change change) { super(project, loadingTitle, queue); @@ -70,6 +84,7 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { myChange = (ConflictedSvnChange) change; myPath = ChangesUtil.getFilePath(myChange); myChildDisposables = new ArrayList(); + myRightRevisionsList = new TLongArrayList(); } @Override @@ -118,8 +133,7 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { if (myChange.getBeforeRevision() != null) { myCommittedRevision = (SvnRevisionNumber)SvnHistorySession.getCurrentCommittedRevision(myVcs, myPath.getIOFile()); } - boolean differentURLs = description.getSourceLeftVersion() != null && description.getSourceRightVersion() != null && - ! Comparing.equal(description.getSourceLeftVersion().getPath(), description.getSourceRightVersion().getPath()); + boolean differentURLs = isDifferentURLs(description); ConflictSidePresentation leftSide = null; ConflictSidePresentation rightSide = null; @@ -161,6 +175,11 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { return new BeforeAfter(leftSide, rightSide); } + private boolean isDifferentURLs(SVNTreeConflictDescription description) { + return description.getSourceLeftVersion() != null && description.getSourceRightVersion() != null && + ! Comparing.equal(description.getSourceLeftVersion().getPath(), description.getSourceRightVersion().getPath()); + } + private ConflictSidePresentation createSide(SVNConflictVersion version, final SVNRevision untilThisOther, final boolean isLeft) throws VcsException { if (version == null) return EmptyConflictSide.getInstance(); SvnRevisionNumber number = null; @@ -176,7 +195,11 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { } else { return new HistoryConflictSide(myVcs, version); }*/ - return new HistoryConflictSide(myVcs, version, untilThisOther); + HistoryConflictSide side = new HistoryConflictSide(myVcs, version, untilThisOther); + if (untilThisOther != null && ! isLeft) { + side.setListToReportLoaded(myRightRevisionsList); + } + return side; } @Override @@ -230,8 +253,10 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { enableAndSetListener(createMerge(description), merge); enableAndSetListener(createLeft(description), left); enableAndSetListener(createRight(description), right); - /*wrapper.add(both); - wrapper.add(merge);*/ + //wrapper.add(both); + if (merge.isEnabled()) { + wrapper.add(merge); + } wrapper.add(left); wrapper.add(right); main.add(wrapper, gb); @@ -272,12 +297,118 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { }; } - private ActionListener createMerge(SVNTreeConflictDescription description) { - return null; //To change body of created methods use File | Settings | File Templates. + private ActionListener createMerge(final SVNTreeConflictDescription description) { + if (isDifferentURLs(description)) { + return null; + } + // my edit, theirs move or delete + /*if (SVNConflictAction.DELETE.equals(description.getConflictAction()) && description.getSourceLeftVersion() != null) { + //todo + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mergeMyEditTheirsDelete(); + } + }; + } */ + if (SVNConflictAction.EDIT.equals(description.getConflictAction()) && description.getSourceLeftVersion() != null && + SVNConflictReason.DELETED.equals(description.getConflictReason()) && (myChange.isMoved() || myChange.isRenamed()) && + myCommittedRevision != null) { + if (! myPath.isDirectory() && ! SVNNodeKind.DIR.equals(description.getSourceRightVersion().getKind())) { + return createMergeTheirsForFile(description); + } + } + return null; + } + + private ActionListener createMergeTheirsForFile(final SVNTreeConflictDescription description) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final FilePath oldFilePath = myChange.getBeforeRevision().getFile(); + final FilePath newFilePath = myChange.getAfterRevision().getFile(); + int ok = Messages.showOkCancelDialog(myVcs.getProject(), + (myChange.isMoved() ? + SvnBundle.message("confirmation.resolve.tree.conflict.merge.moved", filePath(oldFilePath), + filePath(newFilePath)) : + SvnBundle.message("confirmation.resolve.tree.conflict.merge.renamed", filePath(oldFilePath), + filePath(newFilePath))), + "Resolve tree conflict", Messages.getQuestionIcon()); + if (Messages.OK != ok) return; + + final String name = "Merge changes from theirs for: " + oldFilePath; + + final GatheringContinuationContext cc = new GatheringContinuationContext(); + cc.addExceptionHandler(VcsException.class, new Consumer() { + @Override + public void consume(VcsException e) { + AbstractVcsHelper.getInstance(myVcs.getProject()).showErrors(Collections.singletonList(e), name); + } + }); + cc.next(new TaskDescriptor("Creating patch for theirs changes", Where.POOLED) { + @Override + public void run(ContinuationContext context) { + try { + ProgressManager.progress("Getting base and theirs revisions content"); + final SvnContentRevision base = + SvnContentRevision.createBaseRevision(myVcs, newFilePath, myCommittedRevision.getRevision()); + final SvnContentRevision remote = + SvnContentRevision.createRemote(myVcs, oldFilePath, + SVNRevision.create(description.getSourceRightVersion().getPegRevision())); + final ContentRevision newBase = + new SimpleContentRevision(base.getContent(), newFilePath, base.getRevisionNumber().asString()); + final ContentRevision newRemote = + new SimpleContentRevision(remote.getContent(), newFilePath, remote.getRevisionNumber().asString()); + + ProgressManager.progress("Creating patch for theirs changes"); + final VirtualFile baseForPatch = ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public VirtualFile compute() { + return ChangesUtil.findValidParent(newFilePath); + } + }); + final Project project = myVcs.getProject(); + final List patches = IdeaTextPatchBuilder + .buildPatch(project, Collections.singletonList(new Change(newBase, newRemote)), baseForPatch.getPath(), false); + + ProgressManager.progress("Applying patch to " + newFilePath.getPath()); + final ChangeListManager clManager = ChangeListManager.getInstance(project); + final LocalChangeList changeList = clManager.getChangeList(myChange); + final PatchApplier patchApplier = + new PatchApplier(project, baseForPatch, patches, changeList, null, null); + patchApplier.scheduleSelf(false, context, true); + context.last(new TaskDescriptor("Accepting working state", Where.POOLED) { + @Override + public void run(ContinuationContext context) { + try { + new SvnTreeConflictResolver(myVcs, oldFilePath, myCommittedRevision, null).resolveSelectMineFull(description); + } + catch (VcsException e1) { + context.handleException(e1); + } + } + }); + } + catch (VcsException e1) { + context.handleException(e1); + } + } + }); + final Continuation fragmented = Continuation.createFragmented(myVcs.getProject(), false); + fragmented.run(cc.getList()); + } + }; + } + + private String filePath(FilePath newFilePath) { + return newFilePath.getName() + + " (" + + newFilePath.getParentPath().getPath() + + ")"; } private ActionListener createBoth(SVNTreeConflictDescription description) { - return null; //To change body of created methods use File | Settings | File Templates. + return null; } private void enableAndSetListener(final ActionListener al, final JButton b) { @@ -367,6 +498,7 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { private final SvnVcs myVcs; private final SVNRevision myPeg; private FileHistoryPanelImpl myFileHistoryPanel; + private TLongArrayList myListToReportLoaded; private HistoryConflictSide(SvnVcs vcs, SVNConflictVersion version, final SVNRevision peg) throws VcsException { super(vcs.getProject(), version); @@ -386,6 +518,10 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { myProvider = (SvnHistoryProvider) myVcs.getVcsHistoryProvider(); } + public void setListToReportLoaded(TLongArrayList listToReportLoaded) { + myListToReportLoaded = listToReportLoaded; + } + @Override public VcsRevisionNumber convert(VcsFileRevision o) { return o.getRevisionNumber(); @@ -400,6 +536,13 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { } else { myProvider.reportAppendableHistory(myPath, mySessionAdapter, from, myPeg, 0, myPeg, true); } + VcsAbstractHistorySession session = mySessionAdapter.getSession(); + List list = session.getRevisionList(); + if (myListToReportLoaded != null) { + for (VcsFileRevision revision : list) { + myListToReportLoaded.add(((SvnRevisionNumber) revision.getRevisionNumber()).getRevision().getNumber()); + } + } } @Override diff --git a/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle111.properties similarity index 100% rename from plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle.properties rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle111.properties