/*******************************************************************************
* Copyright (c) 2011, 2015 Chris Aniszczyk <caniszczyk@gmail.com> and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Chris Aniszczyk <caniszczyk@gmail.com> - initial implementation
* EclipseSource - Filtered Viewer
* Robin Stocker <robin@nibor.org> - Show In support
* Tobias Baumann <tobbaumann@gmail.com> - Bug 475836
* Thomas Wolf <thomas.wolf@paranor.ch> - Bug 477248
*******************************************************************************/
package org.eclipse.egit.ui.internal.reflog;
import java.io.IOException;
import org.eclipse.core.resources.IResource;
import org.eclipse.egit.core.AdapterUtils;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.PreferenceBasedDateFormatter;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.actions.ResetMenu;
import org.eclipse.egit.ui.internal.commit.CommitEditor;
import org.eclipse.egit.ui.internal.commit.RepositoryCommit;
import org.eclipse.egit.ui.internal.reflog.ReflogViewContentProvider.ReflogInput;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.window.Window;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.events.RefsChangedListener;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.OpenAndLinkWithEditorHelper;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.Form;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
/**
* A view that shows reflog entries. The View includes a quick filter that
* searches on both the commit hashes and commit messages.
*/
public class ReflogView extends ViewPart implements RefsChangedListener, IShowInTarget {
/**
* View id
*/
public static final String VIEW_ID = "org.eclipse.egit.ui.ReflogView"; //$NON-NLS-1$
/**
* Context menu id
*/
public static final String POPUP_MENU_ID = "org.eclipse.egit.ui.internal.reflogview.popup";//$NON-NLS-1$
private FormToolkit toolkit;
private Form form;
private TreeViewer refLogTableTreeViewer;
private ISelectionListener selectionChangedListener;
private ListenerHandle addRefsChangedListener;
private IPropertyChangeListener uiPrefsListener;
private PreferenceBasedDateFormatter dateFormatter;
@Override
public void createPartControl(Composite parent) {
dateFormatter = PreferenceBasedDateFormatter.create();
GridLayoutFactory.fillDefaults().applyTo(parent);
toolkit = new FormToolkit(parent.getDisplay());
parent.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
toolkit.dispose();
}
});
form = toolkit.createForm(parent);
Image repoImage = UIIcons.REPOSITORY.createImage();
UIUtils.hookDisposal(form, repoImage);
final Image branchImage = UIIcons.CHANGESET.createImage();
UIUtils.hookDisposal(form, branchImage);
form.setImage(repoImage);
form.setText(UIText.StagingView_NoSelectionTitle);
GridDataFactory.fillDefaults().grab(true, true).applyTo(form);
toolkit.decorateFormHeading(form);
GridLayoutFactory.fillDefaults().applyTo(form.getBody());
Composite tableComposite = toolkit.createComposite(form.getBody());
tableComposite.setLayout(new GridLayout());
GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
final TreeColumnLayout layout = new TreeColumnLayout();
FilteredTree filteredTree = new FilteredTree(tableComposite, SWT.NONE
| SWT.BORDER | SWT.FULL_SELECTION, new PatternFilter(), true) {
@Override
protected void createControl(Composite composite, int treeStyle) {
super.createControl(composite, treeStyle);
treeComposite.setLayout(layout);
}
};
toolkit.adapt(filteredTree);
refLogTableTreeViewer = filteredTree.getViewer();
refLogTableTreeViewer.getTree().setLinesVisible(true);
refLogTableTreeViewer.getTree().setHeaderVisible(true);
refLogTableTreeViewer
.setContentProvider(new ReflogViewContentProvider());
ColumnViewerToolTipSupport.enableFor(refLogTableTreeViewer);
TreeViewerColumn toColumn = createColumn(layout,
UIText.ReflogView_CommitColumnHeader, 10, SWT.LEFT);
toColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof ReflogEntry) {
final ReflogEntry entry = (ReflogEntry) element;
return entry.getNewId().abbreviate(7).name();
}
return null;
}
@Override
public String getToolTipText(Object element) {
if (element instanceof ReflogEntry) {
final ReflogEntry entry = (ReflogEntry) element;
return entry.getNewId().name();
}
return null;
}
@Override
public Image getImage(Object element) {
if (element instanceof ReflogEntry) {
return branchImage;
}
return null;
}
});
TreeViewerColumn commitMessageColumn = createColumn(layout,
UIText.ReflogView_CommitMessageColumnHeader, 40, SWT.LEFT);
commitMessageColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof ReflogEntry) {
final ReflogEntry entry = (ReflogEntry) element;
RevCommit c = getCommit(entry);
return c == null ? "" : c.getShortMessage(); //$NON-NLS-1$
} else if (element instanceof IWorkbenchAdapter) {
return ((IWorkbenchAdapter) element).getLabel(element);
}
return null;
}
private RevCommit getCommit(final ReflogEntry entry) {
try (RevWalk walk = new RevWalk(getRepository())) {
walk.setRetainBody(true);
return walk.parseCommit(entry.getNewId());
} catch (IOException ignored) {
// ignore
return null;
}
}
});
TreeViewerColumn dateColumn = createColumn(layout,
UIText.ReflogView_DateColumnHeader, 15, SWT.LEFT);
dateColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof ReflogEntry) {
final ReflogEntry entry = (ReflogEntry) element;
final PersonIdent who = entry.getWho();
return dateFormatter.formatDate(who);
}
return null;
}
@Override
public Image getImage(Object element) {
return null;
}
});
TreeViewerColumn messageColumn = createColumn(layout,
UIText.ReflogView_MessageColumnHeader, 40, SWT.LEFT);
messageColumn.setLabelProvider(new ColumnLabelProvider() {
private ResourceManager resourceManager = new LocalResourceManager(
JFaceResources.getResources());
@Override
public String getText(Object element) {
if (element instanceof ReflogEntry) {
final ReflogEntry entry = (ReflogEntry) element;
return entry.getComment();
}
return null;
}
@Override
public Image getImage(Object element) {
if (!(element instanceof ReflogEntry)) {
return null;
}
String comment = ((ReflogEntry) element).getComment();
if (comment.startsWith("commit:") || comment.startsWith("commit (initial):")) //$NON-NLS-1$ //$NON-NLS-2$
return (Image) resourceManager.get(UIIcons.COMMIT);
if (comment.startsWith("commit (amend):")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.AMEND_COMMIT);
if (comment.startsWith("pull")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.PULL);
if (comment.startsWith("clone")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.CLONEGIT);
if (comment.startsWith("rebase")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.REBASE);
if (comment.startsWith("merge")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.MERGE);
if (comment.startsWith("fetch")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.FETCH);
if (comment.startsWith("branch")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.CREATE_BRANCH);
if (comment.startsWith("checkout")) //$NON-NLS-1$
return (Image) resourceManager.get(UIIcons.CHECKOUT);
return null;
}
@Override
public void dispose() {
resourceManager.dispose();
super.dispose();
}
});
new OpenAndLinkWithEditorHelper(refLogTableTreeViewer) {
@Override
protected void linkToEditor(ISelection selection) {
// Not supported
}
@Override
protected void open(ISelection sel, boolean activate) {
handleOpen(sel, OpenStrategy.activateOnOpen());
}
@Override
protected void activate(ISelection selection) {
handleOpen(selection, true);
}
private void handleOpen(ISelection selection, boolean activateOnOpen) {
if (selection instanceof IStructuredSelection)
if (selection.isEmpty())
return;
Repository repo = getRepository();
if (repo == null)
return;
try (RevWalk walk = new RevWalk(repo)) {
for (Object element : ((IStructuredSelection)selection).toArray()) {
ReflogEntry entry = (ReflogEntry) element;
ObjectId id = entry.getNewId();
if (id == null || id.equals(ObjectId.zeroId()))
id = entry.getOldId();
if (id != null && !id.equals(ObjectId.zeroId()))
CommitEditor.openQuiet(new RepositoryCommit(repo,
walk.parseCommit(id)), activateOnOpen);
}
} catch (IOException e) {
Activator.logError(UIText.ReflogView_ErrorOnOpenCommit, e);
}
}
};
uiPrefsListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
if (UIPreferences.DATE_FORMAT.equals(property)
|| UIPreferences.DATE_FORMAT_CHOICE.equals(property)) {
dateFormatter = PreferenceBasedDateFormatter.create();
refLogTableTreeViewer.refresh();
}
}
};
Activator.getDefault().getPreferenceStore()
.addPropertyChangeListener(uiPrefsListener);
selectionChangedListener = new ISelectionListener() {
@Override
public void selectionChanged(IWorkbenchPart part,
ISelection selection) {
if (part instanceof IEditorPart) {
IEditorInput input = ((IEditorPart) part).getEditorInput();
if (input instanceof IFileEditorInput)
reactOnSelection(new StructuredSelection(
((IFileEditorInput) input).getFile()));
} else
reactOnSelection(selection);
}
};
IWorkbenchPartSite site = getSite();
ISelectionService service = CommonUtils.getService(site, ISelectionService.class);
service.addPostSelectionListener(selectionChangedListener);
// Use current selection to populate reflog view
UIUtils.notifySelectionChangedWithCurrentSelection(
selectionChangedListener, site);
site.setSelectionProvider(refLogTableTreeViewer);
addRefsChangedListener = Repository.getGlobalListenerList()
.addRefsChangedListener(this);
// register context menu
MenuManager menuManager = new MenuManager();
menuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
Tree tree = refLogTableTreeViewer.getTree();
tree.setMenu(menuManager.createContextMenu(tree));
MenuManager resetManager = ResetMenu.createMenu(getSite());
menuManager.add(resetManager);
getSite().registerContextMenu(POPUP_MENU_ID, menuManager, refLogTableTreeViewer);
}
@Override
public void setFocus() {
refLogTableTreeViewer.getControl().setFocus();
activateContextService();
}
private void activateContextService() {
IContextService contextService = CommonUtils.getService(getSite(), IContextService.class);
if (contextService != null)
contextService.activateContext(VIEW_ID);
}
@Override
public void dispose() {
super.dispose();
ISelectionService service = CommonUtils.getService(getSite(), ISelectionService.class);
service.removePostSelectionListener(selectionChangedListener);
if (addRefsChangedListener != null) {
addRefsChangedListener.remove();
}
Activator.getDefault().getPreferenceStore()
.removePropertyChangeListener(uiPrefsListener);
}
private void reactOnSelection(ISelection selection) {
if (!(selection instanceof IStructuredSelection)) {
return;
}
IStructuredSelection ssel = (IStructuredSelection) selection;
if (ssel.size() != 1) {
return;
}
Repository selectedRepo = null;
Object first = ssel.getFirstElement();
IResource adapted = AdapterUtils.adaptToAnyResource(first);
if (adapted != null) {
RepositoryMapping mapping = RepositoryMapping.getMapping(adapted);
if (mapping != null) {
selectedRepo = mapping.getRepository();
}
}
if (selectedRepo == null) {
selectedRepo = AdapterUtils.adapt(first, Repository.class);
}
if (selectedRepo == null) {
return;
}
// Only update when different repository is selected
Repository currentRepo = getRepository();
if (currentRepo == null
|| !selectedRepo.getDirectory().equals(
currentRepo.getDirectory())) {
showReflogFor(selectedRepo);
}
}
private void updateRefLink(final String name) {
IToolBarManager toolbar = form.getToolBarManager();
toolbar.removeAll();
ControlContribution refLabelControl = new ControlContribution(
"refLabel") { //$NON-NLS-1$
@Override
protected Control createControl(Composite cParent) {
Composite composite = toolkit.createComposite(cParent);
composite.setLayout(new RowLayout());
composite.setBackground(null);
final ImageHyperlink refLink = new ImageHyperlink(composite,
SWT.NONE);
Image image = UIIcons.BRANCH.createImage();
UIUtils.hookDisposal(refLink, image);
refLink.setImage(image);
refLink.setFont(JFaceResources.getBannerFont());
refLink.setForeground(toolkit.getColors().getColor(
IFormColors.TITLE));
refLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent event) {
Repository repository = getRepository();
if (repository == null)
return;
RefSelectionDialog dialog = new RefSelectionDialog(
refLink.getShell(), repository);
if (Window.OK == dialog.open())
showReflogFor(repository, dialog.getRefName());
}
});
refLink.setText(Repository.shortenRefName(name));
return composite;
}
};
toolbar.add(refLabelControl);
toolbar.update(true);
}
/**
* @return the repository the view is showing the reflog for
*/
public Repository getRepository() {
Object input = refLogTableTreeViewer.getInput();
if (input instanceof ReflogInput)
return ((ReflogInput) input).getRepository();
return null;
}
@Override
public boolean show(ShowInContext context) {
ISelection selection = context.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
for (Object element : structuredSelection.toList()) {
if (element instanceof RepositoryTreeNode) {
RepositoryTreeNode node = (RepositoryTreeNode) element;
showReflogFor(node.getRepository());
return true;
}
}
}
return false;
}
/**
* Defines the repository for the reflog to show.
*
* @param repository
*/
private void showReflogFor(Repository repository) {
showReflogFor(repository, Constants.HEAD);
}
/**
* Defines the repository for the reflog to show.
*
* @param repository
* @param ref
*/
private void showReflogFor(Repository repository, String ref) {
if (repository != null && ref != null) {
refLogTableTreeViewer.setInput(new ReflogInput(repository, ref));
updateRefLink(ref);
form.setText(getRepositoryName(repository));
}
}
private TreeViewerColumn createColumn(
final TreeColumnLayout columnLayout, final String text,
final int weight, final int style) {
final TreeViewerColumn viewerColumn = new TreeViewerColumn(
refLogTableTreeViewer, style);
final TreeColumn column = viewerColumn.getColumn();
column.setText(text);
columnLayout.setColumnData(column, new ColumnWeightData(weight, 10));
return viewerColumn;
}
private static String getRepositoryName(Repository repository) {
String repoName = Activator.getDefault().getRepositoryUtil()
.getRepositoryName(repository);
RepositoryState state = repository.getRepositoryState();
if (state != RepositoryState.SAFE)
return repoName + '|' + state.getDescription();
else
return repoName;
}
@Override
public void onRefsChanged(RefsChangedEvent event) {
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
refLogTableTreeViewer.refresh();
}
});
}
/**
* @return selection provider
*/
public ISelectionProvider getSelectionProvider() {
return refLogTableTreeViewer;
}
}