blob: aff94e123a318accecaf00f47b8400f034161192 [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.update;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.update.FileGroup;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.SvnFileUrlMapping;
import org.jetbrains.idea.svn.SvnRevisionNumber;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.api.EventAction;
import org.jetbrains.idea.svn.api.ProgressEvent;
import org.jetbrains.idea.svn.api.ProgressTracker;
import org.jetbrains.idea.svn.status.StatusType;
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.util.SVNLogType;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lesya
*/
public class UpdateEventHandler implements ProgressTracker {
private ProgressIndicator myProgressIndicator;
private UpdatedFiles myUpdatedFiles;
private int myExternalsCount;
private final SvnVcs myVCS;
@Nullable private final SvnUpdateContext mySequentialUpdatesContext;
private final Map<File, SVNURL> myUrlToCheckForSwitch;
// pair.first - group id, pair.second - file path
// Stack is used to correctly handle cases when updates of externals occur during ordinary update, because these inner updates could have
// its own revisions.
private final Stack<List<Pair<String, String>>> myFilesWaitingForRevision;
protected String myText;
protected String myText2;
public UpdateEventHandler(SvnVcs vcs, ProgressIndicator progressIndicator,
@Nullable final SvnUpdateContext sequentialUpdatesContext) {
myProgressIndicator = progressIndicator;
myVCS = vcs;
mySequentialUpdatesContext = sequentialUpdatesContext;
myExternalsCount = 1;
myUrlToCheckForSwitch = new HashMap<File, SVNURL>();
myFilesWaitingForRevision = ContainerUtil.newStack();
// It is more suitable to make this push while handling UPDATE_NONE event - for command line like "svn update <folder>" this event will
// be fired when update of <folder> is started. But it's not clear if this event won't be fired in other cases by SVNKit. So currently
// first push is made here. If further we want to support commands like "svn update <folder1> <folder2>" this logic should be revised.
myFilesWaitingForRevision.push(ContainerUtil.<Pair<String, String>>newArrayList());
}
public void addToSwitch(final File file, final SVNURL url) {
myUrlToCheckForSwitch.put(file, url);
}
public void setUpdatedFiles(final UpdatedFiles updatedFiles) {
myUpdatedFiles = updatedFiles;
}
public void consume(final ProgressEvent event) {
if (event == null || event.getFile() == null) {
return;
}
String path = event.getFile().getAbsolutePath();
String displayPath = event.getFile().getName();
myText2 = null;
myText = null;
if (handleInDescendants(event)) {
updateProgressIndicator();
return;
}
if (event.getAction() == EventAction.TREE_CONFLICT) {
myText2 = SvnBundle.message("progress.text2.treeconflicted", displayPath);
updateProgressIndicator();
myUpdatedFiles.registerGroup(createFileGroup(VcsBundle.message("update.group.name.merged.with.tree.conflicts"),
FileGroup.MERGED_WITH_TREE_CONFLICT));
addFileToGroup(FileGroup.MERGED_WITH_TREE_CONFLICT, event);
}
if (event.getAction() == EventAction.UPDATE_ADD ||
event.getAction() == EventAction.ADD) {
myText2 = SvnBundle.message("progress.text2.added", displayPath);
if (event.getContentsStatus() == StatusType.CONFLICTED || event.getPropertiesStatus() == StatusType.CONFLICTED) {
addFileToGroup(FileGroup.MERGED_WITH_CONFLICT_ID, event);
myText2 = SvnBundle.message("progress.text2.conflicted", displayPath);
} else if (myUpdatedFiles.getGroupById(FileGroup.REMOVED_FROM_REPOSITORY_ID).getFiles().contains(path)) {
myUpdatedFiles.getGroupById(FileGroup.REMOVED_FROM_REPOSITORY_ID).getFiles().remove(path);
if (myUpdatedFiles.getGroupById(AbstractSvnUpdateIntegrateEnvironment.REPLACED_ID) == null) {
myUpdatedFiles.registerGroup(createFileGroup(SvnBundle.message("status.group.name.replaced"),
AbstractSvnUpdateIntegrateEnvironment.REPLACED_ID));
}
addFileToGroup(AbstractSvnUpdateIntegrateEnvironment.REPLACED_ID, event);
} else {
addFileToGroup(FileGroup.CREATED_ID, event);
}
}
else if (event.getAction() == EventAction.UPDATE_NONE) {
// skip it
return;
}
else if (event.getAction() == EventAction.UPDATE_DELETE) {
myText2 = SvnBundle.message("progress.text2.deleted", displayPath);
addFileToGroup(FileGroup.REMOVED_FROM_REPOSITORY_ID, event);
}
else if (event.getAction() == EventAction.UPDATE_UPDATE) {
possiblySwitched(event);
if (event.getContentsStatus() == StatusType.CONFLICTED || event.getPropertiesStatus() == StatusType.CONFLICTED) {
if (event.getContentsStatus() == StatusType.CONFLICTED) {
addFileToGroup(FileGroup.MERGED_WITH_CONFLICT_ID, event);
}
if (event.getPropertiesStatus() == StatusType.CONFLICTED) {
addFileToGroup(FileGroup.MERGED_WITH_PROPERTY_CONFLICT_ID, event);
}
myText2 = SvnBundle.message("progress.text2.conflicted", displayPath);
}
else if (event.getContentsStatus() == StatusType.MERGED || event.getPropertiesStatus() == StatusType.MERGED) {
myText2 = SvnBundle.message("progres.text2.merged", displayPath);
addFileToGroup(FileGroup.MERGED_ID, event);
}
else if (event.getContentsStatus() == StatusType.CHANGED || event.getPropertiesStatus() == StatusType.CHANGED) {
myText2 = SvnBundle.message("progres.text2.updated", displayPath);
addFileToGroup(FileGroup.UPDATED_ID, event);
}
else if (event.getContentsStatus() == StatusType.UNCHANGED &&
(event.getPropertiesStatus() == StatusType.UNCHANGED || event.getPropertiesStatus() == StatusType.UNKNOWN)) {
myText2 = SvnBundle.message("progres.text2.updated", displayPath);
} else if (StatusType.INAPPLICABLE.equals(event.getContentsStatus()) &&
(event.getPropertiesStatus() == StatusType.UNCHANGED || event.getPropertiesStatus() == StatusType.UNKNOWN)) {
myText2 = SvnBundle.message("progres.text2.updated", displayPath);
}
else {
myText2 = "";
addFileToGroup(FileGroup.UNKNOWN_ID, event);
}
}
else if (event.getAction() == EventAction.UPDATE_EXTERNAL) {
if (mySequentialUpdatesContext != null) {
mySequentialUpdatesContext.registerExternalRootBeingUpdated(event.getFile());
}
myFilesWaitingForRevision.push(ContainerUtil.<Pair<String, String>>newArrayList());
myExternalsCount++;
myText = SvnBundle.message("progress.text.updating.external.location", event.getFile().getAbsolutePath());
}
else if (event.getAction() == EventAction.RESTORE) {
myText2 = SvnBundle.message("progress.text2.restored.file", displayPath);
addFileToGroup(FileGroup.RESTORED_ID, event);
}
else if (event.getAction() == EventAction.UPDATE_COMPLETED && event.getRevision() >= 0) {
possiblySwitched(event);
setRevisionForWaitingFiles(event.getRevision());
myExternalsCount--;
myText2 = SvnBundle.message("progres.text2.updated.to.revision", event.getRevision());
if (myExternalsCount == 0) {
myExternalsCount = 1;
StatusBar.Info.set(SvnBundle.message("status.text.updated.to.revision", event.getRevision()), myVCS.getProject());
}
}
else if (event.getAction() == EventAction.SKIP) {
myText2 = SvnBundle.message("progress.text2.skipped.file", displayPath);
addFileToGroup(FileGroup.SKIPPED_ID, event);
}
updateProgressIndicator();
}
private void possiblySwitched(ProgressEvent event) {
final File file = event.getFile();
if (file == null) return;
final SVNURL wasUrl = myUrlToCheckForSwitch.get(file);
if (wasUrl != null && ! wasUrl.equals(event.getURL())) {
myUrlToCheckForSwitch.remove(file);
addFileToGroup(FileGroup.SWITCHED_ID, event);
}
}
private boolean itemSwitched(final ProgressEvent event) {
final File file = event.getFile();
final SvnFileUrlMapping urlMapping = myVCS.getSvnFileUrlMapping();
final SVNURL currentUrl = urlMapping.getUrlForFile(file);
return (currentUrl != null) && (! currentUrl.equals(event.getURL()));
}
private void updateProgressIndicator() {
if (myProgressIndicator != null) {
if (myText != null) {
myProgressIndicator.setText(myText);
}
if (myText2 != null) {
myProgressIndicator.setText2(myText2);
}
}
}
protected boolean handleInDescendants(final ProgressEvent event) {
return false;
}
protected void addFileToGroup(final String id, final ProgressEvent event) {
final FileGroup fileGroup = myUpdatedFiles.getGroupById(id);
final String path = event.getFile().getAbsolutePath();
myFilesWaitingForRevision.peek().add(Pair.create(id, path));
if (event.getErrorMessage() != null) {
fileGroup.addError(path, event.getErrorMessage().getMessage());
}
}
private void setRevisionForWaitingFiles(long revisionNumber) {
SvnRevisionNumber revision = new SvnRevisionNumber(SVNRevision.create(revisionNumber));
for (Pair<String, String> pair : myFilesWaitingForRevision.pop()) {
FileGroup fileGroup = myUpdatedFiles.getGroupById(pair.getFirst());
fileGroup.add(pair.getSecond(), SvnVcs.getKey(), revision);
}
}
public void checkCancelled() throws SVNCancelException {
if (myProgressIndicator != null) {
myProgressIndicator.checkCanceled();
if (myProgressIndicator.isCanceled()) {
SVNErrorManager.cancel(SvnBundle.message("exception.text.update.operation.cancelled"), SVNLogType.DEFAULT);
}
}
}
private static FileGroup createFileGroup(String name, String id) {
return new FileGroup(name, name, false, id, true);
}
public void setProgressIndicator(ProgressIndicator progressIndicator) {
myProgressIndicator = progressIndicator;
}
}