Reflog view: serialize asynchronous loading jobs
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / reflog / ReflogView.java
blob4c7ad0791d13644447875a09118dcf10e849503c
1 /*******************************************************************************
2 * Copyright (c) 2011, 2015 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 v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
8 * Contributors:
9 * Chris Aniszczyk <caniszczyk@gmail.com> - initial implementation
10 * EclipseSource - Filtered Viewer
11 * Robin Stocker <robin@nibor.org> - Show In support
12 * Tobias Baumann <tobbaumann@gmail.com> - Bug 475836
13 * Thomas Wolf <thomas.wolf@paranor.ch> - Bug 477248
14 *******************************************************************************/
15 package org.eclipse.egit.ui.internal.reflog;
17 import java.io.IOException;
19 import org.eclipse.core.resources.IResource;
20 import org.eclipse.egit.core.AdapterUtils;
21 import org.eclipse.egit.core.project.RepositoryMapping;
22 import org.eclipse.egit.ui.Activator;
23 import org.eclipse.egit.ui.UIPreferences;
24 import org.eclipse.egit.ui.UIUtils;
25 import org.eclipse.egit.ui.internal.CommonUtils;
26 import org.eclipse.egit.ui.internal.PreferenceBasedDateFormatter;
27 import org.eclipse.egit.ui.internal.UIIcons;
28 import org.eclipse.egit.ui.internal.UIText;
29 import org.eclipse.egit.ui.internal.actions.ResetMenu;
30 import org.eclipse.egit.ui.internal.commit.CommitEditor;
31 import org.eclipse.egit.ui.internal.commit.RepositoryCommit;
32 import org.eclipse.egit.ui.internal.reflog.ReflogViewContentProvider.ReflogInput;
33 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
34 import org.eclipse.jface.action.ControlContribution;
35 import org.eclipse.jface.action.IToolBarManager;
36 import org.eclipse.jface.action.MenuManager;
37 import org.eclipse.jface.action.Separator;
38 import org.eclipse.jface.layout.GridDataFactory;
39 import org.eclipse.jface.layout.GridLayoutFactory;
40 import org.eclipse.jface.layout.TreeColumnLayout;
41 import org.eclipse.jface.resource.JFaceResources;
42 import org.eclipse.jface.resource.LocalResourceManager;
43 import org.eclipse.jface.resource.ResourceManager;
44 import org.eclipse.jface.util.IPropertyChangeListener;
45 import org.eclipse.jface.util.OpenStrategy;
46 import org.eclipse.jface.util.PropertyChangeEvent;
47 import org.eclipse.jface.viewers.ColumnLabelProvider;
48 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
49 import org.eclipse.jface.viewers.ColumnWeightData;
50 import org.eclipse.jface.viewers.ISelection;
51 import org.eclipse.jface.viewers.ISelectionProvider;
52 import org.eclipse.jface.viewers.IStructuredSelection;
53 import org.eclipse.jface.viewers.StructuredSelection;
54 import org.eclipse.jface.viewers.TreeViewer;
55 import org.eclipse.jface.viewers.TreeViewerColumn;
56 import org.eclipse.jface.window.Window;
57 import org.eclipse.jgit.events.ListenerHandle;
58 import org.eclipse.jgit.events.RefsChangedEvent;
59 import org.eclipse.jgit.events.RefsChangedListener;
60 import org.eclipse.jgit.lib.Constants;
61 import org.eclipse.jgit.lib.ObjectId;
62 import org.eclipse.jgit.lib.PersonIdent;
63 import org.eclipse.jgit.lib.ReflogEntry;
64 import org.eclipse.jgit.lib.Repository;
65 import org.eclipse.jgit.lib.RepositoryState;
66 import org.eclipse.jgit.revwalk.RevCommit;
67 import org.eclipse.jgit.revwalk.RevWalk;
68 import org.eclipse.swt.SWT;
69 import org.eclipse.swt.events.DisposeEvent;
70 import org.eclipse.swt.events.DisposeListener;
71 import org.eclipse.swt.graphics.Image;
72 import org.eclipse.swt.layout.GridLayout;
73 import org.eclipse.swt.layout.RowLayout;
74 import org.eclipse.swt.widgets.Composite;
75 import org.eclipse.swt.widgets.Control;
76 import org.eclipse.swt.widgets.Tree;
77 import org.eclipse.swt.widgets.TreeColumn;
78 import org.eclipse.ui.IEditorInput;
79 import org.eclipse.ui.IEditorPart;
80 import org.eclipse.ui.IFileEditorInput;
81 import org.eclipse.ui.ISelectionListener;
82 import org.eclipse.ui.ISelectionService;
83 import org.eclipse.ui.IWorkbenchActionConstants;
84 import org.eclipse.ui.IWorkbenchPart;
85 import org.eclipse.ui.IWorkbenchPartSite;
86 import org.eclipse.ui.OpenAndLinkWithEditorHelper;
87 import org.eclipse.ui.PlatformUI;
88 import org.eclipse.ui.contexts.IContextService;
89 import org.eclipse.ui.dialogs.FilteredTree;
90 import org.eclipse.ui.dialogs.PatternFilter;
91 import org.eclipse.ui.forms.IFormColors;
92 import org.eclipse.ui.forms.events.HyperlinkAdapter;
93 import org.eclipse.ui.forms.events.HyperlinkEvent;
94 import org.eclipse.ui.forms.widgets.Form;
95 import org.eclipse.ui.forms.widgets.FormToolkit;
96 import org.eclipse.ui.forms.widgets.ImageHyperlink;
97 import org.eclipse.ui.model.IWorkbenchAdapter;
98 import org.eclipse.ui.part.IShowInTarget;
99 import org.eclipse.ui.part.ShowInContext;
100 import org.eclipse.ui.part.ViewPart;
103 * A view that shows reflog entries. The View includes a quick filter that
104 * searches on both the commit hashes and commit messages.
106 public class ReflogView extends ViewPart implements RefsChangedListener, IShowInTarget {
109 * View id
111 public static final String VIEW_ID = "org.eclipse.egit.ui.ReflogView"; //$NON-NLS-1$
114 * Context menu id
116 public static final String POPUP_MENU_ID = "org.eclipse.egit.ui.internal.reflogview.popup";//$NON-NLS-1$
118 private FormToolkit toolkit;
120 private Form form;
122 private TreeViewer refLogTableTreeViewer;
124 private ISelectionListener selectionChangedListener;
126 private ListenerHandle addRefsChangedListener;
128 private IPropertyChangeListener uiPrefsListener;
130 private PreferenceBasedDateFormatter dateFormatter;
132 @Override
133 public void createPartControl(Composite parent) {
134 dateFormatter = PreferenceBasedDateFormatter.create();
135 GridLayoutFactory.fillDefaults().applyTo(parent);
137 toolkit = new FormToolkit(parent.getDisplay());
138 parent.addDisposeListener(new DisposeListener() {
139 @Override
140 public void widgetDisposed(DisposeEvent e) {
141 toolkit.dispose();
145 form = toolkit.createForm(parent);
147 Image repoImage = UIIcons.REPOSITORY.createImage();
148 UIUtils.hookDisposal(form, repoImage);
149 final Image branchImage = UIIcons.CHANGESET.createImage();
150 UIUtils.hookDisposal(form, branchImage);
151 form.setImage(repoImage);
152 form.setText(UIText.StagingView_NoSelectionTitle);
153 GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
154 toolkit.decorateFormHeading(form);
155 GridLayoutFactory.fillDefaults().applyTo(form.getBody());
157 Composite tableComposite = toolkit.createComposite(form.getBody());
158 tableComposite.setLayout(new GridLayout());
160 GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
162 final TreeColumnLayout layout = new TreeColumnLayout();
164 FilteredTree filteredTree = new FilteredTree(tableComposite, SWT.NONE
165 | SWT.BORDER | SWT.FULL_SELECTION, new PatternFilter(), true) {
166 @Override
167 protected void createControl(Composite composite, int treeStyle) {
168 super.createControl(composite, treeStyle);
169 treeComposite.setLayout(layout);
173 toolkit.adapt(filteredTree);
174 refLogTableTreeViewer = filteredTree.getViewer();
175 refLogTableTreeViewer.getTree().setLinesVisible(true);
176 refLogTableTreeViewer.getTree().setHeaderVisible(true);
177 refLogTableTreeViewer
178 .setContentProvider(new ReflogViewContentProvider());
180 ColumnViewerToolTipSupport.enableFor(refLogTableTreeViewer);
182 TreeViewerColumn toColumn = createColumn(layout,
183 UIText.ReflogView_CommitColumnHeader, 10, SWT.LEFT);
184 toColumn.setLabelProvider(new ColumnLabelProvider() {
186 @Override
187 public String getText(Object element) {
188 if (element instanceof ReflogEntry) {
189 final ReflogEntry entry = (ReflogEntry) element;
190 return entry.getNewId().abbreviate(7).name();
192 return null;
195 @Override
196 public String getToolTipText(Object element) {
197 if (element instanceof ReflogEntry) {
198 final ReflogEntry entry = (ReflogEntry) element;
199 return entry.getNewId().name();
201 return null;
204 @Override
205 public Image getImage(Object element) {
206 if (element instanceof ReflogEntry) {
207 return branchImage;
209 return null;
214 TreeViewerColumn commitMessageColumn = createColumn(layout,
215 UIText.ReflogView_CommitMessageColumnHeader, 40, SWT.LEFT);
216 commitMessageColumn.setLabelProvider(new ColumnLabelProvider() {
218 @Override
219 public String getText(Object element) {
220 if (element instanceof ReflogEntry) {
221 final ReflogEntry entry = (ReflogEntry) element;
222 RevCommit c = getCommit(entry);
223 return c == null ? "" : c.getShortMessage(); //$NON-NLS-1$
224 } else if (element instanceof IWorkbenchAdapter) {
225 return ((IWorkbenchAdapter) element).getLabel(element);
227 return null;
230 private RevCommit getCommit(final ReflogEntry entry) {
231 try (RevWalk walk = new RevWalk(getRepository())) {
232 walk.setRetainBody(true);
233 return walk.parseCommit(entry.getNewId());
234 } catch (IOException ignored) {
235 // ignore
236 return null;
241 TreeViewerColumn dateColumn = createColumn(layout,
242 UIText.ReflogView_DateColumnHeader, 15, SWT.LEFT);
243 dateColumn.setLabelProvider(new ColumnLabelProvider() {
245 @Override
246 public String getText(Object element) {
247 if (element instanceof ReflogEntry) {
248 final ReflogEntry entry = (ReflogEntry) element;
249 final PersonIdent who = entry.getWho();
250 return dateFormatter.formatDate(who);
252 return null;
255 @Override
256 public Image getImage(Object element) {
257 return null;
262 TreeViewerColumn messageColumn = createColumn(layout,
263 UIText.ReflogView_MessageColumnHeader, 40, SWT.LEFT);
264 messageColumn.setLabelProvider(new ColumnLabelProvider() {
266 private ResourceManager resourceManager = new LocalResourceManager(
267 JFaceResources.getResources());
269 @Override
270 public String getText(Object element) {
271 if (element instanceof ReflogEntry) {
272 final ReflogEntry entry = (ReflogEntry) element;
273 return entry.getComment();
275 return null;
278 @Override
279 public Image getImage(Object element) {
280 if (!(element instanceof ReflogEntry)) {
281 return null;
283 String comment = ((ReflogEntry) element).getComment();
284 if (comment.startsWith("commit:") || comment.startsWith("commit (initial):")) //$NON-NLS-1$ //$NON-NLS-2$
285 return (Image) resourceManager.get(UIIcons.COMMIT);
286 if (comment.startsWith("commit (amend):")) //$NON-NLS-1$
287 return (Image) resourceManager.get(UIIcons.AMEND_COMMIT);
288 if (comment.startsWith("pull")) //$NON-NLS-1$
289 return (Image) resourceManager.get(UIIcons.PULL);
290 if (comment.startsWith("clone")) //$NON-NLS-1$
291 return (Image) resourceManager.get(UIIcons.CLONEGIT);
292 if (comment.startsWith("rebase")) //$NON-NLS-1$
293 return (Image) resourceManager.get(UIIcons.REBASE);
294 if (comment.startsWith("merge")) //$NON-NLS-1$
295 return (Image) resourceManager.get(UIIcons.MERGE);
296 if (comment.startsWith("fetch")) //$NON-NLS-1$
297 return (Image) resourceManager.get(UIIcons.FETCH);
298 if (comment.startsWith("branch")) //$NON-NLS-1$
299 return (Image) resourceManager.get(UIIcons.CREATE_BRANCH);
300 if (comment.startsWith("checkout")) //$NON-NLS-1$
301 return (Image) resourceManager.get(UIIcons.CHECKOUT);
302 return null;
305 @Override
306 public void dispose() {
307 resourceManager.dispose();
308 super.dispose();
312 new OpenAndLinkWithEditorHelper(refLogTableTreeViewer) {
313 @Override
314 protected void linkToEditor(ISelection selection) {
315 // Not supported
318 @Override
319 protected void open(ISelection sel, boolean activate) {
320 handleOpen(sel, OpenStrategy.activateOnOpen());
322 @Override
323 protected void activate(ISelection selection) {
324 handleOpen(selection, true);
326 private void handleOpen(ISelection selection, boolean activateOnOpen) {
327 if (selection instanceof IStructuredSelection)
328 if (selection.isEmpty())
329 return;
330 Repository repo = getRepository();
331 if (repo == null)
332 return;
333 try (RevWalk walk = new RevWalk(repo)) {
334 for (Object element : ((IStructuredSelection)selection).toArray()) {
335 ReflogEntry entry = (ReflogEntry) element;
336 ObjectId id = entry.getNewId();
337 if (id == null || id.equals(ObjectId.zeroId()))
338 id = entry.getOldId();
339 if (id != null && !id.equals(ObjectId.zeroId()))
340 CommitEditor.openQuiet(new RepositoryCommit(repo,
341 walk.parseCommit(id)), activateOnOpen);
343 } catch (IOException e) {
344 Activator.logError(UIText.ReflogView_ErrorOnOpenCommit, e);
349 uiPrefsListener = new IPropertyChangeListener() {
350 @Override
351 public void propertyChange(PropertyChangeEvent event) {
352 String property = event.getProperty();
353 if (UIPreferences.DATE_FORMAT.equals(property)
354 || UIPreferences.DATE_FORMAT_CHOICE.equals(property)) {
355 dateFormatter = PreferenceBasedDateFormatter.create();
356 refLogTableTreeViewer.refresh();
360 Activator.getDefault().getPreferenceStore()
361 .addPropertyChangeListener(uiPrefsListener);
362 selectionChangedListener = new ISelectionListener() {
363 @Override
364 public void selectionChanged(IWorkbenchPart part,
365 ISelection selection) {
366 if (part instanceof IEditorPart) {
367 IEditorInput input = ((IEditorPart) part).getEditorInput();
368 if (input instanceof IFileEditorInput)
369 reactOnSelection(new StructuredSelection(
370 ((IFileEditorInput) input).getFile()));
371 } else
372 reactOnSelection(selection);
376 IWorkbenchPartSite site = getSite();
377 ISelectionService service = CommonUtils.getService(site, ISelectionService.class);
378 service.addPostSelectionListener(selectionChangedListener);
380 // Use current selection to populate reflog view
381 UIUtils.notifySelectionChangedWithCurrentSelection(
382 selectionChangedListener, site);
384 site.setSelectionProvider(refLogTableTreeViewer);
386 addRefsChangedListener = Repository.getGlobalListenerList()
387 .addRefsChangedListener(this);
389 // register context menu
390 MenuManager menuManager = new MenuManager();
391 menuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
392 Tree tree = refLogTableTreeViewer.getTree();
393 tree.setMenu(menuManager.createContextMenu(tree));
395 MenuManager resetManager = ResetMenu.createMenu(getSite());
396 menuManager.add(resetManager);
398 getSite().registerContextMenu(POPUP_MENU_ID, menuManager, refLogTableTreeViewer);
401 @Override
402 public void setFocus() {
403 refLogTableTreeViewer.getControl().setFocus();
404 activateContextService();
407 private void activateContextService() {
408 IContextService contextService = CommonUtils.getService(getSite(), IContextService.class);
409 if (contextService != null)
410 contextService.activateContext(VIEW_ID);
414 @Override
415 public void dispose() {
416 super.dispose();
417 ISelectionService service = CommonUtils.getService(getSite(), ISelectionService.class);
418 service.removePostSelectionListener(selectionChangedListener);
419 if (addRefsChangedListener != null) {
420 addRefsChangedListener.remove();
422 Activator.getDefault().getPreferenceStore()
423 .removePropertyChangeListener(uiPrefsListener);
426 private void reactOnSelection(ISelection selection) {
427 if (!(selection instanceof IStructuredSelection)) {
428 return;
430 IStructuredSelection ssel = (IStructuredSelection) selection;
431 if (ssel.size() != 1) {
432 return;
434 Repository selectedRepo = null;
435 Object first = ssel.getFirstElement();
436 IResource adapted = AdapterUtils.adaptToAnyResource(first);
437 if (adapted != null) {
438 RepositoryMapping mapping = RepositoryMapping.getMapping(adapted);
439 if (mapping != null) {
440 selectedRepo = mapping.getRepository();
443 if (selectedRepo == null) {
444 selectedRepo = AdapterUtils.adapt(first, Repository.class);
446 if (selectedRepo == null) {
447 return;
450 // Only update when different repository is selected
451 Repository currentRepo = getRepository();
452 if (currentRepo == null
453 || !selectedRepo.getDirectory().equals(
454 currentRepo.getDirectory())) {
455 showReflogFor(selectedRepo);
459 private void updateRefLink(final String name) {
460 IToolBarManager toolbar = form.getToolBarManager();
461 toolbar.removeAll();
463 ControlContribution refLabelControl = new ControlContribution(
464 "refLabel") { //$NON-NLS-1$
465 @Override
466 protected Control createControl(Composite cParent) {
467 Composite composite = toolkit.createComposite(cParent);
468 composite.setLayout(new RowLayout());
469 composite.setBackground(null);
471 final ImageHyperlink refLink = new ImageHyperlink(composite,
472 SWT.NONE);
473 Image image = UIIcons.BRANCH.createImage();
474 UIUtils.hookDisposal(refLink, image);
475 refLink.setImage(image);
476 refLink.setFont(JFaceResources.getBannerFont());
477 refLink.setForeground(toolkit.getColors().getColor(
478 IFormColors.TITLE));
479 refLink.addHyperlinkListener(new HyperlinkAdapter() {
480 @Override
481 public void linkActivated(HyperlinkEvent event) {
482 Repository repository = getRepository();
483 if (repository == null)
484 return;
485 RefSelectionDialog dialog = new RefSelectionDialog(
486 refLink.getShell(), repository);
487 if (Window.OK == dialog.open())
488 showReflogFor(repository, dialog.getRefName());
491 refLink.setText(Repository.shortenRefName(name));
493 return composite;
496 toolbar.add(refLabelControl);
497 toolbar.update(true);
501 * @return the repository the view is showing the reflog for
503 public Repository getRepository() {
504 Object input = refLogTableTreeViewer.getInput();
505 if (input instanceof ReflogInput)
506 return ((ReflogInput) input).getRepository();
507 return null;
510 @Override
511 public boolean show(ShowInContext context) {
512 ISelection selection = context.getSelection();
513 if (selection instanceof IStructuredSelection) {
514 IStructuredSelection structuredSelection = (IStructuredSelection) selection;
515 for (Object element : structuredSelection.toList()) {
516 if (element instanceof RepositoryTreeNode) {
517 RepositoryTreeNode node = (RepositoryTreeNode) element;
518 showReflogFor(node.getRepository());
519 return true;
523 return false;
527 * Defines the repository for the reflog to show.
529 * @param repository
531 private void showReflogFor(Repository repository) {
532 showReflogFor(repository, Constants.HEAD);
536 * Defines the repository for the reflog to show.
538 * @param repository
539 * @param ref
541 private void showReflogFor(Repository repository, String ref) {
542 if (repository != null && ref != null) {
543 refLogTableTreeViewer.setInput(new ReflogInput(repository, ref));
544 updateRefLink(ref);
545 form.setText(getRepositoryName(repository));
549 private TreeViewerColumn createColumn(
550 final TreeColumnLayout columnLayout, final String text,
551 final int weight, final int style) {
552 final TreeViewerColumn viewerColumn = new TreeViewerColumn(
553 refLogTableTreeViewer, style);
554 final TreeColumn column = viewerColumn.getColumn();
555 column.setText(text);
556 columnLayout.setColumnData(column, new ColumnWeightData(weight, 10));
557 return viewerColumn;
560 private static String getRepositoryName(Repository repository) {
561 String repoName = Activator.getDefault().getRepositoryUtil()
562 .getRepositoryName(repository);
563 RepositoryState state = repository.getRepositoryState();
564 if (state != RepositoryState.SAFE)
565 return repoName + '|' + state.getDescription();
566 else
567 return repoName;
570 @Override
571 public void onRefsChanged(RefsChangedEvent event) {
572 PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
573 @Override
574 public void run() {
575 Object currentInput = refLogTableTreeViewer.getInput();
576 if (currentInput instanceof ReflogInput) {
577 ReflogInput oldInput = (ReflogInput) currentInput;
578 refLogTableTreeViewer.setInput(new ReflogInput(
579 oldInput.getRepository(), oldInput.getRef()));
586 * @return selection provider
588 public ISelectionProvider getSelectionProvider() {
589 return refLogTableTreeViewer;