com.microsoft.tfs.client.common.ui.tasks.vc.CheckinTask.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.ui.tasks.vc.CheckinTask.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.common.ui.tasks.vc;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;

import com.microsoft.tfs.checkinpolicies.ExtensionPointPolicyLoader;
import com.microsoft.tfs.client.common.codemarker.CodeMarker;
import com.microsoft.tfs.client.common.codemarker.CodeMarkerDispatch;
import com.microsoft.tfs.client.common.commands.vc.CheckinCommand;
import com.microsoft.tfs.client.common.commands.vc.DeleteShelvesetsCommand;
import com.microsoft.tfs.client.common.commands.vc.QueryShelvesetsCommand;
import com.microsoft.tfs.client.common.framework.command.ICommand;
import com.microsoft.tfs.client.common.framework.command.ICommandExecutor;
import com.microsoft.tfs.client.common.framework.command.ICommandFinishedCallback;
import com.microsoft.tfs.client.common.framework.resources.command.ResourceChangingCommand;
import com.microsoft.tfs.client.common.repository.TFSRepository;
import com.microsoft.tfs.client.common.ui.Messages;
import com.microsoft.tfs.client.common.ui.TFSCommonUIClientPlugin;
import com.microsoft.tfs.client.common.ui.commands.IQueueGatedCheckinBuild;
import com.microsoft.tfs.client.common.ui.controls.vc.checkin.CheckinControl.ValidationResult;
import com.microsoft.tfs.client.common.ui.dialogs.vc.CheckinDialog;
import com.microsoft.tfs.client.common.ui.dialogs.vc.GatedCheckinDialog;
import com.microsoft.tfs.client.common.ui.framework.command.UICommandExecutorFactory;
import com.microsoft.tfs.client.common.ui.framework.command.UICommandFinishedCallbackFactory;
import com.microsoft.tfs.client.common.ui.prefs.UIPreferenceConstants;
import com.microsoft.tfs.client.common.ui.tasks.BaseTask;
import com.microsoft.tfs.client.common.util.ExtensionLoader;
import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.build.IBuildDefinition;
import com.microsoft.tfs.core.clients.build.IBuildDetail;
import com.microsoft.tfs.core.clients.build.IQueuedBuild;
import com.microsoft.tfs.core.clients.versioncontrol.conflicts.ConflictDescription;
import com.microsoft.tfs.core.clients.versioncontrol.exceptions.GatedCheckinException;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.PendingChange;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.PolicyOverrideInfo;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Shelveset;
import com.microsoft.tfs.core.clients.versioncontrol.specs.ItemSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.WorkspaceSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.WorkspaceSpecParseException;
import com.microsoft.tfs.core.pendingcheckin.PendingCheckin;
import com.microsoft.tfs.util.Check;

/**
 * Handles the checkin procedure, either by prompting the user for the changes
 * to check-in from the given {@link PendingChange}s, or by checking in a
 * specified {@link PendingCheckin}.
 *
 * @threadsafety unknown
 */
public class CheckinTask extends BaseTask {
    public static final CodeMarker CODEMARKER_GATED_CHECKIN_QUEUED = new CodeMarker(
            "com.microsoft.tfs.client.common.ui.tasks.vc.CheckinTask#GatedCheckinQueued"); //$NON-NLS-1$

    private static final String QUEUE_BUILD_EXTENSION_POINT_ID = "com.microsoft.tfs.client.common.ui.queueGatedCheckinBuild"; //$NON-NLS-1$

    private static final Log log = LogFactory.getLog(ExtensionPointPolicyLoader.class);
    private final TFSRepository repository;

    private PendingChange[] pendingChanges;
    private String comment;
    private PendingCheckin pendingCheckin;
    private PolicyOverrideInfo policyOverrideInfo;
    private boolean pendingChangesCleared;

    private int changesetID;
    private IQueuedBuild queuedGatedBuild;

    /**
     * Creates a {@link CheckinTask}.
     *
     * @param shell
     *        the shell (must not be <code>null</code>)
     * @param repository
     *        the repository (must not be <code>null</code>)
     * @param pendingChanges
     *        the pending changes (must not be <code>null</code>)
     * @param comment
     *        the comment text (may be <code>null</code>)
     */
    public CheckinTask(final Shell shell, final TFSRepository repository, final PendingChange[] pendingChanges,
            final String comment) {
        super(shell);

        Check.notNull(repository, "repository"); //$NON-NLS-1$
        Check.notNull(pendingChanges, "pendingChanges"); //$NON-NLS-1$

        this.repository = repository;
        this.pendingChanges = pendingChanges;
        this.comment = comment;
    }

    /**
     * Creates a {@link CheckinTask}.
     *
     * @param shell
     *        the shell (must not be <code>null</code>)
     * @param repository
     *        the repository (must not be <code>null</code>)
     * @param pendingCheckin
     *        the pending check-in (must not be <code>null</code>)
     * @param policyOverrideInfo
     *        the policy override information (may be <code>null</code>)
     */
    public CheckinTask(final Shell shell, final TFSRepository repository, final PendingCheckin pendingCheckin,
            final PolicyOverrideInfo policyOverrideInfo) {
        super(shell);

        Check.notNull(repository, "repository"); //$NON-NLS-1$
        Check.notNull(pendingCheckin, "pendingCheckin"); //$NON-NLS-1$

        this.repository = repository;
        this.pendingCheckin = pendingCheckin;
        this.policyOverrideInfo = policyOverrideInfo;
    }

    @Override
    public IStatus run() {
        // Show the dialog if the caller did not already supply pending changes
        // to check-in.
        if (pendingCheckin == null) {
            final CheckinDialog checkinDialog = new CheckinDialog(getShell(), repository, pendingChanges, comment);

            if (checkinDialog.open() != IDialogConstants.OK_ID) {
                return Status.OK_STATUS;
            }

            /*
             * Get the checkin information from the dialog.
             */
            pendingCheckin = checkinDialog.getPendingCheckin();

            /*
             * The dialog did the validation before it allowed OK to close the
             * dialog.
             */
            final ValidationResult validationResult = checkinDialog.getValidationResult();
            Check.notNull(validationResult, "validationResult"); //$NON-NLS-1$
            Check.isTrue(validationResult.getSucceeded(), "validationResult.getSucceeded()"); //$NON-NLS-1$

            /*
             * Build the override information from the validation result.
             */
            if (validationResult.getPolicyOverrideReason() != null
                    && validationResult.getPolicyOverrideReason().length() > 0) {
                policyOverrideInfo = new PolicyOverrideInfo(validationResult.getPolicyOverrideReason(),
                        validationResult.getPolicyFailures());
            }
        }

        final CheckinCommand checkinCommand = new CheckinCommand(repository, pendingCheckin, policyOverrideInfo);
        checkinCommand.setAutoResolveConflicts(TFSCommonUIClientPlugin.getDefault().getPreferenceStore()
                .getBoolean(UIPreferenceConstants.AUTO_RESOLVE_CONFLICTS));
        checkinCommand.setQueryConflicts(true);

        /*
         * Make sure to use a command executor that does not raise error dialogs
         * - we need to handle conflicts (IStatus.WARNING) specially.
         *
         * Wrap these standard callbacks with a custom command finished callback
         * to gracefully handle some standard checkin exceptions.
         */
        final ICommandExecutor commandExecutor = getCommandExecutor();
        commandExecutor.setCommandFinishedCallback(new CheckinCommandFinishedCallback());

        boolean retryWithOverride;
        IStatus checkinStatus;

        do {
            retryWithOverride = false;
            checkinStatus = commandExecutor.execute(new ResourceChangingCommand(checkinCommand));

            if (checkinStatus.isOK()) {
                changesetID = checkinCommand.getChangeset();

                if (changesetID == 0) {
                    MessageDialog.openWarning(getShell(),
                            Messages.getString("PendingChangesView.NoChangesDialogTitle"), //$NON-NLS-1$
                            Messages.getString("PendingChangesView.ChangeChangesDialogText")); //$NON-NLS-1$
                }

                pendingChangesCleared = true;
                return checkinStatus;
            }

            if (checkinStatus.getException() instanceof GatedCheckinException) {
                // The server rejected the check-in attempt because some of the
                // pending changes affect build definition(s) which have gated
                // check-ins.
                final GatedCheckinException e = (GatedCheckinException) checkinStatus.getException();

                // Strip the owner from the shelveset name
                String shelvesetName;
                try {
                    final WorkspaceSpec spec = WorkspaceSpec.parse(e.getShelvesetName(), null);
                    shelvesetName = spec.getName();
                } catch (final WorkspaceSpecParseException ex) {
                    shelvesetName = e.getShelvesetName();
                    log.warn("The shelveset name '" + shelvesetName + "' could not be parsed", e); //$NON-NLS-1$ //$NON-NLS-2$
                }

                // Determine if any changes had a lock.
                boolean allowKeepCheckedOut = true;

                for (final PendingChange pc : pendingCheckin.getPendingChanges().getCheckedPendingChanges()) {
                    if (pc.isLock()) {
                        allowKeepCheckedOut = false;
                        break;
                    }
                }

                // Display the gated check-in dialog to the user.
                final GatedCheckinDialog dialog = new GatedCheckinDialog(getShell(), shelvesetName,
                        e.getAffectedBuildDefinitionNames(), e.getAffectedBuildDefinitionURIs(),
                        e.getOverridePermission(), allowKeepCheckedOut);

                if (dialog.open() != IDialogConstants.OK_ID) {
                    deleteGatedCheckinShelveset(e.getShelvesetName());
                    return Status.OK_STATUS;
                }

                if (dialog.getBypassBuild()) {
                    // The user chose to override the gated check-in. Attempt to
                    // resubmit the check-in with the override property set.
                    checkinCommand.setOverrideGatedCheckinOption();
                    retryWithOverride = true;
                    deleteGatedCheckinShelveset(e.getShelvesetName());
                    continue; // retry the checkin
                } else {
                    // The user chose to build the changes for the gated
                    // check-in. Queue a build for the shelved changes against
                    // the selected build definition.
                    final IQueueGatedCheckinBuild queueGatedCheckinBuild = getQueueGatedCheckinBuildContributor();

                    if (queueGatedCheckinBuild != null) {
                        final String buildDefinitionUri = dialog.getSelectedBuildDefinitionURI();
                        final TFSTeamProjectCollection c = repository.getVersionControlClient().getConnection();
                        final IBuildDefinition buildDefinition = c.getBuildServer()
                                .getBuildDefinition(buildDefinitionUri);

                        if (queueGatedCheckinBuild.queueBuild(getShell(), buildDefinition, buildDefinitionUri,
                                e.getShelvesetName(), e.getCheckinTicket())) {
                            queuedGatedBuild = queueGatedCheckinBuild.getQueuedBuild();

                            if (!dialog.getPreserveLocalChanges()) {
                                // The user chose to undo the local pending
                                // changes after queuing the build.
                                final PendingChange[] checkedPendingChanges = pendingCheckin.getPendingChanges()
                                        .getCheckedPendingChanges();

                                // Create item specs from the pending changes.
                                final ItemSpec[] itemSpecs = new ItemSpec[checkedPendingChanges.length];
                                for (int i = 0; i < checkedPendingChanges.length; i++) {
                                    final PendingChange pendingChange = checkedPendingChanges[i];
                                    itemSpecs[i] = new ItemSpec(pendingChange.getServerItem(), RecursionType.NONE);
                                }

                                // Undo the local pending changes. The undo task
                                // does not prompt the user for confirmation
                                // when supplying item specs, which is the
                                // desired behavior here.
                                final UndoPendingChangesTask undoTask = new UndoPendingChangesTask(getShell(),
                                        repository, itemSpecs);

                                undoTask.run();
                                pendingChangesCleared = true;
                            }

                            CodeMarkerDispatch.dispatch(CODEMARKER_GATED_CHECKIN_QUEUED);
                        }
                    }
                    return Status.OK_STATUS;
                }
            } else if (checkinStatus.getException() != null) {
                break;
            }

            if (checkinCommand.allConflictsResolved()) {
                MessageDialog.openInformation(getShell(), Messages.getString("CheckinTask.AllResolvedDialogTitle"), //$NON-NLS-1$
                        Messages.getString("CheckinTask.AllResolvedDialogMessage")); //$NON-NLS-1$
                return Status.OK_STATUS;
            }

            else if (!checkinCommand.hasResolvableConflicts()) {
                ErrorDialog.openError(getShell(), Messages.getString("CheckinTask.ErrorDialogTitle"), //$NON-NLS-1$
                        null, checkinStatus);
                return checkinStatus;
            }

            final ConflictDescription[] conflicts = checkinCommand.getCheckinConflicts();

            final ConflictResolutionTask conflictTask = new ConflictResolutionTask(getShell(), repository,
                    conflicts);
            final IStatus conflictStatus = conflictTask.run();

            if (conflictStatus.isOK()) {
                return Status.OK_STATUS;
            }
        } while (retryWithOverride);

        return checkinStatus;
    }

    /**
     * Returns true if the pending changes were cleared as part of this task.
     *
     * @return True if the pending changes were cleared.
     */
    public boolean getPendingChangesCleared() {
        return pendingChangesCleared;
    }

    /**
     * Returns the integer ID of the successfully checked-in changeset.
     *
     *
     * @return
     */
    public int getChangesetID() {
        return changesetID;
    }

    /**
     * Returns the queued build for a gated check-in. Will be null if a gated
     * check-in build did not occur.
     *
     *
     * @return
     */
    public IBuildDetail getGatedBuildDetail() {
        return queuedGatedBuild == null ? null : queuedGatedBuild.getBuild();
    }

    /**
     * Delete the gated check-in shelveset which was created by the server. Let
     * the caller decide how to handle exceptions.
     *
     * @param gatedCheckinShelvesetName
     *        The gated check-in shelveset to delete.
     * @returns A command status.
     */
    private IStatus deleteGatedCheckinShelveset(final String gatedCheckinShelvesetName) {
        final String fallbackUser = repository.getVersionControlClient().getConnection().getAuthorizedAccountName();
        final WorkspaceSpec spec = WorkspaceSpec.parse(gatedCheckinShelvesetName, fallbackUser);

        final QueryShelvesetsCommand queryCommand = new QueryShelvesetsCommand(repository.getVersionControlClient(),
                spec.getName(), spec.getOwner());

        final ICommandExecutor executor = UICommandExecutorFactory.newUICommandExecutor(getShell());
        IStatus status = executor.execute(queryCommand);

        if (status.isOK()) {
            final Shelveset[] shelvesets = queryCommand.getShelvesets();
            if (shelvesets.length > 0) {
                final DeleteShelvesetsCommand deleteCommand = new DeleteShelvesetsCommand(repository,
                        shelvesets[0]);
                status = executor.execute(deleteCommand);
            }
        }

        return status;
    }

    /**
     * Searches for the extension contributor for the
     * {@link #QUEUE_BUILD_EXTENSION_POINT_ID} point.
     *
     * @return Returns the extension point contributor or null if none if found.
     */
    private IQueueGatedCheckinBuild getQueueGatedCheckinBuildContributor() {
        try {
            return (IQueueGatedCheckinBuild) ExtensionLoader
                    .loadSingleExtensionClass(QUEUE_BUILD_EXTENSION_POINT_ID);
        } catch (final Exception e) {
            log.error("The extension point to build a gated check-in was not found.", e); //$NON-NLS-1$
        }

        return null;
    }

    private static class CheckinCommandFinishedCallback implements ICommandFinishedCallback {
        private final ICommandFinishedCallback defaultCallbacks;

        public CheckinCommandFinishedCallback() {
            defaultCallbacks = UICommandFinishedCallbackFactory.getDefaultNoErrorDialogCallback();
        }

        @Override
        public void onCommandFinished(final ICommand command, final IStatus status) {
            /*
             * GatedCheckinExceptions shouldn't be fed to the callbacks (which
             * may log them, display them to the user, etc.) since they're a
             * normal part of checking in to a gated definition and are handled
             * by other event listeners.
             */
            if (!status.isMultiStatus() && status.getException() instanceof GatedCheckinException) {
                return;
            }

            defaultCallbacks.onCommandFinished(command, status);
        }
    }
}