[staging] Support wildcard filter
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / staging / StagingView.java
blob8336a8d14b5714d5a5857815438ddb6a36611d54
1 /*******************************************************************************
2 * Copyright (C) 2011, 2017 Bernard Leach <leachbj@bouncycastle.org> and others.
3 * Copyright (C) 2015 SAP SE (Christian Georgi <christian.georgi@sap.com>)
4 * Copyright (C) 2015 Denis Zygann <d.zygann@web.de>
5 * Copyright (C) 2016 IBM (Daniel Megert <daniel_megert@ch.ibm.com>)
7 * All rights reserved. This program and the accompanying materials
8 * are made available under the terms of the Eclipse Public License v1.0
9 * which accompanies this distribution, and is available at
10 * http://www.eclipse.org/legal/epl-v10.html
12 * Contributors:
13 * Tobias Baumann <tobbaumann@gmail.com> - Bug 373969, 473544
14 * Thomas Wolf <thomas.wolf@paranor.ch>
15 * Tobias Hein <th.mailinglists@googlemail.com> - Bug 499697
16 * Ralf M Petter <ralf.petter@gmail.com> - Bug 509945
17 *******************************************************************************/
18 package org.eclipse.egit.ui.internal.staging;
20 import static org.eclipse.egit.ui.internal.CommonUtils.runCommand;
22 import java.io.File;
23 import java.text.MessageFormat;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.EnumSet;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.LinkedHashMap;
32 import java.util.LinkedHashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.function.Consumer;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 import org.eclipse.core.commands.ExecutionException;
41 import org.eclipse.core.commands.operations.IUndoContext;
42 import org.eclipse.core.resources.IContainer;
43 import org.eclipse.core.resources.IFile;
44 import org.eclipse.core.resources.IMarker;
45 import org.eclipse.core.resources.IResource;
46 import org.eclipse.core.resources.ResourcesPlugin;
47 import org.eclipse.core.runtime.CoreException;
48 import org.eclipse.core.runtime.IPath;
49 import org.eclipse.core.runtime.IProgressMonitor;
50 import org.eclipse.core.runtime.IStatus;
51 import org.eclipse.core.runtime.Path;
52 import org.eclipse.core.runtime.Status;
53 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
54 import org.eclipse.core.runtime.jobs.Job;
55 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
56 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
57 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
58 import org.eclipse.core.runtime.preferences.InstanceScope;
59 import org.eclipse.debug.core.ILaunchConfiguration;
60 import org.eclipse.egit.core.AdapterUtils;
61 import org.eclipse.egit.core.RepositoryUtil;
62 import org.eclipse.egit.core.internal.gerrit.GerritUtil;
63 import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry;
64 import org.eclipse.egit.core.internal.indexdiff.IndexDiffChangedListener;
65 import org.eclipse.egit.core.internal.indexdiff.IndexDiffData;
66 import org.eclipse.egit.core.internal.job.JobUtil;
67 import org.eclipse.egit.core.internal.job.RuleUtil;
68 import org.eclipse.egit.core.op.AssumeUnchangedOperation;
69 import org.eclipse.egit.core.op.CommitOperation;
70 import org.eclipse.egit.core.op.UntrackOperation;
71 import org.eclipse.egit.core.project.RepositoryMapping;
72 import org.eclipse.egit.ui.Activator;
73 import org.eclipse.egit.ui.JobFamilies;
74 import org.eclipse.egit.ui.UIPreferences;
75 import org.eclipse.egit.ui.UIUtils;
76 import org.eclipse.egit.ui.internal.ActionUtils;
77 import org.eclipse.egit.ui.internal.CommonUtils;
78 import org.eclipse.egit.ui.internal.GitLabels;
79 import org.eclipse.egit.ui.internal.UIIcons;
80 import org.eclipse.egit.ui.internal.UIText;
81 import org.eclipse.egit.ui.internal.actions.ActionCommands;
82 import org.eclipse.egit.ui.internal.actions.BooleanPrefAction;
83 import org.eclipse.egit.ui.internal.actions.ReplaceWithOursTheirsMenu;
84 import org.eclipse.egit.ui.internal.branch.LaunchFinder;
85 import org.eclipse.egit.ui.internal.commands.shared.AbortRebaseCommand;
86 import org.eclipse.egit.ui.internal.commands.shared.AbstractRebaseCommandHandler;
87 import org.eclipse.egit.ui.internal.commands.shared.ContinueRebaseCommand;
88 import org.eclipse.egit.ui.internal.commands.shared.SkipRebaseCommand;
89 import org.eclipse.egit.ui.internal.commit.CommitHelper;
90 import org.eclipse.egit.ui.internal.commit.CommitJob;
91 import org.eclipse.egit.ui.internal.commit.CommitMessageHistory;
92 import org.eclipse.egit.ui.internal.commit.CommitProposalProcessor;
93 import org.eclipse.egit.ui.internal.commit.DiffViewer;
94 import org.eclipse.egit.ui.internal.components.RepositoryMenuUtil.RepositoryToolbarAction;
95 import org.eclipse.egit.ui.internal.components.ToggleableWarningLabel;
96 import org.eclipse.egit.ui.internal.decorators.IProblemDecoratable;
97 import org.eclipse.egit.ui.internal.decorators.ProblemLabelDecorator;
98 import org.eclipse.egit.ui.internal.dialogs.CommitMessageArea;
99 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponent;
100 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentState;
101 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentStateManager;
102 import org.eclipse.egit.ui.internal.dialogs.ICommitMessageComponentNotifications;
103 import org.eclipse.egit.ui.internal.dialogs.SpellcheckableMessageArea;
104 import org.eclipse.egit.ui.internal.operations.DeletePathsOperationUI;
105 import org.eclipse.egit.ui.internal.operations.IgnoreOperationUI;
106 import org.eclipse.egit.ui.internal.push.PushMode;
107 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
108 import org.eclipse.egit.ui.internal.selection.MultiViewerSelectionProvider;
109 import org.eclipse.egit.ui.internal.selection.RepositorySelectionProvider;
110 import org.eclipse.jface.action.Action;
111 import org.eclipse.jface.action.ControlContribution;
112 import org.eclipse.jface.action.IAction;
113 import org.eclipse.jface.action.IContributionItem;
114 import org.eclipse.jface.action.IMenuListener;
115 import org.eclipse.jface.action.IMenuManager;
116 import org.eclipse.jface.action.IToolBarManager;
117 import org.eclipse.jface.action.MenuManager;
118 import org.eclipse.jface.action.Separator;
119 import org.eclipse.jface.action.ToolBarManager;
120 import org.eclipse.jface.dialogs.DialogSettings;
121 import org.eclipse.jface.dialogs.IDialogConstants;
122 import org.eclipse.jface.dialogs.IDialogSettings;
123 import org.eclipse.jface.dialogs.MessageDialog;
124 import org.eclipse.jface.layout.GridDataFactory;
125 import org.eclipse.jface.layout.GridLayoutFactory;
126 import org.eclipse.jface.preference.IPersistentPreferenceStore;
127 import org.eclipse.jface.preference.IPreferenceStore;
128 import org.eclipse.jface.resource.ImageDescriptor;
129 import org.eclipse.jface.resource.JFaceResources;
130 import org.eclipse.jface.resource.LocalResourceManager;
131 import org.eclipse.jface.util.IPropertyChangeListener;
132 import org.eclipse.jface.util.LocalSelectionTransfer;
133 import org.eclipse.jface.util.PropertyChangeEvent;
134 import org.eclipse.jface.viewers.AbstractTreeViewer;
135 import org.eclipse.jface.viewers.BaseLabelProvider;
136 import org.eclipse.jface.viewers.ContentViewer;
137 import org.eclipse.jface.viewers.DecoratingLabelProvider;
138 import org.eclipse.jface.viewers.IBaseLabelProvider;
139 import org.eclipse.jface.viewers.ILabelDecorator;
140 import org.eclipse.jface.viewers.ILabelProvider;
141 import org.eclipse.jface.viewers.ILabelProviderListener;
142 import org.eclipse.jface.viewers.ISelection;
143 import org.eclipse.jface.viewers.ISelectionProvider;
144 import org.eclipse.jface.viewers.IStructuredSelection;
145 import org.eclipse.jface.viewers.ITreeViewerListener;
146 import org.eclipse.jface.viewers.OpenEvent;
147 import org.eclipse.jface.viewers.StructuredSelection;
148 import org.eclipse.jface.viewers.TreeExpansionEvent;
149 import org.eclipse.jface.viewers.TreeViewer;
150 import org.eclipse.jface.viewers.Viewer;
151 import org.eclipse.jface.viewers.ViewerComparator;
152 import org.eclipse.jface.viewers.ViewerFilter;
153 import org.eclipse.jface.window.Window;
154 import org.eclipse.jgit.annotations.NonNull;
155 import org.eclipse.jgit.annotations.Nullable;
156 import org.eclipse.jgit.api.AddCommand;
157 import org.eclipse.jgit.api.CheckoutCommand;
158 import org.eclipse.jgit.api.Git;
159 import org.eclipse.jgit.api.ResetCommand;
160 import org.eclipse.jgit.api.RmCommand;
161 import org.eclipse.jgit.api.errors.GitAPIException;
162 import org.eclipse.jgit.api.errors.JGitInternalException;
163 import org.eclipse.jgit.api.errors.NoFilepatternException;
164 import org.eclipse.jgit.events.ListenerHandle;
165 import org.eclipse.jgit.lib.Constants;
166 import org.eclipse.jgit.lib.ObjectId;
167 import org.eclipse.jgit.lib.Repository;
168 import org.eclipse.jgit.lib.RepositoryState;
169 import org.eclipse.jgit.revwalk.RevCommit;
170 import org.eclipse.swt.SWT;
171 import org.eclipse.swt.custom.SashForm;
172 import org.eclipse.swt.custom.VerifyKeyListener;
173 import org.eclipse.swt.dnd.Clipboard;
174 import org.eclipse.swt.dnd.DND;
175 import org.eclipse.swt.dnd.DragSourceAdapter;
176 import org.eclipse.swt.dnd.DragSourceEvent;
177 import org.eclipse.swt.dnd.DropTargetAdapter;
178 import org.eclipse.swt.dnd.DropTargetEvent;
179 import org.eclipse.swt.dnd.FileTransfer;
180 import org.eclipse.swt.dnd.TextTransfer;
181 import org.eclipse.swt.dnd.Transfer;
182 import org.eclipse.swt.events.ControlEvent;
183 import org.eclipse.swt.events.ControlListener;
184 import org.eclipse.swt.events.DisposeEvent;
185 import org.eclipse.swt.events.DisposeListener;
186 import org.eclipse.swt.events.FocusEvent;
187 import org.eclipse.swt.events.FocusListener;
188 import org.eclipse.swt.events.ModifyEvent;
189 import org.eclipse.swt.events.ModifyListener;
190 import org.eclipse.swt.events.SelectionAdapter;
191 import org.eclipse.swt.events.SelectionEvent;
192 import org.eclipse.swt.events.VerifyEvent;
193 import org.eclipse.swt.graphics.Cursor;
194 import org.eclipse.swt.graphics.Image;
195 import org.eclipse.swt.graphics.Point;
196 import org.eclipse.swt.layout.GridData;
197 import org.eclipse.swt.layout.GridLayout;
198 import org.eclipse.swt.layout.RowLayout;
199 import org.eclipse.swt.widgets.Button;
200 import org.eclipse.swt.widgets.Composite;
201 import org.eclipse.swt.widgets.Control;
202 import org.eclipse.swt.widgets.Display;
203 import org.eclipse.swt.widgets.Label;
204 import org.eclipse.swt.widgets.Text;
205 import org.eclipse.swt.widgets.Tree;
206 import org.eclipse.swt.widgets.TreeItem;
207 import org.eclipse.ui.IActionBars;
208 import org.eclipse.ui.IEditorInput;
209 import org.eclipse.ui.IEditorPart;
210 import org.eclipse.ui.IFileEditorInput;
211 import org.eclipse.ui.IMemento;
212 import org.eclipse.ui.IPartListener2;
213 import org.eclipse.ui.IPartService;
214 import org.eclipse.ui.ISelectionListener;
215 import org.eclipse.ui.ISelectionService;
216 import org.eclipse.ui.IURIEditorInput;
217 import org.eclipse.ui.IViewSite;
218 import org.eclipse.ui.IWorkbenchPart;
219 import org.eclipse.ui.IWorkbenchPartReference;
220 import org.eclipse.ui.IWorkbenchPartSite;
221 import org.eclipse.ui.IWorkbenchWindow;
222 import org.eclipse.ui.PartInitException;
223 import org.eclipse.ui.PlatformUI;
224 import org.eclipse.ui.actions.ActionFactory;
225 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
226 import org.eclipse.ui.forms.IFormColors;
227 import org.eclipse.ui.forms.widgets.ExpandableComposite;
228 import org.eclipse.ui.forms.widgets.Form;
229 import org.eclipse.ui.forms.widgets.FormToolkit;
230 import org.eclipse.ui.forms.widgets.Section;
231 import org.eclipse.ui.handlers.IHandlerService;
232 import org.eclipse.ui.operations.UndoRedoActionGroup;
233 import org.eclipse.ui.part.IShowInSource;
234 import org.eclipse.ui.part.IShowInTarget;
235 import org.eclipse.ui.part.ShowInContext;
236 import org.eclipse.ui.part.ViewPart;
237 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
238 import org.eclipse.ui.progress.WorkbenchJob;
241 * A GitX style staging view with embedded commit dialog.
243 public class StagingView extends ViewPart
244 implements IShowInSource, IShowInTarget {
247 * Staging view id
249 public static final String VIEW_ID = "org.eclipse.egit.ui.StagingView"; //$NON-NLS-1$
251 private static final String EMPTY_STRING = ""; //$NON-NLS-1$
253 private static final String SORT_ITEM_TOOLBAR_ID = "sortItem"; //$NON-NLS-1$
255 private static final String EXPAND_ALL_ITEM_TOOLBAR_ID = "expandAllItem"; //$NON-NLS-1$
257 private static final String COLLAPSE_ALL_ITEM_TOOLBAR_ID = "collapseAllItem"; //$NON-NLS-1$
259 private static final String STORE_SORT_STATE = SORT_ITEM_TOOLBAR_ID
260 + "State"; //$NON-NLS-1$
262 private static final String HORIZONTAL_SASH_FORM_WEIGHT = "HORIZONTAL_SASH_FORM_WEIGHT"; //$NON-NLS-1$
264 private static final String STAGING_SASH_FORM_WEIGHT = "STAGING_SASH_FORM_WEIGHT"; //$NON-NLS-1$
266 private ISelection initialSelection;
268 private FormToolkit toolkit;
270 private Form form;
272 private SashForm mainSashForm;
274 private Section stagedSection;
276 private Section unstagedSection;
278 private Section commitMessageSection;
280 private TreeViewer stagedViewer;
282 private TreeViewer unstagedViewer;
284 private ToggleableWarningLabel warningLabel;
286 private Text filterText;
288 /** Remember compiled pattern of the current filter string for performance. */
289 private Pattern filterPattern;
291 private SpellcheckableMessageArea commitMessageText;
293 private Text committerText;
295 private Text authorText;
297 private CommitMessageComponent commitMessageComponent;
299 private boolean reactOnSelection = true;
301 private boolean isViewHidden;
303 /** Tracks the last selection while the view is not active. */
304 private StructuredSelection lastSelection;
306 private ISelectionListener selectionChangedListener;
308 private IPartListener2 partListener;
310 private ToolBarManager unstagedToolBarManager;
312 private ToolBarManager stagedToolBarManager;
314 private IAction listPresentationAction;
316 private IAction treePresentationAction;
318 private IAction compactTreePresentationAction;
320 private IAction unstagedExpandAllAction;
322 private IAction unstagedCollapseAllAction;
324 private IAction stagedExpandAllAction;
326 private IAction stagedCollapseAllAction;
328 private IAction unstageAction;
330 private IAction stageAction;
332 private IAction unstageAllAction;
334 private IAction stageAllAction;
336 private IAction compareModeAction;
338 private IWorkbenchAction switchRepositoriesAction;
340 /** The currently set repository, even if it is bare. */
341 private Repository realRepository;
343 /** The currently set repository, if it's not a bare repository. */
344 @Nullable
345 private Repository currentRepository;
347 private Presentation presentation = Presentation.LIST;
349 private Set<IPath> pathsToExpandInStaged = new HashSet<>();
351 private Set<IPath> pathsToExpandInUnstaged = new HashSet<>();
354 * Presentation mode of the staged/unstaged files.
356 public enum Presentation {
357 /** Show files in flat list */
358 LIST,
359 /** Show folder structure in full tree */
360 TREE,
362 * Show folder structure in compact tree (folders with only one child
363 * are folded into parent)
365 COMPACT_TREE;
368 static class StagingViewUpdate {
369 Repository repository;
370 IndexDiffData indexDiff;
371 Collection<String> changedResources;
373 StagingViewUpdate(Repository theRepository,
374 IndexDiffData theIndexDiff, Collection<String> theChanges) {
375 this.repository = theRepository;
376 this.indexDiff = theIndexDiff;
377 this.changedResources = theChanges;
381 private static class StagingDragSelection implements IStructuredSelection {
383 private final IStructuredSelection delegate;
385 private final boolean fromUnstaged;
387 public StagingDragSelection(IStructuredSelection original,
388 boolean fromUnstaged) {
389 this.delegate = original;
390 this.fromUnstaged = fromUnstaged;
393 @Override
394 public boolean isEmpty() {
395 return delegate.isEmpty();
398 @Override
399 public Object getFirstElement() {
400 return delegate.getFirstElement();
403 @Override
404 public Iterator iterator() {
405 return delegate.iterator();
408 @Override
409 public int size() {
410 return delegate.size();
413 @Override
414 public Object[] toArray() {
415 return delegate.toArray();
418 @Override
419 public List toList() {
420 return delegate.toList();
423 public boolean isFromUnstaged() {
424 return fromUnstaged;
428 private static class StagingDragListener extends DragSourceAdapter {
430 private final ISelectionProvider provider;
432 private final StagingViewContentProvider contentProvider;
434 private final boolean unstaged;
436 public StagingDragListener(ISelectionProvider provider,
437 StagingViewContentProvider contentProvider, boolean unstaged) {
438 this.provider = provider;
439 this.contentProvider = contentProvider;
440 this.unstaged = unstaged;
443 @Override
444 public void dragStart(DragSourceEvent event) {
445 event.doit = !provider.getSelection().isEmpty();
448 @Override
449 public void dragFinished(DragSourceEvent event) {
450 if (LocalSelectionTransfer.getTransfer().isSupportedType(
451 event.dataType)) {
452 LocalSelectionTransfer.getTransfer().setSelection(null);
456 @Override
457 public void dragSetData(DragSourceEvent event) {
458 IStructuredSelection selection = (IStructuredSelection) provider
459 .getSelection();
460 if (selection.isEmpty()) {
461 // Should never happen as per dragStart()
462 return;
464 if (LocalSelectionTransfer.getTransfer().isSupportedType(
465 event.dataType)) {
466 LocalSelectionTransfer.getTransfer().setSelection(
467 new StagingDragSelection(selection, unstaged));
468 return;
471 if (FileTransfer.getInstance().isSupportedType(event.dataType)) {
472 Set<String> files = new HashSet<>();
473 for (Object selected : selection.toList())
474 if (selected instanceof StagingEntry) {
475 add((StagingEntry) selected, files);
476 } else if (selected instanceof StagingFolderEntry) {
477 // Only add the files, otherwise much more than intended
478 // might be copied or moved. The user selected a staged
479 // or unstaged folder, so only the staged or unstaged
480 // files inside that folder should be included, not
481 // everything.
482 StagingFolderEntry folder = (StagingFolderEntry) selected;
483 for (StagingEntry entry : contentProvider
484 .getStagingEntriesFiltered(folder)) {
485 add(entry, files);
488 if (!files.isEmpty()) {
489 event.data = files.toArray(new String[files.size()]);
490 return;
492 // We may still end up with an empty list here if the selection
493 // contained only deleted files. In that case, the drag&drop
494 // will log an SWTException: Data does not have correct format
495 // for type. Note that GTK sometimes creates the FileTransfer
496 // up front even though a drag between our own viewers would
497 // need only the LocalSelectionTransfer. Drag&drop between our
498 // viewers still works (also on GTK) even if the creation of
499 // the FileTransfer fails.
503 private void add(StagingEntry entry, Collection<String> files) {
504 File file = entry.getLocation().toFile();
505 if (file.exists()) {
506 files.add(file.getAbsolutePath());
511 private final class PartListener implements IPartListener2 {
513 @Override
514 public void partVisible(IWorkbenchPartReference partRef) {
515 updateHiddenState(partRef, false);
518 @Override
519 public void partOpened(IWorkbenchPartReference partRef) {
520 updateHiddenState(partRef, false);
523 @Override
524 public void partHidden(IWorkbenchPartReference partRef) {
525 updateHiddenState(partRef, true);
528 @Override
529 public void partClosed(IWorkbenchPartReference partRef) {
530 updateHiddenState(partRef, true);
533 @Override
534 public void partActivated(IWorkbenchPartReference partRef) {
535 if (isMe(partRef)) {
536 if (lastSelection != null) {
537 // view activated: synchronize with last active part
538 // selection
539 reactOnSelection(lastSelection);
540 lastSelection = null;
542 return;
544 IWorkbenchPart part = partRef.getPart(false);
545 StructuredSelection sel = getSelectionOfPart(part);
546 if (isViewHidden) {
547 // remember last selection in the part so that we can
548 // synchronize on it as soon as we will be visible
549 lastSelection = sel;
550 } else {
551 lastSelection = null;
552 if (sel != null) {
553 reactOnSelection(sel);
559 private void updateHiddenState(IWorkbenchPartReference partRef,
560 boolean hidden) {
561 if (isMe(partRef)) {
562 isViewHidden = hidden;
566 private boolean isMe(IWorkbenchPartReference partRef) {
567 return partRef.getPart(false) == StagingView.this;
570 @Override
571 public void partDeactivated(IWorkbenchPartReference partRef) {
575 @Override
576 public void partBroughtToTop(IWorkbenchPartReference partRef) {
580 @Override
581 public void partInputChanged(IWorkbenchPartReference partRef) {
587 * A wrapped {@link DecoratingLabelProvider} to be used in the tree viewers
588 * of the staging view. We wrap it instead of deriving directly because a
589 * {@link DecoratingLabelProvider} is a
590 * {@link org.eclipse.jface.viewers.ITreePathLabelProvider
591 * ITreePathLabelProvider}, which makes the tree viewer compute a
592 * {@link org.eclipse.jface.viewers.TreePath TreePath} for each element,
593 * which is then ultimately unused because the
594 * {@link StagingViewLabelProvider} is <em>not</em> a
595 * {@link org.eclipse.jface.viewers.ITreePathLabelProvider
596 * ITreePathLabelProvider}. Computing the
597 * {@link org.eclipse.jface.viewers.TreePath TreePath} is a fairly expensive
598 * operation on GTK, and avoiding to compute it speeds up label updates
599 * significantly.
601 private static class TreeDecoratingLabelProvider extends BaseLabelProvider
602 implements ILabelProvider {
604 private final DecoratingLabelProvider provider;
606 public TreeDecoratingLabelProvider(ILabelProvider provider,
607 ILabelDecorator decorator) {
608 this.provider = new DecoratingLabelProvider(provider, decorator);
611 @Override
612 public Image getImage(Object element) {
613 return provider.getImage(element);
616 @Override
617 public String getText(Object element) {
618 return provider.getText(element);
621 @Override
622 public void addListener(ILabelProviderListener listener) {
623 provider.addListener(listener);
626 @Override
627 public void removeListener(ILabelProviderListener listener) {
628 provider.removeListener(listener);
631 @Override
632 public void dispose() {
633 provider.dispose();
636 public ILabelProvider getLabelProvider() {
637 return provider.getLabelProvider();
642 static class StagingViewSearchThread extends Thread {
643 private StagingView stagingView;
645 private static final Object lock = new Object();
647 private volatile static int globalThreadIndex = 0;
649 private int currentThreadIx;
651 public StagingViewSearchThread(StagingView stagingView) {
652 super("staging_view_filter_thread" + ++globalThreadIndex); //$NON-NLS-1$
653 this.stagingView = stagingView;
654 currentThreadIx = globalThreadIndex;
657 @Override
658 public void run() {
659 synchronized (lock) {
660 if (currentThreadIx < globalThreadIndex)
661 return;
662 stagingView.refreshViewersPreservingExpandedElements();
668 private final IPreferenceChangeListener prefListener = new IPreferenceChangeListener() {
670 @Override
671 public void preferenceChange(PreferenceChangeEvent event) {
672 if (!RepositoryUtil.PREFS_DIRECTORIES_REL.equals(event.getKey())) {
673 return;
675 final Repository repo = currentRepository;
676 if (repo == null || Activator.getDefault().getRepositoryUtil()
677 .contains(repo)) {
678 return;
680 reload(null);
685 private final IPropertyChangeListener uiPrefsListener = new IPropertyChangeListener() {
686 @Override
687 public void propertyChange(PropertyChangeEvent event) {
688 if (UIPreferences.COMMIT_DIALOG_WARN_ABOUT_MESSAGE_SECOND_LINE
689 .equals(event.getProperty())) {
690 asyncExec(() -> {
691 if (!commitMessageSection.isDisposed()) {
692 updateMessage();
699 private Action signedOffByAction;
701 private Action addChangeIdAction;
703 private Action amendPreviousCommitAction;
705 private Action openNewCommitsAction;
707 private Action columnLayoutAction;
709 private Action fileNameModeAction;
711 private Action refreshAction;
713 private Action sortAction;
715 private SashForm stagingSashForm;
717 private IndexDiffChangedListener myIndexDiffListener = new IndexDiffChangedListener() {
718 @Override
719 public void indexDiffChanged(Repository repository,
720 IndexDiffData indexDiffData) {
721 reload(repository);
725 private IndexDiffCacheEntry cacheEntry;
727 private UndoRedoActionGroup undoRedoActionGroup;
729 private Button commitButton;
731 private Button commitAndPushButton;
733 private Section rebaseSection;
735 private Button rebaseContinueButton;
737 private Button rebaseSkipButton;
739 private Button rebaseAbortButton;
741 private Button ignoreErrors;
743 private ListenerHandle refsChangedListener;
745 private ListenerHandle configChangedListener;
747 private LocalResourceManager resources = new LocalResourceManager(
748 JFaceResources.getResources());
750 private boolean disposed;
752 private Image getImage(ImageDescriptor descriptor) {
753 return (Image) this.resources.get(descriptor);
756 @Override
757 public void init(IViewSite site, IMemento viewMemento)
758 throws PartInitException {
759 super.init(site, viewMemento);
760 this.initialSelection = site.getWorkbenchWindow().getSelectionService()
761 .getSelection();
764 @Override
765 public void createPartControl(final Composite parent) {
766 GridLayoutFactory.fillDefaults().applyTo(parent);
768 toolkit = new FormToolkit(parent.getDisplay());
769 parent.addDisposeListener(new DisposeListener() {
771 @Override
772 public void widgetDisposed(DisposeEvent e) {
773 if (commitMessageComponent.isAmending()
774 || userEnteredCommitMessage())
775 saveCommitMessageComponentState();
776 else
777 deleteCommitMessageComponentState();
778 resources.dispose();
779 toolkit.dispose();
783 form = toolkit.createForm(parent);
784 parent.addControlListener(new ControlListener() {
786 private int[] defaultWeights = { 1, 1 };
788 @Override
789 public void controlResized(ControlEvent e) {
790 org.eclipse.swt.graphics.Rectangle b = parent.getBounds();
791 int oldOrientation = mainSashForm.getOrientation();
792 if ((oldOrientation == SWT.HORIZONTAL)
793 && (b.height > b.width)) {
794 mainSashForm.setOrientation(SWT.VERTICAL);
795 mainSashForm.setWeights(defaultWeights);
796 } else if ((oldOrientation == SWT.VERTICAL)
797 && (b.height <= b.width)) {
798 mainSashForm.setOrientation(SWT.HORIZONTAL);
799 mainSashForm.setWeights(defaultWeights);
803 @Override
804 public void controlMoved(ControlEvent e) {
805 // ignore
808 form.setImage(getImage(UIIcons.REPOSITORY));
809 form.setText(UIText.StagingView_NoSelectionTitle);
810 GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
811 toolkit.decorateFormHeading(form);
812 GridLayoutFactory.swtDefaults().applyTo(form.getBody());
814 mainSashForm = new SashForm(form.getBody(), SWT.HORIZONTAL);
815 saveSashFormWeightsOnDisposal(mainSashForm,
816 HORIZONTAL_SASH_FORM_WEIGHT);
817 toolkit.adapt(mainSashForm, true, true);
818 GridDataFactory.fillDefaults().grab(true, true)
819 .applyTo(mainSashForm);
821 stagingSashForm = new SashForm(mainSashForm,
822 getStagingFormOrientation());
823 saveSashFormWeightsOnDisposal(stagingSashForm,
824 STAGING_SASH_FORM_WEIGHT);
825 toolkit.adapt(stagingSashForm, true, true);
826 GridDataFactory.fillDefaults().grab(true, true)
827 .applyTo(stagingSashForm);
829 unstageAction = new Action(UIText.StagingView_UnstageItemMenuLabel,
830 UIIcons.UNSTAGE) {
831 @Override
832 public void run() {
833 unstage((IStructuredSelection) stagedViewer.getSelection());
836 unstageAction.setToolTipText(UIText.StagingView_UnstageItemTooltip);
837 stageAction = new Action(UIText.StagingView_StageItemMenuLabel,
838 UIIcons.ELCL16_ADD) {
839 @Override
840 public void run() {
841 stage((IStructuredSelection) unstagedViewer.getSelection());
844 stageAction.setToolTipText(UIText.StagingView_StageItemTooltip);
846 unstageAction.setEnabled(false);
847 stageAction.setEnabled(false);
849 unstageAllAction = new Action(
850 UIText.StagingView_UnstageAllItemMenuLabel,
851 UIIcons.UNSTAGE_ALL) {
852 @Override
853 public void run() {
854 stagedViewer.getTree().selectAll();
855 unstage((IStructuredSelection) stagedViewer.getSelection());
858 unstageAllAction
859 .setToolTipText(UIText.StagingView_UnstageAllItemTooltip);
860 stageAllAction = new Action(UIText.StagingView_StageAllItemMenuLabel,
861 UIIcons.ELCL16_ADD_ALL) {
862 @Override
863 public void run() {
864 unstagedViewer.getTree().selectAll();
865 stage((IStructuredSelection) unstagedViewer.getSelection());
868 stageAllAction.setToolTipText(UIText.StagingView_StageAllItemTooltip);
870 unstageAllAction.setEnabled(false);
871 stageAllAction.setEnabled(false);
873 unstagedSection = toolkit.createSection(stagingSashForm,
874 ExpandableComposite.SHORT_TITLE_BAR);
875 unstagedSection.clientVerticalSpacing = 0;
877 unstagedSection.setLayoutData(
878 GridDataFactory.fillDefaults().grab(true, true).create());
880 createUnstagedToolBarComposite();
882 Composite unstagedComposite = toolkit.createComposite(unstagedSection);
883 toolkit.paintBordersFor(unstagedComposite);
884 unstagedSection.setClient(unstagedComposite);
885 GridLayoutFactory.fillDefaults().applyTo(unstagedComposite);
887 unstagedViewer = createViewer(unstagedComposite, true,
888 selection -> unstage(selection), stageAction);
890 unstagedViewer.addSelectionChangedListener(event -> {
891 boolean hasSelection = !event.getSelection().isEmpty();
892 if (hasSelection != stageAction.isEnabled()) {
893 stageAction.setEnabled(hasSelection);
894 unstagedToolBarManager.update(true);
897 Composite rebaseAndCommitComposite = toolkit.createComposite(mainSashForm);
898 rebaseAndCommitComposite.setLayout(GridLayoutFactory.fillDefaults().create());
900 rebaseSection = toolkit.createSection(rebaseAndCommitComposite,
901 ExpandableComposite.SHORT_TITLE_BAR);
902 rebaseSection.clientVerticalSpacing = 0;
903 rebaseSection.setText(UIText.StagingView_RebaseLabel);
905 Composite rebaseComposite = toolkit.createComposite(rebaseSection);
906 toolkit.paintBordersFor(rebaseComposite);
907 rebaseSection.setClient(rebaseComposite);
909 rebaseSection.setLayoutData(GridDataFactory.fillDefaults().create());
910 rebaseComposite.setLayout(GridLayoutFactory.fillDefaults()
911 .numColumns(3).equalWidth(true).create());
912 GridDataFactory buttonGridData = GridDataFactory.fillDefaults().align(
913 SWT.FILL, SWT.CENTER);
915 this.rebaseAbortButton = toolkit.createButton(rebaseComposite,
916 UIText.StagingView_RebaseAbort, SWT.PUSH);
917 rebaseAbortButton.addSelectionListener(new SelectionAdapter() {
918 @Override
919 public void widgetSelected(SelectionEvent e) {
920 rebaseAbort();
923 rebaseAbortButton.setImage(getImage(UIIcons.REBASE_ABORT));
924 buttonGridData.applyTo(rebaseAbortButton);
926 this.rebaseSkipButton = toolkit.createButton(rebaseComposite,
927 UIText.StagingView_RebaseSkip, SWT.PUSH);
928 rebaseSkipButton.addSelectionListener(new SelectionAdapter() {
929 @Override
930 public void widgetSelected(SelectionEvent e) {
931 rebaseSkip();
934 rebaseSkipButton.setImage(getImage(UIIcons.REBASE_SKIP));
935 buttonGridData.applyTo(rebaseSkipButton);
937 this.rebaseContinueButton = toolkit.createButton(rebaseComposite,
938 UIText.StagingView_RebaseContinue, SWT.PUSH);
939 rebaseContinueButton.addSelectionListener(new SelectionAdapter() {
940 @Override
941 public void widgetSelected(SelectionEvent e) {
942 rebaseContinue();
945 rebaseContinueButton.setImage(getImage(UIIcons.REBASE_CONTINUE));
946 buttonGridData.applyTo(rebaseContinueButton);
948 showControl(rebaseSection, false);
950 commitMessageSection = toolkit.createSection(rebaseAndCommitComposite,
951 ExpandableComposite.SHORT_TITLE_BAR);
952 commitMessageSection.clientVerticalSpacing = 0;
953 commitMessageSection.setText(UIText.StagingView_CommitMessage);
954 commitMessageSection.setLayoutData(GridDataFactory.fillDefaults()
955 .grab(true, true).create());
957 Composite commitMessageToolbarComposite = toolkit
958 .createComposite(commitMessageSection);
959 commitMessageToolbarComposite.setBackground(null);
960 commitMessageToolbarComposite.setLayout(createRowLayoutWithoutMargin());
961 commitMessageSection.setTextClient(commitMessageToolbarComposite);
962 ToolBarManager commitMessageToolBarManager = new ToolBarManager(
963 SWT.FLAT | SWT.HORIZONTAL);
965 amendPreviousCommitAction = new Action(
966 UIText.StagingView_Ammend_Previous_Commit, IAction.AS_CHECK_BOX) {
968 @Override
969 public void run() {
970 commitMessageComponent.setAmendingButtonSelection(isChecked());
971 updateMessage();
974 amendPreviousCommitAction.setImageDescriptor(UIIcons.AMEND_COMMIT);
975 commitMessageToolBarManager.add(amendPreviousCommitAction);
977 signedOffByAction = new Action(UIText.StagingView_Add_Signed_Off_By,
978 IAction.AS_CHECK_BOX) {
980 @Override
981 public void run() {
982 commitMessageComponent.setSignedOffButtonSelection(isChecked());
985 signedOffByAction.setImageDescriptor(UIIcons.SIGNED_OFF);
986 commitMessageToolBarManager.add(signedOffByAction);
988 addChangeIdAction = new Action(UIText.StagingView_Add_Change_ID,
989 IAction.AS_CHECK_BOX) {
991 @Override
992 public void run() {
993 commitMessageComponent.setChangeIdButtonSelection(isChecked());
996 addChangeIdAction.setImageDescriptor(UIIcons.GERRIT);
997 commitMessageToolBarManager.add(addChangeIdAction);
999 commitMessageToolBarManager
1000 .createControl(commitMessageToolbarComposite);
1002 Composite commitMessageComposite = toolkit
1003 .createComposite(commitMessageSection);
1004 commitMessageSection.setClient(commitMessageComposite);
1005 GridLayoutFactory.fillDefaults().numColumns(1)
1006 .applyTo(commitMessageComposite);
1008 warningLabel = new ToggleableWarningLabel(commitMessageComposite,
1009 SWT.NONE);
1010 GridDataFactory.fillDefaults().grab(true, false).exclude(true)
1011 .applyTo(warningLabel);
1013 Composite commitMessageTextComposite = toolkit
1014 .createComposite(commitMessageComposite);
1015 toolkit.paintBordersFor(commitMessageTextComposite);
1016 GridDataFactory.fillDefaults().grab(true, true)
1017 .applyTo(commitMessageTextComposite);
1018 GridLayoutFactory.fillDefaults().numColumns(1)
1019 .extendedMargins(2, 2, 2, 2)
1020 .applyTo(commitMessageTextComposite);
1022 final CommitProposalProcessor commitProposalProcessor = new CommitProposalProcessor() {
1023 @Override
1024 protected Collection<String> computeFileNameProposals() {
1025 return getStagedFileNames();
1028 @Override
1029 protected Collection<String> computeMessageProposals() {
1030 return CommitMessageHistory.getCommitHistory();
1033 commitMessageText = new CommitMessageArea(commitMessageTextComposite,
1034 EMPTY_STRING, SWT.NONE) {
1035 @Override
1036 protected CommitProposalProcessor getCommitProposalProcessor() {
1037 return commitProposalProcessor;
1039 @Override
1040 protected IHandlerService getHandlerService() {
1041 return CommonUtils.getService(getSite(), IHandlerService.class);
1044 commitMessageText.setData(FormToolkit.KEY_DRAW_BORDER,
1045 FormToolkit.TEXT_BORDER);
1046 GridDataFactory.fillDefaults().grab(true, true)
1047 .applyTo(commitMessageText);
1048 UIUtils.addBulbDecorator(commitMessageText.getTextWidget(),
1049 UIText.CommitDialog_ContentAssist);
1051 Composite composite = toolkit.createComposite(commitMessageComposite);
1052 toolkit.paintBordersFor(composite);
1053 GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
1054 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(composite);
1056 toolkit.createLabel(composite, UIText.StagingView_Author)
1057 .setForeground(
1058 toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
1059 authorText = toolkit.createText(composite, null);
1060 authorText
1061 .setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
1062 authorText.setLayoutData(GridDataFactory.fillDefaults()
1063 .grab(true, false).create());
1065 toolkit.createLabel(composite, UIText.StagingView_Committer)
1066 .setForeground(
1067 toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
1068 committerText = toolkit.createText(composite, null);
1069 committerText.setData(FormToolkit.KEY_DRAW_BORDER,
1070 FormToolkit.TEXT_BORDER);
1071 committerText.setLayoutData(GridDataFactory.fillDefaults()
1072 .grab(true, false).create());
1074 Composite buttonsContainer = toolkit.createComposite(composite);
1075 GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
1076 .indent(0, 8).applyTo(buttonsContainer);
1077 GridLayoutFactory.fillDefaults().numColumns(2)
1078 .applyTo(buttonsContainer);
1080 ignoreErrors = toolkit.createButton(buttonsContainer,
1081 UIText.StagingView_IgnoreErrors, SWT.CHECK);
1082 ignoreErrors.setSelection(false);
1083 ignoreErrors.addSelectionListener(new SelectionAdapter() {
1084 @Override
1085 public void widgetSelected(SelectionEvent e) {
1086 updateMessage();
1087 updateCommitButtons();
1090 getPreferenceStore()
1091 .addPropertyChangeListener(new IPropertyChangeListener() {
1092 @Override
1093 public void propertyChange(PropertyChangeEvent event) {
1094 if (isDisposed()) {
1095 getPreferenceStore()
1096 .removePropertyChangeListener(this);
1097 return;
1099 asyncExec(() -> {
1100 updateIgnoreErrorsButtonVisibility();
1101 updateMessage();
1102 updateCommitButtons();
1107 GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING)
1108 .grab(true, true).applyTo(ignoreErrors);
1109 updateIgnoreErrorsButtonVisibility();
1111 Label filler = toolkit.createLabel(buttonsContainer, ""); //$NON-NLS-1$
1112 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
1113 .grab(true, true).applyTo(filler);
1115 Composite commitButtonsContainer = toolkit
1116 .createComposite(buttonsContainer);
1117 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
1118 .applyTo(commitButtonsContainer);
1119 GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true)
1120 .applyTo(commitButtonsContainer);
1123 this.commitAndPushButton = toolkit.createButton(commitButtonsContainer,
1124 UIText.StagingView_CommitAndPush, SWT.PUSH);
1125 commitAndPushButton.setImage(getImage(UIIcons.PUSH));
1126 commitAndPushButton.addSelectionListener(new SelectionAdapter() {
1127 @Override
1128 public void widgetSelected(SelectionEvent e) {
1129 commit(true);
1132 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
1133 .applyTo(commitAndPushButton);
1135 this.commitButton = toolkit.createButton(commitButtonsContainer,
1136 UIText.StagingView_Commit, SWT.PUSH);
1137 commitButton.setImage(getImage(UIIcons.COMMIT));
1138 commitButton.setText(UIText.StagingView_Commit);
1139 commitButton.addSelectionListener(new SelectionAdapter() {
1140 @Override
1141 public void widgetSelected(SelectionEvent e) {
1142 commit(false);
1145 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
1146 .applyTo(commitButton);
1148 stagedSection = toolkit.createSection(stagingSashForm,
1149 ExpandableComposite.SHORT_TITLE_BAR);
1150 stagedSection.clientVerticalSpacing = 0;
1152 createStagedToolBarComposite();
1154 Composite stagedComposite = toolkit.createComposite(stagedSection);
1155 toolkit.paintBordersFor(stagedComposite);
1156 stagedSection.setClient(stagedComposite);
1157 GridLayoutFactory.fillDefaults().applyTo(stagedComposite);
1159 stagedViewer = createViewer(stagedComposite, false,
1160 selection -> stage(selection), unstageAction);
1161 stagedViewer.getLabelProvider().addListener(event -> {
1162 updateMessage();
1163 updateCommitButtons();
1165 stagedViewer.addSelectionChangedListener(event -> {
1166 boolean hasSelection = !event.getSelection().isEmpty();
1167 if (hasSelection != unstageAction.isEnabled()) {
1168 unstageAction.setEnabled(hasSelection);
1169 stagedToolBarManager.update(true);
1173 selectionChangedListener = new ISelectionListener() {
1174 @Override
1175 public void selectionChanged(IWorkbenchPart part,
1176 ISelection selection) {
1177 if (part == getSite().getPart()) {
1178 return;
1180 // don't accept text selection, only structural one
1181 if (selection instanceof StructuredSelection) {
1182 reactOnSelection((StructuredSelection) selection);
1187 partListener = new PartListener();
1189 IPreferenceStore preferenceStore = getPreferenceStore();
1190 if (preferenceStore.contains(UIPreferences.STAGING_VIEW_SYNC_SELECTION))
1191 reactOnSelection = preferenceStore.getBoolean(
1192 UIPreferences.STAGING_VIEW_SYNC_SELECTION);
1193 else
1194 preferenceStore.setDefault(UIPreferences.STAGING_VIEW_SYNC_SELECTION, true);
1196 preferenceStore.addPropertyChangeListener(uiPrefsListener);
1198 InstanceScope.INSTANCE.getNode(
1199 org.eclipse.egit.core.Activator.getPluginId())
1200 .addPreferenceChangeListener(prefListener);
1202 updateSectionText();
1203 stagedSection.setToolTipText(UIText.StagingView_StagedChangesTooltip);
1204 unstagedSection
1205 .setToolTipText(UIText.StagingView_UnstagedChangesTooltip);
1206 updateToolbar();
1207 enableCommitWidgets(false);
1208 refreshAction.setEnabled(false);
1210 createPopupMenu(unstagedViewer);
1211 createPopupMenu(stagedViewer);
1213 final ICommitMessageComponentNotifications listener = new ICommitMessageComponentNotifications() {
1215 @Override
1216 public void updateSignedOffToggleSelection(boolean selection) {
1217 signedOffByAction.setChecked(selection);
1220 @Override
1221 public void updateChangeIdToggleSelection(boolean selection) {
1222 addChangeIdAction.setChecked(selection);
1223 commitAndPushButton
1224 .setImage(getImage(
1225 selection ? UIIcons.GERRIT : UIIcons.PUSH));
1228 @Override
1229 public void statusUpdated() {
1230 updateMessage();
1233 commitMessageComponent = new CommitMessageComponent(listener);
1234 commitMessageComponent.attachControls(commitMessageText, authorText,
1235 committerText);
1237 // allow to commit with ctrl-enter
1238 commitMessageText.getTextWidget().addVerifyKeyListener(new VerifyKeyListener() {
1239 @Override
1240 public void verifyKey(VerifyEvent event) {
1241 if (UIUtils.isSubmitKeyEvent(event)) {
1242 event.doit = false;
1243 commit(false);
1248 commitMessageText.getTextWidget().addFocusListener(new FocusListener() {
1249 @Override
1250 public void focusGained(FocusEvent e) {
1251 // Ctrl+Enter shortcut only works when the focus is on the commit message text
1252 String commitButtonTooltip = MessageFormat.format(
1253 UIText.StagingView_CommitToolTip,
1254 UIUtils.SUBMIT_KEY_STROKE.format());
1255 commitButton.setToolTipText(commitButtonTooltip);
1258 @Override
1259 public void focusLost(FocusEvent e) {
1260 commitButton.setToolTipText(null);
1264 // react on selection changes
1265 IWorkbenchPartSite site = getSite();
1266 ISelectionService srv = CommonUtils.getService(site, ISelectionService.class);
1267 srv.addPostSelectionListener(selectionChangedListener);
1268 CommonUtils.getService(site, IPartService.class).addPartListener(
1269 partListener);
1271 // Use current selection to populate staging view
1272 UIUtils.notifySelectionChangedWithCurrentSelection(
1273 selectionChangedListener, site);
1275 site.setSelectionProvider(new RepositorySelectionProvider(
1276 new MultiViewerSelectionProvider(unstagedViewer, stagedViewer),
1277 () -> realRepository));
1279 ViewerFilter filter = new ViewerFilter() {
1280 @Override
1281 public boolean select(Viewer viewer, Object parentElement,
1282 Object element) {
1283 StagingViewContentProvider contentProvider = getContentProvider((TreeViewer) viewer);
1284 if (element instanceof StagingEntry)
1285 return contentProvider.isInFilter((StagingEntry) element);
1286 else if (element instanceof StagingFolderEntry)
1287 return contentProvider
1288 .hasVisibleChildren((StagingFolderEntry) element);
1289 return true;
1292 unstagedViewer.addFilter(filter);
1293 stagedViewer.addFilter(filter);
1295 restoreSashFormWeights();
1296 reactOnInitialSelection();
1298 IWorkbenchSiteProgressService service = CommonUtils.getService(
1299 getSite(), IWorkbenchSiteProgressService.class);
1300 if (service != null && reactOnSelection)
1301 // If we are linked, each time IndexDiffUpdateJob starts, indicate
1302 // that the view is busy (e.g. reload() will trigger this job in
1303 // background!).
1304 service.showBusyForFamily(org.eclipse.egit.core.JobFamilies.INDEX_DIFF_CACHE_UPDATE);
1307 private boolean commitAndPushEnabled(boolean commitEnabled) {
1308 Repository repo = currentRepository;
1309 if (repo == null) {
1310 return false;
1312 return commitEnabled && !repo.getRepositoryState().isRebasing();
1315 private void updateIgnoreErrorsButtonVisibility() {
1316 boolean visible = getPreferenceStore()
1317 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
1318 && getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT);
1319 showControl(ignoreErrors, visible);
1320 mainSashForm.layout();
1323 private int getProblemsSeverity() {
1324 int result = IProblemDecoratable.SEVERITY_NONE;
1325 StagingViewContentProvider stagedContentProvider = getContentProvider(
1326 stagedViewer);
1327 StagingEntry[] entries = stagedContentProvider.getStagingEntries();
1328 for (StagingEntry entry : entries) {
1329 if (entry.getProblemSeverity() >= IMarker.SEVERITY_WARNING) {
1330 if (result < entry.getProblemSeverity()) {
1331 result = entry.getProblemSeverity();
1335 return result;
1338 private void updateCommitButtons() {
1339 IndexDiffData indexDiff;
1340 if (cacheEntry != null) {
1341 indexDiff = cacheEntry.getIndexDiff();
1342 } else {
1343 Repository repo = currentRepository;
1344 if (repo == null) {
1345 indexDiff = null;
1346 } else {
1347 indexDiff = doReload(repo);
1350 boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
1351 boolean noConflicts = noConflicts(indexDiff);
1353 boolean commitEnabled = !isCommitBlocked() && noConflicts
1354 && indexDiffAvailable;
1356 boolean commitAndPushEnabled = commitAndPushEnabled(commitEnabled);
1358 commitButton.setEnabled(commitEnabled);
1359 commitAndPushButton.setEnabled(commitAndPushEnabled);
1362 private void saveSashFormWeightsOnDisposal(final SashForm sashForm,
1363 final String settingsKey) {
1364 sashForm.addDisposeListener(new DisposeListener() {
1365 @Override
1366 public void widgetDisposed(DisposeEvent e) {
1367 getDialogSettings().put(settingsKey,
1368 intArrayToString(sashForm.getWeights()));
1373 private IDialogSettings getDialogSettings() {
1374 return DialogSettings.getOrCreateSection(
1375 Activator.getDefault().getDialogSettings(),
1376 StagingView.class.getName());
1379 private static String intArrayToString(int[] ints) {
1380 StringBuilder res = new StringBuilder();
1381 if (ints != null && ints.length > 0) {
1382 res.append(String.valueOf(ints[0]));
1383 for (int i = 1; i < ints.length; i++) {
1384 res.append(',');
1385 res.append(String.valueOf(ints[i]));
1388 return res.toString();
1391 private void restoreSashFormWeights() {
1392 restoreSashFormWeights(mainSashForm,
1393 HORIZONTAL_SASH_FORM_WEIGHT);
1394 restoreSashFormWeights(stagingSashForm,
1395 STAGING_SASH_FORM_WEIGHT);
1398 private void restoreSashFormWeights(SashForm sashForm, String settingsKey) {
1399 IDialogSettings settings = getDialogSettings();
1400 String weights = settings.get(settingsKey);
1401 if (weights != null && !weights.isEmpty()) {
1402 sashForm.setWeights(stringToIntArray(weights));
1406 private static int[] stringToIntArray(String s) {
1407 String[] parts = s.split(","); //$NON-NLS-1$
1408 int[] ints = new int[parts.length];
1409 for (int i = 0; i < parts.length; i++) {
1410 ints[i] = Integer.parseInt(parts[i]);
1412 return ints;
1415 private void reactOnInitialSelection() {
1416 StructuredSelection sel = null;
1417 if (initialSelection instanceof StructuredSelection) {
1418 sel = (StructuredSelection) initialSelection;
1419 } else if (initialSelection != null && !initialSelection.isEmpty()) {
1420 sel = getSelectionOfActiveEditor();
1422 if (sel != null) {
1423 reactOnSelection(sel);
1425 initialSelection = null;
1428 private StructuredSelection getSelectionOfActiveEditor() {
1429 IEditorPart activeEditor = getSite().getPage().getActiveEditor();
1430 if (activeEditor == null) {
1431 return null;
1433 return getSelectionOfPart(activeEditor);
1436 private static StructuredSelection getSelectionOfPart(IWorkbenchPart part) {
1437 StructuredSelection sel = null;
1438 if (part instanceof IEditorPart) {
1439 IResource resource = getResource((IEditorPart) part);
1440 if (resource != null) {
1441 sel = new StructuredSelection(resource);
1442 } else {
1443 Repository repository = getRepository((IEditorPart) part);
1444 if (repository != null) {
1445 sel = new StructuredSelection(repository);
1448 } else {
1449 ISelection selection = part.getSite().getPage().getSelection();
1450 if (selection instanceof StructuredSelection) {
1451 sel = (StructuredSelection) selection;
1454 return sel;
1457 @Nullable
1458 private static Repository getRepository(IEditorPart part) {
1459 IEditorInput input = part.getEditorInput();
1460 if (!(input instanceof IURIEditorInput)) {
1461 return null;
1463 return AdapterUtils.adapt(input, Repository.class);
1466 private static IResource getResource(IEditorPart part) {
1467 IEditorInput input = part.getEditorInput();
1468 if (input instanceof IFileEditorInput) {
1469 return ((IFileEditorInput) input).getFile();
1470 } else {
1471 return AdapterUtils.adaptToAnyResource(input);
1475 private boolean getSortCheckState() {
1476 return getDialogSettings().getBoolean(STORE_SORT_STATE);
1479 private void executeRebaseOperation(AbstractRebaseCommandHandler command) {
1480 try {
1481 command.execute(currentRepository);
1482 } catch (ExecutionException e) {
1483 Activator.showError(e.getMessage(), e);
1488 * Abort rebase command in progress
1490 protected void rebaseAbort() {
1491 AbortRebaseCommand abortCommand = new AbortRebaseCommand();
1492 executeRebaseOperation(abortCommand);
1496 * Rebase next commit and continue rebase in progress
1498 protected void rebaseSkip() {
1499 SkipRebaseCommand skipCommand = new SkipRebaseCommand();
1500 executeRebaseOperation(skipCommand);
1504 * Continue rebase command in progress
1506 protected void rebaseContinue() {
1507 ContinueRebaseCommand continueCommand = new ContinueRebaseCommand();
1508 executeRebaseOperation(continueCommand);
1511 private void createUnstagedToolBarComposite() {
1512 Composite unstagedToolbarComposite = toolkit
1513 .createComposite(unstagedSection);
1514 unstagedToolbarComposite.setBackground(null);
1515 unstagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
1516 unstagedSection.setTextClient(unstagedToolbarComposite);
1517 unstagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
1518 IAction.AS_PUSH_BUTTON) {
1519 @Override
1520 public void run() {
1521 unstagedViewer.expandAll();
1522 enableAutoExpand(unstagedViewer);
1525 unstagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
1526 unstagedExpandAllAction.setId(EXPAND_ALL_ITEM_TOOLBAR_ID);
1528 unstagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
1529 IAction.AS_PUSH_BUTTON) {
1530 @Override
1531 public void run() {
1532 unstagedViewer.collapseAll();
1533 disableAutoExpand(unstagedViewer);
1536 unstagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1537 unstagedCollapseAllAction.setId(COLLAPSE_ALL_ITEM_TOOLBAR_ID);
1539 sortAction = new Action(UIText.StagingView_UnstagedSort,
1540 IAction.AS_CHECK_BOX) {
1542 @Override
1543 public void run() {
1544 StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
1545 .getComparator();
1546 comparator.setAlphabeticSort(!isChecked());
1547 comparator = (StagingEntryComparator) stagedViewer.getComparator();
1548 comparator.setAlphabeticSort(!isChecked());
1549 unstagedViewer.refresh();
1550 stagedViewer.refresh();
1554 sortAction.setImageDescriptor(UIIcons.STATE_SORT);
1555 sortAction.setId(SORT_ITEM_TOOLBAR_ID);
1556 sortAction.setChecked(getSortCheckState());
1558 unstagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
1560 unstagedToolBarManager.add(stageAction);
1561 unstagedToolBarManager.add(stageAllAction);
1562 unstagedToolBarManager.add(sortAction);
1563 unstagedToolBarManager.add(unstagedExpandAllAction);
1564 unstagedToolBarManager.add(unstagedCollapseAllAction);
1566 unstagedToolBarManager.update(true);
1567 unstagedToolBarManager.createControl(unstagedToolbarComposite);
1570 private void createStagedToolBarComposite() {
1571 Composite stagedToolbarComposite = toolkit
1572 .createComposite(stagedSection);
1573 stagedToolbarComposite.setBackground(null);
1574 stagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
1575 stagedSection.setTextClient(stagedToolbarComposite);
1576 stagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
1577 IAction.AS_PUSH_BUTTON) {
1578 @Override
1579 public void run() {
1580 stagedViewer.expandAll();
1581 enableAutoExpand(stagedViewer);
1584 stagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
1585 stagedExpandAllAction.setId(EXPAND_ALL_ITEM_TOOLBAR_ID);
1587 stagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
1588 IAction.AS_PUSH_BUTTON) {
1589 @Override
1590 public void run() {
1591 stagedViewer.collapseAll();
1592 disableAutoExpand(stagedViewer);
1595 stagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1596 stagedCollapseAllAction.setId(COLLAPSE_ALL_ITEM_TOOLBAR_ID);
1598 stagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
1600 stagedToolBarManager.add(unstageAction);
1601 stagedToolBarManager.add(unstageAllAction);
1602 stagedToolBarManager.add(stagedExpandAllAction);
1603 stagedToolBarManager.add(stagedCollapseAllAction);
1604 stagedToolBarManager.update(true);
1605 stagedToolBarManager.createControl(stagedToolbarComposite);
1608 private static RowLayout createRowLayoutWithoutMargin() {
1609 RowLayout layout = new RowLayout();
1610 layout.marginHeight = 0;
1611 layout.marginWidth = 0;
1612 layout.marginTop = 0;
1613 layout.marginBottom = 0;
1614 layout.marginLeft = 0;
1615 layout.marginRight = 0;
1616 return layout;
1619 private static void addListenerToDisableAutoExpandOnCollapse(
1620 TreeViewer treeViewer) {
1621 treeViewer.addTreeListener(new ITreeViewerListener() {
1622 @Override
1623 public void treeCollapsed(TreeExpansionEvent event) {
1624 disableAutoExpand(event.getTreeViewer());
1627 @Override
1628 public void treeExpanded(TreeExpansionEvent event) {
1629 // Nothing to do
1634 private static void enableAutoExpand(AbstractTreeViewer treeViewer) {
1635 treeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
1638 private static void disableAutoExpand(AbstractTreeViewer treeViewer) {
1639 treeViewer.setAutoExpandLevel(0);
1643 * @return selected repository
1645 public Repository getCurrentRepository() {
1646 return currentRepository;
1649 @Override
1650 public ShowInContext getShowInContext() {
1651 if (stagedViewer != null && stagedViewer.getTree().isFocusControl())
1652 return getShowInContext(stagedViewer);
1653 else if (unstagedViewer != null
1654 && unstagedViewer.getTree().isFocusControl())
1655 return getShowInContext(unstagedViewer);
1656 else
1657 return null;
1660 @Override
1661 public boolean show(ShowInContext context) {
1662 ISelection selection = context.getSelection();
1663 if (selection instanceof IStructuredSelection) {
1664 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
1665 for (Object element : structuredSelection.toList()) {
1666 if (element instanceof RepositoryTreeNode) {
1667 RepositoryTreeNode node = (RepositoryTreeNode) element;
1668 reload(node.getRepository());
1669 return true;
1673 return false;
1676 private ShowInContext getShowInContext(TreeViewer treeViewer) {
1677 IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
1678 List<Object> elements = new ArrayList<>();
1679 for (Object selectedElement : selection.toList()) {
1680 if (selectedElement instanceof StagingEntry) {
1681 StagingEntry entry = (StagingEntry) selectedElement;
1682 IFile file = entry.getFile();
1683 if (file != null)
1684 elements.add(file);
1685 else
1686 elements.add(entry.getLocation());
1687 } else if (selectedElement instanceof StagingFolderEntry) {
1688 StagingFolderEntry entry = (StagingFolderEntry) selectedElement;
1689 IContainer container = entry.getContainer();
1690 if (container != null)
1691 elements.add(container);
1692 else
1693 elements.add(entry.getLocation());
1696 return new ShowInContext(null, new StructuredSelection(elements));
1699 private int getStagingFormOrientation() {
1700 boolean columnLayout = Activator.getDefault().getPreferenceStore()
1701 .getBoolean(UIPreferences.STAGING_VIEW_COLUMN_LAYOUT);
1702 if (columnLayout)
1703 return SWT.HORIZONTAL;
1704 else
1705 return SWT.VERTICAL;
1708 private void enableAllWidgets(boolean enabled) {
1709 if (isDisposed())
1710 return;
1711 enableCommitWidgets(enabled);
1712 commitMessageText.setEnabled(enabled);
1713 enableStagingWidgets(enabled);
1716 private void enableStagingWidgets(boolean enabled) {
1717 if (isDisposed())
1718 return;
1719 unstagedViewer.getControl().setEnabled(enabled);
1720 stagedViewer.getControl().setEnabled(enabled);
1723 private void enableCommitWidgets(boolean enabled) {
1724 if (isDisposed()) {
1725 return;
1727 committerText.setEnabled(enabled);
1728 enableAuthorText(enabled);
1729 amendPreviousCommitAction.setEnabled(enabled);
1730 signedOffByAction.setEnabled(enabled);
1731 addChangeIdAction.setEnabled(enabled);
1732 commitButton.setEnabled(enabled);
1733 commitAndPushButton.setEnabled(enabled);
1736 private void enableAuthorText(boolean enabled) {
1737 Repository repo = currentRepository;
1738 if (repo != null && repo.getRepositoryState()
1739 .equals(RepositoryState.CHERRY_PICKING_RESOLVED)) {
1740 authorText.setEnabled(false);
1741 } else {
1742 authorText.setEnabled(enabled);
1746 private void updateToolbar() {
1748 ControlContribution controlContribution = new ControlContribution(
1749 "StagingView.searchText") { //$NON-NLS-1$
1750 @Override
1751 protected Control createControl(Composite parent) {
1752 Composite toolbarComposite = toolkit.createComposite(parent,
1753 SWT.NONE);
1754 toolbarComposite.setBackground(null);
1755 GridLayout headLayout = new GridLayout();
1756 headLayout.numColumns = 2;
1757 headLayout.marginHeight = 0;
1758 headLayout.marginWidth = 0;
1759 headLayout.marginTop = 0;
1760 headLayout.marginBottom = 0;
1761 headLayout.marginLeft = 0;
1762 headLayout.marginRight = 0;
1763 toolbarComposite.setLayout(headLayout);
1765 filterText = new Text(toolbarComposite, SWT.SEARCH
1766 | SWT.ICON_CANCEL | SWT.ICON_SEARCH);
1767 filterText.setMessage(UIText.StagingView_Find);
1768 GridData data = new GridData(GridData.FILL_HORIZONTAL);
1769 data.widthHint = 150;
1770 filterText.setLayoutData(data);
1771 final Display display = Display.getCurrent();
1772 filterText.addModifyListener(new ModifyListener() {
1773 @Override
1774 public void modifyText(ModifyEvent e) {
1775 filterPattern = wildcardToRegex(filterText.getText());
1776 final StagingViewSearchThread searchThread = new StagingViewSearchThread(
1777 StagingView.this);
1778 display.timerExec(200, new Runnable() {
1779 @Override
1780 public void run() {
1781 searchThread.start();
1786 return toolbarComposite;
1790 IActionBars actionBars = getViewSite().getActionBars();
1791 IToolBarManager toolbar = actionBars.getToolBarManager();
1793 toolbar.add(controlContribution);
1795 refreshAction = new Action(UIText.StagingView_Refresh, IAction.AS_PUSH_BUTTON) {
1796 @Override
1797 public void run() {
1798 if (cacheEntry != null) {
1799 schedule(
1800 cacheEntry.createRefreshResourcesAndIndexDiffJob(),
1801 false);
1805 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1806 toolbar.add(refreshAction);
1808 // link with selection
1809 Action linkSelectionAction = new BooleanPrefAction(
1810 (IPersistentPreferenceStore) getPreferenceStore(),
1811 UIPreferences.STAGING_VIEW_SYNC_SELECTION,
1812 UIText.StagingView_LinkSelection) {
1813 @Override
1814 public void apply(boolean value) {
1815 reactOnSelection = value;
1818 linkSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1819 toolbar.add(linkSelectionAction);
1821 toolbar.add(new Separator());
1823 switchRepositoriesAction = new RepositoryToolbarAction(false,
1824 () -> realRepository,
1825 repo -> {
1826 if (realRepository != repo) {
1827 reload(repo);
1830 toolbar.add(switchRepositoriesAction);
1832 compareModeAction = new Action(UIText.StagingView_CompareMode,
1833 IAction.AS_CHECK_BOX) {
1834 @Override
1835 public void run() {
1836 getPreferenceStore().setValue(
1837 UIPreferences.STAGING_VIEW_COMPARE_MODE, isChecked());
1840 compareModeAction.setImageDescriptor(UIIcons.ELCL16_COMPARE_VIEW);
1841 compareModeAction.setChecked(getPreferenceStore()
1842 .getBoolean(UIPreferences.STAGING_VIEW_COMPARE_MODE));
1844 toolbar.add(compareModeAction);
1845 toolbar.add(new Separator());
1847 openNewCommitsAction = new Action(UIText.StagingView_OpenNewCommits,
1848 IAction.AS_CHECK_BOX) {
1850 @Override
1851 public void run() {
1852 getPreferenceStore().setValue(
1853 UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS, isChecked());
1856 openNewCommitsAction.setChecked(getPreferenceStore().getBoolean(
1857 UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS));
1859 columnLayoutAction = new Action(UIText.StagingView_ColumnLayout,
1860 IAction.AS_CHECK_BOX) {
1862 @Override
1863 public void run() {
1864 getPreferenceStore().setValue(
1865 UIPreferences.STAGING_VIEW_COLUMN_LAYOUT, isChecked());
1866 stagingSashForm.setOrientation(isChecked() ? SWT.HORIZONTAL
1867 : SWT.VERTICAL);
1870 columnLayoutAction.setChecked(getPreferenceStore().getBoolean(
1871 UIPreferences.STAGING_VIEW_COLUMN_LAYOUT));
1873 fileNameModeAction = new Action(UIText.StagingView_ShowFileNamesFirst,
1874 IAction.AS_CHECK_BOX) {
1876 @Override
1877 public void run() {
1878 final boolean enable = isChecked();
1879 getLabelProvider(stagedViewer).setFileNameMode(enable);
1880 getLabelProvider(unstagedViewer).setFileNameMode(enable);
1881 getContentProvider(stagedViewer).setFileNameMode(enable);
1882 getContentProvider(unstagedViewer).setFileNameMode(enable);
1883 StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
1884 .getComparator();
1885 comparator.setFileNamesFirst(enable);
1886 comparator = (StagingEntryComparator) stagedViewer.getComparator();
1887 comparator.setFileNamesFirst(enable);
1888 getPreferenceStore().setValue(
1889 UIPreferences.STAGING_VIEW_FILENAME_MODE, enable);
1890 refreshViewersPreservingExpandedElements();
1893 fileNameModeAction.setChecked(getPreferenceStore().getBoolean(
1894 UIPreferences.STAGING_VIEW_FILENAME_MODE));
1896 IMenuManager dropdownMenu = actionBars.getMenuManager();
1897 MenuManager presentationMenu = new MenuManager(
1898 UIText.StagingView_Presentation);
1899 listPresentationAction = new Action(UIText.StagingView_List,
1900 IAction.AS_RADIO_BUTTON) {
1901 @Override
1902 public void run() {
1903 if (!isChecked()) {
1904 return;
1906 switchToListMode();
1907 refreshViewers();
1910 listPresentationAction.setImageDescriptor(UIIcons.FLAT);
1911 presentationMenu.add(listPresentationAction);
1913 treePresentationAction = new Action(UIText.StagingView_Tree,
1914 IAction.AS_RADIO_BUTTON) {
1915 @Override
1916 public void run() {
1917 if (!isChecked()) {
1918 return;
1920 presentation = Presentation.TREE;
1921 setPresentation(presentation, false);
1922 listPresentationAction.setChecked(false);
1923 compactTreePresentationAction.setChecked(false);
1924 setExpandCollapseActionsVisible(false, isExpandAllowed(false),
1925 true);
1926 setExpandCollapseActionsVisible(true, isExpandAllowed(true),
1927 true);
1928 refreshViewers();
1931 treePresentationAction.setImageDescriptor(UIIcons.HIERARCHY);
1932 presentationMenu.add(treePresentationAction);
1934 compactTreePresentationAction = new Action(UIText.StagingView_CompactTree,
1935 IAction.AS_RADIO_BUTTON) {
1936 @Override
1937 public void run() {
1938 if (!isChecked()) {
1939 return;
1941 switchToCompactModeInternal(false);
1942 refreshViewers();
1946 compactTreePresentationAction.setImageDescriptor(UIIcons.COMPACT);
1947 presentationMenu.add(compactTreePresentationAction);
1949 presentation = readPresentation(UIPreferences.STAGING_VIEW_PRESENTATION,
1950 Presentation.LIST);
1951 switch (presentation) {
1952 case LIST:
1953 listPresentationAction.setChecked(true);
1954 setExpandCollapseActionsVisible(false, false, false);
1955 setExpandCollapseActionsVisible(true, false, false);
1956 break;
1957 case TREE:
1958 treePresentationAction.setChecked(true);
1959 break;
1960 case COMPACT_TREE:
1961 compactTreePresentationAction.setChecked(true);
1962 break;
1963 default:
1964 break;
1966 dropdownMenu.add(presentationMenu);
1967 dropdownMenu.add(new Separator());
1968 dropdownMenu.add(openNewCommitsAction);
1969 dropdownMenu.add(columnLayoutAction);
1970 dropdownMenu.add(fileNameModeAction);
1971 dropdownMenu.add(compareModeAction);
1973 actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), new GlobalDeleteActionHandler());
1975 // For the normal resource undo/redo actions to be active, so that files
1976 // deleted via the "Delete" action in the staging view can be restored.
1977 IUndoContext workspaceContext = AdapterUtils.adapt(ResourcesPlugin.getWorkspace(), IUndoContext.class);
1978 undoRedoActionGroup = new UndoRedoActionGroup(getViewSite(), workspaceContext, true);
1979 undoRedoActionGroup.fillActionBars(actionBars);
1981 actionBars.updateActionBars();
1984 private Presentation readPresentation(String key, Presentation def) {
1985 String presentationString = getPreferenceStore().getString(key);
1986 if (presentationString.length() > 0) {
1987 try {
1988 return Presentation.valueOf(presentationString);
1989 } catch (IllegalArgumentException e) {
1990 // Use given default
1993 return def;
1996 private void setPresentation(Presentation newOne, boolean auto) {
1997 Presentation old = presentation;
1998 presentation = newOne;
1999 IPreferenceStore store = getPreferenceStore();
2000 store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION, newOne.name());
2001 if (auto && old != newOne) {
2002 // remember user choice if we switch mode automatically
2003 store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED,
2004 true);
2005 } else {
2006 store.setToDefault(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
2010 private void setExpandCollapseActionsVisible(boolean staged,
2011 boolean visibleExpandAll,
2012 boolean visibleCollapseAll) {
2013 ToolBarManager toolBarManager = staged ? stagedToolBarManager
2014 : unstagedToolBarManager;
2015 for (IContributionItem item : toolBarManager.getItems()) {
2016 String id = item.getId();
2017 if (EXPAND_ALL_ITEM_TOOLBAR_ID.equals(id)) {
2018 item.setVisible(visibleExpandAll);
2019 } else if (COLLAPSE_ALL_ITEM_TOOLBAR_ID.equals(id)) {
2020 item.setVisible(visibleCollapseAll);
2023 (staged ? stagedExpandAllAction : unstagedExpandAllAction)
2024 .setEnabled(visibleExpandAll);
2025 (staged ? stagedCollapseAllAction : unstagedCollapseAllAction)
2026 .setEnabled(visibleCollapseAll);
2027 toolBarManager.update(true);
2030 private boolean isExpandAllowed(boolean staged) {
2031 StagingViewContentProvider contentProvider = getContentProvider(
2032 staged ? stagedViewer : unstagedViewer);
2033 return contentProvider.getCount() <= getMaxLimitForListMode();
2036 private TreeViewer createTree(Composite composite) {
2037 Tree tree = toolkit.createTree(composite, SWT.FULL_SELECTION
2038 | SWT.MULTI);
2039 TreeViewer treeViewer = new TreeViewer(tree);
2040 return treeViewer;
2043 private IBaseLabelProvider createLabelProvider(TreeViewer treeViewer) {
2044 StagingViewLabelProvider baseProvider = new StagingViewLabelProvider(
2045 this);
2046 baseProvider.setFileNameMode(getPreferenceStore().getBoolean(
2047 UIPreferences.STAGING_VIEW_FILENAME_MODE));
2049 ProblemLabelDecorator decorator = new ProblemLabelDecorator(treeViewer);
2050 return new TreeDecoratingLabelProvider(baseProvider, decorator);
2053 private StagingViewContentProvider createStagingContentProvider(
2054 boolean unstaged) {
2055 StagingViewContentProvider provider = new StagingViewContentProvider(
2056 this, unstaged) {
2058 @Override
2059 public void inputChanged(Viewer viewer, Object oldInput,
2060 Object newInput) {
2061 super.inputChanged(viewer, oldInput, newInput);
2062 if (unstaged) {
2063 stageAllAction.setEnabled(getCount() > 0);
2064 unstagedToolBarManager.update(true);
2065 } else {
2066 unstageAllAction.setEnabled(getCount() > 0);
2067 stagedToolBarManager.update(true);
2071 provider.setFileNameMode(getPreferenceStore().getBoolean(
2072 UIPreferences.STAGING_VIEW_FILENAME_MODE));
2073 return provider;
2076 private TreeViewer createViewer(Composite parent, boolean unstaged,
2077 final Consumer<IStructuredSelection> dropAction,
2078 IAction... tooltipActions) {
2079 final TreeViewer viewer = createTree(parent);
2080 GridDataFactory.fillDefaults().grab(true, true)
2081 .applyTo(viewer.getControl());
2082 viewer.getTree().setData(FormToolkit.KEY_DRAW_BORDER,
2083 FormToolkit.TREE_BORDER);
2084 viewer.setLabelProvider(createLabelProvider(viewer));
2085 StagingViewContentProvider contentProvider = createStagingContentProvider(
2086 unstaged);
2087 viewer.setContentProvider(contentProvider);
2088 if (tooltipActions != null && tooltipActions.length > 0) {
2089 StagingViewTooltips tooltips = new StagingViewTooltips(viewer,
2090 tooltipActions);
2091 tooltips.setShift(new Point(1, 1));
2093 viewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
2094 new Transfer[] { LocalSelectionTransfer.getTransfer(),
2095 FileTransfer.getInstance() },
2096 new StagingDragListener(viewer, contentProvider, unstaged));
2097 viewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
2098 new Transfer[] { LocalSelectionTransfer.getTransfer() },
2099 new DropTargetAdapter() {
2101 @Override
2102 public void drop(DropTargetEvent event) {
2103 // Bug 411466: It is very important that detail is set
2104 // to DND.DROP_COPY. If it was left as DND.DROP_MOVE and
2105 // the drag comes from the Navigator view, the code in
2106 // NavigatorDragAdapter would delete the resources.
2107 event.detail = DND.DROP_COPY;
2108 if (event.data instanceof IStructuredSelection) {
2109 final IStructuredSelection selection = (IStructuredSelection) event.data;
2110 if ((selection instanceof StagingDragSelection)
2111 && ((StagingDragSelection) selection)
2112 .isFromUnstaged() == unstaged) {
2113 // Dropped a selection made in this viewer
2114 // back on this viewer: don't do anything,
2115 // otherwise if there are folders in the
2116 // selection, we might unstage or stage files
2117 // not selected!
2118 return;
2120 dropAction.accept(selection);
2124 viewer.addOpenListener(event -> compareWith(event));
2125 viewer.setComparator(new StagingEntryComparator(getSortCheckState(),
2126 getPreferenceStore()
2127 .getBoolean(UIPreferences.STAGING_VIEW_FILENAME_MODE)));
2128 viewer.addDoubleClickListener(event -> {
2129 IStructuredSelection selection = (IStructuredSelection) event
2130 .getSelection();
2131 Object selectedNode = selection.getFirstElement();
2132 if (selectedNode instanceof StagingFolderEntry) {
2133 viewer.setExpandedState(selectedNode,
2134 !viewer.getExpandedState(selectedNode));
2137 addCopyAction(viewer);
2138 enableAutoExpand(viewer);
2139 addListenerToDisableAutoExpandOnCollapse(viewer);
2140 return viewer;
2143 private void addCopyAction(final TreeViewer viewer) {
2144 IAction copyAction = createSelectionPathCopyAction(viewer);
2146 ActionUtils.setGlobalActions(viewer.getControl(),
2147 getSite().getService(IHandlerService.class), copyAction);
2150 private IAction createSelectionPathCopyAction(final TreeViewer viewer) {
2151 IStructuredSelection selection = (IStructuredSelection) viewer
2152 .getSelection();
2153 String copyPathActionText = (selection.size() <= 1) ? UIText.StagingView_CopyPath
2154 : UIText.StagingView_CopyPaths;
2155 IAction copyAction = ActionUtils.createGlobalAction(ActionFactory.COPY,
2156 () -> copyPathOfSelectionToClipboard(viewer));
2157 copyAction.setText(copyPathActionText);
2158 return copyAction;
2161 private void copyPathOfSelectionToClipboard(final TreeViewer viewer) {
2162 Clipboard cb = new Clipboard(viewer.getControl().getDisplay());
2163 TextTransfer t = TextTransfer.getInstance();
2164 String text = getTextFrom(
2165 (IStructuredSelection) viewer.getSelection());
2166 try {
2167 if (text != null) {
2168 cb.setContents(new Object[] { text }, new Transfer[] { t });
2170 } finally {
2171 cb.dispose();
2175 @Nullable
2176 private String getTextFrom(IStructuredSelection selection) {
2177 Object[] selectionEntries = selection.toArray();
2178 if (selectionEntries.length <= 0) {
2179 return null;
2180 } else if (selectionEntries.length == 1) {
2181 return getPathFrom(selectionEntries[0]);
2182 } else {
2183 StringBuilder sb = new StringBuilder();
2184 for (int i = 0; i < selectionEntries.length; i++) {
2185 String text = getPathFrom(selectionEntries[i]);
2186 if (text != null) {
2187 if (i < selectionEntries.length - 1) {
2188 sb.append(text).append(System.lineSeparator());
2189 } else {
2190 sb.append(text);
2194 return sb.toString();
2198 @Nullable
2199 private String getPathFrom(Object obj) {
2200 if (obj instanceof StagingEntry) {
2201 return ((StagingEntry) obj).getPath();
2202 } else if (obj instanceof StagingFolderEntry) {
2203 return ((StagingFolderEntry) obj).getPath().toString();
2205 return null;
2208 private void setStagingViewerInput(TreeViewer stagingViewer,
2209 StagingViewUpdate newInput, Object[] previous,
2210 Set<IPath> additionalPaths) {
2211 // Disable painting and show a busy cursor for the tree during the
2212 // entire update process.
2213 final Tree tree = stagingViewer.getTree();
2214 tree.setRedraw(false);
2215 Cursor oldCursor = tree.getCursor();
2216 tree.setCursor(tree.getDisplay().getSystemCursor(SWT.CURSOR_WAIT));
2218 try {
2219 // Remember the elements at or before the current top element of the
2220 // viewer.
2221 TreeItem topItem = tree.getTopItem();
2222 final Set<Object> precedingObjects = new LinkedHashSet<>();
2223 if (topItem != null) {
2224 new TreeItemVisitor(tree.getItems()) {
2225 @Override
2226 public boolean visit(TreeItem treeItem) {
2227 precedingObjects.add(treeItem.getData());
2228 return true;
2230 }.traverse(topItem);
2231 precedingObjects.remove(null);
2234 // Controls whether we'll try to preserve the top element in the
2235 // view, i.e., the scroll position. We generally want that unless
2236 // the update has added new objects to the view, in which case those
2237 // are selected and revealed.
2238 boolean preserveTop = true;
2239 boolean keepSelectionVisible = false;
2240 StagingViewUpdate oldInput = (StagingViewUpdate) stagingViewer
2241 .getInput();
2242 if (oldInput != null && oldInput.repository == newInput.repository
2243 && oldInput.indexDiff != null) {
2244 // If the input has changed and wasn't empty before or wasn't
2245 // for a different repository before, record the contents of the
2246 // viewer before the input is changed.
2247 StagingViewContentProvider contentProvider = getContentProvider(
2248 stagingViewer);
2249 ViewerComparator comparator = stagingViewer.getComparator();
2250 Map<String, Object> oldPaths = buildElementMap(stagingViewer,
2251 contentProvider, comparator);
2253 // Update the input.
2254 stagingViewer.setInput(newInput);
2255 // Restore the previous expansion state, if there is one.
2256 if (previous != null) {
2257 expandPreviousExpandedAndPaths(previous, stagingViewer,
2258 additionalPaths);
2261 // Update the selection.
2262 StagingViewerUpdate stagingViewerUpdate = updateSelection(
2263 stagingViewer, contentProvider, oldPaths,
2264 buildElementMap(stagingViewer, contentProvider,
2265 comparator));
2267 // If something has been removed, the element before the removed
2268 // item has been selected, in which case we want to preserve the
2269 // scroll state as much as possible, keeping the selection in
2270 // view. If something has been added, those added things have
2271 // been selected and revealed, so we don't want to preserve the
2272 // top but rather leave the revealed selection alone. If nothing
2273 // has changed, we want to preserve the top, regardless of where
2274 // the current unmodified selection might be, which is what's
2275 // done by default anyway.
2276 if (stagingViewerUpdate == StagingViewerUpdate.REMOVED) {
2277 keepSelectionVisible = true;
2278 } else if (stagingViewerUpdate == StagingViewerUpdate.ADDED) {
2279 preserveTop = false;
2281 } else {
2282 // The update is completely different so don't do any of the
2283 // above analysis to see what's different.
2284 stagingViewer.setInput(newInput);
2285 // Restore the previous expansion state, if there is one.
2286 if (previous != null) {
2287 expandPreviousExpandedAndPaths(previous, stagingViewer,
2288 additionalPaths);
2292 if (preserveTop) {
2293 // It's likely that the tree has scrolled to change the top
2294 // item. So try to restore the current top item to be the one
2295 // with the same data as was at the top before, either starting
2296 // with the selection so that it generally stays in view, or at
2297 // the bottom, if we're not trying to keep the selection
2298 // visible.
2299 TreeItem[] selection = tree.getSelection();
2300 TreeItem initialItem = keepSelectionVisible
2301 && selection.length > 0 ? selection[0] : null;
2302 new TreeItemVisitor(tree.getItems()) {
2303 @Override
2304 public boolean visit(TreeItem treeItem) {
2305 if (precedingObjects.contains(treeItem.getData())) {
2306 // If we reach an item that was at or before the
2307 // original top item, make it the top item
2308 // again, and stop the visitor.
2309 tree.setTopItem(treeItem);
2310 return false;
2312 return true;
2314 }.traverse(initialItem);
2316 } finally {
2317 // The viewer is fully updated now, so we can paint it.
2318 tree.setRedraw(true);
2319 tree.setCursor(oldCursor);
2323 private static Map<String, Object> buildElementMap(TreeViewer stagingViewer,
2324 StagingViewContentProvider contentProvider,
2325 ViewerComparator comparator) {
2326 // Builds a map from paths, represented as strings, to elements visible
2327 // in the staging viewer.
2328 Map<String, Object> result = new LinkedHashMap<>();
2329 // Start visiting the root elements in the order in which they appear in
2330 // the UI.
2331 Object[] elements = contentProvider.getElements(null);
2332 comparator.sort(stagingViewer, elements);
2333 for (Object element : elements) {
2334 visitElement(stagingViewer, contentProvider, comparator, element,
2335 result);
2337 return result;
2340 private static boolean visitElement(TreeViewer stagingViewer,
2341 StagingViewContentProvider contentProvider,
2342 ViewerComparator comparator,
2343 Object element, Map<String, Object> paths) {
2344 if (element instanceof StagingEntry) {
2345 StagingEntry stagingEntry = (StagingEntry) element;
2346 if (contentProvider.isInFilter(stagingEntry)) {
2347 // If the element is a staging entry, and it's included by the
2348 // filter, add a mapping for it.
2349 String path = stagingEntry.getPath();
2350 paths.put(path, stagingEntry);
2351 return true;
2354 return false;
2357 // If the element is a staging folder entry, visit all the children,
2358 // checking that at least one visited descendant has been added to the
2359 // map before adding a mapping for this staging folder entry.
2360 if (element instanceof StagingFolderEntry) {
2361 StagingFolderEntry stagingFolderEntry = (StagingFolderEntry) element;
2362 // Visit the children in the order in which they appear in the UI.
2363 Object[] children = contentProvider.getChildren(stagingFolderEntry);
2364 comparator.sort(stagingViewer, children);
2366 IPath path = stagingFolderEntry.getPath();
2367 String pathString = path.toString();
2368 paths.put(pathString, stagingFolderEntry);
2370 boolean hasVisibleChildren = false;
2371 for (Object child : children) {
2372 if (visitElement(stagingViewer, contentProvider, comparator,
2373 child, paths)) {
2374 hasVisibleChildren = true;
2378 if (hasVisibleChildren) {
2379 return true;
2382 // If there were no visible children, remove the path from the map.
2383 paths.remove(pathString);
2384 return false;
2387 return false;
2390 private enum StagingViewerUpdate {
2391 ADDED, REMOVED, UNCHANGED
2395 * Updates the selection depending on the type of change in the staging
2396 * viewer's state. If something has been removed, it returns
2397 * {@link StagingViewerUpdate#REMOVED} and the item before the removed
2398 * element is selected. If something has been added, it returns
2399 * {@link StagingViewerUpdate#ADDED} and those added elements are selected
2400 * and revealed. If nothing has changed, it returns
2401 * {@link StagingViewerUpdate#UNCHANGED} and the selection state is
2402 * unchanged.
2404 * @param stagingViewer
2405 * the staging viewer for which to update the selection.
2406 * @param contentProvider
2407 * the content provider used by that staging viewer.
2408 * @param oldPaths
2409 * the old content state of the staging viewer.
2410 * @param newPaths
2411 * the new content state of the staging viewer.
2412 * @return the type of change to the selecting of the staging viewer
2414 private static StagingViewerUpdate updateSelection(TreeViewer stagingViewer,
2415 StagingViewContentProvider contentProvider,
2416 Map<String, Object> oldPaths, Map<String, Object> newPaths) {
2417 // Update the staging viewer's selection by analyzing the change
2418 // to the contents of the viewer.
2419 Map<String, Object> addedPaths = new LinkedHashMap<>(newPaths);
2420 addedPaths.keySet().removeAll(oldPaths.keySet());
2421 if (!addedPaths.isEmpty()) {
2422 // If anything has been added to the viewer, select those added
2423 // things. But, to minimize the selection, select a parent node when
2424 // all its children have been added. The general idea is that if you
2425 // drag and drop between staged and unstaged, the new selection in
2426 // the target view, when dragged back again to the source view, will
2427 // undo the original drag-and-drop operation operation.
2428 List<Object> newSelection = new ArrayList<>();
2429 Set<Object> elements = new LinkedHashSet<>(addedPaths.values());
2430 Set<Object> excludeChildren = new LinkedHashSet<>();
2431 for (Object element : elements) {
2432 if (element instanceof StagingEntry) {
2433 StagingEntry stagingEntry = (StagingEntry) element;
2434 if (!excludeChildren.contains(stagingEntry.getParent())) {
2435 // If it's a leaf entry and its parent has not been
2436 // excluded from the selection, include it in the
2437 // selection.
2438 newSelection.add(stagingEntry);
2440 } else if (element instanceof StagingFolderEntry) {
2441 StagingFolderEntry stagingFolderEntry = (StagingFolderEntry) element;
2442 StagingFolderEntry parent = stagingFolderEntry.getParent();
2443 if (excludeChildren.contains(parent)) {
2444 // If its parent has been excluded from the selection,
2445 // exclude this folder entry also.
2446 excludeChildren.add(stagingFolderEntry);
2447 } else if (elements.containsAll(contentProvider
2448 .getStagingEntriesFiltered(stagingFolderEntry))) {
2449 // If all of this folder's visible children are added,
2450 // i.e., it had no existing children before, then
2451 // include it in the selection, and exclude its
2452 // children from the selection.
2453 newSelection.add(stagingFolderEntry);
2454 excludeChildren.add(stagingFolderEntry);
2459 // Select and reveal the selection of the newly added elements.
2460 stagingViewer.setSelection(new StructuredSelection(newSelection),
2461 true);
2462 return StagingViewerUpdate.ADDED;
2463 } else {
2464 Map<String, Object> removedPaths = new LinkedHashMap<>(oldPaths);
2465 removedPaths.keySet().removeAll(newPaths.keySet());
2466 if (!removedPaths.isEmpty()) {
2467 // If anything has been removed from the viewer, try to select
2468 // the closest following unremoved sibling of the first removed
2469 // element, a parent if there isn't such a sibling, or the first
2470 // element in the viewer failing those. The general idea is that
2471 // it's really annoying to have the viewer scroll to the top
2472 // element whenever you drag something out of a staging viewer.
2473 Collection<Object> removedElements = removedPaths.values();
2474 Object firstRemovedElement = removedElements.iterator()
2475 .next();
2476 Object parent = contentProvider.getParent(firstRemovedElement);
2477 Object candidate = null;
2478 boolean visitSubsequentSiblings = false;
2479 for (Object oldElement : oldPaths.values()) {
2480 if (oldElement == firstRemovedElement) {
2481 // Once we reach the first removed element, siblings
2482 // that follow are ideal candidates.
2483 visitSubsequentSiblings = true;
2486 if (visitSubsequentSiblings) {
2487 if (!removedElements.contains(oldElement)) {
2488 if (contentProvider
2489 .getParent(oldElement) == parent) {
2490 // If this is a subsequent sibling that's not
2491 // itself removed, it's the best candidate.
2492 candidate = oldElement;
2493 break;
2494 } else if (candidate != null) {
2495 // If we already have a candidate, and we're
2496 // looking for a subsequent sibling, but now
2497 // we've hit an element with a different parent
2498 // of the removed element, then we're never
2499 // going to find a subsequent unremoved sibling,
2500 // so just return the candidate.
2501 break;
2504 } else if (candidate == null || oldElement == parent
2505 || contentProvider
2506 .getParent(oldElement) == parent) {
2507 // If there is no candidate, or there is a better
2508 // candidate, i.e., the parent or an element with the
2509 // same parent, record the current entry.
2510 candidate = oldElement;
2514 if (candidate == null && !newPaths.isEmpty()) {
2515 // If there is no selected object yet, just choose the first
2516 // element in the viewer, if there is such an element.
2517 candidate = newPaths.values().iterator().next();
2520 if (candidate != null) {
2521 // If we have a selection, which will always be the case
2522 // unless the viewer is empty, set it. This selection is
2523 // preserved during update of the viewer. Unfortunately the
2524 // scroll position is generally quite poor. Fixing the
2525 // scroll position is done after the viewer is updated.
2526 stagingViewer.setSelection(
2527 new StructuredSelection(candidate), true);
2528 return StagingViewerUpdate.REMOVED;
2532 return StagingViewerUpdate.UNCHANGED;
2537 * This visitor is used to traverse all visible tree items of a tree viewer
2538 * starting at some specific item, visiting the items in the reverse order
2539 * in which they appear in the UI.
2541 private static abstract class TreeItemVisitor {
2542 private final TreeItem[] roots;
2544 public TreeItemVisitor(TreeItem[] roots) {
2545 this.roots = roots;
2548 public abstract boolean visit(TreeItem treeItem);
2551 * The public entry point for invoking this visitor.
2553 * @param treeItem
2554 * the item at which to start, are null, to start at the
2555 * bottom.
2557 public void traverse(TreeItem treeItem) {
2558 if (treeItem == null) {
2559 treeItem = getLastItem(roots);
2560 if (treeItem == null) {
2561 return;
2564 if (treeItem.isDisposed()) {
2565 return;
2567 if (treeItem.getData() != null && visit(treeItem)) {
2568 traversePrecedingSiblings(treeItem);
2572 private TreeItem getLastItem(TreeItem[] treeItems) {
2573 if (treeItems.length == 0) {
2574 return null;
2576 TreeItem lastItem = treeItems[treeItems.length - 1];
2577 if (lastItem.getExpanded()) {
2578 TreeItem result = getLastItem(lastItem.getItems());
2579 if (result != null) {
2580 return result;
2583 return lastItem;
2586 private boolean traversePrecedingSiblings(TreeItem treeItem) {
2587 TreeItem parent = treeItem.getParentItem();
2588 if (parent == null) {
2589 // If there is no parent, traverse based on the root items.
2590 return traversePrecedingSiblings(roots, treeItem);
2592 // Traverse based on the parent items, i.e., the siblings of the
2593 // tree item.
2594 if (!traversePrecedingSiblings(parent.getItems(), treeItem)) {
2595 return false;
2597 // Recursively traverse the parent.
2598 return traversePrecedingSiblings(parent);
2601 private boolean traversePrecedingSiblings(TreeItem[] siblings,
2602 TreeItem treeItem) {
2603 // Traverse the siblings in reverse order, skipping the ones that
2604 // are at or before the tree item.
2605 boolean start = false;
2606 for (int i = siblings.length - 1; i >= 0; --i) {
2607 TreeItem sibling = siblings[i];
2608 if (start) {
2609 // Traverse all the visible children of this preceding
2610 // sibling.
2611 if (!traverseChildren(sibling)) {
2612 return false;
2614 } else if (sibling == treeItem) {
2615 start = true;
2619 return true;
2622 private boolean traverseChildren(TreeItem treeItem) {
2623 if (treeItem.getExpanded()) {
2624 // If the tree item is expanded, traverse all the children in
2625 // reverse order.
2626 TreeItem[] children = treeItem.getItems();
2627 for (int i = children.length - 1; i >= 0; --i) {
2628 // Recursively traverse the children of the children.
2629 if (!traverseChildren(children[i])) {
2630 return false;
2634 // Call the visitor callback after the children have been visited.
2635 return visit(treeItem);
2639 private IPreferenceStore getPreferenceStore() {
2640 return Activator.getDefault().getPreferenceStore();
2643 private StagingViewLabelProvider getLabelProvider(ContentViewer viewer) {
2644 IBaseLabelProvider base = viewer.getLabelProvider();
2645 ILabelProvider labelProvider = ((TreeDecoratingLabelProvider) base)
2646 .getLabelProvider();
2647 return (StagingViewLabelProvider) labelProvider;
2650 private StagingViewContentProvider getContentProvider(ContentViewer viewer) {
2651 return (StagingViewContentProvider) viewer.getContentProvider();
2654 private void updateSectionText() {
2655 stagedSection.setText(MessageFormat
2656 .format(UIText.StagingView_StagedChanges,
2657 getSectionCount(stagedViewer)));
2658 unstagedSection.setText(MessageFormat.format(
2659 UIText.StagingView_UnstagedChanges,
2660 getSectionCount(unstagedViewer)));
2663 private String getSectionCount(TreeViewer viewer) {
2664 StagingViewContentProvider contentProvider = getContentProvider(viewer);
2665 int count = contentProvider.getCount();
2666 int shownCount = contentProvider.getShownCount();
2667 if (shownCount == count)
2668 return Integer.toString(count);
2669 else
2670 return shownCount + "/" + count; //$NON-NLS-1$
2673 private void updateMessage() {
2674 if (hasErrorsOrWarnings()) {
2675 warningLabel.showMessage(UIText.StagingView_MessageErrors);
2676 commitMessageSection.redraw();
2677 } else {
2678 String message = commitMessageComponent.getStatus().getMessage();
2679 boolean needsRedraw = false;
2680 if (message != null) {
2681 warningLabel.showMessage(message);
2682 needsRedraw = true;
2683 } else {
2684 needsRedraw = warningLabel.getVisible();
2685 warningLabel.hideMessage();
2687 // Without this explicit redraw, the ControlDecoration of the
2688 // commit message area would not get updated and cause visual
2689 // corruption.
2690 if (needsRedraw)
2691 commitMessageSection.redraw();
2695 private void compareWith(OpenEvent event) {
2696 IStructuredSelection selection = (IStructuredSelection) event
2697 .getSelection();
2698 if (selection.isEmpty()
2699 || !(selection.getFirstElement() instanceof StagingEntry))
2700 return;
2701 StagingEntry stagingEntry = (StagingEntry) selection.getFirstElement();
2702 if (stagingEntry.isSubmodule())
2703 return;
2704 switch (stagingEntry.getState()) {
2705 case ADDED:
2706 case CHANGED:
2707 case REMOVED:
2708 runCommand(ActionCommands.COMPARE_INDEX_WITH_HEAD_ACTION, selection);
2709 break;
2711 case CONFLICTING:
2712 runCommand(ActionCommands.MERGE_TOOL_ACTION, selection);
2713 break;
2715 case MISSING:
2716 case MISSING_AND_CHANGED:
2717 case MODIFIED:
2718 case MODIFIED_AND_CHANGED:
2719 case MODIFIED_AND_ADDED:
2720 case UNTRACKED:
2721 default:
2722 if (Activator.getDefault().getPreferenceStore().getBoolean(
2723 UIPreferences.STAGING_VIEW_COMPARE_MODE)) {
2724 // compare with index
2725 runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION, selection);
2726 } else {
2727 openSelectionInEditor(selection);
2733 private void createPopupMenu(final TreeViewer treeViewer) {
2734 final MenuManager menuMgr = new MenuManager();
2735 menuMgr.setRemoveAllWhenShown(true);
2736 Control control = treeViewer.getControl();
2737 control.setMenu(menuMgr.createContextMenu(control));
2738 menuMgr.addMenuListener(new IMenuListener() {
2740 @Override
2741 public void menuAboutToShow(IMenuManager manager) {
2742 control.setFocus();
2743 final IStructuredSelection selection = (IStructuredSelection) treeViewer
2744 .getSelection();
2745 if (selection.isEmpty())
2746 return;
2748 Set<StagingEntry> stagingEntrySet = new LinkedHashSet<>();
2749 Set<StagingFolderEntry> stagingFolderSet = new LinkedHashSet<>();
2751 boolean submoduleSelected = false;
2752 boolean folderSelected = false;
2753 boolean onlyFoldersSelected = true;
2754 for (Object element : selection.toArray()) {
2755 if (element instanceof StagingFolderEntry) {
2756 StagingFolderEntry folder = (StagingFolderEntry) element;
2757 folderSelected = true;
2758 if (onlyFoldersSelected) {
2759 stagingFolderSet.add(folder);
2761 StagingViewContentProvider contentProvider = getContentProvider(treeViewer);
2762 stagingEntrySet.addAll(contentProvider
2763 .getStagingEntriesFiltered(folder));
2764 } else if (element instanceof StagingEntry) {
2765 if (onlyFoldersSelected) {
2766 stagingFolderSet.clear();
2768 onlyFoldersSelected = false;
2769 StagingEntry entry = (StagingEntry) element;
2770 if (entry.isSubmodule()) {
2771 submoduleSelected = true;
2773 stagingEntrySet.add(entry);
2777 List<StagingEntry> stagingEntryList = new ArrayList<>(
2778 stagingEntrySet);
2779 final IStructuredSelection fileSelection = new StructuredSelection(
2780 stagingEntryList);
2781 stagingEntrySet = null;
2783 if (!folderSelected) {
2784 Action openWorkingTreeVersion = new Action(
2785 UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) {
2786 @Override
2787 public void run() {
2788 openSelectionInEditor(fileSelection);
2791 openWorkingTreeVersion.setEnabled(!submoduleSelected
2792 && anyElementIsExistingFile(fileSelection));
2793 menuMgr.add(openWorkingTreeVersion);
2794 String label = stagingEntryList.get(0).isStaged()
2795 ? UIText.CommitFileDiffViewer_CompareWorkingDirectoryMenuLabel
2796 : UIText.StagingView_CompareWithIndexMenuLabel;
2797 Action openCompareWithIndex = new Action(label) {
2798 @Override
2799 public void run() {
2800 runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION,
2801 fileSelection);
2804 menuMgr.add(openCompareWithIndex);
2807 menuMgr.add(createSelectionPathCopyAction(treeViewer));
2809 Set<StagingEntry.Action> availableActions = getAvailableActions(fileSelection);
2811 boolean addReplaceWithFileInGitIndex = availableActions.contains(StagingEntry.Action.REPLACE_WITH_FILE_IN_GIT_INDEX);
2812 boolean addReplaceWithHeadRevision = availableActions.contains(StagingEntry.Action.REPLACE_WITH_HEAD_REVISION);
2813 boolean addStage = availableActions.contains(StagingEntry.Action.STAGE);
2814 boolean addUnstage = availableActions.contains(StagingEntry.Action.UNSTAGE);
2815 boolean addDelete = availableActions.contains(StagingEntry.Action.DELETE);
2816 boolean addIgnore = availableActions.contains(StagingEntry.Action.IGNORE);
2817 boolean addLaunchMergeTool = availableActions.contains(StagingEntry.Action.LAUNCH_MERGE_TOOL);
2818 boolean addReplaceWithOursTheirsMenu = availableActions
2819 .contains(StagingEntry.Action.REPLACE_WITH_OURS_THEIRS_MENU);
2820 boolean addAssumeUnchanged = availableActions
2821 .contains(StagingEntry.Action.ASSUME_UNCHANGED);
2822 boolean addUntrack = availableActions
2823 .contains(StagingEntry.Action.UNTRACK);
2825 if (addStage) {
2826 menuMgr.add(
2827 new Action(UIText.StagingView_StageItemMenuLabel,
2828 UIIcons.ELCL16_ADD) {
2829 @Override
2830 public void run() {
2831 stage(selection);
2835 if (addUnstage) {
2836 menuMgr.add(
2837 new Action(UIText.StagingView_UnstageItemMenuLabel,
2838 UIIcons.UNSTAGE) {
2839 @Override
2840 public void run() {
2841 unstage(selection);
2845 boolean selectionIncludesNonWorkspaceResources = selectionIncludesNonWorkspaceResources(fileSelection);
2846 if (addReplaceWithFileInGitIndex) {
2847 if (selectionIncludesNonWorkspaceResources) {
2848 menuMgr.add(new ReplaceAction(
2849 UIText.StagingView_replaceWithFileInGitIndex,
2850 fileSelection, false));
2851 } else {
2852 menuMgr.add(createItem(
2853 UIText.StagingView_replaceWithFileInGitIndex,
2854 ActionCommands.DISCARD_CHANGES_ACTION,
2855 fileSelection)); // replace with index
2858 if (addReplaceWithHeadRevision) {
2859 if (selectionIncludesNonWorkspaceResources) {
2860 menuMgr.add(new ReplaceAction(
2861 UIText.StagingView_replaceWithHeadRevision,
2862 fileSelection, true));
2863 } else {
2864 menuMgr.add(createItem(
2865 UIText.StagingView_replaceWithHeadRevision,
2866 ActionCommands.REPLACE_WITH_HEAD_ACTION,
2867 fileSelection));
2870 if (addIgnore) {
2871 if (!stagingFolderSet.isEmpty()) {
2872 menuMgr.add(new IgnoreFoldersAction(stagingFolderSet));
2874 menuMgr.add(new IgnoreAction(fileSelection));
2876 if (addDelete) {
2877 menuMgr.add(new DeleteAction(fileSelection));
2879 if (addLaunchMergeTool) {
2880 menuMgr.add(createItem(UIText.StagingView_MergeTool,
2881 ActionCommands.MERGE_TOOL_ACTION,
2882 fileSelection));
2884 if (addReplaceWithOursTheirsMenu) {
2885 MenuManager replaceWithMenu = new MenuManager(
2886 UIText.StagingView_ReplaceWith);
2887 ReplaceWithOursTheirsMenu oursTheirsMenu = new ReplaceWithOursTheirsMenu();
2888 oursTheirsMenu.initialize(getSite());
2889 replaceWithMenu.add(oursTheirsMenu);
2890 menuMgr.add(replaceWithMenu);
2892 if (addAssumeUnchanged) {
2893 menuMgr.add(
2894 new Action(UIText.StagingView_Assume_Unchanged,
2895 UIIcons.ASSUME_UNCHANGED) {
2896 @Override
2897 public void run() {
2898 assumeUnchanged(selection);
2902 if (addUntrack) {
2903 menuMgr.add(new Action(UIText.StagingView_Untrack,
2904 UIIcons.UNTRACK) {
2905 @Override
2906 public void run() {
2907 untrack(selection);
2911 menuMgr.add(new Separator());
2912 menuMgr.add(createShowInMenu());
2918 private boolean anyElementIsExistingFile(IStructuredSelection s) {
2919 for (Object element : s.toList()) {
2920 if (element instanceof StagingEntry) {
2921 StagingEntry entry = (StagingEntry) element;
2922 if (entry.getType() != IResource.FILE) {
2923 continue;
2925 if (entry.getLocation().toFile().exists()) {
2926 return true;
2930 return false;
2934 * @return selected presentation
2936 Presentation getPresentation() {
2937 return presentation;
2941 * @return the trimmed string which is the current filter, empty string for
2942 * no filter
2944 Pattern getFilterPattern() {
2945 return filterPattern;
2949 * Convert a filter string to a regex pattern. Wildcard characters "*" will
2950 * match anything, other characters must match literally (case insensitive).
2951 * The filter string will be trimmed.
2953 * @param filter
2954 * @return compiled pattern, or {@code null} if trimmed filter input is
2955 * empty
2957 private Pattern wildcardToRegex(String filter) {
2958 String trimmed = filter.trim();
2959 if (trimmed.isEmpty()) {
2960 return null;
2962 String regex = (trimmed.contains("*") ? "^" : "") + "\\Q"//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
2963 + trimmed.replaceAll("\\*", //$NON-NLS-1$
2964 Matcher.quoteReplacement("\\E.*?\\Q")) //$NON-NLS-1$
2965 + "\\E";//$NON-NLS-1$
2966 // remove potentially empty quotes at begin or end
2967 regex = regex.replaceAll(Pattern.quote("\\Q\\E"), ""); //$NON-NLS-1$ //$NON-NLS-2$
2968 return Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
2972 * Refresh the unstaged and staged viewers without preserving expanded
2973 * elements
2975 public void refreshViewers() {
2976 syncExec(new Runnable() {
2977 @Override
2978 public void run() {
2979 refreshViewersInternal();
2985 * Refresh the unstaged and staged viewers, preserving expanded elements
2987 public void refreshViewersPreservingExpandedElements() {
2988 syncExec(new Runnable() {
2989 @Override
2990 public void run() {
2991 Object[] unstagedExpanded = unstagedViewer.getVisibleExpandedElements();
2992 Object[] stagedExpanded = stagedViewer.getVisibleExpandedElements();
2993 refreshViewersInternal();
2994 unstagedViewer.setExpandedElements(unstagedExpanded);
2995 stagedViewer.setExpandedElements(stagedExpanded);
3000 private void refreshViewersInternal() {
3001 unstagedViewer.refresh();
3002 stagedViewer.refresh();
3003 updateSectionText();
3006 private IContributionItem createShowInMenu() {
3007 IWorkbenchWindow workbenchWindow = getSite().getWorkbenchWindow();
3008 return UIUtils.createShowInMenu(workbenchWindow);
3011 private class ReplaceAction extends Action {
3013 IStructuredSelection selection;
3014 private final boolean headRevision;
3016 ReplaceAction(String text, @NonNull IStructuredSelection selection,
3017 boolean headRevision) {
3018 super(text);
3019 this.selection = selection;
3020 this.headRevision = headRevision;
3023 private void getSelectedFiles(@NonNull List<String> files,
3024 @NonNull List<String> inaccessibleFiles) {
3025 Iterator iterator = selection.iterator();
3026 while (iterator.hasNext()) {
3027 Object selectedItem = iterator.next();
3028 if (selectedItem instanceof StagingEntry) {
3029 StagingEntry stagingEntry = (StagingEntry) selectedItem;
3030 String path = stagingEntry.getPath();
3031 files.add(path);
3032 IFile resource = stagingEntry.getFile();
3033 if (resource == null || !resource.isAccessible()) {
3034 inaccessibleFiles.add(path);
3040 private void replaceWith(@NonNull List<String> files,
3041 @NonNull List<String> inaccessibleFiles) {
3042 Repository repository = currentRepository;
3043 if (files.isEmpty() || repository == null) {
3044 return;
3046 try (Git git = new Git(repository)) {
3047 CheckoutCommand checkoutCommand = git.checkout();
3048 if (headRevision) {
3049 checkoutCommand.setStartPoint(Constants.HEAD);
3051 for (String path : files) {
3052 checkoutCommand.addPath(path);
3054 checkoutCommand.call();
3055 if (!inaccessibleFiles.isEmpty()) {
3056 IndexDiffCacheEntry indexDiffCacheForRepository = org.eclipse.egit.core.Activator
3057 .getDefault().getIndexDiffCache()
3058 .getIndexDiffCacheEntry(repository);
3059 if (indexDiffCacheForRepository != null) {
3060 indexDiffCacheForRepository
3061 .refreshFiles(inaccessibleFiles);
3064 } catch (Exception e) {
3065 Activator.handleError(UIText.StagingView_checkoutFailed, e,
3066 true);
3070 @Override
3071 public void run() {
3072 String question = UIText.DiscardChangesAction_confirmActionMessage;
3073 ILaunchConfiguration launch = LaunchFinder
3074 .getRunningLaunchConfiguration(
3075 Collections.singleton(getCurrentRepository()),
3076 null);
3077 if (launch != null) {
3078 question = MessageFormat.format(question,
3079 "\n\n" + MessageFormat.format( //$NON-NLS-1$
3080 UIText.LaunchFinder_RunningLaunchMessage,
3081 launch.getName()));
3082 } else {
3083 question = MessageFormat.format(question, ""); //$NON-NLS-1$
3086 MessageDialog dlg = new MessageDialog(form.getShell(),
3087 UIText.DiscardChangesAction_confirmActionTitle, null,
3088 question, MessageDialog.CONFIRM,
3089 new String[] {
3090 UIText.DiscardChangesAction_discardChangesButtonText,
3091 IDialogConstants.CANCEL_LABEL },
3093 if (dlg.open() != Window.OK) {
3094 return;
3096 List<String> files = new ArrayList<>();
3097 List<String> inaccessibleFiles = new ArrayList<>();
3098 getSelectedFiles(files, inaccessibleFiles);
3099 replaceWith(files, inaccessibleFiles);
3103 private static class IgnoreAction extends Action {
3105 private final IStructuredSelection selection;
3107 IgnoreAction(IStructuredSelection selection) {
3108 super(UIText.StagingView_IgnoreItemMenuLabel);
3109 this.selection = selection;
3112 @Override
3113 public void run() {
3114 IgnoreOperationUI operation = new IgnoreOperationUI(
3115 getSelectedPaths(selection));
3116 operation.run();
3120 private static class IgnoreFoldersAction extends Action {
3121 private final Set<StagingFolderEntry> selection;
3123 IgnoreFoldersAction(Set<StagingFolderEntry> selection) {
3124 super(UIText.StagingView_IgnoreFolderMenuLabel);
3125 this.selection = selection;
3128 @Override
3129 public void run() {
3130 List<IPath> paths = new ArrayList<>();
3131 for (StagingFolderEntry folder : selection) {
3132 paths.add(folder.getLocation());
3134 IgnoreOperationUI operation = new IgnoreOperationUI(paths);
3135 operation.run();
3140 private class DeleteAction extends Action {
3142 private final IStructuredSelection selection;
3144 DeleteAction(IStructuredSelection selection) {
3145 super(UIText.StagingView_DeleteItemMenuLabel,
3146 UIIcons.ELCL16_DELETE);
3147 this.selection = selection;
3150 @Override
3151 public void run() {
3152 DeletePathsOperationUI operation = new DeletePathsOperationUI(
3153 getSelectedPaths(selection), getSite());
3154 operation.run();
3158 private class GlobalDeleteActionHandler extends Action {
3160 @Override
3161 public void run() {
3162 DeletePathsOperationUI operation = new DeletePathsOperationUI(
3163 getSelectedPaths(getSelection()), getSite());
3164 operation.run();
3167 @Override
3168 public boolean isEnabled() {
3169 if (!unstagedViewer.getTree().isFocusControl())
3170 return false;
3172 IStructuredSelection selection = getSelection();
3173 if (selection.isEmpty())
3174 return false;
3176 for (Object element : selection.toList()) {
3177 if (!(element instanceof StagingEntry))
3178 return false;
3179 StagingEntry entry = (StagingEntry) element;
3180 if (!entry.getAvailableActions().contains(StagingEntry.Action.DELETE))
3181 return false;
3184 return true;
3187 private IStructuredSelection getSelection() {
3188 return (IStructuredSelection) unstagedViewer.getSelection();
3192 private static List<IPath> getSelectedPaths(IStructuredSelection selection) {
3193 List<IPath> paths = new ArrayList<>();
3194 Iterator iterator = selection.iterator();
3195 while (iterator.hasNext()) {
3196 StagingEntry stagingEntry = (StagingEntry) iterator.next();
3197 paths.add(stagingEntry.getLocation());
3199 return paths;
3203 * @param selection
3204 * @return true if the selection includes a non-workspace resource, false otherwise
3206 private boolean selectionIncludesNonWorkspaceResources(ISelection selection) {
3207 if (!(selection instanceof IStructuredSelection))
3208 return false;
3209 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
3210 Iterator iterator = structuredSelection.iterator();
3211 while (iterator.hasNext()) {
3212 Object selectedObject = iterator.next();
3213 if (!(selectedObject instanceof StagingEntry))
3214 return false;
3215 StagingEntry stagingEntry = (StagingEntry) selectedObject;
3216 IFile file = stagingEntry.getFile();
3217 if (file == null || !file.isAccessible()) {
3218 return true;
3221 return false;
3224 private void openSelectionInEditor(ISelection s) {
3225 Repository repo = currentRepository;
3226 if (repo == null || s.isEmpty() || !(s instanceof IStructuredSelection)) {
3227 return;
3229 final IStructuredSelection iss = (IStructuredSelection) s;
3230 for (Object element : iss.toList()) {
3231 if (element instanceof StagingEntry) {
3232 StagingEntry entry = (StagingEntry) element;
3233 String relativePath = entry.getPath();
3234 File file = new Path(repo.getWorkTree().getAbsolutePath())
3235 .append(relativePath).toFile();
3236 DiffViewer.openFileInEditor(file, -1);
3241 private static Set<StagingEntry.Action> getAvailableActions(IStructuredSelection selection) {
3242 Set<StagingEntry.Action> availableActions = EnumSet.noneOf(StagingEntry.Action.class);
3243 for (Iterator it = selection.iterator(); it.hasNext(); ) {
3244 StagingEntry stagingEntry = (StagingEntry) it.next();
3245 if (availableActions.isEmpty())
3246 availableActions.addAll(stagingEntry.getAvailableActions());
3247 else
3248 availableActions.retainAll(stagingEntry.getAvailableActions());
3250 return availableActions;
3253 private IAction createItem(String text, final String commandId,
3254 final IStructuredSelection selection) {
3255 return new Action(text) {
3256 @Override
3257 public void run() {
3258 CommonUtils.runCommand(commandId, selection);
3263 private boolean shouldUpdateSelection() {
3264 return !isDisposed() && !isViewHidden && reactOnSelection;
3267 private void reactOnSelection(StructuredSelection selection) {
3268 if (selection.size() != 1 || isDisposed()) {
3269 return;
3271 if (!shouldUpdateSelection()) {
3272 // Remember it all the same to be able to update the view when it
3273 // becomes active again
3274 lastSelection = reactOnSelection ? selection : null;
3275 return;
3277 lastSelection = null;
3278 Object firstElement = selection.getFirstElement();
3279 if (firstElement instanceof RepositoryTreeNode) {
3280 RepositoryTreeNode repoNode = (RepositoryTreeNode) firstElement;
3281 if (currentRepository != repoNode.getRepository()) {
3282 reload(repoNode.getRepository());
3284 } else if (firstElement instanceof Repository) {
3285 Repository repo = (Repository) firstElement;
3286 if (currentRepository != repo) {
3287 reload(repo);
3289 } else {
3290 Repository repo = AdapterUtils.adapt(firstElement,
3291 Repository.class);
3292 if (repo != null) {
3293 if (currentRepository != repo) {
3294 reload(repo);
3296 } else {
3297 IResource resource = AdapterUtils
3298 .adaptToAnyResource(firstElement);
3299 if (resource != null) {
3300 showResource(resource);
3306 private void showResource(final IResource resource) {
3307 if (resource == null || !resource.isAccessible()) {
3308 return;
3310 Job.getJobManager().cancel(JobFamilies.UPDATE_SELECTION);
3311 Job job = new Job(UIText.StagingView_GetRepo) {
3312 @Override
3313 protected IStatus run(IProgressMonitor monitor) {
3314 if (monitor.isCanceled()) {
3315 return Status.CANCEL_STATUS;
3317 RepositoryMapping mapping = RepositoryMapping
3318 .getMapping(resource);
3319 if (mapping != null) {
3320 Repository newRep = mapping.getRepository();
3321 if (newRep != null && newRep != currentRepository) {
3322 if (monitor.isCanceled()) {
3323 return Status.CANCEL_STATUS;
3325 reload(newRep);
3328 return Status.OK_STATUS;
3331 @Override
3332 public boolean belongsTo(Object family) {
3333 return JobFamilies.UPDATE_SELECTION == family;
3336 @Override
3337 public boolean shouldRun() {
3338 return shouldUpdateSelection();
3341 job.setSystem(true);
3342 schedule(job, false);
3345 private void stage(IStructuredSelection selection) {
3346 StagingViewContentProvider contentProvider = getContentProvider(unstagedViewer);
3347 final Repository repository = currentRepository;
3348 Iterator iterator = selection.iterator();
3349 final List<String> addPaths = new ArrayList<>();
3350 final List<String> rmPaths = new ArrayList<>();
3351 resetPathsToExpand();
3352 while (iterator.hasNext()) {
3353 Object element = iterator.next();
3354 if (element instanceof StagingEntry) {
3355 StagingEntry entry = (StagingEntry) element;
3356 selectEntryForStaging(entry, addPaths, rmPaths);
3357 addPathAndParentPaths(entry.getParentPath(), pathsToExpandInStaged);
3358 } else if (element instanceof StagingFolderEntry) {
3359 StagingFolderEntry folder = (StagingFolderEntry) element;
3360 List<StagingEntry> entries = contentProvider
3361 .getStagingEntriesFiltered(folder);
3362 for (StagingEntry entry : entries)
3363 selectEntryForStaging(entry, addPaths, rmPaths);
3364 addExpandedPathsBelowFolder(folder, unstagedViewer,
3365 pathsToExpandInStaged);
3366 } else {
3367 IResource resource = AdapterUtils.adaptToAnyResource(element);
3368 if (resource != null) {
3369 RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
3370 // doesn't do anything if the current repository is a
3371 // submodule of the mapped repo
3372 if (mapping != null && mapping.getRepository() == currentRepository) {
3373 String path = mapping.getRepoRelativePath(resource);
3374 // If resource corresponds to root of working directory
3375 if ("".equals(path)) //$NON-NLS-1$
3376 addPaths.add("."); //$NON-NLS-1$
3377 else
3378 addPaths.add(path);
3384 // start long running operations
3385 if (!addPaths.isEmpty()) {
3386 Job addJob = new Job(UIText.StagingView_AddJob) {
3387 @Override
3388 protected IStatus run(IProgressMonitor monitor) {
3389 try (Git git = new Git(repository)) {
3390 AddCommand add = git.add();
3391 for (String addPath : addPaths)
3392 add.addFilepattern(addPath);
3393 add.call();
3394 } catch (NoFilepatternException e1) {
3395 // cannot happen
3396 } catch (JGitInternalException e1) {
3397 Activator.handleError(e1.getCause().getMessage(),
3398 e1.getCause(), true);
3399 } catch (Exception e1) {
3400 Activator.handleError(e1.getMessage(), e1, true);
3402 return Status.OK_STATUS;
3405 @Override
3406 public boolean belongsTo(Object family) {
3407 return family == JobFamilies.ADD_TO_INDEX;
3411 schedule(addJob, true);
3414 if (!rmPaths.isEmpty()) {
3415 Job removeJob = new Job(UIText.StagingView_RemoveJob) {
3416 @Override
3417 protected IStatus run(IProgressMonitor monitor) {
3418 try (Git git = new Git(repository)) {
3419 RmCommand rm = git.rm().setCached(true);
3420 for (String rmPath : rmPaths)
3421 rm.addFilepattern(rmPath);
3422 rm.call();
3423 } catch (NoFilepatternException e) {
3424 // cannot happen
3425 } catch (JGitInternalException e) {
3426 Activator.handleError(e.getCause().getMessage(),
3427 e.getCause(), true);
3428 } catch (Exception e) {
3429 Activator.handleError(e.getMessage(), e, true);
3431 return Status.OK_STATUS;
3434 @Override
3435 public boolean belongsTo(Object family) {
3436 return family == JobFamilies.REMOVE_FROM_INDEX;
3440 schedule(removeJob, true);
3444 private void selectEntryForStaging(StagingEntry entry,
3445 List<String> addPaths, List<String> rmPaths) {
3446 switch (entry.getState()) {
3447 case ADDED:
3448 case CHANGED:
3449 case REMOVED:
3450 // already staged
3451 break;
3452 case CONFLICTING:
3453 case MODIFIED:
3454 case MODIFIED_AND_CHANGED:
3455 case MODIFIED_AND_ADDED:
3456 case UNTRACKED:
3457 addPaths.add(entry.getPath());
3458 break;
3459 case MISSING:
3460 case MISSING_AND_CHANGED:
3461 rmPaths.add(entry.getPath());
3462 break;
3466 private void unstage(IStructuredSelection selection) {
3467 if (selection.isEmpty())
3468 return;
3470 final List<String> paths = processUnstageSelection(selection);
3471 if (paths.isEmpty())
3472 return;
3474 final Repository repository = currentRepository;
3476 Job resetJob = new Job(UIText.StagingView_ResetJob) {
3477 @Override
3478 protected IStatus run(IProgressMonitor monitor) {
3479 try (Git git = new Git(repository)) {
3480 ResetCommand reset = git.reset();
3481 for (String path : paths)
3482 reset.addPath(path);
3483 reset.call();
3484 } catch (GitAPIException e) {
3485 Activator.handleError(e.getMessage(), e, true);
3487 return Status.OK_STATUS;
3490 @Override
3491 public boolean belongsTo(Object family) {
3492 return family == JobFamilies.RESET;
3495 schedule(resetJob, true);
3498 private List<String> processUnstageSelection(IStructuredSelection selection) {
3499 List<String> paths = new ArrayList<>();
3500 resetPathsToExpand();
3501 for (Object element : selection.toList()) {
3502 if (element instanceof StagingEntry) {
3503 StagingEntry entry = (StagingEntry) element;
3504 addUnstagePath(entry, paths);
3505 addPathAndParentPaths(entry.getParentPath(), pathsToExpandInUnstaged);
3506 } else if (element instanceof StagingFolderEntry) {
3507 StagingFolderEntry folder = (StagingFolderEntry) element;
3508 List<StagingEntry> entries = getContentProvider(stagedViewer)
3509 .getStagingEntriesFiltered(folder);
3510 for (StagingEntry entry : entries)
3511 addUnstagePath(entry, paths);
3512 addExpandedPathsBelowFolder(folder, stagedViewer,
3513 pathsToExpandInUnstaged);
3516 return paths;
3519 private void addUnstagePath(StagingEntry entry, List<String> paths) {
3520 switch (entry.getState()) {
3521 case ADDED:
3522 case CHANGED:
3523 case REMOVED:
3524 paths.add(entry.getPath());
3525 return;
3526 default:
3527 // unstaged
3531 private void assumeUnchanged(@NonNull IStructuredSelection selection) {
3532 List<IPath> locations = new ArrayList<>();
3533 collectPaths(selection.toList(), locations);
3535 if (locations.isEmpty()) {
3536 return;
3539 JobUtil.scheduleUserJob(
3540 new AssumeUnchangedOperation(currentRepository, locations, true),
3541 UIText.AssumeUnchanged_assumeUnchanged,
3542 JobFamilies.ASSUME_NOASSUME_UNCHANGED);
3545 private void untrack(@NonNull IStructuredSelection selection) {
3546 List<IPath> locations = new ArrayList<>();
3547 collectPaths(selection.toList(), locations);
3549 if (locations.isEmpty()) {
3550 return;
3553 JobUtil.scheduleUserJob(
3554 new UntrackOperation(currentRepository, locations),
3555 UIText.Untrack_untrack, JobFamilies.UNTRACK);
3558 private void collectPaths(Object o, List<IPath> result) {
3559 if (o instanceof Iterable<?>) {
3560 ((Iterable<?>) o).forEach(child -> collectPaths(child, result));
3561 } else if (o instanceof StagingFolderEntry) {
3562 StagingViewContentProvider contentProvider = getContentProvider(unstagedViewer);
3563 List<StagingEntry> entries = contentProvider
3564 .getStagingEntriesFiltered((StagingFolderEntry) o);
3565 collectPaths(entries, result);
3566 } else if (o instanceof StagingEntry) {
3567 result.add(AdapterUtils.adapt(o, IPath.class));
3571 private void resetPathsToExpand() {
3572 pathsToExpandInStaged = new HashSet<>();
3573 pathsToExpandInUnstaged = new HashSet<>();
3576 private static void addExpandedPathsBelowFolder(StagingFolderEntry folder,
3577 TreeViewer treeViewer, Set<IPath> addToSet) {
3578 Object[] expandedElements = treeViewer.getVisibleExpandedElements();
3579 for (Object expandedElement : expandedElements) {
3580 if (expandedElement instanceof StagingFolderEntry) {
3581 StagingFolderEntry expandedFolder = (StagingFolderEntry) expandedElement;
3582 if (folder.getPath().isPrefixOf(
3583 expandedFolder.getPath()))
3584 addPathAndParentPaths(expandedFolder.getPath(), addToSet);
3589 private static void addPathAndParentPaths(IPath initialPath, Set<IPath> addToSet) {
3590 for (IPath p = initialPath; p.segmentCount() >= 1; p = p
3591 .removeLastSegments(1))
3592 addToSet.add(p);
3595 private boolean isValidRepo(final Repository repository) {
3596 return repository != null
3597 && !repository.isBare()
3598 && repository.getWorkTree().exists();
3602 * Clear the view's state.
3603 * <p>
3604 * This method must be called from the UI-thread
3606 * @param repository
3608 private void clearRepository(@Nullable Repository repository) {
3609 saveCommitMessageComponentState();
3610 realRepository = repository;
3611 currentRepository = null;
3612 if (isDisposed()) {
3613 return;
3615 StagingViewUpdate update = new StagingViewUpdate(null, null, null);
3616 setStagingViewerInput(unstagedViewer, update, null, null);
3617 setStagingViewerInput(stagedViewer, update, null, null);
3618 enableCommitWidgets(false);
3619 refreshAction.setEnabled(false);
3620 updateSectionText();
3621 if (repository != null && repository.isBare()) {
3622 form.setText(UIText.StagingView_BareRepoSelection);
3623 } else {
3624 form.setText(UIText.StagingView_NoSelectionTitle);
3626 updateIgnoreErrorsButtonVisibility();
3627 updateRebaseButtonVisibility(false);
3628 // Force a selection changed event
3629 unstagedViewer.setSelection(unstagedViewer.getSelection());
3633 * Show rebase buttons only if a rebase operation is in progress
3635 * @param isRebasing
3636 * {@code}true if rebase is in progress
3638 protected void updateRebaseButtonVisibility(final boolean isRebasing) {
3639 asyncExec(() -> {
3640 showControl(rebaseSection, isRebasing);
3641 rebaseSection.getParent().layout(true);
3645 private static void showControl(Control c, final boolean show) {
3646 c.setVisible(show);
3647 GridData g = (GridData) c.getLayoutData();
3648 g.exclude = !show;
3652 * @param isAmending
3653 * if the current commit should be amended
3655 public void setAmending(boolean isAmending) {
3656 if (isDisposed())
3657 return;
3658 if (amendPreviousCommitAction.isChecked() != isAmending) {
3659 amendPreviousCommitAction.setChecked(isAmending);
3660 amendPreviousCommitAction.run();
3665 * @param message
3666 * commit message to set for current repository
3668 public void setCommitMessage(String message) {
3669 commitMessageText.setText(message);
3673 * Reload the staging view asynchronously
3675 * @param repository
3677 public void reload(final Repository repository) {
3678 if (isDisposed()) {
3679 return;
3681 if (repository == null) {
3682 asyncUpdate(() -> clearRepository(null));
3683 return;
3686 if (!isValidRepo(repository)) {
3687 asyncUpdate(() -> clearRepository(repository));
3688 return;
3691 final boolean repositoryChanged = currentRepository != repository;
3692 realRepository = repository;
3693 currentRepository = repository;
3695 asyncUpdate(() -> {
3696 if (isDisposed()) {
3697 return;
3700 final IndexDiffData indexDiff = doReload(repository);
3701 boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
3702 boolean noConflicts = noConflicts(indexDiff);
3704 if (repositoryChanged) {
3705 // Reset paths, they're from the old repository
3706 resetPathsToExpand();
3707 if (refsChangedListener != null)
3708 refsChangedListener.remove();
3709 refsChangedListener = repository.getListenerList()
3710 .addRefsChangedListener(
3711 event -> updateRebaseButtonVisibility(repository
3712 .getRepositoryState().isRebasing()));
3713 if (configChangedListener != null) {
3714 configChangedListener.remove();
3716 configChangedListener = repository.getListenerList()
3717 .addConfigChangedListener(
3718 event -> resetCommitMessageComponent());
3720 final StagingViewUpdate update = new StagingViewUpdate(repository,
3721 indexDiff, null);
3722 Object[] unstagedExpanded = unstagedViewer
3723 .getVisibleExpandedElements();
3724 Object[] stagedExpanded = stagedViewer.getVisibleExpandedElements();
3726 int unstagedElementsCount = updateAutoExpand(unstagedViewer,
3727 getUnstaged(indexDiff));
3728 int stagedElementsCount = updateAutoExpand(stagedViewer,
3729 getStaged(indexDiff));
3730 int elementsCount = unstagedElementsCount + stagedElementsCount;
3732 if (elementsCount > getMaxLimitForListMode()) {
3733 listPresentationAction.setEnabled(false);
3734 if (presentation == Presentation.LIST) {
3735 compactTreePresentationAction.setChecked(true);
3736 switchToCompactModeInternal(true);
3737 } else {
3738 setExpandCollapseActionsVisible(false,
3739 unstagedElementsCount <= getMaxLimitForListMode(),
3740 true);
3741 setExpandCollapseActionsVisible(true,
3742 stagedElementsCount <= getMaxLimitForListMode(),
3743 true);
3745 } else {
3746 listPresentationAction.setEnabled(true);
3747 boolean changed = getPreferenceStore().getBoolean(
3748 UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
3749 if (changed) {
3750 listPresentationAction.setChecked(true);
3751 switchToListMode();
3752 } else if (presentation != Presentation.LIST) {
3753 setExpandCollapseActionsVisible(false, true, true);
3754 setExpandCollapseActionsVisible(true, true, true);
3758 setStagingViewerInput(unstagedViewer, update, unstagedExpanded,
3759 pathsToExpandInUnstaged);
3760 setStagingViewerInput(stagedViewer, update, stagedExpanded,
3761 pathsToExpandInStaged);
3762 resetPathsToExpand();
3763 // Force a selection changed event
3764 unstagedViewer.setSelection(unstagedViewer.getSelection());
3765 refreshAction.setEnabled(true);
3767 updateRebaseButtonVisibility(
3768 repository.getRepositoryState().isRebasing());
3770 updateIgnoreErrorsButtonVisibility();
3772 boolean rebaseContinueEnabled = indexDiffAvailable
3773 && repository.getRepositoryState().isRebasing()
3774 && noConflicts;
3775 rebaseContinueButton.setEnabled(rebaseContinueEnabled);
3777 form.setText(GitLabels.getStyledLabelSafe(repository).toString());
3778 updateCommitMessageComponent(repositoryChanged, indexDiffAvailable);
3779 enableCommitWidgets(indexDiffAvailable && noConflicts);
3781 updateCommitButtons();
3782 updateSectionText();
3787 * The max number of changed files we can handle in the "list" presentation
3788 * without freezing Eclipse UI for a too long time.
3790 * @return default is 10000
3792 private int getMaxLimitForListMode() {
3793 return Activator.getDefault().getPreferenceStore()
3794 .getInt(UIPreferences.STAGING_VIEW_MAX_LIMIT_LIST_MODE);
3797 private static int getUnstaged(@Nullable IndexDiffData indexDiff) {
3798 if (indexDiff == null) {
3799 return 0;
3801 int size = indexDiff.getUntracked().size();
3802 size += indexDiff.getMissing().size();
3803 size += indexDiff.getModified().size();
3804 size += indexDiff.getConflicting().size();
3805 return size;
3808 private static int getStaged(@Nullable IndexDiffData indexDiff) {
3809 if (indexDiff == null) {
3810 return 0;
3812 int size = indexDiff.getAdded().size();
3813 size += indexDiff.getChanged().size();
3814 size += indexDiff.getRemoved().size();
3815 return size;
3818 private int updateAutoExpand(TreeViewer viewer, int newSize) {
3819 if (newSize > getMaxLimitForListMode()) {
3820 // auto expand with too many nodes freezes eclipse
3821 disableAutoExpand(viewer);
3823 return newSize;
3826 private void switchToCompactModeInternal(boolean auto) {
3827 setPresentation(Presentation.COMPACT_TREE, auto);
3828 listPresentationAction.setChecked(false);
3829 treePresentationAction.setChecked(false);
3830 if (auto) {
3831 setExpandCollapseActionsVisible(false, false, true);
3832 setExpandCollapseActionsVisible(true, false, true);
3833 } else {
3834 setExpandCollapseActionsVisible(false, isExpandAllowed(false),
3835 true);
3836 setExpandCollapseActionsVisible(true, isExpandAllowed(true), true);
3840 private void switchToListMode() {
3841 setPresentation(Presentation.LIST, false);
3842 treePresentationAction.setChecked(false);
3843 compactTreePresentationAction.setChecked(false);
3844 setExpandCollapseActionsVisible(false, false, false);
3845 setExpandCollapseActionsVisible(true, false, false);
3848 private static boolean noConflicts(IndexDiffData indexDiff) {
3849 return indexDiff == null ? true : indexDiff.getConflicting().isEmpty();
3852 private static boolean indexDiffAvailable(IndexDiffData indexDiff) {
3853 return indexDiff == null ? false : true;
3856 private boolean hasErrorsOrWarnings() {
3857 return getPreferenceStore()
3858 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
3859 ? (getProblemsSeverity() >= Integer
3860 .parseInt(getPreferenceStore()
3861 .getString(UIPreferences.WARN_BEFORE_COMMITTING_LEVEL))
3862 && !ignoreErrors.getSelection()) : false;
3865 private boolean isCommitBlocked() {
3866 return getPreferenceStore()
3867 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
3868 && getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT)
3869 ? (getProblemsSeverity() >= Integer
3870 .parseInt(getPreferenceStore().getString(
3871 UIPreferences.BLOCK_COMMIT_LEVEL))
3872 && !ignoreErrors.getSelection())
3873 : false;
3876 private IndexDiffData doReload(@NonNull final Repository repository) {
3877 IndexDiffCacheEntry entry = org.eclipse.egit.core.Activator.getDefault()
3878 .getIndexDiffCache().getIndexDiffCacheEntry(repository);
3880 if(cacheEntry != null && cacheEntry != entry)
3881 cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
3883 cacheEntry = entry;
3884 cacheEntry.addIndexDiffChangedListener(myIndexDiffListener);
3886 return cacheEntry.getIndexDiff();
3889 private void expandPreviousExpandedAndPaths(Object[] previous,
3890 TreeViewer viewer, Set<IPath> additionalPaths) {
3892 StagingViewContentProvider stagedContentProvider = getContentProvider(
3893 viewer);
3894 int count = stagedContentProvider.getCount();
3895 updateAutoExpand(viewer, count);
3897 // Auto-expand is on, so don't change expanded items
3898 if (viewer.getAutoExpandLevel() == AbstractTreeViewer.ALL_LEVELS) {
3899 return;
3902 // No need to expand anything
3903 if (getPresentation() == Presentation.LIST)
3904 return;
3906 Set<IPath> paths = new HashSet<>(additionalPaths);
3907 // Instead of just expanding the previous elements directly, also expand
3908 // all parent paths. This makes it work in case of "re-folding" of
3909 // compact tree.
3910 for (Object element : previous) {
3911 if (element instanceof StagingFolderEntry) {
3912 addPathAndParentPaths(((StagingFolderEntry) element).getPath(), paths);
3915 // Also consider the currently expanded elements because auto selection
3916 // could have expanded some elements.
3917 for (Object element : viewer.getVisibleExpandedElements()) {
3918 if (element instanceof StagingFolderEntry) {
3919 addPathAndParentPaths(((StagingFolderEntry) element).getPath(),
3920 paths);
3924 List<StagingFolderEntry> expand = new ArrayList<>();
3926 calculateNodesToExpand(paths, stagedContentProvider.getElements(null),
3927 expand);
3928 viewer.setExpandedElements(expand.toArray());
3931 private void calculateNodesToExpand(Set<IPath> paths, Object[] elements,
3932 List<StagingFolderEntry> result) {
3933 if (elements == null)
3934 return;
3936 for (Object element : elements) {
3937 if (element instanceof StagingFolderEntry) {
3938 StagingFolderEntry folder = (StagingFolderEntry) element;
3939 if (paths.contains(folder.getPath())) {
3940 result.add(folder);
3941 // Only recurs if folder matched (i.e. don't try to expand
3942 // children of unexpanded parents)
3943 calculateNodesToExpand(paths, folder.getChildren(), result);
3949 private void clearCommitMessageToggles() {
3950 amendPreviousCommitAction.setChecked(false);
3951 addChangeIdAction.setChecked(false);
3952 signedOffByAction.setChecked(false);
3955 void updateCommitMessageComponent(boolean repositoryChanged, boolean indexDiffAvailable) {
3956 if (repositoryChanged)
3957 if (commitMessageComponent.isAmending()
3958 || userEnteredCommitMessage())
3959 saveCommitMessageComponentState();
3960 else
3961 deleteCommitMessageComponentState();
3962 if (!indexDiffAvailable)
3963 return; // only try to restore the stored repo commit message if
3964 // indexDiff is ready
3966 CommitHelper helper = new CommitHelper(currentRepository);
3967 CommitMessageComponentState oldState = null;
3968 if (repositoryChanged
3969 || commitMessageComponent.getRepository() != currentRepository) {
3970 oldState = loadCommitMessageComponentState();
3971 commitMessageComponent.setRepository(currentRepository);
3972 if (oldState == null)
3973 loadInitialState(helper);
3974 else
3975 loadExistingState(helper, oldState);
3976 } else { // repository did not change
3977 if (!commitMessageComponent.getHeadCommit().equals(
3978 helper.getPreviousCommit())
3979 || !commitMessageComponent.isAmending()) {
3980 if (!commitMessageComponent.isAmending()
3981 && userEnteredCommitMessage())
3982 addHeadChangedWarning(commitMessageComponent
3983 .getCommitMessage());
3984 else
3985 loadInitialState(helper);
3988 amendPreviousCommitAction.setChecked(commitMessageComponent
3989 .isAmending());
3990 amendPreviousCommitAction.setEnabled(helper.amendAllowed());
3991 updateMessage();
3995 * Resets the commit message component state and saves the overwritten
3996 * commit message into message history
3998 public void resetCommitMessageComponent() {
3999 if (currentRepository != null) {
4000 String commitMessage = commitMessageComponent.getCommitMessage();
4001 if (commitMessage.trim().length() > 0) {
4002 CommitMessageHistory.saveCommitHistory(commitMessage);
4004 loadInitialState(new CommitHelper(currentRepository));
4008 private void loadExistingState(CommitHelper helper,
4009 CommitMessageComponentState oldState) {
4010 boolean headCommitChanged = !oldState.getHeadCommit().equals(
4011 getCommitId(helper.getPreviousCommit()));
4012 commitMessageComponent.enableListeners(false);
4013 commitMessageComponent.setAuthor(oldState.getAuthor());
4014 if (headCommitChanged) {
4015 addHeadChangedWarning(oldState.getCommitMessage());
4016 } else {
4017 commitMessageComponent
4018 .setCommitMessage(oldState.getCommitMessage());
4019 commitMessageComponent
4020 .setCaretPosition(oldState.getCaretPosition());
4022 commitMessageComponent.setCommitter(oldState.getCommitter());
4023 commitMessageComponent.setHeadCommit(getCommitId(helper
4024 .getPreviousCommit()));
4025 commitMessageComponent.setCommitAllowed(helper.canCommit());
4026 commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
4027 boolean amendAllowed = helper.amendAllowed();
4028 commitMessageComponent.setAmendAllowed(amendAllowed);
4029 if (!amendAllowed)
4030 commitMessageComponent.setAmending(false);
4031 else if (!headCommitChanged && oldState.getAmend())
4032 commitMessageComponent.setAmending(true);
4033 else
4034 commitMessageComponent.setAmending(false);
4035 commitMessageComponent.updateUIFromState();
4036 commitMessageComponent.updateSignedOffAndChangeIdButton();
4037 commitMessageComponent.enableListeners(true);
4040 private void addHeadChangedWarning(String commitMessage) {
4041 if (!commitMessage.startsWith(UIText.StagingView_headCommitChanged)) {
4042 String message = UIText.StagingView_headCommitChanged
4043 + Text.DELIMITER + Text.DELIMITER + commitMessage;
4044 commitMessageComponent.setCommitMessage(message);
4048 private void loadInitialState(CommitHelper helper) {
4049 commitMessageComponent.enableListeners(false);
4050 commitMessageComponent.resetState();
4051 commitMessageComponent.setAuthor(helper.getAuthor());
4052 commitMessageComponent.setCommitMessage(helper.getCommitMessage());
4053 commitMessageComponent.setCommitter(helper.getCommitter());
4054 commitMessageComponent.setHeadCommit(getCommitId(helper
4055 .getPreviousCommit()));
4056 commitMessageComponent.setCommitAllowed(helper.canCommit());
4057 commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
4058 commitMessageComponent.setAmendAllowed(helper.amendAllowed());
4059 commitMessageComponent.setAmending(false);
4060 // set the defaults for change id and signed off buttons.
4061 commitMessageComponent.setDefaults();
4062 commitMessageComponent.updateUI();
4063 commitMessageComponent.enableListeners(true);
4066 private boolean userEnteredCommitMessage() {
4067 if (commitMessageComponent.getRepository() == null)
4068 return false;
4069 String message = commitMessageComponent.getCommitMessage().replace(
4070 UIText.StagingView_headCommitChanged, ""); //$NON-NLS-1$
4071 if (message == null || message.trim().length() == 0)
4072 return false;
4074 String chIdLine = "Change-Id: I" + ObjectId.zeroId().name(); //$NON-NLS-1$
4075 Repository repo = currentRepository;
4076 if (repo != null && GerritUtil.getCreateChangeId(repo.getConfig())
4077 && commitMessageComponent.getCreateChangeId()) {
4078 if (message.trim().equals(chIdLine))
4079 return false;
4081 // change id was added automatically, but there is more in the
4082 // message; strip the id, and check for the signed-off-by tag
4083 message = message.replace(chIdLine, ""); //$NON-NLS-1$
4086 if (org.eclipse.egit.ui.Activator.getDefault().getPreferenceStore()
4087 .getBoolean(UIPreferences.COMMIT_DIALOG_SIGNED_OFF_BY)
4088 && commitMessageComponent.isSignedOff()
4089 && message.trim().equals(
4090 Constants.SIGNED_OFF_BY_TAG
4091 + commitMessageComponent.getCommitter()))
4092 return false;
4094 return true;
4097 private ObjectId getCommitId(RevCommit commit) {
4098 if (commit == null)
4099 return ObjectId.zeroId();
4100 return commit.getId();
4103 private void saveCommitMessageComponentState() {
4104 final Repository repo = commitMessageComponent.getRepository();
4105 if (repo != null)
4106 CommitMessageComponentStateManager.persistState(repo,
4107 commitMessageComponent.getState());
4110 private void deleteCommitMessageComponentState() {
4111 if (commitMessageComponent.getRepository() != null)
4112 CommitMessageComponentStateManager
4113 .deleteState(commitMessageComponent.getRepository());
4116 private CommitMessageComponentState loadCommitMessageComponentState() {
4117 return CommitMessageComponentStateManager.loadState(currentRepository);
4120 private Collection<String> getStagedFileNames() {
4121 StagingViewContentProvider stagedContentProvider = getContentProvider(stagedViewer);
4122 StagingEntry[] entries = stagedContentProvider.getStagingEntries();
4123 List<String> files = new ArrayList<>();
4124 for (StagingEntry entry : entries)
4125 files.add(entry.getPath());
4126 return files;
4129 private void commit(boolean pushUpstream) {
4130 if (!isCommitWithoutFilesAllowed()) {
4131 MessageDialog md = new MessageDialog(getSite().getShell(),
4132 UIText.StagingView_committingNotPossible, null,
4133 UIText.StagingView_noStagedFiles, MessageDialog.ERROR,
4134 new String[] { IDialogConstants.CLOSE_LABEL }, 0);
4135 md.open();
4136 return;
4138 if (!commitMessageComponent.checkCommitInfo())
4139 return;
4141 if (!UIUtils.saveAllEditors(currentRepository,
4142 UIText.StagingView_cancelCommitAfterSaving))
4143 return;
4145 String commitMessage = commitMessageComponent.getCommitMessage();
4146 CommitOperation commitOperation = null;
4147 try {
4148 commitOperation = new CommitOperation(currentRepository,
4149 commitMessageComponent.getAuthor(),
4150 commitMessageComponent.getCommitter(),
4151 commitMessage);
4152 } catch (CoreException e) {
4153 Activator.handleError(UIText.StagingView_commitFailed, e, true);
4154 return;
4156 if (amendPreviousCommitAction.isChecked())
4157 commitOperation.setAmending(true);
4158 final boolean gerritMode = addChangeIdAction.isChecked();
4159 commitOperation.setComputeChangeId(gerritMode);
4161 PushMode pushMode = null;
4162 if (pushUpstream) {
4163 pushMode = gerritMode ? PushMode.GERRIT : PushMode.UPSTREAM;
4165 Job commitJob = new CommitJob(currentRepository, commitOperation)
4166 .setOpenCommitEditor(openNewCommitsAction.isChecked())
4167 .setPushUpstream(pushMode);
4169 // don't allow to do anything as long as commit is in progress
4170 enableAllWidgets(false);
4171 commitJob.addJobChangeListener(new JobChangeAdapter() {
4173 @Override
4174 public void done(IJobChangeEvent event) {
4175 asyncExec(() -> {
4176 enableAllWidgets(true);
4177 if (event.getResult().isOK()) {
4178 commitMessageText.setText(EMPTY_STRING);
4184 schedule(commitJob, true);
4186 CommitMessageHistory.saveCommitHistory(commitMessage);
4187 clearCommitMessageToggles();
4191 * Schedule given job in context of the current view. The view will indicate
4192 * progress as long as job is running.
4194 * @param job
4195 * non null
4196 * @param useRepositoryRule
4197 * true to use current repository rule for the given job, false
4198 * to not enforce any rule on the job
4200 private void schedule(Job job, boolean useRepositoryRule) {
4201 if (useRepositoryRule)
4202 job.setRule(RuleUtil.getRule(currentRepository));
4203 IWorkbenchSiteProgressService service = CommonUtils.getService(getSite(), IWorkbenchSiteProgressService.class);
4204 if (service != null)
4205 service.schedule(job, 0, true);
4206 else
4207 job.schedule();
4210 private boolean isCommitWithoutFilesAllowed() {
4211 if (stagedViewer.getTree().getItemCount() > 0)
4212 return true;
4214 if (amendPreviousCommitAction.isChecked())
4215 return true;
4217 return CommitHelper.isCommitWithoutFilesAllowed(currentRepository);
4220 @Override
4221 public void setFocus() {
4222 Tree tree = unstagedViewer.getTree();
4223 if (tree.getItemCount() > 0 && !isAutoStageOnCommitEnabled()) {
4224 unstagedViewer.getControl().setFocus();
4225 return;
4227 commitMessageText.setFocus();
4230 private boolean isAutoStageOnCommitEnabled() {
4231 IPreferenceStore uiPreferences = Activator.getDefault()
4232 .getPreferenceStore();
4233 return uiPreferences.getBoolean(UIPreferences.AUTO_STAGE_ON_COMMIT);
4236 @Override
4237 public void dispose() {
4238 super.dispose();
4240 ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class);
4241 srv.removePostSelectionListener(selectionChangedListener);
4242 CommonUtils.getService(getSite(), IPartService.class)
4243 .removePartListener(partListener);
4245 if (cacheEntry != null) {
4246 cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
4249 if (undoRedoActionGroup != null) {
4250 undoRedoActionGroup.dispose();
4253 InstanceScope.INSTANCE.getNode(
4254 org.eclipse.egit.core.Activator.getPluginId())
4255 .removePreferenceChangeListener(prefListener);
4256 if (refsChangedListener != null) {
4257 refsChangedListener.remove();
4259 if (configChangedListener != null) {
4260 configChangedListener.remove();
4263 if (switchRepositoriesAction != null) {
4264 switchRepositoriesAction.dispose();
4265 switchRepositoriesAction = null;
4268 getPreferenceStore().removePropertyChangeListener(uiPrefsListener);
4270 getDialogSettings().put(STORE_SORT_STATE, sortAction.isChecked());
4272 currentRepository = null;
4273 lastSelection = null;
4274 disposed = true;
4277 private boolean isDisposed() {
4278 return disposed;
4281 private static void syncExec(Runnable runnable) {
4282 PlatformUI.getWorkbench().getDisplay().syncExec(runnable);
4285 private void asyncExec(Runnable runnable) {
4286 if (!isDisposed()) {
4287 PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
4288 if (!isDisposed()) {
4289 runnable.run();
4295 private void asyncUpdate(Runnable runnable) {
4296 if (isDisposed()) {
4297 return;
4299 Job update = new WorkbenchJob(UIText.StagingView_LoadJob) {
4301 @Override
4302 public IStatus runInUIThread(IProgressMonitor monitor) {
4303 try {
4304 runnable.run();
4305 return Status.OK_STATUS;
4306 } catch (Exception e) {
4307 return Activator.createErrorStatus(e.getLocalizedMessage(),
4312 @Override
4313 public boolean shouldSchedule() {
4314 return super.shouldSchedule() && !isDisposed();
4317 @Override
4318 public boolean shouldRun() {
4319 return super.shouldRun() && !isDisposed();
4322 @Override
4323 public boolean belongsTo(Object family) {
4324 return family == JobFamilies.STAGING_VIEW_RELOAD
4325 || super.belongsTo(family);
4328 update.setSystem(true);
4329 update.schedule();
4333 * This comparator sorts the {@link StagingEntry}s alphabetically or groups
4334 * them by state. If grouped by state the entries in the same group are also
4335 * ordered alphabetically.
4337 private static class StagingEntryComparator extends ViewerComparator {
4339 private boolean alphabeticSort;
4341 private Comparator<String> comparator;
4343 private boolean fileNamesFirst;
4345 private StagingEntryComparator(boolean alphabeticSort,
4346 boolean fileNamesFirst) {
4347 this.alphabeticSort = alphabeticSort;
4348 this.setFileNamesFirst(fileNamesFirst);
4349 comparator = CommonUtils.STRING_ASCENDING_COMPARATOR;
4352 public boolean isFileNamesFirst() {
4353 return fileNamesFirst;
4356 public void setFileNamesFirst(boolean fileNamesFirst) {
4357 this.fileNamesFirst = fileNamesFirst;
4360 private void setAlphabeticSort(boolean sort) {
4361 this.alphabeticSort = sort;
4364 private boolean isAlphabeticSort() {
4365 return alphabeticSort;
4368 @Override
4369 public int category(Object element) {
4370 if (!isAlphabeticSort()) {
4371 StagingEntry stagingEntry = getStagingEntry(element);
4372 if (stagingEntry != null) {
4373 return getState(stagingEntry);
4376 return super.category(element);
4379 @Override
4380 public int compare(Viewer viewer, Object e1, Object e2) {
4381 int cat1 = category(e1);
4382 int cat2 = category(e2);
4384 if (cat1 != cat2) {
4385 return cat1 - cat2;
4388 String name1 = getStagingEntryText(e1);
4389 String name2 = getStagingEntryText(e2);
4391 return comparator.compare(name1, name2);
4394 private String getStagingEntryText(Object element) {
4395 String text = ""; //$NON-NLS-1$
4396 StagingEntry stagingEntry = getStagingEntry(element);
4397 if (stagingEntry != null) {
4398 if (isFileNamesFirst()) {
4399 text = stagingEntry.getName();
4400 } else {
4401 text = stagingEntry.getPath();
4404 return text;
4407 @Nullable
4408 private StagingEntry getStagingEntry(Object element) {
4409 StagingEntry entry = null;
4410 if (element instanceof StagingEntry) {
4411 entry = (StagingEntry) element;
4413 if (element instanceof TreeItem) {
4414 TreeItem item = (TreeItem) element;
4415 if (item.getData() instanceof StagingEntry) {
4416 entry = (StagingEntry) item.getData();
4419 return entry;
4422 private int getState(StagingEntry entry) {
4423 switch (entry.getState()) {
4424 case CONFLICTING:
4425 return 1;
4426 case MODIFIED:
4427 return 2;
4428 case MODIFIED_AND_ADDED:
4429 return 3;
4430 case MODIFIED_AND_CHANGED:
4431 return 4;
4432 case ADDED:
4433 return 5;
4434 case CHANGED:
4435 return 6;
4436 case MISSING:
4437 return 7;
4438 case MISSING_AND_CHANGED:
4439 return 8;
4440 case REMOVED:
4441 return 9;
4442 case UNTRACKED:
4443 return 10;
4444 default:
4445 return super.category(entry);