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