org.jetbrains.tfsIntegration.core.tfs.operations.ApplyGetOperations.java Source code

Java tutorial

Introduction

Here is the source code for org.jetbrains.tfsIntegration.core.tfs.operations.ApplyGetOperations.java

Source

/*
 * Copyright 2000-2008 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.tfsIntegration.core.tfs.operations;

import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.update.FileGroup;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.util.WaitForProgressToShow;
import com.microsoft.schemas.teamfoundation._2005._06.versioncontrol.clientservices._03.ChangeType_type0;
import com.microsoft.schemas.teamfoundation._2005._06.versioncontrol.clientservices._03.GetOperation;
import com.microsoft.schemas.teamfoundation._2005._06.versioncontrol.clientservices._03.ItemType;
import com.microsoft.schemas.teamfoundation._2005._06.versioncontrol.clientservices._03.LocalVersionUpdate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.tfsIntegration.core.TFSBundle;
import org.jetbrains.tfsIntegration.core.TFSVcs;
import org.jetbrains.tfsIntegration.core.tfs.*;
import org.jetbrains.tfsIntegration.exceptions.OperationFailedException;
import org.jetbrains.tfsIntegration.exceptions.TfsException;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

// TODO review file groups

@SuppressWarnings({ "HardCodedStringLiteral" })
public class ApplyGetOperations {
    private static LocalConflictHandlingType ourLocalConflictHandlingType = LocalConflictHandlingType.SHOW_MESSAGE;

    private final Project myProject;
    private final WorkspaceInfo myWorkspace;
    private final Collection<GetOperation> myOperations;
    private final @NotNull ApplyProgress myProgress;
    private final @Nullable UpdatedFiles myUpdatedFiles;
    private final Collection<VcsException> myErrors = new ArrayList<VcsException>();
    private final Collection<LocalVersionUpdate> myUpdateLocalVersions = new ArrayList<LocalVersionUpdate>();
    private final DownloadMode myDownloadMode;

    public enum DownloadMode {
        FORCE, ALLOW, FORBID, MERGE
    }

    public enum LocalConflictHandlingType {
        SHOW_MESSAGE, ERROR
    }

    private ApplyGetOperations(Project project, WorkspaceInfo workspace, Collection<GetOperation> operations,
            final @NotNull ApplyProgress progress, final @Nullable UpdatedFiles updatedFiles,
            final DownloadMode downloadMode) {
        myProject = project;
        myWorkspace = workspace;
        myOperations = operations;
        myProgress = progress;
        myUpdatedFiles = updatedFiles;
        myDownloadMode = downloadMode;
    }

    public static LocalConflictHandlingType getLocalConflictHandlingType() {
        return ourLocalConflictHandlingType;
    }

    public static void setLocalConflictHandlingType(LocalConflictHandlingType type) {
        ourLocalConflictHandlingType = type;
    }

    public static Collection<VcsException> execute(Project project, WorkspaceInfo workspace,
            Collection<GetOperation> operations, final @NotNull ApplyProgress progress,
            final @Nullable UpdatedFiles updatedFiles, DownloadMode downloadMode) {
        ApplyGetOperations session = new ApplyGetOperations(project, workspace, operations, progress, updatedFiles,
                downloadMode);
        session.execute();
        return session.myErrors;
    }

    private void execute() {
        if (myOperations.isEmpty()) {
            return;
        }

        // parent folders modificating operations should be processed before children to update affected child paths correctly
        List<GetOperation> sortedOperations = new ArrayList<GetOperation>(myOperations);//GetOperationsUtil.sortGetOperations(myOperations);
        // TODO do we need to sort them or they come in apply order?

        try {
            for (int i = 0; i < sortedOperations.size(); i++) {
                if (myProgress.isCancelled()) {
                    throw new ProcessCanceledException();
                }

                GetOperation operationToExecute = sortedOperations.get(i);

                String currentPath = VersionControlPath.localPathFromTfsRepresentation(
                        operationToExecute.getTlocal() != null ? operationToExecute.getTlocal()
                                : operationToExecute.getSlocal());
                if (currentPath == null) {
                    FilePath unexistingPath = myWorkspace.findLocalPathByServerPath(operationToExecute.getTitem(),
                            operationToExecute.getType() == ItemType.Folder, myProject);
                    currentPath = unexistingPath.getPresentableUrl();
                }
                myProgress.setFraction(i / sortedOperations.size());
                myProgress.setText(currentPath);

                if (operationToExecute.getCnflct()) {
                    // TODO can be confict on undo?
                    // conflict will be resolved later
                    processConflict(operationToExecute);
                } else if (operationToExecute.getSlocal() == null && operationToExecute.getTlocal() == null) {
                    updateLocalVersion(operationToExecute);
                } else if (operationToExecute.getTlocal() == null) {
                    if (operationToExecute.getType() == ItemType.File) {
                        processDeleteFile(operationToExecute);
                    } else {
                        processDeleteFolder(operationToExecute);
                    }
                } else if (operationToExecute.getSlocal() == null) {
                    if (operationToExecute.getType() == ItemType.File) {
                        processCreateFile(operationToExecute);
                    } else {
                        processCreateFolder(operationToExecute);
                    }
                } else if (operationToExecute.getType() == ItemType.File) {
                    processFileChange(operationToExecute);
                } else {
                    processFolderChange(operationToExecute);
                    if (!operationToExecute.getSlocal().equals(operationToExecute.getTlocal())) {
                        GetOperationsUtil.updateSourcePaths(sortedOperations, i, operationToExecute);
                    }
                }
            }

            myWorkspace.getServer().getVCS().updateLocalVersions(myWorkspace.getName(), myWorkspace.getOwnerName(),
                    myUpdateLocalVersions, myProject, TFSBundle.message("updating.local.version"));
        } catch (TfsException e) {
            myErrors.add(new VcsException(e));
        }
    }

    private void processDeleteFile(final GetOperation operation) throws TfsException {
        File source = VersionControlPath.getFile(operation.getSlocal());
        if (source.isDirectory()) {
            String errorMessage = MessageFormat.format(
                    "Cannot delete file ''{0}'' because there is a folder with the same name", source.getPath());
            myErrors.add(new VcsException(errorMessage));
            return;
        }
        if (source.canWrite() && !canOverrideLocalConflictingItem(operation, true)) {
            return;
        }

        final boolean exists = source.exists();
        if (!deleteFile(source)) {
            return;
        }

        updateLocalVersion(operation);
        if (exists) {
            addToGroup(FileGroup.REMOVED_FROM_REPOSITORY_ID, source, operation);
        }
    }

    private void processDeleteFolder(final GetOperation operation) throws TfsException {
        File source = VersionControlPath.getFile(operation.getSlocal());
        if (source.isFile()) {
            String errorMessage = MessageFormat.format(
                    "Cannot delete folder ''{0}'' because there is a file with the same name", source.getPath());
            myErrors.add(new VcsException(errorMessage));
            return;
        }

        // TODO: if force, delete anyway?
        if (!canDeleteFolder(source)) {
            String errorMessage = MessageFormat.format("Cannot delete folder ''{0}'' because it is not empty",
                    source.getPath());
            myErrors.add(new VcsException(errorMessage));
            return;
        }

        boolean exists = source.exists();
        if (!deleteFile(source)) {
            return;
        }

        updateLocalVersion(operation);
        if (exists) {
            addToGroup(FileGroup.REMOVED_FROM_REPOSITORY_ID, source, operation);
        }
    }

    private void processCreateFile(final @NotNull GetOperation operation) throws TfsException {
        File target = VersionControlPath.getFile(operation.getTlocal());
        if (target.isDirectory()) {
            String errorMessage = MessageFormat.format(
                    "Cannot create file ''{0}'' because there is a folder with the same name", target.getPath());
            myErrors.add(new VcsException(errorMessage));
            return;
        }

        if (target.canWrite()) {
            if (!canOverrideLocalConflictingItem(operation, false)) {
                return;
            }
            if (!FileUtil.delete(target)) {
                String errorMessage = MessageFormat.format("Cannot overwrite file ''{0}''", target.getPath());
                myErrors.add(new VcsException(errorMessage));
                return;
            }
        }

        if (!createFolder(target.getParentFile())) {
            return;
        }

        if (downloadFile(operation)) {
            updateLocalVersion(operation);
            addToGroup(FileGroup.CREATED_ID, target, operation);
        }
    }

    private void processCreateFolder(final GetOperation operation) throws TfsException {
        File target = VersionControlPath.getFile(operation.getTlocal());
        if (target.isFile() && target.canWrite() && !canOverrideLocalConflictingItem(operation, false)) {
            return;
        }

        if (target.isFile() && !FileUtil.delete(target)) {
            String errorMessage = MessageFormat.format(
                    "Cannot create folder ''{0}'' because there is a folder with the same name", target.getPath());
            myErrors.add(new VcsException(errorMessage));
            return;
        }

        boolean folderExists = target.exists();
        if (!createFolder(target)) {
            return;
        }

        updateLocalVersion(operation);
        if (!folderExists) {
            addToGroup(FileGroup.CREATED_ID, target, operation);
        }
    }

    private void processFileChange(final GetOperation operation) throws TfsException {
        File source = VersionControlPath.getFile(operation.getSlocal());
        File target = VersionControlPath.getFile(operation.getTlocal());
        final ChangeTypeMask change = new ChangeTypeMask(operation.getChg());

        if (source.equals(target) && operation.getLver() == operation.getSver()
                && (change.containsOnly(ChangeType_type0.Rename)
                        || (myDownloadMode != DownloadMode.FORCE && myDownloadMode != DownloadMode.MERGE))) {
            // rename + source=target means rename of parent folder 
            // not an explicit change, nothing to do
            updateLocalVersion(operation);
            return;
        }

        if (!source.equals(target) && source.canWrite()) {
            if (canOverrideLocalConflictingItem(operation, true)) {
                if (myDownloadMode == DownloadMode.FORCE && !deleteFile(source)) {
                    return;
                }
            } else {
                return;
            }
        }

        if (!source.equals(target) && source.isDirectory()
                && (!canOverrideLocalConflictingItem(operation, true) || !deleteFile(source))) {
            return;
        }

        if (target.isDirectory()) {
            // Note: TFC does not report local conflict in this case
            String errorMessage = MessageFormat.format(
                    "Cannot create file ''{0}'' because there is a folder with same name", target.getPath());
            myErrors.add(new VcsException(errorMessage));
            return;
        }

        // don't ask 2nd time if source = target
        if (target.canWrite() && !target.equals(source) && !canOverrideLocalConflictingItem(operation, false)) {
            return;
        }

        if (!createFolder(target.getParentFile())) {
            return;
        }

        if (myDownloadMode == DownloadMode.FORCE
                || (myDownloadMode != DownloadMode.MERGE && operation.getLver() != operation.getSver())) {
            // remove source, create target
            // don't download file if undoing Add
            if ((source.equals(target) || deleteFile(source))
                    && (change.contains(ChangeType_type0.Add) || downloadFile(operation))) {
                updateLocalVersion(operation);
                if (source.equals(target)) {
                    addToGroup(FileGroup.UPDATED_ID, target, operation);
                } else {
                    addToGroup(FileGroup.REMOVED_FROM_REPOSITORY_ID, source, operation);
                    addToGroup(FileGroup.CREATED_ID, target, operation);
                }
            }
            return;
        }

        if (!target.exists()) {
            if (source.exists()) {
                // source exists (so it is a file), target is not
                if (rename(source, target)) {
                    addToGroup(FileGroup.UPDATED_ID, target, operation);
                    updateLocalVersion(operation);
                }
            } else {
                // source & target not exist
                // don't create file if undoing locally missing scheduled for addition file
                if (!change.contains(ChangeType_type0.Add) || !source.equals(target)
                        || operation.getLver() != operation.getSver()) {
                    if (downloadFile(operation)) {
                        addToGroup(FileGroup.CREATED_ID, target, operation);
                        updateLocalVersion(operation);
                    }
                }
            }
        } else {
            // target exists
            if (!source.equals(target)) {
                deleteFile(source);
            }
            if (myDownloadMode == DownloadMode.MERGE) {
                addToGroup(FileGroup.MERGED_ID, target, operation);
            }
            updateLocalVersion(operation);
        }
    }

    private void processFolderChange(final GetOperation operation) throws TfsException {
        File source = VersionControlPath.getFile(operation.getSlocal());
        File target = VersionControlPath.getFile(operation.getTlocal());
        final ChangeTypeMask change = new ChangeTypeMask(operation.getChg());

        if (source.equals(target) && operation.getLver() == operation.getSver()
                && (change.containsOnly(ChangeType_type0.Rename)
                        || (myDownloadMode != DownloadMode.FORCE && myDownloadMode != DownloadMode.MERGE))) {
            // rename + source=target means rename of parent folder
            // not an explicit change, nothing to do
            updateLocalVersion(operation);
            return;
        }

        // redundand check source.equals(target) for consistency with processFileChange() 
        if (!source.equals(target) && source.isFile() && !source.canWrite() && !deleteFile(source)) {
            return;
        }

        if (target.isFile() && !target.canWrite() && !deleteFile(target)) {
            return;
        }

        if (target.isFile() && target.canWrite()
                && (!canOverrideLocalConflictingItem(operation, false) || !deleteFile(target))) {
            return;
        }

        if (!createFolder(target.getParentFile())) {
            return;
        }

        if (!target.exists()) {
            if (source.isDirectory()) {
                // source exists, target is not
                if (rename(source, target)) {
                    addToGroup(FileGroup.UPDATED_ID, target, operation);
                    updateLocalVersion(operation);
                }
            } else {
                // source & target not exist
                // don't create folder if undoing locally missing scheduled for addition folder
                if (!change.contains(ChangeType_type0.Add) || !source.equals(target)
                        || operation.getLver() != operation.getSver()) {
                    if (createFolder(target)) {
                        addToGroup(FileGroup.CREATED_ID, target, operation);
                        updateLocalVersion(operation);
                    }
                }
            }
        } else {
            // target exists
            if (!source.equals(target)) {
                deleteFile(source);
            }
            updateLocalVersion(operation);
        }
    }

    private void processConflict(final GetOperation operation) {
        //File subject = new File(operation.getTlocal() != null ? operation.getTlocal() : operation.getSlocal());
        //addToGroup(FileGroup.MODIFIED_ID, subject, operation);
    }

    private void addToGroup(String groupId, File file, GetOperation operation) {
        if (myUpdatedFiles != null) {
            int revisionNumber = operation.getSver() != Integer.MIN_VALUE ? operation.getSver() : 0;
            myUpdatedFiles.getGroupById(groupId).add(file.getPath(), TFSVcs.getKey(),
                    new TfsRevisionNumber(revisionNumber));
        }
    }

    private boolean deleteFile(File target) {
        if (myDownloadMode != DownloadMode.FORBID && !FileUtil.delete(target)) {
            String errorMessage = MessageFormat.format("Cannot delete {0} ''{1}''",
                    target.isFile() ? "file" : "folder", target.getPath());
            myErrors.add(new VcsException(errorMessage));
            return false;
        } else {
            return true;
        }
    }

    private boolean canDeleteFolder(final File folder) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File child : files) {
                if (child.isFile()) {
                    if (child.canWrite()) {
                        return false;
                    } else {
                        boolean childWillBeDeletedAnyway = false;
                        for (GetOperation operation : myOperations) {
                            if (operation.getSlocal() != null
                                    && VersionControlPath.getFile(operation.getSlocal()).equals(child)
                                    && operation.getTlocal() == null) {
                                childWillBeDeletedAnyway = true;
                                break;
                            }
                        }
                        if (!childWillBeDeletedAnyway) {
                            return false;
                        }
                    }
                } else if (!canDeleteFolder(child)) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean createFolder(File target) {
        if (myDownloadMode != DownloadMode.FORBID && !target.exists() && !target.mkdirs()) {
            String errorMessage = MessageFormat.format("Cannot create folder ''{0}''", target.getPath());
            myErrors.add(new VcsException(errorMessage));
            return false;
        } else {
            return true;
        }
    }

    private boolean rename(File source, File target) {
        if (myDownloadMode != DownloadMode.FORBID && !source.equals(target) && !source.renameTo(target)) {
            String errorMessage = MessageFormat.format("Cannot rename {0} ''{1}'' to ''{2}''",
                    source.isFile() ? "file" : "folder", source.getPath(), target.getPath());
            myErrors.add(new VcsException(errorMessage));
            return false;
        } else {
            return true;
        }
    }

    private boolean downloadFile(final GetOperation operation) throws TfsException {
        TFSVcs.assertTrue(operation.getDurl() != null, "Null download url for "
                + VersionControlPath.localPathFromTfsRepresentation(operation.getTlocal()));

        if (myDownloadMode == DownloadMode.FORBID) {
            return true;
        }

        final File target = VersionControlPath.getFile(operation.getTlocal());
        try {
            TfsFileUtil.setFileContent(target, new TfsFileUtil.ContentWriter() {
                public void write(final OutputStream outputStream) throws TfsException {
                    myWorkspace.getServer().getVCS().downloadItem(myProject, operation.getDurl(), outputStream,
                            TFSBundle.message("downloading.0", target.getName()));
                }
            });
            if (!target.setReadOnly()) {
                String errorMessage = MessageFormat.format("Cannot write to file ''{0}''", target.getPath());
                myErrors.add(new VcsException(errorMessage));
                return false;
            }
            return true;
        } catch (IOException e) {
            String errorMessage = MessageFormat.format("Cannot write to file ''{0}'': {1}", target.getPath(),
                    e.getMessage());
            myErrors.add(new VcsException(errorMessage));
            return false;
        }
    }

    private boolean canOverrideLocalConflictingItem(final GetOperation operation, boolean sourceNotTarget)
            throws TfsException {
        if (myDownloadMode == DownloadMode.FORCE || myDownloadMode == DownloadMode.MERGE) {
            return true;
        }

        LocalConflictHandlingType conflictHandlingType = getLocalConflictHandlingType();
        if (conflictHandlingType == LocalConflictHandlingType.ERROR) {
            throw new OperationFailedException(
                    "Local conflict detected for " + VersionControlPath.localPathFromTfsRepresentation(
                            sourceNotTarget ? operation.getSlocal() : operation.getTlocal()));
        } else if (conflictHandlingType == LocalConflictHandlingType.SHOW_MESSAGE) {
            String path = VersionControlPath.localPathFromTfsRepresentation(
                    sourceNotTarget ? operation.getSlocal() : operation.getTlocal());
            final String message = MessageFormat.format("Local conflict detected. Override local item?\n {0}",
                    path);
            // TODO: more detailed message needed
            final String title = "Modify Files";
            final Ref<Integer> result = new Ref<Integer>();
            WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(new Runnable() {
                public void run() {
                    result.set(Messages.showYesNoDialog(message, title, Messages.getQuestionIcon()));
                }
            });

            if (result.get() == Messages.YES) {
                return true;
            } else {
                reportLocalConflict(operation, sourceNotTarget);
                return false;
            }
        } else {
            throw new IllegalArgumentException("Unknown conflict handling type: " + conflictHandlingType);
        }
    }

    private void reportLocalConflict(final GetOperation operation, boolean sourceNotTarget) throws TfsException {
        int reason = sourceNotTarget ? VersionControlServer.LOCAL_CONFLICT_REASON_SOURCE
                : VersionControlServer.LOCAL_CONFLICT_REASON_TARGET;
        myWorkspace.getServer().getVCS().addLocalConflict(myWorkspace.getName(), myWorkspace.getOwnerName(),
                operation.getItemid(), operation.getSver(),
                operation.getPcid() != Integer.MIN_VALUE ? operation.getPcid() : 0, operation.getSlocal(),
                operation.getTlocal(), reason, myProject, TFSBundle.message("reporting.conflict"));
    }

    private void updateLocalVersion(GetOperation operation) {
        myUpdateLocalVersions.add(VersionControlServer.getLocalVersionUpdate(operation));
    }

}