com.vectrace.MercurialEclipse.dialogs.CommitDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.vectrace.MercurialEclipse.dialogs.CommitDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2006-2009 VecTrace (Zingo Andersen) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Eclipse.org - see CommitWizardCommitPage
 *     Software Balm Consulting Inc (Peter Hunnisett <peter_hge at softwarebalm dot com>) - implementation
 *     Bastian Doetsch - Added spellchecking and some other stuff
 *     StefanC - many updates
 *     Zingo Andersen - some updates
 *     Andrei Loskutov - bug fixes
 *     Adam Berkes (Intland) - bug fixes
 *     Amenel VOGLOZIN - bug fixes
 *******************************************************************************/
package com.vectrace.MercurialEclipse.dialogs;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.aragost.javahg.Phase;
import com.vectrace.MercurialEclipse.HgFeatures;
import com.vectrace.MercurialEclipse.MercurialEclipsePlugin;
import com.vectrace.MercurialEclipse.commands.HgAddClient;
import com.vectrace.MercurialEclipse.commands.HgCommitClient;
import com.vectrace.MercurialEclipse.commands.HgPatchClient;
import com.vectrace.MercurialEclipse.commands.HgRemoveClient;
import com.vectrace.MercurialEclipse.commands.HgStatusClient;
import com.vectrace.MercurialEclipse.commands.extensions.HgStripClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQAppliedClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQDeleteClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQFinishClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQFoldClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQImportClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQNewClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQPopClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQPushClient;
import com.vectrace.MercurialEclipse.commands.extensions.mq.HgQRefreshClient;
import com.vectrace.MercurialEclipse.exception.HgException;
import com.vectrace.MercurialEclipse.menu.SwitchHandler;
import com.vectrace.MercurialEclipse.model.ChangeSet;
import com.vectrace.MercurialEclipse.model.HgRoot;
import com.vectrace.MercurialEclipse.model.JHgChangeSet;
import com.vectrace.MercurialEclipse.mylyn.MylynFacadeFactory;
import com.vectrace.MercurialEclipse.preferences.MercurialPreferenceConstants;
import com.vectrace.MercurialEclipse.storage.HgCommitMessageManager;
import com.vectrace.MercurialEclipse.synchronize.cs.UncommittedChangesetGroup;
import com.vectrace.MercurialEclipse.team.ActionRevert;
import com.vectrace.MercurialEclipse.team.MercurialTeamProvider;
import com.vectrace.MercurialEclipse.team.cache.LocalChangesetCache;
import com.vectrace.MercurialEclipse.team.cache.RefreshRootJob;
import com.vectrace.MercurialEclipse.team.cache.RefreshWorkspaceStatusJob;
import com.vectrace.MercurialEclipse.ui.ChangesetInfoTray;
import com.vectrace.MercurialEclipse.ui.CommitFilesChooser;
import com.vectrace.MercurialEclipse.ui.SWTWidgetHelper;
import com.vectrace.MercurialEclipse.utils.ResourceUtils;
import com.vectrace.MercurialEclipse.utils.StringUtils;

/**
 * A commit dialog box allowing choosing of what files to commit and a commit message for those
 * files. Untracked files may also be chosen.
 */
public class CommitDialog extends BaseCommitDialog {
    public static final String FILE_MODIFIED = Messages.getString("CommitDialog.modified"); //$NON-NLS-1$
    public static final String FILE_ADDED = Messages.getString("CommitDialog.added"); //$NON-NLS-1$
    public static final String FILE_REMOVED = Messages.getString("CommitDialog.removed"); //$NON-NLS-1$
    public static final String FILE_UNTRACKED = Messages.getString("CommitDialog.untracked"); //$NON-NLS-1$
    public static final String FILE_DELETED = Messages.getString("CommitDialog.deletedInWorkspace"); //$NON-NLS-1$
    public static final String FILE_CLEAN = Messages.getString("CommitDialog.clean"); //$NON-NLS-1$

    protected CommitFilesChooser commitFilesList;
    private List<IResource> resourcesToAdd;
    private List<IResource> resourcesToCommit;
    private List<IResource> resourcesToRemove;
    private final List<IResource> inResources;
    private Text userTextField;
    private String user;
    private Button revertCheckBox;
    protected final HgRoot root;
    private Button closeBranchCheckBox;
    private Button amendCheckbox;
    private JHgChangeSet currentChangeset;
    private ProgressMonitorPart monitor;
    private Sash sash;
    private ChangesetInfoTray tray;
    private Label leftSeparator;
    private Label rightSeparator;
    private Control trayControl;

    /**
     * @param hgRoot
     *            non null
     * @param resources
     *            might be null
     */
    public CommitDialog(Shell shell, HgRoot hgRoot, List<IResource> resources) {
        super(shell);

        Assert.isNotNull(hgRoot);

        // refresh the project first?
        IPreferenceStore store = MercurialEclipsePlugin.getDefault().getPreferenceStore();
        boolean refreshFirst = store.getBoolean(MercurialPreferenceConstants.PREF_REFRESH_BEFORE_COMMIT);
        if (refreshFirst) {
            try {
                hgRoot.getResource().refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
            } catch (Exception ex) {
                ex.printStackTrace(System.err);
            }
        }

        this.root = hgRoot;
        setShellStyle(getShellStyle() | SWT.RESIZE | SWT.TITLE);
        setBlockOnOpen(false);
        inResources = resources;
    }

    public List<IResource> getResourcesToCommit() {
        return resourcesToCommit;
    }

    public List<IResource> getResourcesToAdd() {
        return resourcesToAdd;
    }

    @Override
    protected IDialogSettings getDialogBoundsSettings() {
        IDialogSettings dialogSettings = MercurialEclipsePlugin.getDefault().getDialogSettings();
        String sectionName = getClass().getSimpleName();
        IDialogSettings section = dialogSettings.getSection(sectionName);
        if (section == null) {
            dialogSettings.addNewSection(sectionName);
        }
        return section;
    }

    /**
     * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsStrategy()
     */
    @Override
    protected int getDialogBoundsStrategy() {
        int strategy = super.getDialogBoundsStrategy();

        // When amend is set it changes the dialog size
        if (trayControl != null) {
            strategy &= ~DIALOG_PERSISTSIZE;
        }

        return strategy;
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite container = SWTWidgetHelper.createComposite(parent, 1);
        GridData gd = SWTWidgetHelper.getFillGD(400);
        gd.minimumWidth = 500;
        container.setLayoutData(gd);
        super.createDialogArea(parent);

        container.addKeyListener(new KeyListener() {
            public void keyReleased(KeyEvent e) {
                if (e.character == SWT.CR && e.stateMask == SWT.MOD1) {
                    okPressed();
                    e.doit = false;
                }
            }

            public void keyPressed(KeyEvent e) {
            }
        });

        createCommitTextBox(container);
        createOldCommitCombo(container);
        createUserCommitCombo(container);
        createCloseBranchCheckBox(container);
        createAmendCheckBox(container);
        createRevertCheckBox(container);
        commitFilesList = createFilesList(container);

        getShell().setText(Messages.getString("CommitDialog.window.title") + ": " + root.getName()); //$NON-NLS-1$
        setTitle(Messages.getString("CommitDialog.title")); //$NON-NLS-1$";
        monitor = new ProgressMonitorPart(container, null);
        monitor.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        monitor.setVisible(false);
        return container;
    }

    @Override
    protected Control createContents(Composite parent) {
        Control control = super.createContents(parent);
        final String initialCommitMessage = getInitialCommitMessage();
        setCommitMessage(initialCommitMessage);

        if (commitTextBox != null) {
            commitTextBox.getTextWidget().setFocus();
            commitTextBox.getTextWidget().selectAll();
        }

        return control;
    }

    protected String getInitialCommitMessage() {
        return MylynFacadeFactory.getMylynFacade()
                .getCurrentTaskComment(inResources == null ? null : inResources.toArray(new IResource[0]));
    }

    /**
     * Tells whether the text in the Commit textbox is a default commit message.
     */
    private boolean isDefaultCommitMessage() {
        String message = commitTextBox.getDocument().get();
        return StringUtils.isEmpty(message) || DEFAULT_COMMIT_MESSAGE.equals(message);
    }

    @Override
    protected void validateControls() {
        if (isDefaultCommitMessage()) {
            setErrorMessage(Messages.getString("CommitDialog.commitMessageRequired")); // ";
            getButton(IDialogConstants.OK_ID).setEnabled(false);
        } else if (commitFilesList.getCheckedResources().size() == 0 && !options.allowEmptyCommit
                && commitFilesList.isSelectable() && (amendCheckbox == null || !amendCheckbox.getSelection())
                && !isCloseBranchSelected()) {
            setErrorMessage(Messages.getString("CommitDialog.noResourcesSelected")); // ";
            getButton(IDialogConstants.OK_ID).setEnabled(false);
        } else {
            setErrorMessage(null);
            getButton(IDialogConstants.OK_ID).setEnabled(true);

            if (isAmend() && HgFeatures.PHASES.isEnabled() && currentChangeset.getPhase() == Phase.PUBLIC) {
                setMessage(Messages.getString("CommitDialog.amendPublicWarning"), IMessageProvider.WARNING);
            } else {
                setMessage(Messages.getString(options.readyMessageSelector == null ? "CommitDialog.readyToCommit"
                        : options.readyMessageSelector));
            }
        }
    }

    private void createRevertCheckBox(Composite container) {
        if (!options.showRevert) {
            return;
        }
        revertCheckBox = SWTWidgetHelper.createCheckBox(container,
                Messages.getString("CommitDialog.revertCheckBoxLabel.revertUncheckedResources")); //$NON-NLS-1$
    }

    private void createCloseBranchCheckBox(Composite container) {
        if (!options.showCloseBranch) {
            return;
        }
        closeBranchCheckBox = SWTWidgetHelper.createCheckBox(container,
                Messages.getString("CommitDialog.closeBranch"));

        closeBranchCheckBox.addSelectionListener(new SelectionListener() {
            public void widgetSelected(SelectionEvent e) {
                validateControls();

                if (closeBranchCheckBox.getSelection() && isDefaultCommitMessage()) {
                    commitTextDocument.set(Messages.getString("CommitDialog.closingBranch",
                            MercurialTeamProvider.getCurrentBranch(root)));
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
    }

    private void createAmendCheckBox(Composite container) {
        if (!options.showAmend) {
            return;
        }
        try {
            currentChangeset = LocalChangesetCache.getInstance().getCurrentChangeSet(root);
        } catch (HgException e) {
            MercurialEclipsePlugin.logError(e);
            setErrorMessage(e.getLocalizedMessage());
        }
        if (currentChangeset == null) {
            return;
        }
        String branch = MercurialTeamProvider.getCurrentBranch(root);
        String label = Messages.getString("CommitDialog.amendCurrentChangeset1") + currentChangeset.getIndex() + ":" //$NON-NLS-2$
                + currentChangeset.getNodeShort() + "@" + branch + ")"; //$NON-NLS-1$ //$NON-NLS-2$
        amendCheckbox = SWTWidgetHelper.createCheckBox(container, label);
        amendCheckbox.addSelectionListener(new SelectionListener() {
            public void widgetSelected(SelectionEvent e) {
                if (amendCheckbox.getSelection() && currentChangeset != null) {
                    openSash();
                } else {
                    closeSash();
                }
                validateControls();
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                widgetSelected(e);
            }
        });
    }

    protected CommitFilesChooser createFilesList(Composite container) {
        SWTWidgetHelper.createLabel(container,
                Messages.getString(options.filesSelectable ? "CommitDialog.selectFiles.selectable"
                        : "CommitDialog.selectFiles.unselectable"));
        CommitFilesChooser chooser = new CommitFilesChooser(container, areFilesSelectable(), inResources, true,
                true, false);

        chooser.addStateListener(new Listener() {
            public void handleEvent(Event event) {
                validateControls();
            }
        });

        IResource[] mylynTaskResources = MylynFacadeFactory.getMylynFacade().getCurrentTaskResources();
        if (mylynTaskResources != null) {
            chooser.setSelectedResources(Arrays.asList(mylynTaskResources));
        }
        return chooser;
    }

    private boolean areFilesSelectable() {
        return options.filesSelectable;
    }

    private void createUserCommitCombo(Composite container) {
        if (!options.showUser) {
            return;
        }
        Composite comp = SWTWidgetHelper.createComposite(container, 2);
        SWTWidgetHelper.createLabel(comp, Messages.getString("CommitDialog.userLabel.text"));
        userTextField = SWTWidgetHelper.createTextField(comp);
        user = getInitialCommitUserName();
        userTextField.setText(user);
    }

    protected String getInitialCommitUserName() {
        return HgCommitMessageManager.getDefaultCommitName(root);
    }

    /**
     * Override the OK button pressed to capture the info we want first and then call super.
     */
    @Override
    protected void okPressed() {
        IProgressMonitor pm = monitor;
        monitor.setVisible(true);
        monitor.attachToCancelComponent(getButton(IDialogConstants.CANCEL_ID));
        pm.beginTask("Committing...", 20);

        // get checked resources and add them to the maps to be used by hg
        pm.subTask("Determining resources to add, remove and to commit.");
        resourcesToAdd = commitFilesList.getCheckedResources(FILE_UNTRACKED);
        resourcesToCommit = commitFilesList.getCheckedResources();
        resourcesToRemove = commitFilesList.getCheckedResources(FILE_DELETED);
        pm.worked(3);

        // get commit message
        String commitMessage = getCommitMessage();

        // get commit username
        user = userTextField.getText();
        if (user == null || user.length() == 0) {
            user = getInitialCommitUserName();
        }

        boolean closeBranchSelected = isCloseBranchSelected();
        boolean amend = isAmend() && currentChangeset != null;

        try {
            // amend changeset
            if (amend) {
                if (closeBranchSelected) {
                    setErrorMessage(Messages.getString("Cannot close branch while amending"));
                    return;
                }

                // only one root allowed when amending
                Map<HgRoot, List<IResource>> map = ResourceUtils.groupByRoot(resourcesToCommit);
                if (map.size() > 1) {
                    setErrorMessage(Messages.getString("CommitDialog.amendingOnlyForOneRoot"));
                    return;
                }

                // only proceed if files are present
                if (currentChangeset.getChangedFiles().isEmpty()) {
                    setErrorMessage(Messages.getString("CommitDialog.noChangesetToAmend"));
                    return;
                }

                if (currentChangeset.isMerge()) {
                    setErrorMessage(Messages.getString("CommitDialog.noAmendForMerge"));
                    return;
                }

                if (HgQAppliedClient.getAppliedPatches(root).size() > 0) {
                    setErrorMessage("Cannot amend when there are applied MQ patches");
                    return;
                }

                pm.worked(1);
                if (!confirmHistoryRewrite()) {
                    setErrorMessage(Messages.getString("CommitDialog.abortedAmending"));
                    return;
                }
            }

            // add new resources
            pm.subTask("Adding selected untracked resources to repository.");
            HgAddClient.addResources(resourcesToAdd, pm);
            pm.worked(1);

            // remove deleted resources
            pm.subTask("Removing selected deleted resources from repository.");
            HgRemoveClient.removeResources(resourcesToRemove);
            pm.worked(1);

            // perform commit
            pm.subTask("Committing resources to repository.");
            if (amend && !HgFeatures.COMMIT_AMEND.isEnabled()) {
                performAmendMQ(commitMessage, currentChangeset);
            } else {
                performCommit(commitMessage, closeBranchSelected, amend);
            }
            pm.worked(1);

            /* Store commit message in the database if not a default message */
            if (!commitMessage.equals(options.defaultCommitMessage) || (// Covers the case of changesets of which the msg has not been edited between
                                                                        // the dialog opening and the user clicking the OK button.
            options.committingChangeset && !commitMessage.startsWith(UncommittedChangesetGroup.DEFAULT_NAME))) {
                pm.subTask("Storing the commit message for later use.");
                MercurialEclipsePlugin.getCommitMessageManager().saveCommitMessage(commitMessage);
            }
            pm.worked(1);

            // revertCheckBox can be null if this is a merge dialog
            if (isRevertSelected()) {
                revertResources();
            }
        } catch (HgException e) {
            setErrorMessage(e.getConciseMessage());
            MercurialEclipsePlugin.logError(e);
            return;
        } catch (CoreException e) {
            setErrorMessage(e.getLocalizedMessage());
            MercurialEclipsePlugin.logError(e);
            return;
        } finally {
            monitor.done();
            monitor.removeFromCancelComponent(getButton(IDialogConstants.CANCEL_ID));
            monitor.setVisible(false);
        }
        super.okPressed();

        if (closeBranchSelected) {
            // open "switch to" dialog as the user decided to close the branch and will
            // go to the switch dialog anyway
            try {
                new SwitchHandler().run(root);
            } catch (CoreException e) {
                MercurialEclipsePlugin.logError(e);
            }
        }
    }

    private boolean isAmend() {
        return amendCheckbox != null && amendCheckbox.getSelection();
    }

    private boolean isRevertSelected() {
        return revertCheckBox != null && revertCheckBox.getSelection();
    }

    private boolean isCloseBranchSelected() {
        return closeBranchCheckBox != null && closeBranchCheckBox.getSelection();
    }

    private boolean confirmHistoryRewrite() {
        if (HgFeatures.PHASES.isEnabled()) {
            // For secret or draft silently allow amend
            if (Phase.PUBLIC == currentChangeset.getPhase()) {
                if (!MessageDialog.openConfirm(getShell(), Messages.getString("CommitDialog.amendPublic.title"),
                        Messages.getString("CommitDialog.amendPublic.message"))) {
                    return false;
                }

                currentChangeset.setDraft();
            }

            return true;
        }

        // Always prompt if phases are not supported
        MessageDialog dialog = new MessageDialog(getShell(),
                Messages.getString("CommitDialog.reallyAmendAndRewriteHistory"), //$NON-NLS-1$
                null, Messages.getString("CommitDialog.amendWarning1") //$NON-NLS-1$
                        + Messages.getString("CommitDialog.amendWarning2") //$NON-NLS-1$
                        + Messages.getString("CommitDialog.amendWarning3"), //$NON-NLS-1$
                MessageDialog.CONFIRM, new String[] { IDialogConstants.YES_LABEL, IDialogConstants.CANCEL_LABEL }, 1 // default index - cancel
        );
        dialog.setBlockOnOpen(true); // if false then may show in background
        return dialog.open() == 0; // 0 means yes

    }

    private static String makePatchName(String id) {
        return "HGE-" + id + "-" + System.currentTimeMillis() + HgPatchClient.PATCH_EXTENSION;
    }

    /**
     * Amend adds the changes in the selected files to the parent changeset. QRefresh isn't used
     * generally because when it fails (eg inconsistent line endings) then programatically restoring
     * state is hard. Instead the parent is qimported (a), the selected files are qnew'd (b), if
     * still dirty remaining files are added to a 2nd qnew'd patch (c). Then a and b are folded and
     * c is applied and then stripped. This also works around the problem of qrefresh unexpectedly
     * adding changes to a file if changes in that file are already in the patch.
     *
     * TODO: allow amending commit user as well.
     */
    protected void performAmendMQ(String message, ChangeSet cs) throws HgException {
        final IProgressMonitor pm = monitor;
        final String origPatchName = makePatchName("amend-orig");
        boolean exceptionExpected = true;
        boolean refreshWorspace = false;

        try {
            pm.subTask("Importing changeset into MQ.");
            HgQImportClient.qimport(root, true, currentChangeset, origPatchName);
            pm.worked(1);

            if (!resourcesToCommit.isEmpty() || HgStatusClient.isDirty(root)) {
                final String amendPatchName = makePatchName("amendment");
                String notIncludedPatchName = null;

                refreshWorspace = true;
                pm.subTask("Creating new MQ patch containing changes to amend");

                // May throw exceptions eg: inconsistent newline style or patch file exists
                HgQNewClient.createNewPatch(root, "Changes to amend with previous", resourcesToCommit, user, null,
                        amendPatchName);
                pm.worked(1);

                if (HgStatusClient.isDirty(root)) {
                    notIncludedPatchName = makePatchName("notSelected");

                    // Note: If an exception occurs here 2 patches will be qfinished
                    // This error is semi-expected
                    HgQNewClient.createNewPatch(root, "Changes to amend with previous", user, null,
                            notIncludedPatchName);

                    exceptionExpected = false;
                    HgStatusClient.assertClean(root);
                }

                // These should not fail
                pm.subTask("Folding MQ patches");
                exceptionExpected = false;
                HgQPopClient.pop(root, false, origPatchName);
                pm.worked(1);
                HgStatusClient.assertClean(root);
                HgQFoldClient.fold(root, false, message, amendPatchName);
                pm.worked(1);

                if (notIncludedPatchName != null) {
                    pm.subTask("Restoring uncommitted changes");
                    HgQPushClient.push(root, false, notIncludedPatchName);
                    HgStripClient.stripCurrent(root, true, false, false);
                    HgQDeleteClient.delete(root, false, notIncludedPatchName);
                    pm.worked(1);
                }
            } else {
                // refresh patch to update commit message
                pm.subTask("Refreshing MQ amend patch with newly added/removed/changed files.");
                HgQRefreshClient.refresh(root, true, resourcesToCommit, message, true);
                pm.worked(4);
            }
        } catch (HgException e) {
            if (!exceptionExpected) {
                MessageDialog.openError(getShell(), "Error amending!",
                        "An unexpected error occurred performing amend!\n"
                                + "To recover refer to the Mercurial Patch Queue View.\n"
                                + "Intermediate steps are saved in <repo root>/.hg/patches folder.\n"
                                + "Please file a MercurialEclipse bug and include the Eclipse error log.");
                MercurialEclipsePlugin.logError("An unexpected error occurred during amend!", e);
            }

            throw e;
        } finally {
            try {
                pm.subTask("Removing amend patch from MQ and promoting it to repository.");
                HgQFinishClient.finishAllApplied(root);
                pm.worked(1);
            } catch (HgException e) {
                MessageDialog.openError(getShell(), "Error amending!",
                        "An unexpected error occurred restoring state during amend!\n"
                                + "To recover refer to the Mercurial Patch Queue View.\n"
                                + "Intermediate steps are saved in <repo root>/.hg/patches folder.\n"
                                + "Please file a MercurialEclipse bug and include the Eclipse error log.");
                MercurialEclipsePlugin.logError("An unexpected error occurred restoring state during amend!", e);
            }

            RefreshRootJob job;
            if (refreshWorspace) {
                // The file contents should be the same but hg may have changed timestamps, etc
                // which will cause out of sync messages.
                job = new RefreshWorkspaceStatusJob(root, RefreshRootJob.LOCAL_AND_OUTGOING);
            } else {
                job = new RefreshRootJob(Messages.getString("CommitDialog.refreshingAfterAmend1") + root.getName() //$NON-NLS-1$
                        + Messages.getString("CommitDialog.refreshingAfterAmend2"), root, //$NON-NLS-1$
                        RefreshRootJob.LOCAL_AND_OUTGOING);
            }
            job.schedule();
        }
    }

    protected void performCommit(String message, boolean closeBranch, boolean amend) throws CoreException {
        if (resourcesToCommit.isEmpty() && (!options.filesSelectable || closeBranch || amend)) {
            // enforce commit anyway
            HgCommitClient.commitResources(root, user, message, closeBranch, amend, monitor);
        }
        HgCommitClient.commitResources(resourcesToCommit, user, message, closeBranch, amend, monitor);
    }

    private void revertResources() {
        final List<IResource> revertResources = commitFilesList.getUncheckedResources(FILE_ADDED, FILE_DELETED,
                FILE_MODIFIED, FILE_REMOVED);
        final List<IResource> untrackedResources = commitFilesList.getUncheckedResources(FILE_UNTRACKED);
        new Job(Messages.getString("CommitDialog.revertJob.RevertingFiles")) {
            @Override
            protected IStatus run(IProgressMonitor m) {
                ActionRevert action = new ActionRevert();
                try {
                    action.doRevert(m, revertResources, untrackedResources, false, null);
                } catch (HgException e) {
                    MercurialEclipsePlugin.logError(e);
                    return e.getStatus();
                }
                return Status.OK_STATUS;
            }
        }.schedule();
    }

    public List<IResource> getResourcesToRemove() {
        return resourcesToRemove;
    }

    protected void setCommitMessage(String msg) {
        if (msg == null) {
            msg = options.defaultCommitMessage;
        }
        commitTextDocument.set(msg);

        if (commitTextBox != null) {
            commitTextBox.setSelectedRange(0, msg.length());
        }
    }

    public String getUser() {
        return user;
    }

    private void openSash() {
        IProgressMonitor pm = this.monitor;
        monitor.setVisible(true);
        pm.beginTask("Loading amend data.", 2);

        // only one root allowed when amending
        Map<HgRoot, List<IResource>> map = ResourceUtils.groupByRoot(resourcesToCommit);
        if (map.size() > 1) {
            setMessage(Messages.getString("CommitDialog.amendingOnlyForOneRoot"));
            amendCheckbox.setEnabled(false);
            amendCheckbox.setSelection(false);
        }

        pm.done();
        monitor.setVisible(false);

        // set old commit message
        IDocument msg = commitTextDocument;
        if ("".equals(msg.get()) || msg.get().equals(DEFAULT_COMMIT_MESSAGE)) {
            msg.set(currentChangeset.getComment());
        }

        // create tray controls
        ChangesetInfoTray t = new ChangesetInfoTray(currentChangeset);
        final Shell shell = getShell();
        leftSeparator = new Label(shell, SWT.SEPARATOR | SWT.VERTICAL);
        leftSeparator.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        sash = new Sash(shell, SWT.VERTICAL);
        sash.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        rightSeparator = new Label(shell, SWT.SEPARATOR | SWT.VERTICAL);
        rightSeparator.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        trayControl = t.createContents(shell);

        // calculate width
        Rectangle clientArea = shell.getClientArea();
        final GridData data = new GridData(GridData.FILL_VERTICAL);
        data.widthHint = trayControl.computeSize(clientArea.width * 3 / 4, clientArea.height).x;
        trayControl.setLayoutData(data);
        int trayWidth = leftSeparator.computeSize(SWT.DEFAULT, clientArea.height).x
                + sash.computeSize(SWT.DEFAULT, clientArea.height).x
                + rightSeparator.computeSize(SWT.DEFAULT, clientArea.height).x + data.widthHint;
        Rectangle bounds = shell.getBounds();
        shell.setBounds(bounds.x - ((Window.getDefaultOrientation() == SWT.RIGHT_TO_LEFT) ? trayWidth : 0),
                bounds.y, bounds.width + trayWidth, bounds.height);
        sash.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                if (event.detail != SWT.DRAG) {
                    Rectangle rect = shell.getClientArea();
                    int newWidth = rect.width - event.x - (sash.getSize().x + rightSeparator.getSize().x);
                    if (newWidth != data.widthHint) {
                        data.widthHint = newWidth;
                        shell.layout();
                    }
                }
            }
        });
        this.tray = t;
    }

    private void closeSash() {
        if (tray == null) {
            throw new IllegalStateException("Tray was not open"); //$NON-NLS-1$
        }
        int trayWidth = trayControl.getSize().x + leftSeparator.getSize().x + sash.getSize().x
                + rightSeparator.getSize().x;
        trayControl.dispose();
        trayControl = null;
        tray = null;
        leftSeparator.dispose();
        leftSeparator = null;
        rightSeparator.dispose();
        rightSeparator = null;
        sash.dispose();
        sash = null;
        Shell shell = getShell();
        Rectangle bounds = shell.getBounds();
        shell.setBounds(bounds.x + ((Window.getDefaultOrientation() == SWT.RIGHT_TO_LEFT) ? trayWidth : 0),
                bounds.y, bounds.width - trayWidth, bounds.height);
    }

    /**
     * @param options non null
     */
    public void setOptions(Options options) {
        this.options = options;
    }

}