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
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
{
114 public static final String VIEW_ID
= "org.eclipse.egit.ui.ReflogView"; //$NON-NLS-1$
119 public static final String POPUP_MENU_ID
= "org.eclipse.egit.ui.internal.reflogview.popup";//$NON-NLS-1$
121 private FormToolkit toolkit
;
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")
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() {
146 public void widgetDisposed(DisposeEvent e
) {
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) {
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() {
194 public String
getText(Object element
) {
195 if (element
instanceof ReflogEntry
) {
196 final ReflogEntry entry
= (ReflogEntry
) element
;
197 return entry
.getNewId().abbreviate(7).name();
203 public String
getToolTipText(Object element
) {
204 if (element
instanceof ReflogEntry
) {
205 final ReflogEntry entry
= (ReflogEntry
) element
;
206 return entry
.getNewId().name();
212 public Image
getImage(Object element
) {
213 if (element
instanceof ReflogEntry
) {
221 TreeViewerColumn commitMessageColumn
= createColumn(layout
,
222 UIText
.ReflogView_CommitMessageColumnHeader
, 40, SWT
.LEFT
);
223 commitMessageColumn
.setLabelProvider(new ColumnLabelProvider() {
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
);
238 TreeViewerColumn dateColumn
= createColumn(layout
,
239 UIText
.ReflogView_DateColumnHeader
, 15, SWT
.LEFT
);
240 dateColumn
.setLabelProvider(new ColumnLabelProvider() {
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
);
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());
262 public String
getText(Object element
) {
263 if (element
instanceof ReflogEntry
) {
264 final ReflogEntry entry
= (ReflogEntry
) element
;
265 return entry
.getComment();
271 public Image
getImage(Object element
) {
272 if (!(element
instanceof ReflogEntry
)) {
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
);
304 public void dispose() {
305 resourceManager
.dispose();
310 new OpenAndLinkWithEditorHelper(refLogTableTreeViewer
) {
312 protected void linkToEditor(ISelection selection
) {
317 protected void open(ISelection sel
, boolean activate
) {
318 handleOpen(sel
, OpenStrategy
.activateOnOpen());
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())
328 Repository repo
= getRepository();
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() {
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() {
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()));
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);
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
);
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
);
422 public void 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
)) {
441 IStructuredSelection ssel
= (IStructuredSelection
) selection
;
442 if (ssel
.size() != 1) {
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) {
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();
474 ControlContribution refLabelControl
= new ControlContribution(
475 "refLabel") { //$NON-NLS-1$
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
,
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(
490 refLink
.addHyperlinkListener(new HyperlinkAdapter() {
492 public void linkActivated(HyperlinkEvent event
) {
493 Repository repository
= getRepository();
494 if (repository
== null)
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
));
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();
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());
538 * Defines the repository for the reflog to show.
542 private void showReflogFor(Repository repository
) {
543 showReflogFor(repository
, Constants
.HEAD
);
547 * Defines the repository for the reflog to show.
552 private void showReflogFor(Repository repository
, String ref
) {
553 if (repository
!= null && ref
!= null) {
554 refLogTableTreeViewer
.setInput(new ReflogInput(repository
, 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));
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();
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()));