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
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
{
111 public static final String VIEW_ID
= "org.eclipse.egit.ui.ReflogView"; //$NON-NLS-1$
116 public static final String POPUP_MENU_ID
= "org.eclipse.egit.ui.internal.reflogview.popup";//$NON-NLS-1$
118 private FormToolkit toolkit
;
122 private TreeViewer refLogTableTreeViewer
;
124 private ISelectionListener selectionChangedListener
;
126 private ListenerHandle addRefsChangedListener
;
128 private IPropertyChangeListener uiPrefsListener
;
130 private PreferenceBasedDateFormatter dateFormatter
;
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() {
140 public void widgetDisposed(DisposeEvent e
) {
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) {
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() {
187 public String
getText(Object element
) {
188 if (element
instanceof ReflogEntry
) {
189 final ReflogEntry entry
= (ReflogEntry
) element
;
190 return entry
.getNewId().abbreviate(7).name();
196 public String
getToolTipText(Object element
) {
197 if (element
instanceof ReflogEntry
) {
198 final ReflogEntry entry
= (ReflogEntry
) element
;
199 return entry
.getNewId().name();
205 public Image
getImage(Object element
) {
206 if (element
instanceof ReflogEntry
) {
214 TreeViewerColumn commitMessageColumn
= createColumn(layout
,
215 UIText
.ReflogView_CommitMessageColumnHeader
, 40, SWT
.LEFT
);
216 commitMessageColumn
.setLabelProvider(new ColumnLabelProvider() {
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
);
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
) {
241 TreeViewerColumn dateColumn
= createColumn(layout
,
242 UIText
.ReflogView_DateColumnHeader
, 15, SWT
.LEFT
);
243 dateColumn
.setLabelProvider(new ColumnLabelProvider() {
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
);
256 public Image
getImage(Object element
) {
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());
270 public String
getText(Object element
) {
271 if (element
instanceof ReflogEntry
) {
272 final ReflogEntry entry
= (ReflogEntry
) element
;
273 return entry
.getComment();
279 public Image
getImage(Object element
) {
280 if (!(element
instanceof ReflogEntry
)) {
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
);
306 public void dispose() {
307 resourceManager
.dispose();
312 new OpenAndLinkWithEditorHelper(refLogTableTreeViewer
) {
314 protected void linkToEditor(ISelection selection
) {
319 protected void open(ISelection sel
, boolean activate
) {
320 handleOpen(sel
, OpenStrategy
.activateOnOpen());
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())
330 Repository repo
= getRepository();
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() {
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() {
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()));
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
);
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
);
415 public void 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
)) {
430 IStructuredSelection ssel
= (IStructuredSelection
) selection
;
431 if (ssel
.size() != 1) {
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) {
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();
463 ControlContribution refLabelControl
= new ControlContribution(
464 "refLabel") { //$NON-NLS-1$
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
,
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(
479 refLink
.addHyperlinkListener(new HyperlinkAdapter() {
481 public void linkActivated(HyperlinkEvent event
) {
482 Repository repository
= getRepository();
483 if (repository
== null)
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
));
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();
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());
527 * Defines the repository for the reflog to show.
531 private void showReflogFor(Repository repository
) {
532 showReflogFor(repository
, Constants
.HEAD
);
536 * Defines the repository for the reflog to show.
541 private void showReflogFor(Repository repository
, String ref
) {
542 if (repository
!= null && ref
!= null) {
543 refLogTableTreeViewer
.setInput(new ReflogInput(repository
, 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));
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();
571 public void onRefsChanged(RefsChangedEvent event
) {
572 PlatformUI
.getWorkbench().getDisplay().syncExec(new Runnable() {
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
;