StagingView wrongly sorted by state initially
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / staging / StagingView.java
blobb76f0b478561d1c1da8bad75784017eb9b025404
1 /*******************************************************************************
2 * Copyright (C) 2011, 2018 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 2.0
9 * which accompanies this distribution, and is available at
10 * https://www.eclipse.org/legal/epl-2.0/
12 * SPDX-License-Identifier: EPL-2.0
14 * Contributors:
15 * Tobias Baumann <tobbaumann@gmail.com> - Bug 373969, 473544
16 * Thomas Wolf <thomas.wolf@paranor.ch>
17 * Tobias Hein <th.mailinglists@googlemail.com> - Bug 499697
18 * Ralf M Petter <ralf.petter@gmail.com> - Bug 509945
19 *******************************************************************************/
20 package org.eclipse.egit.ui.internal.staging;
22 import static org.eclipse.egit.ui.internal.CommonUtils.runCommand;
24 import java.io.File;
25 import java.io.IOException;
26 import java.text.MessageFormat;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.EnumSet;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.LinkedHashMap;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.function.Consumer;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
43 import org.eclipse.core.commands.ExecutionException;
44 import org.eclipse.core.commands.operations.IUndoContext;
45 import org.eclipse.core.resources.IContainer;
46 import org.eclipse.core.resources.IFile;
47 import org.eclipse.core.resources.IMarker;
48 import org.eclipse.core.resources.IResource;
49 import org.eclipse.core.resources.ResourcesPlugin;
50 import org.eclipse.core.runtime.CoreException;
51 import org.eclipse.core.runtime.IPath;
52 import org.eclipse.core.runtime.IProgressMonitor;
53 import org.eclipse.core.runtime.IStatus;
54 import org.eclipse.core.runtime.Path;
55 import org.eclipse.core.runtime.Status;
56 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
57 import org.eclipse.core.runtime.jobs.Job;
58 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
59 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
60 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
61 import org.eclipse.core.runtime.preferences.InstanceScope;
62 import org.eclipse.debug.core.ILaunchConfiguration;
63 import org.eclipse.egit.core.AdapterUtils;
64 import org.eclipse.egit.core.RepositoryUtil;
65 import org.eclipse.egit.core.internal.gerrit.GerritUtil;
66 import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry;
67 import org.eclipse.egit.core.internal.indexdiff.IndexDiffChangedListener;
68 import org.eclipse.egit.core.internal.indexdiff.IndexDiffData;
69 import org.eclipse.egit.core.internal.job.JobUtil;
70 import org.eclipse.egit.core.internal.job.RuleUtil;
71 import org.eclipse.egit.core.op.AssumeUnchangedOperation;
72 import org.eclipse.egit.core.op.CommitOperation;
73 import org.eclipse.egit.core.op.UntrackOperation;
74 import org.eclipse.egit.core.project.RepositoryMapping;
75 import org.eclipse.egit.ui.Activator;
76 import org.eclipse.egit.ui.JobFamilies;
77 import org.eclipse.egit.ui.UIPreferences;
78 import org.eclipse.egit.ui.UIUtils;
79 import org.eclipse.egit.ui.internal.ActionUtils;
80 import org.eclipse.egit.ui.internal.CommonUtils;
81 import org.eclipse.egit.ui.internal.GitLabels;
82 import org.eclipse.egit.ui.internal.UIIcons;
83 import org.eclipse.egit.ui.internal.UIText;
84 import org.eclipse.egit.ui.internal.actions.ActionCommands;
85 import org.eclipse.egit.ui.internal.actions.BooleanPrefAction;
86 import org.eclipse.egit.ui.internal.actions.ReplaceWithOursTheirsMenu;
87 import org.eclipse.egit.ui.internal.branch.LaunchFinder;
88 import org.eclipse.egit.ui.internal.commands.shared.AbortRebaseCommand;
89 import org.eclipse.egit.ui.internal.commands.shared.AbstractRebaseCommandHandler;
90 import org.eclipse.egit.ui.internal.commands.shared.ContinueRebaseCommand;
91 import org.eclipse.egit.ui.internal.commands.shared.SkipRebaseCommand;
92 import org.eclipse.egit.ui.internal.commit.CommitHelper;
93 import org.eclipse.egit.ui.internal.commit.CommitJob;
94 import org.eclipse.egit.ui.internal.commit.CommitMessageHistory;
95 import org.eclipse.egit.ui.internal.commit.CommitProposalProcessor;
96 import org.eclipse.egit.ui.internal.commit.DiffViewer;
97 import org.eclipse.egit.ui.internal.components.RepositoryMenuUtil.RepositoryToolbarAction;
98 import org.eclipse.egit.ui.internal.decorators.IProblemDecoratable;
99 import org.eclipse.egit.ui.internal.decorators.ProblemLabelDecorator;
100 import org.eclipse.egit.ui.internal.dialogs.CommitMessageArea;
101 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponent;
102 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentState;
103 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentStateManager;
104 import org.eclipse.egit.ui.internal.dialogs.ICommitMessageComponentNotifications;
105 import org.eclipse.egit.ui.internal.dialogs.SpellcheckableMessageArea;
106 import org.eclipse.egit.ui.internal.operations.DeletePathsOperationUI;
107 import org.eclipse.egit.ui.internal.operations.IgnoreOperationUI;
108 import org.eclipse.egit.ui.internal.push.PushMode;
109 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
110 import org.eclipse.egit.ui.internal.selection.MultiViewerSelectionProvider;
111 import org.eclipse.egit.ui.internal.selection.RepositorySelectionProvider;
112 import org.eclipse.jface.action.Action;
113 import org.eclipse.jface.action.ControlContribution;
114 import org.eclipse.jface.action.IAction;
115 import org.eclipse.jface.action.IContributionItem;
116 import org.eclipse.jface.action.IMenuListener;
117 import org.eclipse.jface.action.IMenuManager;
118 import org.eclipse.jface.action.IToolBarManager;
119 import org.eclipse.jface.action.MenuManager;
120 import org.eclipse.jface.action.Separator;
121 import org.eclipse.jface.action.ToolBarManager;
122 import org.eclipse.jface.dialogs.DialogSettings;
123 import org.eclipse.jface.dialogs.IDialogConstants;
124 import org.eclipse.jface.dialogs.IDialogSettings;
125 import org.eclipse.jface.dialogs.MessageDialog;
126 import org.eclipse.jface.layout.GridDataFactory;
127 import org.eclipse.jface.layout.GridLayoutFactory;
128 import org.eclipse.jface.preference.IPersistentPreferenceStore;
129 import org.eclipse.jface.preference.IPreferenceStore;
130 import org.eclipse.jface.resource.ImageDescriptor;
131 import org.eclipse.jface.resource.JFaceResources;
132 import org.eclipse.jface.resource.LocalResourceManager;
133 import org.eclipse.jface.util.IPropertyChangeListener;
134 import org.eclipse.jface.util.LocalSelectionTransfer;
135 import org.eclipse.jface.util.PropertyChangeEvent;
136 import org.eclipse.jface.viewers.AbstractTreeViewer;
137 import org.eclipse.jface.viewers.BaseLabelProvider;
138 import org.eclipse.jface.viewers.ContentViewer;
139 import org.eclipse.jface.viewers.DecoratingLabelProvider;
140 import org.eclipse.jface.viewers.IBaseLabelProvider;
141 import org.eclipse.jface.viewers.ILabelDecorator;
142 import org.eclipse.jface.viewers.ILabelProvider;
143 import org.eclipse.jface.viewers.ILabelProviderListener;
144 import org.eclipse.jface.viewers.ISelection;
145 import org.eclipse.jface.viewers.ISelectionProvider;
146 import org.eclipse.jface.viewers.IStructuredSelection;
147 import org.eclipse.jface.viewers.ITreeViewerListener;
148 import org.eclipse.jface.viewers.OpenEvent;
149 import org.eclipse.jface.viewers.StructuredSelection;
150 import org.eclipse.jface.viewers.TreeExpansionEvent;
151 import org.eclipse.jface.viewers.TreeViewer;
152 import org.eclipse.jface.viewers.Viewer;
153 import org.eclipse.jface.viewers.ViewerComparator;
154 import org.eclipse.jface.viewers.ViewerFilter;
155 import org.eclipse.jface.window.Window;
156 import org.eclipse.jgit.annotations.NonNull;
157 import org.eclipse.jgit.annotations.Nullable;
158 import org.eclipse.jgit.api.AddCommand;
159 import org.eclipse.jgit.api.CheckoutCommand;
160 import org.eclipse.jgit.api.Git;
161 import org.eclipse.jgit.api.ResetCommand;
162 import org.eclipse.jgit.api.RmCommand;
163 import org.eclipse.jgit.api.errors.GitAPIException;
164 import org.eclipse.jgit.api.errors.JGitInternalException;
165 import org.eclipse.jgit.api.errors.NoFilepatternException;
166 import org.eclipse.jgit.events.ListenerHandle;
167 import org.eclipse.jgit.lib.Constants;
168 import org.eclipse.jgit.lib.ObjectId;
169 import org.eclipse.jgit.lib.Ref;
170 import org.eclipse.jgit.lib.Repository;
171 import org.eclipse.jgit.lib.RepositoryState;
172 import org.eclipse.jgit.revwalk.RevCommit;
173 import org.eclipse.swt.SWT;
174 import org.eclipse.swt.custom.SashForm;
175 import org.eclipse.swt.custom.VerifyKeyListener;
176 import org.eclipse.swt.dnd.Clipboard;
177 import org.eclipse.swt.dnd.DND;
178 import org.eclipse.swt.dnd.DragSourceAdapter;
179 import org.eclipse.swt.dnd.DragSourceEvent;
180 import org.eclipse.swt.dnd.DropTargetAdapter;
181 import org.eclipse.swt.dnd.DropTargetEvent;
182 import org.eclipse.swt.dnd.FileTransfer;
183 import org.eclipse.swt.dnd.TextTransfer;
184 import org.eclipse.swt.dnd.Transfer;
185 import org.eclipse.swt.events.ControlEvent;
186 import org.eclipse.swt.events.ControlListener;
187 import org.eclipse.swt.events.DisposeEvent;
188 import org.eclipse.swt.events.DisposeListener;
189 import org.eclipse.swt.events.FocusEvent;
190 import org.eclipse.swt.events.FocusListener;
191 import org.eclipse.swt.events.ModifyEvent;
192 import org.eclipse.swt.events.ModifyListener;
193 import org.eclipse.swt.events.SelectionAdapter;
194 import org.eclipse.swt.events.SelectionEvent;
195 import org.eclipse.swt.events.VerifyEvent;
196 import org.eclipse.swt.graphics.Cursor;
197 import org.eclipse.swt.graphics.Image;
198 import org.eclipse.swt.graphics.Point;
199 import org.eclipse.swt.layout.GridData;
200 import org.eclipse.swt.layout.GridLayout;
201 import org.eclipse.swt.layout.RowLayout;
202 import org.eclipse.swt.widgets.Button;
203 import org.eclipse.swt.widgets.Composite;
204 import org.eclipse.swt.widgets.Control;
205 import org.eclipse.swt.widgets.Display;
206 import org.eclipse.swt.widgets.Label;
207 import org.eclipse.swt.widgets.Text;
208 import org.eclipse.swt.widgets.Tree;
209 import org.eclipse.swt.widgets.TreeItem;
210 import org.eclipse.ui.IActionBars;
211 import org.eclipse.ui.IEditorInput;
212 import org.eclipse.ui.IEditorPart;
213 import org.eclipse.ui.IFileEditorInput;
214 import org.eclipse.ui.IMemento;
215 import org.eclipse.ui.IPartListener2;
216 import org.eclipse.ui.IPartService;
217 import org.eclipse.ui.ISelectionListener;
218 import org.eclipse.ui.ISelectionService;
219 import org.eclipse.ui.IURIEditorInput;
220 import org.eclipse.ui.IViewSite;
221 import org.eclipse.ui.IWorkbenchPart;
222 import org.eclipse.ui.IWorkbenchPartReference;
223 import org.eclipse.ui.IWorkbenchPartSite;
224 import org.eclipse.ui.IWorkbenchWindow;
225 import org.eclipse.ui.PartInitException;
226 import org.eclipse.ui.PlatformUI;
227 import org.eclipse.ui.actions.ActionFactory;
228 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
229 import org.eclipse.ui.forms.IFormColors;
230 import org.eclipse.ui.forms.widgets.ExpandableComposite;
231 import org.eclipse.ui.forms.widgets.Form;
232 import org.eclipse.ui.forms.widgets.FormToolkit;
233 import org.eclipse.ui.forms.widgets.Section;
234 import org.eclipse.ui.handlers.IHandlerService;
235 import org.eclipse.ui.operations.UndoRedoActionGroup;
236 import org.eclipse.ui.part.IShowInSource;
237 import org.eclipse.ui.part.IShowInTarget;
238 import org.eclipse.ui.part.ShowInContext;
239 import org.eclipse.ui.part.ViewPart;
240 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
241 import org.eclipse.ui.progress.WorkbenchJob;
244 * A GitX style staging view with embedded commit dialog.
246 public class StagingView extends ViewPart
247 implements IShowInSource, IShowInTarget {
250 * Staging view id
252 public static final String VIEW_ID = "org.eclipse.egit.ui.StagingView"; //$NON-NLS-1$
254 private static final String EMPTY_STRING = ""; //$NON-NLS-1$
256 private static final String SORT_ITEM_TOOLBAR_ID = "sortItem"; //$NON-NLS-1$
258 private static final String EXPAND_ALL_ITEM_TOOLBAR_ID = "expandAllItem"; //$NON-NLS-1$
260 private static final String COLLAPSE_ALL_ITEM_TOOLBAR_ID = "collapseAllItem"; //$NON-NLS-1$
262 private static final String STORE_SORT_STATE = SORT_ITEM_TOOLBAR_ID
263 + "State"; //$NON-NLS-1$
265 private static final String HORIZONTAL_SASH_FORM_WEIGHT = "HORIZONTAL_SASH_FORM_WEIGHT"; //$NON-NLS-1$
267 private static final String STAGING_SASH_FORM_WEIGHT = "STAGING_SASH_FORM_WEIGHT"; //$NON-NLS-1$
269 private ISelection initialSelection;
271 private FormToolkit toolkit;
273 private Form form;
275 private SashForm mainSashForm;
277 private Section stagedSection;
279 private Section unstagedSection;
281 private Section commitMessageSection;
283 private TreeViewer stagedViewer;
285 private TreeViewer unstagedViewer;
287 private ToggleableLabel warningLabel;
289 private Text filterText;
291 /** Remember compiled pattern of the current filter string for performance. */
292 private Pattern filterPattern;
294 private SpellcheckableMessageArea commitMessageText;
296 private Text committerText;
298 private Text authorText;
300 private CommitMessageComponent commitMessageComponent;
302 private boolean reactOnSelection = true;
304 private boolean isViewHidden;
306 /** Tracks the last selection while the view is not active. */
307 private StructuredSelection lastSelection;
309 private ISelectionListener selectionChangedListener;
311 private IPartListener2 partListener;
313 private ToolBarManager unstagedToolBarManager;
315 private ToolBarManager stagedToolBarManager;
317 private IAction listPresentationAction;
319 private IAction treePresentationAction;
321 private IAction compactTreePresentationAction;
323 private IAction unstagedExpandAllAction;
325 private IAction unstagedCollapseAllAction;
327 private IAction stagedExpandAllAction;
329 private IAction stagedCollapseAllAction;
331 private IAction unstageAction;
333 private IAction stageAction;
335 private IAction unstageAllAction;
337 private IAction stageAllAction;
339 private IAction compareModeAction;
341 private IWorkbenchAction switchRepositoriesAction;
343 /** The currently set repository, even if it is bare. */
344 private Repository realRepository;
346 /** The currently set repository, if it's not a bare repository. */
347 @Nullable
348 private Repository currentRepository;
350 private Presentation presentation = Presentation.LIST;
352 private Set<IPath> pathsToExpandInStaged = new HashSet<>();
354 private Set<IPath> pathsToExpandInUnstaged = new HashSet<>();
356 private boolean isUnbornHead;
358 private String currentBranch;
361 * Presentation mode of the staged/unstaged files.
363 public enum Presentation {
364 /** Show files in flat list */
365 LIST,
366 /** Show folder structure in full tree */
367 TREE,
369 * Show folder structure in compact tree (folders with only one child
370 * are folded into parent)
372 COMPACT_TREE;
375 static class StagingViewUpdate {
376 Repository repository;
377 IndexDiffData indexDiff;
378 Collection<String> changedResources;
380 StagingViewUpdate(Repository theRepository,
381 IndexDiffData theIndexDiff, Collection<String> theChanges) {
382 this.repository = theRepository;
383 this.indexDiff = theIndexDiff;
384 this.changedResources = theChanges;
388 private static class StagingDragSelection implements IStructuredSelection {
390 private final IStructuredSelection delegate;
392 private final boolean fromUnstaged;
394 public StagingDragSelection(IStructuredSelection original,
395 boolean fromUnstaged) {
396 this.delegate = original;
397 this.fromUnstaged = fromUnstaged;
400 @Override
401 public boolean isEmpty() {
402 return delegate.isEmpty();
405 @Override
406 public Object getFirstElement() {
407 return delegate.getFirstElement();
410 @Override
411 public Iterator iterator() {
412 return delegate.iterator();
415 @Override
416 public int size() {
417 return delegate.size();
420 @Override
421 public Object[] toArray() {
422 return delegate.toArray();
425 @Override
426 public List toList() {
427 return delegate.toList();
430 public boolean isFromUnstaged() {
431 return fromUnstaged;
435 private static class StagingDragListener extends DragSourceAdapter {
437 private final ISelectionProvider provider;
439 private final StagingViewContentProvider contentProvider;
441 private final boolean unstaged;
443 public StagingDragListener(ISelectionProvider provider,
444 StagingViewContentProvider contentProvider, boolean unstaged) {
445 this.provider = provider;
446 this.contentProvider = contentProvider;
447 this.unstaged = unstaged;
450 @Override
451 public void dragStart(DragSourceEvent event) {
452 event.doit = !provider.getSelection().isEmpty();
455 @Override
456 public void dragFinished(DragSourceEvent event) {
457 if (LocalSelectionTransfer.getTransfer().isSupportedType(
458 event.dataType)) {
459 LocalSelectionTransfer.getTransfer().setSelection(null);
463 @Override
464 public void dragSetData(DragSourceEvent event) {
465 IStructuredSelection selection = (IStructuredSelection) provider
466 .getSelection();
467 if (selection.isEmpty()) {
468 // Should never happen as per dragStart()
469 return;
471 if (LocalSelectionTransfer.getTransfer().isSupportedType(
472 event.dataType)) {
473 LocalSelectionTransfer.getTransfer().setSelection(
474 new StagingDragSelection(selection, unstaged));
475 return;
478 if (FileTransfer.getInstance().isSupportedType(event.dataType)) {
479 Set<String> files = new HashSet<>();
480 for (Object selected : selection.toList())
481 if (selected instanceof StagingEntry) {
482 add((StagingEntry) selected, files);
483 } else if (selected instanceof StagingFolderEntry) {
484 // Only add the files, otherwise much more than intended
485 // might be copied or moved. The user selected a staged
486 // or unstaged folder, so only the staged or unstaged
487 // files inside that folder should be included, not
488 // everything.
489 StagingFolderEntry folder = (StagingFolderEntry) selected;
490 for (StagingEntry entry : contentProvider
491 .getStagingEntriesFiltered(folder)) {
492 add(entry, files);
495 if (!files.isEmpty()) {
496 event.data = files.toArray(new String[files.size()]);
497 return;
499 // We may still end up with an empty list here if the selection
500 // contained only deleted files. In that case, the drag&drop
501 // will log an SWTException: Data does not have correct format
502 // for type. Note that GTK sometimes creates the FileTransfer
503 // up front even though a drag between our own viewers would
504 // need only the LocalSelectionTransfer. Drag&drop between our
505 // viewers still works (also on GTK) even if the creation of
506 // the FileTransfer fails.
510 private void add(StagingEntry entry, Collection<String> files) {
511 File file = entry.getLocation().toFile();
512 if (file.exists()) {
513 files.add(file.getAbsolutePath());
518 private final class PartListener implements IPartListener2 {
520 @Override
521 public void partVisible(IWorkbenchPartReference partRef) {
522 updateHiddenState(partRef, false);
525 @Override
526 public void partOpened(IWorkbenchPartReference partRef) {
527 updateHiddenState(partRef, false);
530 @Override
531 public void partHidden(IWorkbenchPartReference partRef) {
532 updateHiddenState(partRef, true);
535 @Override
536 public void partClosed(IWorkbenchPartReference partRef) {
537 updateHiddenState(partRef, true);
540 @Override
541 public void partActivated(IWorkbenchPartReference partRef) {
542 if (isMe(partRef)) {
543 if (lastSelection != null) {
544 // view activated: synchronize with last active part
545 // selection
546 reactOnSelection(lastSelection);
547 lastSelection = null;
549 return;
551 IWorkbenchPart part = partRef.getPart(false);
552 StructuredSelection sel = getSelectionOfPart(part);
553 if (isViewHidden) {
554 // remember last selection in the part so that we can
555 // synchronize on it as soon as we will be visible
556 lastSelection = sel;
557 } else {
558 lastSelection = null;
559 if (sel != null) {
560 reactOnSelection(sel);
566 private void updateHiddenState(IWorkbenchPartReference partRef,
567 boolean hidden) {
568 if (isMe(partRef)) {
569 isViewHidden = hidden;
573 private boolean isMe(IWorkbenchPartReference partRef) {
574 return partRef.getPart(false) == StagingView.this;
577 @Override
578 public void partDeactivated(IWorkbenchPartReference partRef) {
582 @Override
583 public void partBroughtToTop(IWorkbenchPartReference partRef) {
587 @Override
588 public void partInputChanged(IWorkbenchPartReference partRef) {
594 * A wrapped {@link DecoratingLabelProvider} to be used in the tree viewers
595 * of the staging view. We wrap it instead of deriving directly because a
596 * {@link DecoratingLabelProvider} is a
597 * {@link org.eclipse.jface.viewers.ITreePathLabelProvider
598 * ITreePathLabelProvider}, which makes the tree viewer compute a
599 * {@link org.eclipse.jface.viewers.TreePath TreePath} for each element,
600 * which is then ultimately unused because the
601 * {@link StagingViewLabelProvider} is <em>not</em> a
602 * {@link org.eclipse.jface.viewers.ITreePathLabelProvider
603 * ITreePathLabelProvider}. Computing the
604 * {@link org.eclipse.jface.viewers.TreePath TreePath} is a fairly expensive
605 * operation on GTK, and avoiding to compute it speeds up label updates
606 * significantly.
608 private static class TreeDecoratingLabelProvider extends BaseLabelProvider
609 implements ILabelProvider {
611 private final DecoratingLabelProvider provider;
613 public TreeDecoratingLabelProvider(ILabelProvider provider,
614 ILabelDecorator decorator) {
615 this.provider = new DecoratingLabelProvider(provider, decorator);
618 @Override
619 public Image getImage(Object element) {
620 return provider.getImage(element);
623 @Override
624 public String getText(Object element) {
625 return provider.getText(element);
628 @Override
629 public void addListener(ILabelProviderListener listener) {
630 provider.addListener(listener);
633 @Override
634 public void removeListener(ILabelProviderListener listener) {
635 provider.removeListener(listener);
638 @Override
639 public void dispose() {
640 provider.dispose();
643 public ILabelProvider getLabelProvider() {
644 return provider.getLabelProvider();
649 static class StagingViewSearchThread extends Thread {
650 private StagingView stagingView;
652 private static final Object lock = new Object();
654 private volatile static int globalThreadIndex = 0;
656 private int currentThreadIx;
658 public StagingViewSearchThread(StagingView stagingView) {
659 super("staging_view_filter_thread" + ++globalThreadIndex); //$NON-NLS-1$
660 this.stagingView = stagingView;
661 currentThreadIx = globalThreadIndex;
664 @Override
665 public void run() {
666 synchronized (lock) {
667 if (currentThreadIx < globalThreadIndex)
668 return;
669 stagingView.refreshViewersPreservingExpandedElements();
675 private final IPreferenceChangeListener prefListener = new IPreferenceChangeListener() {
677 @Override
678 public void preferenceChange(PreferenceChangeEvent event) {
679 if (!RepositoryUtil.PREFS_DIRECTORIES_REL.equals(event.getKey())) {
680 return;
682 final Repository repo = currentRepository;
683 if (repo == null || Activator.getDefault().getRepositoryUtil()
684 .contains(repo)) {
685 return;
687 reload(null);
692 private final IPropertyChangeListener uiPrefsListener = new IPropertyChangeListener() {
693 @Override
694 public void propertyChange(PropertyChangeEvent event) {
695 if (UIPreferences.COMMIT_DIALOG_WARN_ABOUT_MESSAGE_SECOND_LINE
696 .equals(event.getProperty())) {
697 asyncExec(() -> {
698 if (!commitMessageSection.isDisposed()) {
699 updateMessage();
706 private Action signedOffByAction;
708 private Action addChangeIdAction;
710 private Action amendPreviousCommitAction;
712 private Action openNewCommitsAction;
714 private Action columnLayoutAction;
716 private Action fileNameModeAction;
718 private Action refreshAction;
720 private Action sortAction;
722 private SashForm stagingSashForm;
724 private IndexDiffChangedListener myIndexDiffListener = new IndexDiffChangedListener() {
725 @Override
726 public void indexDiffChanged(Repository repository,
727 IndexDiffData indexDiffData) {
728 reload(repository);
732 private IndexDiffCacheEntry cacheEntry;
734 private UndoRedoActionGroup undoRedoActionGroup;
736 private Button commitButton;
738 private Button commitAndPushButton;
740 private Section rebaseSection;
742 private Button rebaseContinueButton;
744 private Button rebaseSkipButton;
746 private Button rebaseAbortButton;
748 private Button ignoreErrors;
750 private ListenerHandle refsChangedListener;
752 private LocalResourceManager resources = new LocalResourceManager(
753 JFaceResources.getResources());
755 private boolean disposed;
757 private Image getImage(ImageDescriptor descriptor) {
758 return (Image) this.resources.get(descriptor);
761 @Override
762 public void init(IViewSite site, IMemento viewMemento)
763 throws PartInitException {
764 super.init(site, viewMemento);
765 this.initialSelection = site.getWorkbenchWindow().getSelectionService()
766 .getSelection();
769 @Override
770 public void createPartControl(final Composite parent) {
771 GridLayoutFactory.fillDefaults().applyTo(parent);
773 toolkit = new FormToolkit(parent.getDisplay());
774 parent.addDisposeListener(new DisposeListener() {
776 @Override
777 public void widgetDisposed(DisposeEvent e) {
778 if (commitMessageComponent.isAmending()
779 || userEnteredCommitMessage())
780 saveCommitMessageComponentState();
781 else
782 deleteCommitMessageComponentState();
783 resources.dispose();
784 toolkit.dispose();
788 form = toolkit.createForm(parent);
789 parent.addControlListener(new ControlListener() {
791 private int[] defaultWeights = { 1, 1 };
793 @Override
794 public void controlResized(ControlEvent e) {
795 org.eclipse.swt.graphics.Rectangle b = parent.getBounds();
796 int oldOrientation = mainSashForm.getOrientation();
797 if ((oldOrientation == SWT.HORIZONTAL)
798 && (b.height > b.width)) {
799 mainSashForm.setOrientation(SWT.VERTICAL);
800 mainSashForm.setWeights(defaultWeights);
801 } else if ((oldOrientation == SWT.VERTICAL)
802 && (b.height <= b.width)) {
803 mainSashForm.setOrientation(SWT.HORIZONTAL);
804 mainSashForm.setWeights(defaultWeights);
808 @Override
809 public void controlMoved(ControlEvent e) {
810 // ignore
813 form.setImage(getImage(UIIcons.REPOSITORY));
814 form.setText(UIText.StagingView_NoSelectionTitle);
815 GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
816 toolkit.decorateFormHeading(form);
817 GridLayoutFactory.swtDefaults().applyTo(form.getBody());
819 mainSashForm = new SashForm(form.getBody(), SWT.HORIZONTAL);
820 saveSashFormWeightsOnDisposal(mainSashForm,
821 HORIZONTAL_SASH_FORM_WEIGHT);
822 toolkit.adapt(mainSashForm, true, true);
823 GridDataFactory.fillDefaults().grab(true, true)
824 .applyTo(mainSashForm);
826 stagingSashForm = new SashForm(mainSashForm,
827 getStagingFormOrientation());
828 saveSashFormWeightsOnDisposal(stagingSashForm,
829 STAGING_SASH_FORM_WEIGHT);
830 toolkit.adapt(stagingSashForm, true, true);
831 GridDataFactory.fillDefaults().grab(true, true)
832 .applyTo(stagingSashForm);
834 unstageAction = new Action(UIText.StagingView_UnstageItemMenuLabel,
835 UIIcons.UNSTAGE) {
836 @Override
837 public void run() {
838 unstage((IStructuredSelection) stagedViewer.getSelection());
841 unstageAction.setToolTipText(UIText.StagingView_UnstageItemTooltip);
842 stageAction = new Action(UIText.StagingView_StageItemMenuLabel,
843 UIIcons.ELCL16_ADD) {
844 @Override
845 public void run() {
846 stage((IStructuredSelection) unstagedViewer.getSelection());
849 stageAction.setToolTipText(UIText.StagingView_StageItemTooltip);
851 unstageAction.setEnabled(false);
852 stageAction.setEnabled(false);
854 unstageAllAction = new Action(
855 UIText.StagingView_UnstageAllItemMenuLabel,
856 UIIcons.UNSTAGE_ALL) {
857 @Override
858 public void run() {
859 stagedViewer.getTree().selectAll();
860 unstage((IStructuredSelection) stagedViewer.getSelection());
863 unstageAllAction
864 .setToolTipText(UIText.StagingView_UnstageAllItemTooltip);
865 stageAllAction = new Action(UIText.StagingView_StageAllItemMenuLabel,
866 UIIcons.ELCL16_ADD_ALL) {
867 @Override
868 public void run() {
869 unstagedViewer.getTree().selectAll();
870 stage((IStructuredSelection) unstagedViewer.getSelection());
873 stageAllAction.setToolTipText(UIText.StagingView_StageAllItemTooltip);
875 unstageAllAction.setEnabled(false);
876 stageAllAction.setEnabled(false);
878 unstagedSection = toolkit.createSection(stagingSashForm,
879 ExpandableComposite.SHORT_TITLE_BAR);
880 unstagedSection.clientVerticalSpacing = 0;
882 unstagedSection.setLayoutData(
883 GridDataFactory.fillDefaults().grab(true, true).create());
885 createUnstagedToolBarComposite();
887 Composite unstagedComposite = toolkit.createComposite(unstagedSection);
888 toolkit.paintBordersFor(unstagedComposite);
889 unstagedSection.setClient(unstagedComposite);
890 GridLayoutFactory.fillDefaults().applyTo(unstagedComposite);
892 unstagedViewer = createViewer(unstagedComposite, true,
893 selection -> unstage(selection), stageAction);
895 unstagedViewer.addSelectionChangedListener(event -> {
896 boolean hasSelection = !event.getSelection().isEmpty();
897 if (hasSelection != stageAction.isEnabled()) {
898 stageAction.setEnabled(hasSelection);
899 unstagedToolBarManager.update(true);
902 Composite rebaseAndCommitComposite = toolkit.createComposite(mainSashForm);
903 rebaseAndCommitComposite.setLayout(GridLayoutFactory.fillDefaults().create());
905 rebaseSection = toolkit.createSection(rebaseAndCommitComposite,
906 ExpandableComposite.SHORT_TITLE_BAR);
907 rebaseSection.clientVerticalSpacing = 0;
908 rebaseSection.setText(UIText.StagingView_RebaseLabel);
910 Composite rebaseComposite = toolkit.createComposite(rebaseSection);
911 toolkit.paintBordersFor(rebaseComposite);
912 rebaseSection.setClient(rebaseComposite);
914 rebaseSection.setLayoutData(GridDataFactory.fillDefaults().create());
915 rebaseComposite.setLayout(GridLayoutFactory.fillDefaults()
916 .numColumns(3).equalWidth(true).create());
917 GridDataFactory buttonGridData = GridDataFactory.fillDefaults().align(
918 SWT.FILL, SWT.CENTER);
920 this.rebaseAbortButton = toolkit.createButton(rebaseComposite,
921 UIText.StagingView_RebaseAbort, SWT.PUSH);
922 rebaseAbortButton.addSelectionListener(new SelectionAdapter() {
923 @Override
924 public void widgetSelected(SelectionEvent e) {
925 rebaseAbort();
928 rebaseAbortButton.setImage(getImage(UIIcons.REBASE_ABORT));
929 buttonGridData.applyTo(rebaseAbortButton);
931 this.rebaseSkipButton = toolkit.createButton(rebaseComposite,
932 UIText.StagingView_RebaseSkip, SWT.PUSH);
933 rebaseSkipButton.addSelectionListener(new SelectionAdapter() {
934 @Override
935 public void widgetSelected(SelectionEvent e) {
936 rebaseSkip();
939 rebaseSkipButton.setImage(getImage(UIIcons.REBASE_SKIP));
940 buttonGridData.applyTo(rebaseSkipButton);
942 this.rebaseContinueButton = toolkit.createButton(rebaseComposite,
943 UIText.StagingView_RebaseContinue, SWT.PUSH);
944 rebaseContinueButton.addSelectionListener(new SelectionAdapter() {
945 @Override
946 public void widgetSelected(SelectionEvent e) {
947 rebaseContinue();
950 rebaseContinueButton.setImage(getImage(UIIcons.REBASE_CONTINUE));
951 buttonGridData.applyTo(rebaseContinueButton);
953 showControl(rebaseSection, false);
955 commitMessageSection = toolkit.createSection(rebaseAndCommitComposite,
956 ExpandableComposite.SHORT_TITLE_BAR);
957 commitMessageSection.clientVerticalSpacing = 0;
958 commitMessageSection.setText(UIText.StagingView_CommitMessage);
959 commitMessageSection.setLayoutData(GridDataFactory.fillDefaults()
960 .grab(true, true).create());
962 Composite commitMessageToolbarComposite = toolkit
963 .createComposite(commitMessageSection);
964 commitMessageToolbarComposite.setBackground(null);
965 commitMessageToolbarComposite.setLayout(createRowLayoutWithoutMargin());
966 commitMessageSection.setTextClient(commitMessageToolbarComposite);
967 ToolBarManager commitMessageToolBarManager = new ToolBarManager(
968 SWT.FLAT | SWT.HORIZONTAL);
970 amendPreviousCommitAction = new Action(
971 UIText.StagingView_Ammend_Previous_Commit, IAction.AS_CHECK_BOX) {
973 @Override
974 public void run() {
975 commitMessageComponent.setAmendingButtonSelection(isChecked());
976 updateMessage();
979 amendPreviousCommitAction.setImageDescriptor(UIIcons.AMEND_COMMIT);
980 commitMessageToolBarManager.add(amendPreviousCommitAction);
982 signedOffByAction = new Action(UIText.StagingView_Add_Signed_Off_By,
983 IAction.AS_CHECK_BOX) {
985 @Override
986 public void run() {
987 commitMessageComponent.setSignedOffButtonSelection(isChecked());
990 signedOffByAction.setImageDescriptor(UIIcons.SIGNED_OFF);
991 commitMessageToolBarManager.add(signedOffByAction);
993 addChangeIdAction = new Action(UIText.StagingView_Add_Change_ID,
994 IAction.AS_CHECK_BOX) {
996 @Override
997 public void run() {
998 commitMessageComponent.setChangeIdButtonSelection(isChecked());
1001 addChangeIdAction.setImageDescriptor(UIIcons.GERRIT);
1002 commitMessageToolBarManager.add(addChangeIdAction);
1004 commitMessageToolBarManager
1005 .createControl(commitMessageToolbarComposite);
1007 Composite commitMessageComposite = toolkit
1008 .createComposite(commitMessageSection);
1009 commitMessageSection.setClient(commitMessageComposite);
1010 GridLayoutFactory.fillDefaults().numColumns(1)
1011 .applyTo(commitMessageComposite);
1013 warningLabel = new ToggleableLabel(commitMessageComposite,
1014 SWT.NONE);
1015 GridDataFactory.fillDefaults().grab(true, false).exclude(true)
1016 .applyTo(warningLabel);
1018 Composite commitMessageTextComposite = toolkit
1019 .createComposite(commitMessageComposite);
1020 toolkit.paintBordersFor(commitMessageTextComposite);
1021 GridDataFactory.fillDefaults().grab(true, true)
1022 .applyTo(commitMessageTextComposite);
1023 GridLayoutFactory.fillDefaults().numColumns(1)
1024 .extendedMargins(2, 2, 2, 2)
1025 .applyTo(commitMessageTextComposite);
1027 final CommitProposalProcessor commitProposalProcessor = new CommitProposalProcessor() {
1028 @Override
1029 protected Collection<String> computeFileNameProposals() {
1030 return getStagedFileNames();
1033 @Override
1034 protected Collection<String> computeMessageProposals() {
1035 return CommitMessageHistory.getCommitHistory();
1038 commitMessageText = new CommitMessageArea(commitMessageTextComposite,
1039 EMPTY_STRING, SWT.NONE) {
1040 @Override
1041 protected CommitProposalProcessor getCommitProposalProcessor() {
1042 return commitProposalProcessor;
1044 @Override
1045 protected IHandlerService getHandlerService() {
1046 return CommonUtils.getService(getSite(), IHandlerService.class);
1049 commitMessageText.setData(FormToolkit.KEY_DRAW_BORDER,
1050 FormToolkit.TEXT_BORDER);
1051 GridDataFactory.fillDefaults().grab(true, true)
1052 .applyTo(commitMessageText);
1053 UIUtils.addBulbDecorator(commitMessageText.getTextWidget(),
1054 UIText.CommitDialog_ContentAssist);
1056 Composite composite = toolkit.createComposite(commitMessageComposite);
1057 toolkit.paintBordersFor(composite);
1058 GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
1059 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(composite);
1061 toolkit.createLabel(composite, UIText.StagingView_Author)
1062 .setForeground(
1063 toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
1064 authorText = toolkit.createText(composite, null);
1065 authorText
1066 .setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
1067 authorText.setLayoutData(GridDataFactory.fillDefaults()
1068 .grab(true, false).create());
1070 toolkit.createLabel(composite, UIText.StagingView_Committer)
1071 .setForeground(
1072 toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
1073 committerText = toolkit.createText(composite, null);
1074 committerText.setData(FormToolkit.KEY_DRAW_BORDER,
1075 FormToolkit.TEXT_BORDER);
1076 committerText.setLayoutData(GridDataFactory.fillDefaults()
1077 .grab(true, false).create());
1079 Composite buttonsContainer = toolkit.createComposite(composite);
1080 GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
1081 .indent(0, 8).applyTo(buttonsContainer);
1082 GridLayoutFactory.fillDefaults().numColumns(2)
1083 .applyTo(buttonsContainer);
1085 ignoreErrors = toolkit.createButton(buttonsContainer,
1086 UIText.StagingView_IgnoreErrors, SWT.CHECK);
1087 ignoreErrors.setSelection(false);
1088 ignoreErrors.addSelectionListener(new SelectionAdapter() {
1089 @Override
1090 public void widgetSelected(SelectionEvent e) {
1091 updateMessage();
1092 updateCommitButtons();
1095 getPreferenceStore()
1096 .addPropertyChangeListener(new IPropertyChangeListener() {
1097 @Override
1098 public void propertyChange(PropertyChangeEvent event) {
1099 if (isDisposed()) {
1100 getPreferenceStore()
1101 .removePropertyChangeListener(this);
1102 return;
1104 asyncExec(() -> {
1105 updateIgnoreErrorsButtonVisibility();
1106 updateMessage();
1107 updateCommitButtons();
1112 GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING)
1113 .grab(true, true).applyTo(ignoreErrors);
1114 updateIgnoreErrorsButtonVisibility();
1116 Label filler = toolkit.createLabel(buttonsContainer, ""); //$NON-NLS-1$
1117 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
1118 .grab(true, true).applyTo(filler);
1120 Composite commitButtonsContainer = toolkit
1121 .createComposite(buttonsContainer);
1122 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
1123 .applyTo(commitButtonsContainer);
1124 GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true)
1125 .applyTo(commitButtonsContainer);
1128 this.commitAndPushButton = toolkit.createButton(commitButtonsContainer,
1129 UIText.StagingView_CommitAndPush, SWT.PUSH);
1130 commitAndPushButton.setImage(getImage(UIIcons.PUSH));
1131 commitAndPushButton.addSelectionListener(new SelectionAdapter() {
1132 @Override
1133 public void widgetSelected(SelectionEvent e) {
1134 commit(true);
1137 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
1138 .applyTo(commitAndPushButton);
1140 this.commitButton = toolkit.createButton(commitButtonsContainer,
1141 UIText.StagingView_Commit, SWT.PUSH);
1142 commitButton.setImage(getImage(UIIcons.COMMIT));
1143 commitButton.setText(UIText.StagingView_Commit);
1144 commitButton.addSelectionListener(new SelectionAdapter() {
1145 @Override
1146 public void widgetSelected(SelectionEvent e) {
1147 commit(false);
1150 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
1151 .applyTo(commitButton);
1153 stagedSection = toolkit.createSection(stagingSashForm,
1154 ExpandableComposite.SHORT_TITLE_BAR);
1155 stagedSection.clientVerticalSpacing = 0;
1157 createStagedToolBarComposite();
1159 Composite stagedComposite = toolkit.createComposite(stagedSection);
1160 toolkit.paintBordersFor(stagedComposite);
1161 stagedSection.setClient(stagedComposite);
1162 GridLayoutFactory.fillDefaults().applyTo(stagedComposite);
1164 stagedViewer = createViewer(stagedComposite, false,
1165 selection -> stage(selection), unstageAction);
1166 stagedViewer.getLabelProvider().addListener(event -> {
1167 updateMessage();
1168 updateCommitButtons();
1170 stagedViewer.addSelectionChangedListener(event -> {
1171 boolean hasSelection = !event.getSelection().isEmpty();
1172 if (hasSelection != unstageAction.isEnabled()) {
1173 unstageAction.setEnabled(hasSelection);
1174 stagedToolBarManager.update(true);
1178 selectionChangedListener = new ISelectionListener() {
1179 @Override
1180 public void selectionChanged(IWorkbenchPart part,
1181 ISelection selection) {
1182 if (part == getSite().getPart()) {
1183 return;
1185 // don't accept text selection, only structural one
1186 if (selection instanceof StructuredSelection) {
1187 reactOnSelection((StructuredSelection) selection);
1192 partListener = new PartListener();
1194 IPreferenceStore preferenceStore = getPreferenceStore();
1195 if (preferenceStore.contains(UIPreferences.STAGING_VIEW_SYNC_SELECTION))
1196 reactOnSelection = preferenceStore.getBoolean(
1197 UIPreferences.STAGING_VIEW_SYNC_SELECTION);
1198 else
1199 preferenceStore.setDefault(UIPreferences.STAGING_VIEW_SYNC_SELECTION, true);
1201 preferenceStore.addPropertyChangeListener(uiPrefsListener);
1203 InstanceScope.INSTANCE.getNode(
1204 org.eclipse.egit.core.Activator.getPluginId())
1205 .addPreferenceChangeListener(prefListener);
1207 updateSectionText();
1208 stagedSection.setToolTipText(UIText.StagingView_StagedChangesTooltip);
1209 unstagedSection
1210 .setToolTipText(UIText.StagingView_UnstagedChangesTooltip);
1211 updateToolbar();
1212 enableCommitWidgets(false);
1213 refreshAction.setEnabled(false);
1215 createPopupMenu(unstagedViewer);
1216 createPopupMenu(stagedViewer);
1218 final ICommitMessageComponentNotifications listener = new ICommitMessageComponentNotifications() {
1220 @Override
1221 public void updateSignedOffToggleSelection(boolean selection) {
1222 signedOffByAction.setChecked(selection);
1225 @Override
1226 public void updateChangeIdToggleSelection(boolean selection) {
1227 addChangeIdAction.setChecked(selection);
1228 commitAndPushButton
1229 .setImage(getImage(
1230 selection ? UIIcons.GERRIT : UIIcons.PUSH));
1233 @Override
1234 public void statusUpdated() {
1235 updateMessage();
1238 commitMessageComponent = new CommitMessageComponent(listener);
1239 commitMessageComponent.attachControls(commitMessageText, authorText,
1240 committerText);
1242 // allow to commit with ctrl-enter
1243 commitMessageText.getTextWidget().addVerifyKeyListener(new VerifyKeyListener() {
1244 @Override
1245 public void verifyKey(VerifyEvent event) {
1246 if (UIUtils.isSubmitKeyEvent(event)) {
1247 event.doit = false;
1248 commit(false);
1253 commitMessageText.getTextWidget().addFocusListener(new FocusListener() {
1254 @Override
1255 public void focusGained(FocusEvent e) {
1256 // Ctrl+Enter shortcut only works when the focus is on the commit message text
1257 String commitButtonTooltip = MessageFormat.format(
1258 UIText.StagingView_CommitToolTip,
1259 UIUtils.SUBMIT_KEY_STROKE.format());
1260 commitButton.setToolTipText(commitButtonTooltip);
1263 @Override
1264 public void focusLost(FocusEvent e) {
1265 commitButton.setToolTipText(null);
1269 // react on selection changes
1270 IWorkbenchPartSite site = getSite();
1271 ISelectionService srv = CommonUtils.getService(site, ISelectionService.class);
1272 srv.addPostSelectionListener(selectionChangedListener);
1273 CommonUtils.getService(site, IPartService.class).addPartListener(
1274 partListener);
1276 // Use current selection to populate staging view
1277 UIUtils.notifySelectionChangedWithCurrentSelection(
1278 selectionChangedListener, site);
1280 site.setSelectionProvider(new RepositorySelectionProvider(
1281 new MultiViewerSelectionProvider(unstagedViewer, stagedViewer),
1282 () -> realRepository));
1284 ViewerFilter filter = new ViewerFilter() {
1285 @Override
1286 public boolean select(Viewer viewer, Object parentElement,
1287 Object element) {
1288 StagingViewContentProvider contentProvider = getContentProvider((TreeViewer) viewer);
1289 if (element instanceof StagingEntry)
1290 return contentProvider.isInFilter((StagingEntry) element);
1291 else if (element instanceof StagingFolderEntry)
1292 return contentProvider
1293 .hasVisibleChildren((StagingFolderEntry) element);
1294 return true;
1297 unstagedViewer.addFilter(filter);
1298 stagedViewer.addFilter(filter);
1300 restoreSashFormWeights();
1301 reactOnInitialSelection();
1303 IWorkbenchSiteProgressService service = CommonUtils.getService(
1304 getSite(), IWorkbenchSiteProgressService.class);
1305 if (service != null && reactOnSelection)
1306 // If we are linked, each time IndexDiffUpdateJob starts, indicate
1307 // that the view is busy (e.g. reload() will trigger this job in
1308 // background!).
1309 service.showBusyForFamily(org.eclipse.egit.core.JobFamilies.INDEX_DIFF_CACHE_UPDATE);
1312 private boolean commitAndPushEnabled(boolean commitEnabled) {
1313 Repository repo = currentRepository;
1314 if (repo == null) {
1315 return false;
1317 return commitEnabled && !repo.getRepositoryState().isRebasing();
1320 private void updateIgnoreErrorsButtonVisibility() {
1321 boolean visible = getPreferenceStore()
1322 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
1323 && getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT);
1324 showControl(ignoreErrors, visible);
1325 mainSashForm.layout();
1328 private int getProblemsSeverity() {
1329 int result = IProblemDecoratable.SEVERITY_NONE;
1330 StagingViewContentProvider stagedContentProvider = getContentProvider(
1331 stagedViewer);
1332 StagingEntry[] entries = stagedContentProvider.getStagingEntries();
1333 for (StagingEntry entry : entries) {
1334 if (entry.getProblemSeverity() >= IMarker.SEVERITY_WARNING) {
1335 if (result < entry.getProblemSeverity()) {
1336 result = entry.getProblemSeverity();
1340 return result;
1343 private void updateCommitButtons() {
1344 IndexDiffData indexDiff;
1345 if (cacheEntry != null) {
1346 indexDiff = cacheEntry.getIndexDiff();
1347 } else {
1348 Repository repo = currentRepository;
1349 if (repo == null) {
1350 indexDiff = null;
1351 } else {
1352 indexDiff = doReload(repo);
1355 boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
1356 boolean noConflicts = noConflicts(indexDiff);
1358 boolean commitEnabled = !isCommitBlocked() && noConflicts
1359 && indexDiffAvailable;
1361 boolean commitAndPushEnabled = commitAndPushEnabled(commitEnabled);
1363 commitButton.setEnabled(commitEnabled);
1364 commitAndPushButton.setEnabled(commitAndPushEnabled);
1367 private void saveSashFormWeightsOnDisposal(final SashForm sashForm,
1368 final String settingsKey) {
1369 sashForm.addDisposeListener(new DisposeListener() {
1370 @Override
1371 public void widgetDisposed(DisposeEvent e) {
1372 getDialogSettings().put(settingsKey,
1373 intArrayToString(sashForm.getWeights()));
1378 private IDialogSettings getDialogSettings() {
1379 return DialogSettings.getOrCreateSection(
1380 Activator.getDefault().getDialogSettings(),
1381 StagingView.class.getName());
1384 private static String intArrayToString(int[] ints) {
1385 StringBuilder res = new StringBuilder();
1386 if (ints != null && ints.length > 0) {
1387 res.append(String.valueOf(ints[0]));
1388 for (int i = 1; i < ints.length; i++) {
1389 res.append(',');
1390 res.append(String.valueOf(ints[i]));
1393 return res.toString();
1396 private void restoreSashFormWeights() {
1397 restoreSashFormWeights(mainSashForm,
1398 HORIZONTAL_SASH_FORM_WEIGHT);
1399 restoreSashFormWeights(stagingSashForm,
1400 STAGING_SASH_FORM_WEIGHT);
1403 private void restoreSashFormWeights(SashForm sashForm, String settingsKey) {
1404 IDialogSettings settings = getDialogSettings();
1405 String weights = settings.get(settingsKey);
1406 if (weights != null && !weights.isEmpty()) {
1407 sashForm.setWeights(stringToIntArray(weights));
1411 private static int[] stringToIntArray(String s) {
1412 String[] parts = s.split(","); //$NON-NLS-1$
1413 int[] ints = new int[parts.length];
1414 for (int i = 0; i < parts.length; i++) {
1415 ints[i] = Integer.parseInt(parts[i]);
1417 return ints;
1420 private void reactOnInitialSelection() {
1421 StructuredSelection sel = null;
1422 if (initialSelection instanceof StructuredSelection) {
1423 sel = (StructuredSelection) initialSelection;
1424 } else if (initialSelection != null && !initialSelection.isEmpty()) {
1425 sel = getSelectionOfActiveEditor();
1427 if (sel != null) {
1428 reactOnSelection(sel);
1430 initialSelection = null;
1433 private StructuredSelection getSelectionOfActiveEditor() {
1434 IEditorPart activeEditor = getSite().getPage().getActiveEditor();
1435 if (activeEditor == null) {
1436 return null;
1438 return getSelectionOfPart(activeEditor);
1441 private static StructuredSelection getSelectionOfPart(IWorkbenchPart part) {
1442 StructuredSelection sel = null;
1443 if (part instanceof IEditorPart) {
1444 IResource resource = getResource((IEditorPart) part);
1445 if (resource != null) {
1446 sel = new StructuredSelection(resource);
1447 } else {
1448 Repository repository = getRepository((IEditorPart) part);
1449 if (repository != null) {
1450 sel = new StructuredSelection(repository);
1453 } else {
1454 ISelection selection = part.getSite().getPage().getSelection();
1455 if (selection instanceof StructuredSelection) {
1456 sel = (StructuredSelection) selection;
1459 return sel;
1462 @Nullable
1463 private static Repository getRepository(IEditorPart part) {
1464 IEditorInput input = part.getEditorInput();
1465 if (!(input instanceof IURIEditorInput)) {
1466 return null;
1468 return AdapterUtils.adapt(input, Repository.class);
1471 private static IResource getResource(IEditorPart part) {
1472 IEditorInput input = part.getEditorInput();
1473 if (input instanceof IFileEditorInput) {
1474 return ((IFileEditorInput) input).getFile();
1475 } else {
1476 return AdapterUtils.adaptToAnyResource(input);
1480 private boolean getSortCheckState() {
1481 return getDialogSettings().getBoolean(STORE_SORT_STATE);
1484 private void executeRebaseOperation(AbstractRebaseCommandHandler command) {
1485 try {
1486 command.execute(currentRepository);
1487 } catch (ExecutionException e) {
1488 Activator.showError(e.getMessage(), e);
1493 * Abort rebase command in progress
1495 protected void rebaseAbort() {
1496 AbortRebaseCommand abortCommand = new AbortRebaseCommand();
1497 executeRebaseOperation(abortCommand);
1501 * Rebase next commit and continue rebase in progress
1503 protected void rebaseSkip() {
1504 SkipRebaseCommand skipCommand = new SkipRebaseCommand();
1505 executeRebaseOperation(skipCommand);
1509 * Continue rebase command in progress
1511 protected void rebaseContinue() {
1512 ContinueRebaseCommand continueCommand = new ContinueRebaseCommand();
1513 executeRebaseOperation(continueCommand);
1516 private void createUnstagedToolBarComposite() {
1517 Composite unstagedToolbarComposite = toolkit
1518 .createComposite(unstagedSection);
1519 unstagedToolbarComposite.setBackground(null);
1520 unstagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
1521 unstagedSection.setTextClient(unstagedToolbarComposite);
1522 unstagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
1523 IAction.AS_PUSH_BUTTON) {
1524 @Override
1525 public void run() {
1526 unstagedViewer.expandAll();
1527 enableAutoExpand(unstagedViewer);
1530 unstagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
1531 unstagedExpandAllAction.setId(EXPAND_ALL_ITEM_TOOLBAR_ID);
1533 unstagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
1534 IAction.AS_PUSH_BUTTON) {
1535 @Override
1536 public void run() {
1537 unstagedViewer.collapseAll();
1538 disableAutoExpand(unstagedViewer);
1541 unstagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1542 unstagedCollapseAllAction.setId(COLLAPSE_ALL_ITEM_TOOLBAR_ID);
1544 sortAction = new Action(UIText.StagingView_UnstagedSort,
1545 IAction.AS_CHECK_BOX) {
1547 @Override
1548 public void run() {
1549 StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
1550 .getComparator();
1551 comparator.setAlphabeticSort(!isChecked());
1552 comparator = (StagingEntryComparator) stagedViewer.getComparator();
1553 comparator.setAlphabeticSort(!isChecked());
1554 unstagedViewer.refresh();
1555 stagedViewer.refresh();
1559 sortAction.setImageDescriptor(UIIcons.STATE_SORT);
1560 sortAction.setId(SORT_ITEM_TOOLBAR_ID);
1561 sortAction.setChecked(getSortCheckState());
1563 unstagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
1565 unstagedToolBarManager.add(stageAction);
1566 unstagedToolBarManager.add(stageAllAction);
1567 unstagedToolBarManager.add(sortAction);
1568 unstagedToolBarManager.add(unstagedExpandAllAction);
1569 unstagedToolBarManager.add(unstagedCollapseAllAction);
1571 unstagedToolBarManager.update(true);
1572 unstagedToolBarManager.createControl(unstagedToolbarComposite);
1575 private void createStagedToolBarComposite() {
1576 Composite stagedToolbarComposite = toolkit
1577 .createComposite(stagedSection);
1578 stagedToolbarComposite.setBackground(null);
1579 stagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
1580 stagedSection.setTextClient(stagedToolbarComposite);
1581 stagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
1582 IAction.AS_PUSH_BUTTON) {
1583 @Override
1584 public void run() {
1585 stagedViewer.expandAll();
1586 enableAutoExpand(stagedViewer);
1589 stagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
1590 stagedExpandAllAction.setId(EXPAND_ALL_ITEM_TOOLBAR_ID);
1592 stagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
1593 IAction.AS_PUSH_BUTTON) {
1594 @Override
1595 public void run() {
1596 stagedViewer.collapseAll();
1597 disableAutoExpand(stagedViewer);
1600 stagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1601 stagedCollapseAllAction.setId(COLLAPSE_ALL_ITEM_TOOLBAR_ID);
1603 stagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
1605 stagedToolBarManager.add(unstageAction);
1606 stagedToolBarManager.add(unstageAllAction);
1607 stagedToolBarManager.add(stagedExpandAllAction);
1608 stagedToolBarManager.add(stagedCollapseAllAction);
1609 stagedToolBarManager.update(true);
1610 stagedToolBarManager.createControl(stagedToolbarComposite);
1613 private static RowLayout createRowLayoutWithoutMargin() {
1614 RowLayout layout = new RowLayout();
1615 layout.marginHeight = 0;
1616 layout.marginWidth = 0;
1617 layout.marginTop = 0;
1618 layout.marginBottom = 0;
1619 layout.marginLeft = 0;
1620 layout.marginRight = 0;
1621 return layout;
1624 private static void addListenerToDisableAutoExpandOnCollapse(
1625 TreeViewer treeViewer) {
1626 treeViewer.addTreeListener(new ITreeViewerListener() {
1627 @Override
1628 public void treeCollapsed(TreeExpansionEvent event) {
1629 disableAutoExpand(event.getTreeViewer());
1632 @Override
1633 public void treeExpanded(TreeExpansionEvent event) {
1634 // Nothing to do
1639 private static void enableAutoExpand(AbstractTreeViewer treeViewer) {
1640 treeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
1643 private static void disableAutoExpand(AbstractTreeViewer treeViewer) {
1644 treeViewer.setAutoExpandLevel(0);
1648 * @return selected repository
1650 public Repository getCurrentRepository() {
1651 return currentRepository;
1654 @Override
1655 public ShowInContext getShowInContext() {
1656 if (stagedViewer != null && stagedViewer.getTree().isFocusControl())
1657 return getShowInContext(stagedViewer);
1658 else if (unstagedViewer != null
1659 && unstagedViewer.getTree().isFocusControl())
1660 return getShowInContext(unstagedViewer);
1661 else
1662 return null;
1665 @Override
1666 public boolean show(ShowInContext context) {
1667 ISelection selection = context.getSelection();
1668 if (selection instanceof IStructuredSelection) {
1669 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
1670 for (Object element : structuredSelection.toList()) {
1671 if (element instanceof RepositoryTreeNode) {
1672 RepositoryTreeNode node = (RepositoryTreeNode) element;
1673 reload(node.getRepository());
1674 return true;
1678 return false;
1681 private ShowInContext getShowInContext(TreeViewer treeViewer) {
1682 IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
1683 List<Object> elements = new ArrayList<>();
1684 for (Object selectedElement : selection.toList()) {
1685 if (selectedElement instanceof StagingEntry) {
1686 StagingEntry entry = (StagingEntry) selectedElement;
1687 IFile file = entry.getFile();
1688 if (file != null)
1689 elements.add(file);
1690 else
1691 elements.add(entry.getLocation());
1692 } else if (selectedElement instanceof StagingFolderEntry) {
1693 StagingFolderEntry entry = (StagingFolderEntry) selectedElement;
1694 IContainer container = entry.getContainer();
1695 if (container != null)
1696 elements.add(container);
1697 else
1698 elements.add(entry.getLocation());
1701 return new ShowInContext(null, new StructuredSelection(elements));
1704 private int getStagingFormOrientation() {
1705 boolean columnLayout = Activator.getDefault().getPreferenceStore()
1706 .getBoolean(UIPreferences.STAGING_VIEW_COLUMN_LAYOUT);
1707 if (columnLayout)
1708 return SWT.HORIZONTAL;
1709 else
1710 return SWT.VERTICAL;
1713 private void enableAllWidgets(boolean enabled) {
1714 if (isDisposed())
1715 return;
1716 enableCommitWidgets(enabled);
1717 commitMessageText.setEnabled(enabled);
1718 enableStagingWidgets(enabled);
1721 private void enableStagingWidgets(boolean enabled) {
1722 if (isDisposed())
1723 return;
1724 unstagedViewer.getControl().setEnabled(enabled);
1725 stagedViewer.getControl().setEnabled(enabled);
1728 private void enableCommitWidgets(boolean enabled) {
1729 if (isDisposed()) {
1730 return;
1732 committerText.setEnabled(enabled);
1733 enableAuthorText(enabled);
1734 amendPreviousCommitAction.setEnabled(enabled);
1735 signedOffByAction.setEnabled(enabled);
1736 addChangeIdAction.setEnabled(enabled);
1737 commitButton.setEnabled(enabled);
1738 commitAndPushButton.setEnabled(enabled);
1741 private void enableAuthorText(boolean enabled) {
1742 Repository repo = currentRepository;
1743 if (repo != null && repo.getRepositoryState()
1744 .equals(RepositoryState.CHERRY_PICKING_RESOLVED)) {
1745 authorText.setEnabled(false);
1746 } else {
1747 authorText.setEnabled(enabled);
1751 private void updateToolbar() {
1753 ControlContribution controlContribution = new ControlContribution(
1754 "StagingView.searchText") { //$NON-NLS-1$
1755 @Override
1756 protected Control createControl(Composite parent) {
1757 Composite toolbarComposite = toolkit.createComposite(parent,
1758 SWT.NONE);
1759 toolbarComposite.setBackground(null);
1760 GridLayout headLayout = new GridLayout();
1761 headLayout.numColumns = 2;
1762 headLayout.marginHeight = 0;
1763 headLayout.marginWidth = 0;
1764 headLayout.marginTop = 0;
1765 headLayout.marginBottom = 0;
1766 headLayout.marginLeft = 0;
1767 headLayout.marginRight = 0;
1768 toolbarComposite.setLayout(headLayout);
1770 filterText = new Text(toolbarComposite, SWT.SEARCH
1771 | SWT.ICON_CANCEL | SWT.ICON_SEARCH);
1772 filterText.setMessage(UIText.StagingView_Find);
1773 GridData data = new GridData(GridData.FILL_HORIZONTAL);
1774 data.widthHint = 150;
1775 filterText.setLayoutData(data);
1776 final Display display = Display.getCurrent();
1777 filterText.addModifyListener(new ModifyListener() {
1778 @Override
1779 public void modifyText(ModifyEvent e) {
1780 filterPattern = wildcardToRegex(filterText.getText());
1781 final StagingViewSearchThread searchThread = new StagingViewSearchThread(
1782 StagingView.this);
1783 display.timerExec(200, new Runnable() {
1784 @Override
1785 public void run() {
1786 searchThread.start();
1791 return toolbarComposite;
1795 IActionBars actionBars = getViewSite().getActionBars();
1796 IToolBarManager toolbar = actionBars.getToolBarManager();
1798 toolbar.add(controlContribution);
1800 refreshAction = new Action(UIText.StagingView_Refresh, IAction.AS_PUSH_BUTTON) {
1801 @Override
1802 public void run() {
1803 if (cacheEntry != null) {
1804 schedule(
1805 cacheEntry.createRefreshResourcesAndIndexDiffJob(),
1806 false);
1810 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1811 toolbar.add(refreshAction);
1813 // link with selection
1814 Action linkSelectionAction = new BooleanPrefAction(
1815 (IPersistentPreferenceStore) getPreferenceStore(),
1816 UIPreferences.STAGING_VIEW_SYNC_SELECTION,
1817 UIText.StagingView_LinkSelection) {
1818 @Override
1819 public void apply(boolean value) {
1820 reactOnSelection = value;
1823 linkSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1824 toolbar.add(linkSelectionAction);
1826 toolbar.add(new Separator());
1828 switchRepositoriesAction = new RepositoryToolbarAction(false,
1829 () -> realRepository,
1830 repo -> {
1831 if (realRepository != repo) {
1832 reload(repo);
1835 toolbar.add(switchRepositoriesAction);
1837 compareModeAction = new Action(UIText.StagingView_CompareMode,
1838 IAction.AS_CHECK_BOX) {
1839 @Override
1840 public void run() {
1841 getPreferenceStore().setValue(
1842 UIPreferences.STAGING_VIEW_COMPARE_MODE, isChecked());
1845 compareModeAction.setImageDescriptor(UIIcons.ELCL16_COMPARE_VIEW);
1846 compareModeAction.setChecked(getPreferenceStore()
1847 .getBoolean(UIPreferences.STAGING_VIEW_COMPARE_MODE));
1849 toolbar.add(compareModeAction);
1850 toolbar.add(new Separator());
1852 openNewCommitsAction = new Action(UIText.StagingView_OpenNewCommits,
1853 IAction.AS_CHECK_BOX) {
1855 @Override
1856 public void run() {
1857 getPreferenceStore().setValue(
1858 UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS, isChecked());
1861 openNewCommitsAction.setChecked(getPreferenceStore().getBoolean(
1862 UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS));
1864 columnLayoutAction = new Action(UIText.StagingView_ColumnLayout,
1865 IAction.AS_CHECK_BOX) {
1867 @Override
1868 public void run() {
1869 getPreferenceStore().setValue(
1870 UIPreferences.STAGING_VIEW_COLUMN_LAYOUT, isChecked());
1871 stagingSashForm.setOrientation(isChecked() ? SWT.HORIZONTAL
1872 : SWT.VERTICAL);
1875 columnLayoutAction.setChecked(getPreferenceStore().getBoolean(
1876 UIPreferences.STAGING_VIEW_COLUMN_LAYOUT));
1878 fileNameModeAction = new Action(UIText.StagingView_ShowFileNamesFirst,
1879 IAction.AS_CHECK_BOX) {
1881 @Override
1882 public void run() {
1883 final boolean enable = isChecked();
1884 getLabelProvider(stagedViewer).setFileNameMode(enable);
1885 getLabelProvider(unstagedViewer).setFileNameMode(enable);
1886 getContentProvider(stagedViewer).setFileNameMode(enable);
1887 getContentProvider(unstagedViewer).setFileNameMode(enable);
1888 StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
1889 .getComparator();
1890 comparator.setFileNamesFirst(enable);
1891 comparator = (StagingEntryComparator) stagedViewer.getComparator();
1892 comparator.setFileNamesFirst(enable);
1893 getPreferenceStore().setValue(
1894 UIPreferences.STAGING_VIEW_FILENAME_MODE, enable);
1895 refreshViewersPreservingExpandedElements();
1898 fileNameModeAction.setChecked(getPreferenceStore().getBoolean(
1899 UIPreferences.STAGING_VIEW_FILENAME_MODE));
1901 IMenuManager dropdownMenu = actionBars.getMenuManager();
1902 MenuManager presentationMenu = new MenuManager(
1903 UIText.StagingView_Presentation);
1904 listPresentationAction = new Action(UIText.StagingView_List,
1905 IAction.AS_RADIO_BUTTON) {
1906 @Override
1907 public void run() {
1908 if (!isChecked()) {
1909 return;
1911 switchToListMode();
1912 refreshViewers();
1915 listPresentationAction.setImageDescriptor(UIIcons.FLAT);
1916 presentationMenu.add(listPresentationAction);
1918 treePresentationAction = new Action(UIText.StagingView_Tree,
1919 IAction.AS_RADIO_BUTTON) {
1920 @Override
1921 public void run() {
1922 if (!isChecked()) {
1923 return;
1925 presentation = Presentation.TREE;
1926 setPresentation(presentation, false);
1927 listPresentationAction.setChecked(false);
1928 compactTreePresentationAction.setChecked(false);
1929 setExpandCollapseActionsVisible(false, isExpandAllowed(false),
1930 true);
1931 setExpandCollapseActionsVisible(true, isExpandAllowed(true),
1932 true);
1933 refreshViewers();
1936 treePresentationAction.setImageDescriptor(UIIcons.HIERARCHY);
1937 presentationMenu.add(treePresentationAction);
1939 compactTreePresentationAction = new Action(UIText.StagingView_CompactTree,
1940 IAction.AS_RADIO_BUTTON) {
1941 @Override
1942 public void run() {
1943 if (!isChecked()) {
1944 return;
1946 switchToCompactModeInternal(false);
1947 refreshViewers();
1951 compactTreePresentationAction.setImageDescriptor(UIIcons.COMPACT);
1952 presentationMenu.add(compactTreePresentationAction);
1954 presentation = readPresentation(UIPreferences.STAGING_VIEW_PRESENTATION,
1955 Presentation.LIST);
1956 switch (presentation) {
1957 case LIST:
1958 listPresentationAction.setChecked(true);
1959 setExpandCollapseActionsVisible(false, false, false);
1960 setExpandCollapseActionsVisible(true, false, false);
1961 break;
1962 case TREE:
1963 treePresentationAction.setChecked(true);
1964 break;
1965 case COMPACT_TREE:
1966 compactTreePresentationAction.setChecked(true);
1967 break;
1968 default:
1969 break;
1971 dropdownMenu.add(presentationMenu);
1972 dropdownMenu.add(new Separator());
1973 dropdownMenu.add(openNewCommitsAction);
1974 dropdownMenu.add(columnLayoutAction);
1975 dropdownMenu.add(fileNameModeAction);
1976 dropdownMenu.add(compareModeAction);
1978 actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), new GlobalDeleteActionHandler());
1980 // For the normal resource undo/redo actions to be active, so that files
1981 // deleted via the "Delete" action in the staging view can be restored.
1982 IUndoContext workspaceContext = AdapterUtils.adapt(ResourcesPlugin.getWorkspace(), IUndoContext.class);
1983 undoRedoActionGroup = new UndoRedoActionGroup(getViewSite(), workspaceContext, true);
1984 undoRedoActionGroup.fillActionBars(actionBars);
1986 actionBars.updateActionBars();
1989 private Presentation readPresentation(String key, Presentation def) {
1990 String presentationString = getPreferenceStore().getString(key);
1991 if (presentationString.length() > 0) {
1992 try {
1993 return Presentation.valueOf(presentationString);
1994 } catch (IllegalArgumentException e) {
1995 // Use given default
1998 return def;
2001 private void setPresentation(Presentation newOne, boolean auto) {
2002 Presentation old = presentation;
2003 presentation = newOne;
2004 IPreferenceStore store = getPreferenceStore();
2005 store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION, newOne.name());
2006 if (auto && old != newOne) {
2007 // remember user choice if we switch mode automatically
2008 store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED,
2009 true);
2010 } else {
2011 store.setToDefault(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
2015 private void setExpandCollapseActionsVisible(boolean staged,
2016 boolean visibleExpandAll,
2017 boolean visibleCollapseAll) {
2018 ToolBarManager toolBarManager = staged ? stagedToolBarManager
2019 : unstagedToolBarManager;
2020 for (IContributionItem item : toolBarManager.getItems()) {
2021 String id = item.getId();
2022 if (EXPAND_ALL_ITEM_TOOLBAR_ID.equals(id)) {
2023 item.setVisible(visibleExpandAll);
2024 } else if (COLLAPSE_ALL_ITEM_TOOLBAR_ID.equals(id)) {
2025 item.setVisible(visibleCollapseAll);
2028 (staged ? stagedExpandAllAction : unstagedExpandAllAction)
2029 .setEnabled(visibleExpandAll);
2030 (staged ? stagedCollapseAllAction : unstagedCollapseAllAction)
2031 .setEnabled(visibleCollapseAll);
2032 toolBarManager.update(true);
2035 private boolean isExpandAllowed(boolean staged) {
2036 StagingViewContentProvider contentProvider = getContentProvider(
2037 staged ? stagedViewer : unstagedViewer);
2038 return contentProvider.getCount() <= getMaxLimitForListMode();
2041 private TreeViewer createTree(Composite composite) {
2042 Tree tree = toolkit.createTree(composite, SWT.FULL_SELECTION
2043 | SWT.MULTI);
2044 TreeViewer treeViewer = new TreeViewer(tree);
2045 treeViewer.setUseHashlookup(true);
2046 return treeViewer;
2049 private IBaseLabelProvider createLabelProvider(TreeViewer treeViewer) {
2050 StagingViewLabelProvider baseProvider = new StagingViewLabelProvider(
2051 this);
2052 baseProvider.setFileNameMode(getPreferenceStore().getBoolean(
2053 UIPreferences.STAGING_VIEW_FILENAME_MODE));
2055 ProblemLabelDecorator decorator = new ProblemLabelDecorator(treeViewer);
2056 return new TreeDecoratingLabelProvider(baseProvider, decorator);
2059 private StagingViewContentProvider createStagingContentProvider(
2060 boolean unstaged) {
2061 StagingViewContentProvider provider = new StagingViewContentProvider(
2062 this, unstaged) {
2064 @Override
2065 public void inputChanged(Viewer viewer, Object oldInput,
2066 Object newInput) {
2067 super.inputChanged(viewer, oldInput, newInput);
2068 if (unstaged) {
2069 stageAllAction.setEnabled(getCount() > 0);
2070 unstagedToolBarManager.update(true);
2071 } else {
2072 unstageAllAction.setEnabled(getCount() > 0);
2073 stagedToolBarManager.update(true);
2077 provider.setFileNameMode(getPreferenceStore().getBoolean(
2078 UIPreferences.STAGING_VIEW_FILENAME_MODE));
2079 return provider;
2082 private TreeViewer createViewer(Composite parent, boolean unstaged,
2083 final Consumer<IStructuredSelection> dropAction,
2084 IAction... tooltipActions) {
2085 final TreeViewer viewer = createTree(parent);
2086 GridDataFactory.fillDefaults().grab(true, true)
2087 .applyTo(viewer.getControl());
2088 viewer.getTree().setData(FormToolkit.KEY_DRAW_BORDER,
2089 FormToolkit.TREE_BORDER);
2090 viewer.setLabelProvider(createLabelProvider(viewer));
2091 StagingViewContentProvider contentProvider = createStagingContentProvider(
2092 unstaged);
2093 viewer.setContentProvider(contentProvider);
2094 if (tooltipActions != null && tooltipActions.length > 0) {
2095 StagingViewTooltips tooltips = new StagingViewTooltips(viewer,
2096 tooltipActions);
2097 tooltips.setShift(new Point(1, 1));
2099 viewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
2100 new Transfer[] { LocalSelectionTransfer.getTransfer(),
2101 FileTransfer.getInstance() },
2102 new StagingDragListener(viewer, contentProvider, unstaged));
2103 viewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
2104 new Transfer[] { LocalSelectionTransfer.getTransfer() },
2105 new DropTargetAdapter() {
2107 @Override
2108 public void drop(DropTargetEvent event) {
2109 // Bug 411466: It is very important that detail is set
2110 // to DND.DROP_COPY. If it was left as DND.DROP_MOVE and
2111 // the drag comes from the Navigator view, the code in
2112 // NavigatorDragAdapter would delete the resources.
2113 event.detail = DND.DROP_COPY;
2114 if (event.data instanceof IStructuredSelection) {
2115 final IStructuredSelection selection = (IStructuredSelection) event.data;
2116 if ((selection instanceof StagingDragSelection)
2117 && ((StagingDragSelection) selection)
2118 .isFromUnstaged() == unstaged) {
2119 // Dropped a selection made in this viewer
2120 // back on this viewer: don't do anything,
2121 // otherwise if there are folders in the
2122 // selection, we might unstage or stage files
2123 // not selected!
2124 return;
2126 dropAction.accept(selection);
2130 viewer.addOpenListener(event -> compareWith(event));
2131 viewer.setComparator(new StagingEntryComparator(!getSortCheckState(),
2132 getPreferenceStore()
2133 .getBoolean(UIPreferences.STAGING_VIEW_FILENAME_MODE)));
2134 viewer.addDoubleClickListener(event -> {
2135 IStructuredSelection selection = (IStructuredSelection) event
2136 .getSelection();
2137 Object selectedNode = selection.getFirstElement();
2138 if (selectedNode instanceof StagingFolderEntry) {
2139 viewer.setExpandedState(selectedNode,
2140 !viewer.getExpandedState(selectedNode));
2143 addCopyAction(viewer);
2144 enableAutoExpand(viewer);
2145 addListenerToDisableAutoExpandOnCollapse(viewer);
2146 return viewer;
2149 private void addCopyAction(final TreeViewer viewer) {
2150 IAction copyAction = createSelectionPathCopyAction(viewer);
2152 ActionUtils.setGlobalActions(viewer.getControl(),
2153 getSite().getService(IHandlerService.class), copyAction);
2156 private IAction createSelectionPathCopyAction(final TreeViewer viewer) {
2157 IStructuredSelection selection = (IStructuredSelection) viewer
2158 .getSelection();
2159 String copyPathActionText = (selection.size() <= 1) ? UIText.StagingView_CopyPath
2160 : UIText.StagingView_CopyPaths;
2161 IAction copyAction = ActionUtils.createGlobalAction(ActionFactory.COPY,
2162 () -> copyPathOfSelectionToClipboard(viewer));
2163 copyAction.setText(copyPathActionText);
2164 return copyAction;
2167 private void copyPathOfSelectionToClipboard(final TreeViewer viewer) {
2168 Clipboard cb = new Clipboard(viewer.getControl().getDisplay());
2169 TextTransfer t = TextTransfer.getInstance();
2170 String text = getTextFrom(
2171 (IStructuredSelection) viewer.getSelection());
2172 try {
2173 if (text != null) {
2174 cb.setContents(new Object[] { text }, new Transfer[] { t });
2176 } finally {
2177 cb.dispose();
2181 @Nullable
2182 private String getTextFrom(IStructuredSelection selection) {
2183 Object[] selectionEntries = selection.toArray();
2184 if (selectionEntries.length <= 0) {
2185 return null;
2186 } else if (selectionEntries.length == 1) {
2187 return getPathFrom(selectionEntries[0]);
2188 } else {
2189 StringBuilder sb = new StringBuilder();
2190 for (int i = 0; i < selectionEntries.length; i++) {
2191 String text = getPathFrom(selectionEntries[i]);
2192 if (text != null) {
2193 if (i < selectionEntries.length - 1) {
2194 sb.append(text).append(System.lineSeparator());
2195 } else {
2196 sb.append(text);
2200 return sb.toString();
2204 @Nullable
2205 private String getPathFrom(Object obj) {
2206 if (obj instanceof StagingEntry) {
2207 return ((StagingEntry) obj).getPath();
2208 } else if (obj instanceof StagingFolderEntry) {
2209 return ((StagingFolderEntry) obj).getPath().toString();
2211 return null;
2214 private void setStagingViewerInput(TreeViewer stagingViewer,
2215 StagingViewUpdate newInput, Object[] previous,
2216 Set<IPath> additionalPaths) {
2217 // Disable painting and show a busy cursor for the tree during the
2218 // entire update process.
2219 final Tree tree = stagingViewer.getTree();
2220 tree.setRedraw(false);
2221 Cursor oldCursor = tree.getCursor();
2222 tree.setCursor(tree.getDisplay().getSystemCursor(SWT.CURSOR_WAIT));
2224 try {
2225 // Remember the elements at or before the current top element of the
2226 // viewer.
2227 TreeItem topItem = tree.getTopItem();
2228 final Set<Object> precedingObjects = new LinkedHashSet<>();
2229 if (topItem != null) {
2230 new TreeItemVisitor(tree.getItems()) {
2231 @Override
2232 public boolean visit(TreeItem treeItem) {
2233 precedingObjects.add(treeItem.getData());
2234 return true;
2236 }.traverse(topItem);
2237 precedingObjects.remove(null);
2240 // Controls whether we'll try to preserve the top element in the
2241 // view, i.e., the scroll position. We generally want that unless
2242 // the update has added new objects to the view, in which case those
2243 // are selected and revealed.
2244 boolean preserveTop = true;
2245 boolean keepSelectionVisible = false;
2246 StagingViewUpdate oldInput = (StagingViewUpdate) stagingViewer
2247 .getInput();
2248 if (oldInput != null && oldInput.repository == newInput.repository
2249 && oldInput.indexDiff != null) {
2250 // If the input has changed and wasn't empty before or wasn't
2251 // for a different repository before, record the contents of the
2252 // viewer before the input is changed.
2253 StagingViewContentProvider contentProvider = getContentProvider(
2254 stagingViewer);
2255 ViewerComparator comparator = stagingViewer.getComparator();
2256 Map<String, Object> oldPaths = buildElementMap(stagingViewer,
2257 contentProvider, comparator);
2259 // Update the input.
2260 stagingViewer.setInput(newInput);
2261 // Restore the previous expansion state, if there is one.
2262 if (previous != null) {
2263 expandPreviousExpandedAndPaths(previous, stagingViewer,
2264 additionalPaths);
2267 // Update the selection.
2268 StagingViewerUpdate stagingViewerUpdate = updateSelection(
2269 stagingViewer, contentProvider, oldPaths,
2270 buildElementMap(stagingViewer, contentProvider,
2271 comparator));
2273 // If something has been removed, the element before the removed
2274 // item has been selected, in which case we want to preserve the
2275 // scroll state as much as possible, keeping the selection in
2276 // view. If something has been added, those added things have
2277 // been selected and revealed, so we don't want to preserve the
2278 // top but rather leave the revealed selection alone. If nothing
2279 // has changed, we want to preserve the top, regardless of where
2280 // the current unmodified selection might be, which is what's
2281 // done by default anyway.
2282 if (stagingViewerUpdate == StagingViewerUpdate.REMOVED) {
2283 keepSelectionVisible = true;
2284 } else if (stagingViewerUpdate == StagingViewerUpdate.ADDED) {
2285 preserveTop = false;
2287 } else {
2288 // The update is completely different so don't do any of the
2289 // above analysis to see what's different.
2290 stagingViewer.setInput(newInput);
2291 // Restore the previous expansion state, if there is one.
2292 if (previous != null) {
2293 expandPreviousExpandedAndPaths(previous, stagingViewer,
2294 additionalPaths);
2298 if (preserveTop) {
2299 // It's likely that the tree has scrolled to change the top
2300 // item. So try to restore the current top item to be the one
2301 // with the same data as was at the top before, either starting
2302 // with the selection so that it generally stays in view, or at
2303 // the bottom, if we're not trying to keep the selection
2304 // visible.
2305 TreeItem[] selection = tree.getSelection();
2306 TreeItem initialItem = keepSelectionVisible
2307 && selection.length > 0 ? selection[0] : null;
2308 new TreeItemVisitor(tree.getItems()) {
2309 @Override
2310 public boolean visit(TreeItem treeItem) {
2311 if (precedingObjects.contains(treeItem.getData())) {
2312 // If we reach an item that was at or before the
2313 // original top item, make it the top item
2314 // again, and stop the visitor.
2315 tree.setTopItem(treeItem);
2316 return false;
2318 return true;
2320 }.traverse(initialItem);
2322 } finally {
2323 // The viewer is fully updated now, so we can paint it.
2324 tree.setRedraw(true);
2325 tree.setCursor(oldCursor);
2329 private static Map<String, Object> buildElementMap(TreeViewer stagingViewer,
2330 StagingViewContentProvider contentProvider,
2331 ViewerComparator comparator) {
2332 // Builds a map from paths, represented as strings, to elements visible
2333 // in the staging viewer.
2334 Map<String, Object> result = new LinkedHashMap<>();
2335 // Start visiting the root elements in the order in which they appear in
2336 // the UI.
2337 Object[] elements = contentProvider.getElements(null);
2338 comparator.sort(stagingViewer, elements);
2339 for (Object element : elements) {
2340 visitElement(stagingViewer, contentProvider, comparator, element,
2341 result);
2343 return result;
2346 private static boolean visitElement(TreeViewer stagingViewer,
2347 StagingViewContentProvider contentProvider,
2348 ViewerComparator comparator,
2349 Object element, Map<String, Object> paths) {
2350 if (element instanceof StagingEntry) {
2351 StagingEntry stagingEntry = (StagingEntry) element;
2352 if (contentProvider.isInFilter(stagingEntry)) {
2353 // If the element is a staging entry, and it's included by the
2354 // filter, add a mapping for it.
2355 String path = stagingEntry.getPath();
2356 paths.put(path, stagingEntry);
2357 return true;
2360 return false;
2363 // If the element is a staging folder entry, visit all the children,
2364 // checking that at least one visited descendant has been added to the
2365 // map before adding a mapping for this staging folder entry.
2366 if (element instanceof StagingFolderEntry) {
2367 StagingFolderEntry stagingFolderEntry = (StagingFolderEntry) element;
2368 // Visit the children in the order in which they appear in the UI.
2369 Object[] children = contentProvider.getChildren(stagingFolderEntry);
2370 comparator.sort(stagingViewer, children);
2372 IPath path = stagingFolderEntry.getPath();
2373 String pathString = path.toString();
2374 paths.put(pathString, stagingFolderEntry);
2376 boolean hasVisibleChildren = false;
2377 for (Object child : children) {
2378 if (visitElement(stagingViewer, contentProvider, comparator,
2379 child, paths)) {
2380 hasVisibleChildren = true;
2384 if (hasVisibleChildren) {
2385 return true;
2388 // If there were no visible children, remove the path from the map.
2389 paths.remove(pathString);
2390 return false;
2393 return false;
2396 private enum StagingViewerUpdate {
2397 ADDED, REMOVED, UNCHANGED
2401 * Updates the selection depending on the type of change in the staging
2402 * viewer's state. If something has been removed, it returns
2403 * {@link StagingViewerUpdate#REMOVED} and the item before the removed
2404 * element is selected. If something has been added, it returns
2405 * {@link StagingViewerUpdate#ADDED} and those added elements are selected
2406 * and revealed. If nothing has changed, it returns
2407 * {@link StagingViewerUpdate#UNCHANGED} and the selection state is
2408 * unchanged.
2410 * @param stagingViewer
2411 * the staging viewer for which to update the selection.
2412 * @param contentProvider
2413 * the content provider used by that staging viewer.
2414 * @param oldPaths
2415 * the old content state of the staging viewer.
2416 * @param newPaths
2417 * the new content state of the staging viewer.
2418 * @return the type of change to the selecting of the staging viewer
2420 private static StagingViewerUpdate updateSelection(TreeViewer stagingViewer,
2421 StagingViewContentProvider contentProvider,
2422 Map<String, Object> oldPaths, Map<String, Object> newPaths) {
2423 // Update the staging viewer's selection by analyzing the change
2424 // to the contents of the viewer.
2425 Map<String, Object> addedPaths = new LinkedHashMap<>(newPaths);
2426 addedPaths.keySet().removeAll(oldPaths.keySet());
2427 if (!addedPaths.isEmpty()) {
2428 // If anything has been added to the viewer, select those added
2429 // things. But, to minimize the selection, select a parent node when
2430 // all its children have been added. The general idea is that if you
2431 // drag and drop between staged and unstaged, the new selection in
2432 // the target view, when dragged back again to the source view, will
2433 // undo the original drag-and-drop operation operation.
2434 List<Object> newSelection = new ArrayList<>();
2435 Set<Object> elements = new LinkedHashSet<>(addedPaths.values());
2436 Set<Object> excludeChildren = new LinkedHashSet<>();
2437 for (Object element : elements) {
2438 if (element instanceof StagingEntry) {
2439 StagingEntry stagingEntry = (StagingEntry) element;
2440 if (!excludeChildren.contains(stagingEntry.getParent())) {
2441 // If it's a leaf entry and its parent has not been
2442 // excluded from the selection, include it in the
2443 // selection.
2444 newSelection.add(stagingEntry);
2446 } else if (element instanceof StagingFolderEntry) {
2447 StagingFolderEntry stagingFolderEntry = (StagingFolderEntry) element;
2448 StagingFolderEntry parent = stagingFolderEntry.getParent();
2449 if (excludeChildren.contains(parent)) {
2450 // If its parent has been excluded from the selection,
2451 // exclude this folder entry also.
2452 excludeChildren.add(stagingFolderEntry);
2453 } else if (elements.containsAll(contentProvider
2454 .getStagingEntriesFiltered(stagingFolderEntry))) {
2455 // If all of this folder's visible children are added,
2456 // i.e., it had no existing children before, then
2457 // include it in the selection, and exclude its
2458 // children from the selection.
2459 newSelection.add(stagingFolderEntry);
2460 excludeChildren.add(stagingFolderEntry);
2465 // Select and reveal the selection of the newly added elements.
2466 stagingViewer.setSelection(new StructuredSelection(newSelection),
2467 true);
2468 return StagingViewerUpdate.ADDED;
2469 } else {
2470 Map<String, Object> removedPaths = new LinkedHashMap<>(oldPaths);
2471 removedPaths.keySet().removeAll(newPaths.keySet());
2472 if (!removedPaths.isEmpty()) {
2473 // If anything has been removed from the viewer, try to select
2474 // the closest following unremoved sibling of the first removed
2475 // element, a parent if there isn't such a sibling, or the first
2476 // element in the viewer failing those. The general idea is that
2477 // it's really annoying to have the viewer scroll to the top
2478 // element whenever you drag something out of a staging viewer.
2479 Collection<Object> removedElements = removedPaths.values();
2480 Object firstRemovedElement = removedElements.iterator()
2481 .next();
2482 Object parent = contentProvider.getParent(firstRemovedElement);
2483 Object candidate = null;
2484 boolean visitSubsequentSiblings = false;
2485 for (Object oldElement : oldPaths.values()) {
2486 if (oldElement == firstRemovedElement) {
2487 // Once we reach the first removed element, siblings
2488 // that follow are ideal candidates.
2489 visitSubsequentSiblings = true;
2492 if (visitSubsequentSiblings) {
2493 if (!removedElements.contains(oldElement)) {
2494 if (contentProvider
2495 .getParent(oldElement) == parent) {
2496 // If this is a subsequent sibling that's not
2497 // itself removed, it's the best candidate.
2498 candidate = oldElement;
2499 break;
2500 } else if (candidate != null) {
2501 // If we already have a candidate, and we're
2502 // looking for a subsequent sibling, but now
2503 // we've hit an element with a different parent
2504 // of the removed element, then we're never
2505 // going to find a subsequent unremoved sibling,
2506 // so just return the candidate.
2507 break;
2510 } else if (candidate == null || oldElement == parent
2511 || contentProvider
2512 .getParent(oldElement) == parent) {
2513 // If there is no candidate, or there is a better
2514 // candidate, i.e., the parent or an element with the
2515 // same parent, record the current entry.
2516 candidate = oldElement;
2520 if (candidate == null && !newPaths.isEmpty()) {
2521 // If there is no selected object yet, just choose the first
2522 // element in the viewer, if there is such an element.
2523 candidate = newPaths.values().iterator().next();
2526 if (candidate != null) {
2527 // If we have a selection, which will always be the case
2528 // unless the viewer is empty, set it. This selection is
2529 // preserved during update of the viewer. Unfortunately the
2530 // scroll position is generally quite poor. Fixing the
2531 // scroll position is done after the viewer is updated.
2532 stagingViewer.setSelection(
2533 new StructuredSelection(candidate), true);
2534 return StagingViewerUpdate.REMOVED;
2538 return StagingViewerUpdate.UNCHANGED;
2543 * This visitor is used to traverse all visible tree items of a tree viewer
2544 * starting at some specific item, visiting the items in the reverse order
2545 * in which they appear in the UI.
2547 private static abstract class TreeItemVisitor {
2548 private final TreeItem[] roots;
2550 public TreeItemVisitor(TreeItem[] roots) {
2551 this.roots = roots;
2554 public abstract boolean visit(TreeItem treeItem);
2557 * The public entry point for invoking this visitor.
2559 * @param treeItem
2560 * the item at which to start, are null, to start at the
2561 * bottom.
2563 public void traverse(TreeItem treeItem) {
2564 if (treeItem == null) {
2565 treeItem = getLastItem(roots);
2566 if (treeItem == null) {
2567 return;
2570 if (treeItem.isDisposed()) {
2571 return;
2573 if (treeItem.getData() != null && visit(treeItem)) {
2574 traversePrecedingSiblings(treeItem);
2578 private TreeItem getLastItem(TreeItem[] treeItems) {
2579 if (treeItems.length == 0) {
2580 return null;
2582 TreeItem lastItem = treeItems[treeItems.length - 1];
2583 if (lastItem.getExpanded()) {
2584 TreeItem result = getLastItem(lastItem.getItems());
2585 if (result != null) {
2586 return result;
2589 return lastItem;
2592 private boolean traversePrecedingSiblings(TreeItem treeItem) {
2593 TreeItem parent = treeItem.getParentItem();
2594 if (parent == null) {
2595 // If there is no parent, traverse based on the root items.
2596 return traversePrecedingSiblings(roots, treeItem);
2598 // Traverse based on the parent items, i.e., the siblings of the
2599 // tree item.
2600 if (!traversePrecedingSiblings(parent.getItems(), treeItem)) {
2601 return false;
2603 // Recursively traverse the parent.
2604 return traversePrecedingSiblings(parent);
2607 private boolean traversePrecedingSiblings(TreeItem[] siblings,
2608 TreeItem treeItem) {
2609 // Traverse the siblings in reverse order, skipping the ones that
2610 // are at or before the tree item.
2611 boolean start = false;
2612 for (int i = siblings.length - 1; i >= 0; --i) {
2613 TreeItem sibling = siblings[i];
2614 if (start) {
2615 // Traverse all the visible children of this preceding
2616 // sibling.
2617 if (!traverseChildren(sibling)) {
2618 return false;
2620 } else if (sibling == treeItem) {
2621 start = true;
2625 return true;
2628 private boolean traverseChildren(TreeItem treeItem) {
2629 if (treeItem.getExpanded()) {
2630 // If the tree item is expanded, traverse all the children in
2631 // reverse order.
2632 TreeItem[] children = treeItem.getItems();
2633 for (int i = children.length - 1; i >= 0; --i) {
2634 // Recursively traverse the children of the children.
2635 if (!traverseChildren(children[i])) {
2636 return false;
2640 // Call the visitor callback after the children have been visited.
2641 return visit(treeItem);
2645 private IPreferenceStore getPreferenceStore() {
2646 return Activator.getDefault().getPreferenceStore();
2649 private StagingViewLabelProvider getLabelProvider(ContentViewer viewer) {
2650 IBaseLabelProvider base = viewer.getLabelProvider();
2651 ILabelProvider labelProvider = ((TreeDecoratingLabelProvider) base)
2652 .getLabelProvider();
2653 return (StagingViewLabelProvider) labelProvider;
2656 private StagingViewContentProvider getContentProvider(ContentViewer viewer) {
2657 return (StagingViewContentProvider) viewer.getContentProvider();
2660 private void updateSectionText() {
2661 stagedSection.setText(MessageFormat
2662 .format(UIText.StagingView_StagedChanges,
2663 getSectionCount(stagedViewer)));
2664 unstagedSection.setText(MessageFormat.format(
2665 UIText.StagingView_UnstagedChanges,
2666 getSectionCount(unstagedViewer)));
2669 private String getSectionCount(TreeViewer viewer) {
2670 StagingViewContentProvider contentProvider = getContentProvider(viewer);
2671 int count = contentProvider.getCount();
2672 int shownCount = contentProvider.getShownCount();
2673 if (shownCount == count)
2674 return Integer.toString(count);
2675 else
2676 return shownCount + "/" + count; //$NON-NLS-1$
2679 private void updateMessage() {
2680 if (hasErrorsOrWarnings()) {
2681 warningLabel.showMessage(UIText.StagingView_MessageErrors);
2682 commitMessageSection.redraw();
2683 } else {
2684 String message = commitMessageComponent.getStatus().getMessage();
2685 boolean needsRedraw = false;
2686 if (message != null) {
2687 warningLabel.showMessage(message);
2688 needsRedraw = true;
2689 } else if (isUnbornHead) {
2690 warningLabel.showInfo(MessageFormat.format(
2691 UIText.StagingView_InitialCommitText, currentBranch));
2692 needsRedraw = true;
2693 } else {
2694 needsRedraw = warningLabel.getVisible();
2695 warningLabel.hideMessage();
2697 // Without this explicit redraw, the ControlDecoration of the
2698 // commit message area would not get updated and cause visual
2699 // corruption.
2700 if (needsRedraw)
2701 commitMessageSection.redraw();
2705 private void compareWith(OpenEvent event) {
2706 IStructuredSelection selection = (IStructuredSelection) event
2707 .getSelection();
2708 if (selection.isEmpty()
2709 || !(selection.getFirstElement() instanceof StagingEntry))
2710 return;
2711 StagingEntry stagingEntry = (StagingEntry) selection.getFirstElement();
2712 if (stagingEntry.isSubmodule())
2713 return;
2714 switch (stagingEntry.getState()) {
2715 case ADDED:
2716 case CHANGED:
2717 case REMOVED:
2718 runCommand(ActionCommands.COMPARE_INDEX_WITH_HEAD_ACTION, selection);
2719 break;
2721 case CONFLICTING:
2722 runCommand(ActionCommands.MERGE_TOOL_ACTION, selection);
2723 break;
2725 case MISSING:
2726 case MISSING_AND_CHANGED:
2727 case MODIFIED:
2728 case MODIFIED_AND_CHANGED:
2729 case MODIFIED_AND_ADDED:
2730 case UNTRACKED:
2731 default:
2732 if (Activator.getDefault().getPreferenceStore().getBoolean(
2733 UIPreferences.STAGING_VIEW_COMPARE_MODE)) {
2734 // compare with index
2735 runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION, selection);
2736 } else {
2737 openSelectionInEditor(selection);
2743 private void createPopupMenu(final TreeViewer treeViewer) {
2744 final MenuManager menuMgr = new MenuManager();
2745 menuMgr.setRemoveAllWhenShown(true);
2746 Control control = treeViewer.getControl();
2747 control.setMenu(menuMgr.createContextMenu(control));
2748 menuMgr.addMenuListener(new IMenuListener() {
2750 @Override
2751 public void menuAboutToShow(IMenuManager manager) {
2752 control.setFocus();
2753 final IStructuredSelection selection = (IStructuredSelection) treeViewer
2754 .getSelection();
2755 if (selection.isEmpty())
2756 return;
2758 Set<StagingEntry> stagingEntrySet = new LinkedHashSet<>();
2759 Set<StagingFolderEntry> stagingFolderSet = new LinkedHashSet<>();
2761 boolean submoduleSelected = false;
2762 boolean folderSelected = false;
2763 boolean onlyFoldersSelected = true;
2764 for (Object element : selection.toArray()) {
2765 if (element instanceof StagingFolderEntry) {
2766 StagingFolderEntry folder = (StagingFolderEntry) element;
2767 folderSelected = true;
2768 if (onlyFoldersSelected) {
2769 stagingFolderSet.add(folder);
2771 StagingViewContentProvider contentProvider = getContentProvider(treeViewer);
2772 stagingEntrySet.addAll(contentProvider
2773 .getStagingEntriesFiltered(folder));
2774 } else if (element instanceof StagingEntry) {
2775 if (onlyFoldersSelected) {
2776 stagingFolderSet.clear();
2778 onlyFoldersSelected = false;
2779 StagingEntry entry = (StagingEntry) element;
2780 if (entry.isSubmodule()) {
2781 submoduleSelected = true;
2783 stagingEntrySet.add(entry);
2787 List<StagingEntry> stagingEntryList = new ArrayList<>(
2788 stagingEntrySet);
2789 final IStructuredSelection fileSelection = new StructuredSelection(
2790 stagingEntryList);
2791 stagingEntrySet = null;
2793 if (!folderSelected) {
2794 Action openWorkingTreeVersion = new Action(
2795 UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) {
2796 @Override
2797 public void run() {
2798 openSelectionInEditor(fileSelection);
2801 openWorkingTreeVersion.setEnabled(!submoduleSelected
2802 && anyElementIsExistingFile(fileSelection));
2803 menuMgr.add(openWorkingTreeVersion);
2804 String label = stagingEntryList.get(0).isStaged()
2805 ? UIText.CommitFileDiffViewer_CompareWorkingDirectoryMenuLabel
2806 : UIText.StagingView_CompareWithIndexMenuLabel;
2807 Action openCompareWithIndex = new Action(label) {
2808 @Override
2809 public void run() {
2810 runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION,
2811 fileSelection);
2814 menuMgr.add(openCompareWithIndex);
2817 menuMgr.add(createSelectionPathCopyAction(treeViewer));
2819 Set<StagingEntry.Action> availableActions = getAvailableActions(fileSelection);
2821 boolean addReplaceWithFileInGitIndex = availableActions.contains(StagingEntry.Action.REPLACE_WITH_FILE_IN_GIT_INDEX);
2822 boolean addReplaceWithHeadRevision = availableActions.contains(StagingEntry.Action.REPLACE_WITH_HEAD_REVISION);
2823 boolean addStage = availableActions.contains(StagingEntry.Action.STAGE);
2824 boolean addUnstage = availableActions.contains(StagingEntry.Action.UNSTAGE);
2825 boolean addDelete = availableActions.contains(StagingEntry.Action.DELETE);
2826 boolean addIgnore = availableActions.contains(StagingEntry.Action.IGNORE);
2827 boolean addLaunchMergeTool = availableActions.contains(StagingEntry.Action.LAUNCH_MERGE_TOOL);
2828 boolean addReplaceWithOursTheirsMenu = availableActions
2829 .contains(StagingEntry.Action.REPLACE_WITH_OURS_THEIRS_MENU);
2830 boolean addAssumeUnchanged = availableActions
2831 .contains(StagingEntry.Action.ASSUME_UNCHANGED);
2832 boolean addUntrack = availableActions
2833 .contains(StagingEntry.Action.UNTRACK);
2835 if (addStage) {
2836 menuMgr.add(
2837 new Action(UIText.StagingView_StageItemMenuLabel,
2838 UIIcons.ELCL16_ADD) {
2839 @Override
2840 public void run() {
2841 stage(selection);
2845 if (addUnstage) {
2846 menuMgr.add(
2847 new Action(UIText.StagingView_UnstageItemMenuLabel,
2848 UIIcons.UNSTAGE) {
2849 @Override
2850 public void run() {
2851 unstage(selection);
2855 boolean selectionIncludesNonWorkspaceResources = selectionIncludesNonWorkspaceResources(fileSelection);
2856 if (addReplaceWithFileInGitIndex) {
2857 if (selectionIncludesNonWorkspaceResources) {
2858 menuMgr.add(new ReplaceAction(
2859 UIText.StagingView_replaceWithFileInGitIndex,
2860 fileSelection, false));
2861 } else {
2862 menuMgr.add(createItem(
2863 UIText.StagingView_replaceWithFileInGitIndex,
2864 ActionCommands.DISCARD_CHANGES_ACTION,
2865 fileSelection)); // replace with index
2868 if (addReplaceWithHeadRevision) {
2869 if (selectionIncludesNonWorkspaceResources) {
2870 menuMgr.add(new ReplaceAction(
2871 UIText.StagingView_replaceWithHeadRevision,
2872 fileSelection, true));
2873 } else {
2874 menuMgr.add(createItem(
2875 UIText.StagingView_replaceWithHeadRevision,
2876 ActionCommands.REPLACE_WITH_HEAD_ACTION,
2877 fileSelection));
2880 if (addIgnore) {
2881 if (!stagingFolderSet.isEmpty()) {
2882 menuMgr.add(new IgnoreFoldersAction(stagingFolderSet));
2884 menuMgr.add(new IgnoreAction(fileSelection));
2886 if (addDelete) {
2887 menuMgr.add(new DeleteAction(fileSelection));
2889 if (addLaunchMergeTool) {
2890 menuMgr.add(createItem(UIText.StagingView_MergeTool,
2891 ActionCommands.MERGE_TOOL_ACTION,
2892 fileSelection));
2894 if (addReplaceWithOursTheirsMenu) {
2895 MenuManager replaceWithMenu = new MenuManager(
2896 UIText.StagingView_ReplaceWith);
2897 ReplaceWithOursTheirsMenu oursTheirsMenu = new ReplaceWithOursTheirsMenu();
2898 oursTheirsMenu.initialize(getSite());
2899 replaceWithMenu.add(oursTheirsMenu);
2900 menuMgr.add(replaceWithMenu);
2902 if (addAssumeUnchanged) {
2903 menuMgr.add(
2904 new Action(UIText.StagingView_Assume_Unchanged,
2905 UIIcons.ASSUME_UNCHANGED) {
2906 @Override
2907 public void run() {
2908 assumeUnchanged(selection);
2912 if (addUntrack) {
2913 menuMgr.add(new Action(UIText.StagingView_Untrack,
2914 UIIcons.UNTRACK) {
2915 @Override
2916 public void run() {
2917 untrack(selection);
2921 menuMgr.add(new Separator());
2922 menuMgr.add(createShowInMenu());
2928 private boolean anyElementIsExistingFile(IStructuredSelection s) {
2929 for (Object element : s.toList()) {
2930 if (element instanceof StagingEntry) {
2931 StagingEntry entry = (StagingEntry) element;
2932 if (entry.getType() != IResource.FILE) {
2933 continue;
2935 if (entry.getLocation().toFile().exists()) {
2936 return true;
2940 return false;
2944 * @return selected presentation
2946 Presentation getPresentation() {
2947 return presentation;
2951 * @return the trimmed string which is the current filter, empty string for
2952 * no filter
2954 Pattern getFilterPattern() {
2955 return filterPattern;
2959 * Convert a filter string to a regex pattern. Wildcard characters "*" will
2960 * match anything, other characters must match literally (case insensitive).
2961 * The filter string will be trimmed.
2963 * @param filter
2964 * @return compiled pattern, or {@code null} if trimmed filter input is
2965 * empty
2967 private Pattern wildcardToRegex(String filter) {
2968 String trimmed = filter.trim();
2969 if (trimmed.isEmpty()) {
2970 return null;
2972 String regex = (trimmed.contains("*") ? "^" : "") + "\\Q"//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
2973 + trimmed.replaceAll("\\*", //$NON-NLS-1$
2974 Matcher.quoteReplacement("\\E.*?\\Q")) //$NON-NLS-1$
2975 + "\\E";//$NON-NLS-1$
2976 // remove potentially empty quotes at begin or end
2977 regex = regex.replaceAll(Pattern.quote("\\Q\\E"), ""); //$NON-NLS-1$ //$NON-NLS-2$
2978 return Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
2982 * Refresh the unstaged and staged viewers without preserving expanded
2983 * elements
2985 public void refreshViewers() {
2986 syncExec(new Runnable() {
2987 @Override
2988 public void run() {
2989 refreshViewersInternal();
2995 * Refresh the unstaged and staged viewers, preserving expanded elements
2997 public void refreshViewersPreservingExpandedElements() {
2998 syncExec(new Runnable() {
2999 @Override
3000 public void run() {
3001 Object[] unstagedExpanded = unstagedViewer.getVisibleExpandedElements();
3002 Object[] stagedExpanded = stagedViewer.getVisibleExpandedElements();
3003 refreshViewersInternal();
3004 unstagedViewer.setExpandedElements(unstagedExpanded);
3005 stagedViewer.setExpandedElements(stagedExpanded);
3010 private void refreshViewersInternal() {
3011 unstagedViewer.refresh();
3012 stagedViewer.refresh();
3013 updateSectionText();
3016 private IContributionItem createShowInMenu() {
3017 IWorkbenchWindow workbenchWindow = getSite().getWorkbenchWindow();
3018 return UIUtils.createShowInMenu(workbenchWindow);
3021 private class ReplaceAction extends Action {
3023 IStructuredSelection selection;
3024 private final boolean headRevision;
3026 ReplaceAction(String text, @NonNull IStructuredSelection selection,
3027 boolean headRevision) {
3028 super(text);
3029 this.selection = selection;
3030 this.headRevision = headRevision;
3033 private void getSelectedFiles(@NonNull List<String> files,
3034 @NonNull List<String> inaccessibleFiles) {
3035 Iterator iterator = selection.iterator();
3036 while (iterator.hasNext()) {
3037 Object selectedItem = iterator.next();
3038 if (selectedItem instanceof StagingEntry) {
3039 StagingEntry stagingEntry = (StagingEntry) selectedItem;
3040 String path = stagingEntry.getPath();
3041 files.add(path);
3042 IFile resource = stagingEntry.getFile();
3043 if (resource == null || !resource.isAccessible()) {
3044 inaccessibleFiles.add(path);
3050 private void replaceWith(@NonNull List<String> files,
3051 @NonNull List<String> inaccessibleFiles) {
3052 Repository repository = currentRepository;
3053 if (files.isEmpty() || repository == null) {
3054 return;
3056 try (Git git = new Git(repository)) {
3057 CheckoutCommand checkoutCommand = git.checkout();
3058 if (headRevision) {
3059 checkoutCommand.setStartPoint(Constants.HEAD);
3061 for (String path : files) {
3062 checkoutCommand.addPath(path);
3064 checkoutCommand.call();
3065 if (!inaccessibleFiles.isEmpty()) {
3066 IndexDiffCacheEntry indexDiffCacheForRepository = org.eclipse.egit.core.Activator
3067 .getDefault().getIndexDiffCache()
3068 .getIndexDiffCacheEntry(repository);
3069 if (indexDiffCacheForRepository != null) {
3070 indexDiffCacheForRepository
3071 .refreshFiles(inaccessibleFiles);
3074 } catch (Exception e) {
3075 Activator.handleError(UIText.StagingView_checkoutFailed, e,
3076 true);
3080 @Override
3081 public void run() {
3082 String question = UIText.DiscardChangesAction_confirmActionMessage;
3083 ILaunchConfiguration launch = LaunchFinder
3084 .getRunningLaunchConfiguration(
3085 Collections.singleton(getCurrentRepository()),
3086 null);
3087 if (launch != null) {
3088 question = MessageFormat.format(question,
3089 "\n\n" + MessageFormat.format( //$NON-NLS-1$
3090 UIText.LaunchFinder_RunningLaunchMessage,
3091 launch.getName()));
3092 } else {
3093 question = MessageFormat.format(question, ""); //$NON-NLS-1$
3096 MessageDialog dlg = new MessageDialog(form.getShell(),
3097 UIText.DiscardChangesAction_confirmActionTitle, null,
3098 question, MessageDialog.CONFIRM,
3099 new String[] {
3100 UIText.DiscardChangesAction_discardChangesButtonText,
3101 IDialogConstants.CANCEL_LABEL },
3103 if (dlg.open() != Window.OK) {
3104 return;
3106 List<String> files = new ArrayList<>();
3107 List<String> inaccessibleFiles = new ArrayList<>();
3108 getSelectedFiles(files, inaccessibleFiles);
3109 replaceWith(files, inaccessibleFiles);
3113 private static class IgnoreAction extends Action {
3115 private final IStructuredSelection selection;
3117 IgnoreAction(IStructuredSelection selection) {
3118 super(UIText.StagingView_IgnoreItemMenuLabel);
3119 this.selection = selection;
3122 @Override
3123 public void run() {
3124 IgnoreOperationUI operation = new IgnoreOperationUI(
3125 getSelectedPaths(selection));
3126 operation.run();
3130 private static class IgnoreFoldersAction extends Action {
3131 private final Set<StagingFolderEntry> selection;
3133 IgnoreFoldersAction(Set<StagingFolderEntry> selection) {
3134 super(UIText.StagingView_IgnoreFolderMenuLabel);
3135 this.selection = selection;
3138 @Override
3139 public void run() {
3140 List<IPath> paths = new ArrayList<>();
3141 for (StagingFolderEntry folder : selection) {
3142 paths.add(folder.getLocation());
3144 IgnoreOperationUI operation = new IgnoreOperationUI(paths);
3145 operation.run();
3150 private class DeleteAction extends Action {
3152 private final IStructuredSelection selection;
3154 DeleteAction(IStructuredSelection selection) {
3155 super(UIText.StagingView_DeleteItemMenuLabel,
3156 UIIcons.ELCL16_DELETE);
3157 this.selection = selection;
3160 @Override
3161 public void run() {
3162 DeletePathsOperationUI operation = new DeletePathsOperationUI(
3163 getSelectedPaths(selection), getSite());
3164 operation.run();
3168 private class GlobalDeleteActionHandler extends Action {
3170 @Override
3171 public void run() {
3172 DeletePathsOperationUI operation = new DeletePathsOperationUI(
3173 getSelectedPaths(getSelection()), getSite());
3174 operation.run();
3177 @Override
3178 public boolean isEnabled() {
3179 if (!unstagedViewer.getTree().isFocusControl())
3180 return false;
3182 IStructuredSelection selection = getSelection();
3183 if (selection.isEmpty())
3184 return false;
3186 for (Object element : selection.toList()) {
3187 if (!(element instanceof StagingEntry))
3188 return false;
3189 StagingEntry entry = (StagingEntry) element;
3190 if (!entry.getAvailableActions().contains(StagingEntry.Action.DELETE))
3191 return false;
3194 return true;
3197 private IStructuredSelection getSelection() {
3198 return (IStructuredSelection) unstagedViewer.getSelection();
3202 private static List<IPath> getSelectedPaths(IStructuredSelection selection) {
3203 List<IPath> paths = new ArrayList<>();
3204 Iterator iterator = selection.iterator();
3205 while (iterator.hasNext()) {
3206 StagingEntry stagingEntry = (StagingEntry) iterator.next();
3207 paths.add(stagingEntry.getLocation());
3209 return paths;
3213 * @param selection
3214 * @return true if the selection includes a non-workspace resource, false otherwise
3216 private boolean selectionIncludesNonWorkspaceResources(ISelection selection) {
3217 if (!(selection instanceof IStructuredSelection))
3218 return false;
3219 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
3220 Iterator iterator = structuredSelection.iterator();
3221 while (iterator.hasNext()) {
3222 Object selectedObject = iterator.next();
3223 if (!(selectedObject instanceof StagingEntry))
3224 return false;
3225 StagingEntry stagingEntry = (StagingEntry) selectedObject;
3226 IFile file = stagingEntry.getFile();
3227 if (file == null || !file.isAccessible()) {
3228 return true;
3231 return false;
3234 private void openSelectionInEditor(ISelection s) {
3235 Repository repo = currentRepository;
3236 if (repo == null || s.isEmpty() || !(s instanceof IStructuredSelection)) {
3237 return;
3239 final IStructuredSelection iss = (IStructuredSelection) s;
3240 for (Object element : iss.toList()) {
3241 if (element instanceof StagingEntry) {
3242 StagingEntry entry = (StagingEntry) element;
3243 String relativePath = entry.getPath();
3244 File file = new Path(repo.getWorkTree().getAbsolutePath())
3245 .append(relativePath).toFile();
3246 DiffViewer.openFileInEditor(file, -1);
3251 private static Set<StagingEntry.Action> getAvailableActions(IStructuredSelection selection) {
3252 Set<StagingEntry.Action> availableActions = EnumSet.noneOf(StagingEntry.Action.class);
3253 for (Iterator it = selection.iterator(); it.hasNext(); ) {
3254 StagingEntry stagingEntry = (StagingEntry) it.next();
3255 if (availableActions.isEmpty())
3256 availableActions.addAll(stagingEntry.getAvailableActions());
3257 else
3258 availableActions.retainAll(stagingEntry.getAvailableActions());
3260 return availableActions;
3263 private IAction createItem(String text, final String commandId,
3264 final IStructuredSelection selection) {
3265 return new Action(text) {
3266 @Override
3267 public void run() {
3268 CommonUtils.runCommand(commandId, selection);
3273 private boolean shouldUpdateSelection() {
3274 return !isDisposed() && !isViewHidden && reactOnSelection;
3277 private void reactOnSelection(StructuredSelection selection) {
3278 if (selection.size() != 1 || isDisposed()) {
3279 return;
3281 if (!shouldUpdateSelection()) {
3282 // Remember it all the same to be able to update the view when it
3283 // becomes active again
3284 lastSelection = reactOnSelection ? selection : null;
3285 return;
3287 lastSelection = null;
3288 Object firstElement = selection.getFirstElement();
3289 if (firstElement instanceof RepositoryTreeNode) {
3290 RepositoryTreeNode repoNode = (RepositoryTreeNode) firstElement;
3291 if (currentRepository != repoNode.getRepository()) {
3292 reload(repoNode.getRepository());
3294 } else if (firstElement instanceof Repository) {
3295 Repository repo = (Repository) firstElement;
3296 if (currentRepository != repo) {
3297 reload(repo);
3299 } else {
3300 Repository repo = AdapterUtils.adapt(firstElement,
3301 Repository.class);
3302 if (repo != null) {
3303 if (currentRepository != repo) {
3304 reload(repo);
3306 } else {
3307 IResource resource = AdapterUtils
3308 .adaptToAnyResource(firstElement);
3309 if (resource != null) {
3310 showResource(resource);
3316 private void showResource(final IResource resource) {
3317 if (resource == null || !resource.isAccessible()) {
3318 return;
3320 Job.getJobManager().cancel(JobFamilies.UPDATE_SELECTION);
3321 Job job = new Job(UIText.StagingView_GetRepo) {
3322 @Override
3323 protected IStatus run(IProgressMonitor monitor) {
3324 if (monitor.isCanceled()) {
3325 return Status.CANCEL_STATUS;
3327 RepositoryMapping mapping = RepositoryMapping
3328 .getMapping(resource);
3329 if (mapping != null) {
3330 Repository newRep = mapping.getRepository();
3331 if (newRep != null && newRep != currentRepository) {
3332 if (monitor.isCanceled()) {
3333 return Status.CANCEL_STATUS;
3335 reload(newRep);
3338 return Status.OK_STATUS;
3341 @Override
3342 public boolean belongsTo(Object family) {
3343 return JobFamilies.UPDATE_SELECTION == family;
3346 @Override
3347 public boolean shouldRun() {
3348 return shouldUpdateSelection();
3351 job.setSystem(true);
3352 schedule(job, false);
3355 private void stage(IStructuredSelection selection) {
3356 StagingViewContentProvider contentProvider = getContentProvider(unstagedViewer);
3357 final Repository repository = currentRepository;
3358 Iterator iterator = selection.iterator();
3359 final Set<String> addPaths = new HashSet<>();
3360 final Set<String> rmPaths = new HashSet<>();
3361 resetPathsToExpand();
3362 while (iterator.hasNext()) {
3363 Object element = iterator.next();
3364 if (element instanceof StagingEntry) {
3365 StagingEntry entry = (StagingEntry) element;
3366 selectEntryForStaging(entry, addPaths, rmPaths);
3367 addPathAndParentPaths(entry.getParentPath(), pathsToExpandInStaged);
3368 } else if (element instanceof StagingFolderEntry) {
3369 StagingFolderEntry folder = (StagingFolderEntry) element;
3370 List<StagingEntry> entries = contentProvider
3371 .getStagingEntriesFiltered(folder);
3372 for (StagingEntry entry : entries)
3373 selectEntryForStaging(entry, addPaths, rmPaths);
3374 addExpandedPathsBelowFolder(folder, unstagedViewer,
3375 pathsToExpandInStaged);
3376 } else {
3377 IResource resource = AdapterUtils.adaptToAnyResource(element);
3378 if (resource != null) {
3379 RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
3380 // doesn't do anything if the current repository is a
3381 // submodule of the mapped repo
3382 if (mapping != null && mapping.getRepository() == currentRepository) {
3383 String path = mapping.getRepoRelativePath(resource);
3384 // If resource corresponds to root of working directory
3385 if ("".equals(path)) //$NON-NLS-1$
3386 addPaths.add("."); //$NON-NLS-1$
3387 else
3388 addPaths.add(path);
3394 // start long running operations
3395 if (!addPaths.isEmpty()) {
3396 Job addJob = new Job(UIText.StagingView_AddJob) {
3397 @Override
3398 protected IStatus run(IProgressMonitor monitor) {
3399 try (Git git = new Git(repository)) {
3400 AddCommand add = git.add();
3401 for (String addPath : addPaths)
3402 add.addFilepattern(addPath);
3403 add.call();
3404 } catch (NoFilepatternException e1) {
3405 // cannot happen
3406 } catch (JGitInternalException e1) {
3407 Activator.handleError(e1.getCause().getMessage(),
3408 e1.getCause(), true);
3409 } catch (Exception e1) {
3410 Activator.handleError(e1.getMessage(), e1, true);
3412 return Status.OK_STATUS;
3415 @Override
3416 public boolean belongsTo(Object family) {
3417 return family == JobFamilies.ADD_TO_INDEX;
3421 schedule(addJob, true);
3424 if (!rmPaths.isEmpty()) {
3425 Job removeJob = new Job(UIText.StagingView_RemoveJob) {
3426 @Override
3427 protected IStatus run(IProgressMonitor monitor) {
3428 try (Git git = new Git(repository)) {
3429 RmCommand rm = git.rm().setCached(true);
3430 for (String rmPath : rmPaths)
3431 rm.addFilepattern(rmPath);
3432 rm.call();
3433 } catch (NoFilepatternException e) {
3434 // cannot happen
3435 } catch (JGitInternalException e) {
3436 Activator.handleError(e.getCause().getMessage(),
3437 e.getCause(), true);
3438 } catch (Exception e) {
3439 Activator.handleError(e.getMessage(), e, true);
3441 return Status.OK_STATUS;
3444 @Override
3445 public boolean belongsTo(Object family) {
3446 return family == JobFamilies.REMOVE_FROM_INDEX;
3450 schedule(removeJob, true);
3454 private void selectEntryForStaging(StagingEntry entry,
3455 Collection<String> addPaths, Collection<String> rmPaths) {
3456 switch (entry.getState()) {
3457 case ADDED:
3458 case CHANGED:
3459 case REMOVED:
3460 // already staged
3461 break;
3462 case CONFLICTING:
3463 case MODIFIED:
3464 case MODIFIED_AND_CHANGED:
3465 case MODIFIED_AND_ADDED:
3466 case UNTRACKED:
3467 addPaths.add(entry.getPath());
3468 break;
3469 case MISSING:
3470 case MISSING_AND_CHANGED:
3471 rmPaths.add(entry.getPath());
3472 break;
3476 private void unstage(IStructuredSelection selection) {
3477 if (selection.isEmpty())
3478 return;
3480 Collection<String> paths = processUnstageSelection(selection);
3481 if (paths.isEmpty())
3482 return;
3484 final Repository repository = currentRepository;
3486 Job resetJob = new Job(UIText.StagingView_ResetJob) {
3487 @Override
3488 protected IStatus run(IProgressMonitor monitor) {
3489 try (Git git = new Git(repository)) {
3490 ResetCommand reset = git.reset();
3491 for (String path : paths)
3492 reset.addPath(path);
3493 reset.call();
3494 } catch (GitAPIException e) {
3495 Activator.handleError(e.getMessage(), e, true);
3497 return Status.OK_STATUS;
3500 @Override
3501 public boolean belongsTo(Object family) {
3502 return family == JobFamilies.RESET;
3505 schedule(resetJob, true);
3508 private Collection<String> processUnstageSelection(
3509 IStructuredSelection selection) {
3510 Set<String> paths = new HashSet<>();
3511 resetPathsToExpand();
3512 for (Object element : selection.toList()) {
3513 if (element instanceof StagingEntry) {
3514 StagingEntry entry = (StagingEntry) element;
3515 addUnstagePath(entry, paths);
3516 addPathAndParentPaths(entry.getParentPath(), pathsToExpandInUnstaged);
3517 } else if (element instanceof StagingFolderEntry) {
3518 StagingFolderEntry folder = (StagingFolderEntry) element;
3519 List<StagingEntry> entries = getContentProvider(stagedViewer)
3520 .getStagingEntriesFiltered(folder);
3521 for (StagingEntry entry : entries)
3522 addUnstagePath(entry, paths);
3523 addExpandedPathsBelowFolder(folder, stagedViewer,
3524 pathsToExpandInUnstaged);
3527 return paths;
3530 private void addUnstagePath(StagingEntry entry, Collection<String> paths) {
3531 switch (entry.getState()) {
3532 case ADDED:
3533 case CHANGED:
3534 case REMOVED:
3535 paths.add(entry.getPath());
3536 return;
3537 default:
3538 // unstaged
3542 private void assumeUnchanged(@NonNull IStructuredSelection selection) {
3543 List<IPath> locations = new ArrayList<>();
3544 collectPaths(selection.toList(), locations);
3546 if (locations.isEmpty()) {
3547 return;
3550 JobUtil.scheduleUserJob(
3551 new AssumeUnchangedOperation(currentRepository, locations, true),
3552 UIText.AssumeUnchanged_assumeUnchanged,
3553 JobFamilies.ASSUME_NOASSUME_UNCHANGED);
3556 private void untrack(@NonNull IStructuredSelection selection) {
3557 List<IPath> locations = new ArrayList<>();
3558 collectPaths(selection.toList(), locations);
3560 if (locations.isEmpty()) {
3561 return;
3564 JobUtil.scheduleUserJob(
3565 new UntrackOperation(currentRepository, locations),
3566 UIText.Untrack_untrack, JobFamilies.UNTRACK);
3569 private void collectPaths(Object o, List<IPath> result) {
3570 if (o instanceof Iterable<?>) {
3571 ((Iterable<?>) o).forEach(child -> collectPaths(child, result));
3572 } else if (o instanceof StagingFolderEntry) {
3573 StagingViewContentProvider contentProvider = getContentProvider(unstagedViewer);
3574 List<StagingEntry> entries = contentProvider
3575 .getStagingEntriesFiltered((StagingFolderEntry) o);
3576 collectPaths(entries, result);
3577 } else if (o instanceof StagingEntry) {
3578 result.add(AdapterUtils.adapt(o, IPath.class));
3582 private void resetPathsToExpand() {
3583 pathsToExpandInStaged = new HashSet<>();
3584 pathsToExpandInUnstaged = new HashSet<>();
3587 private static void addExpandedPathsBelowFolder(StagingFolderEntry folder,
3588 TreeViewer treeViewer, Set<IPath> addToSet) {
3589 if (treeViewer.getExpandedState(folder)) {
3590 addPathAndParentPaths(folder.getPath(), addToSet);
3592 addExpandedSubfolders(folder, treeViewer, addToSet);
3595 private static void addExpandedSubfolders(StagingFolderEntry folder,
3596 TreeViewer treeViewer, Set<IPath> addToSet) {
3597 for (Object child : folder.getChildren()) {
3598 if (child instanceof StagingFolderEntry
3599 && treeViewer.getExpandedState(child)) {
3600 addToSet.add(((StagingFolderEntry) child).getPath());
3601 addExpandedSubfolders((StagingFolderEntry) child, treeViewer,
3602 addToSet);
3607 private static void addPathAndParentPaths(IPath initialPath, Set<IPath> addToSet) {
3608 for (IPath p = initialPath; p.segmentCount() >= 1; p = p
3609 .removeLastSegments(1))
3610 addToSet.add(p);
3613 private boolean isValidRepo(final Repository repository) {
3614 return repository != null
3615 && !repository.isBare()
3616 && repository.getWorkTree().exists();
3620 * Clear the view's state.
3621 * <p>
3622 * This method must be called from the UI-thread
3624 * @param repository
3626 private void clearRepository(@Nullable Repository repository) {
3627 saveCommitMessageComponentState();
3628 realRepository = repository;
3629 currentRepository = null;
3630 if (isDisposed()) {
3631 return;
3633 StagingViewUpdate update = new StagingViewUpdate(null, null, null);
3634 setStagingViewerInput(unstagedViewer, update, null, null);
3635 setStagingViewerInput(stagedViewer, update, null, null);
3636 enableCommitWidgets(false);
3637 refreshAction.setEnabled(false);
3638 updateSectionText();
3639 if (repository != null && repository.isBare()) {
3640 form.setText(UIText.StagingView_BareRepoSelection);
3641 } else {
3642 form.setText(UIText.StagingView_NoSelectionTitle);
3644 updateIgnoreErrorsButtonVisibility();
3645 updateRebaseButtonVisibility(false);
3646 // Force a selection changed event
3647 unstagedViewer.setSelection(unstagedViewer.getSelection());
3651 * Show rebase buttons only if a rebase operation is in progress
3653 * @param isRebasing
3654 * {@code}true if rebase is in progress
3656 protected void updateRebaseButtonVisibility(final boolean isRebasing) {
3657 asyncExec(() -> {
3658 showControl(rebaseSection, isRebasing);
3659 rebaseSection.getParent().layout(true);
3663 private static void showControl(Control c, final boolean show) {
3664 c.setVisible(show);
3665 GridData g = (GridData) c.getLayoutData();
3666 g.exclude = !show;
3670 * @param isAmending
3671 * if the current commit should be amended
3673 public void setAmending(boolean isAmending) {
3674 if (isDisposed())
3675 return;
3676 if (amendPreviousCommitAction.isChecked() != isAmending) {
3677 amendPreviousCommitAction.setChecked(isAmending);
3678 amendPreviousCommitAction.run();
3683 * @param message
3684 * commit message to set for current repository
3686 public void setCommitMessage(String message) {
3687 commitMessageText.setText(message);
3691 * Reload the staging view asynchronously
3693 * @param repository
3695 public void reload(final Repository repository) {
3696 if (isDisposed()) {
3697 return;
3699 if (repository == null) {
3700 asyncUpdate(() -> clearRepository(null));
3701 return;
3704 if (!isValidRepo(repository)) {
3705 asyncUpdate(() -> clearRepository(repository));
3706 return;
3709 final boolean repositoryChanged = currentRepository != repository;
3710 realRepository = repository;
3711 currentRepository = repository;
3713 asyncUpdate(() -> {
3714 if (isDisposed()) {
3715 return;
3718 final IndexDiffData indexDiff = doReload(repository);
3719 boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
3720 boolean noConflicts = noConflicts(indexDiff);
3722 if (repositoryChanged) {
3723 // Reset paths, they're from the old repository
3724 resetPathsToExpand();
3725 if (refsChangedListener != null)
3726 refsChangedListener.remove();
3727 refsChangedListener = repository.getListenerList()
3728 .addRefsChangedListener(
3729 event -> updateRebaseButtonVisibility(repository
3730 .getRepositoryState().isRebasing()));
3732 final StagingViewUpdate update = new StagingViewUpdate(repository,
3733 indexDiff, null);
3734 Object[] unstagedExpanded = unstagedViewer
3735 .getVisibleExpandedElements();
3736 Object[] stagedExpanded = stagedViewer.getVisibleExpandedElements();
3738 int unstagedElementsCount = updateAutoExpand(unstagedViewer,
3739 getUnstaged(indexDiff));
3740 int stagedElementsCount = updateAutoExpand(stagedViewer,
3741 getStaged(indexDiff));
3742 int elementsCount = unstagedElementsCount + stagedElementsCount;
3744 if (elementsCount > getMaxLimitForListMode()) {
3745 listPresentationAction.setEnabled(false);
3746 if (presentation == Presentation.LIST) {
3747 compactTreePresentationAction.setChecked(true);
3748 switchToCompactModeInternal(true);
3749 } else {
3750 setExpandCollapseActionsVisible(false,
3751 unstagedElementsCount <= getMaxLimitForListMode(),
3752 true);
3753 setExpandCollapseActionsVisible(true,
3754 stagedElementsCount <= getMaxLimitForListMode(),
3755 true);
3757 } else {
3758 listPresentationAction.setEnabled(true);
3759 boolean changed = getPreferenceStore().getBoolean(
3760 UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
3761 if (changed) {
3762 listPresentationAction.setChecked(true);
3763 switchToListMode();
3764 } else if (presentation != Presentation.LIST) {
3765 setExpandCollapseActionsVisible(false, true, true);
3766 setExpandCollapseActionsVisible(true, true, true);
3770 setStagingViewerInput(unstagedViewer, update, unstagedExpanded,
3771 pathsToExpandInUnstaged);
3772 setStagingViewerInput(stagedViewer, update, stagedExpanded,
3773 pathsToExpandInStaged);
3774 resetPathsToExpand();
3775 // Force a selection changed event
3776 unstagedViewer.setSelection(unstagedViewer.getSelection());
3777 refreshAction.setEnabled(true);
3779 updateRebaseButtonVisibility(
3780 repository.getRepositoryState().isRebasing());
3782 updateIgnoreErrorsButtonVisibility();
3784 boolean rebaseContinueEnabled = indexDiffAvailable
3785 && repository.getRepositoryState().isRebasing()
3786 && noConflicts;
3787 rebaseContinueButton.setEnabled(rebaseContinueEnabled);
3789 isUnbornHead = false;
3790 if (repository.getRepositoryState() == RepositoryState.SAFE) {
3791 try {
3792 Ref head = repository.exactRef(Constants.HEAD);
3793 if (head != null && head.isSymbolic()
3794 && head.getObjectId() == null) {
3795 isUnbornHead = true;
3797 currentBranch = repository.getBranch();
3798 } catch (IOException e) {
3799 Activator.logError(e.getLocalizedMessage(), e);
3802 form.setText(GitLabels.getStyledLabelSafe(repository).toString());
3803 updateCommitMessageComponent(repositoryChanged, indexDiffAvailable);
3804 enableCommitWidgets(indexDiffAvailable && noConflicts);
3806 updateCommitButtons();
3807 updateSectionText();
3812 * The max number of changed files we can handle in the "list" presentation
3813 * without freezing Eclipse UI for a too long time.
3815 * @return default is 10000
3817 private int getMaxLimitForListMode() {
3818 return Activator.getDefault().getPreferenceStore()
3819 .getInt(UIPreferences.STAGING_VIEW_MAX_LIMIT_LIST_MODE);
3822 private static int getUnstaged(@Nullable IndexDiffData indexDiff) {
3823 if (indexDiff == null) {
3824 return 0;
3826 int size = indexDiff.getUntracked().size();
3827 size += indexDiff.getMissing().size();
3828 size += indexDiff.getModified().size();
3829 size += indexDiff.getConflicting().size();
3830 return size;
3833 private static int getStaged(@Nullable IndexDiffData indexDiff) {
3834 if (indexDiff == null) {
3835 return 0;
3837 int size = indexDiff.getAdded().size();
3838 size += indexDiff.getChanged().size();
3839 size += indexDiff.getRemoved().size();
3840 return size;
3843 private int updateAutoExpand(TreeViewer viewer, int newSize) {
3844 if (newSize > getMaxLimitForListMode()) {
3845 // auto expand with too many nodes freezes eclipse
3846 disableAutoExpand(viewer);
3848 return newSize;
3851 private void switchToCompactModeInternal(boolean auto) {
3852 setPresentation(Presentation.COMPACT_TREE, auto);
3853 listPresentationAction.setChecked(false);
3854 treePresentationAction.setChecked(false);
3855 if (auto) {
3856 setExpandCollapseActionsVisible(false, false, true);
3857 setExpandCollapseActionsVisible(true, false, true);
3858 } else {
3859 setExpandCollapseActionsVisible(false, isExpandAllowed(false),
3860 true);
3861 setExpandCollapseActionsVisible(true, isExpandAllowed(true), true);
3865 private void switchToListMode() {
3866 setPresentation(Presentation.LIST, false);
3867 treePresentationAction.setChecked(false);
3868 compactTreePresentationAction.setChecked(false);
3869 setExpandCollapseActionsVisible(false, false, false);
3870 setExpandCollapseActionsVisible(true, false, false);
3873 private static boolean noConflicts(IndexDiffData indexDiff) {
3874 return indexDiff == null ? true : indexDiff.getConflicting().isEmpty();
3877 private static boolean indexDiffAvailable(IndexDiffData indexDiff) {
3878 return indexDiff == null ? false : true;
3881 private boolean hasErrorsOrWarnings() {
3882 return getPreferenceStore()
3883 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
3884 ? (getProblemsSeverity() >= Integer
3885 .parseInt(getPreferenceStore()
3886 .getString(UIPreferences.WARN_BEFORE_COMMITTING_LEVEL))
3887 && !ignoreErrors.getSelection()) : false;
3890 private boolean isCommitBlocked() {
3891 return getPreferenceStore()
3892 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
3893 && getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT)
3894 ? (getProblemsSeverity() >= Integer
3895 .parseInt(getPreferenceStore().getString(
3896 UIPreferences.BLOCK_COMMIT_LEVEL))
3897 && !ignoreErrors.getSelection())
3898 : false;
3901 private IndexDiffData doReload(@NonNull final Repository repository) {
3902 IndexDiffCacheEntry entry = org.eclipse.egit.core.Activator.getDefault()
3903 .getIndexDiffCache().getIndexDiffCacheEntry(repository);
3905 if(cacheEntry != null && cacheEntry != entry)
3906 cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
3908 cacheEntry = entry;
3909 cacheEntry.addIndexDiffChangedListener(myIndexDiffListener);
3911 return cacheEntry.getIndexDiff();
3914 private void expandPreviousExpandedAndPaths(Object[] previous,
3915 TreeViewer viewer, Set<IPath> additionalPaths) {
3917 StagingViewContentProvider stagedContentProvider = getContentProvider(
3918 viewer);
3919 int count = stagedContentProvider.getCount();
3920 updateAutoExpand(viewer, count);
3922 // Auto-expand is on, so don't change expanded items
3923 if (viewer.getAutoExpandLevel() == AbstractTreeViewer.ALL_LEVELS) {
3924 return;
3927 // No need to expand anything
3928 if (getPresentation() == Presentation.LIST)
3929 return;
3931 Set<IPath> paths = new HashSet<>(additionalPaths);
3932 // Instead of just expanding the previous elements directly, also expand
3933 // all parent paths. This makes it work in case of "re-folding" of
3934 // compact tree.
3935 for (Object element : previous) {
3936 if (element instanceof StagingFolderEntry) {
3937 addPathAndParentPaths(((StagingFolderEntry) element).getPath(), paths);
3940 // Also consider the currently expanded elements because auto selection
3941 // could have expanded some elements.
3942 for (Object element : viewer.getVisibleExpandedElements()) {
3943 if (element instanceof StagingFolderEntry) {
3944 addPathAndParentPaths(((StagingFolderEntry) element).getPath(),
3945 paths);
3949 List<StagingFolderEntry> expand = new ArrayList<>();
3951 calculateNodesToExpand(paths, stagedContentProvider.getElements(null),
3952 expand);
3953 viewer.setExpandedElements(expand.toArray());
3956 private void calculateNodesToExpand(Set<IPath> paths, Object[] elements,
3957 List<StagingFolderEntry> result) {
3958 if (elements == null)
3959 return;
3961 for (Object element : elements) {
3962 if (element instanceof StagingFolderEntry) {
3963 StagingFolderEntry folder = (StagingFolderEntry) element;
3964 if (paths.contains(folder.getPath())) {
3965 result.add(folder);
3966 // Only recurs if folder matched (i.e. don't try to expand
3967 // children of unexpanded parents)
3968 calculateNodesToExpand(paths, folder.getChildren(), result);
3974 private void clearCommitMessageToggles() {
3975 amendPreviousCommitAction.setChecked(false);
3976 addChangeIdAction.setChecked(false);
3977 signedOffByAction.setChecked(false);
3980 void updateCommitMessageComponent(boolean repositoryChanged, boolean indexDiffAvailable) {
3981 if (repositoryChanged)
3982 if (commitMessageComponent.isAmending()
3983 || userEnteredCommitMessage())
3984 saveCommitMessageComponentState();
3985 else
3986 deleteCommitMessageComponentState();
3987 if (!indexDiffAvailable)
3988 return; // only try to restore the stored repo commit message if
3989 // indexDiff is ready
3991 CommitHelper helper = new CommitHelper(currentRepository);
3992 CommitMessageComponentState oldState = null;
3993 if (repositoryChanged
3994 || commitMessageComponent.getRepository() != currentRepository) {
3995 oldState = loadCommitMessageComponentState();
3996 commitMessageComponent.setRepository(currentRepository);
3997 if (oldState == null)
3998 loadInitialState(helper);
3999 else
4000 loadExistingState(helper, oldState);
4001 } else { // repository did not change
4002 if (!commitMessageComponent.getHeadCommit().equals(
4003 helper.getPreviousCommit())
4004 || !commitMessageComponent.isAmending()) {
4005 if (!commitMessageComponent.isAmending()
4006 && userEnteredCommitMessage())
4007 addHeadChangedWarning(commitMessageComponent
4008 .getCommitMessage());
4009 else
4010 loadInitialState(helper);
4013 amendPreviousCommitAction.setChecked(commitMessageComponent
4014 .isAmending());
4015 amendPreviousCommitAction.setEnabled(helper.amendAllowed());
4016 updateMessage();
4020 * Resets the commit message component state and saves the overwritten
4021 * commit message into message history
4023 public void resetCommitMessageComponent() {
4024 if (currentRepository != null) {
4025 String commitMessage = commitMessageComponent.getCommitMessage();
4026 if (commitMessage.trim().length() > 0) {
4027 CommitMessageHistory.saveCommitHistory(commitMessage);
4029 loadInitialState(new CommitHelper(currentRepository));
4033 private void loadExistingState(CommitHelper helper,
4034 CommitMessageComponentState oldState) {
4035 boolean headCommitChanged = !oldState.getHeadCommit().equals(
4036 getCommitId(helper.getPreviousCommit()));
4037 commitMessageComponent.enableListeners(false);
4038 commitMessageComponent.setAuthor(oldState.getAuthor());
4039 if (headCommitChanged) {
4040 addHeadChangedWarning(oldState.getCommitMessage());
4041 } else {
4042 commitMessageComponent
4043 .setCommitMessage(oldState.getCommitMessage());
4044 commitMessageComponent
4045 .setCaretPosition(oldState.getCaretPosition());
4047 commitMessageComponent.setCommitter(oldState.getCommitter());
4048 commitMessageComponent.setHeadCommit(getCommitId(helper
4049 .getPreviousCommit()));
4050 commitMessageComponent.setCommitAllowed(helper.canCommit());
4051 commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
4052 boolean amendAllowed = helper.amendAllowed();
4053 commitMessageComponent.setAmendAllowed(amendAllowed);
4054 if (!amendAllowed)
4055 commitMessageComponent.setAmending(false);
4056 else if (!headCommitChanged && oldState.getAmend())
4057 commitMessageComponent.setAmending(true);
4058 else
4059 commitMessageComponent.setAmending(false);
4060 commitMessageComponent.updateUIFromState();
4061 commitMessageComponent.updateSignedOffAndChangeIdButton();
4062 commitMessageComponent.enableListeners(true);
4065 private void addHeadChangedWarning(String commitMessage) {
4066 if (!commitMessage.startsWith(UIText.StagingView_headCommitChanged)) {
4067 String message = UIText.StagingView_headCommitChanged
4068 + Text.DELIMITER + Text.DELIMITER + commitMessage;
4069 commitMessageComponent.setCommitMessage(message);
4073 private void loadInitialState(CommitHelper helper) {
4074 commitMessageComponent.enableListeners(false);
4075 commitMessageComponent.resetState();
4076 commitMessageComponent.setAuthor(helper.getAuthor());
4077 commitMessageComponent.setCommitMessage(helper.getCommitMessage());
4078 commitMessageComponent.setCommitter(helper.getCommitter());
4079 commitMessageComponent.setHeadCommit(getCommitId(helper
4080 .getPreviousCommit()));
4081 commitMessageComponent.setCommitAllowed(helper.canCommit());
4082 commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
4083 commitMessageComponent.setAmendAllowed(helper.amendAllowed());
4084 commitMessageComponent.setAmending(false);
4085 // set the defaults for change id and signed off buttons.
4086 commitMessageComponent.setDefaults();
4087 commitMessageComponent.updateUI();
4088 commitMessageComponent.enableListeners(true);
4091 private boolean userEnteredCommitMessage() {
4092 if (commitMessageComponent.getRepository() == null)
4093 return false;
4094 String message = commitMessageComponent.getCommitMessage().replace(
4095 UIText.StagingView_headCommitChanged, ""); //$NON-NLS-1$
4096 if (message == null || message.trim().length() == 0)
4097 return false;
4099 String chIdLine = "Change-Id: I" + ObjectId.zeroId().name(); //$NON-NLS-1$
4100 Repository repo = currentRepository;
4101 if (repo != null && GerritUtil.getCreateChangeId(repo.getConfig())
4102 && commitMessageComponent.getCreateChangeId()) {
4103 if (message.trim().equals(chIdLine))
4104 return false;
4106 // change id was added automatically, but there is more in the
4107 // message; strip the id, and check for the signed-off-by tag
4108 message = message.replace(chIdLine, ""); //$NON-NLS-1$
4111 if (org.eclipse.egit.ui.Activator.getDefault().getPreferenceStore()
4112 .getBoolean(UIPreferences.COMMIT_DIALOG_SIGNED_OFF_BY)
4113 && commitMessageComponent.isSignedOff()
4114 && message.trim().equals(
4115 Constants.SIGNED_OFF_BY_TAG
4116 + commitMessageComponent.getCommitter()))
4117 return false;
4119 return true;
4122 private ObjectId getCommitId(RevCommit commit) {
4123 if (commit == null)
4124 return ObjectId.zeroId();
4125 return commit.getId();
4128 private void saveCommitMessageComponentState() {
4129 final Repository repo = commitMessageComponent.getRepository();
4130 if (repo != null)
4131 CommitMessageComponentStateManager.persistState(repo,
4132 commitMessageComponent.getState());
4135 private void deleteCommitMessageComponentState() {
4136 if (commitMessageComponent.getRepository() != null)
4137 CommitMessageComponentStateManager
4138 .deleteState(commitMessageComponent.getRepository());
4141 private CommitMessageComponentState loadCommitMessageComponentState() {
4142 return CommitMessageComponentStateManager.loadState(currentRepository);
4145 private Collection<String> getStagedFileNames() {
4146 StagingViewContentProvider stagedContentProvider = getContentProvider(stagedViewer);
4147 StagingEntry[] entries = stagedContentProvider.getStagingEntries();
4148 List<String> files = new ArrayList<>();
4149 for (StagingEntry entry : entries)
4150 files.add(entry.getPath());
4151 return files;
4154 private void commit(boolean pushUpstream) {
4155 if (!isCommitWithoutFilesAllowed()) {
4156 MessageDialog md = new MessageDialog(getSite().getShell(),
4157 UIText.StagingView_committingNotPossible, null,
4158 UIText.StagingView_noStagedFiles, MessageDialog.ERROR,
4159 new String[] { IDialogConstants.CLOSE_LABEL }, 0);
4160 md.open();
4161 return;
4163 if (!commitMessageComponent.checkCommitInfo())
4164 return;
4166 if (!UIUtils.saveAllEditors(currentRepository,
4167 UIText.StagingView_cancelCommitAfterSaving))
4168 return;
4170 String commitMessage = commitMessageComponent.getCommitMessage();
4171 CommitOperation commitOperation = null;
4172 try {
4173 commitOperation = new CommitOperation(currentRepository,
4174 commitMessageComponent.getAuthor(),
4175 commitMessageComponent.getCommitter(),
4176 commitMessage);
4177 } catch (CoreException e) {
4178 Activator.handleError(UIText.StagingView_commitFailed, e, true);
4179 return;
4181 if (amendPreviousCommitAction.isChecked())
4182 commitOperation.setAmending(true);
4183 final boolean gerritMode = addChangeIdAction.isChecked();
4184 commitOperation.setComputeChangeId(gerritMode);
4186 PushMode pushMode = null;
4187 if (pushUpstream) {
4188 pushMode = gerritMode ? PushMode.GERRIT : PushMode.UPSTREAM;
4190 Job commitJob = new CommitJob(currentRepository, commitOperation)
4191 .setOpenCommitEditor(openNewCommitsAction.isChecked())
4192 .setPushUpstream(pushMode);
4194 // don't allow to do anything as long as commit is in progress
4195 enableAllWidgets(false);
4196 commitJob.addJobChangeListener(new JobChangeAdapter() {
4198 @Override
4199 public void done(IJobChangeEvent event) {
4200 asyncExec(() -> {
4201 enableAllWidgets(true);
4202 if (event.getResult().isOK()) {
4203 commitMessageText.setText(EMPTY_STRING);
4209 schedule(commitJob, true);
4211 CommitMessageHistory.saveCommitHistory(commitMessage);
4212 clearCommitMessageToggles();
4216 * Schedule given job in context of the current view. The view will indicate
4217 * progress as long as job is running.
4219 * @param job
4220 * non null
4221 * @param useRepositoryRule
4222 * true to use current repository rule for the given job, false
4223 * to not enforce any rule on the job
4225 private void schedule(Job job, boolean useRepositoryRule) {
4226 if (useRepositoryRule)
4227 job.setRule(RuleUtil.getRule(currentRepository));
4228 IWorkbenchSiteProgressService service = CommonUtils.getService(getSite(), IWorkbenchSiteProgressService.class);
4229 if (service != null)
4230 service.schedule(job, 0, true);
4231 else
4232 job.schedule();
4235 private boolean isCommitWithoutFilesAllowed() {
4236 if (stagedViewer.getTree().getItemCount() > 0)
4237 return true;
4239 if (amendPreviousCommitAction.isChecked())
4240 return true;
4242 return CommitHelper.isCommitWithoutFilesAllowed(currentRepository);
4245 @Override
4246 public void setFocus() {
4247 Tree tree = unstagedViewer.getTree();
4248 if (tree.getItemCount() > 0 && !isAutoStageOnCommitEnabled()) {
4249 unstagedViewer.getControl().setFocus();
4250 return;
4252 commitMessageText.setFocus();
4255 private boolean isAutoStageOnCommitEnabled() {
4256 IPreferenceStore uiPreferences = Activator.getDefault()
4257 .getPreferenceStore();
4258 return uiPreferences.getBoolean(UIPreferences.AUTO_STAGE_ON_COMMIT);
4261 @Override
4262 public void dispose() {
4263 super.dispose();
4265 ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class);
4266 srv.removePostSelectionListener(selectionChangedListener);
4267 CommonUtils.getService(getSite(), IPartService.class)
4268 .removePartListener(partListener);
4270 if (cacheEntry != null) {
4271 cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
4274 if (undoRedoActionGroup != null) {
4275 undoRedoActionGroup.dispose();
4278 InstanceScope.INSTANCE.getNode(
4279 org.eclipse.egit.core.Activator.getPluginId())
4280 .removePreferenceChangeListener(prefListener);
4281 if (refsChangedListener != null) {
4282 refsChangedListener.remove();
4285 if (switchRepositoriesAction != null) {
4286 switchRepositoriesAction.dispose();
4287 switchRepositoriesAction = null;
4290 getPreferenceStore().removePropertyChangeListener(uiPrefsListener);
4292 getDialogSettings().put(STORE_SORT_STATE, sortAction.isChecked());
4294 currentRepository = null;
4295 lastSelection = null;
4296 disposed = true;
4299 private boolean isDisposed() {
4300 return disposed;
4303 private static void syncExec(Runnable runnable) {
4304 PlatformUI.getWorkbench().getDisplay().syncExec(runnable);
4307 private void asyncExec(Runnable runnable) {
4308 if (!isDisposed()) {
4309 PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
4310 if (!isDisposed()) {
4311 runnable.run();
4317 private void asyncUpdate(Runnable runnable) {
4318 if (isDisposed()) {
4319 return;
4321 Job update = new WorkbenchJob(UIText.StagingView_LoadJob) {
4323 @Override
4324 public IStatus runInUIThread(IProgressMonitor monitor) {
4325 try {
4326 runnable.run();
4327 return Status.OK_STATUS;
4328 } catch (Exception e) {
4329 return Activator.createErrorStatus(e.getLocalizedMessage(),
4334 @Override
4335 public boolean shouldSchedule() {
4336 return super.shouldSchedule() && !isDisposed();
4339 @Override
4340 public boolean shouldRun() {
4341 return super.shouldRun() && !isDisposed();
4344 @Override
4345 public boolean belongsTo(Object family) {
4346 return family == JobFamilies.STAGING_VIEW_RELOAD
4347 || super.belongsTo(family);
4350 update.setSystem(true);
4351 update.schedule();
4355 * This comparator sorts the {@link StagingEntry}s alphabetically or groups
4356 * them by state. If grouped by state the entries in the same group are also
4357 * ordered alphabetically.
4359 private static class StagingEntryComparator extends ViewerComparator {
4361 private boolean alphabeticSort;
4363 private Comparator<String> comparator;
4365 private boolean fileNamesFirst;
4367 private StagingEntryComparator(boolean alphabeticSort,
4368 boolean fileNamesFirst) {
4369 this.alphabeticSort = alphabeticSort;
4370 this.setFileNamesFirst(fileNamesFirst);
4371 comparator = CommonUtils.STRING_ASCENDING_COMPARATOR;
4374 public boolean isFileNamesFirst() {
4375 return fileNamesFirst;
4378 public void setFileNamesFirst(boolean fileNamesFirst) {
4379 this.fileNamesFirst = fileNamesFirst;
4382 private void setAlphabeticSort(boolean sort) {
4383 this.alphabeticSort = sort;
4386 private boolean isAlphabeticSort() {
4387 return alphabeticSort;
4390 @Override
4391 public int category(Object element) {
4392 if (!isAlphabeticSort()) {
4393 StagingEntry stagingEntry = getStagingEntry(element);
4394 if (stagingEntry != null) {
4395 return getState(stagingEntry);
4398 return super.category(element);
4401 @Override
4402 public int compare(Viewer viewer, Object e1, Object e2) {
4403 int cat1 = category(e1);
4404 int cat2 = category(e2);
4406 if (cat1 != cat2) {
4407 return cat1 - cat2;
4410 String name1 = getStagingEntryText(e1);
4411 String name2 = getStagingEntryText(e2);
4413 return comparator.compare(name1, name2);
4416 private String getStagingEntryText(Object element) {
4417 String text = ""; //$NON-NLS-1$
4418 StagingEntry stagingEntry = getStagingEntry(element);
4419 if (stagingEntry != null) {
4420 if (isFileNamesFirst()) {
4421 text = stagingEntry.getName();
4422 } else {
4423 text = stagingEntry.getPath();
4426 return text;
4429 @Nullable
4430 private StagingEntry getStagingEntry(Object element) {
4431 StagingEntry entry = null;
4432 if (element instanceof StagingEntry) {
4433 entry = (StagingEntry) element;
4435 if (element instanceof TreeItem) {
4436 TreeItem item = (TreeItem) element;
4437 if (item.getData() instanceof StagingEntry) {
4438 entry = (StagingEntry) item.getData();
4441 return entry;
4444 private int getState(StagingEntry entry) {
4445 switch (entry.getState()) {
4446 case CONFLICTING:
4447 return 1;
4448 case MODIFIED:
4449 return 2;
4450 case MODIFIED_AND_ADDED:
4451 return 3;
4452 case MODIFIED_AND_CHANGED:
4453 return 4;
4454 case ADDED:
4455 return 5;
4456 case CHANGED:
4457 return 6;
4458 case MISSING:
4459 return 7;
4460 case MISSING_AND_CHANGED:
4461 return 8;
4462 case REMOVED:
4463 return 9;
4464 case UNTRACKED:
4465 return 10;
4466 default:
4467 return super.category(entry);