/*******************************************************************************
* Copyright (C) 2011, 2016 Bernard Leach <leachbj@bouncycastle.org> and others.
* Copyright (C) 2015 SAP SE (Christian Georgi <christian.georgi@sap.com>)
* Copyright (C) 2015 Denis Zygann <d.zygann@web.de>
* Copyright (C) 2016 IBM (Daniel Megert <daniel_megert@ch.ibm.com>)
*
* 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:
* Tobias Baumann <tobbaumann@gmail.com> - Bug 373969, 473544
* Thomas Wolf <thomas.wolf@paranor.ch>
* Tobias Hein <th.mailinglists@googlemail.com> - Bug 499697
* Ralf M Petter <ralf.petter@gmail.com> - Bug 509945
*******************************************************************************/
package org.eclipse.egit.ui.internal.staging;
import static org.eclipse.egit.ui.internal.CommonUtils.runCommand;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
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.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
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.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.egit.core.AdapterUtils;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.gerrit.GerritUtil;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffChangedListener;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffData;
import org.eclipse.egit.core.internal.job.RuleUtil;
import org.eclipse.egit.core.op.CommitOperation;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.GitLabels;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.actions.ActionCommands;
import org.eclipse.egit.ui.internal.actions.BooleanPrefAction;
import org.eclipse.egit.ui.internal.actions.ReplaceWithOursTheirsMenu;
import org.eclipse.egit.ui.internal.branch.LaunchFinder;
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.ContinueRebaseCommand;
import org.eclipse.egit.ui.internal.commands.shared.SkipRebaseCommand;
import org.eclipse.egit.ui.internal.commit.CommitHelper;
import org.eclipse.egit.ui.internal.commit.CommitJob;
import org.eclipse.egit.ui.internal.commit.CommitMessageHistory;
import org.eclipse.egit.ui.internal.commit.CommitProposalProcessor;
import org.eclipse.egit.ui.internal.commit.DiffViewer;
import org.eclipse.egit.ui.internal.components.ToggleableWarningLabel;
import org.eclipse.egit.ui.internal.decorators.IProblemDecoratable;
import org.eclipse.egit.ui.internal.decorators.ProblemLabelDecorator;
import org.eclipse.egit.ui.internal.dialogs.CommitMessageArea;
import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponent;
import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentState;
import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentStateManager;
import org.eclipse.egit.ui.internal.dialogs.ICommitMessageComponentNotifications;
import org.eclipse.egit.ui.internal.dialogs.SpellcheckableMessageArea;
import org.eclipse.egit.ui.internal.operations.DeletePathsOperationUI;
import org.eclipse.egit.ui.internal.operations.IgnoreOperationUI;
import org.eclipse.egit.ui.internal.push.PushMode;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.DialogSettings;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.BaseLabelProvider;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.RmCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.operations.UndoRedoActionGroup;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
/**
* A GitX style staging view with embedded commit dialog.
*/
public class StagingView extends ViewPart
implements IShowInSource, IShowInTarget {
/**
* Staging view id
*/
public static final String VIEW_ID = "org.eclipse.egit.ui.StagingView"; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final String SORT_ITEM_TOOLBAR_ID = "sortItem"; //$NON-NLS-1$
private static final String EXPAND_ALL_ITEM_TOOLBAR_ID = "expandAllItem"; //$NON-NLS-1$
private static final String COLLAPSE_ALL_ITEM_TOOLBAR_ID = "collapseAllItem"; //$NON-NLS-1$
private static final String STORE_SORT_STATE = SORT_ITEM_TOOLBAR_ID
+ "State"; //$NON-NLS-1$
private static final String HORIZONTAL_SASH_FORM_WEIGHT = "HORIZONTAL_SASH_FORM_WEIGHT"; //$NON-NLS-1$
private static final String STAGING_SASH_FORM_WEIGHT = "STAGING_SASH_FORM_WEIGHT"; //$NON-NLS-1$
private ISelection initialSelection;
private FormToolkit toolkit;
private Form form;
private SashForm mainSashForm;
private Section stagedSection;
private Section unstagedSection;
private Section commitMessageSection;
private TreeViewer stagedViewer;
private TreeViewer unstagedViewer;
private ToggleableWarningLabel warningLabel;
private Text filterText;
private SpellcheckableMessageArea commitMessageText;
private Text committerText;
private Text authorText;
private CommitMessageComponent commitMessageComponent;
private boolean reactOnSelection = true;
private boolean isViewHidden;
/** Tracks the last selection while the view is not active. */
private StructuredSelection lastSelection;
private ISelectionListener selectionChangedListener;
private IPartListener2 partListener;
private ToolBarManager unstagedToolBarManager;
private ToolBarManager stagedToolBarManager;
private Action listPresentationAction;
private Action treePresentationAction;
private Action compactTreePresentationAction;
private Action unstagedExpandAllAction;
private Action unstagedCollapseAllAction;
private Action stagedExpandAllAction;
private Action stagedCollapseAllAction;
private Action compareModeAction;
@Nullable
private Repository currentRepository;
private Presentation presentation = Presentation.LIST;
private Set<IPath> pathsToExpandInStaged = new HashSet<>();
private Set<IPath> pathsToExpandInUnstaged = new HashSet<>();
/**
* Presentation mode of the staged/unstaged files.
*/
public enum Presentation {
/** Show files in flat list */
LIST,
/** Show folder structure in full tree */
TREE,
/**
* Show folder structure in compact tree (folders with only one child
* are folded into parent)
*/
COMPACT_TREE;
}
static class StagingViewUpdate {
Repository repository;
IndexDiffData indexDiff;
Collection<String> changedResources;
StagingViewUpdate(Repository theRepository,
IndexDiffData theIndexDiff, Collection<String> theChanges) {
this.repository = theRepository;
this.indexDiff = theIndexDiff;
this.changedResources = theChanges;
}
}
private static class StagingDragSelection implements IStructuredSelection {
private final IStructuredSelection delegate;
private final boolean fromUnstaged;
public StagingDragSelection(IStructuredSelection original,
boolean fromUnstaged) {
this.delegate = original;
this.fromUnstaged = fromUnstaged;
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public Object getFirstElement() {
return delegate.getFirstElement();
}
@Override
public Iterator iterator() {
return delegate.iterator();
}
@Override
public int size() {
return delegate.size();
}
@Override
public Object[] toArray() {
return delegate.toArray();
}
@Override
public List toList() {
return delegate.toList();
}
public boolean isFromUnstaged() {
return fromUnstaged;
}
}
private static class StagingDragListener extends DragSourceAdapter {
private final ISelectionProvider provider;
private final StagingViewContentProvider contentProvider;
private final boolean unstaged;
public StagingDragListener(ISelectionProvider provider,
StagingViewContentProvider contentProvider, boolean unstaged) {
this.provider = provider;
this.contentProvider = contentProvider;
this.unstaged = unstaged;
}
@Override
public void dragStart(DragSourceEvent event) {
event.doit = !provider.getSelection().isEmpty();
}
@Override
public void dragFinished(DragSourceEvent event) {
if (LocalSelectionTransfer.getTransfer().isSupportedType(
event.dataType)) {
LocalSelectionTransfer.getTransfer().setSelection(null);
}
}
@Override
public void dragSetData(DragSourceEvent event) {
IStructuredSelection selection = (IStructuredSelection) provider
.getSelection();
if (selection.isEmpty()) {
// Should never happen as per dragStart()
return;
}
if (LocalSelectionTransfer.getTransfer().isSupportedType(
event.dataType)) {
LocalSelectionTransfer.getTransfer().setSelection(
new StagingDragSelection(selection, unstaged));
return;
}
if (FileTransfer.getInstance().isSupportedType(event.dataType)) {
Set<String> files = new HashSet<>();
for (Object selected : selection.toList())
if (selected instanceof StagingEntry) {
add((StagingEntry) selected, files);
} else if (selected instanceof StagingFolderEntry) {
// Only add the files, otherwise much more than intended
// might be copied or moved. The user selected a staged
// or unstaged folder, so only the staged or unstaged
// files inside that folder should be included, not
// everything.
StagingFolderEntry folder = (StagingFolderEntry) selected;
for (StagingEntry entry : contentProvider
.getStagingEntriesFiltered(folder)) {
add(entry, files);
}
}
if (!files.isEmpty()) {
event.data = files.toArray(new String[files.size()]);
return;
}
// We may still end up with an empty list here if the selection
// contained only deleted files. In that case, the drag&drop
// will log an SWTException: Data does not have correct format
// for type. Note that GTK sometimes creates the FileTransfer
// up front even though a drag between our own viewers would
// need only the LocalSelectionTransfer. Drag&drop between our
// viewers still works (also on GTK) even if the creation of
// the FileTransfer fails.
}
}
private void add(StagingEntry entry, Collection<String> files) {
File file = entry.getLocation().toFile();
if (file.exists()) {
files.add(file.getAbsolutePath());
}
}
}
private final class PartListener implements IPartListener2 {
@Override
public void partVisible(IWorkbenchPartReference partRef) {
updateHiddenState(partRef, false);
}
@Override
public void partOpened(IWorkbenchPartReference partRef) {
updateHiddenState(partRef, false);
}
@Override
public void partHidden(IWorkbenchPartReference partRef) {
updateHiddenState(partRef, true);
}
@Override
public void partClosed(IWorkbenchPartReference partRef) {
updateHiddenState(partRef, true);
}
@Override
public void partActivated(IWorkbenchPartReference partRef) {
if (isMe(partRef)) {
if (lastSelection != null) {
// view activated: synchronize with last active part
// selection
reactOnSelection(lastSelection);
lastSelection = null;
}
return;
}
IWorkbenchPart part = partRef.getPart(false);
StructuredSelection sel = getSelectionOfPart(part);
if (isViewHidden) {
// remember last selection in the part so that we can
// synchronize on it as soon as we will be visible
lastSelection = sel;
} else {
lastSelection = null;
if (sel != null) {
reactOnSelection(sel);
}
}
}
private void updateHiddenState(IWorkbenchPartReference partRef,
boolean hidden) {
if (isMe(partRef)) {
isViewHidden = hidden;
}
}
private boolean isMe(IWorkbenchPartReference partRef) {
return partRef.getPart(false) == StagingView.this;
}
@Override
public void partDeactivated(IWorkbenchPartReference partRef) {
//
}
@Override
public void partBroughtToTop(IWorkbenchPartReference partRef) {
//
}
@Override
public void partInputChanged(IWorkbenchPartReference partRef) {
//
}
}
/**
* A wrapped {@link DecoratingLabelProvider} to be used in the tree viewers
* of the staging view. We wrap it instead of deriving directly because a
* {@link DecoratingLabelProvider} is a
* {@link org.eclipse.jface.viewers.ITreePathLabelProvider
* ITreePathLabelProvider}, which makes the tree viewer compute a
* {@link org.eclipse.jface.viewers.TreePath TreePath} for each element,
* which is then ultimately unused because the
* {@link StagingViewLabelProvider} is <em>not</em> a
* {@link org.eclipse.jface.viewers.ITreePathLabelProvider
* ITreePathLabelProvider}. Computing the
* {@link org.eclipse.jface.viewers.TreePath TreePath} is a fairly expensive
* operation on GTK, and avoiding to compute it speeds up label updates
* significantly.
*/
private static class TreeDecoratingLabelProvider extends BaseLabelProvider
implements ILabelProvider {
private final DecoratingLabelProvider provider;
public TreeDecoratingLabelProvider(ILabelProvider provider,
ILabelDecorator decorator) {
this.provider = new DecoratingLabelProvider(provider, decorator);
}
@Override
public Image getImage(Object element) {
return provider.getImage(element);
}
@Override
public String getText(Object element) {
return provider.getText(element);
}
@Override
public void addListener(ILabelProviderListener listener) {
provider.addListener(listener);
}
@Override
public void removeListener(ILabelProviderListener listener) {
provider.removeListener(listener);
}
@Override
public void dispose() {
provider.dispose();
}
public ILabelProvider getLabelProvider() {
return provider.getLabelProvider();
}
}
static class StagingViewSearchThread extends Thread {
private StagingView stagingView;
private static final Object lock = new Object();
private volatile static int globalThreadIndex = 0;
private int currentThreadIx;
public StagingViewSearchThread(StagingView stagingView) {
super("staging_view_filter_thread" + ++globalThreadIndex); //$NON-NLS-1$
this.stagingView = stagingView;
currentThreadIx = globalThreadIndex;
}
@Override
public void run() {
synchronized (lock) {
if (currentThreadIx < globalThreadIndex)
return;
stagingView.refreshViewersPreservingExpandedElements();
}
}
}
private final IPreferenceChangeListener prefListener = new IPreferenceChangeListener() {
@Override
public void preferenceChange(PreferenceChangeEvent event) {
if (!RepositoryUtil.PREFS_DIRECTORIES_REL.equals(event.getKey())) {
return;
}
final Repository repo = currentRepository;
if (repo == null)
return;
if (Activator.getDefault().getRepositoryUtil().contains(repo))
return;
reload(null);
}
};
private final IPropertyChangeListener uiPrefsListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (UIPreferences.COMMIT_DIALOG_WARN_ABOUT_MESSAGE_SECOND_LINE
.equals(event.getProperty())) {
asyncExec(new Runnable() {
@Override
public void run() {
if (!commitMessageSection.isDisposed()) {
updateMessage();
}
}
});
}
}
};
private Action signedOffByAction;
private Action addChangeIdAction;
private Action amendPreviousCommitAction;
private Action openNewCommitsAction;
private Action columnLayoutAction;
private Action fileNameModeAction;
private Action refreshAction;
private Action sortAction;
private SashForm stagingSashForm;
private IndexDiffChangedListener myIndexDiffListener = new IndexDiffChangedListener() {
@Override
public void indexDiffChanged(Repository repository,
IndexDiffData indexDiffData) {
reload(repository);
}
};
private IndexDiffCacheEntry cacheEntry;
private UndoRedoActionGroup undoRedoActionGroup;
private Button commitButton;
private Button commitAndPushButton;
private Section rebaseSection;
private Button rebaseContinueButton;
private Button rebaseSkipButton;
private Button rebaseAbortButton;
private Button ignoreErrors;
private ListenerHandle refsChangedListener;
private LocalResourceManager resources = new LocalResourceManager(
JFaceResources.getResources());
private boolean disposed;
private Image getImage(ImageDescriptor descriptor) {
return (Image) this.resources.get(descriptor);
}
@Override
public void init(IViewSite site, IMemento viewMemento)
throws PartInitException {
super.init(site, viewMemento);
this.initialSelection = site.getWorkbenchWindow().getSelectionService()
.getSelection();
}
@Override
public void createPartControl(final Composite parent) {
GridLayoutFactory.fillDefaults().applyTo(parent);
toolkit = new FormToolkit(parent.getDisplay());
parent.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (commitMessageComponent.isAmending()
|| userEnteredCommitMessage())
saveCommitMessageComponentState();
else
deleteCommitMessageComponentState();
resources.dispose();
toolkit.dispose();
}
});
form = toolkit.createForm(parent);
parent.addControlListener(new ControlListener() {
private int[] defaultWeights = { 1, 1 };
@Override
public void controlResized(ControlEvent e) {
org.eclipse.swt.graphics.Rectangle b = parent.getBounds();
int oldOrientation = mainSashForm.getOrientation();
if ((oldOrientation == SWT.HORIZONTAL)
&& (b.height > b.width)) {
mainSashForm.setOrientation(SWT.VERTICAL);
mainSashForm.setWeights(defaultWeights);
} else if ((oldOrientation == SWT.VERTICAL)
&& (b.height <= b.width)) {
mainSashForm.setOrientation(SWT.HORIZONTAL);
mainSashForm.setWeights(defaultWeights);
}
}
@Override
public void controlMoved(ControlEvent e) {
// ignore
}
});
form.setImage(getImage(UIIcons.REPOSITORY));
form.setText(UIText.StagingView_NoSelectionTitle);
GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
toolkit.decorateFormHeading(form);
GridLayoutFactory.swtDefaults().applyTo(form.getBody());
mainSashForm = new SashForm(form.getBody(), SWT.HORIZONTAL);
saveSashFormWeightsOnDisposal(mainSashForm,
HORIZONTAL_SASH_FORM_WEIGHT);
toolkit.adapt(mainSashForm, true, true);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(mainSashForm);
stagingSashForm = new SashForm(mainSashForm,
getStagingFormOrientation());
saveSashFormWeightsOnDisposal(stagingSashForm,
STAGING_SASH_FORM_WEIGHT);
toolkit.adapt(stagingSashForm, true, true);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(stagingSashForm);
unstagedSection = toolkit.createSection(stagingSashForm,
ExpandableComposite.TITLE_BAR);
unstagedSection.setLayoutData(
GridDataFactory.fillDefaults().grab(true, true).create());
createUnstagedToolBarComposite();
Composite unstagedComposite = toolkit.createComposite(unstagedSection);
toolkit.paintBordersFor(unstagedComposite);
unstagedSection.setClient(unstagedComposite);
GridLayoutFactory.fillDefaults().extendedMargins(2, 2, 2, 2)
.applyTo(unstagedComposite);
unstagedViewer = createViewer(unstagedComposite, true,
selection -> unstage(selection));
Composite rebaseAndCommitComposite = toolkit.createComposite(mainSashForm);
rebaseAndCommitComposite.setLayout(GridLayoutFactory.fillDefaults().create());
rebaseSection = toolkit.createSection(rebaseAndCommitComposite,
ExpandableComposite.TITLE_BAR);
rebaseSection.setText(UIText.StagingView_RebaseLabel);
Composite rebaseComposite = toolkit.createComposite(rebaseSection);
toolkit.paintBordersFor(rebaseComposite);
rebaseSection.setClient(rebaseComposite);
rebaseSection.setLayoutData(GridDataFactory.fillDefaults().create());
rebaseComposite.setLayout(GridLayoutFactory.fillDefaults()
.numColumns(3).equalWidth(true).create());
GridDataFactory buttonGridData = GridDataFactory.fillDefaults().align(
SWT.FILL, SWT.CENTER);
this.rebaseAbortButton = toolkit.createButton(rebaseComposite,
UIText.StagingView_RebaseAbort, SWT.PUSH);
rebaseAbortButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
rebaseAbort();
}
});
rebaseAbortButton.setImage(getImage(UIIcons.REBASE_ABORT));
buttonGridData.applyTo(rebaseAbortButton);
this.rebaseSkipButton = toolkit.createButton(rebaseComposite,
UIText.StagingView_RebaseSkip, SWT.PUSH);
rebaseSkipButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
rebaseSkip();
}
});
rebaseSkipButton.setImage(getImage(UIIcons.REBASE_SKIP));
buttonGridData.applyTo(rebaseSkipButton);
this.rebaseContinueButton = toolkit.createButton(rebaseComposite,
UIText.StagingView_RebaseContinue, SWT.PUSH);
rebaseContinueButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
rebaseContinue();
}
});
rebaseContinueButton.setImage(getImage(UIIcons.REBASE_CONTINUE));
buttonGridData.applyTo(rebaseContinueButton);
showControl(rebaseSection, false);
commitMessageSection = toolkit.createSection(rebaseAndCommitComposite,
ExpandableComposite.TITLE_BAR);
commitMessageSection.setText(UIText.StagingView_CommitMessage);
commitMessageSection.setLayoutData(GridDataFactory.fillDefaults()
.grab(true, true).create());
Composite commitMessageToolbarComposite = toolkit
.createComposite(commitMessageSection);
commitMessageToolbarComposite.setBackground(null);
commitMessageToolbarComposite.setLayout(createRowLayoutWithoutMargin());
commitMessageSection.setTextClient(commitMessageToolbarComposite);
ToolBarManager commitMessageToolBarManager = new ToolBarManager(
SWT.FLAT | SWT.HORIZONTAL);
amendPreviousCommitAction = new Action(
UIText.StagingView_Ammend_Previous_Commit, IAction.AS_CHECK_BOX) {
@Override
public void run() {
commitMessageComponent.setAmendingButtonSelection(isChecked());
updateMessage();
}
};
amendPreviousCommitAction.setImageDescriptor(UIIcons.AMEND_COMMIT);
commitMessageToolBarManager.add(amendPreviousCommitAction);
signedOffByAction = new Action(UIText.StagingView_Add_Signed_Off_By,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
commitMessageComponent.setSignedOffButtonSelection(isChecked());
}
};
signedOffByAction.setImageDescriptor(UIIcons.SIGNED_OFF);
commitMessageToolBarManager.add(signedOffByAction);
addChangeIdAction = new Action(UIText.StagingView_Add_Change_ID,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
commitMessageComponent.setChangeIdButtonSelection(isChecked());
}
};
addChangeIdAction.setImageDescriptor(UIIcons.GERRIT);
commitMessageToolBarManager.add(addChangeIdAction);
commitMessageToolBarManager
.createControl(commitMessageToolbarComposite);
Composite commitMessageComposite = toolkit
.createComposite(commitMessageSection);
commitMessageSection.setClient(commitMessageComposite);
GridLayoutFactory.fillDefaults().numColumns(1)
.applyTo(commitMessageComposite);
warningLabel = new ToggleableWarningLabel(commitMessageComposite,
SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false).exclude(true)
.applyTo(warningLabel);
Composite commitMessageTextComposite = toolkit
.createComposite(commitMessageComposite);
toolkit.paintBordersFor(commitMessageTextComposite);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(commitMessageTextComposite);
GridLayoutFactory.fillDefaults().numColumns(1)
.extendedMargins(2, 2, 2, 2)
.applyTo(commitMessageTextComposite);
final CommitProposalProcessor commitProposalProcessor = new CommitProposalProcessor() {
@Override
protected Collection<String> computeFileNameProposals() {
return getStagedFileNames();
}
@Override
protected Collection<String> computeMessageProposals() {
return CommitMessageHistory.getCommitHistory();
}
};
commitMessageText = new CommitMessageArea(commitMessageTextComposite,
EMPTY_STRING, SWT.NONE) {
@Override
protected CommitProposalProcessor getCommitProposalProcessor() {
return commitProposalProcessor;
}
@Override
protected IHandlerService getHandlerService() {
return CommonUtils.getService(getSite(), IHandlerService.class);
}
};
commitMessageText.setData(FormToolkit.KEY_DRAW_BORDER,
FormToolkit.TEXT_BORDER);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(commitMessageText);
UIUtils.addBulbDecorator(commitMessageText.getTextWidget(),
UIText.CommitDialog_ContentAssist);
Composite composite = toolkit.createComposite(commitMessageComposite);
toolkit.paintBordersFor(composite);
GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
GridLayoutFactory.swtDefaults().numColumns(2).applyTo(composite);
toolkit.createLabel(composite, UIText.StagingView_Author)
.setForeground(
toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
authorText = toolkit.createText(composite, null);
authorText
.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
authorText.setLayoutData(GridDataFactory.fillDefaults()
.grab(true, false).create());
toolkit.createLabel(composite, UIText.StagingView_Committer)
.setForeground(
toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
committerText = toolkit.createText(composite, null);
committerText.setData(FormToolkit.KEY_DRAW_BORDER,
FormToolkit.TEXT_BORDER);
committerText.setLayoutData(GridDataFactory.fillDefaults()
.grab(true, false).create());
Composite buttonsContainer = toolkit.createComposite(composite);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
.indent(0, 8).applyTo(buttonsContainer);
GridLayoutFactory.fillDefaults().numColumns(2)
.applyTo(buttonsContainer);
ignoreErrors = toolkit.createButton(buttonsContainer,
UIText.StagingView_IgnoreErrors, SWT.CHECK);
ignoreErrors.setSelection(false);
ignoreErrors.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateMessage();
updateCommitButtons();
}
});
getPreferenceStore()
.addPropertyChangeListener(new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (isDisposed()) {
getPreferenceStore()
.removePropertyChangeListener(this);
return;
}
asyncExec(new Runnable() {
@Override
public void run() {
updateIgnoreErrorsButtonVisibility();
updateMessage();
updateCommitButtons();
}
});
}
});
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING)
.grab(true, true).applyTo(ignoreErrors);
updateIgnoreErrorsButtonVisibility();
Label filler = toolkit.createLabel(buttonsContainer, ""); //$NON-NLS-1$
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
.grab(true, true).applyTo(filler);
Composite commitButtonsContainer = toolkit
.createComposite(buttonsContainer);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.applyTo(commitButtonsContainer);
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true)
.applyTo(commitButtonsContainer);
this.commitAndPushButton = toolkit.createButton(commitButtonsContainer,
UIText.StagingView_CommitAndPush, SWT.PUSH);
commitAndPushButton.setImage(getImage(UIIcons.PUSH));
commitAndPushButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
commit(true);
}
});
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.applyTo(commitAndPushButton);
this.commitButton = toolkit.createButton(commitButtonsContainer,
UIText.StagingView_Commit, SWT.PUSH);
commitButton.setImage(getImage(UIIcons.COMMIT));
commitButton.setText(UIText.StagingView_Commit);
commitButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
commit(false);
}
});
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
.applyTo(commitButton);
stagedSection = toolkit.createSection(stagingSashForm,
ExpandableComposite.TITLE_BAR);
createStagedToolBarComposite();
Composite stagedComposite = toolkit.createComposite(stagedSection);
toolkit.paintBordersFor(stagedComposite);
stagedSection.setClient(stagedComposite);
GridLayoutFactory.fillDefaults().extendedMargins(2, 2, 2, 2)
.applyTo(stagedComposite);
stagedViewer = createViewer(stagedComposite, false,
selection -> stage(selection));
stagedViewer.getLabelProvider().addListener(event -> {
updateMessage();
updateCommitButtons();
});
selectionChangedListener = new ISelectionListener() {
@Override
public void selectionChanged(IWorkbenchPart part,
ISelection selection) {
if (part == getSite().getPart()) {
return;
}
// don't accept text selection, only structural one
if (selection instanceof StructuredSelection) {
reactOnSelection((StructuredSelection) selection);
}
}
};
partListener = new PartListener();
IPreferenceStore preferenceStore = getPreferenceStore();
if (preferenceStore.contains(UIPreferences.STAGING_VIEW_SYNC_SELECTION))
reactOnSelection = preferenceStore.getBoolean(
UIPreferences.STAGING_VIEW_SYNC_SELECTION);
else
preferenceStore.setDefault(UIPreferences.STAGING_VIEW_SYNC_SELECTION, true);
preferenceStore.addPropertyChangeListener(uiPrefsListener);
InstanceScope.INSTANCE.getNode(
org.eclipse.egit.core.Activator.getPluginId())
.addPreferenceChangeListener(prefListener);
updateSectionText();
updateToolbar();
enableCommitWidgets(false);
refreshAction.setEnabled(false);
createPopupMenu(unstagedViewer);
createPopupMenu(stagedViewer);
final ICommitMessageComponentNotifications listener = new ICommitMessageComponentNotifications() {
@Override
public void updateSignedOffToggleSelection(boolean selection) {
signedOffByAction.setChecked(selection);
}
@Override
public void updateChangeIdToggleSelection(boolean selection) {
addChangeIdAction.setChecked(selection);
commitAndPushButton
.setImage(getImage(
selection ? UIIcons.GERRIT : UIIcons.PUSH));
}
@Override
public void statusUpdated() {
updateMessage();
}
};
commitMessageComponent = new CommitMessageComponent(listener);
commitMessageComponent.attachControls(commitMessageText, authorText,
committerText);
// allow to commit with ctrl-enter
commitMessageText.getTextWidget().addVerifyKeyListener(new VerifyKeyListener() {
@Override
public void verifyKey(VerifyEvent event) {
if (UIUtils.isSubmitKeyEvent(event)) {
event.doit = false;
commit(false);
}
}
});
commitMessageText.getTextWidget().addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
// Ctrl+Enter shortcut only works when the focus is on the commit message text
String commitButtonTooltip = MessageFormat.format(
UIText.StagingView_CommitToolTip,
UIUtils.SUBMIT_KEY_STROKE.format());
commitButton.setToolTipText(commitButtonTooltip);
}
@Override
public void focusLost(FocusEvent e) {
commitButton.setToolTipText(null);
}
});
// react on selection changes
IWorkbenchPartSite site = getSite();
ISelectionService srv = CommonUtils.getService(site, ISelectionService.class);
srv.addPostSelectionListener(selectionChangedListener);
CommonUtils.getService(site, IPartService.class).addPartListener(
partListener);
// Use current selection to populate staging view
UIUtils.notifySelectionChangedWithCurrentSelection(
selectionChangedListener, site);
site.setSelectionProvider(unstagedViewer);
ViewerFilter filter = new ViewerFilter() {
@Override
public boolean select(Viewer viewer, Object parentElement,
Object element) {
StagingViewContentProvider contentProvider = getContentProvider((TreeViewer) viewer);
if (element instanceof StagingEntry)
return contentProvider.isInFilter((StagingEntry) element);
else if (element instanceof StagingFolderEntry)
return contentProvider
.hasVisibleChildren((StagingFolderEntry) element);
return true;
}
};
unstagedViewer.addFilter(filter);
stagedViewer.addFilter(filter);
restoreSashFormWeights();
reactOnInitialSelection();
IWorkbenchSiteProgressService service = CommonUtils.getService(
getSite(), IWorkbenchSiteProgressService.class);
if (service != null && reactOnSelection)
// If we are linked, each time IndexDiffUpdateJob starts, indicate
// that the view is busy (e.g. reload() will trigger this job in
// background!).
service.showBusyForFamily(org.eclipse.egit.core.JobFamilies.INDEX_DIFF_CACHE_UPDATE);
}
private boolean commitAndPushEnabled(boolean commitEnabled) {
Repository repo = currentRepository;
if (repo == null) {
return false;
}
return commitEnabled && !repo.getRepositoryState().isRebasing();
}
private void updateIgnoreErrorsButtonVisibility() {
boolean visible = getPreferenceStore()
.getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
&& getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT);
showControl(ignoreErrors, visible);
mainSashForm.layout();
}
private int getProblemsSeverity() {
int result = IProblemDecoratable.SEVERITY_NONE;
StagingViewContentProvider stagedContentProvider = getContentProvider(
stagedViewer);
StagingEntry[] entries = stagedContentProvider.getStagingEntries();
for (StagingEntry entry : entries) {
if (entry.getProblemSeverity() >= IMarker.SEVERITY_WARNING) {
if (result < entry.getProblemSeverity()) {
result = entry.getProblemSeverity();
}
}
}
return result;
}
private void updateCommitButtons() {
IndexDiffData indexDiff;
if (cacheEntry != null) {
indexDiff = cacheEntry.getIndexDiff();
} else {
Repository repo = currentRepository;
if (repo == null) {
indexDiff = null;
} else {
indexDiff = doReload(repo);
}
}
boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
boolean noConflicts = noConflicts(indexDiff);
boolean commitEnabled = !isCommitBlocked() && noConflicts
&& indexDiffAvailable;
boolean commitAndPushEnabled = commitAndPushEnabled(commitEnabled);
commitButton.setEnabled(commitEnabled);
commitAndPushButton.setEnabled(commitAndPushEnabled);
}
private void saveSashFormWeightsOnDisposal(final SashForm sashForm,
final String settingsKey) {
sashForm.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
getDialogSettings().put(settingsKey,
intArrayToString(sashForm.getWeights()));
}
});
}
private IDialogSettings getDialogSettings() {
return DialogSettings.getOrCreateSection(
Activator.getDefault().getDialogSettings(),
StagingView.class.getName());
}
private static String intArrayToString(int[] ints) {
StringBuilder res = new StringBuilder();
if (ints != null && ints.length > 0) {
res.append(String.valueOf(ints[0]));
for (int i = 1; i < ints.length; i++) {
res.append(',');
res.append(String.valueOf(ints[i]));
}
}
return res.toString();
}
private void restoreSashFormWeights() {
restoreSashFormWeights(mainSashForm,
HORIZONTAL_SASH_FORM_WEIGHT);
restoreSashFormWeights(stagingSashForm,
STAGING_SASH_FORM_WEIGHT);
}
private void restoreSashFormWeights(SashForm sashForm, String settingsKey) {
IDialogSettings settings = getDialogSettings();
String weights = settings.get(settingsKey);
if (weights != null && !weights.isEmpty()) {
sashForm.setWeights(stringToIntArray(weights));
}
}
private static int[] stringToIntArray(String s) {
String[] parts = s.split(","); //$NON-NLS-1$
int[] ints = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
ints[i] = Integer.parseInt(parts[i]);
}
return ints;
}
private void reactOnInitialSelection() {
StructuredSelection sel = null;
if (initialSelection instanceof StructuredSelection) {
sel = (StructuredSelection) initialSelection;
} else if (initialSelection != null && !initialSelection.isEmpty()) {
sel = getSelectionOfActiveEditor();
}
if (sel != null) {
reactOnSelection(sel);
}
initialSelection = null;
}
private StructuredSelection getSelectionOfActiveEditor() {
IEditorPart activeEditor = getSite().getPage().getActiveEditor();
if (activeEditor == null) {
return null;
}
return getSelectionOfPart(activeEditor);
}
private static StructuredSelection getSelectionOfPart(IWorkbenchPart part) {
StructuredSelection sel = null;
if (part instanceof IEditorPart) {
IResource resource = getResource((IEditorPart) part);
if (resource != null) {
sel = new StructuredSelection(resource);
} else {
Repository repository = getRepository((IEditorPart) part);
if (repository != null) {
sel = new StructuredSelection(repository);
}
}
} else {
ISelection selection = part.getSite().getPage().getSelection();
if (selection instanceof StructuredSelection) {
sel = (StructuredSelection) selection;
}
}
return sel;
}
@Nullable
private static Repository getRepository(IEditorPart part) {
IEditorInput input = part.getEditorInput();
if (!(input instanceof IURIEditorInput)) {
return null;
}
return AdapterUtils.adapt(input, Repository.class);
}
private static IResource getResource(IEditorPart part) {
IEditorInput input = part.getEditorInput();
if (input instanceof IFileEditorInput) {
return ((IFileEditorInput) input).getFile();
} else {
return AdapterUtils.adaptToAnyResource(input);
}
}
private boolean getSortCheckState() {
return getDialogSettings().getBoolean(STORE_SORT_STATE);
}
private void executeRebaseOperation(AbstractRebaseCommandHandler command) {
try {
command.execute(currentRepository);
} catch (ExecutionException e) {
Activator.showError(e.getMessage(), e);
}
}
/**
* Abort rebase command in progress
*/
protected void rebaseAbort() {
AbortRebaseCommand abortCommand = new AbortRebaseCommand();
executeRebaseOperation(abortCommand);
}
/**
* Rebase next commit and continue rebase in progress
*/
protected void rebaseSkip() {
SkipRebaseCommand skipCommand = new SkipRebaseCommand();
executeRebaseOperation(skipCommand);
}
/**
* Continue rebase command in progress
*/
protected void rebaseContinue() {
ContinueRebaseCommand continueCommand = new ContinueRebaseCommand();
executeRebaseOperation(continueCommand);
}
private void createUnstagedToolBarComposite() {
Composite unstagedToolbarComposite = toolkit
.createComposite(unstagedSection);
unstagedToolbarComposite.setBackground(null);
unstagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
unstagedSection.setTextClient(unstagedToolbarComposite);
unstagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
IAction.AS_PUSH_BUTTON) {
@Override
public void run() {
unstagedViewer.expandAll();
enableAutoExpand(unstagedViewer);
}
};
unstagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
unstagedExpandAllAction.setId(EXPAND_ALL_ITEM_TOOLBAR_ID);
unstagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
IAction.AS_PUSH_BUTTON) {
@Override
public void run() {
unstagedViewer.collapseAll();
disableAutoExpand(unstagedViewer);
}
};
unstagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
unstagedCollapseAllAction.setId(COLLAPSE_ALL_ITEM_TOOLBAR_ID);
sortAction = new Action(UIText.StagingView_UnstagedSort,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
.getComparator();
comparator.setAlphabeticSort(!isChecked());
comparator = (StagingEntryComparator) stagedViewer.getComparator();
comparator.setAlphabeticSort(!isChecked());
unstagedViewer.refresh();
stagedViewer.refresh();
}
};
sortAction.setImageDescriptor(UIIcons.STATE_SORT);
sortAction.setId(SORT_ITEM_TOOLBAR_ID);
sortAction.setChecked(getSortCheckState());
unstagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
unstagedToolBarManager.add(sortAction);
unstagedToolBarManager.add(unstagedExpandAllAction);
unstagedToolBarManager.add(unstagedCollapseAllAction);
unstagedToolBarManager.update(true);
unstagedToolBarManager.createControl(unstagedToolbarComposite);
}
private void createStagedToolBarComposite() {
Composite stagedToolbarComposite = toolkit
.createComposite(stagedSection);
stagedToolbarComposite.setBackground(null);
stagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
stagedSection.setTextClient(stagedToolbarComposite);
stagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
IAction.AS_PUSH_BUTTON) {
@Override
public void run() {
stagedViewer.expandAll();
enableAutoExpand(stagedViewer);
}
};
stagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
stagedExpandAllAction.setId(EXPAND_ALL_ITEM_TOOLBAR_ID);
stagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
IAction.AS_PUSH_BUTTON) {
@Override
public void run() {
stagedViewer.collapseAll();
disableAutoExpand(stagedViewer);
}
};
stagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
stagedCollapseAllAction.setId(COLLAPSE_ALL_ITEM_TOOLBAR_ID);
stagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
stagedToolBarManager.add(stagedExpandAllAction);
stagedToolBarManager.add(stagedCollapseAllAction);
stagedToolBarManager.update(true);
stagedToolBarManager.createControl(stagedToolbarComposite);
}
private static RowLayout createRowLayoutWithoutMargin() {
RowLayout layout = new RowLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.marginTop = 0;
layout.marginBottom = 0;
layout.marginLeft = 0;
layout.marginRight = 0;
return layout;
}
private static void addListenerToDisableAutoExpandOnCollapse(
TreeViewer treeViewer) {
treeViewer.addTreeListener(new ITreeViewerListener() {
@Override
public void treeCollapsed(TreeExpansionEvent event) {
disableAutoExpand(event.getTreeViewer());
}
@Override
public void treeExpanded(TreeExpansionEvent event) {
// Nothing to do
}
});
}
private static void enableAutoExpand(AbstractTreeViewer treeViewer) {
treeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
}
private static void disableAutoExpand(AbstractTreeViewer treeViewer) {
treeViewer.setAutoExpandLevel(0);
}
/**
* @return selected repository
*/
public Repository getCurrentRepository() {
return currentRepository;
}
@Override
public ShowInContext getShowInContext() {
if (stagedViewer != null && stagedViewer.getTree().isFocusControl())
return getShowInContext(stagedViewer);
else if (unstagedViewer != null
&& unstagedViewer.getTree().isFocusControl())
return getShowInContext(unstagedViewer);
else
return null;
}
@Override
public boolean show(ShowInContext context) {
ISelection selection = context.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
for (Object element : structuredSelection.toList()) {
if (element instanceof RepositoryTreeNode) {
RepositoryTreeNode node = (RepositoryTreeNode) element;
reload(node.getRepository());
return true;
}
}
}
return false;
}
private ShowInContext getShowInContext(TreeViewer treeViewer) {
IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
List<Object> elements = new ArrayList<>();
for (Object selectedElement : selection.toList()) {
if (selectedElement instanceof StagingEntry) {
StagingEntry entry = (StagingEntry) selectedElement;
IFile file = entry.getFile();
if (file != null)
elements.add(file);
else
elements.add(entry.getLocation());
} else if (selectedElement instanceof StagingFolderEntry) {
StagingFolderEntry entry = (StagingFolderEntry) selectedElement;
IContainer container = entry.getContainer();
if (container != null)
elements.add(container);
else
elements.add(entry.getLocation());
}
}
return new ShowInContext(null, new StructuredSelection(elements));
}
private int getStagingFormOrientation() {
boolean columnLayout = Activator.getDefault().getPreferenceStore()
.getBoolean(UIPreferences.STAGING_VIEW_COLUMN_LAYOUT);
if (columnLayout)
return SWT.HORIZONTAL;
else
return SWT.VERTICAL;
}
private void enableAllWidgets(boolean enabled) {
if (isDisposed())
return;
enableCommitWidgets(enabled);
commitMessageText.setEnabled(enabled);
enableStagingWidgets(enabled);
}
private void enableStagingWidgets(boolean enabled) {
if (isDisposed())
return;
unstagedViewer.getControl().setEnabled(enabled);
stagedViewer.getControl().setEnabled(enabled);
}
private void enableCommitWidgets(boolean enabled) {
if (isDisposed()) {
return;
}
committerText.setEnabled(enabled);
enableAuthorText(enabled);
amendPreviousCommitAction.setEnabled(enabled);
signedOffByAction.setEnabled(enabled);
addChangeIdAction.setEnabled(enabled);
commitButton.setEnabled(enabled);
commitAndPushButton.setEnabled(enabled);
}
private void enableAuthorText(boolean enabled) {
Repository repo = currentRepository;
if (repo != null && repo.getRepositoryState()
.equals(RepositoryState.CHERRY_PICKING_RESOLVED)) {
authorText.setEnabled(false);
} else {
authorText.setEnabled(enabled);
}
}
private void updateToolbar() {
ControlContribution controlContribution = new ControlContribution(
"StagingView.searchText") { //$NON-NLS-1$
@Override
protected Control createControl(Composite parent) {
Composite toolbarComposite = toolkit.createComposite(parent,
SWT.NONE);
toolbarComposite.setBackground(null);
GridLayout headLayout = new GridLayout();
headLayout.numColumns = 2;
headLayout.marginHeight = 0;
headLayout.marginWidth = 0;
headLayout.marginTop = 0;
headLayout.marginBottom = 0;
headLayout.marginLeft = 0;
headLayout.marginRight = 0;
toolbarComposite.setLayout(headLayout);
filterText = new Text(toolbarComposite, SWT.SEARCH
| SWT.ICON_CANCEL | SWT.ICON_SEARCH);
filterText.setMessage(UIText.StagingView_Find);
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.widthHint = 150;
filterText.setLayoutData(data);
final Display display = Display.getCurrent();
filterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
final StagingViewSearchThread searchThread = new StagingViewSearchThread(
StagingView.this);
display.timerExec(200, new Runnable() {
@Override
public void run() {
searchThread.start();
}
});
}
});
return toolbarComposite;
}
};
IActionBars actionBars = getViewSite().getActionBars();
IToolBarManager toolbar = actionBars.getToolBarManager();
toolbar.add(controlContribution);
refreshAction = new Action(UIText.StagingView_Refresh, IAction.AS_PUSH_BUTTON) {
@Override
public void run() {
if (cacheEntry != null) {
schedule(
cacheEntry.createRefreshResourcesAndIndexDiffJob(),
false);
}
}
};
refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
toolbar.add(refreshAction);
// link with selection
Action linkSelectionAction = new BooleanPrefAction(
(IPersistentPreferenceStore) getPreferenceStore(),
UIPreferences.STAGING_VIEW_SYNC_SELECTION,
UIText.StagingView_LinkSelection) {
@Override
public void apply(boolean value) {
reactOnSelection = value;
}
};
linkSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
toolbar.add(linkSelectionAction);
toolbar.add(new Separator());
compareModeAction = new Action(UIText.StagingView_CompareMode,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
getPreferenceStore().setValue(
UIPreferences.STAGING_VIEW_COMPARE_MODE, isChecked());
}
};
compareModeAction.setImageDescriptor(UIIcons.ELCL16_COMPARE_VIEW);
compareModeAction.setChecked(getPreferenceStore()
.getBoolean(UIPreferences.STAGING_VIEW_COMPARE_MODE));
toolbar.add(compareModeAction);
toolbar.add(new Separator());
openNewCommitsAction = new Action(UIText.StagingView_OpenNewCommits,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
getPreferenceStore().setValue(
UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS, isChecked());
}
};
openNewCommitsAction.setChecked(getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS));
columnLayoutAction = new Action(UIText.StagingView_ColumnLayout,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
getPreferenceStore().setValue(
UIPreferences.STAGING_VIEW_COLUMN_LAYOUT, isChecked());
stagingSashForm.setOrientation(isChecked() ? SWT.HORIZONTAL
: SWT.VERTICAL);
}
};
columnLayoutAction.setChecked(getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_COLUMN_LAYOUT));
fileNameModeAction = new Action(UIText.StagingView_ShowFileNamesFirst,
IAction.AS_CHECK_BOX) {
@Override
public void run() {
final boolean enable = isChecked();
getLabelProvider(stagedViewer).setFileNameMode(enable);
getLabelProvider(unstagedViewer).setFileNameMode(enable);
getContentProvider(stagedViewer).setFileNameMode(enable);
getContentProvider(unstagedViewer).setFileNameMode(enable);
StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
.getComparator();
comparator.setFileNamesFirst(enable);
comparator = (StagingEntryComparator) stagedViewer.getComparator();
comparator.setFileNamesFirst(enable);
getPreferenceStore().setValue(
UIPreferences.STAGING_VIEW_FILENAME_MODE, enable);
refreshViewersPreservingExpandedElements();
}
};
fileNameModeAction.setChecked(getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_FILENAME_MODE));
IMenuManager dropdownMenu = actionBars.getMenuManager();
MenuManager presentationMenu = new MenuManager(
UIText.StagingView_Presentation);
listPresentationAction = new Action(UIText.StagingView_List,
IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
if (!isChecked()) {
return;
}
switchToListMode();
refreshViewers();
}
};
listPresentationAction.setImageDescriptor(UIIcons.FLAT);
presentationMenu.add(listPresentationAction);
treePresentationAction = new Action(UIText.StagingView_Tree,
IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
if (!isChecked()) {
return;
}
presentation = Presentation.TREE;
setPresentation(presentation, false);
listPresentationAction.setChecked(false);
compactTreePresentationAction.setChecked(false);
setExpandCollapseActionsVisible(false, isExpandAllowed(false),
true);
setExpandCollapseActionsVisible(true, isExpandAllowed(true),
true);
refreshViewers();
}
};
treePresentationAction.setImageDescriptor(UIIcons.HIERARCHY);
presentationMenu.add(treePresentationAction);
compactTreePresentationAction = new Action(UIText.StagingView_CompactTree,
IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
if (!isChecked()) {
return;
}
switchToCompactModeInternal(false);
refreshViewers();
}
};
compactTreePresentationAction.setImageDescriptor(UIIcons.COMPACT);
presentationMenu.add(compactTreePresentationAction);
presentation = readPresentation(UIPreferences.STAGING_VIEW_PRESENTATION,
Presentation.LIST);
switch (presentation) {
case LIST:
listPresentationAction.setChecked(true);
setExpandCollapseActionsVisible(false, false, false);
setExpandCollapseActionsVisible(true, false, false);
break;
case TREE:
treePresentationAction.setChecked(true);
break;
case COMPACT_TREE:
compactTreePresentationAction.setChecked(true);
break;
default:
break;
}
dropdownMenu.add(presentationMenu);
dropdownMenu.add(new Separator());
dropdownMenu.add(openNewCommitsAction);
dropdownMenu.add(columnLayoutAction);
dropdownMenu.add(fileNameModeAction);
dropdownMenu.add(compareModeAction);
actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), new GlobalDeleteActionHandler());
// For the normal resource undo/redo actions to be active, so that files
// deleted via the "Delete" action in the staging view can be restored.
IUndoContext workspaceContext = AdapterUtils.adapt(ResourcesPlugin.getWorkspace(), IUndoContext.class);
undoRedoActionGroup = new UndoRedoActionGroup(getViewSite(), workspaceContext, true);
undoRedoActionGroup.fillActionBars(actionBars);
actionBars.updateActionBars();
}
private Presentation readPresentation(String key, Presentation def) {
String presentationString = getPreferenceStore().getString(key);
if (presentationString.length() > 0) {
try {
return Presentation.valueOf(presentationString);
} catch (IllegalArgumentException e) {
// Use given default
}
}
return def;
}
private void setPresentation(Presentation newOne, boolean auto) {
Presentation old = presentation;
presentation = newOne;
IPreferenceStore store = getPreferenceStore();
store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION, newOne.name());
if (auto && old != newOne) {
// remember user choice if we switch mode automatically
store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED,
true);
} else {
store.setToDefault(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
}
}
private void setExpandCollapseActionsVisible(boolean staged,
boolean visibleExpandAll,
boolean visibleCollapseAll) {
ToolBarManager toolBarManager = staged ? stagedToolBarManager
: unstagedToolBarManager;
for (IContributionItem item : toolBarManager.getItems()) {
String id = item.getId();
if (EXPAND_ALL_ITEM_TOOLBAR_ID.equals(id)) {
item.setVisible(visibleExpandAll);
} else if (COLLAPSE_ALL_ITEM_TOOLBAR_ID.equals(id)) {
item.setVisible(visibleCollapseAll);
}
}
(staged ? stagedExpandAllAction : unstagedExpandAllAction)
.setEnabled(visibleExpandAll);
(staged ? stagedCollapseAllAction : unstagedCollapseAllAction)
.setEnabled(visibleCollapseAll);
toolBarManager.update(true);
}
private boolean isExpandAllowed(boolean staged) {
StagingViewContentProvider contentProvider = getContentProvider(
staged ? stagedViewer : unstagedViewer);
return contentProvider.getCount() <= getMaxLimitForListMode();
}
private TreeViewer createTree(Composite composite) {
Tree tree = toolkit.createTree(composite, SWT.FULL_SELECTION
| SWT.MULTI);
TreeViewer treeViewer = new TreeViewer(tree);
return treeViewer;
}
private IBaseLabelProvider createLabelProvider(TreeViewer treeViewer) {
StagingViewLabelProvider baseProvider = new StagingViewLabelProvider(
this);
baseProvider.setFileNameMode(getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_FILENAME_MODE));
ProblemLabelDecorator decorator = new ProblemLabelDecorator(treeViewer);
return new TreeDecoratingLabelProvider(baseProvider, decorator);
}
private StagingViewContentProvider createStagingContentProvider(
boolean unstaged) {
StagingViewContentProvider provider = new StagingViewContentProvider(
this, unstaged);
provider.setFileNameMode(getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_FILENAME_MODE));
return provider;
}
private TreeViewer createViewer(Composite parent, boolean unstaged,
final Consumer<IStructuredSelection> dropAction) {
final TreeViewer viewer = createTree(parent);
GridDataFactory.fillDefaults().grab(true, true)
.applyTo(viewer.getControl());
viewer.getTree().setData(FormToolkit.KEY_DRAW_BORDER,
FormToolkit.TREE_BORDER);
viewer.setLabelProvider(createLabelProvider(viewer));
StagingViewContentProvider contentProvider = createStagingContentProvider(
unstaged);
viewer.setContentProvider(contentProvider);
viewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
new Transfer[] { LocalSelectionTransfer.getTransfer(),
FileTransfer.getInstance() },
new StagingDragListener(viewer, contentProvider, unstaged));
viewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
new Transfer[] { LocalSelectionTransfer.getTransfer() },
new DropTargetAdapter() {
@Override
public void drop(DropTargetEvent event) {
// Bug 411466: It is very important that detail is set
// to DND.DROP_COPY. If it was left as DND.DROP_MOVE and
// the drag comes from the Navigator view, the code in
// NavigatorDragAdapter would delete the resources.
event.detail = DND.DROP_COPY;
if (event.data instanceof IStructuredSelection) {
final IStructuredSelection selection = (IStructuredSelection) event.data;
if ((selection instanceof StagingDragSelection)
&& ((StagingDragSelection) selection)
.isFromUnstaged() == unstaged) {
// Dropped a selection made in this viewer
// back on this viewer: don't do anything,
// otherwise if there are folders in the
// selection, we might unstage or stage files
// not selected!
return;
}
dropAction.accept(selection);
}
}
});
viewer.addOpenListener(event -> compareWith(event));
viewer.setComparator(new StagingEntryComparator(getSortCheckState(),
getPreferenceStore()
.getBoolean(UIPreferences.STAGING_VIEW_FILENAME_MODE)));
viewer.addDoubleClickListener(event -> {
IStructuredSelection selection = (IStructuredSelection) event
.getSelection();
Object selectedNode = selection.getFirstElement();
if (selectedNode instanceof StagingFolderEntry) {
viewer.setExpandedState(selectedNode,
!viewer.getExpandedState(selectedNode));
}
});
enableAutoExpand(viewer);
addListenerToDisableAutoExpandOnCollapse(viewer);
return viewer;
}
private void setStagingViewerInput(TreeViewer stagingViewer,
StagingViewUpdate newInput, Object[] previous,
Set<IPath> additionalPaths) {
// Disable painting and show a busy cursor for the tree during the
// entire update process.
final Tree tree = stagingViewer.getTree();
tree.setRedraw(false);
Cursor oldCursor = tree.getCursor();
tree.setCursor(tree.getDisplay().getSystemCursor(SWT.CURSOR_WAIT));
try {
// Remember the elements at or before the current top element of the
// viewer.
TreeItem topItem = tree.getTopItem();
final Set<Object> precedingObjects = new LinkedHashSet<>();
if (topItem != null) {
new TreeItemVisitor(tree.getItems()) {
@Override
public boolean visit(TreeItem treeItem) {
precedingObjects.add(treeItem.getData());
return true;
}
}.traverse(topItem);
precedingObjects.remove(null);
}
// Controls whether we'll try to preserve the top element in the
// view, i.e., the scroll position. We generally want that unless
// the update has added new objects to the view, in which case those
// are selected and revealed.
boolean preserveTop = true;
boolean keepSelectionVisible = false;
StagingViewUpdate oldInput = (StagingViewUpdate) stagingViewer
.getInput();
if (oldInput != null && oldInput.repository == newInput.repository
&& oldInput.indexDiff != null) {
// If the input has changed and wasn't empty before or wasn't
// for a different repository before, record the contents of the
// viewer before the input is changed.
StagingViewContentProvider contentProvider = getContentProvider(
stagingViewer);
ViewerComparator comparator = stagingViewer.getComparator();
Map<String, Object> oldPaths = buildElementMap(stagingViewer,
contentProvider, comparator);
// Update the input.
stagingViewer.setInput(newInput);
// Restore the previous expansion state, if there is one.
if (previous != null) {
expandPreviousExpandedAndPaths(previous, stagingViewer,
additionalPaths);
}
// Update the selection.
StagingViewerUpdate stagingViewerUpdate = updateSelection(
stagingViewer, contentProvider, oldPaths,
buildElementMap(stagingViewer, contentProvider,
comparator));
// If something has been removed, the element before the removed
// item has been selected, in which case we want to preserve the
// scroll state as much as possible, keeping the selection in
// view. If something has been added, those added things have
// been selected and revealed, so we don't want to preserve the
// top but rather leave the revealed selection alone. If nothing
// has changed, we want to preserve the top, regardless of where
// the current unmodified selection might be, which is what's
// done by default anyway.
if (stagingViewerUpdate == StagingViewerUpdate.REMOVED) {
keepSelectionVisible = true;
} else if (stagingViewerUpdate == StagingViewerUpdate.ADDED) {
preserveTop = false;
}
} else {
// The update is completely different so don't do any of the
// above analysis to see what's different.
stagingViewer.setInput(newInput);
// Restore the previous expansion state, if there is one.
if (previous != null) {
expandPreviousExpandedAndPaths(previous, stagingViewer,
additionalPaths);
}
}
if (preserveTop) {
// It's likely that the tree has scrolled to change the top
// item. So try to restore the current top item to be the one
// with the same data as was at the top before, either starting
// with the selection so that it generally stays in view, or at
// the bottom, if we're not trying to keep the selection
// visible.
TreeItem[] selection = tree.getSelection();
TreeItem initialItem = keepSelectionVisible
&& selection.length > 0 ? selection[0] : null;
new TreeItemVisitor(tree.getItems()) {
@Override
public boolean visit(TreeItem treeItem) {
if (precedingObjects.contains(treeItem.getData())) {
// If we reach an item that was at or before the
// original top item, make it the top item
// again, and stop the visitor.
tree.setTopItem(treeItem);
return false;
}
return true;
}
}.traverse(initialItem);
}
} finally {
// The viewer is fully updated now, so we can paint it.
tree.setRedraw(true);
tree.setCursor(oldCursor);
}
}
private static Map<String, Object> buildElementMap(TreeViewer stagingViewer,
StagingViewContentProvider contentProvider,
ViewerComparator comparator) {
// Builds a map from paths, represented as strings, to elements visible
// in the staging viewer.
Map<String, Object> result = new LinkedHashMap<>();
// Start visiting the root elements in the order in which they appear in
// the UI.
Object[] elements = contentProvider.getElements(null);
comparator.sort(stagingViewer, elements);
for (Object element : elements) {
visitElement(stagingViewer, contentProvider, comparator, element,
result);
}
return result;
}
private static boolean visitElement(TreeViewer stagingViewer,
StagingViewContentProvider contentProvider,
ViewerComparator comparator,
Object element, Map<String, Object> paths) {
if (element instanceof StagingEntry) {
StagingEntry stagingEntry = (StagingEntry) element;
if (contentProvider.isInFilter(stagingEntry)) {
// If the element is a staging entry, and it's included by the
// filter, add a mapping for it.
String path = stagingEntry.getPath();
paths.put(path, stagingEntry);
return true;
}
return false;
}
// If the element is a staging folder entry, visit all the children,
// checking that at least one visited descendant has been added to the
// map before adding a mapping for this staging folder entry.
if (element instanceof StagingFolderEntry) {
StagingFolderEntry stagingFolderEntry = (StagingFolderEntry) element;
// Visit the children in the order in which they appear in the UI.
Object[] children = contentProvider.getChildren(stagingFolderEntry);
comparator.sort(stagingViewer, children);
IPath path = stagingFolderEntry.getPath();
String pathString = path.toString();
paths.put(pathString, stagingFolderEntry);
boolean hasVisibleChildren = false;
for (Object child : children) {
if (visitElement(stagingViewer, contentProvider, comparator,
child, paths)) {
hasVisibleChildren = true;
}
}
if (hasVisibleChildren) {
return true;
}
// If there were no visible children, remove the path from the map.
paths.remove(pathString);
return false;
}
return false;
}
private enum StagingViewerUpdate {
ADDED, REMOVED, UNCHANGED
}
/**
* Updates the selection depending on the type of change in the staging
* viewer's state. If something has been removed, it returns
* {@link StagingViewerUpdate#REMOVED} and the item before the removed
* element is selected. If something has been added, it returns
* {@link StagingViewerUpdate#ADDED} and those added elements are selected
* and revealed. If nothing has changed, it returns
* {@link StagingViewerUpdate#UNCHANGED} and the selection state is
* unchanged.
*
* @param stagingViewer
* the staging viewer for which to update the selection.
* @param contentProvider
* the content provider used by that staging viewer.
* @param oldPaths
* the old content state of the staging viewer.
* @param newPaths
* the new content state of the staging viewer.
* @return the type of change to the selecting of the staging viewer
*/
private static StagingViewerUpdate updateSelection(TreeViewer stagingViewer,
StagingViewContentProvider contentProvider,
Map<String, Object> oldPaths, Map<String, Object> newPaths) {
// Update the staging viewer's selection by analyzing the change
// to the contents of the viewer.
Map<String, Object> addedPaths = new LinkedHashMap<>(newPaths);
addedPaths.keySet().removeAll(oldPaths.keySet());
if (!addedPaths.isEmpty()) {
// If anything has been added to the viewer, select those added
// things. But, to minimize the selection, select a parent node when
// all its children have been added. The general idea is that if you
// drag and drop between staged and unstaged, the new selection in
// the target view, when dragged back again to the source view, will
// undo the original drag-and-drop operation operation.
List<Object> newSelection = new ArrayList<>();
Set<Object> elements = new LinkedHashSet<>(addedPaths.values());
Set<Object> excludeChildren = new LinkedHashSet<>();
for (Object element : elements) {
if (element instanceof StagingEntry) {
StagingEntry stagingEntry = (StagingEntry) element;
if (!excludeChildren.contains(stagingEntry.getParent())) {
// If it's a leaf entry and its parent has not been
// excluded from the selection, include it in the
// selection.
newSelection.add(stagingEntry);
}
} else if (element instanceof StagingFolderEntry) {
StagingFolderEntry stagingFolderEntry = (StagingFolderEntry) element;
StagingFolderEntry parent = stagingFolderEntry.getParent();
if (excludeChildren.contains(parent)) {
// If its parent has been excluded from the selection,
// exclude this folder entry also.
excludeChildren.add(stagingFolderEntry);
} else if (elements.containsAll(contentProvider
.getStagingEntriesFiltered(stagingFolderEntry))) {
// If all of this folder's visible children are added,
// i.e., it had no existing children before, then
// include it in the selection, and exclude its
// children from the selection.
newSelection.add(stagingFolderEntry);
excludeChildren.add(stagingFolderEntry);
}
}
}
// Select and reveal the selection of the newly added elements.
stagingViewer.setSelection(new StructuredSelection(newSelection),
true);
return StagingViewerUpdate.ADDED;
} else {
Map<String, Object> removedPaths = new LinkedHashMap<>(oldPaths);
removedPaths.keySet().removeAll(newPaths.keySet());
if (!removedPaths.isEmpty()) {
// If anything has been removed from the viewer, try to select
// the closest following unremoved sibling of the first removed
// element, a parent if there isn't such a sibling, or the first
// element in the viewer failing those. The general idea is that
// it's really annoying to have the viewer scroll to the top
// element whenever you drag something out of a staging viewer.
Collection<Object> removedElements = removedPaths.values();
Object firstRemovedElement = removedElements.iterator()
.next();
Object parent = contentProvider.getParent(firstRemovedElement);
Object candidate = null;
boolean visitSubsequentSiblings = false;
for (Object oldElement : oldPaths.values()) {
if (oldElement == firstRemovedElement) {
// Once we reach the first removed element, siblings
// that follow are ideal candidates.
visitSubsequentSiblings = true;
}
if (visitSubsequentSiblings) {
if (!removedElements.contains(oldElement)) {
if (contentProvider
.getParent(oldElement) == parent) {
// If this is a subsequent sibling that's not
// itself removed, it's the best candidate.
candidate = oldElement;
break;
} else if (candidate != null) {
// If we already have a candidate, and we're
// looking for a subsequent sibling, but now
// we've hit an element with a different parent
// of the removed element, then we're never
// going to find a subsequent unremoved sibling,
// so just return the candidate.
break;
}
}
} else if (candidate == null || oldElement == parent
|| contentProvider
.getParent(oldElement) == parent) {
// If there is no candidate, or there is a better
// candidate, i.e., the parent or an element with the
// same parent, record the current entry.
candidate = oldElement;
}
}
if (candidate == null && !newPaths.isEmpty()) {
// If there is no selected object yet, just choose the first
// element in the viewer, if there is such an element.
candidate = newPaths.values().iterator().next();
}
if (candidate != null) {
// If we have a selection, which will always be the case
// unless the viewer is empty, set it. This selection is
// preserved during update of the viewer. Unfortunately the
// scroll position is generally quite poor. Fixing the
// scroll position is done after the viewer is updated.
stagingViewer.setSelection(
new StructuredSelection(candidate), true);
return StagingViewerUpdate.REMOVED;
}
}
return StagingViewerUpdate.UNCHANGED;
}
}
/**
* This visitor is used to traverse all visible tree items of a tree viewer
* starting at some specific item, visiting the items in the reverse order
* in which they appear in the UI.
*/
private static abstract class TreeItemVisitor {
private final TreeItem[] roots;
public TreeItemVisitor(TreeItem[] roots) {
this.roots = roots;
}
public abstract boolean visit(TreeItem treeItem);
/**
* The public entry point for invoking this visitor.
*
* @param treeItem
* the item at which to start, are null, to start at the
* bottom.
*/
public void traverse(TreeItem treeItem) {
if (treeItem == null) {
treeItem = getLastItem(roots);
if (treeItem == null) {
return;
}
}
if (treeItem.isDisposed()) {
return;
}
if (treeItem.getData() != null && visit(treeItem)) {
traversePrecedingSiblings(treeItem);
}
}
private TreeItem getLastItem(TreeItem[] treeItems) {
if (treeItems.length == 0) {
return null;
}
TreeItem lastItem = treeItems[treeItems.length - 1];
if (lastItem.getExpanded()) {
TreeItem result = getLastItem(lastItem.getItems());
if (result != null) {
return result;
}
}
return lastItem;
}
private boolean traversePrecedingSiblings(TreeItem treeItem) {
TreeItem parent = treeItem.getParentItem();
if (parent == null) {
// If there is no parent, traverse based on the root items.
return traversePrecedingSiblings(roots, treeItem);
}
// Traverse based on the parent items, i.e., the siblings of the
// tree item.
if (!traversePrecedingSiblings(parent.getItems(), treeItem)) {
return false;
}
// Recursively traverse the parent.
return traversePrecedingSiblings(parent);
}
private boolean traversePrecedingSiblings(TreeItem[] siblings,
TreeItem treeItem) {
// Traverse the siblings in reverse order, skipping the ones that
// are at or before the tree item.
boolean start = false;
for (int i = siblings.length - 1; i >= 0; --i) {
TreeItem sibling = siblings[i];
if (start) {
// Traverse all the visible children of this preceding
// sibling.
if (!traverseChildren(sibling)) {
return false;
}
} else if (sibling == treeItem) {
start = true;
}
}
return true;
}
private boolean traverseChildren(TreeItem treeItem) {
if (treeItem.getExpanded()) {
// If the tree item is expanded, traverse all the children in
// reverse order.
TreeItem[] children = treeItem.getItems();
for (int i = children.length - 1; i >= 0; --i) {
// Recursively traverse the children of the children.
if (!traverseChildren(children[i])) {
return false;
}
}
}
// Call the visitor callback after the children have been visited.
return visit(treeItem);
}
}
private IPreferenceStore getPreferenceStore() {
return Activator.getDefault().getPreferenceStore();
}
private StagingViewLabelProvider getLabelProvider(ContentViewer viewer) {
IBaseLabelProvider base = viewer.getLabelProvider();
ILabelProvider labelProvider = ((TreeDecoratingLabelProvider) base)
.getLabelProvider();
return (StagingViewLabelProvider) labelProvider;
}
private StagingViewContentProvider getContentProvider(ContentViewer viewer) {
return (StagingViewContentProvider) viewer.getContentProvider();
}
private void updateSectionText() {
stagedSection.setText(MessageFormat
.format(UIText.StagingView_StagedChanges,
getSectionCount(stagedViewer)));
unstagedSection.setText(MessageFormat.format(
UIText.StagingView_UnstagedChanges,
getSectionCount(unstagedViewer)));
}
private String getSectionCount(TreeViewer viewer) {
StagingViewContentProvider contentProvider = getContentProvider(viewer);
int count = contentProvider.getCount();
int shownCount = contentProvider.getShownCount();
if (shownCount == count)
return Integer.toString(count);
else
return shownCount + "/" + count; //$NON-NLS-1$
}
private void updateMessage() {
if (hasErrorsOrWarnings()) {
warningLabel.showMessage(UIText.StagingView_MessageErrors);
commitMessageSection.redraw();
} else {
String message = commitMessageComponent.getStatus().getMessage();
boolean needsRedraw = false;
if (message != null) {
warningLabel.showMessage(message);
needsRedraw = true;
} else {
needsRedraw = warningLabel.getVisible();
warningLabel.hideMessage();
}
// Without this explicit redraw, the ControlDecoration of the
// commit message area would not get updated and cause visual
// corruption.
if (needsRedraw)
commitMessageSection.redraw();
}
}
private void compareWith(OpenEvent event) {
IStructuredSelection selection = (IStructuredSelection) event
.getSelection();
if (selection.isEmpty()
|| !(selection.getFirstElement() instanceof StagingEntry))
return;
StagingEntry stagingEntry = (StagingEntry) selection.getFirstElement();
if (stagingEntry.isSubmodule())
return;
switch (stagingEntry.getState()) {
case ADDED:
case CHANGED:
case REMOVED:
runCommand(ActionCommands.COMPARE_INDEX_WITH_HEAD_ACTION, selection);
break;
case CONFLICTING:
runCommand(ActionCommands.MERGE_TOOL_ACTION, selection);
break;
case MISSING:
case MISSING_AND_CHANGED:
case MODIFIED:
case MODIFIED_AND_CHANGED:
case MODIFIED_AND_ADDED:
case UNTRACKED:
default:
if (Activator.getDefault().getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_COMPARE_MODE)) {
// compare with index
runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION, selection);
} else {
openSelectionInEditor(selection);
}
}
}
private void createPopupMenu(final TreeViewer treeViewer) {
final MenuManager menuMgr = new MenuManager();
menuMgr.setRemoveAllWhenShown(true);
Control control = treeViewer.getControl();
control.setMenu(menuMgr.createContextMenu(control));
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
final IStructuredSelection selection = (IStructuredSelection) treeViewer
.getSelection();
if (selection.isEmpty())
return;
Set<StagingEntry> stagingEntrySet = new LinkedHashSet<>();
Set<StagingFolderEntry> stagingFolderSet = new LinkedHashSet<>();
boolean submoduleSelected = false;
boolean folderSelected = false;
boolean onlyFoldersSelected = true;
for (Object element : selection.toArray()) {
if (element instanceof StagingFolderEntry) {
StagingFolderEntry folder = (StagingFolderEntry) element;
folderSelected = true;
if (onlyFoldersSelected) {
stagingFolderSet.add(folder);
}
StagingViewContentProvider contentProvider = getContentProvider(treeViewer);
stagingEntrySet.addAll(contentProvider
.getStagingEntriesFiltered(folder));
} else if (element instanceof StagingEntry) {
if (onlyFoldersSelected) {
stagingFolderSet.clear();
}
onlyFoldersSelected = false;
StagingEntry entry = (StagingEntry) element;
if (entry.isSubmodule()) {
submoduleSelected = true;
}
stagingEntrySet.add(entry);
}
}
List<StagingEntry> stagingEntryList = new ArrayList<>(
stagingEntrySet);
final IStructuredSelection fileSelection = new StructuredSelection(
stagingEntryList);
stagingEntrySet = null;
if (!folderSelected) {
Action openWorkingTreeVersion = new Action(
UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) {
@Override
public void run() {
openSelectionInEditor(fileSelection);
}
};
openWorkingTreeVersion.setEnabled(!submoduleSelected
&& anyElementIsExistingFile(fileSelection));
menuMgr.add(openWorkingTreeVersion);
String label = stagingEntryList.get(0).isStaged()
? UIText.CommitFileDiffViewer_CompareWorkingDirectoryMenuLabel
: UIText.StagingView_CompareWithIndexMenuLabel;
Action openCompareWithIndex = new Action(label) {
@Override
public void run() {
runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION,
fileSelection);
}
};
menuMgr.add(openCompareWithIndex);
}
Set<StagingEntry.Action> availableActions = getAvailableActions(fileSelection);
boolean addReplaceWithFileInGitIndex = availableActions.contains(StagingEntry.Action.REPLACE_WITH_FILE_IN_GIT_INDEX);
boolean addReplaceWithHeadRevision = availableActions.contains(StagingEntry.Action.REPLACE_WITH_HEAD_REVISION);
boolean addStage = availableActions.contains(StagingEntry.Action.STAGE);
boolean addUnstage = availableActions.contains(StagingEntry.Action.UNSTAGE);
boolean addDelete = availableActions.contains(StagingEntry.Action.DELETE);
boolean addIgnore = availableActions.contains(StagingEntry.Action.IGNORE);
boolean addLaunchMergeTool = availableActions.contains(StagingEntry.Action.LAUNCH_MERGE_TOOL);
boolean addReplaceWithOursTheirsMenu = availableActions
.contains(StagingEntry.Action.REPLACE_WITH_OURS_THEIRS_MENU);
if (addStage)
menuMgr.add(new Action(UIText.StagingView_StageItemMenuLabel) {
@Override
public void run() {
stage(selection);
}
});
if (addUnstage)
menuMgr.add(new Action(UIText.StagingView_UnstageItemMenuLabel) {
@Override
public void run() {
unstage(selection);
}
});
boolean selectionIncludesNonWorkspaceResources = selectionIncludesNonWorkspaceResources(fileSelection);
if (addReplaceWithFileInGitIndex)
if (selectionIncludesNonWorkspaceResources)
menuMgr.add(new ReplaceAction(
UIText.StagingView_replaceWithFileInGitIndex,
fileSelection, false));
else
menuMgr.add(createItem(
UIText.StagingView_replaceWithFileInGitIndex,
ActionCommands.DISCARD_CHANGES_ACTION,
fileSelection)); // replace with index
if (addReplaceWithHeadRevision)
if (selectionIncludesNonWorkspaceResources)
menuMgr.add(new ReplaceAction(
UIText.StagingView_replaceWithHeadRevision,
fileSelection, true));
else
menuMgr.add(createItem(
UIText.StagingView_replaceWithHeadRevision,
ActionCommands.REPLACE_WITH_HEAD_ACTION,
fileSelection));
if (addIgnore) {
if (!stagingFolderSet.isEmpty()) {
menuMgr.add(new IgnoreFoldersAction(stagingFolderSet));
}
menuMgr.add(new IgnoreAction(fileSelection));
}
if (addDelete)
menuMgr.add(new DeleteAction(fileSelection));
if (addLaunchMergeTool)
menuMgr.add(createItem(UIText.StagingView_MergeTool,
ActionCommands.MERGE_TOOL_ACTION,
fileSelection));
if (addReplaceWithOursTheirsMenu) {
MenuManager replaceWithMenu = new MenuManager(
UIText.StagingView_ReplaceWith);
ReplaceWithOursTheirsMenu oursTheirsMenu = new ReplaceWithOursTheirsMenu();
oursTheirsMenu.initialize(getSite());
replaceWithMenu.add(oursTheirsMenu);
menuMgr.add(replaceWithMenu);
}
menuMgr.add(new Separator());
menuMgr.add(createShowInMenu());
}
});
}
private boolean anyElementIsExistingFile(IStructuredSelection s) {
for (Object element : s.toList()) {
if (element instanceof StagingEntry) {
StagingEntry entry = (StagingEntry) element;
if (entry.getType() != IResource.FILE) {
continue;
}
if (entry.getLocation().toFile().exists()) {
return true;
}
}
}
return false;
}
/**
* @return selected presentation
*/
Presentation getPresentation() {
return presentation;
}
/**
* @return the trimmed string which is the current filter, empty string for
* no filter
*/
String getFilterString() {
if (filterText != null && !filterText.isDisposed())
return filterText.getText().trim();
else
return ""; //$NON-NLS-1$
}
/**
* Refresh the unstaged and staged viewers without preserving expanded
* elements
*/
public void refreshViewers() {
syncExec(new Runnable() {
@Override
public void run() {
refreshViewersInternal();
}
});
}
/**
* Refresh the unstaged and staged viewers, preserving expanded elements
*/
public void refreshViewersPreservingExpandedElements() {
syncExec(new Runnable() {
@Override
public void run() {
Object[] unstagedExpanded = unstagedViewer.getVisibleExpandedElements();
Object[] stagedExpanded = stagedViewer.getVisibleExpandedElements();
refreshViewersInternal();
unstagedViewer.setExpandedElements(unstagedExpanded);
stagedViewer.setExpandedElements(stagedExpanded);
}
});
}
private void refreshViewersInternal() {
unstagedViewer.refresh();
stagedViewer.refresh();
updateSectionText();
}
private IContributionItem createShowInMenu() {
IWorkbenchWindow workbenchWindow = getSite().getWorkbenchWindow();
return UIUtils.createShowInMenu(workbenchWindow);
}
private class ReplaceAction extends Action {
IStructuredSelection selection;
private final boolean headRevision;
ReplaceAction(String text, @NonNull IStructuredSelection selection,
boolean headRevision) {
super(text);
this.selection = selection;
this.headRevision = headRevision;
}
private void getSelectedFiles(@NonNull List<String> files,
@NonNull List<String> inaccessibleFiles) {
Iterator iterator = selection.iterator();
while (iterator.hasNext()) {
Object selectedItem = iterator.next();
if (selectedItem instanceof StagingEntry) {
StagingEntry stagingEntry = (StagingEntry) selectedItem;
String path = stagingEntry.getPath();
files.add(path);
IFile resource = stagingEntry.getFile();
if (resource == null || !resource.isAccessible()) {
inaccessibleFiles.add(path);
}
}
}
}
private void replaceWith(@NonNull List<String> files,
@NonNull List<String> inaccessibleFiles) {
Repository repository = currentRepository;
if (files.isEmpty() || repository == null) {
return;
}
try (Git git = new Git(repository)) {
CheckoutCommand checkoutCommand = git.checkout();
if (headRevision) {
checkoutCommand.setStartPoint(Constants.HEAD);
}
for (String path : files) {
checkoutCommand.addPath(path);
}
checkoutCommand.call();
if (!inaccessibleFiles.isEmpty()) {
IndexDiffCacheEntry indexDiffCacheForRepository = org.eclipse.egit.core.Activator
.getDefault().getIndexDiffCache()
.getIndexDiffCacheEntry(repository);
if (indexDiffCacheForRepository != null) {
indexDiffCacheForRepository
.refreshFiles(inaccessibleFiles);
}
}
} catch (Exception e) {
Activator.handleError(UIText.StagingView_checkoutFailed, e,
true);
}
}
@Override
public void run() {
String question = UIText.DiscardChangesAction_confirmActionMessage;
ILaunchConfiguration launch = LaunchFinder
.getRunningLaunchConfiguration(
Collections.singleton(getCurrentRepository()),
null);
if (launch != null) {
question = MessageFormat.format(question,
"\n\n" + MessageFormat.format( //$NON-NLS-1$
UIText.LaunchFinder_RunningLaunchMessage,
launch.getName()));
} else {
question = MessageFormat.format(question, ""); //$NON-NLS-1$
}
boolean performAction = MessageDialog.openConfirm(form.getShell(),
UIText.DiscardChangesAction_confirmActionTitle, question);
if (!performAction) {
return;
}
List<String> files = new ArrayList<>();
List<String> inaccessibleFiles = new ArrayList<>();
getSelectedFiles(files, inaccessibleFiles);
replaceWith(files, inaccessibleFiles);
}
}
private static class IgnoreAction extends Action {
private final IStructuredSelection selection;
IgnoreAction(IStructuredSelection selection) {
super(UIText.StagingView_IgnoreItemMenuLabel);
this.selection = selection;
}
@Override
public void run() {
IgnoreOperationUI operation = new IgnoreOperationUI(
getSelectedPaths(selection));
operation.run();
}
}
private static class IgnoreFoldersAction extends Action {
private final Set<StagingFolderEntry> selection;
IgnoreFoldersAction(Set<StagingFolderEntry> selection) {
super(UIText.StagingView_IgnoreFolderMenuLabel);
this.selection = selection;
}
@Override
public void run() {
List<IPath> paths = new ArrayList<>();
for (StagingFolderEntry folder : selection) {
paths.add(folder.getLocation());
}
IgnoreOperationUI operation = new IgnoreOperationUI(paths);
operation.run();
}
}
private class DeleteAction extends Action {
private final IStructuredSelection selection;
DeleteAction(IStructuredSelection selection) {
super(UIText.StagingView_DeleteItemMenuLabel);
this.selection = selection;
}
@Override
public void run() {
DeletePathsOperationUI operation = new DeletePathsOperationUI(
getSelectedPaths(selection), getSite());
operation.run();
}
}
private class GlobalDeleteActionHandler extends Action {
@Override
public void run() {
DeletePathsOperationUI operation = new DeletePathsOperationUI(
getSelectedPaths(getSelection()), getSite());
operation.run();
}
@Override
public boolean isEnabled() {
if (!unstagedViewer.getTree().isFocusControl())
return false;
IStructuredSelection selection = getSelection();
if (selection.isEmpty())
return false;
for (Object element : selection.toList()) {
if (!(element instanceof StagingEntry))
return false;
StagingEntry entry = (StagingEntry) element;
if (!entry.getAvailableActions().contains(StagingEntry.Action.DELETE))
return false;
}
return true;
}
private IStructuredSelection getSelection() {
return (IStructuredSelection) unstagedViewer.getSelection();
}
}
private static List<IPath> getSelectedPaths(IStructuredSelection selection) {
List<IPath> paths = new ArrayList<>();
Iterator iterator = selection.iterator();
while (iterator.hasNext()) {
StagingEntry stagingEntry = (StagingEntry) iterator.next();
paths.add(stagingEntry.getLocation());
}
return paths;
}
/**
* @param selection
* @return true if the selection includes a non-workspace resource, false otherwise
*/
private boolean selectionIncludesNonWorkspaceResources(ISelection selection) {
if (!(selection instanceof IStructuredSelection))
return false;
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
Iterator iterator = structuredSelection.iterator();
while (iterator.hasNext()) {
Object selectedObject = iterator.next();
if (!(selectedObject instanceof StagingEntry))
return false;
StagingEntry stagingEntry = (StagingEntry) selectedObject;
IFile file = stagingEntry.getFile();
if (file == null || !file.isAccessible()) {
return true;
}
}
return false;
}
private void openSelectionInEditor(ISelection s) {
Repository repo = currentRepository;
if (repo == null || s.isEmpty() || !(s instanceof IStructuredSelection)) {
return;
}
final IStructuredSelection iss = (IStructuredSelection) s;
for (Object element : iss.toList()) {
if (element instanceof StagingEntry) {
StagingEntry entry = (StagingEntry) element;
String relativePath = entry.getPath();
File file = new Path(repo.getWorkTree().getAbsolutePath())
.append(relativePath).toFile();
DiffViewer.openFileInEditor(file, -1);
}
}
}
private static Set<StagingEntry.Action> getAvailableActions(IStructuredSelection selection) {
Set<StagingEntry.Action> availableActions = EnumSet.noneOf(StagingEntry.Action.class);
for (Iterator it = selection.iterator(); it.hasNext(); ) {
StagingEntry stagingEntry = (StagingEntry) it.next();
if (availableActions.isEmpty())
availableActions.addAll(stagingEntry.getAvailableActions());
else
availableActions.retainAll(stagingEntry.getAvailableActions());
}
return availableActions;
}
private IAction createItem(String text, final String commandId,
final IStructuredSelection selection) {
return new Action(text) {
@Override
public void run() {
CommonUtils.runCommand(commandId, selection);
}
};
}
private boolean shouldUpdateSelection() {
return !isDisposed() && !isViewHidden && reactOnSelection;
}
private void reactOnSelection(StructuredSelection selection) {
if (selection.size() != 1 || isDisposed()) {
return;
}
if (!shouldUpdateSelection()) {
// Remember it all the same to be able to update the view when it
// becomes active again
lastSelection = reactOnSelection ? selection : null;
return;
}
lastSelection = null;
Object firstElement = selection.getFirstElement();
if (firstElement instanceof RepositoryTreeNode) {
RepositoryTreeNode repoNode = (RepositoryTreeNode) firstElement;
if (currentRepository != repoNode.getRepository()) {
reload(repoNode.getRepository());
}
} else if (firstElement instanceof Repository) {
Repository repo = (Repository) firstElement;
if (currentRepository != repo) {
reload(repo);
}
} else {
Repository repo = AdapterUtils.adapt(firstElement,
Repository.class);
if (repo != null) {
if (currentRepository != repo) {
reload(repo);
}
} else {
IResource resource = AdapterUtils
.adaptToAnyResource(firstElement);
if (resource != null) {
showResource(resource);
}
}
}
}
private void showResource(final IResource resource) {
if (resource == null || !resource.isAccessible()) {
return;
}
Job.getJobManager().cancel(JobFamilies.UPDATE_SELECTION);
Job job = new Job(UIText.StagingView_GetRepo) {
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
RepositoryMapping mapping = RepositoryMapping
.getMapping(resource);
if (mapping != null) {
Repository newRep = mapping.getRepository();
if (newRep != null && newRep != currentRepository) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
reload(newRep);
}
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return JobFamilies.UPDATE_SELECTION == family;
}
@Override
public boolean shouldRun() {
return shouldUpdateSelection();
}
};
job.setSystem(true);
schedule(job, false);
}
private void stage(IStructuredSelection selection) {
StagingViewContentProvider contentProvider = getContentProvider(unstagedViewer);
final Repository repository = currentRepository;
Iterator iterator = selection.iterator();
final List<String> addPaths = new ArrayList<>();
final List<String> rmPaths = new ArrayList<>();
resetPathsToExpand();
while (iterator.hasNext()) {
Object element = iterator.next();
if (element instanceof StagingEntry) {
StagingEntry entry = (StagingEntry) element;
selectEntryForStaging(entry, addPaths, rmPaths);
addPathAndParentPaths(entry.getParentPath(), pathsToExpandInStaged);
} else if (element instanceof StagingFolderEntry) {
StagingFolderEntry folder = (StagingFolderEntry) element;
List<StagingEntry> entries = contentProvider
.getStagingEntriesFiltered(folder);
for (StagingEntry entry : entries)
selectEntryForStaging(entry, addPaths, rmPaths);
addExpandedPathsBelowFolder(folder, unstagedViewer,
pathsToExpandInStaged);
} else {
IResource resource = AdapterUtils.adaptToAnyResource(element);
if (resource != null) {
RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
// doesn't do anything if the current repository is a
// submodule of the mapped repo
if (mapping != null && mapping.getRepository() == currentRepository) {
String path = mapping.getRepoRelativePath(resource);
// If resource corresponds to root of working directory
if ("".equals(path)) //$NON-NLS-1$
addPaths.add("."); //$NON-NLS-1$
else
addPaths.add(path);
}
}
}
}
// start long running operations
if (!addPaths.isEmpty()) {
Job addJob = new Job(UIText.StagingView_AddJob) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try (Git git = new Git(repository)) {
AddCommand add = git.add();
for (String addPath : addPaths)
add.addFilepattern(addPath);
add.call();
} catch (NoFilepatternException e1) {
// cannot happen
} catch (JGitInternalException e1) {
Activator.handleError(e1.getCause().getMessage(),
e1.getCause(), true);
} catch (Exception e1) {
Activator.handleError(e1.getMessage(), e1, true);
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return family == JobFamilies.ADD_TO_INDEX;
}
};
schedule(addJob, true);
}
if (!rmPaths.isEmpty()) {
Job removeJob = new Job(UIText.StagingView_RemoveJob) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try (Git git = new Git(repository)) {
RmCommand rm = git.rm().setCached(true);
for (String rmPath : rmPaths)
rm.addFilepattern(rmPath);
rm.call();
} catch (NoFilepatternException e) {
// cannot happen
} catch (JGitInternalException e) {
Activator.handleError(e.getCause().getMessage(),
e.getCause(), true);
} catch (Exception e) {
Activator.handleError(e.getMessage(), e, true);
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return family == JobFamilies.REMOVE_FROM_INDEX;
}
};
schedule(removeJob, true);
}
}
private void selectEntryForStaging(StagingEntry entry,
List<String> addPaths, List<String> rmPaths) {
switch (entry.getState()) {
case ADDED:
case CHANGED:
case REMOVED:
// already staged
break;
case CONFLICTING:
case MODIFIED:
case MODIFIED_AND_CHANGED:
case MODIFIED_AND_ADDED:
case UNTRACKED:
addPaths.add(entry.getPath());
break;
case MISSING:
case MISSING_AND_CHANGED:
rmPaths.add(entry.getPath());
break;
}
}
private void unstage(IStructuredSelection selection) {
if (selection.isEmpty())
return;
final List<String> paths = processUnstageSelection(selection);
if (paths.isEmpty())
return;
final Repository repository = currentRepository;
Job resetJob = new Job(UIText.StagingView_ResetJob) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try (Git git = new Git(repository)) {
ResetCommand reset = git.reset();
for (String path : paths)
reset.addPath(path);
reset.call();
} catch (GitAPIException e) {
Activator.handleError(e.getMessage(), e, true);
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
return family == JobFamilies.RESET;
}
};
schedule(resetJob, true);
}
private List<String> processUnstageSelection(IStructuredSelection selection) {
List<String> paths = new ArrayList<>();
resetPathsToExpand();
for (Object element : selection.toList()) {
if (element instanceof StagingEntry) {
StagingEntry entry = (StagingEntry) element;
addUnstagePath(entry, paths);
addPathAndParentPaths(entry.getParentPath(), pathsToExpandInUnstaged);
} else if (element instanceof StagingFolderEntry) {
StagingFolderEntry folder = (StagingFolderEntry) element;
List<StagingEntry> entries = getContentProvider(stagedViewer)
.getStagingEntriesFiltered(folder);
for (StagingEntry entry : entries)
addUnstagePath(entry, paths);
addExpandedPathsBelowFolder(folder, stagedViewer,
pathsToExpandInUnstaged);
}
}
return paths;
}
private void addUnstagePath(StagingEntry entry, List<String> paths) {
switch (entry.getState()) {
case ADDED:
case CHANGED:
case REMOVED:
paths.add(entry.getPath());
return;
default:
// unstaged
}
}
private void resetPathsToExpand() {
pathsToExpandInStaged = new HashSet<>();
pathsToExpandInUnstaged = new HashSet<>();
}
private static void addExpandedPathsBelowFolder(StagingFolderEntry folder,
TreeViewer treeViewer, Set<IPath> addToSet) {
Object[] expandedElements = treeViewer.getVisibleExpandedElements();
for (Object expandedElement : expandedElements) {
if (expandedElement instanceof StagingFolderEntry) {
StagingFolderEntry expandedFolder = (StagingFolderEntry) expandedElement;
if (folder.getPath().isPrefixOf(
expandedFolder.getPath()))
addPathAndParentPaths(expandedFolder.getPath(), addToSet);
}
}
}
private static void addPathAndParentPaths(IPath initialPath, Set<IPath> addToSet) {
for (IPath p = initialPath; p.segmentCount() >= 1; p = p
.removeLastSegments(1))
addToSet.add(p);
}
private boolean isValidRepo(final Repository repository) {
return repository != null
&& !repository.isBare()
&& repository.getWorkTree().exists();
}
/**
* Clear the view's state.
* <p>
* This method must be called from the UI-thread
*
* @param repository
*/
private void clearRepository(@Nullable Repository repository) {
saveCommitMessageComponentState();
currentRepository = null;
StagingViewUpdate update = new StagingViewUpdate(null, null, null);
setStagingViewerInput(unstagedViewer, update, null, null);
setStagingViewerInput(stagedViewer, update, null, null);
enableCommitWidgets(false);
refreshAction.setEnabled(false);
updateSectionText();
if (repository != null && repository.isBare()) {
form.setText(UIText.StagingView_BareRepoSelection);
} else {
form.setText(UIText.StagingView_NoSelectionTitle);
}
updateIgnoreErrorsButtonVisibility();
}
/**
* Show rebase buttons only if a rebase operation is in progress
*
* @param isRebasing
* {@code}true if rebase is in progress
*/
protected void updateRebaseButtonVisibility(final boolean isRebasing) {
asyncExec(new Runnable() {
@Override
public void run() {
if (isDisposed())
return;
showControl(rebaseSection, isRebasing);
rebaseSection.getParent().layout(true);
}
});
}
private static void showControl(Control c, final boolean show) {
c.setVisible(show);
GridData g = (GridData) c.getLayoutData();
g.exclude = !show;
}
/**
* @param isAmending
* if the current commit should be amended
*/
public void setAmending(boolean isAmending) {
if (isDisposed())
return;
if (amendPreviousCommitAction.isChecked() != isAmending) {
amendPreviousCommitAction.setChecked(isAmending);
amendPreviousCommitAction.run();
}
}
/**
* @param message
* commit message to set for current repository
*/
public void setCommitMessage(String message) {
commitMessageText.setText(message);
}
/**
* Reload the staging view asynchronously
*
* @param repository
*/
public void reload(final Repository repository) {
if (isDisposed()) {
return;
}
if (repository == null) {
asyncExec(new Runnable() {
@Override
public void run() {
clearRepository(null);
}
});
return;
}
if (!isValidRepo(repository)) {
asyncExec(new Runnable() {
@Override
public void run() {
clearRepository(repository);
}
});
return;
}
final boolean repositoryChanged = currentRepository != repository;
currentRepository = repository;
asyncExec(new Runnable() {
@Override
public void run() {
if (isDisposed()) {
return;
}
final IndexDiffData indexDiff = doReload(repository);
boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
boolean noConflicts = noConflicts(indexDiff);
if (repositoryChanged) {
// Reset paths, they're from the old repository
resetPathsToExpand();
if (refsChangedListener != null)
refsChangedListener.remove();
refsChangedListener = repository.getListenerList()
.addRefsChangedListener(new RefsChangedListener() {
@Override
public void onRefsChanged(RefsChangedEvent event) {
updateRebaseButtonVisibility(repository
.getRepositoryState().isRebasing());
}
});
}
final StagingViewUpdate update = new StagingViewUpdate(repository, indexDiff, null);
Object[] unstagedExpanded = unstagedViewer.getVisibleExpandedElements();
Object[] stagedExpanded = stagedViewer.getVisibleExpandedElements();
int unstagedElementsCount = updateAutoExpand(unstagedViewer,
getUnstaged(indexDiff));
int stagedElementsCount = updateAutoExpand(stagedViewer,
getStaged(indexDiff));
int elementsCount = unstagedElementsCount + stagedElementsCount;
if (elementsCount > getMaxLimitForListMode()) {
listPresentationAction.setEnabled(false);
if (presentation == Presentation.LIST) {
compactTreePresentationAction.setChecked(true);
switchToCompactModeInternal(true);
} else {
setExpandCollapseActionsVisible(false,
unstagedElementsCount <= getMaxLimitForListMode(),
true);
setExpandCollapseActionsVisible(true,
stagedElementsCount <= getMaxLimitForListMode(),
true);
}
} else {
listPresentationAction.setEnabled(true);
boolean changed = getPreferenceStore().getBoolean(
UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
if (changed) {
listPresentationAction.setChecked(true);
switchToListMode();
} else if (presentation != Presentation.LIST) {
setExpandCollapseActionsVisible(false, true, true);
setExpandCollapseActionsVisible(true, true, true);
}
}
setStagingViewerInput(unstagedViewer, update, unstagedExpanded,
pathsToExpandInUnstaged);
setStagingViewerInput(stagedViewer, update, stagedExpanded,
pathsToExpandInStaged);
resetPathsToExpand();
refreshAction.setEnabled(true);
updateRebaseButtonVisibility(repository.getRepositoryState()
.isRebasing());
updateIgnoreErrorsButtonVisibility();
boolean rebaseContinueEnabled = indexDiffAvailable
&& repository.getRepositoryState().isRebasing()
&& noConflicts;
rebaseContinueButton.setEnabled(rebaseContinueEnabled);
form.setText(GitLabels.getStyledLabelSafe(repository).toString());
updateCommitMessageComponent(repositoryChanged, indexDiffAvailable);
enableCommitWidgets(indexDiffAvailable && noConflicts);
updateCommitButtons();
updateSectionText();
}
});
}
/**
* The max number of changed files we can handle in the "list" presentation
* without freezing Eclipse UI for a too long time.
*
* @return default is 10000
*/
private int getMaxLimitForListMode() {
return Activator.getDefault().getPreferenceStore()
.getInt(UIPreferences.STAGING_VIEW_MAX_LIMIT_LIST_MODE);
}
private static int getUnstaged(@Nullable IndexDiffData indexDiff) {
if (indexDiff == null) {
return 0;
}
int size = indexDiff.getUntracked().size();
size += indexDiff.getMissing().size();
size += indexDiff.getModified().size();
size += indexDiff.getConflicting().size();
return size;
}
private static int getStaged(@Nullable IndexDiffData indexDiff) {
if (indexDiff == null) {
return 0;
}
int size = indexDiff.getAdded().size();
size += indexDiff.getChanged().size();
size += indexDiff.getRemoved().size();
return size;
}
private int updateAutoExpand(TreeViewer viewer, int newSize) {
if (newSize > getMaxLimitForListMode()) {
// auto expand with too many nodes freezes eclipse
disableAutoExpand(viewer);
}
return newSize;
}
private void switchToCompactModeInternal(boolean auto) {
setPresentation(Presentation.COMPACT_TREE, auto);
listPresentationAction.setChecked(false);
treePresentationAction.setChecked(false);
if (auto) {
setExpandCollapseActionsVisible(false, false, true);
setExpandCollapseActionsVisible(true, false, true);
} else {
setExpandCollapseActionsVisible(false, isExpandAllowed(false),
true);
setExpandCollapseActionsVisible(true, isExpandAllowed(true), true);
}
}
private void switchToListMode() {
setPresentation(Presentation.LIST, false);
treePresentationAction.setChecked(false);
compactTreePresentationAction.setChecked(false);
setExpandCollapseActionsVisible(false, false, false);
setExpandCollapseActionsVisible(true, false, false);
}
private static boolean noConflicts(IndexDiffData indexDiff) {
return indexDiff == null ? true : indexDiff.getConflicting().isEmpty();
}
private static boolean indexDiffAvailable(IndexDiffData indexDiff) {
return indexDiff == null ? false : true;
}
private boolean hasErrorsOrWarnings() {
return getPreferenceStore()
.getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
? (getProblemsSeverity() >= Integer
.parseInt(getPreferenceStore()
.getString(UIPreferences.WARN_BEFORE_COMMITTING_LEVEL))
&& !ignoreErrors.getSelection()) : false;
}
private boolean isCommitBlocked() {
return getPreferenceStore()
.getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
&& getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT)
? (getProblemsSeverity() >= Integer
.parseInt(getPreferenceStore().getString(
UIPreferences.BLOCK_COMMIT_LEVEL))
&& !ignoreErrors.getSelection())
: false;
}
private IndexDiffData doReload(@NonNull final Repository repository) {
IndexDiffCacheEntry entry = org.eclipse.egit.core.Activator.getDefault()
.getIndexDiffCache().getIndexDiffCacheEntry(repository);
if(cacheEntry != null && cacheEntry != entry)
cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
cacheEntry = entry;
cacheEntry.addIndexDiffChangedListener(myIndexDiffListener);
return cacheEntry.getIndexDiff();
}
private void expandPreviousExpandedAndPaths(Object[] previous,
TreeViewer viewer, Set<IPath> additionalPaths) {
StagingViewContentProvider stagedContentProvider = getContentProvider(
viewer);
int count = stagedContentProvider.getCount();
updateAutoExpand(viewer, count);
// Auto-expand is on, so don't change expanded items
if (viewer.getAutoExpandLevel() == AbstractTreeViewer.ALL_LEVELS) {
return;
}
// No need to expand anything
if (getPresentation() == Presentation.LIST)
return;
Set<IPath> paths = new HashSet<>(additionalPaths);
// Instead of just expanding the previous elements directly, also expand
// all parent paths. This makes it work in case of "re-folding" of
// compact tree.
for (Object element : previous) {
if (element instanceof StagingFolderEntry) {
addPathAndParentPaths(((StagingFolderEntry) element).getPath(), paths);
}
}
// Also consider the currently expanded elements because auto selection
// could have expanded some elements.
for (Object element : viewer.getVisibleExpandedElements()) {
if (element instanceof StagingFolderEntry) {
addPathAndParentPaths(((StagingFolderEntry) element).getPath(),
paths);
}
}
List<StagingFolderEntry> expand = new ArrayList<>();
calculateNodesToExpand(paths, stagedContentProvider.getElements(null),
expand);
viewer.setExpandedElements(expand.toArray());
}
private void calculateNodesToExpand(Set<IPath> paths, Object[] elements,
List<StagingFolderEntry> result) {
if (elements == null)
return;
for (Object element : elements) {
if (element instanceof StagingFolderEntry) {
StagingFolderEntry folder = (StagingFolderEntry) element;
if (paths.contains(folder.getPath())) {
result.add(folder);
// Only recurs if folder matched (i.e. don't try to expand
// children of unexpanded parents)
calculateNodesToExpand(paths, folder.getChildren(), result);
}
}
}
}
private void clearCommitMessageToggles() {
amendPreviousCommitAction.setChecked(false);
addChangeIdAction.setChecked(false);
signedOffByAction.setChecked(false);
}
void updateCommitMessageComponent(boolean repositoryChanged, boolean indexDiffAvailable) {
if (repositoryChanged)
if (commitMessageComponent.isAmending()
|| userEnteredCommitMessage())
saveCommitMessageComponentState();
else
deleteCommitMessageComponentState();
if (!indexDiffAvailable)
return; // only try to restore the stored repo commit message if
// indexDiff is ready
CommitHelper helper = new CommitHelper(currentRepository);
CommitMessageComponentState oldState = null;
if (repositoryChanged
|| commitMessageComponent.getRepository() != currentRepository) {
oldState = loadCommitMessageComponentState();
commitMessageComponent.setRepository(currentRepository);
if (oldState == null)
loadInitialState(helper);
else
loadExistingState(helper, oldState);
} else { // repository did not change
if (!commitMessageComponent.getHeadCommit().equals(
helper.getPreviousCommit())
|| !commitMessageComponent.isAmending()) {
if (!commitMessageComponent.isAmending()
&& userEnteredCommitMessage())
addHeadChangedWarning(commitMessageComponent
.getCommitMessage());
else
loadInitialState(helper);
}
}
amendPreviousCommitAction.setChecked(commitMessageComponent
.isAmending());
amendPreviousCommitAction.setEnabled(helper.amendAllowed());
updateMessage();
}
/**
* Resets the commit message component state and saves the overwritten
* commit message into message history
*/
public void resetCommitMessageComponent() {
if (currentRepository != null) {
String commitMessage = commitMessageComponent.getCommitMessage();
if (commitMessage.trim().length() > 0) {
CommitMessageHistory.saveCommitHistory(commitMessage);
}
loadInitialState(new CommitHelper(currentRepository));
}
}
private void loadExistingState(CommitHelper helper,
CommitMessageComponentState oldState) {
boolean headCommitChanged = !oldState.getHeadCommit().equals(
getCommitId(helper.getPreviousCommit()));
commitMessageComponent.enableListeners(false);
commitMessageComponent.setAuthor(oldState.getAuthor());
if (headCommitChanged)
addHeadChangedWarning(oldState.getCommitMessage());
else
commitMessageComponent
.setCommitMessage(oldState.getCommitMessage());
commitMessageComponent.setCommitter(oldState.getCommitter());
commitMessageComponent.setHeadCommit(getCommitId(helper
.getPreviousCommit()));
commitMessageComponent.setCommitAllowed(helper.canCommit());
commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
boolean amendAllowed = helper.amendAllowed();
commitMessageComponent.setAmendAllowed(amendAllowed);
if (!amendAllowed)
commitMessageComponent.setAmending(false);
else if (!headCommitChanged && oldState.getAmend())
commitMessageComponent.setAmending(true);
else
commitMessageComponent.setAmending(false);
commitMessageComponent.updateUIFromState();
commitMessageComponent.updateSignedOffAndChangeIdButton();
commitMessageComponent.enableListeners(true);
}
private void addHeadChangedWarning(String commitMessage) {
if (!commitMessage.startsWith(UIText.StagingView_headCommitChanged)) {
String message = UIText.StagingView_headCommitChanged
+ Text.DELIMITER + Text.DELIMITER + commitMessage;
commitMessageComponent.setCommitMessage(message);
}
}
private void loadInitialState(CommitHelper helper) {
commitMessageComponent.enableListeners(false);
commitMessageComponent.resetState();
commitMessageComponent.setAuthor(helper.getAuthor());
commitMessageComponent.setCommitMessage(helper.getCommitMessage());
commitMessageComponent.setCommitter(helper.getCommitter());
commitMessageComponent.setHeadCommit(getCommitId(helper
.getPreviousCommit()));
commitMessageComponent.setCommitAllowed(helper.canCommit());
commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
commitMessageComponent.setAmendAllowed(helper.amendAllowed());
commitMessageComponent.setAmending(false);
// set the defaults for change id and signed off buttons.
commitMessageComponent.setDefaults();
commitMessageComponent.updateUI();
commitMessageComponent.enableListeners(true);
}
private boolean userEnteredCommitMessage() {
if (commitMessageComponent.getRepository() == null)
return false;
String message = commitMessageComponent.getCommitMessage().replace(
UIText.StagingView_headCommitChanged, ""); //$NON-NLS-1$
if (message == null || message.trim().length() == 0)
return false;
String chIdLine = "Change-Id: I" + ObjectId.zeroId().name(); //$NON-NLS-1$
Repository repo = currentRepository;
if (repo != null && GerritUtil.getCreateChangeId(repo.getConfig())
&& commitMessageComponent.getCreateChangeId()) {
if (message.trim().equals(chIdLine))
return false;
// change id was added automatically, but there is more in the
// message; strip the id, and check for the signed-off-by tag
message = message.replace(chIdLine, ""); //$NON-NLS-1$
}
if (org.eclipse.egit.ui.Activator.getDefault().getPreferenceStore()
.getBoolean(UIPreferences.COMMIT_DIALOG_SIGNED_OFF_BY)
&& commitMessageComponent.isSignedOff()
&& message.trim().equals(
Constants.SIGNED_OFF_BY_TAG
+ commitMessageComponent.getCommitter()))
return false;
return true;
}
private ObjectId getCommitId(RevCommit commit) {
if (commit == null)
return ObjectId.zeroId();
return commit.getId();
}
private void saveCommitMessageComponentState() {
final Repository repo = commitMessageComponent.getRepository();
if (repo != null)
CommitMessageComponentStateManager.persistState(repo,
commitMessageComponent.getState());
}
private void deleteCommitMessageComponentState() {
if (commitMessageComponent.getRepository() != null)
CommitMessageComponentStateManager
.deleteState(commitMessageComponent.getRepository());
}
private CommitMessageComponentState loadCommitMessageComponentState() {
return CommitMessageComponentStateManager.loadState(currentRepository);
}
private Collection<String> getStagedFileNames() {
StagingViewContentProvider stagedContentProvider = getContentProvider(stagedViewer);
StagingEntry[] entries = stagedContentProvider.getStagingEntries();
List<String> files = new ArrayList<>();
for (StagingEntry entry : entries)
files.add(entry.getPath());
return files;
}
private void commit(boolean pushUpstream) {
if (!isCommitWithoutFilesAllowed()) {
MessageDialog.openError(getSite().getShell(),
UIText.StagingView_committingNotPossible,
UIText.StagingView_noStagedFiles);
return;
}
if (!commitMessageComponent.checkCommitInfo())
return;
if (!UIUtils.saveAllEditors(currentRepository,
UIText.StagingView_cancelCommitAfterSaving))
return;
String commitMessage = commitMessageComponent.getCommitMessage();
CommitOperation commitOperation = null;
try {
commitOperation = new CommitOperation(currentRepository,
commitMessageComponent.getAuthor(),
commitMessageComponent.getCommitter(),
commitMessage);
} catch (CoreException e) {
Activator.handleError(UIText.StagingView_commitFailed, e, true);
return;
}
if (amendPreviousCommitAction.isChecked())
commitOperation.setAmending(true);
final boolean gerritMode = addChangeIdAction.isChecked();
commitOperation.setComputeChangeId(gerritMode);
PushMode pushMode = null;
if (pushUpstream) {
pushMode = gerritMode ? PushMode.GERRIT : PushMode.UPSTREAM;
}
Job commitJob = new CommitJob(currentRepository, commitOperation)
.setOpenCommitEditor(openNewCommitsAction.isChecked())
.setPushUpstream(pushMode);
// don't allow to do anything as long as commit is in progress
enableAllWidgets(false);
commitJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
asyncExec(new Runnable() {
@Override
public void run() {
enableAllWidgets(true);
if (event.getResult().isOK()) {
commitMessageText.setText(EMPTY_STRING);
}
}
});
}
});
schedule(commitJob, true);
CommitMessageHistory.saveCommitHistory(commitMessage);
clearCommitMessageToggles();
}
/**
* Schedule given job in context of the current view. The view will indicate
* progress as long as job is running.
*
* @param job
* non null
* @param useRepositoryRule
* true to use current repository rule for the given job, false
* to not enforce any rule on the job
*/
private void schedule(Job job, boolean useRepositoryRule) {
if (useRepositoryRule)
job.setRule(RuleUtil.getRule(currentRepository));
IWorkbenchSiteProgressService service = CommonUtils.getService(getSite(), IWorkbenchSiteProgressService.class);
if (service != null)
service.schedule(job, 0, true);
else
job.schedule();
}
private boolean isCommitWithoutFilesAllowed() {
if (stagedViewer.getTree().getItemCount() > 0)
return true;
if (amendPreviousCommitAction.isChecked())
return true;
return CommitHelper.isCommitWithoutFilesAllowed(currentRepository);
}
@Override
public void setFocus() {
Tree tree = unstagedViewer.getTree();
if (tree.getItemCount() > 0 && !isAutoStageOnCommitEnabled()) {
unstagedViewer.getControl().setFocus();
return;
}
commitMessageText.setFocus();
}
private boolean isAutoStageOnCommitEnabled() {
IPreferenceStore uiPreferences = Activator.getDefault()
.getPreferenceStore();
return uiPreferences.getBoolean(UIPreferences.AUTO_STAGE_ON_COMMIT);
}
@Override
public void dispose() {
super.dispose();
ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class);
srv.removePostSelectionListener(selectionChangedListener);
CommonUtils.getService(getSite(), IPartService.class)
.removePartListener(partListener);
if (cacheEntry != null) {
cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
}
if (undoRedoActionGroup != null) {
undoRedoActionGroup.dispose();
}
InstanceScope.INSTANCE.getNode(
org.eclipse.egit.core.Activator.getPluginId())
.removePreferenceChangeListener(prefListener);
if (refsChangedListener != null) {
refsChangedListener.remove();
}
getPreferenceStore().removePropertyChangeListener(uiPrefsListener);
getDialogSettings().put(STORE_SORT_STATE, sortAction.isChecked());
currentRepository = null;
lastSelection = null;
disposed = true;
}
private boolean isDisposed() {
return disposed;
}
private static void syncExec(Runnable runnable) {
PlatformUI.getWorkbench().getDisplay().syncExec(runnable);
}
private void asyncExec(Runnable runnable) {
PlatformUI.getWorkbench().getDisplay().asyncExec(runnable);
}
/**
* This comparator sorts the {@link StagingEntry}s alphabetically or groups
* them by state. If grouped by state the entries in the same group are also
* ordered alphabetically.
*/
private static class StagingEntryComparator extends ViewerComparator {
private boolean alphabeticSort;
private Comparator<String> comparator;
private boolean fileNamesFirst;
private StagingEntryComparator(boolean alphabeticSort,
boolean fileNamesFirst) {
this.alphabeticSort = alphabeticSort;
this.setFileNamesFirst(fileNamesFirst);
comparator = CommonUtils.STRING_ASCENDING_COMPARATOR;
}
public boolean isFileNamesFirst() {
return fileNamesFirst;
}
public void setFileNamesFirst(boolean fileNamesFirst) {
this.fileNamesFirst = fileNamesFirst;
}
private void setAlphabeticSort(boolean sort) {
this.alphabeticSort = sort;
}
private boolean isAlphabeticSort() {
return alphabeticSort;
}
@Override
public int category(Object element) {
if (!isAlphabeticSort()) {
StagingEntry stagingEntry = getStagingEntry(element);
if (stagingEntry != null) {
return getState(stagingEntry);
}
}
return super.category(element);
}
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
int cat1 = category(e1);
int cat2 = category(e2);
if (cat1 != cat2) {
return cat1 - cat2;
}
String name1 = getStagingEntryText(e1);
String name2 = getStagingEntryText(e2);
return comparator.compare(name1, name2);
}
private String getStagingEntryText(Object element) {
String text = ""; //$NON-NLS-1$
StagingEntry stagingEntry = getStagingEntry(element);
if (stagingEntry != null) {
if (isFileNamesFirst()) {
text = stagingEntry.getName();
} else {
text = stagingEntry.getPath();
}
}
return text;
}
@Nullable
private StagingEntry getStagingEntry(Object element) {
StagingEntry entry = null;
if (element instanceof StagingEntry) {
entry = (StagingEntry) element;
}
if (element instanceof TreeItem) {
TreeItem item = (TreeItem) element;
if (item.getData() instanceof StagingEntry) {
entry = (StagingEntry) item.getData();
}
}
return entry;
}
private int getState(StagingEntry entry) {
switch (entry.getState()) {
case CONFLICTING:
return 1;
case MODIFIED:
return 2;
case MODIFIED_AND_ADDED:
return 3;
case MODIFIED_AND_CHANGED:
return 4;
case ADDED:
return 5;
case CHANGED:
return 6;
case MISSING:
return 7;
case MISSING_AND_CHANGED:
return 8;
case REMOVED:
return 9;
case UNTRACKED:
return 10;
default:
return super.category(entry);
}
}
}
}