com.genuitec.org.eclipse.egit.ui.internal.branch.BranchOperationUI.java Source code

Java tutorial

Introduction

Here is the source code for com.genuitec.org.eclipse.egit.ui.internal.branch.BranchOperationUI.java

Source

/**
 *  Copyright (c) 2015 Genuitec LLC.
 *  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:
 *  Piotr Tomiak <piotr@genuitec.com> - initial API and implementation
 */
package com.genuitec.org.eclipse.egit.ui.internal.branch;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.ISourceContainer;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;
import org.eclipse.debug.core.sourcelookup.containers.ProjectSourceContainer;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.util.ProjectUtil;
import org.eclipse.egit.core.op.BranchOperation;
import org.eclipse.egit.core.op.IEGitOperation.PostExecuteTask;
import org.eclipse.egit.core.op.IEGitOperation.PreExecuteTask;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.branch.CleanupUncomittedChangesDialog;
import org.eclipse.egit.ui.internal.decorators.GitLightweightDecorator;
import org.eclipse.egit.ui.internal.dialogs.AbstractBranchSelectionDialog;
import org.eclipse.egit.ui.internal.dialogs.CheckoutDialog;
import org.eclipse.egit.ui.internal.dialogs.DeleteBranchDialog;
import org.eclipse.egit.ui.internal.dialogs.NonDeletedFilesDialog;
import org.eclipse.egit.ui.internal.dialogs.RenameBranchDialog;
import org.eclipse.egit.ui.internal.repository.CreateBranchWizard;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.jgit.api.CheckoutResult;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.PlatformUI;

/**
 * The UI wrapper for {@link BranchOperation}
 */
@SuppressWarnings("restriction")
public class BranchOperationUI {
    // create
    private final static int MODE_CREATE = 1;

    private final static int MODE_CHECKOUT = 2;

    private final static int MODE_DELETE = 3;

    private final static int MODE_RENAME = 4;

    private final Repository repository;

    private String target;

    private String base;

    /**
     * In the case of checkout conflicts, a dialog is shown to let the user
     * stash, reset or commit. After that, checkout is tried again. The second
     * time we do checkout, we don't want to ask any questions we already asked
     * the first time, so this will be false then.
     */
    private final boolean showQuestionAboutTarget;

    private final int mode;

    private DoneCallback doneCallback;

    /**
     * Create an operation for manipulating branches
     *
     * @param repository
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI rename(Repository repository) {
        return new BranchOperationUI(repository, MODE_RENAME);
    }

    /**
     * Create an operation for manipulating branches
     *
     * @param repository
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI delete(Repository repository) {
        return new BranchOperationUI(repository, MODE_DELETE);
    }

    /**
     * Create an operation for creating a local branch
     *
     * @param repository
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI create(Repository repository) {
        BranchOperationUI op = new BranchOperationUI(repository, MODE_CREATE);
        return op;
    }

    /**
     * Create an operation for creating a local branch with a given base reference
     *
     * @param repository
     * @param baseRef
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI createWithRef(Repository repository, String baseRef) {
        BranchOperationUI op = new BranchOperationUI(repository, MODE_CREATE);
        op.base = baseRef;
        return op;
    }

    /**
     * Create an operation for checking out a local branch
     *
     * @param repository
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI checkout(Repository repository) {
        return new BranchOperationUI(repository, MODE_CHECKOUT);
    }

    /**
     * Create an operation for checking out a branch
     *
     * @param repository
     * @param target
     *            a valid {@link Ref} name or commit id
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI checkout(Repository repository, String target) {
        return new BranchOperationUI(repository, target, true);
    }

    /**
     * Create an operation for checking out a branch without showing a question
     * dialog about the target.
     *
     * @param repository
     * @param target
     *            a valid {@link Ref} name or commit id
     * @return the {@link BranchOperationUI}
     */
    public static BranchOperationUI checkoutWithoutQuestion(Repository repository, String target) {
        return new BranchOperationUI(repository, target, false);
    }

    /**
     * @param refName
     *            the full ref name which will be checked out
     * @return true if checkout will need additional input from the user before
     *         continuing
     */
    public static boolean checkoutWillShowQuestionDialog(String refName) {
        return shouldShowCheckoutRemoteTrackingDialog(refName);
    }

    /**
     * @param repository
     * @param target
     * @param showQuestionAboutTarget
     */
    private BranchOperationUI(Repository repository, String target, boolean showQuestionAboutTarget) {
        this.repository = repository;
        this.target = target;
        this.showQuestionAboutTarget = showQuestionAboutTarget;
        this.mode = 0;
    }

    /**
     * Select and checkout a branch
     *
     * @param repository
     * @param mode
     */
    private BranchOperationUI(Repository repository, int mode) {
        this.repository = repository;
        this.mode = mode;
        this.showQuestionAboutTarget = true;
    }

    public BranchOperationUI doneCallback(DoneCallback doneCallback) {
        this.doneCallback = doneCallback;
        return this;
    }

    /**
     * Starts the operation asynchronously
     */
    public void start() {
        if (!repository.getRepositoryState().canCheckout()) {
            MessageDialog.openError(getShell(), UIText.BranchAction_cannotCheckout, NLS
                    .bind(UIText.BranchAction_repositoryState, repository.getRepositoryState().getDescription()));
            return;
        }

        if (shouldCancelBecauseOfRunningLaunches())
            return;

        askForTargetIfNecessary();
        if (target == null)
            return;

        String repoName = Activator.getDefault().getRepositoryUtil().getRepositoryName(repository);
        String jobname = NLS.bind(UIText.BranchAction_checkingOut, repoName, target);

        final boolean restore = Activator.getDefault().getPreferenceStore()
                .getBoolean(UIPreferences.CHECKOUT_PROJECT_RESTORE);
        final BranchOperation bop = new BranchOperation(repository, target, !restore);

        Job job = new WorkspaceJob(jobname) {

            @Override
            public IStatus runInWorkspace(IProgressMonitor monitor) {
                try {
                    if (restore) {
                        final BranchProjectTracker tracker = new BranchProjectTracker(repository);
                        final AtomicReference<IMemento> memento = new AtomicReference<IMemento>();
                        bop.addPreExecuteTask(new PreExecuteTask() {

                            public void preExecute(Repository pRepo, IProgressMonitor pMonitor)
                                    throws CoreException {
                                // Snapshot current projects before checkout
                                // begins
                                memento.set(tracker.snapshot());
                            }
                        });
                        bop.addPostExecuteTask(new PostExecuteTask() {

                            public void postExecute(Repository pRepo, IProgressMonitor pMonitor)
                                    throws CoreException {
                                IMemento snapshot = memento.get();
                                if (snapshot == null)
                                    return;
                                // Save previous branch's projects and restore
                                // current branch's projects
                                tracker.save(snapshot).restore(pMonitor);
                            }
                        });
                    }

                    bop.execute(monitor);
                } catch (CoreException e) {
                    switch (bop.getResult().getStatus()) {
                    case CONFLICTS:
                    case NONDELETED:
                        break;
                    default:
                        return Activator.createErrorStatus(UIText.BranchAction_branchFailed, e);
                    }
                } finally {
                    GitLightweightDecorator.refresh();
                }
                return Status.OK_STATUS;
            }

            @Override
            public boolean belongsTo(Object family) {
                if (family.equals(JobFamilies.CHECKOUT))
                    return true;
                return super.belongsTo(family);
            }
        };
        job.setUser(true);
        // Set scheduling rule to workspace because we may have to re-create
        // projects using BranchProjectTracker.
        if (restore)
            job.setRule(ResourcesPlugin.getWorkspace().getRoot());
        job.addJobChangeListener(new JobChangeAdapter() {
            @Override
            public void done(IJobChangeEvent cevent) {
                show(bop.getResult());
            }
        });
        job.schedule();
    }

    /**
     * Runs the operation synchronously
     *
     * @param monitor
     * @throws CoreException
     *
     */
    public void run(IProgressMonitor monitor) throws CoreException {
        if (!repository.getRepositoryState().canCheckout()) {
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                public void run() {
                    MessageDialog.openError(getShell(), UIText.BranchAction_cannotCheckout, NLS.bind(
                            UIText.BranchAction_repositoryState, repository.getRepositoryState().getDescription()));
                }
            });
            return;
        }

        if (shouldCancelBecauseOfRunningLaunches())
            return;

        askForTargetIfNecessary();
        if (target == null)
            return;

        BranchOperation bop = new BranchOperation(repository, target);
        bop.execute(monitor);

        show(bop.getResult());
    }

    private void askForTargetIfNecessary() {
        if (target == null)
            target = getTargetWithDialog();
        if (target != null && showQuestionAboutTarget) {
            if (shouldShowCheckoutRemoteTrackingDialog(target))
                target = getTargetWithCheckoutRemoteTrackingDialog();
        }
    }

    private static boolean shouldShowCheckoutRemoteTrackingDialog(String refName) {
        boolean isRemoteTrackingBranch = refName != null && refName.startsWith(Constants.R_REMOTES);
        if (isRemoteTrackingBranch) {
            boolean showDetachedHeadWarning = Activator.getDefault().getPreferenceStore()
                    .getBoolean(UIPreferences.SHOW_DETACHED_HEAD_WARNING);
            // If the user has not yet chosen to ignore the warning about
            // getting into a "detached HEAD" state, then we show them a dialog
            // whether a remote-tracking branch should be checked out with a
            // detached HEAD or checking it out as a new local branch.
            return showDetachedHeadWarning;
        } else {
            return false;
        }
    }

    private String getTargetWithDialog() {
        AbstractBranchSelectionDialog dialog;
        switch (mode) {
        case MODE_CHECKOUT:
            dialog = new CheckoutDialog(getShell(), repository);
            break;
        case MODE_CREATE:
            CreateBranchWizard wiz;
            try {
                if (base == null)
                    base = repository.getFullBranch();
                wiz = new CreateBranchWizard(repository, base);
            } catch (IOException e) {
                wiz = new CreateBranchWizard(repository);
            }
            new WizardDialog(getShell(), wiz).open();
            return null;
        case MODE_DELETE:
            new DeleteBranchDialog(getShell(), repository).open();
            return null;
        case MODE_RENAME:
            new RenameBranchDialog(getShell(), repository).open();
            return null;
        default:
            return null;
        }

        if (dialog.open() != Window.OK) {
            return null;
        }
        return dialog.getRefName();
    }

    private String getTargetWithCheckoutRemoteTrackingDialog() {
        String[] buttons = new String[] { UIText.BranchOperationUI_CheckoutRemoteTrackingAsLocal,
                UIText.BranchOperationUI_CheckoutRemoteTrackingCommit, IDialogConstants.CANCEL_LABEL };
        MessageDialog questionDialog = new MessageDialog(getShell(),
                UIText.BranchOperationUI_CheckoutRemoteTrackingTitle, null,
                UIText.BranchOperationUI_CheckoutRemoteTrackingQuestion, MessageDialog.QUESTION, buttons, 0);
        int result = questionDialog.open();
        if (result == 0) {
            // Check out as new local branch
            CreateBranchWizard wizard = new CreateBranchWizard(repository, target);
            WizardDialog createBranchDialog = new WizardDialog(getShell(), wizard);
            createBranchDialog.open();
            return null;
        } else if (result == 1) {
            // Check out commit
            return target;
        } else {
            // Cancel
            return null;
        }
    }

    private Shell getShell() {
        return PlatformUI.getWorkbench().getDisplay().getActiveShell();
    }

    /**
     * @param result
     *            the result to show
     */
    public void show(final CheckoutResult result) {
        if (result.getStatus() == CheckoutResult.Status.CONFLICTS) {
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                public void run() {
                    Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
                    CleanupUncomittedChangesDialog cleanupUncomittedChangesDialog = new CleanupUncomittedChangesDialog(
                            shell, UIText.BranchResultDialog_CheckoutConflictsTitle,
                            NLS.bind(UIText.BranchResultDialog_CheckoutConflictsMessage,
                                    Repository.shortenRefName(target)),
                            repository, result.getConflictList());
                    cleanupUncomittedChangesDialog.open();
                    if (cleanupUncomittedChangesDialog.shouldContinue()) {
                        BranchOperationUI op = BranchOperationUI.checkoutWithoutQuestion(repository, target)
                                .doneCallback(doneCallback);
                        op.start();
                    }
                }
            });
        } else if (result.getStatus() == CheckoutResult.Status.NONDELETED) {
            // double-check if the files are still there
            boolean show = false;
            List<String> pathList = result.getUndeletedList();
            for (String path : pathList)
                if (new File(repository.getWorkTree(), path).exists()) {
                    show = true;
                    break;
                }
            if (!show)
                return;
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                public void run() {
                    Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
                    new NonDeletedFilesDialog(shell, repository, result.getUndeletedList()).open();
                }
            });
        } else if (result.getStatus() == CheckoutResult.Status.OK) {
            if (RepositoryUtil.isDetachedHead(repository))
                showDetachedHeadWarning();
        }
        if (doneCallback != null) {
            doneCallback.done(result);
        }
    }

    private void showDetachedHeadWarning() {
        PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
            public void run() {
                IPreferenceStore store = Activator.getDefault().getPreferenceStore();

                if (store.getBoolean(UIPreferences.SHOW_DETACHED_HEAD_WARNING)) {
                    String toggleMessage = UIText.BranchResultDialog_DetachedHeadWarningDontShowAgain;
                    MessageDialogWithToggle.openInformation(
                            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                            UIText.BranchOperationUI_DetachedHeadTitle,
                            UIText.BranchOperationUI_DetachedHeadMessage, toggleMessage, false, store,
                            UIPreferences.SHOW_DETACHED_HEAD_WARNING);
                }
            }
        });
    }

    private boolean shouldCancelBecauseOfRunningLaunches() {
        if (mode == MODE_CHECKOUT)
            return false;
        IPreferenceStore store = Activator.getDefault().getPreferenceStore();
        if (!store.getBoolean(UIPreferences.SHOW_RUNNING_LAUNCH_ON_CHECKOUT_WARNING))
            return false;

        ILaunchConfiguration launchConfiguration = getRunningLaunchConfiguration();
        if (launchConfiguration != null) {
            String[] buttons = new String[] { UIText.BranchOperationUI_Continue, IDialogConstants.CANCEL_LABEL };
            String message = NLS.bind(UIText.BranchOperationUI_RunningLaunchMessage, launchConfiguration.getName());
            MessageDialogWithToggle continueDialog = new MessageDialogWithToggle(getShell(),
                    UIText.BranchOperationUI_RunningLaunchTitle, null, message, MessageDialog.NONE, buttons, 0,
                    UIText.BranchOperationUI_RunningLaunchDontShowAgain, false);
            int result = continueDialog.open();
            // cancel
            if (result == IDialogConstants.CANCEL_ID || result == SWT.DEFAULT)
                return true;
            boolean dontWarnAgain = continueDialog.getToggleState();
            if (dontWarnAgain)
                store.setValue(UIPreferences.SHOW_RUNNING_LAUNCH_ON_CHECKOUT_WARNING, false);
        }
        return false;
    }

    private ILaunchConfiguration getRunningLaunchConfiguration() {
        Set<IProject> projects = new HashSet<IProject>(Arrays.asList(ProjectUtil.getProjects(repository)));

        ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
        ILaunch[] launches = launchManager.getLaunches();
        for (ILaunch launch : launches) {
            if (launch.isTerminated())
                continue;
            ISourceLocator locator = launch.getSourceLocator();
            if (locator instanceof ISourceLookupDirector) {
                ISourceLookupDirector director = (ISourceLookupDirector) locator;
                ISourceContainer[] containers = director.getSourceContainers();
                if (isAnyProjectInSourceContainers(containers, projects))
                    return launch.getLaunchConfiguration();
            }
        }
        return null;
    }

    private boolean isAnyProjectInSourceContainers(ISourceContainer[] containers, Set<IProject> projects) {
        for (ISourceContainer container : containers) {
            if (container instanceof ProjectSourceContainer) {
                ProjectSourceContainer projectContainer = (ProjectSourceContainer) container;
                if (projects.contains(projectContainer.getProject()))
                    return true;
            }
            try {
                boolean found = isAnyProjectInSourceContainers(container.getSourceContainers(), projects);
                if (found)
                    return true;
            } catch (CoreException e) {
                // Ignore the child source containers, continue search
            }
        }
        return false;
    }

    public interface DoneCallback {

        public void done(CheckoutResult result);

    }

}