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