Have icon for "reset" entry in reflog
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / reflog / ReflogView.java
blob738c5d01f4fe3708a4d42ea8ed70cc9a9dcd019f
1 /*******************************************************************************
2 * Copyright (c) 2011, 2017 Chris Aniszczyk <caniszczyk@gmail.com> and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
10 * Contributors:
11 * Chris Aniszczyk <caniszczyk@gmail.com> - initial implementation
12 * EclipseSource - Filtered Viewer
13 * Robin Stocker <robin@nibor.org> - Show In support
14 * Tobias Baumann <tobbaumann@gmail.com> - Bug 475836
15 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 477248, 518607
16 *******************************************************************************/
17 package org.eclipse.egit.ui.internal.reflog;
19 import java.io.IOException;
21 import org.eclipse.core.resources.IResource;
22 import org.eclipse.egit.core.AdapterUtils;
23 import org.eclipse.egit.core.project.RepositoryMapping;
24 import org.eclipse.egit.ui.Activator;
25 import org.eclipse.egit.ui.UIPreferences;
26 import org.eclipse.egit.ui.UIUtils;
27 import org.eclipse.egit.ui.internal.CommonUtils;
28 import org.eclipse.egit.ui.internal.PreferenceBasedDateFormatter;
29 import org.eclipse.egit.ui.internal.TreeColumnPatternFilter;
30 import org.eclipse.egit.ui.internal.UIIcons;
31 import org.eclipse.egit.ui.internal.UIText;
32 import org.eclipse.egit.ui.internal.actions.ResetMenu;
33 import org.eclipse.egit.ui.internal.commit.CommitEditor;
34 import org.eclipse.egit.ui.internal.commit.RepositoryCommit;
35 import org.eclipse.egit.ui.internal.components.RepositoryMenuUtil.RepositoryToolbarAction;
36 import org.eclipse.egit.ui.internal.reflog.ReflogViewContentProvider.ReflogInput;
37 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
38 import org.eclipse.egit.ui.internal.selection.RepositorySelectionProvider;
39 import org.eclipse.jface.action.ControlContribution;
40 import org.eclipse.jface.action.IToolBarManager;
41 import org.eclipse.jface.action.MenuManager;
42 import org.eclipse.jface.action.Separator;
43 import org.eclipse.jface.layout.GridDataFactory;
44 import org.eclipse.jface.layout.GridLayoutFactory;
45 import org.eclipse.jface.layout.TreeColumnLayout;
46 import org.eclipse.jface.resource.JFaceResources;
47 import org.eclipse.jface.resource.LocalResourceManager;
48 import org.eclipse.jface.resource.ResourceManager;
49 import org.eclipse.jface.util.IPropertyChangeListener;
50 import org.eclipse.jface.util.OpenStrategy;
51 import org.eclipse.jface.util.PropertyChangeEvent;
52 import org.eclipse.jface.viewers.ColumnLabelProvider;
53 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
54 import org.eclipse.jface.viewers.ColumnWeightData;
55 import org.eclipse.jface.viewers.ISelection;
56 import org.eclipse.jface.viewers.IStructuredSelection;
57 import org.eclipse.jface.viewers.StructuredSelection;
58 import org.eclipse.jface.viewers.TreeViewer;
59 import org.eclipse.jface.viewers.TreeViewerColumn;
60 import org.eclipse.jface.window.Window;
61 import org.eclipse.jgit.events.ListenerHandle;
62 import org.eclipse.jgit.events.RefsChangedEvent;
63 import org.eclipse.jgit.events.RefsChangedListener;
64 import org.eclipse.jgit.lib.Constants;
65 import org.eclipse.jgit.lib.ObjectId;
66 import org.eclipse.jgit.lib.PersonIdent;
67 import org.eclipse.jgit.lib.ReflogEntry;
68 import org.eclipse.jgit.lib.Repository;
69 import org.eclipse.jgit.lib.RepositoryState;
70 import org.eclipse.jgit.revwalk.RevWalk;
71 import org.eclipse.swt.SWT;
72 import org.eclipse.swt.events.DisposeEvent;
73 import org.eclipse.swt.events.DisposeListener;
74 import org.eclipse.swt.graphics.Image;
75 import org.eclipse.swt.layout.GridLayout;
76 import org.eclipse.swt.layout.RowLayout;
77 import org.eclipse.swt.widgets.Composite;
78 import org.eclipse.swt.widgets.Control;
79 import org.eclipse.swt.widgets.Tree;
80 import org.eclipse.swt.widgets.TreeColumn;
81 import org.eclipse.ui.IEditorInput;
82 import org.eclipse.ui.IEditorPart;
83 import org.eclipse.ui.IFileEditorInput;
84 import org.eclipse.ui.ISelectionListener;
85 import org.eclipse.ui.ISelectionService;
86 import org.eclipse.ui.IWorkbenchActionConstants;
87 import org.eclipse.ui.IWorkbenchPart;
88 import org.eclipse.ui.IWorkbenchPartSite;
89 import org.eclipse.ui.OpenAndLinkWithEditorHelper;
90 import org.eclipse.ui.PlatformUI;
91 import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
92 import org.eclipse.ui.contexts.IContextService;
93 import org.eclipse.ui.dialogs.FilteredTree;
94 import org.eclipse.ui.forms.IFormColors;
95 import org.eclipse.ui.forms.events.HyperlinkAdapter;
96 import org.eclipse.ui.forms.events.HyperlinkEvent;
97 import org.eclipse.ui.forms.widgets.Form;
98 import org.eclipse.ui.forms.widgets.FormToolkit;
99 import org.eclipse.ui.forms.widgets.ImageHyperlink;
100 import org.eclipse.ui.model.IWorkbenchAdapter;
101 import org.eclipse.ui.part.IShowInTarget;
102 import org.eclipse.ui.part.ShowInContext;
103 import org.eclipse.ui.part.ViewPart;
106 * A view that shows reflog entries. The View includes a quick filter that
107 * searches on both the commit hashes and commit messages.
109 public class ReflogView extends ViewPart implements RefsChangedListener, IShowInTarget {
112 * View id
114 public static final String VIEW_ID = "org.eclipse.egit.ui.ReflogView"; //$NON-NLS-1$
117 * Context menu id
119 public static final String POPUP_MENU_ID = "org.eclipse.egit.ui.internal.reflogview.popup";//$NON-NLS-1$
121 private FormToolkit toolkit;
123 private Form form;
125 private TreeViewer refLogTableTreeViewer;
127 private ISelectionListener selectionChangedListener;
129 private ListenerHandle addRefsChangedListener;
131 private IPropertyChangeListener uiPrefsListener;
133 private PreferenceBasedDateFormatter dateFormatter;
135 private IWorkbenchAction switchRepositoriesAction;
137 @SuppressWarnings("unused")
138 @Override
139 public void createPartControl(Composite parent) {
140 dateFormatter = PreferenceBasedDateFormatter.create();
141 GridLayoutFactory.fillDefaults().applyTo(parent);
143 toolkit = new FormToolkit(parent.getDisplay());
144 parent.addDisposeListener(new DisposeListener() {
145 @Override
146 public void widgetDisposed(DisposeEvent e) {
147 toolkit.dispose();
151 form = toolkit.createForm(parent);
153 Image repoImage = UIIcons.REPOSITORY.createImage();
154 UIUtils.hookDisposal(form, repoImage);
155 Image commitImage = UIIcons.CHANGESET.createImage();
156 UIUtils.hookDisposal(form, commitImage);
157 form.setImage(repoImage);
158 form.setText(UIText.StagingView_NoSelectionTitle);
159 GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
160 toolkit.decorateFormHeading(form);
161 GridLayoutFactory.fillDefaults().applyTo(form.getBody());
163 Composite tableComposite = toolkit.createComposite(form.getBody());
164 tableComposite.setLayout(new GridLayout());
166 GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
168 final TreeColumnLayout layout = new TreeColumnLayout();
170 FilteredTree filteredTree = new FilteredTree(tableComposite, SWT.NONE
171 | SWT.BORDER | SWT.FULL_SELECTION,
172 new TreeColumnPatternFilter(), true) {
173 @Override
174 protected void createControl(Composite composite, int treeStyle) {
175 super.createControl(composite, treeStyle);
176 treeComposite.setLayout(layout);
180 toolkit.adapt(filteredTree);
181 refLogTableTreeViewer = filteredTree.getViewer();
182 refLogTableTreeViewer.getTree().setLinesVisible(true);
183 refLogTableTreeViewer.getTree().setHeaderVisible(true);
184 refLogTableTreeViewer
185 .setContentProvider(new ReflogViewContentProvider());
187 ColumnViewerToolTipSupport.enableFor(refLogTableTreeViewer);
189 TreeViewerColumn toColumn = createColumn(layout,
190 UIText.ReflogView_CommitColumnHeader, 10, SWT.LEFT);
191 toColumn.setLabelProvider(new ColumnLabelProvider() {
193 @Override
194 public String getText(Object element) {
195 if (element instanceof ReflogEntry) {
196 final ReflogEntry entry = (ReflogEntry) element;
197 return entry.getNewId().abbreviate(7).name();
199 return null;
202 @Override
203 public String getToolTipText(Object element) {
204 if (element instanceof ReflogEntry) {
205 final ReflogEntry entry = (ReflogEntry) element;
206 return entry.getNewId().name();
208 return null;
211 @Override
212 public Image getImage(Object element) {
213 if (element instanceof ReflogEntry) {
214 return commitImage;
216 return null;
221 TreeViewerColumn commitMessageColumn = createColumn(layout,
222 UIText.ReflogView_CommitMessageColumnHeader, 40, SWT.LEFT);
223 commitMessageColumn.setLabelProvider(new ColumnLabelProvider() {
225 @Override
226 public String getText(Object element) {
227 if (element instanceof ReflogItem) {
228 ReflogItem entry = (ReflogItem) element;
229 String c = entry.getCommitMessage();
230 return c == null ? "" : c; //$NON-NLS-1$
231 } else if (element instanceof IWorkbenchAdapter) {
232 return ((IWorkbenchAdapter) element).getLabel(element);
234 return null;
238 TreeViewerColumn dateColumn = createColumn(layout,
239 UIText.ReflogView_DateColumnHeader, 15, SWT.LEFT);
240 dateColumn.setLabelProvider(new ColumnLabelProvider() {
242 @Override
243 public String getText(Object element) {
244 if (element instanceof ReflogEntry) {
245 final ReflogEntry entry = (ReflogEntry) element;
246 final PersonIdent who = entry.getWho();
247 return dateFormatter.formatDate(who);
249 return null;
254 TreeViewerColumn messageColumn = createColumn(layout,
255 UIText.ReflogView_MessageColumnHeader, 40, SWT.LEFT);
256 messageColumn.setLabelProvider(new ColumnLabelProvider() {
258 private ResourceManager resourceManager = new LocalResourceManager(
259 JFaceResources.getResources());
261 @Override
262 public String getText(Object element) {
263 if (element instanceof ReflogEntry) {
264 final ReflogEntry entry = (ReflogEntry) element;
265 return entry.getComment();
267 return null;
270 @Override
271 public Image getImage(Object element) {
272 if (!(element instanceof ReflogEntry)) {
273 return null;
275 String comment = ((ReflogEntry) element).getComment();
276 if (comment.startsWith("commit:") || comment.startsWith("commit (initial):")) //$NON-NLS-1$ //$NON-NLS-2$
277 return (Image) resourceManager.get(UIIcons.COMMIT);
278 if (comment.startsWith("commit (amend):")) //$NON-NLS-1$
279 return (Image) resourceManager.get(UIIcons.AMEND_COMMIT);
280 if (comment.startsWith("pull")) //$NON-NLS-1$
281 return (Image) resourceManager.get(UIIcons.PULL);
282 if (comment.startsWith("clone")) //$NON-NLS-1$
283 return (Image) resourceManager.get(UIIcons.CLONEGIT);
284 if (comment.startsWith("rebase")) //$NON-NLS-1$
285 return (Image) resourceManager.get(UIIcons.REBASE);
286 if (comment.startsWith("merge")) //$NON-NLS-1$
287 return (Image) resourceManager.get(UIIcons.MERGE);
288 if (comment.startsWith("fetch")) //$NON-NLS-1$
289 return (Image) resourceManager.get(UIIcons.FETCH);
290 if (comment.startsWith("branch")) //$NON-NLS-1$
291 return (Image) resourceManager.get(UIIcons.CREATE_BRANCH);
292 if (comment.startsWith("checkout")) //$NON-NLS-1$
293 return (Image) resourceManager.get(UIIcons.CHECKOUT);
294 if (comment.startsWith("cherry-pick")) //$NON-NLS-1$
295 return (Image) resourceManager.get(UIIcons.CHERRY_PICK);
296 if (comment.startsWith("Branch: renamed ")) //$NON-NLS-1$
297 return (Image) resourceManager.get(UIIcons.EDITCONFIG);
298 if (comment.startsWith("reset")) //$NON-NLS-1$
299 return (Image) resourceManager.get(UIIcons.RESET);
300 return null;
303 @Override
304 public void dispose() {
305 resourceManager.dispose();
306 super.dispose();
310 new OpenAndLinkWithEditorHelper(refLogTableTreeViewer) {
311 @Override
312 protected void linkToEditor(ISelection selection) {
313 // Not supported
316 @Override
317 protected void open(ISelection sel, boolean activate) {
318 handleOpen(sel, OpenStrategy.activateOnOpen());
320 @Override
321 protected void activate(ISelection selection) {
322 handleOpen(selection, true);
324 private void handleOpen(ISelection selection, boolean activateOnOpen) {
325 if (selection instanceof IStructuredSelection)
326 if (selection.isEmpty())
327 return;
328 Repository repo = getRepository();
329 if (repo == null)
330 return;
331 try (RevWalk walk = new RevWalk(repo)) {
332 for (Object element : ((IStructuredSelection)selection).toArray()) {
333 ReflogEntry entry = (ReflogEntry) element;
334 ObjectId id = entry.getNewId();
335 if (id == null || id.equals(ObjectId.zeroId()))
336 id = entry.getOldId();
337 if (id != null && !id.equals(ObjectId.zeroId()))
338 CommitEditor.openQuiet(new RepositoryCommit(repo,
339 walk.parseCommit(id)), activateOnOpen);
341 } catch (IOException e) {
342 Activator.logError(UIText.ReflogView_ErrorOnOpenCommit, e);
347 uiPrefsListener = new IPropertyChangeListener() {
348 @Override
349 public void propertyChange(PropertyChangeEvent event) {
350 String property = event.getProperty();
351 if (UIPreferences.DATE_FORMAT.equals(property)
352 || UIPreferences.DATE_FORMAT_CHOICE.equals(property)) {
353 dateFormatter = PreferenceBasedDateFormatter.create();
354 refLogTableTreeViewer.refresh();
358 Activator.getDefault().getPreferenceStore()
359 .addPropertyChangeListener(uiPrefsListener);
360 selectionChangedListener = new ISelectionListener() {
361 @Override
362 public void selectionChanged(IWorkbenchPart part,
363 ISelection selection) {
364 if (part instanceof IEditorPart) {
365 IEditorInput input = ((IEditorPart) part).getEditorInput();
366 if (input instanceof IFileEditorInput)
367 reactOnSelection(new StructuredSelection(
368 ((IFileEditorInput) input).getFile()));
369 } else
370 reactOnSelection(selection);
374 IWorkbenchPartSite site = getSite();
375 ISelectionService service = CommonUtils.getService(site, ISelectionService.class);
376 service.addPostSelectionListener(selectionChangedListener);
378 // Use current selection to populate reflog view
379 UIUtils.notifySelectionChangedWithCurrentSelection(
380 selectionChangedListener, site);
382 site.setSelectionProvider(new RepositorySelectionProvider(
383 refLogTableTreeViewer, () -> getRepository()));
385 addRefsChangedListener = Repository.getGlobalListenerList()
386 .addRefsChangedListener(this);
388 // Toolbar
389 IToolBarManager toolbar = getViewSite().getActionBars()
390 .getToolBarManager();
391 switchRepositoriesAction = new RepositoryToolbarAction(false,
392 () -> getRepository(),
393 repo -> reactOnSelection(new StructuredSelection(repo)));
394 toolbar.add(switchRepositoriesAction);
395 getViewSite().getActionBars().updateActionBars();
396 // register context menu
397 MenuManager menuManager = new MenuManager();
398 menuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
399 Tree tree = refLogTableTreeViewer.getTree();
400 tree.setMenu(menuManager.createContextMenu(tree));
402 MenuManager resetManager = ResetMenu.createMenu(getSite());
403 menuManager.add(resetManager);
405 getSite().registerContextMenu(POPUP_MENU_ID, menuManager, refLogTableTreeViewer);
408 @Override
409 public void setFocus() {
410 refLogTableTreeViewer.getControl().setFocus();
411 activateContextService();
414 private void activateContextService() {
415 IContextService contextService = CommonUtils.getService(getSite(), IContextService.class);
416 if (contextService != null)
417 contextService.activateContext(VIEW_ID);
421 @Override
422 public void dispose() {
423 super.dispose();
424 ISelectionService service = CommonUtils.getService(getSite(), ISelectionService.class);
425 service.removePostSelectionListener(selectionChangedListener);
426 if (addRefsChangedListener != null) {
427 addRefsChangedListener.remove();
429 Activator.getDefault().getPreferenceStore()
430 .removePropertyChangeListener(uiPrefsListener);
431 if (switchRepositoriesAction != null) {
432 switchRepositoriesAction.dispose();
433 switchRepositoriesAction = null;
437 private void reactOnSelection(ISelection selection) {
438 if (!(selection instanceof IStructuredSelection)) {
439 return;
441 IStructuredSelection ssel = (IStructuredSelection) selection;
442 if (ssel.size() != 1) {
443 return;
445 Repository selectedRepo = null;
446 Object first = ssel.getFirstElement();
447 IResource adapted = AdapterUtils.adaptToAnyResource(first);
448 if (adapted != null) {
449 RepositoryMapping mapping = RepositoryMapping.getMapping(adapted);
450 if (mapping != null) {
451 selectedRepo = mapping.getRepository();
454 if (selectedRepo == null) {
455 selectedRepo = AdapterUtils.adapt(first, Repository.class);
457 if (selectedRepo == null) {
458 return;
461 // Only update when different repository is selected
462 Repository currentRepo = getRepository();
463 if (currentRepo == null
464 || !selectedRepo.getDirectory().equals(
465 currentRepo.getDirectory())) {
466 showReflogFor(selectedRepo);
470 private void updateRefLink(final String name) {
471 IToolBarManager toolbar = form.getToolBarManager();
472 toolbar.removeAll();
474 ControlContribution refLabelControl = new ControlContribution(
475 "refLabel") { //$NON-NLS-1$
476 @Override
477 protected Control createControl(Composite cParent) {
478 Composite composite = toolkit.createComposite(cParent);
479 composite.setLayout(new RowLayout());
480 composite.setBackground(null);
482 final ImageHyperlink refLink = new ImageHyperlink(composite,
483 SWT.NONE);
484 Image image = UIIcons.BRANCH.createImage();
485 UIUtils.hookDisposal(refLink, image);
486 refLink.setImage(image);
487 refLink.setFont(JFaceResources.getBannerFont());
488 refLink.setForeground(toolkit.getColors().getColor(
489 IFormColors.TITLE));
490 refLink.addHyperlinkListener(new HyperlinkAdapter() {
491 @Override
492 public void linkActivated(HyperlinkEvent event) {
493 Repository repository = getRepository();
494 if (repository == null)
495 return;
496 RefSelectionDialog dialog = new RefSelectionDialog(
497 refLink.getShell(), repository);
498 if (Window.OK == dialog.open())
499 showReflogFor(repository, dialog.getRefName());
502 refLink.setText(Repository.shortenRefName(name));
504 return composite;
507 toolbar.add(refLabelControl);
508 toolbar.update(true);
512 * @return the repository the view is showing the reflog for
514 public Repository getRepository() {
515 Object input = refLogTableTreeViewer.getInput();
516 if (input instanceof ReflogInput)
517 return ((ReflogInput) input).getRepository();
518 return null;
521 @Override
522 public boolean show(ShowInContext context) {
523 ISelection selection = context.getSelection();
524 if (selection instanceof IStructuredSelection) {
525 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
526 for (Object element : structuredSelection.toList()) {
527 if (element instanceof RepositoryTreeNode) {
528 RepositoryTreeNode node = (RepositoryTreeNode) element;
529 showReflogFor(node.getRepository());
530 return true;
534 return false;
538 * Defines the repository for the reflog to show.
540 * @param repository
542 private void showReflogFor(Repository repository) {
543 showReflogFor(repository, Constants.HEAD);
547 * Defines the repository for the reflog to show.
549 * @param repository
550 * @param ref
552 private void showReflogFor(Repository repository, String ref) {
553 if (repository != null && ref != null) {
554 refLogTableTreeViewer.setInput(new ReflogInput(repository, ref));
555 updateRefLink(ref);
556 form.setText(getRepositoryName(repository));
560 private TreeViewerColumn createColumn(
561 final TreeColumnLayout columnLayout, final String text,
562 final int weight, final int style) {
563 final TreeViewerColumn viewerColumn = new TreeViewerColumn(
564 refLogTableTreeViewer, style);
565 final TreeColumn column = viewerColumn.getColumn();
566 column.setText(text);
567 columnLayout.setColumnData(column, new ColumnWeightData(weight, 10));
568 return viewerColumn;
571 private static String getRepositoryName(Repository repository) {
572 String repoName = Activator.getDefault().getRepositoryUtil()
573 .getRepositoryName(repository);
574 RepositoryState state = repository.getRepositoryState();
575 if (state != RepositoryState.SAFE)
576 return repoName + '|' + state.getDescription();
577 else
578 return repoName;
581 @Override
582 public void onRefsChanged(RefsChangedEvent event) {
583 PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
584 Object currentInput = refLogTableTreeViewer.getInput();
585 if (currentInput instanceof ReflogInput) {
586 ReflogInput oldInput = (ReflogInput) currentInput;
587 refLogTableTreeViewer.setInput(new ReflogInput(
588 oldInput.getRepository(), oldInput.getRef()));