Support copy/move of workspace if Git repository is under workspace
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / staging / StagingView.java
blob82cc3f04e3cf12664bde6457989712f2b7a9864f
1 /*******************************************************************************
2 * Copyright (C) 2011, 2015 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>
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
11 * Contributors:
12 * Tobias Baumann <tobbaumann@gmail.com> - Bug 373969, 473544
13 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 481683
14 *******************************************************************************/
15 package org.eclipse.egit.ui.internal.staging;
17 import static org.eclipse.egit.ui.internal.CommonUtils.runCommand;
19 import java.io.File;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Comparator;
24 import java.util.EnumSet;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Set;
30 import org.eclipse.core.commands.ExecutionException;
31 import org.eclipse.core.commands.operations.IUndoContext;
32 import org.eclipse.core.resources.IContainer;
33 import org.eclipse.core.resources.IFile;
34 import org.eclipse.core.resources.IMarker;
35 import org.eclipse.core.resources.IResource;
36 import org.eclipse.core.resources.ResourcesPlugin;
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.core.runtime.IPath;
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.IStatus;
41 import org.eclipse.core.runtime.Path;
42 import org.eclipse.core.runtime.Status;
43 import org.eclipse.core.runtime.jobs.IJobChangeEvent;
44 import org.eclipse.core.runtime.jobs.Job;
45 import org.eclipse.core.runtime.jobs.JobChangeAdapter;
46 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
47 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
48 import org.eclipse.core.runtime.preferences.InstanceScope;
49 import org.eclipse.egit.core.AdapterUtils;
50 import org.eclipse.egit.core.RepositoryUtil;
51 import org.eclipse.egit.core.internal.gerrit.GerritUtil;
52 import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry;
53 import org.eclipse.egit.core.internal.indexdiff.IndexDiffChangedListener;
54 import org.eclipse.egit.core.internal.indexdiff.IndexDiffData;
55 import org.eclipse.egit.core.internal.job.RuleUtil;
56 import org.eclipse.egit.core.op.CommitOperation;
57 import org.eclipse.egit.core.project.RepositoryMapping;
58 import org.eclipse.egit.ui.Activator;
59 import org.eclipse.egit.ui.JobFamilies;
60 import org.eclipse.egit.ui.UIPreferences;
61 import org.eclipse.egit.ui.UIUtils;
62 import org.eclipse.egit.ui.internal.CommonUtils;
63 import org.eclipse.egit.ui.internal.EgitUiEditorUtils;
64 import org.eclipse.egit.ui.internal.GitLabels;
65 import org.eclipse.egit.ui.internal.UIIcons;
66 import org.eclipse.egit.ui.internal.UIText;
67 import org.eclipse.egit.ui.internal.actions.ActionCommands;
68 import org.eclipse.egit.ui.internal.actions.BooleanPrefAction;
69 import org.eclipse.egit.ui.internal.actions.ReplaceWithOursTheirsMenu;
70 import org.eclipse.egit.ui.internal.commands.shared.AbortRebaseCommand;
71 import org.eclipse.egit.ui.internal.commands.shared.AbstractRebaseCommandHandler;
72 import org.eclipse.egit.ui.internal.commands.shared.ContinueRebaseCommand;
73 import org.eclipse.egit.ui.internal.commands.shared.SkipRebaseCommand;
74 import org.eclipse.egit.ui.internal.commit.CommitHelper;
75 import org.eclipse.egit.ui.internal.commit.CommitJob;
76 import org.eclipse.egit.ui.internal.commit.CommitMessageHistory;
77 import org.eclipse.egit.ui.internal.commit.CommitProposalProcessor;
78 import org.eclipse.egit.ui.internal.commit.CommitJob.PushMode;
79 import org.eclipse.egit.ui.internal.components.ToggleableWarningLabel;
80 import org.eclipse.egit.ui.internal.decorators.IProblemDecoratable;
81 import org.eclipse.egit.ui.internal.decorators.ProblemLabelDecorator;
82 import org.eclipse.egit.ui.internal.dialogs.CommitMessageArea;
83 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponent;
84 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentState;
85 import org.eclipse.egit.ui.internal.dialogs.CommitMessageComponentStateManager;
86 import org.eclipse.egit.ui.internal.dialogs.ICommitMessageComponentNotifications;
87 import org.eclipse.egit.ui.internal.dialogs.SpellcheckableMessageArea;
88 import org.eclipse.egit.ui.internal.operations.DeletePathsOperationUI;
89 import org.eclipse.egit.ui.internal.operations.IgnoreOperationUI;
90 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
91 import org.eclipse.jface.action.Action;
92 import org.eclipse.jface.action.ControlContribution;
93 import org.eclipse.jface.action.IAction;
94 import org.eclipse.jface.action.IContributionItem;
95 import org.eclipse.jface.action.IMenuListener;
96 import org.eclipse.jface.action.IMenuManager;
97 import org.eclipse.jface.action.IToolBarManager;
98 import org.eclipse.jface.action.MenuManager;
99 import org.eclipse.jface.action.Separator;
100 import org.eclipse.jface.action.ToolBarManager;
101 import org.eclipse.jface.dialogs.DialogSettings;
102 import org.eclipse.jface.dialogs.IDialogSettings;
103 import org.eclipse.jface.dialogs.MessageDialog;
104 import org.eclipse.jface.layout.GridDataFactory;
105 import org.eclipse.jface.layout.GridLayoutFactory;
106 import org.eclipse.jface.preference.IPersistentPreferenceStore;
107 import org.eclipse.jface.preference.IPreferenceStore;
108 import org.eclipse.jface.resource.ImageDescriptor;
109 import org.eclipse.jface.resource.JFaceResources;
110 import org.eclipse.jface.resource.LocalResourceManager;
111 import org.eclipse.jface.util.IPropertyChangeListener;
112 import org.eclipse.jface.util.LocalSelectionTransfer;
113 import org.eclipse.jface.util.PropertyChangeEvent;
114 import org.eclipse.jface.viewers.AbstractTreeViewer;
115 import org.eclipse.jface.viewers.ContentViewer;
116 import org.eclipse.jface.viewers.DecoratingLabelProvider;
117 import org.eclipse.jface.viewers.IBaseLabelProvider;
118 import org.eclipse.jface.viewers.ILabelDecorator;
119 import org.eclipse.jface.viewers.ILabelProvider;
120 import org.eclipse.jface.viewers.IOpenListener;
121 import org.eclipse.jface.viewers.ISelection;
122 import org.eclipse.jface.viewers.ISelectionProvider;
123 import org.eclipse.jface.viewers.IStructuredSelection;
124 import org.eclipse.jface.viewers.ITreeViewerListener;
125 import org.eclipse.jface.viewers.OpenEvent;
126 import org.eclipse.jface.viewers.StructuredSelection;
127 import org.eclipse.jface.viewers.TreeExpansionEvent;
128 import org.eclipse.jface.viewers.TreeViewer;
129 import org.eclipse.jface.viewers.Viewer;
130 import org.eclipse.jface.viewers.ViewerComparator;
131 import org.eclipse.jface.viewers.ViewerFilter;
132 import org.eclipse.jgit.api.AddCommand;
133 import org.eclipse.jgit.api.CheckoutCommand;
134 import org.eclipse.jgit.api.Git;
135 import org.eclipse.jgit.api.ResetCommand;
136 import org.eclipse.jgit.api.RmCommand;
137 import org.eclipse.jgit.api.errors.GitAPIException;
138 import org.eclipse.jgit.api.errors.JGitInternalException;
139 import org.eclipse.jgit.api.errors.NoFilepatternException;
140 import org.eclipse.jgit.annotations.NonNull;
141 import org.eclipse.jgit.annotations.Nullable;
142 import org.eclipse.jgit.events.ListenerHandle;
143 import org.eclipse.jgit.events.RefsChangedEvent;
144 import org.eclipse.jgit.events.RefsChangedListener;
145 import org.eclipse.jgit.lib.Constants;
146 import org.eclipse.jgit.lib.ObjectId;
147 import org.eclipse.jgit.lib.Repository;
148 import org.eclipse.jgit.lib.RepositoryState;
149 import org.eclipse.jgit.revwalk.RevCommit;
150 import org.eclipse.osgi.util.NLS;
151 import org.eclipse.swt.SWT;
152 import org.eclipse.swt.custom.SashForm;
153 import org.eclipse.swt.custom.VerifyKeyListener;
154 import org.eclipse.swt.dnd.DND;
155 import org.eclipse.swt.dnd.DragSourceAdapter;
156 import org.eclipse.swt.dnd.DragSourceEvent;
157 import org.eclipse.swt.dnd.DropTargetAdapter;
158 import org.eclipse.swt.dnd.DropTargetEvent;
159 import org.eclipse.swt.dnd.FileTransfer;
160 import org.eclipse.swt.dnd.Transfer;
161 import org.eclipse.swt.events.DisposeEvent;
162 import org.eclipse.swt.events.DisposeListener;
163 import org.eclipse.swt.events.FocusEvent;
164 import org.eclipse.swt.events.FocusListener;
165 import org.eclipse.swt.events.ModifyEvent;
166 import org.eclipse.swt.events.ModifyListener;
167 import org.eclipse.swt.events.SelectionAdapter;
168 import org.eclipse.swt.events.SelectionEvent;
169 import org.eclipse.swt.events.VerifyEvent;
170 import org.eclipse.swt.graphics.Image;
171 import org.eclipse.swt.layout.GridData;
172 import org.eclipse.swt.layout.GridLayout;
173 import org.eclipse.swt.layout.RowLayout;
174 import org.eclipse.swt.widgets.Button;
175 import org.eclipse.swt.widgets.Composite;
176 import org.eclipse.swt.widgets.Control;
177 import org.eclipse.swt.widgets.Display;
178 import org.eclipse.swt.widgets.Label;
179 import org.eclipse.swt.widgets.Text;
180 import org.eclipse.swt.widgets.Tree;
181 import org.eclipse.swt.widgets.TreeItem;
182 import org.eclipse.ui.IActionBars;
183 import org.eclipse.ui.IEditorInput;
184 import org.eclipse.ui.IEditorPart;
185 import org.eclipse.ui.IFileEditorInput;
186 import org.eclipse.ui.IMemento;
187 import org.eclipse.ui.IPartListener2;
188 import org.eclipse.ui.IPartService;
189 import org.eclipse.ui.ISelectionListener;
190 import org.eclipse.ui.ISelectionService;
191 import org.eclipse.ui.IURIEditorInput;
192 import org.eclipse.ui.IViewSite;
193 import org.eclipse.ui.IWorkbenchPage;
194 import org.eclipse.ui.IWorkbenchPart;
195 import org.eclipse.ui.IWorkbenchPartReference;
196 import org.eclipse.ui.IWorkbenchPartSite;
197 import org.eclipse.ui.IWorkbenchWindow;
198 import org.eclipse.ui.PartInitException;
199 import org.eclipse.ui.PlatformUI;
200 import org.eclipse.ui.actions.ActionFactory;
201 import org.eclipse.ui.forms.IFormColors;
202 import org.eclipse.ui.forms.widgets.ExpandableComposite;
203 import org.eclipse.ui.forms.widgets.Form;
204 import org.eclipse.ui.forms.widgets.FormToolkit;
205 import org.eclipse.ui.forms.widgets.Section;
206 import org.eclipse.ui.handlers.IHandlerService;
207 import org.eclipse.ui.operations.UndoRedoActionGroup;
208 import org.eclipse.ui.part.IShowInSource;
209 import org.eclipse.ui.part.ShowInContext;
210 import org.eclipse.ui.part.ViewPart;
211 import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
214 * A GitX style staging view with embedded commit dialog.
216 public class StagingView extends ViewPart implements IShowInSource {
219 * Staging view id
221 public static final String VIEW_ID = "org.eclipse.egit.ui.StagingView"; //$NON-NLS-1$
223 private static final String EMPTY_STRING = ""; //$NON-NLS-1$
225 private static final String SORT_ITEM_TOOLBAR_ID = "sortItem"; //$NON-NLS-1$
227 private static final String STORE_SORT_STATE = SORT_ITEM_TOOLBAR_ID
228 + "State"; //$NON-NLS-1$
230 private static final String HORIZONTAL_SASH_FORM_WEIGHT = "HORIZONTAL_SASH_FORM_WEIGHT"; //$NON-NLS-1$
232 private static final String STAGING_SASH_FORM_WEIGHT = "STAGING_SASH_FORM_WEIGHT"; //$NON-NLS-1$
234 private ISelection initialSelection;
236 private FormToolkit toolkit;
238 private Form form;
240 private SashForm horizontalSashForm;
242 private Section stagedSection;
244 private Section unstagedSection;
246 private Section commitMessageSection;
248 private TreeViewer stagedViewer;
250 private TreeViewer unstagedViewer;
252 private ToggleableWarningLabel warningLabel;
254 private Text filterText;
256 private SpellcheckableMessageArea commitMessageText;
258 private Text committerText;
260 private Text authorText;
262 private CommitMessageComponent commitMessageComponent;
264 private boolean reactOnSelection = true;
266 private boolean isViewHidden;
268 private ISelectionListener selectionChangedListener;
270 private IPartListener2 partListener;
272 private ToolBarManager unstagedToolBarManager;
274 private ToolBarManager stagedToolBarManager;
276 private Action listPresentationAction;
278 private Action treePresentationAction;
280 private Action compactTreePresentationAction;
282 private Action unstagedExpandAllAction;
284 private Action unstagedCollapseAllAction;
286 private Action stagedExpandAllAction;
288 private Action stagedCollapseAllAction;
290 private Action compareModeAction;
292 @Nullable
293 private Repository currentRepository;
295 private Presentation presentation = Presentation.LIST;
297 private Set<IPath> pathsToExpandInStaged = new HashSet<IPath>();
299 private Set<IPath> pathsToExpandInUnstaged = new HashSet<IPath>();
302 * Presentation mode of the staged/unstaged files.
304 public enum Presentation {
305 /** Show files in flat list */
306 LIST,
307 /** Show folder structure in full tree */
308 TREE,
310 * Show folder structure in compact tree (folders with only one child
311 * are folded into parent)
313 COMPACT_TREE;
316 static class StagingViewUpdate {
317 Repository repository;
318 IndexDiffData indexDiff;
319 Collection<String> changedResources;
321 StagingViewUpdate(Repository theRepository,
322 IndexDiffData theIndexDiff, Collection<String> theChanges) {
323 this.repository = theRepository;
324 this.indexDiff = theIndexDiff;
325 this.changedResources = theChanges;
329 static class StagingDragListener extends DragSourceAdapter {
331 private ISelectionProvider provider;
333 public StagingDragListener(ISelectionProvider provider) {
334 this.provider = provider;
337 @Override
338 public void dragStart(DragSourceEvent event) {
339 event.doit = !provider.getSelection().isEmpty();
342 @Override
343 public void dragFinished(DragSourceEvent event) {
344 if (LocalSelectionTransfer.getTransfer().isSupportedType(
345 event.dataType))
346 LocalSelectionTransfer.getTransfer().setSelection(null);
349 @Override
350 public void dragSetData(DragSourceEvent event) {
351 IStructuredSelection selection = (IStructuredSelection) provider
352 .getSelection();
353 if (selection.isEmpty())
354 return;
356 if (LocalSelectionTransfer.getTransfer().isSupportedType(
357 event.dataType)) {
358 LocalSelectionTransfer.getTransfer().setSelection(selection);
359 return;
362 if (FileTransfer.getInstance().isSupportedType(event.dataType)) {
363 List<String> files = new ArrayList<String>();
364 for (Object selected : selection.toList())
365 if (selected instanceof StagingEntry) {
366 StagingEntry entry = (StagingEntry) selected;
367 File file = new File(
368 entry.getRepository().getWorkTree(),
369 entry.getPath());
370 if (file.exists())
371 files.add(file.getAbsolutePath());
373 if (!files.isEmpty()) {
374 event.data = files.toArray(new String[files.size()]);
375 return;
381 private final class PartListener implements IPartListener2 {
382 StructuredSelection lastSelection;
384 @Override
385 public void partVisible(IWorkbenchPartReference partRef) {
386 updateHiddenState(partRef, false);
389 @Override
390 public void partOpened(IWorkbenchPartReference partRef) {
391 updateHiddenState(partRef, false);
394 @Override
395 public void partHidden(IWorkbenchPartReference partRef) {
396 updateHiddenState(partRef, true);
399 @Override
400 public void partClosed(IWorkbenchPartReference partRef) {
401 updateHiddenState(partRef, true);
404 @Override
405 public void partActivated(IWorkbenchPartReference partRef) {
406 if (isMe(partRef)) {
407 if (lastSelection != null) {
408 // view activated: synchronize with last active part
409 // selection
410 reactOnSelection(lastSelection);
411 lastSelection = null;
413 return;
415 IWorkbenchPart part = partRef.getPart(false);
416 StructuredSelection sel = getSelectionOfPart(part);
417 if (isViewHidden) {
418 // remember last selection in the part so that we can
419 // synchronize on it as soon as we will be visible
420 lastSelection = sel;
421 } else {
422 lastSelection = null;
423 if (sel != null) {
424 reactOnSelection(sel);
430 private void updateHiddenState(IWorkbenchPartReference partRef,
431 boolean hidden) {
432 if (isMe(partRef)) {
433 isViewHidden = hidden;
437 private boolean isMe(IWorkbenchPartReference partRef) {
438 return partRef.getPart(false) == StagingView.this;
441 @Override
442 public void partDeactivated(IWorkbenchPartReference partRef) {
446 @Override
447 public void partBroughtToTop(IWorkbenchPartReference partRef) {
451 @Override
452 public void partInputChanged(IWorkbenchPartReference partRef) {
457 static class TreeDecoratingLabelProvider extends DecoratingLabelProvider {
459 ILabelProvider provider;
461 ILabelDecorator decorator;
463 public TreeDecoratingLabelProvider(ILabelProvider provider,
464 ILabelDecorator decorator) {
465 super(provider, decorator);
466 this.provider = provider;
467 this.decorator = decorator;
470 public Image getColumnImage(Object element) {
471 Image image = provider.getImage(element);
472 if (image != null && decorator != null) {
473 Image decorated = decorator.decorateImage(image, element);
474 if (decorated != null)
475 return decorated;
477 return image;
480 @Override
481 public String getText(Object element) {
482 return provider.getText(element);
486 static class StagingViewSearchThread extends Thread {
487 private StagingView stagingView;
489 private static final Object lock = new Object();
491 private volatile static int globalThreadIndex = 0;
493 private int currentThreadIx;
495 public StagingViewSearchThread(StagingView stagingView) {
496 super("staging_view_filter_thread" + ++globalThreadIndex); //$NON-NLS-1$
497 this.stagingView = stagingView;
498 currentThreadIx = globalThreadIndex;
501 @Override
502 public void run() {
503 synchronized (lock) {
504 if (currentThreadIx < globalThreadIndex)
505 return;
506 stagingView.refreshViewersPreservingExpandedElements();
512 private final IPreferenceChangeListener prefListener = new IPreferenceChangeListener() {
514 @Override
515 public void preferenceChange(PreferenceChangeEvent event) {
516 if (!RepositoryUtil.PREFS_DIRECTORIES_REL.equals(event.getKey())) {
517 return;
520 final Repository repo = currentRepository;
521 if (repo == null)
522 return;
524 if (Activator.getDefault().getRepositoryUtil().contains(repo))
525 return;
527 reload(null);
532 private final IPropertyChangeListener uiPrefsListener = new IPropertyChangeListener() {
533 @Override
534 public void propertyChange(PropertyChangeEvent event) {
535 if (UIPreferences.COMMIT_DIALOG_WARN_ABOUT_MESSAGE_SECOND_LINE
536 .equals(event.getProperty())) {
537 asyncExec(new Runnable() {
538 @Override
539 public void run() {
540 if (!commitMessageSection.isDisposed()) {
541 updateMessage();
549 private Action signedOffByAction;
551 private Action addChangeIdAction;
553 private Action amendPreviousCommitAction;
555 private Action openNewCommitsAction;
557 private Action columnLayoutAction;
559 private Action fileNameModeAction;
561 private Action refreshAction;
563 private Action sortAction;
565 private SashForm stagingSashForm;
567 private IndexDiffChangedListener myIndexDiffListener = new IndexDiffChangedListener() {
568 @Override
569 public void indexDiffChanged(Repository repository,
570 IndexDiffData indexDiffData) {
571 reload(repository);
575 private IndexDiffCacheEntry cacheEntry;
577 private UndoRedoActionGroup undoRedoActionGroup;
579 private Button commitButton;
581 private Button commitAndPushButton;
583 private Section rebaseSection;
585 private Button rebaseContinueButton;
587 private Button rebaseSkipButton;
589 private Button rebaseAbortButton;
591 private Button ignoreErrors;
593 private ListenerHandle refsChangedListener;
595 private LocalResourceManager resources = new LocalResourceManager(
596 JFaceResources.getResources());
598 private boolean disposed;
600 private Image getImage(ImageDescriptor descriptor) {
601 return (Image) this.resources.get(descriptor);
604 @Override
605 public void init(IViewSite site, IMemento viewMemento)
606 throws PartInitException {
607 super.init(site, viewMemento);
608 this.initialSelection = site.getWorkbenchWindow().getSelectionService()
609 .getSelection();
612 @Override
613 public void createPartControl(Composite parent) {
614 GridLayoutFactory.fillDefaults().applyTo(parent);
616 toolkit = new FormToolkit(parent.getDisplay());
617 parent.addDisposeListener(new DisposeListener() {
619 @Override
620 public void widgetDisposed(DisposeEvent e) {
621 if (commitMessageComponent.isAmending()
622 || userEnteredCommitMessage())
623 saveCommitMessageComponentState();
624 else
625 deleteCommitMessageComponentState();
626 resources.dispose();
627 toolkit.dispose();
631 form = toolkit.createForm(parent);
633 form.setImage(getImage(UIIcons.REPOSITORY));
634 form.setText(UIText.StagingView_NoSelectionTitle);
635 GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
636 toolkit.decorateFormHeading(form);
637 GridLayoutFactory.swtDefaults().applyTo(form.getBody());
639 horizontalSashForm = new SashForm(form.getBody(), SWT.NONE);
640 saveSashFormWeightsOnDisposal(horizontalSashForm,
641 HORIZONTAL_SASH_FORM_WEIGHT);
642 toolkit.adapt(horizontalSashForm, true, true);
643 GridDataFactory.fillDefaults().grab(true, true)
644 .applyTo(horizontalSashForm);
646 stagingSashForm = new SashForm(horizontalSashForm,
647 getStagingFormOrientation());
648 saveSashFormWeightsOnDisposal(stagingSashForm,
649 STAGING_SASH_FORM_WEIGHT);
650 toolkit.adapt(stagingSashForm, true, true);
651 GridDataFactory.fillDefaults().grab(true, true)
652 .applyTo(stagingSashForm);
654 unstagedSection = toolkit.createSection(stagingSashForm,
655 ExpandableComposite.TITLE_BAR);
657 unstagedSection.setLayoutData(
658 GridDataFactory.fillDefaults().grab(true, true).create());
660 createUnstagedToolBarComposite();
662 Composite unstagedComposite = toolkit.createComposite(unstagedSection);
663 toolkit.paintBordersFor(unstagedComposite);
664 unstagedSection.setClient(unstagedComposite);
665 GridLayoutFactory.fillDefaults().extendedMargins(2, 2, 2, 2)
666 .applyTo(unstagedComposite);
668 unstagedViewer = createTree(unstagedComposite);
669 GridDataFactory.fillDefaults().grab(true, true)
670 .applyTo(unstagedViewer.getControl());
671 unstagedViewer.getTree().setData(FormToolkit.KEY_DRAW_BORDER,
672 FormToolkit.TREE_BORDER);
673 unstagedViewer.setLabelProvider(createLabelProvider(unstagedViewer));
674 unstagedViewer.setContentProvider(createStagingContentProvider(true));
675 unstagedViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY
676 | DND.DROP_LINK,
677 new Transfer[] { LocalSelectionTransfer.getTransfer(),
678 FileTransfer.getInstance() }, new StagingDragListener(
679 unstagedViewer));
680 unstagedViewer.addDropSupport(DND.DROP_MOVE,
681 new Transfer[] { LocalSelectionTransfer.getTransfer() },
682 new DropTargetAdapter() {
683 @Override
684 public void drop(DropTargetEvent event) {
685 // Bug 411466: It is very important that detail is set
686 // to DND.DROP_COPY. If it was left as DND.DROP_MOVE and
687 // the drag comes from the Navigator view, the code in
688 // NavigatorDragAdapter would delete the resources.
689 event.detail = DND.DROP_COPY;
690 if (event.data instanceof IStructuredSelection) {
691 final IStructuredSelection selection = (IStructuredSelection) event.data;
692 unstage(selection);
696 @Override
697 public void dragOver(DropTargetEvent event) {
698 event.detail = DND.DROP_MOVE;
701 unstagedViewer.addOpenListener(new IOpenListener() {
702 @Override
703 public void open(OpenEvent event) {
704 compareWith(event);
707 unstagedViewer.setComparator(
708 new StagingEntryComparator(getSortCheckState(), getPreferenceStore()
709 .getBoolean(UIPreferences.STAGING_VIEW_FILENAME_MODE)));
710 enableAutoExpand(unstagedViewer);
711 addListenerToDisableAutoExpandOnCollapse(unstagedViewer);
713 Composite rebaseAndCommitComposite = toolkit.createComposite(horizontalSashForm);
714 rebaseAndCommitComposite.setLayout(GridLayoutFactory.fillDefaults().create());
716 rebaseSection = toolkit.createSection(rebaseAndCommitComposite,
717 ExpandableComposite.TITLE_BAR);
718 rebaseSection.setText(UIText.StagingView_RebaseLabel);
720 Composite rebaseComposite = toolkit.createComposite(rebaseSection);
721 toolkit.paintBordersFor(rebaseComposite);
722 rebaseSection.setClient(rebaseComposite);
724 rebaseSection.setLayoutData(GridDataFactory.fillDefaults().create());
725 rebaseComposite.setLayout(GridLayoutFactory.fillDefaults()
726 .numColumns(3).equalWidth(true).create());
727 GridDataFactory buttonGridData = GridDataFactory.fillDefaults().align(
728 SWT.FILL, SWT.CENTER);
730 this.rebaseAbortButton = toolkit.createButton(rebaseComposite,
731 UIText.StagingView_RebaseAbort, SWT.PUSH);
732 rebaseAbortButton.addSelectionListener(new SelectionAdapter() {
733 @Override
734 public void widgetSelected(SelectionEvent e) {
735 rebaseAbort();
738 rebaseAbortButton.setImage(getImage(UIIcons.REBASE_ABORT));
739 buttonGridData.applyTo(rebaseAbortButton);
741 this.rebaseSkipButton = toolkit.createButton(rebaseComposite,
742 UIText.StagingView_RebaseSkip, SWT.PUSH);
743 rebaseSkipButton.addSelectionListener(new SelectionAdapter() {
744 @Override
745 public void widgetSelected(SelectionEvent e) {
746 rebaseSkip();
749 rebaseSkipButton.setImage(getImage(UIIcons.REBASE_SKIP));
750 buttonGridData.applyTo(rebaseSkipButton);
752 this.rebaseContinueButton = toolkit.createButton(rebaseComposite,
753 UIText.StagingView_RebaseContinue, SWT.PUSH);
754 rebaseContinueButton.addSelectionListener(new SelectionAdapter() {
755 @Override
756 public void widgetSelected(SelectionEvent e) {
757 rebaseContinue();
760 rebaseContinueButton.setImage(getImage(UIIcons.REBASE_CONTINUE));
761 buttonGridData.applyTo(rebaseContinueButton);
763 showControl(rebaseSection, false);
765 commitMessageSection = toolkit.createSection(rebaseAndCommitComposite,
766 ExpandableComposite.TITLE_BAR);
767 commitMessageSection.setText(UIText.StagingView_CommitMessage);
768 commitMessageSection.setLayoutData(GridDataFactory.fillDefaults()
769 .grab(true, true).create());
771 Composite commitMessageToolbarComposite = toolkit
772 .createComposite(commitMessageSection);
773 commitMessageToolbarComposite.setBackground(null);
774 commitMessageToolbarComposite.setLayout(createRowLayoutWithoutMargin());
775 commitMessageSection.setTextClient(commitMessageToolbarComposite);
776 ToolBarManager commitMessageToolBarManager = new ToolBarManager(
777 SWT.FLAT | SWT.HORIZONTAL);
779 amendPreviousCommitAction = new Action(
780 UIText.StagingView_Ammend_Previous_Commit, IAction.AS_CHECK_BOX) {
782 @Override
783 public void run() {
784 commitMessageComponent.setAmendingButtonSelection(isChecked());
785 updateMessage();
788 amendPreviousCommitAction.setImageDescriptor(UIIcons.AMEND_COMMIT);
789 commitMessageToolBarManager.add(amendPreviousCommitAction);
791 signedOffByAction = new Action(UIText.StagingView_Add_Signed_Off_By,
792 IAction.AS_CHECK_BOX) {
794 @Override
795 public void run() {
796 commitMessageComponent.setSignedOffButtonSelection(isChecked());
799 signedOffByAction.setImageDescriptor(UIIcons.SIGNED_OFF);
800 commitMessageToolBarManager.add(signedOffByAction);
802 addChangeIdAction = new Action(UIText.StagingView_Add_Change_ID,
803 IAction.AS_CHECK_BOX) {
805 @Override
806 public void run() {
807 commitMessageComponent.setChangeIdButtonSelection(isChecked());
810 addChangeIdAction.setImageDescriptor(UIIcons.GERRIT);
811 commitMessageToolBarManager.add(addChangeIdAction);
813 commitMessageToolBarManager
814 .createControl(commitMessageToolbarComposite);
816 Composite commitMessageComposite = toolkit
817 .createComposite(commitMessageSection);
818 commitMessageSection.setClient(commitMessageComposite);
819 GridLayoutFactory.fillDefaults().numColumns(1)
820 .applyTo(commitMessageComposite);
822 warningLabel = new ToggleableWarningLabel(commitMessageComposite,
823 SWT.NONE);
824 GridDataFactory.fillDefaults().grab(true, false).exclude(true)
825 .applyTo(warningLabel);
827 Composite commitMessageTextComposite = toolkit
828 .createComposite(commitMessageComposite);
829 toolkit.paintBordersFor(commitMessageTextComposite);
830 GridDataFactory.fillDefaults().grab(true, true)
831 .applyTo(commitMessageTextComposite);
832 GridLayoutFactory.fillDefaults().numColumns(1)
833 .extendedMargins(2, 2, 2, 2)
834 .applyTo(commitMessageTextComposite);
836 final CommitProposalProcessor commitProposalProcessor = new CommitProposalProcessor() {
837 @Override
838 protected Collection<String> computeFileNameProposals() {
839 return getStagedFileNames();
842 @Override
843 protected Collection<String> computeMessageProposals() {
844 return CommitMessageHistory.getCommitHistory();
847 commitMessageText = new CommitMessageArea(commitMessageTextComposite,
848 EMPTY_STRING, toolkit.getBorderStyle()) {
849 @Override
850 protected CommitProposalProcessor getCommitProposalProcessor() {
851 return commitProposalProcessor;
853 @Override
854 protected IHandlerService getHandlerService() {
855 return CommonUtils.getService(getSite(), IHandlerService.class);
858 commitMessageText.setData(FormToolkit.KEY_DRAW_BORDER,
859 FormToolkit.TEXT_BORDER);
860 GridDataFactory.fillDefaults().grab(true, true)
861 .applyTo(commitMessageText);
862 UIUtils.addBulbDecorator(commitMessageText.getTextWidget(),
863 UIText.CommitDialog_ContentAssist);
865 Composite composite = toolkit.createComposite(commitMessageComposite);
866 toolkit.paintBordersFor(composite);
867 GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
868 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(composite);
870 toolkit.createLabel(composite, UIText.StagingView_Author)
871 .setForeground(
872 toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
873 authorText = toolkit.createText(composite, null);
874 authorText
875 .setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
876 authorText.setLayoutData(GridDataFactory.fillDefaults()
877 .grab(true, false).create());
879 toolkit.createLabel(composite, UIText.StagingView_Committer)
880 .setForeground(
881 toolkit.getColors().getColor(IFormColors.TB_TOGGLE));
882 committerText = toolkit.createText(composite, null);
883 committerText.setData(FormToolkit.KEY_DRAW_BORDER,
884 FormToolkit.TEXT_BORDER);
885 committerText.setLayoutData(GridDataFactory.fillDefaults()
886 .grab(true, false).create());
888 Composite buttonsContainer = toolkit.createComposite(composite);
889 GridDataFactory.fillDefaults().grab(true, false).span(2, 1)
890 .indent(0, 8).applyTo(buttonsContainer);
891 GridLayoutFactory.fillDefaults().numColumns(2)
892 .applyTo(buttonsContainer);
894 ignoreErrors = toolkit.createButton(buttonsContainer,
895 UIText.StagingView_IgnoreErrors, SWT.CHECK);
896 ignoreErrors.setSelection(false);
897 ignoreErrors.addSelectionListener(new SelectionAdapter() {
898 @Override
899 public void widgetSelected(SelectionEvent e) {
900 updateMessage();
901 updateCommitButtons();
904 getPreferenceStore()
905 .addPropertyChangeListener(new IPropertyChangeListener() {
906 @Override
907 public void propertyChange(PropertyChangeEvent event) {
908 if (isDisposed()) {
909 getPreferenceStore()
910 .removePropertyChangeListener(this);
911 return;
913 asyncExec(new Runnable() {
914 @Override
915 public void run() {
916 updateIgnoreErrorsButtonVisibility();
917 updateMessage();
918 updateCommitButtons();
924 GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING)
925 .grab(true, true).applyTo(ignoreErrors);
926 updateIgnoreErrorsButtonVisibility();
928 Label filler = toolkit.createLabel(buttonsContainer, ""); //$NON-NLS-1$
929 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL)
930 .grab(true, true).applyTo(filler);
932 Composite commitButtonsContainer = toolkit
933 .createComposite(buttonsContainer);
934 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
935 .applyTo(commitButtonsContainer);
936 GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(true)
937 .applyTo(commitButtonsContainer);
940 this.commitAndPushButton = toolkit.createButton(commitButtonsContainer,
941 UIText.StagingView_CommitAndPush, SWT.PUSH);
942 commitAndPushButton.addSelectionListener(new SelectionAdapter() {
943 @Override
944 public void widgetSelected(SelectionEvent e) {
945 commit(true);
948 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
949 .applyTo(commitAndPushButton);
951 this.commitButton = toolkit.createButton(commitButtonsContainer,
952 UIText.StagingView_Commit, SWT.PUSH);
953 commitButton.setImage(getImage(UIIcons.COMMIT));
954 commitButton.setText(UIText.StagingView_Commit);
955 commitButton.addSelectionListener(new SelectionAdapter() {
956 @Override
957 public void widgetSelected(SelectionEvent e) {
958 commit(false);
961 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
962 .applyTo(commitButton);
964 stagedSection = toolkit.createSection(stagingSashForm,
965 ExpandableComposite.TITLE_BAR);
967 createStagedToolBarComposite();
969 Composite stagedComposite = toolkit.createComposite(stagedSection);
970 toolkit.paintBordersFor(stagedComposite);
971 stagedSection.setClient(stagedComposite);
972 GridLayoutFactory.fillDefaults().extendedMargins(2, 2, 2, 2)
973 .applyTo(stagedComposite);
975 stagedViewer = createTree(stagedComposite);
976 GridDataFactory.fillDefaults().grab(true, true)
977 .applyTo(stagedViewer.getControl());
978 stagedViewer.getTree().setData(FormToolkit.KEY_DRAW_BORDER,
979 FormToolkit.TREE_BORDER);
980 stagedViewer.setLabelProvider(createLabelProvider(stagedViewer));
981 stagedViewer.setContentProvider(createStagingContentProvider(false));
982 stagedViewer.addDragSupport(
983 DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK,
984 new Transfer[] { LocalSelectionTransfer.getTransfer(),
985 FileTransfer.getInstance() }, new StagingDragListener(
986 stagedViewer));
987 stagedViewer.addDropSupport(DND.DROP_MOVE,
988 new Transfer[] { LocalSelectionTransfer.getTransfer() },
989 new DropTargetAdapter() {
990 @Override
991 public void drop(DropTargetEvent event) {
992 // Bug 411466: It is very important that detail is set
993 // to DND.DROP_COPY. If it was left as DND.DROP_MOVE and
994 // the drag comes from the Navigator view, the code in
995 // NavigatorDragAdapter would delete the resources.
996 event.detail = DND.DROP_COPY;
997 if (event.data instanceof IStructuredSelection) {
998 final IStructuredSelection selection = (IStructuredSelection) event.data;
999 stage(selection);
1003 @Override
1004 public void dragOver(DropTargetEvent event) {
1005 event.detail = DND.DROP_MOVE;
1008 stagedViewer.addOpenListener(new IOpenListener() {
1009 @Override
1010 public void open(OpenEvent event) {
1011 compareWith(event);
1014 stagedViewer.setComparator(
1015 new StagingEntryComparator(getSortCheckState(), getPreferenceStore()
1016 .getBoolean(UIPreferences.STAGING_VIEW_FILENAME_MODE)));
1017 enableAutoExpand(stagedViewer);
1018 addListenerToDisableAutoExpandOnCollapse(stagedViewer);
1020 selectionChangedListener = new ISelectionListener() {
1021 @Override
1022 public void selectionChanged(IWorkbenchPart part,
1023 ISelection selection) {
1024 if (part == getSite().getPart()) {
1025 return;
1027 // don't accept text selection, only structural one
1028 if (selection instanceof StructuredSelection) {
1029 reactOnSelection((StructuredSelection) selection);
1034 partListener = new PartListener();
1036 IPreferenceStore preferenceStore = getPreferenceStore();
1037 if (preferenceStore.contains(UIPreferences.STAGING_VIEW_SYNC_SELECTION))
1038 reactOnSelection = preferenceStore.getBoolean(
1039 UIPreferences.STAGING_VIEW_SYNC_SELECTION);
1040 else
1041 preferenceStore.setDefault(UIPreferences.STAGING_VIEW_SYNC_SELECTION, true);
1043 preferenceStore.addPropertyChangeListener(uiPrefsListener);
1045 InstanceScope.INSTANCE.getNode(
1046 org.eclipse.egit.core.Activator.getPluginId())
1047 .addPreferenceChangeListener(prefListener);
1049 updateSectionText();
1050 updateToolbar();
1051 enableCommitWidgets(false);
1052 refreshAction.setEnabled(false);
1054 createPopupMenu(unstagedViewer);
1055 createPopupMenu(stagedViewer);
1057 final ICommitMessageComponentNotifications listener = new ICommitMessageComponentNotifications() {
1059 @Override
1060 public void updateSignedOffToggleSelection(boolean selection) {
1061 signedOffByAction.setChecked(selection);
1064 @Override
1065 public void updateChangeIdToggleSelection(boolean selection) {
1066 addChangeIdAction.setChecked(selection);
1067 commitAndPushButton
1068 .setImage(selection ? getImage(UIIcons.GERRIT) : null);
1071 @Override
1072 public void statusUpdated() {
1073 updateMessage();
1076 commitMessageComponent = new CommitMessageComponent(listener);
1077 commitMessageComponent.attachControls(commitMessageText, authorText,
1078 committerText);
1080 // allow to commit with ctrl-enter
1081 commitMessageText.getTextWidget().addVerifyKeyListener(new VerifyKeyListener() {
1082 @Override
1083 public void verifyKey(VerifyEvent event) {
1084 if (UIUtils.isSubmitKeyEvent(event)) {
1085 event.doit = false;
1086 commit(false);
1091 commitMessageText.getTextWidget().addFocusListener(new FocusListener() {
1092 @Override
1093 public void focusGained(FocusEvent e) {
1094 // Ctrl+Enter shortcut only works when the focus is on the commit message text
1095 String commitButtonTooltip = MessageFormat.format(
1096 UIText.StagingView_CommitToolTip,
1097 UIUtils.SUBMIT_KEY_STROKE.format());
1098 commitButton.setToolTipText(commitButtonTooltip);
1101 @Override
1102 public void focusLost(FocusEvent e) {
1103 commitButton.setToolTipText(null);
1107 // react on selection changes
1108 IWorkbenchPartSite site = getSite();
1109 ISelectionService srv = CommonUtils.getService(site, ISelectionService.class);
1110 srv.addPostSelectionListener(selectionChangedListener);
1111 CommonUtils.getService(site, IPartService.class).addPartListener(
1112 partListener);
1114 // Use current selection to populate staging view
1115 UIUtils.notifySelectionChangedWithCurrentSelection(
1116 selectionChangedListener, site);
1118 site.setSelectionProvider(unstagedViewer);
1120 ViewerFilter filter = new ViewerFilter() {
1121 @Override
1122 public boolean select(Viewer viewer, Object parentElement,
1123 Object element) {
1124 StagingViewContentProvider contentProvider = getContentProvider((TreeViewer) viewer);
1125 if (element instanceof StagingEntry)
1126 return contentProvider.isInFilter((StagingEntry) element);
1127 else if (element instanceof StagingFolderEntry)
1128 return contentProvider
1129 .hasVisibleChildren((StagingFolderEntry) element);
1130 return true;
1133 unstagedViewer.addFilter(filter);
1134 stagedViewer.addFilter(filter);
1136 restoreSashFormWeights();
1137 reactOnInitialSelection();
1139 IWorkbenchSiteProgressService service = CommonUtils.getService(
1140 getSite(), IWorkbenchSiteProgressService.class);
1141 if (service != null && reactOnSelection)
1142 // If we are linked, each time IndexDiffUpdateJob starts, indicate
1143 // that the view is busy (e.g. reload() will trigger this job in
1144 // background!).
1145 service.showBusyForFamily(org.eclipse.egit.core.JobFamilies.INDEX_DIFF_CACHE_UPDATE);
1148 private boolean commitAndPushEnabled(boolean commitEnabled) {
1149 Repository repo = currentRepository;
1150 if (repo == null) {
1151 return false;
1153 return commitEnabled && !repo.getRepositoryState().isRebasing();
1156 private void updateIgnoreErrorsButtonVisibility() {
1157 boolean visible = getPreferenceStore()
1158 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
1159 && getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT);
1160 showControl(ignoreErrors, visible);
1161 ignoreErrors.getParent().layout(true);
1164 private int getProblemsSeverity() {
1165 int result = IProblemDecoratable.SEVERITY_NONE;
1166 StagingViewContentProvider stagedContentProvider = getContentProvider(
1167 stagedViewer);
1168 StagingEntry[] entries = stagedContentProvider.getStagingEntries();
1169 for (StagingEntry entry : entries) {
1170 if (entry.getProblemSeverity() >= IMarker.SEVERITY_WARNING) {
1171 if (result < entry.getProblemSeverity()) {
1172 result = entry.getProblemSeverity();
1176 return result;
1179 private void updateCommitButtons() {
1180 IndexDiffData indexDiff;
1181 if (cacheEntry != null) {
1182 indexDiff = cacheEntry.getIndexDiff();
1183 } else {
1184 Repository repo = currentRepository;
1185 if (repo == null) {
1186 indexDiff = null;
1187 } else {
1188 indexDiff = doReload(repo);
1191 boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
1192 boolean noConflicts = noConflicts(indexDiff);
1194 boolean commitEnabled = !isCommitBlocked() && noConflicts
1195 && indexDiffAvailable;
1197 boolean commitAndPushEnabled = commitAndPushEnabled(commitEnabled);
1199 commitButton.setEnabled(commitEnabled);
1200 commitAndPushButton.setEnabled(commitAndPushEnabled);
1203 private void saveSashFormWeightsOnDisposal(final SashForm sashForm,
1204 final String settingsKey) {
1205 sashForm.addDisposeListener(new DisposeListener() {
1206 @Override
1207 public void widgetDisposed(DisposeEvent e) {
1208 getDialogSettings().put(settingsKey,
1209 intArrayToString(sashForm.getWeights()));
1214 private IDialogSettings getDialogSettings() {
1215 return DialogSettings.getOrCreateSection(
1216 Activator.getDefault().getDialogSettings(),
1217 StagingView.class.getName());
1220 private static String intArrayToString(int[] ints) {
1221 StringBuilder res = new StringBuilder();
1222 if (ints != null && ints.length > 0) {
1223 res.append(String.valueOf(ints[0]));
1224 for (int i = 1; i < ints.length; i++) {
1225 res.append(',');
1226 res.append(String.valueOf(ints[i]));
1229 return res.toString();
1232 private void restoreSashFormWeights() {
1233 restoreSashFormWeights(horizontalSashForm,
1234 HORIZONTAL_SASH_FORM_WEIGHT);
1235 restoreSashFormWeights(stagingSashForm,
1236 STAGING_SASH_FORM_WEIGHT);
1239 private void restoreSashFormWeights(SashForm sashForm, String settingsKey) {
1240 IDialogSettings settings = getDialogSettings();
1241 String weights = settings.get(settingsKey);
1242 if (weights != null && !weights.isEmpty()) {
1243 sashForm.setWeights(stringToIntArray(weights));
1247 private static int[] stringToIntArray(String s) {
1248 String[] parts = s.split(","); //$NON-NLS-1$
1249 int[] ints = new int[parts.length];
1250 for (int i = 0; i < parts.length; i++) {
1251 ints[i] = Integer.valueOf(parts[i]).intValue();
1253 return ints;
1256 private void reactOnInitialSelection() {
1257 StructuredSelection sel = null;
1258 if (initialSelection instanceof StructuredSelection) {
1259 sel = (StructuredSelection) initialSelection;
1260 } else if (initialSelection != null && !initialSelection.isEmpty()) {
1261 sel = getSelectionOfActiveEditor();
1263 if (sel != null) {
1264 reactOnSelection(sel);
1266 initialSelection = null;
1269 private StructuredSelection getSelectionOfActiveEditor() {
1270 IEditorPart activeEditor = getSite().getPage().getActiveEditor();
1271 if (activeEditor == null) {
1272 return null;
1274 return getSelectionOfPart(activeEditor);
1277 private static StructuredSelection getSelectionOfPart(IWorkbenchPart part) {
1278 StructuredSelection sel = null;
1279 if (part instanceof IEditorPart) {
1280 IResource resource = getResource((IEditorPart) part);
1281 if (resource != null) {
1282 sel = new StructuredSelection(resource);
1283 } else {
1284 Repository repository = getRepository((IEditorPart) part);
1285 if (repository != null) {
1286 sel = new StructuredSelection(repository);
1289 } else {
1290 ISelection selection = part.getSite().getPage().getSelection();
1291 if (selection instanceof StructuredSelection) {
1292 sel = (StructuredSelection) selection;
1295 return sel;
1298 @Nullable
1299 private static Repository getRepository(IEditorPart part) {
1300 IEditorInput input = part.getEditorInput();
1301 if (!(input instanceof IURIEditorInput)) {
1302 return null;
1304 return AdapterUtils.adapt(input, Repository.class);
1307 private static IResource getResource(IEditorPart part) {
1308 IEditorInput input = part.getEditorInput();
1309 if (input instanceof IFileEditorInput) {
1310 return ((IFileEditorInput) input).getFile();
1311 } else {
1312 return AdapterUtils.adapt(input, IResource.class);
1316 private boolean getSortCheckState() {
1317 return getDialogSettings().getBoolean(STORE_SORT_STATE);
1320 private void executeRebaseOperation(AbstractRebaseCommandHandler command) {
1321 try {
1322 command.execute(currentRepository);
1323 } catch (ExecutionException e) {
1324 Activator.showError(e.getMessage(), e);
1329 * Abort rebase command in progress
1331 protected void rebaseAbort() {
1332 AbortRebaseCommand abortCommand = new AbortRebaseCommand();
1333 executeRebaseOperation(abortCommand);
1337 * Rebase next commit and continue rebase in progress
1339 protected void rebaseSkip() {
1340 SkipRebaseCommand skipCommand = new SkipRebaseCommand();
1341 executeRebaseOperation(skipCommand);
1345 * Continue rebase command in progress
1347 protected void rebaseContinue() {
1348 ContinueRebaseCommand continueCommand = new ContinueRebaseCommand();
1349 executeRebaseOperation(continueCommand);
1352 private void createUnstagedToolBarComposite() {
1353 Composite unstagedToolbarComposite = toolkit
1354 .createComposite(unstagedSection);
1355 unstagedToolbarComposite.setBackground(null);
1356 unstagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
1357 unstagedSection.setTextClient(unstagedToolbarComposite);
1358 unstagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
1359 IAction.AS_PUSH_BUTTON) {
1360 @Override
1361 public void run() {
1362 unstagedViewer.expandAll();
1363 enableAutoExpand(unstagedViewer);
1366 unstagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
1368 unstagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
1369 IAction.AS_PUSH_BUTTON) {
1370 @Override
1371 public void run() {
1372 unstagedViewer.collapseAll();
1373 disableAutoExpand(unstagedViewer);
1376 unstagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1378 sortAction = new Action(UIText.StagingView_UnstagedSort,
1379 IAction.AS_CHECK_BOX) {
1381 @Override
1382 public void run() {
1383 StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
1384 .getComparator();
1385 comparator.setAlphabeticSort(!isChecked());
1386 comparator = (StagingEntryComparator) stagedViewer.getComparator();
1387 comparator.setAlphabeticSort(!isChecked());
1388 unstagedViewer.refresh();
1389 stagedViewer.refresh();
1393 sortAction.setImageDescriptor(UIIcons.STATE_SORT);
1394 sortAction.setId(SORT_ITEM_TOOLBAR_ID);
1395 sortAction.setChecked(getSortCheckState());
1397 unstagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
1399 unstagedToolBarManager.add(sortAction);
1400 unstagedToolBarManager.add(unstagedExpandAllAction);
1401 unstagedToolBarManager.add(unstagedCollapseAllAction);
1403 unstagedToolBarManager.update(true);
1404 unstagedToolBarManager.createControl(unstagedToolbarComposite);
1407 private void createStagedToolBarComposite() {
1408 Composite stagedToolbarComposite = toolkit
1409 .createComposite(stagedSection);
1410 stagedToolbarComposite.setBackground(null);
1411 stagedToolbarComposite.setLayout(createRowLayoutWithoutMargin());
1412 stagedSection.setTextClient(stagedToolbarComposite);
1413 stagedExpandAllAction = new Action(UIText.UIUtils_ExpandAll,
1414 IAction.AS_PUSH_BUTTON) {
1415 @Override
1416 public void run() {
1417 stagedViewer.expandAll();
1418 enableAutoExpand(stagedViewer);
1421 stagedExpandAllAction.setImageDescriptor(UIIcons.EXPAND_ALL);
1423 stagedCollapseAllAction = new Action(UIText.UIUtils_CollapseAll,
1424 IAction.AS_PUSH_BUTTON) {
1425 @Override
1426 public void run() {
1427 stagedViewer.collapseAll();
1428 disableAutoExpand(stagedViewer);
1431 stagedCollapseAllAction.setImageDescriptor(UIIcons.COLLAPSEALL);
1433 stagedToolBarManager = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL);
1435 stagedToolBarManager.add(stagedExpandAllAction);
1436 stagedToolBarManager.add(stagedCollapseAllAction);
1437 stagedToolBarManager.update(true);
1438 stagedToolBarManager.createControl(stagedToolbarComposite);
1441 private static RowLayout createRowLayoutWithoutMargin() {
1442 RowLayout layout = new RowLayout();
1443 layout.marginHeight = 0;
1444 layout.marginWidth = 0;
1445 layout.marginTop = 0;
1446 layout.marginBottom = 0;
1447 layout.marginLeft = 0;
1448 layout.marginRight = 0;
1449 return layout;
1452 private static void addListenerToDisableAutoExpandOnCollapse(
1453 TreeViewer treeViewer) {
1454 treeViewer.addTreeListener(new ITreeViewerListener() {
1455 @Override
1456 public void treeCollapsed(TreeExpansionEvent event) {
1457 disableAutoExpand(event.getTreeViewer());
1460 @Override
1461 public void treeExpanded(TreeExpansionEvent event) {
1462 // Nothing to do
1467 private static void enableAutoExpand(AbstractTreeViewer treeViewer) {
1468 treeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
1471 private static void disableAutoExpand(AbstractTreeViewer treeViewer) {
1472 treeViewer.setAutoExpandLevel(0);
1476 * @return selected repository
1478 public Repository getCurrentRepository() {
1479 return currentRepository;
1482 @Override
1483 public ShowInContext getShowInContext() {
1484 if (stagedViewer != null && stagedViewer.getTree().isFocusControl())
1485 return getShowInContext(stagedViewer);
1486 else if (unstagedViewer != null
1487 && unstagedViewer.getTree().isFocusControl())
1488 return getShowInContext(unstagedViewer);
1489 else
1490 return null;
1493 private ShowInContext getShowInContext(TreeViewer treeViewer) {
1494 IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
1495 List<Object> elements = new ArrayList<Object>();
1496 for (Object selectedElement : selection.toList()) {
1497 if (selectedElement instanceof StagingEntry) {
1498 StagingEntry entry = (StagingEntry) selectedElement;
1499 IFile file = entry.getFile();
1500 if (file != null)
1501 elements.add(file);
1502 else
1503 elements.add(entry.getLocation());
1504 } else if (selectedElement instanceof StagingFolderEntry) {
1505 StagingFolderEntry entry = (StagingFolderEntry) selectedElement;
1506 IContainer container = entry.getContainer();
1507 if (container != null)
1508 elements.add(container);
1509 else
1510 elements.add(entry.getLocation());
1513 return new ShowInContext(null, new StructuredSelection(elements));
1516 private int getStagingFormOrientation() {
1517 boolean columnLayout = Activator.getDefault().getPreferenceStore()
1518 .getBoolean(UIPreferences.STAGING_VIEW_COLUMN_LAYOUT);
1519 if (columnLayout)
1520 return SWT.HORIZONTAL;
1521 else
1522 return SWT.VERTICAL;
1525 private void enableAllWidgets(boolean enabled) {
1526 if (isDisposed())
1527 return;
1528 enableCommitWidgets(enabled);
1529 enableStagingWidgets(enabled);
1532 private void enableStagingWidgets(boolean enabled) {
1533 if (isDisposed())
1534 return;
1535 unstagedViewer.getControl().setEnabled(enabled);
1536 stagedViewer.getControl().setEnabled(enabled);
1539 private void enableCommitWidgets(boolean enabled) {
1540 if (isDisposed())
1541 return;
1543 commitMessageText.setEnabled(enabled);
1544 committerText.setEnabled(enabled);
1545 enableAuthorText(enabled);
1546 amendPreviousCommitAction.setEnabled(enabled);
1547 signedOffByAction.setEnabled(enabled);
1548 addChangeIdAction.setEnabled(enabled);
1549 commitButton.setEnabled(enabled);
1550 commitAndPushButton.setEnabled(enabled);
1553 private void enableAuthorText(boolean enabled) {
1554 Repository repo = currentRepository;
1555 if (repo != null && repo.getRepositoryState()
1556 .equals(RepositoryState.CHERRY_PICKING_RESOLVED)) {
1557 authorText.setEnabled(false);
1558 } else {
1559 authorText.setEnabled(enabled);
1563 private void updateToolbar() {
1565 ControlContribution controlContribution = new ControlContribution(
1566 "StagingView.searchText") { //$NON-NLS-1$
1567 @Override
1568 protected Control createControl(Composite parent) {
1569 Composite toolbarComposite = toolkit.createComposite(parent,
1570 SWT.NONE);
1571 toolbarComposite.setBackground(null);
1572 GridLayout headLayout = new GridLayout();
1573 headLayout.numColumns = 2;
1574 headLayout.marginHeight = 0;
1575 headLayout.marginWidth = 0;
1576 headLayout.marginTop = 0;
1577 headLayout.marginBottom = 0;
1578 headLayout.marginLeft = 0;
1579 headLayout.marginRight = 0;
1580 toolbarComposite.setLayout(headLayout);
1582 filterText = new Text(toolbarComposite, SWT.SEARCH
1583 | SWT.ICON_CANCEL | SWT.ICON_SEARCH);
1584 filterText.setMessage(UIText.StagingView_Find);
1585 GridData data = new GridData(GridData.FILL_HORIZONTAL);
1586 data.widthHint = 150;
1587 filterText.setLayoutData(data);
1588 final Display display = Display.getCurrent();
1589 filterText.addModifyListener(new ModifyListener() {
1590 @Override
1591 public void modifyText(ModifyEvent e) {
1592 final StagingViewSearchThread searchThread = new StagingViewSearchThread(
1593 StagingView.this);
1594 display.timerExec(200, new Runnable() {
1595 @Override
1596 public void run() {
1597 searchThread.start();
1602 return toolbarComposite;
1606 IActionBars actionBars = getViewSite().getActionBars();
1607 IToolBarManager toolbar = actionBars.getToolBarManager();
1609 toolbar.add(controlContribution);
1611 refreshAction = new Action(UIText.StagingView_Refresh, IAction.AS_PUSH_BUTTON) {
1612 @Override
1613 public void run() {
1614 if (cacheEntry != null) {
1615 schedule(
1616 cacheEntry.createRefreshResourcesAndIndexDiffJob(),
1617 false);
1621 refreshAction.setImageDescriptor(UIIcons.ELCL16_REFRESH);
1622 toolbar.add(refreshAction);
1624 // link with selection
1625 Action linkSelectionAction = new BooleanPrefAction(
1626 (IPersistentPreferenceStore) getPreferenceStore(),
1627 UIPreferences.STAGING_VIEW_SYNC_SELECTION,
1628 UIText.StagingView_LinkSelection) {
1629 @Override
1630 public void apply(boolean value) {
1631 reactOnSelection = value;
1634 linkSelectionAction.setImageDescriptor(UIIcons.ELCL16_SYNCED);
1635 toolbar.add(linkSelectionAction);
1637 toolbar.add(new Separator());
1639 compareModeAction = new Action(UIText.StagingView_CompareMode,
1640 IAction.AS_CHECK_BOX) {
1641 @Override
1642 public void run() {
1643 getPreferenceStore().setValue(
1644 UIPreferences.STAGING_VIEW_COMPARE_MODE, isChecked());
1647 compareModeAction.setImageDescriptor(UIIcons.ELCL16_COMPARE_VIEW);
1648 compareModeAction.setChecked(getPreferenceStore()
1649 .getBoolean(UIPreferences.STAGING_VIEW_COMPARE_MODE));
1651 toolbar.add(compareModeAction);
1652 toolbar.add(new Separator());
1654 openNewCommitsAction = new Action(UIText.StagingView_OpenNewCommits,
1655 IAction.AS_CHECK_BOX) {
1657 @Override
1658 public void run() {
1659 getPreferenceStore().setValue(
1660 UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS, isChecked());
1663 openNewCommitsAction.setChecked(getPreferenceStore().getBoolean(
1664 UIPreferences.STAGING_VIEW_SHOW_NEW_COMMITS));
1666 columnLayoutAction = new Action(UIText.StagingView_ColumnLayout,
1667 IAction.AS_CHECK_BOX) {
1669 @Override
1670 public void run() {
1671 getPreferenceStore().setValue(
1672 UIPreferences.STAGING_VIEW_COLUMN_LAYOUT, isChecked());
1673 stagingSashForm.setOrientation(isChecked() ? SWT.HORIZONTAL
1674 : SWT.VERTICAL);
1677 columnLayoutAction.setChecked(getPreferenceStore().getBoolean(
1678 UIPreferences.STAGING_VIEW_COLUMN_LAYOUT));
1680 fileNameModeAction = new Action(UIText.StagingView_ShowFileNamesFirst,
1681 IAction.AS_CHECK_BOX) {
1683 @Override
1684 public void run() {
1685 final boolean enable = isChecked();
1686 getLabelProvider(stagedViewer).setFileNameMode(enable);
1687 getLabelProvider(unstagedViewer).setFileNameMode(enable);
1688 getContentProvider(stagedViewer).setFileNameMode(enable);
1689 getContentProvider(unstagedViewer).setFileNameMode(enable);
1690 StagingEntryComparator comparator = (StagingEntryComparator) unstagedViewer
1691 .getComparator();
1692 comparator.setFileNamesFirst(enable);
1693 comparator = (StagingEntryComparator) stagedViewer.getComparator();
1694 comparator.setFileNamesFirst(enable);
1695 getPreferenceStore().setValue(
1696 UIPreferences.STAGING_VIEW_FILENAME_MODE, enable);
1697 refreshViewersPreservingExpandedElements();
1700 fileNameModeAction.setChecked(getPreferenceStore().getBoolean(
1701 UIPreferences.STAGING_VIEW_FILENAME_MODE));
1703 IMenuManager dropdownMenu = actionBars.getMenuManager();
1704 MenuManager presentationMenu = new MenuManager(
1705 UIText.StagingView_Presentation);
1706 listPresentationAction = new Action(UIText.StagingView_List,
1707 IAction.AS_RADIO_BUTTON) {
1708 @Override
1709 public void run() {
1710 if (!isChecked()) {
1711 return;
1713 presentation = Presentation.LIST;
1714 setPresentation(presentation, false);
1715 treePresentationAction.setChecked(false);
1716 compactTreePresentationAction.setChecked(false);
1717 setExpandCollapseActionsVisible(false);
1718 refreshViewers();
1721 listPresentationAction.setImageDescriptor(UIIcons.FLAT);
1722 presentationMenu.add(listPresentationAction);
1724 treePresentationAction = new Action(UIText.StagingView_Tree,
1725 IAction.AS_RADIO_BUTTON) {
1726 @Override
1727 public void run() {
1728 if (!isChecked()) {
1729 return;
1731 presentation = Presentation.TREE;
1732 setPresentation(presentation, false);
1733 listPresentationAction.setChecked(false);
1734 compactTreePresentationAction.setChecked(false);
1735 setExpandCollapseActionsVisible(isExpandAllowed());
1736 refreshViewers();
1739 treePresentationAction.setImageDescriptor(UIIcons.HIERARCHY);
1740 presentationMenu.add(treePresentationAction);
1742 compactTreePresentationAction = new Action(UIText.StagingView_CompactTree,
1743 IAction.AS_RADIO_BUTTON) {
1744 @Override
1745 public void run() {
1746 if (!isChecked()) {
1747 return;
1749 switchToCompactModeInternal(false);
1750 refreshViewers();
1754 compactTreePresentationAction.setImageDescriptor(UIIcons.COMPACT);
1755 presentationMenu.add(compactTreePresentationAction);
1757 presentation = readPresentation(UIPreferences.STAGING_VIEW_PRESENTATION,
1758 Presentation.LIST);
1759 switch (presentation) {
1760 case LIST:
1761 listPresentationAction.setChecked(true);
1762 setExpandCollapseActionsVisible(false);
1763 break;
1764 case TREE:
1765 treePresentationAction.setChecked(true);
1766 break;
1767 case COMPACT_TREE:
1768 compactTreePresentationAction.setChecked(true);
1769 break;
1770 default:
1771 break;
1773 dropdownMenu.add(presentationMenu);
1774 dropdownMenu.add(new Separator());
1775 dropdownMenu.add(openNewCommitsAction);
1776 dropdownMenu.add(columnLayoutAction);
1777 dropdownMenu.add(fileNameModeAction);
1778 dropdownMenu.add(compareModeAction);
1780 actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), new GlobalDeleteActionHandler());
1782 // For the normal resource undo/redo actions to be active, so that files
1783 // deleted via the "Delete" action in the staging view can be restored.
1784 IUndoContext workspaceContext = AdapterUtils.adapt(ResourcesPlugin.getWorkspace(), IUndoContext.class);
1785 undoRedoActionGroup = new UndoRedoActionGroup(getViewSite(), workspaceContext, true);
1786 undoRedoActionGroup.fillActionBars(actionBars);
1788 actionBars.updateActionBars();
1791 private Presentation readPresentation(String key, Presentation def) {
1792 String presentationString = getPreferenceStore().getString(key);
1793 if (presentationString.length() > 0) {
1794 try {
1795 return Presentation.valueOf(presentationString);
1796 } catch (IllegalArgumentException e) {
1797 // Use given default
1800 return def;
1803 private void setPresentation(Presentation newOne, boolean auto) {
1804 Presentation old = presentation;
1805 presentation = newOne;
1806 IPreferenceStore store = getPreferenceStore();
1807 store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION, newOne.name());
1808 if (auto && old != newOne) {
1809 // remember user choice if we switch mode automatically
1810 store.setValue(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED,
1811 true);
1812 } else {
1813 store.setToDefault(UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
1817 private void setExpandCollapseActionsVisible(boolean visible) {
1818 for (IContributionItem item : unstagedToolBarManager.getItems()) {
1819 if (!SORT_ITEM_TOOLBAR_ID.equals(item.getId())) {
1820 item.setVisible(visible);
1823 for (IContributionItem item : stagedToolBarManager.getItems()) {
1824 if (!SORT_ITEM_TOOLBAR_ID.equals(item.getId())) {
1825 item.setVisible(visible);
1828 unstagedExpandAllAction.setEnabled(visible);
1829 unstagedCollapseAllAction.setEnabled(visible);
1830 stagedExpandAllAction.setEnabled(visible);
1831 stagedCollapseAllAction.setEnabled(visible);
1832 sortAction.setEnabled(true);
1833 unstagedToolBarManager.update(true);
1834 stagedToolBarManager.update(true);
1837 private boolean isExpandAllowed() {
1838 StagingViewContentProvider contentProvider = getContentProvider(
1839 stagedViewer);
1840 if (contentProvider.getCount() > getMaxLimitForListMode()) {
1841 return false;
1843 contentProvider = getContentProvider(unstagedViewer);
1844 if (contentProvider.getCount() > getMaxLimitForListMode()) {
1845 return false;
1847 return true;
1850 private TreeViewer createTree(Composite composite) {
1851 Tree tree = toolkit.createTree(composite, SWT.FULL_SELECTION
1852 | SWT.MULTI);
1853 TreeViewer treeViewer = new TreeViewer(tree);
1854 return treeViewer;
1857 private IBaseLabelProvider createLabelProvider(TreeViewer treeViewer) {
1858 StagingViewLabelProvider baseProvider = new StagingViewLabelProvider(
1859 this);
1860 baseProvider.setFileNameMode(getPreferenceStore().getBoolean(
1861 UIPreferences.STAGING_VIEW_FILENAME_MODE));
1863 ProblemLabelDecorator decorator = new ProblemLabelDecorator(treeViewer);
1864 return new TreeDecoratingLabelProvider(baseProvider, decorator);
1867 private StagingViewContentProvider createStagingContentProvider(
1868 boolean unstaged) {
1869 StagingViewContentProvider provider = new StagingViewContentProvider(
1870 this, unstaged);
1871 provider.setFileNameMode(getPreferenceStore().getBoolean(
1872 UIPreferences.STAGING_VIEW_FILENAME_MODE));
1873 return provider;
1876 private IPreferenceStore getPreferenceStore() {
1877 return Activator.getDefault().getPreferenceStore();
1880 private StagingViewLabelProvider getLabelProvider(ContentViewer viewer) {
1881 IBaseLabelProvider base = viewer.getLabelProvider();
1882 ILabelProvider labelProvider = ((TreeDecoratingLabelProvider) base)
1883 .getLabelProvider();
1884 return (StagingViewLabelProvider) labelProvider;
1887 private StagingViewContentProvider getContentProvider(ContentViewer viewer) {
1888 return (StagingViewContentProvider) viewer.getContentProvider();
1891 private void updateSectionText() {
1892 stagedSection.setText(MessageFormat
1893 .format(UIText.StagingView_StagedChanges,
1894 getSectionCount(stagedViewer)));
1895 unstagedSection.setText(MessageFormat.format(
1896 UIText.StagingView_UnstagedChanges,
1897 getSectionCount(unstagedViewer)));
1900 private String getSectionCount(TreeViewer viewer) {
1901 StagingViewContentProvider contentProvider = getContentProvider(viewer);
1902 int count = contentProvider.getCount();
1903 int shownCount = contentProvider.getShownCount();
1904 if (shownCount == count)
1905 return Integer.toString(count);
1906 else
1907 return shownCount + "/" + count; //$NON-NLS-1$
1910 private void updateMessage() {
1911 if (hasErrorsOrWarnings()) {
1912 warningLabel.showMessage(UIText.StagingView_MessageErrors);
1913 commitMessageSection.redraw();
1914 } else {
1915 String message = commitMessageComponent.getStatus().getMessage();
1916 boolean needsRedraw = false;
1917 if (message != null) {
1918 warningLabel.showMessage(message);
1919 needsRedraw = true;
1920 } else {
1921 needsRedraw = warningLabel.getVisible();
1922 warningLabel.hideMessage();
1924 // Without this explicit redraw, the ControlDecoration of the
1925 // commit message area would not get updated and cause visual
1926 // corruption.
1927 if (needsRedraw)
1928 commitMessageSection.redraw();
1932 private void compareWith(OpenEvent event) {
1933 IStructuredSelection selection = (IStructuredSelection) event
1934 .getSelection();
1935 if (selection.isEmpty()
1936 || !(selection.getFirstElement() instanceof StagingEntry))
1937 return;
1938 StagingEntry stagingEntry = (StagingEntry) selection.getFirstElement();
1939 if (stagingEntry.isSubmodule())
1940 return;
1941 switch (stagingEntry.getState()) {
1942 case ADDED:
1943 case CHANGED:
1944 case REMOVED:
1945 runCommand(ActionCommands.COMPARE_INDEX_WITH_HEAD_ACTION, selection);
1946 break;
1948 case CONFLICTING:
1949 runCommand(ActionCommands.MERGE_TOOL_ACTION, selection);
1950 break;
1952 case MISSING:
1953 case MISSING_AND_CHANGED:
1954 case MODIFIED:
1955 case MODIFIED_AND_CHANGED:
1956 case MODIFIED_AND_ADDED:
1957 case UNTRACKED:
1958 default:
1959 if (Activator.getDefault().getPreferenceStore().getBoolean(
1960 UIPreferences.STAGING_VIEW_COMPARE_MODE)) {
1961 // compare with index
1962 runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION, selection);
1963 } else {
1964 openSelectionInEditor(selection);
1970 private void createPopupMenu(final TreeViewer treeViewer) {
1971 final MenuManager menuMgr = new MenuManager();
1972 menuMgr.setRemoveAllWhenShown(true);
1973 Control control = treeViewer.getControl();
1974 control.setMenu(menuMgr.createContextMenu(control));
1975 menuMgr.addMenuListener(new IMenuListener() {
1977 @Override
1978 public void menuAboutToShow(IMenuManager manager) {
1979 final IStructuredSelection selection = (IStructuredSelection) treeViewer
1980 .getSelection();
1981 if (selection.isEmpty())
1982 return;
1984 List<StagingEntry> stagingEntryList = new ArrayList<StagingEntry>();
1986 boolean submoduleSelected = false;
1987 boolean folderSelected = false;
1988 for (Object element : selection.toArray()) {
1989 if (element instanceof StagingFolderEntry) {
1990 StagingFolderEntry folder = (StagingFolderEntry) element;
1991 folderSelected = true;
1992 StagingViewContentProvider contentProvider = getContentProvider(treeViewer);
1993 List<StagingEntry> stagingEntries = contentProvider
1994 .getStagingEntriesFiltered(folder);
1995 for (StagingEntry stagingEntry : stagingEntries) {
1996 if (!stagingEntryList.contains(stagingEntry))
1997 stagingEntryList.add(stagingEntry);
1999 } else if (element instanceof StagingEntry) {
2000 StagingEntry entry = (StagingEntry) element;
2001 if (entry.isSubmodule())
2002 submoduleSelected = true;
2003 if (!stagingEntryList.contains(entry))
2004 stagingEntryList.add(entry);
2008 final IStructuredSelection fileSelection = new StructuredSelection(
2009 stagingEntryList);
2011 if (!folderSelected) {
2012 Action openWorkingTreeVersion = new Action(
2013 UIText.CommitFileDiffViewer_OpenWorkingTreeVersionInEditorMenuLabel) {
2014 @Override
2015 public void run() {
2016 openSelectionInEditor(fileSelection);
2019 openWorkingTreeVersion.setEnabled(!submoduleSelected
2020 && anyElementIsExistingFile(fileSelection));
2021 menuMgr.add(openWorkingTreeVersion);
2022 String label = stagingEntryList.get(0).isStaged()
2023 ? UIText.CommitFileDiffViewer_CompareWorkingDirectoryMenuLabel
2024 : UIText.StagingView_CompareWithIndexMenuLabel;
2025 Action openCompareWithIndex = new Action(label) {
2026 @Override
2027 public void run() {
2028 runCommand(ActionCommands.COMPARE_WITH_INDEX_ACTION,
2029 fileSelection);
2032 menuMgr.add(openCompareWithIndex);
2035 Set<StagingEntry.Action> availableActions = getAvailableActions(fileSelection);
2037 boolean addReplaceWithFileInGitIndex = availableActions.contains(StagingEntry.Action.REPLACE_WITH_FILE_IN_GIT_INDEX);
2038 boolean addReplaceWithHeadRevision = availableActions.contains(StagingEntry.Action.REPLACE_WITH_HEAD_REVISION);
2039 boolean addStage = availableActions.contains(StagingEntry.Action.STAGE);
2040 boolean addUnstage = availableActions.contains(StagingEntry.Action.UNSTAGE);
2041 boolean addDelete = availableActions.contains(StagingEntry.Action.DELETE);
2042 boolean addIgnore = availableActions.contains(StagingEntry.Action.IGNORE);
2043 boolean addLaunchMergeTool = availableActions.contains(StagingEntry.Action.LAUNCH_MERGE_TOOL);
2044 boolean addReplaceWithOursTheirsMenu = availableActions
2045 .contains(StagingEntry.Action.REPLACE_WITH_OURS_THEIRS_MENU);
2047 if (addStage)
2048 menuMgr.add(new Action(UIText.StagingView_StageItemMenuLabel) {
2049 @Override
2050 public void run() {
2051 stage(selection);
2054 if (addUnstage)
2055 menuMgr.add(new Action(UIText.StagingView_UnstageItemMenuLabel) {
2056 @Override
2057 public void run() {
2058 unstage(selection);
2061 boolean selectionIncludesNonWorkspaceResources = selectionIncludesNonWorkspaceResources(fileSelection);
2062 if (addReplaceWithFileInGitIndex)
2063 if (selectionIncludesNonWorkspaceResources)
2064 menuMgr.add(new ReplaceAction(
2065 UIText.StagingView_replaceWithFileInGitIndex,
2066 fileSelection, false));
2067 else
2068 menuMgr.add(createItem(
2069 UIText.StagingView_replaceWithFileInGitIndex,
2070 ActionCommands.DISCARD_CHANGES_ACTION,
2071 fileSelection)); // replace with index
2072 if (addReplaceWithHeadRevision)
2073 if (selectionIncludesNonWorkspaceResources)
2074 menuMgr.add(new ReplaceAction(
2075 UIText.StagingView_replaceWithHeadRevision,
2076 fileSelection, true));
2077 else
2078 menuMgr.add(createItem(
2079 UIText.StagingView_replaceWithHeadRevision,
2080 ActionCommands.REPLACE_WITH_HEAD_ACTION,
2081 fileSelection));
2082 if (addIgnore)
2083 menuMgr.add(new IgnoreAction(fileSelection));
2084 if (addDelete)
2085 menuMgr.add(new DeleteAction(fileSelection));
2086 if (addLaunchMergeTool)
2087 menuMgr.add(createItem(UIText.StagingView_MergeTool,
2088 ActionCommands.MERGE_TOOL_ACTION,
2089 fileSelection));
2090 if (addReplaceWithOursTheirsMenu) {
2091 MenuManager replaceWithMenu = new MenuManager(
2092 UIText.StagingView_ReplaceWith);
2093 ReplaceWithOursTheirsMenu oursTheirsMenu = new ReplaceWithOursTheirsMenu();
2094 oursTheirsMenu.initialize(getSite());
2095 replaceWithMenu.add(oursTheirsMenu);
2096 menuMgr.add(replaceWithMenu);
2098 menuMgr.add(new Separator());
2099 menuMgr.add(createShowInMenu());
2105 private boolean anyElementIsExistingFile(IStructuredSelection s) {
2106 for (Object element : s.toList()) {
2107 if (element instanceof StagingEntry) {
2108 StagingEntry entry = (StagingEntry) element;
2109 if (entry.getType() != IResource.FILE) {
2110 continue;
2112 if (entry.getLocation().toFile().exists()) {
2113 return true;
2117 return false;
2121 * @return selected presentation
2123 Presentation getPresentation() {
2124 return presentation;
2128 * @return the trimmed string which is the current filter, empty string for
2129 * no filter
2131 String getFilterString() {
2132 if (filterText != null && !filterText.isDisposed())
2133 return filterText.getText().trim();
2134 else
2135 return ""; //$NON-NLS-1$
2139 * Refresh the unstaged and staged viewers without preserving expanded
2140 * elements
2142 public void refreshViewers() {
2143 syncExec(new Runnable() {
2144 @Override
2145 public void run() {
2146 refreshViewersInternal();
2152 * Refresh the unstaged and staged viewers, preserving expanded elements
2154 public void refreshViewersPreservingExpandedElements() {
2155 syncExec(new Runnable() {
2156 @Override
2157 public void run() {
2158 Object[] unstagedExpanded = unstagedViewer
2159 .getExpandedElements();
2160 Object[] stagedExpanded = stagedViewer.getExpandedElements();
2161 refreshViewersInternal();
2162 unstagedViewer.setExpandedElements(unstagedExpanded);
2163 stagedViewer.setExpandedElements(stagedExpanded);
2168 private void refreshViewersInternal() {
2169 unstagedViewer.refresh();
2170 stagedViewer.refresh();
2171 updateSectionText();
2174 private IContributionItem createShowInMenu() {
2175 IWorkbenchWindow workbenchWindow = getSite().getWorkbenchWindow();
2176 return UIUtils.createShowInMenu(workbenchWindow);
2179 private class ReplaceAction extends Action {
2181 IStructuredSelection selection;
2182 private final boolean headRevision;
2184 ReplaceAction(String text, @NonNull IStructuredSelection selection,
2185 boolean headRevision) {
2186 super(text);
2187 this.selection = selection;
2188 this.headRevision = headRevision;
2191 private void getSelectedFiles(@NonNull List<String> files,
2192 @NonNull List<String> inaccessibleFiles) {
2193 Iterator iterator = selection.iterator();
2194 while (iterator.hasNext()) {
2195 Object selectedItem = iterator.next();
2196 if (selectedItem instanceof StagingEntry) {
2197 StagingEntry stagingEntry = (StagingEntry) selectedItem;
2198 String path = stagingEntry.getPath();
2199 files.add(path);
2200 IFile resource = stagingEntry.getFile();
2201 if (resource == null || !resource.isAccessible()) {
2202 inaccessibleFiles.add(path);
2208 private void replaceWith(@NonNull List<String> files,
2209 @NonNull List<String> inaccessibleFiles) {
2210 Repository repository = currentRepository;
2211 if (files.isEmpty() || repository == null) {
2212 return;
2214 CheckoutCommand checkoutCommand = new Git(repository)
2215 .checkout();
2216 if (headRevision) {
2217 checkoutCommand.setStartPoint(Constants.HEAD);
2219 for (String path : files) {
2220 checkoutCommand.addPath(path);
2222 try {
2223 checkoutCommand.call();
2224 if (!inaccessibleFiles.isEmpty()) {
2225 IndexDiffCacheEntry indexDiffCacheForRepository = org.eclipse.egit.core.Activator
2226 .getDefault().getIndexDiffCache()
2227 .getIndexDiffCacheEntry(repository);
2228 if (indexDiffCacheForRepository != null) {
2229 indexDiffCacheForRepository
2230 .refreshFiles(inaccessibleFiles);
2233 } catch (Exception e) {
2234 Activator.handleError(UIText.StagingView_checkoutFailed, e,
2235 true);
2239 @Override
2240 public void run() {
2241 boolean performAction = MessageDialog.openConfirm(form.getShell(),
2242 UIText.DiscardChangesAction_confirmActionTitle,
2243 UIText.DiscardChangesAction_confirmActionMessage);
2244 if (!performAction) {
2245 return;
2247 List<String> files = new ArrayList<>();
2248 List<String> inaccessibleFiles = new ArrayList<>();
2249 getSelectedFiles(files, inaccessibleFiles);
2250 replaceWith(files, inaccessibleFiles);
2254 private static class IgnoreAction extends Action {
2256 private final IStructuredSelection selection;
2258 IgnoreAction(IStructuredSelection selection) {
2259 super(UIText.StagingView_IgnoreItemMenuLabel);
2260 this.selection = selection;
2263 @Override
2264 public void run() {
2265 IgnoreOperationUI operation = new IgnoreOperationUI(
2266 getSelectedPaths(selection));
2267 operation.run();
2271 private class DeleteAction extends Action {
2273 private final IStructuredSelection selection;
2275 DeleteAction(IStructuredSelection selection) {
2276 super(UIText.StagingView_DeleteItemMenuLabel);
2277 this.selection = selection;
2280 @Override
2281 public void run() {
2282 DeletePathsOperationUI operation = new DeletePathsOperationUI(
2283 getSelectedPaths(selection), getSite());
2284 operation.run();
2288 private class GlobalDeleteActionHandler extends Action {
2290 @Override
2291 public void run() {
2292 DeletePathsOperationUI operation = new DeletePathsOperationUI(
2293 getSelectedPaths(getSelection()), getSite());
2294 operation.run();
2297 @Override
2298 public boolean isEnabled() {
2299 if (!unstagedViewer.getTree().isFocusControl())
2300 return false;
2302 IStructuredSelection selection = getSelection();
2303 if (selection.isEmpty())
2304 return false;
2306 for (Object element : selection.toList()) {
2307 if (!(element instanceof StagingEntry))
2308 return false;
2309 StagingEntry entry = (StagingEntry) element;
2310 if (!entry.getAvailableActions().contains(StagingEntry.Action.DELETE))
2311 return false;
2314 return true;
2317 private IStructuredSelection getSelection() {
2318 return (IStructuredSelection) unstagedViewer.getSelection();
2322 private static List<IPath> getSelectedPaths(IStructuredSelection selection) {
2323 List<IPath> paths = new ArrayList<IPath>();
2324 Iterator iterator = selection.iterator();
2325 while (iterator.hasNext()) {
2326 StagingEntry stagingEntry = (StagingEntry) iterator.next();
2327 paths.add(stagingEntry.getLocation());
2329 return paths;
2333 * @param selection
2334 * @return true if the selection includes a non-workspace resource, false otherwise
2336 private boolean selectionIncludesNonWorkspaceResources(ISelection selection) {
2337 if (!(selection instanceof IStructuredSelection))
2338 return false;
2339 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
2340 Iterator iterator = structuredSelection.iterator();
2341 while (iterator.hasNext()) {
2342 Object selectedObject = iterator.next();
2343 if (!(selectedObject instanceof StagingEntry))
2344 return false;
2345 StagingEntry stagingEntry = (StagingEntry) selectedObject;
2346 IFile file = stagingEntry.getFile();
2347 if (file == null || !file.isAccessible()) {
2348 return true;
2351 return false;
2354 private void openSelectionInEditor(ISelection s) {
2355 Repository repo = currentRepository;
2356 if (repo == null || s.isEmpty() || !(s instanceof IStructuredSelection)) {
2357 return;
2359 final IStructuredSelection iss = (IStructuredSelection) s;
2360 for (Object element : iss.toList()) {
2361 if (element instanceof StagingEntry) {
2362 StagingEntry entry = (StagingEntry) element;
2363 String relativePath = entry.getPath();
2364 String path = new Path(repo.getWorkTree()
2365 .getAbsolutePath()).append(relativePath)
2366 .toOSString();
2367 openFileInEditor(path);
2372 private void openFileInEditor(String filePath) {
2373 IWorkbenchWindow window = PlatformUI.getWorkbench()
2374 .getActiveWorkbenchWindow();
2375 File file = new File(filePath);
2376 if (!file.exists()) {
2377 String message = NLS.bind(UIText.CommitFileDiffViewer_FileDoesNotExist, filePath);
2378 Activator.showError(message, null);
2380 IWorkbenchPage page = window.getActivePage();
2381 EgitUiEditorUtils.openEditor(file, page);
2384 private static Set<StagingEntry.Action> getAvailableActions(IStructuredSelection selection) {
2385 Set<StagingEntry.Action> availableActions = EnumSet.noneOf(StagingEntry.Action.class);
2386 for (Iterator it = selection.iterator(); it.hasNext(); ) {
2387 StagingEntry stagingEntry = (StagingEntry) it.next();
2388 if (availableActions.isEmpty())
2389 availableActions.addAll(stagingEntry.getAvailableActions());
2390 else
2391 availableActions.retainAll(stagingEntry.getAvailableActions());
2393 return availableActions;
2396 private IAction createItem(String text, final String commandId,
2397 final IStructuredSelection selection) {
2398 return new Action(text) {
2399 @Override
2400 public void run() {
2401 CommonUtils.runCommand(commandId, selection);
2406 private boolean shouldUpdateSelection() {
2407 return !isDisposed() && !isViewHidden && reactOnSelection;
2410 private void reactOnSelection(StructuredSelection selection) {
2411 if (selection.size() != 1 || !shouldUpdateSelection()) {
2412 return;
2414 Object firstElement = selection.getFirstElement();
2415 if (firstElement instanceof RepositoryTreeNode) {
2416 RepositoryTreeNode repoNode = (RepositoryTreeNode) firstElement;
2417 if (currentRepository != repoNode.getRepository()) {
2418 reload(repoNode.getRepository());
2420 } else if (firstElement instanceof Repository) {
2421 Repository repo = (Repository) firstElement;
2422 if (currentRepository != repo) {
2423 reload(repo);
2425 } else {
2426 IResource resource = AdapterUtils.adapt(firstElement,
2427 IResource.class);
2428 if (resource != null) {
2429 showResource(resource);
2430 } else {
2431 Repository repo = AdapterUtils.adapt(firstElement,
2432 Repository.class);
2433 if (repo != null && currentRepository != repo) {
2434 reload(repo);
2440 private void showResource(final IResource resource) {
2441 if (resource == null || !resource.isAccessible()) {
2442 return;
2444 Job.getJobManager().cancel(JobFamilies.UPDATE_SELECTION);
2445 Job job = new Job(UIText.StagingView_GetRepo) {
2446 @Override
2447 protected IStatus run(IProgressMonitor monitor) {
2448 if (monitor.isCanceled()) {
2449 return Status.CANCEL_STATUS;
2451 RepositoryMapping mapping = RepositoryMapping
2452 .getMapping(resource);
2453 if (mapping != null) {
2454 Repository newRep = mapping.getRepository();
2455 if (newRep != null && newRep != currentRepository) {
2456 if (monitor.isCanceled()) {
2457 return Status.CANCEL_STATUS;
2459 reload(newRep);
2462 return Status.OK_STATUS;
2465 @Override
2466 public boolean belongsTo(Object family) {
2467 return JobFamilies.UPDATE_SELECTION == family;
2470 @Override
2471 public boolean shouldRun() {
2472 return shouldUpdateSelection();
2475 job.setSystem(true);
2476 schedule(job, false);
2479 private void stage(IStructuredSelection selection) {
2480 StagingViewContentProvider contentProvider = getContentProvider(unstagedViewer);
2481 final Git git = new Git(currentRepository);
2482 Iterator iterator = selection.iterator();
2483 final List<String> addPaths = new ArrayList<String>();
2484 final List<String> rmPaths = new ArrayList<String>();
2485 resetPathsToExpand();
2486 while (iterator.hasNext()) {
2487 Object element = iterator.next();
2488 if (element instanceof StagingEntry) {
2489 StagingEntry entry = (StagingEntry) element;
2490 selectEntryForStaging(entry, addPaths, rmPaths);
2491 addPathAndParentPaths(entry.getParentPath(), pathsToExpandInStaged);
2492 } else if (element instanceof StagingFolderEntry) {
2493 StagingFolderEntry folder = (StagingFolderEntry) element;
2494 List<StagingEntry> entries = contentProvider
2495 .getStagingEntriesFiltered(folder);
2496 for (StagingEntry entry : entries)
2497 selectEntryForStaging(entry, addPaths, rmPaths);
2498 addExpandedPathsBelowFolder(folder, unstagedViewer,
2499 pathsToExpandInStaged);
2500 } else {
2501 IResource resource = AdapterUtils.adapt(element, IResource.class);
2502 if (resource != null) {
2503 RepositoryMapping mapping = RepositoryMapping.getMapping(resource);
2504 // doesn't do anything if the current repository is a
2505 // submodule of the mapped repo
2506 if (mapping != null && mapping.getRepository() == currentRepository) {
2507 String path = mapping.getRepoRelativePath(resource);
2508 // If resource corresponds to root of working directory
2509 if ("".equals(path)) //$NON-NLS-1$
2510 addPaths.add("."); //$NON-NLS-1$
2511 else
2512 addPaths.add(path);
2518 // start long running operations
2519 if (!addPaths.isEmpty()) {
2520 Job addJob = new Job(UIText.StagingView_AddJob) {
2521 @Override
2522 protected IStatus run(IProgressMonitor monitor) {
2523 try {
2524 AddCommand add = git.add();
2525 for (String addPath : addPaths)
2526 add.addFilepattern(addPath);
2527 add.call();
2528 } catch (NoFilepatternException e1) {
2529 // cannot happen
2530 } catch (JGitInternalException e1) {
2531 Activator.handleError(e1.getCause().getMessage(),
2532 e1.getCause(), true);
2533 } catch (Exception e1) {
2534 Activator.handleError(e1.getMessage(), e1, true);
2536 return Status.OK_STATUS;
2539 @Override
2540 public boolean belongsTo(Object family) {
2541 return family == JobFamilies.ADD_TO_INDEX;
2545 schedule(addJob, true);
2548 if (!rmPaths.isEmpty()) {
2549 Job removeJob = new Job(UIText.StagingView_RemoveJob) {
2550 @Override
2551 protected IStatus run(IProgressMonitor monitor) {
2552 try {
2553 RmCommand rm = git.rm().setCached(true);
2554 for (String rmPath : rmPaths)
2555 rm.addFilepattern(rmPath);
2556 rm.call();
2557 } catch (NoFilepatternException e) {
2558 // cannot happen
2559 } catch (JGitInternalException e) {
2560 Activator.handleError(e.getCause().getMessage(),
2561 e.getCause(), true);
2562 } catch (Exception e) {
2563 Activator.handleError(e.getMessage(), e, true);
2565 return Status.OK_STATUS;
2568 @Override
2569 public boolean belongsTo(Object family) {
2570 return family == JobFamilies.REMOVE_FROM_INDEX;
2574 schedule(removeJob, true);
2578 private void selectEntryForStaging(StagingEntry entry,
2579 List<String> addPaths, List<String> rmPaths) {
2580 switch (entry.getState()) {
2581 case ADDED:
2582 case CHANGED:
2583 case REMOVED:
2584 // already staged
2585 break;
2586 case CONFLICTING:
2587 case MODIFIED:
2588 case MODIFIED_AND_CHANGED:
2589 case MODIFIED_AND_ADDED:
2590 case UNTRACKED:
2591 addPaths.add(entry.getPath());
2592 break;
2593 case MISSING:
2594 case MISSING_AND_CHANGED:
2595 rmPaths.add(entry.getPath());
2596 break;
2600 private void unstage(IStructuredSelection selection) {
2601 if (selection.isEmpty())
2602 return;
2604 final List<String> paths = processUnstageSelection(selection);
2605 if (paths.isEmpty())
2606 return;
2608 final Git git = new Git(currentRepository);
2610 Job resetJob = new Job(UIText.StagingView_ResetJob) {
2611 @Override
2612 protected IStatus run(IProgressMonitor monitor) {
2613 try {
2614 ResetCommand reset = git.reset();
2615 for (String path : paths)
2616 reset.addPath(path);
2617 reset.call();
2618 } catch (GitAPIException e) {
2619 Activator.handleError(e.getMessage(), e, true);
2621 return Status.OK_STATUS;
2624 @Override
2625 public boolean belongsTo(Object family) {
2626 return family == JobFamilies.RESET;
2629 schedule(resetJob, true);
2632 private List<String> processUnstageSelection(IStructuredSelection selection) {
2633 List<String> paths = new ArrayList<String>();
2634 resetPathsToExpand();
2635 for (Object element : selection.toList()) {
2636 if (element instanceof StagingEntry) {
2637 StagingEntry entry = (StagingEntry) element;
2638 addUnstagePath(entry, paths);
2639 addPathAndParentPaths(entry.getParentPath(), pathsToExpandInUnstaged);
2640 } else if (element instanceof StagingFolderEntry) {
2641 StagingFolderEntry folder = (StagingFolderEntry) element;
2642 List<StagingEntry> entries = getContentProvider(stagedViewer)
2643 .getStagingEntriesFiltered(folder);
2644 for (StagingEntry entry : entries)
2645 addUnstagePath(entry, paths);
2646 addExpandedPathsBelowFolder(folder, stagedViewer,
2647 pathsToExpandInUnstaged);
2650 return paths;
2653 private void addUnstagePath(StagingEntry entry, List<String> paths) {
2654 switch (entry.getState()) {
2655 case ADDED:
2656 case CHANGED:
2657 case REMOVED:
2658 paths.add(entry.getPath());
2659 return;
2660 default:
2661 // unstaged
2665 private void resetPathsToExpand() {
2666 pathsToExpandInStaged = new HashSet<IPath>();
2667 pathsToExpandInUnstaged = new HashSet<IPath>();
2670 private static void addExpandedPathsBelowFolder(StagingFolderEntry folder,
2671 TreeViewer treeViewer, Set<IPath> addToSet) {
2672 Object[] expandedElements = treeViewer.getExpandedElements();
2673 for (Object expandedElement : expandedElements) {
2674 if (expandedElement instanceof StagingFolderEntry) {
2675 StagingFolderEntry expandedFolder = (StagingFolderEntry) expandedElement;
2676 if (folder.getPath().isPrefixOf(
2677 expandedFolder.getPath()))
2678 addPathAndParentPaths(expandedFolder.getPath(), addToSet);
2683 private static void addPathAndParentPaths(IPath initialPath, Set<IPath> addToSet) {
2684 for (IPath p = initialPath; p.segmentCount() >= 1; p = p
2685 .removeLastSegments(1))
2686 addToSet.add(p);
2689 private boolean isValidRepo(final Repository repository) {
2690 return repository != null
2691 && !repository.isBare()
2692 && repository.getWorkTree().exists();
2696 * Clear the view's state.
2697 * <p>
2698 * This method must be called from the UI-thread
2700 * @param repository
2702 private void clearRepository(@Nullable Repository repository) {
2703 saveCommitMessageComponentState();
2704 currentRepository = null;
2705 StagingViewUpdate update = new StagingViewUpdate(null, null, null);
2706 unstagedViewer.setInput(update);
2707 stagedViewer.setInput(update);
2708 enableCommitWidgets(false);
2709 refreshAction.setEnabled(false);
2710 updateSectionText();
2711 if (repository != null && repository.isBare()) {
2712 form.setText(UIText.StagingView_BareRepoSelection);
2713 } else {
2714 form.setText(UIText.StagingView_NoSelectionTitle);
2716 updateIgnoreErrorsButtonVisibility();
2720 * Show rebase buttons only if a rebase operation is in progress
2722 * @param isRebasing
2723 * {@code}true if rebase is in progress
2725 protected void updateRebaseButtonVisibility(final boolean isRebasing) {
2726 asyncExec(new Runnable() {
2727 @Override
2728 public void run() {
2729 if (isDisposed())
2730 return;
2731 showControl(rebaseSection, isRebasing);
2732 rebaseSection.getParent().layout(true);
2737 private static void showControl(Control c, final boolean show) {
2738 c.setVisible(show);
2739 GridData g = (GridData) c.getLayoutData();
2740 g.exclude = !show;
2744 * @param isAmending
2745 * if the current commit should be amended
2747 public void setAmending(boolean isAmending) {
2748 if (isDisposed())
2749 return;
2750 if (amendPreviousCommitAction.isChecked() != isAmending) {
2751 amendPreviousCommitAction.setChecked(isAmending);
2752 amendPreviousCommitAction.run();
2757 * @param message
2758 * commit message to set for current repository
2760 public void setCommitMessage(String message) {
2761 commitMessageText.setText(message);
2765 * Reload the staging view asynchronously
2767 * @param repository
2769 public void reload(final Repository repository) {
2770 if (isDisposed()) {
2771 return;
2773 if (repository == null) {
2774 asyncExec(new Runnable() {
2775 @Override
2776 public void run() {
2777 clearRepository(null);
2780 return;
2783 if (!isValidRepo(repository)) {
2784 asyncExec(new Runnable() {
2785 @Override
2786 public void run() {
2787 clearRepository(repository);
2790 return;
2793 final boolean repositoryChanged = currentRepository != repository;
2794 currentRepository = repository;
2796 asyncExec(new Runnable() {
2798 @Override
2799 public void run() {
2800 if (isDisposed()) {
2801 return;
2804 final IndexDiffData indexDiff = doReload(repository);
2805 boolean indexDiffAvailable = indexDiffAvailable(indexDiff);
2806 boolean noConflicts = noConflicts(indexDiff);
2808 if (repositoryChanged) {
2809 // Reset paths, they're from the old repository
2810 resetPathsToExpand();
2811 if (refsChangedListener != null)
2812 refsChangedListener.remove();
2813 refsChangedListener = repository.getListenerList()
2814 .addRefsChangedListener(new RefsChangedListener() {
2816 @Override
2817 public void onRefsChanged(RefsChangedEvent event) {
2818 updateRebaseButtonVisibility(repository
2819 .getRepositoryState().isRebasing());
2824 final StagingViewUpdate update = new StagingViewUpdate(repository, indexDiff, null);
2825 Object[] unstagedExpanded = unstagedViewer
2826 .getExpandedElements();
2827 Object[] stagedExpanded = stagedViewer
2828 .getExpandedElements();
2830 int elementsCount = updateAutoExpand(unstagedViewer,
2831 getUnstaged(indexDiff));
2832 elementsCount += updateAutoExpand(stagedViewer,
2833 getStaged(indexDiff));
2835 if (elementsCount > getMaxLimitForListMode()) {
2836 listPresentationAction.setEnabled(false);
2837 if (presentation == Presentation.LIST) {
2838 compactTreePresentationAction.setChecked(true);
2839 switchToCompactModeInternal(true);
2840 } else {
2841 setExpandCollapseActionsVisible(false);
2843 } else {
2844 listPresentationAction.setEnabled(true);
2845 boolean changed = getPreferenceStore().getBoolean(
2846 UIPreferences.STAGING_VIEW_PRESENTATION_CHANGED);
2847 if (changed) {
2848 listPresentationAction.setChecked(true);
2849 listPresentationAction.run();
2850 } else if (presentation != Presentation.LIST) {
2851 setExpandCollapseActionsVisible(true);
2855 unstagedViewer.setInput(update);
2856 stagedViewer.setInput(update);
2857 expandPreviousExpandedAndPaths(unstagedExpanded, unstagedViewer,
2858 pathsToExpandInUnstaged);
2859 expandPreviousExpandedAndPaths(stagedExpanded, stagedViewer,
2860 pathsToExpandInStaged);
2861 refreshAction.setEnabled(true);
2863 updateRebaseButtonVisibility(repository.getRepositoryState()
2864 .isRebasing());
2866 updateIgnoreErrorsButtonVisibility();
2868 boolean rebaseContinueEnabled = indexDiffAvailable
2869 && repository.getRepositoryState().isRebasing()
2870 && noConflicts;
2871 rebaseContinueButton.setEnabled(rebaseContinueEnabled);
2873 form.setText(GitLabels.getStyledLabelSafe(repository).toString());
2874 updateCommitMessageComponent(repositoryChanged, indexDiffAvailable);
2875 enableCommitWidgets(indexDiffAvailable && noConflicts);
2877 updateCommitButtons();
2878 updateSectionText();
2885 * The max number of changed files we can handle in the "list" presentation
2886 * without freezing Eclipse UI for a too long time.
2888 * @return default is 10000
2890 private int getMaxLimitForListMode() {
2891 return Activator.getDefault().getPreferenceStore()
2892 .getInt(UIPreferences.STAGING_VIEW_MAX_LIMIT_LIST_MODE);
2895 private static int getUnstaged(@Nullable IndexDiffData indexDiff) {
2896 if (indexDiff == null) {
2897 return 0;
2899 int size = indexDiff.getUntracked().size();
2900 size += indexDiff.getMissing().size();
2901 size += indexDiff.getModified().size();
2902 size += indexDiff.getConflicting().size();
2903 return size;
2906 private static int getStaged(@Nullable IndexDiffData indexDiff) {
2907 if (indexDiff == null) {
2908 return 0;
2910 int size = indexDiff.getAdded().size();
2911 size += indexDiff.getChanged().size();
2912 size += indexDiff.getRemoved().size();
2913 return size;
2916 private int updateAutoExpand(TreeViewer viewer, int newSize) {
2917 if (newSize > getMaxLimitForListMode()) {
2918 // auto expand with too many nodes freezes eclipse
2919 disableAutoExpand(viewer);
2921 return newSize;
2924 private void switchToCompactModeInternal(boolean auto) {
2925 setPresentation(Presentation.COMPACT_TREE, auto);
2926 listPresentationAction.setChecked(false);
2927 treePresentationAction.setChecked(false);
2928 if (auto) {
2929 setExpandCollapseActionsVisible(false);
2930 } else {
2931 setExpandCollapseActionsVisible(isExpandAllowed());
2935 private static boolean noConflicts(IndexDiffData indexDiff) {
2936 return indexDiff == null ? true : indexDiff.getConflicting().isEmpty();
2939 private static boolean indexDiffAvailable(IndexDiffData indexDiff) {
2940 return indexDiff == null ? false : true;
2943 private boolean hasErrorsOrWarnings() {
2944 return getPreferenceStore()
2945 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
2946 ? (getProblemsSeverity() >= Integer.valueOf(getPreferenceStore()
2947 .getString(UIPreferences.WARN_BEFORE_COMMITTING_LEVEL))
2948 && !ignoreErrors.getSelection()) : false;
2951 @SuppressWarnings("boxing")
2952 private boolean isCommitBlocked() {
2953 return getPreferenceStore()
2954 .getBoolean(UIPreferences.WARN_BEFORE_COMMITTING)
2955 && getPreferenceStore().getBoolean(UIPreferences.BLOCK_COMMIT)
2956 ? (getProblemsSeverity() >= Integer
2957 .valueOf(getPreferenceStore().getString(
2958 UIPreferences.BLOCK_COMMIT_LEVEL))
2959 && !ignoreErrors.getSelection())
2960 : false;
2963 private IndexDiffData doReload(@NonNull final Repository repository) {
2964 IndexDiffCacheEntry entry = org.eclipse.egit.core.Activator.getDefault()
2965 .getIndexDiffCache().getIndexDiffCacheEntry(repository);
2967 if(cacheEntry != null && cacheEntry != entry)
2968 cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
2970 cacheEntry = entry;
2971 cacheEntry.addIndexDiffChangedListener(myIndexDiffListener);
2973 return cacheEntry.getIndexDiff();
2976 private void expandPreviousExpandedAndPaths(Object[] previous,
2977 TreeViewer viewer, Set<IPath> additionalPaths) {
2979 StagingViewContentProvider stagedContentProvider = getContentProvider(
2980 viewer);
2981 int count = stagedContentProvider.getCount();
2982 updateAutoExpand(viewer, count);
2984 // Auto-expand is on, so don't change expanded items
2985 if (viewer.getAutoExpandLevel() == AbstractTreeViewer.ALL_LEVELS) {
2986 return;
2989 // No need to expand anything
2990 if (getPresentation() == Presentation.LIST)
2991 return;
2993 Set<IPath> paths = new HashSet<IPath>(additionalPaths);
2994 // Instead of just expanding the previous elements directly, also expand
2995 // all parent paths. This makes it work in case of "re-folding" of
2996 // compact tree.
2997 for (Object element : previous)
2998 if (element instanceof StagingFolderEntry)
2999 addPathAndParentPaths(((StagingFolderEntry) element).getPath(), paths);
3000 List<StagingFolderEntry> expand = new ArrayList<StagingFolderEntry>();
3002 calculateNodesToExpand(paths, stagedContentProvider.getElements(null),
3003 expand);
3004 viewer.setExpandedElements(expand.toArray());
3007 private void calculateNodesToExpand(Set<IPath> paths, Object[] elements,
3008 List<StagingFolderEntry> result) {
3009 if (elements == null)
3010 return;
3012 for (Object element : elements) {
3013 if (element instanceof StagingFolderEntry) {
3014 StagingFolderEntry folder = (StagingFolderEntry) element;
3015 if (paths.contains(folder.getPath())) {
3016 result.add(folder);
3017 // Only recurs if folder matched (i.e. don't try to expand
3018 // children of unexpanded parents)
3019 calculateNodesToExpand(paths, folder.getChildren(), result);
3025 private void clearCommitMessageToggles() {
3026 amendPreviousCommitAction.setChecked(false);
3027 addChangeIdAction.setChecked(false);
3028 signedOffByAction.setChecked(false);
3031 void updateCommitMessageComponent(boolean repositoryChanged, boolean indexDiffAvailable) {
3032 if (repositoryChanged)
3033 if (commitMessageComponent.isAmending()
3034 || userEnteredCommitMessage())
3035 saveCommitMessageComponentState();
3036 else
3037 deleteCommitMessageComponentState();
3038 if (!indexDiffAvailable)
3039 return; // only try to restore the stored repo commit message if
3040 // indexDiff is ready
3042 CommitHelper helper = new CommitHelper(currentRepository);
3043 CommitMessageComponentState oldState = null;
3044 if (repositoryChanged
3045 || commitMessageComponent.getRepository() != currentRepository) {
3046 oldState = loadCommitMessageComponentState();
3047 commitMessageComponent.setRepository(currentRepository);
3048 if (oldState == null)
3049 loadInitialState(helper);
3050 else
3051 loadExistingState(helper, oldState);
3052 } else { // repository did not change
3053 if (!commitMessageComponent.getHeadCommit().equals(
3054 helper.getPreviousCommit())) {
3055 if (!commitMessageComponent.isAmending()
3056 && userEnteredCommitMessage())
3057 addHeadChangedWarning(commitMessageComponent
3058 .getCommitMessage());
3059 else
3060 loadInitialState(helper);
3063 amendPreviousCommitAction.setChecked(commitMessageComponent
3064 .isAmending());
3065 amendPreviousCommitAction.setEnabled(helper.amendAllowed());
3066 updateMessage();
3069 private void loadExistingState(CommitHelper helper,
3070 CommitMessageComponentState oldState) {
3071 boolean headCommitChanged = !oldState.getHeadCommit().equals(
3072 getCommitId(helper.getPreviousCommit()));
3073 commitMessageComponent.enableListeners(false);
3074 commitMessageComponent.setAuthor(oldState.getAuthor());
3075 if (headCommitChanged)
3076 addHeadChangedWarning(oldState.getCommitMessage());
3077 else
3078 commitMessageComponent
3079 .setCommitMessage(oldState.getCommitMessage());
3080 commitMessageComponent.setCommitter(oldState.getCommitter());
3081 commitMessageComponent.setHeadCommit(getCommitId(helper
3082 .getPreviousCommit()));
3083 commitMessageComponent.setCommitAllowed(helper.canCommit());
3084 commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
3085 boolean amendAllowed = helper.amendAllowed();
3086 commitMessageComponent.setAmendAllowed(amendAllowed);
3087 if (!amendAllowed)
3088 commitMessageComponent.setAmending(false);
3089 else if (!headCommitChanged && oldState.getAmend())
3090 commitMessageComponent.setAmending(true);
3091 else
3092 commitMessageComponent.setAmending(false);
3093 commitMessageComponent.updateUIFromState();
3094 commitMessageComponent.updateSignedOffAndChangeIdButton();
3095 commitMessageComponent.enableListeners(true);
3098 private void addHeadChangedWarning(String commitMessage) {
3099 if (!commitMessage.startsWith(UIText.StagingView_headCommitChanged)) {
3100 String message = UIText.StagingView_headCommitChanged
3101 + Text.DELIMITER + Text.DELIMITER + commitMessage;
3102 commitMessageComponent.setCommitMessage(message);
3106 private void loadInitialState(CommitHelper helper) {
3107 commitMessageComponent.enableListeners(false);
3108 commitMessageComponent.resetState();
3109 commitMessageComponent.setAuthor(helper.getAuthor());
3110 commitMessageComponent.setCommitMessage(helper.getCommitMessage());
3111 commitMessageComponent.setCommitter(helper.getCommitter());
3112 commitMessageComponent.setHeadCommit(getCommitId(helper
3113 .getPreviousCommit()));
3114 commitMessageComponent.setCommitAllowed(helper.canCommit());
3115 commitMessageComponent.setCannotCommitMessage(helper.getCannotCommitMessage());
3116 commitMessageComponent.setAmendAllowed(helper.amendAllowed());
3117 commitMessageComponent.setAmending(false);
3118 // set the defaults for change id and signed off buttons.
3119 commitMessageComponent.setDefaults();
3120 commitMessageComponent.updateUI();
3121 commitMessageComponent.enableListeners(true);
3124 private boolean userEnteredCommitMessage() {
3125 if (commitMessageComponent.getRepository() == null)
3126 return false;
3127 String message = commitMessageComponent.getCommitMessage().replace(
3128 UIText.StagingView_headCommitChanged, ""); //$NON-NLS-1$
3129 if (message == null || message.trim().length() == 0)
3130 return false;
3132 String chIdLine = "Change-Id: I" + ObjectId.zeroId().name(); //$NON-NLS-1$
3133 Repository repo = currentRepository;
3134 if (repo != null && GerritUtil.getCreateChangeId(repo.getConfig())
3135 && commitMessageComponent.getCreateChangeId()) {
3136 if (message.trim().equals(chIdLine))
3137 return false;
3139 // change id was added automatically, but ther is more in the
3140 // message; strip the id, and check for the signed-off-by tag
3141 message = message.replace(chIdLine, ""); //$NON-NLS-1$
3144 if (org.eclipse.egit.ui.Activator.getDefault().getPreferenceStore()
3145 .getBoolean(UIPreferences.COMMIT_DIALOG_SIGNED_OFF_BY)
3146 && commitMessageComponent.isSignedOff()
3147 && message.trim().equals(
3148 Constants.SIGNED_OFF_BY_TAG
3149 + commitMessageComponent.getCommitter()))
3150 return false;
3152 return true;
3155 private ObjectId getCommitId(RevCommit commit) {
3156 if (commit == null)
3157 return ObjectId.zeroId();
3158 return commit.getId();
3161 private void saveCommitMessageComponentState() {
3162 final Repository repo = commitMessageComponent.getRepository();
3163 if (repo != null)
3164 CommitMessageComponentStateManager.persistState(repo,
3165 commitMessageComponent.getState());
3168 private void deleteCommitMessageComponentState() {
3169 if (commitMessageComponent.getRepository() != null)
3170 CommitMessageComponentStateManager
3171 .deleteState(commitMessageComponent.getRepository());
3174 private CommitMessageComponentState loadCommitMessageComponentState() {
3175 return CommitMessageComponentStateManager.loadState(currentRepository);
3178 private Collection<String> getStagedFileNames() {
3179 StagingViewContentProvider stagedContentProvider = getContentProvider(stagedViewer);
3180 StagingEntry[] entries = stagedContentProvider.getStagingEntries();
3181 List<String> files = new ArrayList<String>();
3182 for (StagingEntry entry : entries)
3183 files.add(entry.getPath());
3184 return files;
3187 private void commit(boolean pushUpstream) {
3188 if (!isCommitWithoutFilesAllowed()) {
3189 MessageDialog.openError(getSite().getShell(),
3190 UIText.StagingView_committingNotPossible,
3191 UIText.StagingView_noStagedFiles);
3192 return;
3194 if (!commitMessageComponent.checkCommitInfo())
3195 return;
3197 if (!UIUtils.saveAllEditors(currentRepository,
3198 UIText.StagingView_cancelCommitAfterSaving))
3199 return;
3201 String commitMessage = commitMessageComponent.getCommitMessage();
3202 CommitOperation commitOperation = null;
3203 try {
3204 commitOperation = new CommitOperation(currentRepository,
3205 commitMessageComponent.getAuthor(),
3206 commitMessageComponent.getCommitter(),
3207 commitMessage);
3208 } catch (CoreException e) {
3209 Activator.handleError(UIText.StagingView_commitFailed, e, true);
3210 return;
3212 if (amendPreviousCommitAction.isChecked())
3213 commitOperation.setAmending(true);
3214 final boolean gerritMode = addChangeIdAction.isChecked();
3215 commitOperation.setComputeChangeId(gerritMode);
3217 PushMode pushMode = null;
3218 if (pushUpstream) {
3219 pushMode = gerritMode ? PushMode.GERRIT : PushMode.UPSTREAM;
3221 final Job commitJob = new CommitJob(currentRepository, commitOperation)
3222 .setOpenCommitEditor(openNewCommitsAction.isChecked())
3223 .setPushUpstream(pushMode);
3225 // don't allow to do anything as long as commit is in progress
3226 enableAllWidgets(false);
3227 commitJob.addJobChangeListener(new JobChangeAdapter() {
3228 @Override
3229 public void done(IJobChangeEvent event) {
3230 asyncExec(new Runnable() {
3231 @Override
3232 public void run() {
3233 enableAllWidgets(true);
3234 if (commitJob.getResult().isOK()) {
3235 commitMessageText.setText(EMPTY_STRING);
3242 schedule(commitJob, true);
3244 CommitMessageHistory.saveCommitHistory(commitMessage);
3245 clearCommitMessageToggles();
3249 * Schedule given job in context of the current view. The view will indicate
3250 * progress as long as job is running.
3252 * @param job
3253 * non null
3254 * @param useRepositoryRule
3255 * true to use current repository rule for the given job, false
3256 * to not enforce any rule on the job
3258 private void schedule(Job job, boolean useRepositoryRule) {
3259 if (useRepositoryRule)
3260 job.setRule(RuleUtil.getRule(currentRepository));
3261 IWorkbenchSiteProgressService service = CommonUtils.getService(getSite(), IWorkbenchSiteProgressService.class);
3262 if (service != null)
3263 service.schedule(job, 0, true);
3264 else
3265 job.schedule();
3268 private boolean isCommitWithoutFilesAllowed() {
3269 if (stagedViewer.getTree().getItemCount() > 0)
3270 return true;
3272 if (amendPreviousCommitAction.isChecked())
3273 return true;
3275 return CommitHelper.isCommitWithoutFilesAllowed(currentRepository);
3278 @Override
3279 public void setFocus() {
3280 unstagedViewer.getControl().setFocus();
3283 @Override
3284 public void dispose() {
3285 super.dispose();
3287 ISelectionService srv = CommonUtils.getService(getSite(), ISelectionService.class);
3288 srv.removePostSelectionListener(selectionChangedListener);
3289 CommonUtils.getService(getSite(), IPartService.class)
3290 .removePartListener(partListener);
3292 if (cacheEntry != null) {
3293 cacheEntry.removeIndexDiffChangedListener(myIndexDiffListener);
3296 if (undoRedoActionGroup != null) {
3297 undoRedoActionGroup.dispose();
3300 InstanceScope.INSTANCE.getNode(
3301 org.eclipse.egit.core.Activator.getPluginId())
3302 .removePreferenceChangeListener(prefListener);
3303 if (refsChangedListener != null) {
3304 refsChangedListener.remove();
3307 getPreferenceStore().removePropertyChangeListener(uiPrefsListener);
3309 getDialogSettings().put(STORE_SORT_STATE, sortAction.isChecked());
3311 currentRepository = null;
3313 disposed = true;
3316 private boolean isDisposed() {
3317 return disposed;
3320 private static void syncExec(Runnable runnable) {
3321 PlatformUI.getWorkbench().getDisplay().syncExec(runnable);
3324 private void asyncExec(Runnable runnable) {
3325 PlatformUI.getWorkbench().getDisplay().asyncExec(runnable);
3329 * This comparator sorts the {@link StagingEntry}s alphabetically or groups
3330 * them by state. If grouped by state the entries in the same group are also
3331 * ordered alphabetically.
3333 private static class StagingEntryComparator extends ViewerComparator {
3335 private boolean alphabeticSort;
3337 private Comparator<String> comparator;
3339 private boolean fileNamesFirst;
3341 private StagingEntryComparator(boolean alphabeticSort,
3342 boolean fileNamesFirst) {
3343 this.alphabeticSort = alphabeticSort;
3344 this.setFileNamesFirst(fileNamesFirst);
3345 comparator = CommonUtils.STRING_ASCENDING_COMPARATOR;
3348 public boolean isFileNamesFirst() {
3349 return fileNamesFirst;
3352 public void setFileNamesFirst(boolean fileNamesFirst) {
3353 this.fileNamesFirst = fileNamesFirst;
3356 private void setAlphabeticSort(boolean sort) {
3357 this.alphabeticSort = sort;
3360 private boolean isAlphabeticSort() {
3361 return alphabeticSort;
3364 @Override
3365 public int category(Object element) {
3366 if (!isAlphabeticSort()) {
3367 StagingEntry stagingEntry = getStagingEntry(element);
3368 if (stagingEntry != null) {
3369 return getState(stagingEntry);
3372 return super.category(element);
3375 @Override
3376 public int compare(Viewer viewer, Object e1, Object e2) {
3377 int cat1 = category(e1);
3378 int cat2 = category(e2);
3380 if (cat1 != cat2) {
3381 return cat1 - cat2;
3384 String name1 = getStagingEntryText(e1);
3385 String name2 = getStagingEntryText(e2);
3387 return comparator.compare(name1, name2);
3390 private String getStagingEntryText(Object element) {
3391 String text = ""; //$NON-NLS-1$
3392 StagingEntry stagingEntry = getStagingEntry(element);
3393 if (stagingEntry != null) {
3394 if (isFileNamesFirst()) {
3395 text = stagingEntry.getName();
3396 } else {
3397 text = stagingEntry.getPath();
3400 return text;
3403 @Nullable
3404 private StagingEntry getStagingEntry(Object element) {
3405 StagingEntry entry = null;
3406 if (element instanceof StagingEntry) {
3407 entry = (StagingEntry) element;
3409 if (element instanceof TreeItem) {
3410 TreeItem item = (TreeItem) element;
3411 if (item.getData() instanceof StagingEntry) {
3412 entry = (StagingEntry) item.getData();
3415 return entry;
3418 private int getState(StagingEntry entry) {
3419 switch (entry.getState()) {
3420 case CONFLICTING:
3421 return 1;
3422 case MODIFIED:
3423 return 2;
3424 case MODIFIED_AND_ADDED:
3425 return 3;
3426 case MODIFIED_AND_CHANGED:
3427 return 4;
3428 case ADDED:
3429 return 5;
3430 case CHANGED:
3431 return 6;
3432 case MISSING:
3433 return 7;
3434 case MISSING_AND_CHANGED:
3435 return 8;
3436 case REMOVED:
3437 return 9;
3438 case UNTRACKED:
3439 return 10;
3440 default:
3441 return super.category(entry);