/*******************************************************************************
* Copyright (c) 2010, 2013 SAP AG 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:
* Mathias Kinzler (SAP AG) - initial implementation
*******************************************************************************/
package org.eclipse.egit.ui.internal.rebase;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.internal.FileChecker;
import org.eclipse.egit.core.internal.FileChecker.CheckResult;
import org.eclipse.egit.core.internal.FileChecker.CheckResultEntry;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.commands.shared.AbortRebaseCommand;
import org.eclipse.egit.ui.internal.commands.shared.AbstractRebaseCommandHandler;
import org.eclipse.egit.ui.internal.commands.shared.SkipRebaseCommand;
import org.eclipse.egit.ui.internal.dialogs.CheckoutConflictDialog;
import org.eclipse.egit.ui.internal.merge.GitMergeEditorInput;
import org.eclipse.egit.ui.internal.merge.MergeModeDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jgit.api.RebaseResult;
import org.eclipse.jgit.api.RebaseResult.Status;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
/**
* Display the result of a rebase.
*/
public class RebaseResultDialog extends MessageDialog {
private static final String SPACE = " "; //$NON-NLS-1$
private static final Image INFO = PlatformUI.getWorkbench()
.getSharedImages().getImage(ISharedImages.IMG_OBJS_INFO_TSK);
private final RebaseResult result;
private final Repository repo;
private final Set<String> conflictPaths = new HashSet<String>();
private Button toggleButton;
private Button startMergeButton;
private Button skipCommitButton;
private Button abortRebaseButton;
private Button doNothingButton;
private Group nextStepsGroup;
/**
* @param result
* the result to show
* @param repository
*/
public static void show(final RebaseResult result,
final Repository repository) {
boolean shouldShow = result.getStatus() == Status.STOPPED
|| result.getStatus() == Status.STASH_APPLY_CONFLICTS
|| Activator.getDefault().getPreferenceStore().getBoolean(
UIPreferences.SHOW_REBASE_CONFIRM);
if(result.getStatus() == Status.CONFLICTS) {
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
public void run() {
Shell shell = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getShell();
new CheckoutConflictDialog(shell, repository, result.getConflicts()).open();
}
});
return;
}
if (!shouldShow) {
Activator.getDefault().getLog().log(
new org.eclipse.core.runtime.Status(IStatus.INFO, Activator
.getPluginId(), NLS.bind(
UIText.RebaseResultDialog_StatusLabel, result
.getStatus().name())));
return;
}
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
public void run() {
Shell shell = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow().getShell();
new RebaseResultDialog(shell, repository, result).open();
}
});
}
private static String getTitle(Status status) {
switch (status) {
case OK:
return UIText.RebaseResultDialog_SuccessfullyFinished;
case ABORTED:
return UIText.RebaseResultDialog_Aborted;
case STOPPED:
return UIText.RebaseResultDialog_Stopped;
case EDIT:
return UIText.RebaseResultDialog_Edit;
case FAILED:
return UIText.RebaseResultDialog_Failed;
case UP_TO_DATE:
return UIText.RebaseResultDialog_UpToDate;
case FAST_FORWARD:
return UIText.RebaseResultDialog_FastForward;
case NOTHING_TO_COMMIT:
return UIText.RebaseResultDialog_NothingToCommit;
case INTERACTIVE_PREPARED:
return UIText.RebaseResultDialog_InteractivePrepared;
case UNCOMMITTED_CHANGES:
return UIText.RebaseResultDialog_UncommittedChanges;
case STASH_APPLY_CONFLICTS:
return UIText.RebaseResultDialog_SuccessfullyFinished + ".\n" + //$NON-NLS-1$
UIText.RebaseResultDialog_stashApplyConflicts;
default:
throw new IllegalStateException(status.name());
}
}
/**
* @param status
* @return text describing rebase status in short form
*/
public static String getStatusText(Status status) {
switch (status) {
case OK:
return UIText.RebaseResultDialog_StatusOK;
case ABORTED:
return UIText.RebaseResultDialog_StatusAborted;
case STOPPED:
return UIText.RebaseResultDialog_StatusStopped;
case EDIT:
return UIText.RebaseResultDialog_StatusEdit;
case FAILED:
return UIText.RebaseResultDialog_StatusFailed;
case CONFLICTS:
return UIText.RebaseResultDialog_StatusConflicts;
case UP_TO_DATE:
return UIText.RebaseResultDialog_StatusUpToDate;
case FAST_FORWARD:
return UIText.RebaseResultDialog_StatusFastForward;
case NOTHING_TO_COMMIT:
return UIText.RebaseResultDialog_StatusNothingToCommit;
case UNCOMMITTED_CHANGES:
return UIText.RebaseResultDialog_UncommittedChanges;
case INTERACTIVE_PREPARED:
return UIText.RebaseResultDialog_StatusInteractivePrepared;
case STASH_APPLY_CONFLICTS:
return UIText.RebaseResultDialog_SuccessfullyFinished + ".\n" + //$NON-NLS-1$
UIText.RebaseResultDialog_stashApplyConflicts;
}
return status.toString();
}
/**
* @param shell
* @param repository
* @param result
*/
private RebaseResultDialog(Shell shell, Repository repository,
RebaseResult result) {
super(shell, UIText.RebaseResultDialog_DialogTitle, INFO,
getTitle(result.getStatus()),
result.getStatus() == Status.FAILED ? MessageDialog.ERROR
: MessageDialog.INFORMATION,
new String[] { IDialogConstants.OK_LABEL }, 0);
setShellStyle(getShellStyle() | SWT.SHELL_TRIM);
this.repo = repository;
this.result = result;
}
@Override
protected Control createCustomArea(Composite parent) {
if (result.getStatus() == Status.STOPPED)
return createStoppedDialogArea(parent);
if (result.getStatus() == Status.FAILED
|| result.getStatus() == Status.CONFLICTS)
return createFailedOrConflictDialog(parent);
createToggleButton(parent);
return null;
}
private Control createFailedOrConflictDialog(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
createFailedOrConflictsParts(composite, result);
return composite;
}
/**
* Create the items in composite necessary for a rebase result
*
* @param composite
* @param result
*/
public static void createFailedOrConflictsParts(Composite composite,
RebaseResult result) {
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 2;
composite.setLayout(gridLayout);
// result
Label resultLabel = new Label(composite, SWT.NONE);
resultLabel.setText(UIText.MergeResultDialog_result);
resultLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false,
false));
Text resultText = new Text(composite, SWT.READ_ONLY);
resultText.setText(getStatusText(result.getStatus()));
if (!result.getStatus().isSuccessful())
resultText.setForeground(composite.getParent().getDisplay()
.getSystemColor(SWT.COLOR_RED));
resultText.setSelection(resultText.getCaretPosition());
resultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
if (result.getStatus() == Status.FAILED) {
StringBuilder paths = new StringBuilder();
Label pathsLabel = new Label(composite, SWT.NONE);
pathsLabel.setText(UIText.MergeResultDialog_failed);
pathsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false,
false));
Text pathsText = new Text(composite, SWT.READ_ONLY);
pathsText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false,
false));
Set<Entry<String, MergeFailureReason>> failedPaths = result
.getFailingPaths().entrySet();
int n = 0;
for (Map.Entry<String, MergeFailureReason> e : failedPaths) {
if (n > 0)
paths.append(Text.DELIMITER);
paths.append(e.getValue());
paths.append("\t"); //$NON-NLS-1$
paths.append(e.getKey());
n++;
if (n > 10 && failedPaths.size() > 15)
break;
}
if (n < failedPaths.size()) {
paths.append(Text.DELIMITER);
paths.append(MessageFormat.format(
UIText.MergeResultDialog_nMore,
Integer.valueOf(n - failedPaths.size())));
}
pathsText.setText(paths.toString());
} else if (result.getStatus() == Status.CONFLICTS) {
StringBuilder paths = new StringBuilder();
Label pathsLabel = new Label(composite, SWT.NONE);
pathsLabel.setText(UIText.MergeResultDialog_conflicts);
pathsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false,
false));
Text pathsText = new Text(composite, SWT.READ_ONLY);
pathsText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false,
false));
List<String> conflList = result.getConflicts();
int n = 0;
for (String e : conflList) {
if (n > 0)
paths.append(Text.DELIMITER);
paths.append(e);
n++;
if (n > 10 && conflList.size() > 15)
break;
}
if (n < conflList.size()) {
paths.append(Text.DELIMITER);
paths.append(MessageFormat.format(
UIText.MergeResultDialog_nMore,
Integer.valueOf(n - conflList.size())));
}
pathsText.setText(paths.toString());
}
}
private Control createStoppedDialogArea(Composite parent) {
Composite main = new Composite(parent, SWT.NONE);
main.setLayout(new GridLayout(1, false));
GridDataFactory.fillDefaults().indent(0, 0).grab(true, true).applyTo(
main);
Group commitGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
GridDataFactory.fillDefaults().grab(true, true).applyTo(commitGroup);
commitGroup.setText(UIText.RebaseResultDialog_DetailsGroup);
commitGroup.setLayout(new GridLayout(1, false));
Label commitIdLabel = new Label(commitGroup, SWT.NONE);
commitIdLabel.setText(UIText.RebaseResultDialog_CommitIdLabel);
Text commitId = new Text(commitGroup, SWT.READ_ONLY | SWT.BORDER);
GridDataFactory.fillDefaults().grab(true, false).applyTo(commitId);
Label commitMessageLabel = new Label(commitGroup, SWT.NONE);
commitMessageLabel
.setText(UIText.RebaseResultDialog_CommitMessageLabel);
TextViewer commitMessage = new TextViewer(commitGroup, SWT.H_SCROLL
| SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.READ_ONLY);
GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 60)
.applyTo(commitMessage.getControl());
boolean conflictListFailure = false;
DirCache dc = null;
RevWalk rw = null;
try {
rw = new RevWalk(repo);
// the commits might not have been fully loaded
RevCommit commit = rw.parseCommit(result.getCurrentCommit());
commitMessage.getTextWidget().setText(commit.getFullMessage());
commitId.setText(commit.name());
dc = repo.lockDirCache();
for (int i = 0; i < dc.getEntryCount(); i++)
if (dc.getEntry(i).getStage() > 0)
conflictPaths.add(dc.getEntry(i).getPathString());
if (conflictPaths.size() > 0) {
message = NLS.bind(UIText.RebaseResultDialog_Conflicting,
Integer.valueOf(conflictPaths.size()));
messageLabel.setText(message);
}
} catch (IOException e) {
// the file list will be empty
conflictListFailure = true;
} finally {
if (rw != null)
rw.release();
if (dc != null)
dc.unlock();
}
boolean mergeToolAvailable = true;
final CheckResult checkResult;
if (!conflictListFailure) {
checkResult = FileChecker.checkFiles(repo, conflictPaths);
mergeToolAvailable = checkResult.isOk();
}
else {
checkResult = null;
mergeToolAvailable = false;
}
if (conflictListFailure) {
Label failureLabel = new Label(main, SWT.NONE);
failureLabel
.setText(UIText.RebaseResultDialog_ConflictListFailureMessage);
} else {
if (checkResult != null && !checkResult.isOk()) {
Label failureLabel = new Label(main, SWT.NONE);
failureLabel
.setText(getProblemDescription(checkResult));
}
Label conflictListLabel = new Label(main, SWT.NONE);
conflictListLabel
.setText(UIText.RebaseResultDialog_DiffDetailsLabel);
TableViewer conflictList = new TableViewer(main, SWT.BORDER);
GridDataFactory.fillDefaults().span(2, 1).grab(true, true).applyTo(
conflictList.getTable());
conflictList.setContentProvider(ArrayContentProvider.getInstance());
conflictList.setInput(conflictPaths);
conflictList.setLabelProvider(new LabelProvider() {
@Override
public String getText(Object element) {
String path = (String) element;
if (checkResult != null && !checkResult.isOk()) {
CheckResultEntry entry = checkResult.getEntry(path);
if (entry != null) {
if (!entry.inWorkspace)
return UIText.RebaseResultDialog_notInWorkspace + SPACE + path;
if (!entry.shared)
return UIText.RebaseResultDialog_notShared + SPACE + path;
}
}
return super.getText(element);
}
});
}
Group actionGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
GridDataFactory.fillDefaults().grab(true, false).applyTo(actionGroup);
actionGroup.setText(UIText.RebaseResultDialog_ActionGroupTitle);
actionGroup.setLayout(new GridLayout(1, false));
nextStepsGroup = new Group(main, SWT.SHADOW_ETCHED_IN);
GridDataFactory.fillDefaults().grab(true, true).applyTo(nextStepsGroup);
nextStepsGroup.setText(UIText.RebaseResultDialog_NextSteps);
nextStepsGroup.setLayout(new GridLayout(1, false));
final TextViewer nextSteps = new TextViewer(nextStepsGroup, SWT.MULTI
| SWT.BORDER | SWT.READ_ONLY);
GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 60)
.applyTo(nextSteps.getControl());
nextSteps.getTextWidget().setText(
UIText.RebaseResultDialog_NextStepsAfterResolveConflicts);
startMergeButton = new Button(actionGroup, SWT.RADIO);
startMergeButton.setText(UIText.RebaseResultDialog_StartMergeRadioText);
startMergeButton.setEnabled(mergeToolAvailable);
startMergeButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (startMergeButton.getSelection())
nextSteps
.getTextWidget()
.setText(
UIText.RebaseResultDialog_NextStepsAfterResolveConflicts);
}
public void widgetDefaultSelected(SelectionEvent e) {
// nothing
}
});
skipCommitButton = new Button(actionGroup, SWT.RADIO);
skipCommitButton.setText(UIText.RebaseResultDialog_SkipCommitButton);
skipCommitButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (skipCommitButton.getSelection())
nextSteps.getTextWidget().setText(""); //$NON-NLS-1$
}
public void widgetDefaultSelected(SelectionEvent e) {
// nothing
}
});
abortRebaseButton = new Button(actionGroup, SWT.RADIO);
abortRebaseButton
.setText(UIText.RebaseResultDialog_AbortRebaseRadioText);
abortRebaseButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (abortRebaseButton.getSelection())
nextSteps.getTextWidget().setText(""); //$NON-NLS-1$
}
public void widgetDefaultSelected(SelectionEvent e) {
// nothing
}
});
doNothingButton = new Button(actionGroup, SWT.RADIO);
doNothingButton.setText(UIText.RebaseResultDialog_DoNothingRadioText);
doNothingButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (doNothingButton.getSelection())
nextSteps.getTextWidget().setText(
UIText.RebaseResultDialog_NextStepsDoNothing);
}
public void widgetDefaultSelected(SelectionEvent e) {
// nothing
}
});
if (mergeToolAvailable)
startMergeButton.setSelection(true);
else
doNothingButton.setSelection(true);
commitGroup.pack();
applyDialogFont(main);
return main;
}
private static String getProblemDescription(CheckResult checkResult) {
StringBuffer result = new StringBuffer();
if (checkResult.containsNonWorkspaceFiles())
result.append(UIText.RebaseResultDialog_notInWorkspaceMessage);
if (checkResult.containsNotSharedResources()) {
if (result.length() > 0)
result.append('\n');
result.append(UIText.RebaseResultDialog_notSharedMessage);
}
return result.toString();
}
@Override
protected void buttonPressed(int buttonId) {
// store the preference to hide these dialogs
if (toggleButton != null)
Activator.getDefault().getPreferenceStore().setValue(
UIPreferences.SHOW_REBASE_CONFIRM,
!toggleButton.getSelection());
if (buttonId == IDialogConstants.OK_ID) {
if (result.getStatus() != Status.STOPPED) {
super.buttonPressed(buttonId);
return;
}
if (startMergeButton.getSelection()) {
super.buttonPressed(buttonId);
// open the merge tool
IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()
.getProjects();
for (IProject project : projects) {
RepositoryMapping mapping = RepositoryMapping
.getMapping(project);
if (mapping != null && mapping.getRepository().equals(repo)) {
try {
// make sure to refresh before opening the merge
// tool
project
.refreshLocal(IResource.DEPTH_INFINITE,
null);
} catch (CoreException e) {
Activator.handleError(e.getMessage(), e, false);
}
}
}
List<IPath> locationList = new ArrayList<IPath>();
IPath repoWorkdirPath = new Path(repo.getWorkTree().getPath());
for (String repoPath : conflictPaths) {
IPath location = repoWorkdirPath.append(repoPath);
locationList.add(location);
}
IPath[] locations = locationList.toArray(new IPath[locationList
.size()]);
int mergeMode = Activator.getDefault().getPreferenceStore()
.getInt(UIPreferences.MERGE_MODE);
CompareEditorInput input;
if (mergeMode == 0) {
MergeModeDialog dlg = new MergeModeDialog(getParentShell());
if (dlg.open() != Window.OK)
return;
input = new GitMergeEditorInput(dlg.useWorkspace(),
locations);
} else {
boolean useWorkspace = mergeMode == 1;
input = new GitMergeEditorInput(useWorkspace,
locations);
}
CompareUI.openCompareEditor(input);
return;
} else if (skipCommitButton.getSelection()) {
// skip the rebase
SkipRebaseCommand skipCommand = new SkipRebaseCommand();
execute(skipCommand);
} else if (abortRebaseButton.getSelection()) {
// abort the rebase
AbortRebaseCommand abortCommand = new AbortRebaseCommand();
execute(abortCommand);
} else if (doNothingButton.getSelection()) {
// nothing
}
}
super.buttonPressed(buttonId);
}
private void execute(AbstractRebaseCommandHandler command) {
try {
command.execute(repo);
} catch (ExecutionException e) {
Activator.showError(e.getMessage(), e);
}
}
private void createToggleButton(Composite parent) {
boolean toggleState = !Activator.getDefault().getPreferenceStore()
.getBoolean(UIPreferences.SHOW_REBASE_CONFIRM);
toggleButton = new Button(parent, SWT.CHECK | SWT.LEFT);
toggleButton.setText(UIText.RebaseResultDialog_ToggleShowButton);
toggleButton.setSelection(toggleState);
}
}