| /* |
| * Copyright 2000-2014 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.treeConflict; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diff.impl.patch.*; |
| import com.intellij.openapi.diff.impl.patch.formove.PatchApplier; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.ui.MessageDialogBuilder; |
| import com.intellij.openapi.ui.MessageType; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.ThrowableComputable; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.changes.*; |
| import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; |
| import com.intellij.openapi.vcs.changes.patch.ApplyPatchDifferentiatedDialog; |
| import com.intellij.openapi.vcs.changes.patch.ApplyPatchExecutor; |
| import com.intellij.openapi.vcs.changes.patch.ApplyPatchMode; |
| import com.intellij.openapi.vcs.changes.patch.FilePatchInProgress; |
| import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; |
| 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.VfsUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.Convertor; |
| import com.intellij.util.containers.MultiMap; |
| import com.intellij.util.continuation.Continuation; |
| import com.intellij.util.continuation.ContinuationContext; |
| import com.intellij.util.continuation.TaskDescriptor; |
| import com.intellij.util.continuation.Where; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.idea.svn.*; |
| import org.jetbrains.idea.svn.api.Depth; |
| import org.jetbrains.idea.svn.conflict.TreeConflictDescription; |
| import org.jetbrains.idea.svn.history.SvnChangeList; |
| import org.jetbrains.idea.svn.history.SvnRepositoryLocation; |
| import org.tmatesoft.svn.core.wc.SVNRevision; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| |
| /** |
| * Created with IntelliJ IDEA. |
| * User: Irina.Chernushina |
| * Date: 5/18/12 |
| * Time: 2:44 PM |
| */ |
| public class MergeFromTheirsResolver { |
| private final SvnVcs myVcs; |
| private final TreeConflictDescription myDescription; |
| private final Change myChange; |
| private final FilePath myOldFilePath; |
| private final FilePath myNewFilePath; |
| private final String myOldPresentation; |
| private final String myNewPresentation; |
| private final SvnRevisionNumber myCommittedRevision; |
| private Boolean myAdd; |
| |
| private final List<Change> myTheirsChanges; |
| private final List<Change> myTheirsBinaryChanges; |
| private final List<VcsException> myWarnings; |
| private List<TextFilePatch> myTextPatches; |
| private VirtualFile myBaseForPatch; |
| |
| public MergeFromTheirsResolver(SvnVcs vcs, TreeConflictDescription description, Change change, SvnRevisionNumber revision) { |
| myVcs = vcs; |
| myDescription = description; |
| myChange = change; |
| myCommittedRevision = revision; |
| myOldFilePath = myChange.getBeforeRevision().getFile(); |
| myNewFilePath = myChange.getAfterRevision().getFile(); |
| myBaseForPatch = ChangesUtil.findValidParentAccurately(myNewFilePath); |
| myOldPresentation = TreeConflictRefreshablePanel.filePath(myOldFilePath); |
| myNewPresentation = TreeConflictRefreshablePanel.filePath(myNewFilePath); |
| |
| myTheirsChanges = new ArrayList<Change>(); |
| myTheirsBinaryChanges = new ArrayList<Change>(); |
| myWarnings = new ArrayList<VcsException>(); |
| myTextPatches = Collections.emptyList(); |
| } |
| |
| public void execute() { |
| int ok = Messages.showOkCancelDialog(myVcs.getProject(), (myChange.isMoved() ? |
| SvnBundle.message("confirmation.resolve.tree.conflict.merge.moved", myOldPresentation, myNewPresentation) : |
| SvnBundle.message("confirmation.resolve.tree.conflict.merge.renamed", myOldPresentation, myNewPresentation)), |
| TreeConflictRefreshablePanel.TITLE, Messages.getQuestionIcon()); |
| if (Messages.OK != ok) return; |
| |
| FileDocumentManager.getInstance().saveAllDocuments(); |
| //final String name = "Merge changes from theirs for: " + myOldPresentation; |
| |
| final Continuation fragmented = Continuation.createFragmented(myVcs.getProject(), false); |
| fragmented.addExceptionHandler(VcsException.class, new Consumer<VcsException>() { |
| @Override |
| public void consume(VcsException e) { |
| myWarnings.add(e); |
| if (e.isWarning()) { |
| return; |
| } |
| AbstractVcsHelper.getInstance(myVcs.getProject()).showErrors(myWarnings, TreeConflictRefreshablePanel.TITLE); |
| } |
| }); |
| |
| final List<TaskDescriptor> tasks = new SmartList<TaskDescriptor>(); |
| tasks.add(myDescription.isDirectory() ? new PreloadChangesContentsForDir() : new PreloadChangesContentsForFile()); |
| tasks.add(new ConvertTextPaths()); |
| tasks.add(new PatchCreator()); |
| tasks.add(new SelectPatchesInApplyPatchDialog()); |
| tasks.add(new SelectBinaryFiles()); |
| |
| fragmented.run(tasks); |
| } |
| |
| private void appendResolveConflictToContext(final ContinuationContext context) { |
| context.next(new ResolveConflictInSvn()); |
| } |
| |
| private void appendTailToContextLast(final ContinuationContext context) { |
| context.last(new ApplyBinaryChanges(), new FinalNotification()); |
| } |
| |
| private List<Change> filterOutBinary(List<Change> paths) { |
| List<Change> result = null; |
| for (Iterator<Change> iterator = paths.iterator(); iterator.hasNext(); ) { |
| final Change change = iterator.next(); |
| if (ChangesUtil.isBinaryChange(change)) { |
| result = (result == null ? new SmartList<Change>() : result); |
| result.add(change); |
| iterator.remove(); |
| } |
| } |
| return result; |
| } |
| |
| private class FinalNotification extends TaskDescriptor { |
| private FinalNotification() { |
| super("", Where.AWT); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| final StringBuilder message = new StringBuilder().append("Theirs changes merged for ").append(myOldPresentation); |
| VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), message.toString(), MessageType.INFO); |
| if (! myWarnings.isEmpty()) { |
| AbstractVcsHelper.getInstance(myVcs.getProject()).showErrors(myWarnings, TreeConflictRefreshablePanel.TITLE); |
| } |
| } |
| } |
| |
| private class ResolveConflictInSvn extends TaskDescriptor { |
| private ResolveConflictInSvn() { |
| super("Accepting working state", Where.POOLED); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| try { |
| new SvnTreeConflictResolver(myVcs, myOldFilePath, myCommittedRevision, null).resolveSelectMineFull(myDescription); |
| } |
| catch (VcsException e1) { |
| context.handleException(e1, false); |
| } |
| } |
| } |
| |
| private class ConvertTextPaths extends TaskDescriptor { |
| private ConvertTextPaths() { |
| super("", Where.AWT); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| initAddOption(); |
| List<Change> convertedChanges = new SmartList<Change>(); |
| try { |
| // revision contents is preloaded, so ok to call in awt |
| convertedChanges = convertPaths(myTheirsChanges); |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| } |
| myTheirsChanges.clear(); |
| myTheirsChanges.addAll(convertedChanges); |
| } |
| } |
| |
| private class SelectPatchesInApplyPatchDialog extends TaskDescriptor { |
| private SelectPatchesInApplyPatchDialog() { |
| super("", Where.AWT); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| final ChangeListManager clManager = ChangeListManager.getInstance(myVcs.getProject()); |
| final LocalChangeList changeList = clManager.getChangeList(myChange); |
| final ApplyPatchDifferentiatedDialog dialog = new ApplyPatchDifferentiatedDialog(myVcs.getProject(), |
| new TreeConflictApplyTheirsPatchExecutor(myVcs, context, myBaseForPatch), |
| Collections.<ApplyPatchExecutor>singletonList(new ApplyPatchSaveToFileExecutor(myVcs.getProject(), myBaseForPatch)), |
| ApplyPatchMode.APPLY_PATCH_IN_MEMORY, myTextPatches, changeList); |
| context.suspend(); |
| dialog.show(); |
| } |
| } |
| |
| private class TreeConflictApplyTheirsPatchExecutor implements ApplyPatchExecutor { |
| private final SvnVcs myVcs; |
| private final ContinuationContext myInner; |
| private final VirtualFile myBaseDir; |
| |
| public TreeConflictApplyTheirsPatchExecutor(SvnVcs vcs, ContinuationContext inner, final VirtualFile baseDir) { |
| myVcs = vcs; |
| myInner = inner; |
| myBaseDir = baseDir; |
| } |
| |
| @Override |
| public String getName() { |
| return "Apply patch"; |
| } |
| |
| @Override |
| public void apply(MultiMap<VirtualFile, FilePatchInProgress> patchGroups, LocalChangeList localList, String fileName, |
| TransparentlyFailedValueI<Map<String, Map<String, CharSequence>>, PatchSyntaxException> additionalInfo) { |
| final List<FilePatch> patches; |
| try { |
| patches = ApplyPatchSaveToFileExecutor.patchGroupsToOneGroup(patchGroups, myBaseDir); |
| } |
| catch (IOException e) { |
| myInner.handleException(e, true); |
| return; |
| } |
| |
| final PatchApplier<BinaryFilePatch> patchApplier = |
| new PatchApplier<BinaryFilePatch>(myVcs.getProject(), myBaseDir, patches, localList, null, null); |
| patchApplier.scheduleSelf(false, myInner, true); // 3 |
| boolean thereAreCreations = false; |
| for (FilePatch patch : patches) { |
| if (patch.isNewFile() || ! Comparing.equal(patch.getAfterName(), patch.getBeforeName())) { |
| thereAreCreations = true; |
| break; |
| } |
| } |
| if (thereAreCreations) { |
| // restore deletion of old directory: |
| myInner.next(new DirectoryAddition()); // 2 |
| } |
| appendResolveConflictToContext(myInner); // 1 |
| appendTailToContextLast(myInner); // 4 |
| myInner.ping(); |
| } |
| } |
| |
| private class DirectoryAddition extends TaskDescriptor { |
| private DirectoryAddition() { |
| super("Adding " + myOldPresentation + " to Subversion", Where.POOLED); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| try { |
| // TODO: Previously SVNKit client was invoked with mkDir=true option - so corresponding directory would be created. Now mkDir=false |
| // TODO: is used. Command line also does not support automatic directory creation. |
| // TODO: Need to check additionally if there are cases when directory does not exist and add corresponding code. |
| myVcs.getFactory(myOldFilePath.getIOFile()).createAddClient() |
| .add(myOldFilePath.getIOFile(), Depth.EMPTY, true, false, true, null); |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| } |
| } |
| } |
| |
| private class PatchCreator extends TaskDescriptor { |
| private PatchCreator() { |
| super("Creating patch for theirs changes", Where.POOLED); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| final Project project = myVcs.getProject(); |
| final List<FilePatch> patches; |
| try { |
| patches = IdeaTextPatchBuilder.buildPatch(project, myTheirsChanges, myBaseForPatch.getPath(), false); |
| myTextPatches = ObjectsConvertor.convert(patches, new Convertor<FilePatch, TextFilePatch>() { |
| @Override |
| public TextFilePatch convert(FilePatch o) { |
| return (TextFilePatch)o; |
| } |
| }); |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| } |
| } |
| } |
| |
| private class SelectBinaryFiles extends TaskDescriptor { |
| private SelectBinaryFiles() { |
| super("", Where.AWT); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| if (myTheirsBinaryChanges.isEmpty()) return; |
| final List<Change> converted; |
| try { |
| converted = convertPaths(myTheirsBinaryChanges); |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| return; |
| } |
| if (converted.isEmpty()) return; |
| final Map<FilePath, Change> map = new HashMap<FilePath, Change>(); |
| for (Change change : converted) { |
| map.put(ChangesUtil.getFilePath(change), change); |
| } |
| final Collection<FilePath> selected = chooseBinaryFiles(converted, map.keySet()); |
| myTheirsBinaryChanges.clear(); |
| for (FilePath filePath : selected) { |
| myTheirsBinaryChanges.add(map.get(filePath)); |
| } |
| } |
| } |
| |
| private class ApplyBinaryChanges extends TaskDescriptor { |
| private ApplyBinaryChanges() { |
| super("", Where.AWT); |
| } |
| |
| @Override |
| public void run(final ContinuationContext context) { |
| if (myTheirsBinaryChanges.isEmpty()) return; |
| final Application application = ApplicationManager.getApplication(); |
| final List<FilePath> dirtyPaths = new ArrayList<FilePath>(); |
| for (final Change change : myTheirsBinaryChanges) { |
| try { |
| application.runWriteAction(new ThrowableComputable<Void, VcsException>() { |
| @Override |
| public Void compute() throws VcsException { |
| try { |
| if (change.getAfterRevision() == null) { |
| final FilePath path = change.getBeforeRevision().getFile(); |
| dirtyPaths.add(path); |
| final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(path.getIOFile()); |
| if (file == null) { |
| context.handleException(new VcsException("Can not delete file: " + file.getPath(), true), false); |
| return null; |
| } |
| file.delete(TreeConflictRefreshablePanel.class); |
| } |
| else { |
| final FilePath file = change.getAfterRevision().getFile(); |
| dirtyPaths.add(file); |
| final String parentPath = file.getParentPath().getPath(); |
| final VirtualFile parentFile = VfsUtil.createDirectoryIfMissing(parentPath); |
| if (parentFile == null) { |
| context.handleException(new VcsException("Can not create directory: " + parentPath, true), false); |
| return null; |
| } |
| final VirtualFile child = parentFile.createChildData(TreeConflictRefreshablePanel.class, file.getName()); |
| if (child == null) { |
| context.handleException(new VcsException("Can not create file: " + file.getPath(), true), false); |
| return null; |
| } |
| final BinaryContentRevision revision = (BinaryContentRevision)change.getAfterRevision(); |
| final byte[] content = revision.getBinaryContent(); |
| // actually it was the fix for IDEA-91572 Error saving merged data: Argument 0 for @NotNull parameter of > com/intellij/ |
| if (content == null) { |
| context.handleException(new VcsException("Can not load Theirs content for file " + file.getPath()), false); |
| return null; |
| } |
| child.setBinaryContent(content); |
| } |
| } |
| catch (IOException e) { |
| throw new VcsException(e); |
| } |
| return null; |
| } |
| }); |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| return; |
| } |
| } |
| VcsDirtyScopeManager.getInstance(myVcs.getProject()).filePathsDirty(dirtyPaths, null); |
| } |
| } |
| |
| private Collection<FilePath> chooseBinaryFiles(List<Change> converted, Set<FilePath> paths) { |
| String singleMessage = ""; |
| if (paths.size() == 1) { |
| final Change change = converted.get(0); |
| final FileStatus status = change.getFileStatus(); |
| final FilePath path = ChangesUtil.getFilePath(change); |
| final String stringPath = TreeConflictRefreshablePanel.filePath(path); |
| if (FileStatus.DELETED.equals(status)) { |
| singleMessage = "Delete binary file " + stringPath + " (according to theirs changes)?"; |
| } else if (FileStatus.ADDED.equals(status)) { |
| singleMessage = "Create binary file " + stringPath + " (according to theirs changes)?"; |
| } else { |
| singleMessage = "Apply changes to binary file " + stringPath + " (according to theirs changes)?"; |
| } |
| } |
| return AbstractVcsHelper.getInstance(myVcs.getProject()).selectFilePathsToProcess(new ArrayList<FilePath>(paths), |
| TreeConflictRefreshablePanel.TITLE, "Select binary files to patch", TreeConflictRefreshablePanel.TITLE, |
| singleMessage, new VcsShowConfirmationOption() { |
| |
| @Override |
| public Value getValue() { |
| return null; |
| } |
| |
| @Override |
| public void setValue(Value value) { |
| } |
| |
| @Override |
| public boolean isPersistent() { |
| return false; |
| } |
| }); |
| } |
| |
| private List<Change> convertPaths(List<Change> changesForPatch) throws VcsException { |
| initAddOption(); |
| final List<Change> changes = new ArrayList<Change>(); |
| for (Change change : changesForPatch) { |
| if (! isUnderOldDir(change, myOldFilePath)) continue; |
| ContentRevision before = null; |
| ContentRevision after = null; |
| if (change.getBeforeRevision() != null) { |
| before = new SimpleContentRevision(change.getBeforeRevision().getContent(), |
| rebasePath(myOldFilePath, myNewFilePath, change.getBeforeRevision().getFile()), |
| change.getBeforeRevision().getRevisionNumber().asString()); |
| } |
| if (change.getAfterRevision() != null) { |
| // if addition or move - do not move to the new path |
| if (myAdd && (change.getBeforeRevision() == null || change.isMoved() || change.isRenamed())) { |
| after = change.getAfterRevision(); |
| } else { |
| after = new SimpleContentRevision(change.getAfterRevision().getContent(), |
| rebasePath(myOldFilePath, myNewFilePath, change.getAfterRevision().getFile()), |
| change.getAfterRevision().getRevisionNumber().asString()); |
| } |
| } |
| changes.add(new Change(before, after)); |
| } |
| return changes; |
| } |
| |
| private boolean isUnderOldDir(Change change, FilePath path) { |
| if (change.getBeforeRevision() != null) { |
| final boolean isUnder = FileUtil.isAncestor(path.getIOFile(), change.getBeforeRevision().getFile().getIOFile(), true); |
| if (isUnder) { |
| return true; |
| } |
| } |
| if (change.getAfterRevision() != null) { |
| final boolean isUnder = FileUtil.isAncestor(path.getIOFile(), change.getAfterRevision().getFile().getIOFile(), true); |
| if (isUnder) { |
| return isUnder; |
| } |
| } |
| return false; |
| } |
| |
| private FilePath rebasePath(final FilePath oldBase, final FilePath newBase, final FilePath path) { |
| final String relativePath = FileUtil.getRelativePath(oldBase.getPath(), path.getPath(), File.separatorChar); |
| //if (StringUtil.isEmptyOrSpaces(relativePath)) return path; |
| return ((FilePathImpl) newBase).createChild(relativePath, path.isDirectory()); |
| } |
| |
| private class PreloadChangesContentsForFile extends TaskDescriptor { |
| private PreloadChangesContentsForFile() { |
| super("Getting base and theirs revisions content", Where.POOLED); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| final SvnContentRevision base = SvnContentRevision.createBaseRevision(myVcs, myNewFilePath, myCommittedRevision.getRevision()); |
| final SvnContentRevision remote = SvnContentRevision.createRemote(myVcs, myOldFilePath, SVNRevision.create( |
| myDescription.getSourceRightVersion().getPegRevision())); |
| try { |
| final ContentRevision newBase = new SimpleContentRevision(base.getContent(), myNewFilePath, base.getRevisionNumber().asString()); |
| final ContentRevision newRemote = new SimpleContentRevision(remote.getContent(), myNewFilePath, remote.getRevisionNumber().asString()); |
| myTheirsChanges.add(new Change(newBase, newRemote)); |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| } |
| } |
| } |
| |
| private class PreloadChangesContentsForDir extends TaskDescriptor { |
| private PreloadChangesContentsForDir() { |
| super("Getting base and theirs revisions content", Where.POOLED); |
| } |
| |
| @Override |
| public void run(ContinuationContext context) { |
| final List<Change> changesForPatch; |
| try { |
| final List<CommittedChangeList> lst = loadSvnChangeListsForPatch(myDescription); |
| changesForPatch = CommittedChangesTreeBrowser.collectChanges(lst, true); |
| for (Change change : changesForPatch) { |
| if (change.getBeforeRevision() != null) { |
| preloadRevisionContents(change.getBeforeRevision()); |
| } |
| if (change.getAfterRevision() != null) { |
| preloadRevisionContents(change.getAfterRevision()); |
| } |
| } |
| } |
| catch (VcsException e) { |
| context.handleException(e, true); |
| return; |
| } |
| final List<Change> binaryChanges = filterOutBinary(changesForPatch); |
| if (binaryChanges != null && ! binaryChanges.isEmpty()) { |
| myTheirsBinaryChanges.addAll(binaryChanges); |
| } |
| if (! changesForPatch.isEmpty()) { |
| myTheirsChanges.addAll(changesForPatch); |
| } |
| } |
| } |
| |
| private void preloadRevisionContents(ContentRevision cr) throws VcsException { |
| if (cr instanceof BinaryContentRevision) { |
| ((BinaryContentRevision) cr).getBinaryContent(); |
| } else { |
| cr.getContent(); |
| } |
| } |
| |
| private List<CommittedChangeList> loadSvnChangeListsForPatch(TreeConflictDescription description) throws VcsException { |
| long max = description.getSourceRightVersion().getPegRevision(); |
| long min = description.getSourceLeftVersion().getPegRevision(); |
| |
| final ChangeBrowserSettings settings = new ChangeBrowserSettings(); |
| settings.USE_CHANGE_BEFORE_FILTER = settings.USE_CHANGE_AFTER_FILTER = true; |
| settings.CHANGE_BEFORE = "" + max; |
| settings.CHANGE_AFTER = "" + min; |
| final List<SvnChangeList> committedChanges = myVcs.getCachingCommittedChangesProvider().getCommittedChanges( |
| settings, new SvnRepositoryLocation(description.getSourceRightVersion().getRepositoryRoot().toString()), 0); |
| final List<CommittedChangeList> lst = new ArrayList<CommittedChangeList>(committedChanges.size() - 1); |
| for (SvnChangeList change : committedChanges) { |
| if (change.getNumber() == min) { |
| continue; |
| } |
| lst.add(change); |
| } |
| return lst; |
| } |
| |
| private void initAddOption() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| if (myAdd == null) { |
| myAdd = getAddedFilesPlaceOption(); |
| } |
| } |
| |
| private boolean getAddedFilesPlaceOption() { |
| final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject()); |
| boolean add = Boolean.TRUE.equals(configuration.isKeepNewFilesAsIsForTreeConflictMerge()); |
| if (configuration.isKeepNewFilesAsIsForTreeConflictMerge() != null) { |
| return add; |
| } |
| if (!containAdditions(myTheirsChanges) && !containAdditions(myTheirsBinaryChanges)) { |
| return false; |
| } |
| return Messages.YES == MessageDialogBuilder.yesNo(TreeConflictRefreshablePanel.TITLE, "Keep newly created file(s) in their original place?").yesText("Keep").noText("Move").doNotAsk( |
| new DialogWrapper.DoNotAskOption() { |
| @Override |
| public boolean isToBeShown() { |
| return true; |
| } |
| |
| @Override |
| public void setToBeShown(boolean value, int exitCode) { |
| if (!value) { |
| if (exitCode == 0) { |
| // yes |
| configuration.setKeepNewFilesAsIsForTreeConflictMerge(true); |
| } |
| else { |
| configuration.setKeepNewFilesAsIsForTreeConflictMerge(false); |
| } |
| } |
| } |
| |
| @Override |
| public boolean canBeHidden() { |
| return true; |
| } |
| |
| @Override |
| public boolean shouldSaveOptionsOnCancel() { |
| return true; |
| } |
| |
| @NotNull |
| @Override |
| public String getDoNotShowMessage() { |
| return CommonBundle.message("dialog.options.do.not.ask"); |
| } |
| }).show(); |
| } |
| |
| private boolean containAdditions(final List<Change> changes) { |
| boolean addFound = false; |
| for (Change change : changes) { |
| if (change.getBeforeRevision() == null || change.isMoved() || change.isRenamed()) { |
| addFound = true; |
| break; |
| } |
| } |
| return addFound; |
| } |
| } |