blob: 0dd27d442ae5124618f8ad5b7b0723bd0b98cd24 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.idea.svn.integrate;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode;
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog;
import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vcs.update.*;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnChangeProvider;
import org.jetbrains.idea.svn.SvnUtil;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.status.Status;
import org.jetbrains.idea.svn.status.StatusType;
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.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class SvnIntegrateChangesTask extends Task.Backgroundable {
private final ProjectLevelVcsManagerEx myProjectLevelVcsManager;
private final SvnVcs myVcs;
private final WorkingCopyInfo myInfo;
private final UpdatedFilesReverseSide myAccomulatedFiles;
private UpdatedFiles myRecentlyUpdatedFiles;
private final List<VcsException> myExceptions;
private UpdateEventHandler myHandler;
private IMerger myMerger;
private ResolveWorker myResolveWorker;
private FilePathImpl myMergeTarget;
private final String myTitle;
private final String myBranchName;
private final MergerFactory myMergerFactory;
private final SVNURL myCurrentBranchUrl;
private boolean myDryRun;
public SvnIntegrateChangesTask(final SvnVcs vcs, final WorkingCopyInfo info, final MergerFactory mergerFactory,
final SVNURL currentBranchUrl, final String title, final boolean dryRun, String branchName) {
super(vcs.getProject(), title, true, VcsConfiguration.getInstance(vcs.getProject()).getUpdateOption());
myMergerFactory = mergerFactory;
myCurrentBranchUrl = currentBranchUrl;
myDryRun = dryRun;
myTitle = title;
myBranchName = branchName;
myProjectLevelVcsManager = ProjectLevelVcsManagerEx.getInstanceEx(myProject);
myVcs = vcs;
myInfo = info;
myAccomulatedFiles = new UpdatedFilesReverseSide(UpdatedFiles.create());
myExceptions = new ArrayList<VcsException>();
myHandler = new IntegrateEventHandler(myVcs, ProgressManager.getInstance().getProgressIndicator());
myMerger = myMergerFactory.createMerger(myVcs, new File(myInfo.getLocalPath()), myHandler, myCurrentBranchUrl, myBranchName);
}
private void indicatorOnStart() {
final ProgressIndicator ind = ProgressManager.getInstance().getProgressIndicator();
if (ind != null) {
ind.setIndeterminate(true);
}
if (ind != null) {
ind.setText(SvnBundle.message("action.Subversion.integrate.changes.progress.integrating.text"));
}
}
public void run(@NotNull final ProgressIndicator indicator) {
myHandler.setProgressIndicator(ProgressManager.getInstance().getProgressIndicator());
myResolveWorker = new ResolveWorker(myInfo.isUnderProjectRoot(), myProject);
BlockReloadingUtil.block();
myProjectLevelVcsManager.startBackgroundVcsOperation();
try {
myRecentlyUpdatedFiles = UpdatedFiles.create();
myHandler.setUpdatedFiles(myRecentlyUpdatedFiles);
indicatorOnStart();
// try to do multiple under single progress
while (true) {
doMerge();
RefreshVFsSynchronously.updateAllChanged(myRecentlyUpdatedFiles);
indicator.setText(VcsBundle.message("progress.text.updating.done"));
if (myResolveWorker.needsInteraction(myRecentlyUpdatedFiles) || (! myMerger.hasNext()) ||
(! myExceptions.isEmpty()) || UpdatedFilesReverseSide.containErrors(myRecentlyUpdatedFiles)) {
break;
}
accomulate();
}
} finally {
myProjectLevelVcsManager.stopBackgroundVcsOperation();
}
}
private void createMessage(final boolean getLatest, final boolean warning, final String firstString) {
final List<String> messages = new ArrayList<String>();
messages.add(firstString);
myMerger.getInfo(new Consumer<String>() {
public void consume(final String s) {
messages.add(s);
}
}, getLatest);
final VcsException result = new VcsException(messages);
result.setIsWarning(warning);
myExceptions.add(result);
}
private void doMerge() {
try {
myMerger.mergeNext();
} catch (SVNException e) {
createMessage(true, false, e.getMessage());
} catch (VcsException e) {
createMessage(true, false, e.getMessage());
}
}
public void onCancel() {
try {
if (myProject.isDisposed()) return;
afterExecution(true);
} finally {
BlockReloadingUtil.unblock();
}
}
public void onSuccess() {
try {
if (myProject.isDisposed()) return;
afterExecution(false);
} finally {
BlockReloadingUtil.unblock();
}
}
private void accomulate() {
myAccomulatedFiles.accomulateFiles(myRecentlyUpdatedFiles, UpdatedFilesReverseSide.DuplicateLevel.DUPLICATE_ERRORS);
}
private void afterExecution(final boolean wasCanceled) {
if (! myRecentlyUpdatedFiles.isEmpty()) {
myResolveWorker.execute(myRecentlyUpdatedFiles);
}
final boolean haveConflicts = ResolveWorker.haveUnresolvedConflicts(myRecentlyUpdatedFiles);
accomulate();
if ((! myMerger.hasNext()) || haveConflicts || (! myExceptions.isEmpty()) || myAccomulatedFiles.containErrors() || wasCanceled) {
initMergeTarget();
if (myAccomulatedFiles.isEmpty() && myExceptions.isEmpty() && (myMergeTarget == null) && (! wasCanceled)) {
Messages.showMessageDialog(SvnBundle.message("action.Subversion.integrate.changes.message.files.up.to.date.text"), myTitle,
Messages.getInformationIcon());
} else {
if (haveConflicts) {
final VcsException exception = new VcsException(SvnBundle.message("svn.integrate.changelist.warning.unresolved.conflicts.text"));
exception.setIsWarning(true);
myExceptions.add(exception);
}
if (wasCanceled) {
final List<String> details = new LinkedList<String>();
details.add("Integration was canceled");
myMerger.getSkipped(new Consumer<String>() {
public void consume(String s) {
if (! StringUtil.isEmptyOrSpaces(s)) {
details.add(s);
}
}
});
final VcsException exception = new VcsException(details);
exception.setIsWarning(true);
myExceptions.add(exception);
}
finishActions(wasCanceled);
}
myMerger.afterProcessing();
} else {
stepToNextChangeList();
}
}
private void finishActions(final boolean wasCanceled) {
if (! wasCanceled) {
if (! ApplicationManager.getApplication().isUnitTestMode() &&
(! myDryRun) && (myExceptions.isEmpty()) && (! myAccomulatedFiles.containErrors()) &&
((! myAccomulatedFiles.isEmpty()) || (myMergeTarget != null))) {
if (myInfo.isUnderProjectRoot()) {
showLocalCommit();
} else {
showAlienCommit();
}
return;
}
}
final Collection<FilePath> files = gatherChangedPaths();
VcsDirtyScopeManager.getInstance(myProject).filePathsDirty(files, null);
prepareAndShowResults();
}
// no remote operations
private void prepareAndShowResults() {
// todo unite into one window??
if (! myAccomulatedFiles.isEmpty()) {
showUpdateTree();
}
if (! myExceptions.isEmpty()) {
AbstractVcsHelper.getInstance(myProject).showErrors(myExceptions, VcsBundle.message("message.title.vcs.update.errors", myExceptions.size()));
}
}
private void showUpdateTree() {
RestoreUpdateTree restoreUpdateTree = RestoreUpdateTree.getInstance(myProject);
// action info is actually NOT used
restoreUpdateTree.registerUpdateInformation(myAccomulatedFiles.getUpdatedFiles(), ActionInfo.INTEGRATE);
myProjectLevelVcsManager.showUpdateProjectInfo(myAccomulatedFiles.getUpdatedFiles(), myTitle, ActionInfo.INTEGRATE, false);
}
private void stepToNextChangeList() {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
ProgressManager.getInstance().run(SvnIntegrateChangesTask.this);
}
});
}
/**
* folder that is going to keep merge info record should also be changed
*/
@Nullable
private void initMergeTarget() {
final File mergeInfoHolder = myMerger.getMergeInfoHolder();
if (mergeInfoHolder != null) {
final Status svnStatus = SvnUtil.getStatus(myVcs, mergeInfoHolder);
if ((svnStatus != null) && (StatusType.STATUS_MODIFIED.equals(svnStatus.getPropertiesStatus()))) {
myMergeTarget = FilePathImpl.create(mergeInfoHolder, mergeInfoHolder.isDirectory());
}
}
}
private void showLocalCommit() {
final Collection<FilePath> files = gatherChangedPaths();
// for changes to be detected, we need switch to background change list manager update thread and back to dispatch thread
// so callback is used; ok to be called after VCS update markup closed: no remote operations
final ChangeListManager clManager = ChangeListManager.getInstance(myProject);
clManager.invokeAfterUpdate(new Runnable() {
public void run() {
final Collection<Change> changes = new ArrayList<Change>();
for (FilePath file : files) {
final Change change = clManager.getChange(file);
if (change != null) {
changes.add(change);
}
}
CommitChangeListDialog.commitChanges(myProject, changes, null, null, myMerger.getComment());
prepareAndShowResults();
}
}, InvokeAfterUpdateMode.SYNCHRONOUS_CANCELLABLE, myTitle,
new Consumer<VcsDirtyScopeManager>() {
public void consume(final VcsDirtyScopeManager vcsDirtyScopeManager) {
vcsDirtyScopeManager.filePathsDirty(files, null);
}
}, null);
}
private Collection<FilePath> gatherChangedPaths() {
final Collection<FilePath> files = new ArrayList<FilePath>();
UpdateFilesHelper.iterateFileGroupFiles(myAccomulatedFiles.getUpdatedFiles(), new UpdateFilesHelper.Callback() {
public void onFile(final String filePath, final String groupId) {
final FilePath file = FilePathImpl.create(new File(filePath));
files.add(file);
}
});
if (myMergeTarget != null) {
files.add(myMergeTarget);
}
return files;
}
private void showAlienCommit() {
final AlienDirtyScope dirtyScope = new AlienDirtyScope();
if (myMergeTarget != null) {
dirtyScope.addDir(myMergeTarget);
} else {
UpdateFilesHelper.iterateFileGroupFiles(myAccomulatedFiles.getUpdatedFiles(), new UpdateFilesHelper.Callback() {
public void onFile(final String filePath, final String groupId) {
final FilePath file = FilePathImpl.create(new File(filePath));
dirtyScope.addFile(file);
}
});
}
new Task.Backgroundable(myVcs.getProject(),
SvnBundle.message("action.Subversion.integrate.changes.collecting.changes.to.commit.task.title")) {
private final GatheringChangelistBuilder changesBuilder = new GatheringChangelistBuilder(myVcs, myAccomulatedFiles);
private final Ref<String> caughtError = new Ref<String>();
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(true);
if (!myVcs.getProject().isDisposed()) {
final SvnChangeProvider provider = new SvnChangeProvider(myVcs);
try {
provider.getChanges(dirtyScope, changesBuilder, indicator, null);
}
catch (VcsException e) {
caughtError.set(SvnBundle.message("action.Subversion.integrate.changes.error.unable.to.collect.changes.text", e.getMessage()));
}
}
}
@Override
public void onSuccess() {
if (!caughtError.isNull()) {
VcsBalloonProblemNotifier.showOverVersionControlView(myVcs.getProject(), caughtError.get(), MessageType.ERROR);
}
else if (!changesBuilder.getChanges().isEmpty()) {
CommitChangeListDialog
.commitAlienChanges(myProject, changesBuilder.getChanges(), myVcs, myMerger.getComment(), myMerger.getComment());
}
}
}.queue();
}
}