| /******************************************************************************* |
| * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br> |
| * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> |
| * Copyright (c) 2010, Stefan Lay <stefan.lay@sap.com> |
| * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com> |
| * Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com> |
| * Copyright (C) 2012, Daniel megert <daniel_megert@ch.ibm.com> |
| * Copyright (C) 2012-2013 Robin Stocker <robin@nibor.org> |
| * Copyright (C) 2012, François Rey <eclipse.org_@_francois_._rey_._name> |
| * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>) |
| * Copyright (C) 2015-2018 Thomas Wolf <thomas.wolf@paranor.ch> |
| * Copyright (C) 2015-2017, Stefan Dirix <sdirix@eclipsesource.com> |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| package org.eclipse.egit.ui.internal.history; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.egit.core.AdapterUtils; |
| import org.eclipse.egit.core.project.RepositoryMapping; |
| import org.eclipse.egit.ui.Activator; |
| import org.eclipse.egit.ui.JobFamilies; |
| import org.eclipse.egit.ui.UIPreferences; |
| import org.eclipse.egit.ui.UIUtils; |
| import org.eclipse.egit.ui.internal.CompareUtils; |
| import org.eclipse.egit.ui.internal.UIIcons; |
| import org.eclipse.egit.ui.internal.UIText; |
| import org.eclipse.egit.ui.internal.commit.DiffDocument; |
| import org.eclipse.egit.ui.internal.commit.DiffRegionFormatter; |
| import org.eclipse.egit.ui.internal.commit.DiffViewer; |
| import org.eclipse.egit.ui.internal.commit.FocusTracker; |
| import org.eclipse.egit.ui.internal.components.RepositoryMenuUtil.RepositoryToolbarAction; |
| import org.eclipse.egit.ui.internal.dialogs.HyperlinkSourceViewer; |
| import org.eclipse.egit.ui.internal.dialogs.HyperlinkTokenScanner; |
| import org.eclipse.egit.ui.internal.fetch.FetchHeadChangedEvent; |
| import org.eclipse.egit.ui.internal.history.FindToolbar.StatusListener; |
| import org.eclipse.egit.ui.internal.repository.tree.AdditionalRefNode; |
| import org.eclipse.egit.ui.internal.repository.tree.FileNode; |
| import org.eclipse.egit.ui.internal.repository.tree.FolderNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RefNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RepositoryNode; |
| import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode; |
| import org.eclipse.egit.ui.internal.repository.tree.TagNode; |
| import org.eclipse.egit.ui.internal.selection.RepositorySelectionProvider; |
| import org.eclipse.egit.ui.internal.selection.SelectionUtils; |
| import org.eclipse.egit.ui.internal.trace.GitTraceLocation; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.ControlContribution; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.action.IContributionItem; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.IStatusLineManager; |
| import org.eclipse.jface.action.IToolBarManager; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.bindings.keys.SWTKeySupport; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.preference.IPersistentPreferenceStore; |
| import org.eclipse.jface.preference.PreferenceDialog; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.resource.LocalResourceManager; |
| import org.eclipse.jface.resource.ResourceManager; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextListener; |
| import org.eclipse.jface.text.TextAttribute; |
| import org.eclipse.jface.text.TextEvent; |
| import org.eclipse.jface.text.hyperlink.IHyperlinkDetector; |
| import org.eclipse.jface.text.presentation.IPresentationReconciler; |
| import org.eclipse.jface.text.presentation.PresentationReconciler; |
| import org.eclipse.jface.text.rules.DefaultDamagerRepairer; |
| import org.eclipse.jface.text.rules.IToken; |
| import org.eclipse.jface.text.rules.Token; |
| import org.eclipse.jface.text.source.ISourceViewer; |
| import org.eclipse.jface.text.source.SourceViewerConfiguration; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.jgit.annotations.NonNull; |
| import org.eclipse.jgit.diff.DiffConfig; |
| import org.eclipse.jgit.diff.DiffEntry; |
| import org.eclipse.jgit.events.ListenerHandle; |
| import org.eclipse.jgit.events.RefsChangedEvent; |
| import org.eclipse.jgit.events.RefsChangedListener; |
| import org.eclipse.jgit.lib.AnyObjectId; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.FollowFilter; |
| import org.eclipse.jgit.revwalk.RenameCallback; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevFlag; |
| import org.eclipse.jgit.revwalk.RevObject; |
| import org.eclipse.jgit.revwalk.RevSort; |
| import org.eclipse.jgit.revwalk.RevTag; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.treewalk.TreeWalk; |
| import org.eclipse.jgit.treewalk.filter.AndTreeFilter; |
| import org.eclipse.jgit.treewalk.filter.OrTreeFilter; |
| import org.eclipse.jgit.treewalk.filter.PathFilterGroup; |
| import org.eclipse.jgit.treewalk.filter.TreeFilter; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CLabel; |
| import org.eclipse.swt.custom.SashForm; |
| import org.eclipse.swt.custom.ScrolledComposite; |
| import org.eclipse.swt.custom.StackLayout; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Link; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.team.ui.history.HistoryPage; |
| import org.eclipse.team.ui.history.IHistoryView; |
| import org.eclipse.ui.IActionBars; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchPartSite; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.actions.ActionFactory; |
| import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction; |
| import org.eclipse.ui.dialogs.PreferencesUtil; |
| import org.eclipse.ui.editors.text.EditorsUI; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.progress.IWorkbenchSiteProgressService; |
| import org.eclipse.ui.progress.UIJob; |
| import org.eclipse.ui.texteditor.IUpdate; |
| |
| /** Graphical commit history viewer. */ |
| public class GitHistoryPage extends HistoryPage implements RefsChangedListener, |
| TableLoader, IShowInSource, IShowInTargetList { |
| |
| private static final int INITIAL_ITEM = -1; |
| |
| /** actions used in GitHistoryPage **/ |
| private static class GitHistoryPageActions { |
| |
| private abstract class BooleanPrefAction extends Action implements |
| IPropertyChangeListener, IWorkbenchAction { |
| private final String prefName; |
| |
| BooleanPrefAction(final String pn, final String text) { |
| setText(text); |
| prefName = pn; |
| historyPage.store.addPropertyChangeListener(this); |
| setChecked(historyPage.store.getBoolean(prefName)); |
| } |
| |
| @Override |
| public void run() { |
| historyPage.store.setValue(prefName, isChecked()); |
| if (historyPage.store.needsSaving()) |
| try { |
| historyPage.store.save(); |
| } catch (IOException e) { |
| Activator.handleError(e.getMessage(), e, false); |
| } |
| } |
| |
| abstract void apply(boolean value); |
| |
| @Override |
| public void propertyChange(final PropertyChangeEvent event) { |
| if (prefName.equals(event.getProperty())) { |
| Control control = historyPage.getControl(); |
| if (control != null && !control.isDisposed()) { |
| control.getDisplay().asyncExec(() -> { |
| if (!control.isDisposed()) { |
| setChecked( |
| historyPage.store.getBoolean(prefName)); |
| apply(isChecked()); |
| } |
| }); |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| // stop listening |
| historyPage.store.removePropertyChangeListener(this); |
| } |
| } |
| |
| private class ShowFilterAction extends Action { |
| private final ShowFilter filter; |
| |
| ShowFilterAction(ShowFilter filter, ImageDescriptor icon, |
| String menuLabel, String toolTipText) { |
| super(null, IAction.AS_CHECK_BOX); |
| this.filter = filter; |
| setImageDescriptor(icon); |
| setText(menuLabel); |
| setToolTipText(toolTipText); |
| } |
| |
| @Override |
| public void run() { |
| String oldName = historyPage.getName(); |
| String oldDescription = historyPage.getDescription(); |
| if (!isChecked()) |
| if (historyPage.showAllFilter == filter) { |
| historyPage.showAllFilter = ShowFilter.SHOWALLRESOURCE; |
| showAllResourceVersionsAction.setChecked(true); |
| historyPage.initAndStartRevWalk(false); |
| } |
| if (isChecked() && historyPage.showAllFilter != filter) { |
| historyPage.showAllFilter = filter; |
| if (this != showAllRepoVersionsAction) |
| showAllRepoVersionsAction.setChecked(false); |
| if (this != showAllProjectVersionsAction) |
| showAllProjectVersionsAction.setChecked(false); |
| if (this != showAllFolderVersionsAction) |
| showAllFolderVersionsAction.setChecked(false); |
| if (this != showAllResourceVersionsAction) |
| showAllResourceVersionsAction.setChecked(false); |
| historyPage.initAndStartRevWalk(false); |
| } |
| historyPage.firePropertyChange(historyPage, P_NAME, oldName, |
| historyPage.getName()); |
| // even though this is currently ending nowhere (see bug |
| // 324386), we |
| // still create the event |
| historyPage.firePropertyChange(historyPage, P_DESCRIPTION, |
| oldDescription, historyPage.getDescription()); |
| Activator.getDefault().getPreferenceStore().setValue( |
| PREF_SHOWALLFILTER, |
| historyPage.showAllFilter.toString()); |
| } |
| |
| @Override |
| public String toString() { |
| return "ShowFilter[" + filter.toString() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| List<IWorkbenchAction> actionsToDispose; |
| |
| BooleanPrefAction showRelativeDateAction; |
| |
| BooleanPrefAction showEmailAddressesAction; |
| |
| BooleanPrefAction showNotesAction; |
| |
| BooleanPrefAction showTagSequenceAction; |
| |
| BooleanPrefAction showBranchSequenceAction; |
| |
| BooleanPrefAction wrapCommentAction; |
| |
| BooleanPrefAction fillCommentAction; |
| |
| IAction findAction; |
| |
| IAction refreshAction; |
| |
| BooleanPrefAction showCommentAction; |
| |
| BooleanPrefAction showFilesAction; |
| |
| IWorkbenchAction compareModeAction; |
| |
| IWorkbenchAction showAllBranchesAction; |
| |
| IWorkbenchAction showAdditionalRefsAction; |
| |
| BooleanPrefAction followRenamesAction; |
| |
| IWorkbenchAction reuseCompareEditorAction; |
| |
| ShowFilterAction showAllRepoVersionsAction; |
| |
| ShowFilterAction showAllProjectVersionsAction; |
| |
| ShowFilterAction showAllFolderVersionsAction; |
| |
| ShowFilterAction showAllResourceVersionsAction; |
| |
| RepositoryToolbarAction switchRepositoryAction; |
| |
| private GitHistoryPage historyPage; |
| |
| GitHistoryPageActions(GitHistoryPage historyPage) { |
| actionsToDispose = new ArrayList<>(); |
| this.historyPage = historyPage; |
| createActions(); |
| } |
| |
| private static String formatAccelerator(int accelerator) { |
| return SWTKeySupport.getKeyFormatterForPlatform().format( |
| SWTKeySupport.convertAcceleratorToKeyStroke(accelerator)); |
| } |
| |
| private void createActions() { |
| createRepositorySwitchAction(); |
| createFindToolbarAction(); |
| createRefreshAction(); |
| createFilterActions(); |
| createCompareModeAction(); |
| createReuseCompareEditorAction(); |
| createShowAllBranchesAction(); |
| createShowAdditionalRefsAction(); |
| createShowCommentAction(); |
| createShowFilesAction(); |
| createShowRelativeDateAction(); |
| createShowEmailAddressesAction(); |
| createShowNotesAction(); |
| createShowTagSequenceAction(); |
| createShowBranchSequenceAction(); |
| createWrapCommentAction(); |
| createFillCommentAction(); |
| createFollowRenamesAction(); |
| |
| wrapCommentAction.setEnabled(showCommentAction.isChecked()); |
| fillCommentAction.setEnabled(showCommentAction.isChecked()); |
| } |
| |
| private void createRepositorySwitchAction() { |
| switchRepositoryAction = new RepositoryToolbarAction(true, |
| () -> historyPage.currentRepo, |
| repo -> { |
| Repository current = historyPage.currentRepo; |
| if (current != null && repo.getDirectory() |
| .equals(current.getDirectory())) { |
| HistoryPageInput currentInput = historyPage |
| .getInputInternal(); |
| if (currentInput != null |
| && currentInput.getItems() == null |
| && currentInput.getFileList() == null) { |
| // Already showing this repo unfiltered |
| return; |
| } |
| } |
| if (historyPage.selectionTracker != null) { |
| historyPage.selectionTracker.clearSelection(); |
| } |
| historyPage.getHistoryView() |
| .showHistoryFor(new RepositoryNode(null, repo)); |
| }); |
| actionsToDispose.add(switchRepositoryAction); |
| } |
| |
| private void createFindToolbarAction() { |
| findAction = new Action(UIText.GitHistoryPage_FindMenuLabel, |
| UIIcons.ELCL16_FIND) { |
| |
| @Override |
| public void run() { |
| historyPage.store.setValue( |
| UIPreferences.RESOURCEHISTORY_SHOW_FINDTOOLBAR, |
| isChecked()); |
| if (historyPage.store.needsSaving()) { |
| try { |
| historyPage.store.save(); |
| } catch (IOException e) { |
| Activator.handleError(e.getMessage(), e, false); |
| } |
| } |
| historyPage.searchBar.setVisible(isChecked()); |
| } |
| |
| @Override |
| public void setChecked(boolean checked) { |
| super.setChecked(checked); |
| int accelerator = getAccelerator(); |
| if (checked) { |
| setToolTipText( |
| NLS.bind(UIText.GitHistoryPage_FindHideTooltip, |
| formatAccelerator(accelerator))); |
| } else { |
| setToolTipText( |
| NLS.bind(UIText.GitHistoryPage_FindShowTooltip, |
| formatAccelerator(accelerator))); |
| } |
| } |
| |
| }; |
| // TODO: how not to hard-wire this? |
| findAction.setAccelerator(SWT.MOD1 | 'F'); |
| findAction.setEnabled(false); |
| // Gets enabled once we have commits |
| boolean isChecked = historyPage.store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_FINDTOOLBAR); |
| findAction.setChecked(isChecked); |
| historyPage.getSite().getActionBars().setGlobalActionHandler( |
| ActionFactory.FIND.getId(), findAction); |
| historyPage.getSite().getActionBars().updateActionBars(); |
| } |
| |
| private void createRefreshAction() { |
| refreshAction = new Action(UIText.GitHistoryPage_RefreshMenuLabel, |
| UIIcons.ELCL16_REFRESH) { |
| @Override |
| public void run() { |
| historyPage.refresh(); |
| } |
| }; |
| } |
| |
| private void createFilterActions() { |
| showAllRepoVersionsAction = new ShowFilterAction( |
| ShowFilter.SHOWALLREPO, UIIcons.FILTERNONE, |
| UIText.GitHistoryPage_AllInRepoMenuLabel, |
| UIText.GitHistoryPage_AllInRepoTooltip); |
| |
| showAllProjectVersionsAction = new ShowFilterAction( |
| ShowFilter.SHOWALLPROJECT, UIIcons.FILTERPROJECT, |
| UIText.GitHistoryPage_AllInProjectMenuLabel, |
| UIText.GitHistoryPage_AllInProjectTooltip); |
| |
| showAllFolderVersionsAction = new ShowFilterAction( |
| ShowFilter.SHOWALLFOLDER, UIIcons.FILTERFOLDER, |
| UIText.GitHistoryPage_AllInParentMenuLabel, |
| UIText.GitHistoryPage_AllInParentTooltip); |
| |
| showAllResourceVersionsAction = new ShowFilterAction( |
| ShowFilter.SHOWALLRESOURCE, UIIcons.FILTERRESOURCE, |
| UIText.GitHistoryPage_AllOfResourceMenuLabel, |
| UIText.GitHistoryPage_AllOfResourceTooltip); |
| |
| showAllRepoVersionsAction |
| .setChecked(historyPage.showAllFilter == showAllRepoVersionsAction.filter); |
| showAllProjectVersionsAction |
| .setChecked(historyPage.showAllFilter == showAllProjectVersionsAction.filter); |
| showAllFolderVersionsAction |
| .setChecked(historyPage.showAllFilter == showAllFolderVersionsAction.filter); |
| showAllResourceVersionsAction |
| .setChecked(historyPage.showAllFilter == showAllResourceVersionsAction.filter); |
| } |
| |
| private void createCompareModeAction() { |
| compareModeAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_COMPARE_MODE, |
| UIText.GitHistoryPage_CompareModeMenuLabel) { |
| @Override |
| void apply(boolean value) { |
| // nothing, just switch the preference |
| } |
| }; |
| compareModeAction.setImageDescriptor(UIIcons.ELCL16_COMPARE_VIEW); |
| compareModeAction.setToolTipText(UIText.GitHistoryPage_compareMode); |
| actionsToDispose.add(compareModeAction); |
| } |
| |
| private void createReuseCompareEditorAction() { |
| reuseCompareEditorAction = new CompareUtils.ReuseCompareEditorAction(); |
| actionsToDispose.add(reuseCompareEditorAction); |
| } |
| |
| private void createShowAllBranchesAction() { |
| showAllBranchesAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_ALL_BRANCHES, |
| UIText.GitHistoryPage_ShowAllBranchesMenuLabel) { |
| |
| @Override |
| void apply(boolean value) { |
| historyPage.refresh(); |
| } |
| }; |
| showAllBranchesAction.setImageDescriptor(UIIcons.BRANCH); |
| showAllBranchesAction |
| .setToolTipText(UIText.GitHistoryPage_showAllBranches); |
| actionsToDispose.add(showAllBranchesAction); |
| } |
| |
| private void createShowAdditionalRefsAction() { |
| showAdditionalRefsAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS, |
| UIText.GitHistoryPage_ShowAdditionalRefsMenuLabel) { |
| |
| @Override |
| void apply(boolean value) { |
| historyPage.refresh(); |
| } |
| }; |
| actionsToDispose.add(showAdditionalRefsAction); |
| } |
| |
| private void createFollowRenamesAction() { |
| followRenamesAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_FOLLOW_RENAMES, |
| UIText.GitHistoryPage_FollowRenames) { |
| @Override |
| void apply(boolean follow) { |
| historyPage.refresh(); |
| } |
| }; |
| followRenamesAction.apply(followRenamesAction.isChecked()); |
| actionsToDispose.add(followRenamesAction); |
| } |
| |
| private void createShowCommentAction() { |
| showCommentAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT, |
| UIText.ResourceHistory_toggleRevComment) { |
| @Override |
| void apply(final boolean value) { |
| historyPage.layout(); |
| wrapCommentAction.setEnabled(isChecked()); |
| fillCommentAction.setEnabled(isChecked()); |
| } |
| }; |
| actionsToDispose.add(showCommentAction); |
| } |
| |
| private void createShowFilesAction() { |
| showFilesAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_REV_DETAIL, |
| UIText.ResourceHistory_toggleRevDetail) { |
| @Override |
| void apply(final boolean value) { |
| historyPage.layout(); |
| } |
| }; |
| actionsToDispose.add(showFilesAction); |
| } |
| |
| private void createShowRelativeDateAction() { |
| showRelativeDateAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_RELATIVE_DATE, |
| UIText.ResourceHistory_toggleRelativeDate) { |
| @Override |
| void apply(boolean date) { |
| // nothing, just set the Preference |
| } |
| }; |
| showRelativeDateAction.apply(showRelativeDateAction.isChecked()); |
| actionsToDispose.add(showRelativeDateAction); |
| } |
| |
| private void createShowEmailAddressesAction() { |
| showEmailAddressesAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_EMAIL_ADDRESSES, |
| UIText.GitHistoryPage_toggleEmailAddresses) { |
| @Override |
| void apply(boolean date) { |
| // nothing, just set the Preference |
| } |
| }; |
| showEmailAddressesAction.apply(showEmailAddressesAction.isChecked()); |
| actionsToDispose.add(showEmailAddressesAction); |
| } |
| |
| private void createShowNotesAction() { |
| showNotesAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_NOTES, |
| UIText.ResourceHistory_toggleShowNotes) { |
| @Override |
| void apply(boolean value) { |
| historyPage.refresh(); |
| } |
| }; |
| showNotesAction.apply(showNotesAction.isChecked()); |
| actionsToDispose.add(showNotesAction); |
| } |
| |
| private void createShowTagSequenceAction() { |
| showTagSequenceAction = new BooleanPrefAction( |
| UIPreferences.HISTORY_SHOW_TAG_SEQUENCE, |
| UIText.ResourceHistory_ShowTagSequence) { |
| @Override |
| void apply(boolean value) { |
| // nothing, just set the Preference |
| } |
| }; |
| showTagSequenceAction.apply(showTagSequenceAction.isChecked()); |
| actionsToDispose.add(showTagSequenceAction); |
| } |
| |
| private void createShowBranchSequenceAction() { |
| showBranchSequenceAction = new BooleanPrefAction( |
| UIPreferences.HISTORY_SHOW_BRANCH_SEQUENCE, |
| UIText.ResourceHistory_ShowBranchSequence) { |
| @Override |
| void apply(boolean value) { |
| // nothing, just set the Preference |
| } |
| }; |
| showBranchSequenceAction |
| .apply(showBranchSequenceAction.isChecked()); |
| actionsToDispose.add(showBranchSequenceAction); |
| } |
| |
| private void createWrapCommentAction() { |
| wrapCommentAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP, |
| UIText.ResourceHistory_toggleCommentWrap) { |
| @Override |
| void apply(boolean wrap) { |
| // nothing, just set the Preference |
| } |
| }; |
| wrapCommentAction.apply(wrapCommentAction.isChecked()); |
| actionsToDispose.add(wrapCommentAction); |
| } |
| |
| private void createFillCommentAction() { |
| fillCommentAction = new BooleanPrefAction( |
| UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_FILL, |
| UIText.ResourceHistory_toggleCommentFill) { |
| @Override |
| void apply(boolean fill) { |
| // nothing, just set the Preference |
| } |
| }; |
| fillCommentAction.apply(fillCommentAction.isChecked()); |
| actionsToDispose.add(fillCommentAction); |
| } |
| } |
| |
| /** |
| * This class defines a couple that associates two pieces of information: |
| * the file path, and whether it is a regular file (or a directory). |
| */ |
| private static class FilterPath { |
| |
| private String path; |
| |
| private boolean regularFile; |
| |
| public FilterPath(String path, boolean regularFile) { |
| super(); |
| this.path = path; |
| this.regularFile = regularFile; |
| } |
| |
| /** @return the file path */ |
| public String getPath() { |
| return path; |
| } |
| |
| /** @return <code>true</code> if the file is a regular file, |
| * and <code>false</code> otherwise (directory, project) */ |
| public boolean isRegularFile() { |
| return regularFile; |
| } |
| |
| /** |
| * In {@link FilterPath} class, equality is based on {@link #getPath |
| * path} equality. |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj instanceof FilterPath)) |
| return false; |
| FilterPath other = (FilterPath) obj; |
| if (path == null) |
| return other.path == null; |
| return path.equals(other.path); |
| } |
| |
| @Override |
| public int hashCode() { |
| if (path != null) |
| return path.hashCode(); |
| return super.hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder("Path: "); //$NON-NLS-1$ |
| builder.append(getPath()); |
| builder.append(", regular: "); //$NON-NLS-1$ |
| builder.append(isRegularFile()); |
| |
| return builder.toString(); |
| } |
| } |
| |
| private static class HistoryPageRule implements ISchedulingRule { |
| @Override |
| public boolean contains(ISchedulingRule rule) { |
| return this == rule; |
| } |
| |
| @Override |
| public boolean isConflicting(ISchedulingRule rule) { |
| return this == rule; |
| } |
| } |
| |
| private static final String POPUP_ID = "org.eclipse.egit.ui.historyPageContributions"; //$NON-NLS-1$ |
| |
| private static final String DESCRIPTION_PATTERN = "{0} - {1}"; //$NON-NLS-1$ |
| |
| private static final String NAME_PATTERN = "{0}: {1} [{2}]"; //$NON-NLS-1$ |
| |
| private static final String PREF_SHOWALLFILTER = "org.eclipse.egit.ui.githistorypage.showallfilter"; //$NON-NLS-1$ |
| |
| enum ShowFilter { |
| SHOWALLRESOURCE, SHOWALLFOLDER, SHOWALLPROJECT, SHOWALLREPO, |
| } |
| |
| private ShowFilter showAllFilter = ShowFilter.SHOWALLRESOURCE; |
| |
| private GitHistoryPageActions actions; |
| |
| /** An error text to be shown instead of the control */ |
| private StyledText errorText; |
| |
| private final IPersistentPreferenceStore store = (IPersistentPreferenceStore) Activator |
| .getDefault().getPreferenceStore(); |
| |
| private ListenerHandle myRefsChangedHandle; |
| |
| private HistoryPageInput input; |
| |
| private String name; |
| |
| private boolean trace = GitTraceLocation.HISTORYVIEW.isActive(); |
| |
| /** Overall composite hosting all of our controls. */ |
| private Composite topControl; |
| |
| /** Overall composite hosting the controls that displays the history. */ |
| private Composite historyControl; |
| |
| /** Split between {@link #graph} and {@link #revInfoSplit}. */ |
| private SashForm graphDetailSplit; |
| |
| /** Split between {@link #commentViewer} and {@link #fileViewer}. */ |
| private SashForm revInfoSplit; |
| |
| /** The table showing the DAG, first "paragraph", author, author date. */ |
| private CommitGraphTable graph; |
| |
| /** Viewer displaying the currently selected commit of {@link #graph}. */ |
| private CommitMessageViewer commentViewer; |
| |
| private DiffViewer diffViewer; |
| |
| /** Viewer displaying file difference implied by {@link #graph}'s commit. */ |
| private CommitFileDiffViewer fileViewer; |
| |
| /** A label showing a warning icon */ |
| private Composite warningComposite; |
| |
| /** A label field to display a warning */ |
| private CLabel warningLabel; |
| |
| /** Our context menu manager for the entire page. */ |
| private final MenuManager popupMgr = new MenuManager(null, POPUP_ID); |
| |
| /** Job that is updating our history view, if we are refreshing. */ |
| private GenerateHistoryJob job; |
| |
| private final ResourceManager resources = new LocalResourceManager( |
| JFaceResources.getResources()); |
| |
| /** Last HEAD */ |
| private AnyObjectId currentHeadId; |
| |
| /** Last FETCH_HEAD */ |
| private AnyObjectId currentFetchHeadId; |
| |
| /** Repository of the last input*/ |
| private Repository currentRepo; |
| |
| private boolean currentShowAllBranches; |
| |
| private boolean currentShowAdditionalRefs; |
| |
| private boolean currentShowNotes; |
| |
| private boolean currentFollowRenames; |
| |
| /** Tracks the file names that are to be highlighted in the diff file viewer */ |
| private Set<String> fileViewerInterestingPaths; |
| |
| /** Tree walker to use in the file diff viewer. */ |
| private TreeWalk fileDiffWalker; |
| |
| // react on changes to the relative date preference |
| private final IPropertyChangeListener listener = new IPropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent event) { |
| final String prop = event.getProperty(); |
| if (UIPreferences.HISTORY_MAX_BRANCH_LENGTH.equals(prop) |
| || UIPreferences.HISTORY_MAX_TAG_LENGTH.equals(prop)) |
| graph.getTableView().refresh(); |
| if (UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP.equals(prop)) { |
| setWrap(((Boolean) event.getNewValue()).booleanValue()); |
| } |
| |
| } |
| }; |
| |
| /** Tracks the selection to display the correct input when linked with editors. */ |
| private GitHistorySelectionTracker selectionTracker; |
| |
| /** |
| * List of paths we used to limit the revwalk; null if no paths. |
| * <p> |
| * Note that a change in this list requires that the history is redrawn |
| */ |
| private List<FilterPath> pathFilters; |
| |
| private Runnable refschangedRunnable; |
| |
| private final RenameTracker renameTracker = new RenameTracker(); |
| |
| private ScrolledComposite commentAndDiffScrolledComposite; |
| |
| private Composite commentAndDiffComposite; |
| |
| private volatile boolean resizing; |
| |
| private final HistoryPageRule pageSchedulingRule; |
| |
| /** Toolbar to find commits in the history view. */ |
| private SearchBar searchBar; |
| |
| private FocusTracker focusTracker; |
| |
| /** |
| * Determine if the input can be shown in this viewer. |
| * |
| * @param object |
| * an object that is hopefully of type ResourceList or IResource, |
| * but may be anything (including null). |
| * @return true if the input is a ResourceList or an IResource of type FILE, |
| * FOLDER or PROJECT and we can show it; false otherwise. |
| */ |
| public static boolean canShowHistoryFor(final Object object) { |
| if (object instanceof HistoryPageInput) |
| return true; |
| |
| if (object instanceof IResource) |
| return typeOk((IResource) object); |
| |
| if (object instanceof RepositoryTreeNode) |
| return true; |
| |
| IResource resource = AdapterUtils.adaptToAnyResource(object); |
| if (resource != null && typeOk(resource)) |
| return true; |
| |
| return AdapterUtils.adapt(object, Repository.class) != null; |
| } |
| |
| private static boolean typeOk(final IResource object) { |
| switch (object.getType()) { |
| case IResource.FILE: |
| case IResource.FOLDER: |
| case IResource.PROJECT: |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * The default constructor |
| */ |
| public GitHistoryPage() { |
| trace = GitTraceLocation.HISTORYVIEW.isActive(); |
| pageSchedulingRule = new HistoryPageRule(); |
| if (trace) { |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| } |
| } |
| |
| private interface ICommitsProvider { |
| |
| Object getSearchContext(); |
| |
| SWTCommit[] getCommits(); |
| |
| RevFlag getHighlight(); |
| } |
| |
| private static class SearchBar extends ControlContribution { |
| |
| private IActionBars bars; |
| |
| private FindToolbar toolbar; |
| |
| private Object searchContext; |
| |
| private String lastText; |
| |
| private ObjectId lastObjectId; |
| |
| private Object lastSearchContext; |
| |
| private ICommitsProvider provider; |
| |
| private boolean wasVisible = false; |
| |
| private final CommitGraphTable graph; |
| |
| private final IAction openCloseToggle; |
| |
| /** |
| * "Go to next/previous" from the {@link FindToolbar} sends |
| * {@link SWT#Selection} events with the chosen {@link RevCommit} as |
| * data. |
| */ |
| private final Listener selectionListener = new Listener() { |
| |
| @Override |
| public void handleEvent(Event evt) { |
| final RevCommit commit = (RevCommit) evt.data; |
| lastObjectId = commit.getId(); |
| graph.selectCommit(commit); |
| } |
| }; |
| |
| /** |
| * Listener to close the search bar on ESC. (Ctrl/Cmd-F is already |
| * handled via global retarget action.) |
| */ |
| private final KeyListener keyListener = new KeyAdapter() { |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(e); |
| if (key == SWT.ESC) { |
| setVisible(false); |
| e.doit = false; |
| } |
| } |
| }; |
| |
| /** |
| * Listener to display status messages from the asynchronous find. (Is |
| * called in the UI thread.) |
| */ |
| private final StatusListener statusListener = new StatusListener() { |
| |
| @Override |
| public void setMessage(FindToolbar originator, String text) { |
| IStatusLineManager status = bars.getStatusLineManager(); |
| if (status != null) { |
| status.setMessage(text); |
| } |
| } |
| }; |
| |
| /** |
| * Listener to ensure that the history view is fully activated when the |
| * user clicks into the search bar's text widget. This makes sure our |
| * status manager gets activated and thus shows the status messages. We |
| * don't get a focus event when the user clicks in the field; and |
| * fiddling with the focus in a FocusListener could get hairy anyway. |
| */ |
| private final Listener mouseListener = new Listener() { |
| |
| private boolean hasFocus; |
| |
| private boolean hadFocusOnMouseDown; |
| |
| @Override |
| public void handleEvent(Event e) { |
| switch (e.type) { |
| case SWT.FocusIn: |
| toolbar.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| hasFocus = true; |
| } |
| }); |
| |
| break; |
| case SWT.FocusOut: |
| hasFocus = false; |
| break; |
| case SWT.MouseDown: |
| hadFocusOnMouseDown = hasFocus; |
| break; |
| case SWT.MouseUp: |
| if (!hadFocusOnMouseDown) { |
| graph.getControl().setFocus(); |
| toolbar.setFocus(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| }; |
| |
| public SearchBar(String id, CommitGraphTable graph, |
| IAction openCloseAction, IActionBars bars) { |
| super(id); |
| super.setVisible(false); |
| this.graph = graph; |
| this.openCloseToggle = openCloseAction; |
| this.bars = bars; |
| } |
| |
| private void beforeHide() { |
| lastText = toolbar.getText(); |
| lastSearchContext = searchContext; |
| statusListener.setMessage(toolbar, ""); //$NON-NLS-1$ |
| // It will be disposed by the IToolBarManager |
| toolbar = null; |
| openCloseToggle.setChecked(false); |
| wasVisible = false; |
| } |
| |
| @Override |
| public void setVisible(boolean visible) { |
| if (visible != isVisible()) { |
| if (!visible) { |
| beforeHide(); |
| } |
| super.setVisible(visible); |
| // Update the toolbar. Will dispose our FindToolbar widget on |
| // hide, and will create a new one (through createControl()) |
| // on show. It'll also reposition the toolbar, if needed. |
| // Note: just doing bars.getToolBarManager().update(true); |
| // messes up big time (doesn't resize or re-position). |
| bars.updateActionBars(); |
| if (visible && toolbar != null) { |
| openCloseToggle.setChecked(true); |
| // If the toolbar was moved below the tabs, we now have |
| // the wrong background. It disappears when one clicks |
| // elsewhere. Looks like an inactive selection... No |
| // way found to fix this but this ugly focus juggling: |
| graph.getControl().setFocus(); |
| toolbar.setFocus(); |
| } else if (!visible && !graph.getControl().isDisposed()) { |
| graph.getControl().setFocus(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isDynamic() { |
| // We toggle our own visibility |
| return true; |
| } |
| |
| @Override |
| protected Control createControl(Composite parent) { |
| toolbar = new FindToolbar(parent); |
| toolbar.setBackground(null); |
| toolbar.addKeyListener(keyListener); |
| toolbar.addListener(SWT.FocusIn, mouseListener); |
| toolbar.addListener(SWT.FocusOut, mouseListener); |
| toolbar.addListener(SWT.MouseDown, mouseListener); |
| toolbar.addListener(SWT.MouseUp, mouseListener); |
| toolbar.addListener(SWT.Modify, |
| (e) -> lastText = toolbar.getText()); |
| toolbar.addStatusListener(statusListener); |
| toolbar.addSelectionListener(selectionListener); |
| boolean hasInput = provider != null; |
| if (hasInput) { |
| setInput(provider); |
| } |
| if (lastText != null) { |
| if (lastSearchContext != null |
| && lastSearchContext.equals(searchContext)) { |
| toolbar.setPreselect(lastObjectId); |
| } |
| toolbar.setText(lastText, hasInput); |
| } |
| lastSearchContext = null; |
| lastObjectId = null; |
| if (wasVisible) { |
| return toolbar; |
| } |
| wasVisible = true; |
| // This fixes the wrong background when Eclipse starts up with the |
| // search bar visible. |
| toolbar.getDisplay().asyncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| if (toolbar != null && !toolbar.isDisposed()) { |
| // See setVisible() above. Somehow, we need this, too. |
| graph.getControl().setFocus(); |
| toolbar.setFocus(); |
| } |
| } |
| }); |
| return toolbar; |
| } |
| |
| public void setInput(ICommitsProvider provider) { |
| this.provider = provider; |
| if (toolbar != null) { |
| searchContext = provider.getSearchContext(); |
| toolbar.setInput(provider.getHighlight(), |
| graph.getTableView().getTable(), provider.getCommits()); |
| } |
| } |
| |
| } |
| |
| @Override |
| public void createControl(final Composite parent) { |
| trace = GitTraceLocation.HISTORYVIEW.isActive(); |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| |
| attachSelectionTracker(); |
| |
| historyControl = createMainPanel(parent); |
| |
| warningComposite = new Composite(historyControl, SWT.NONE); |
| warningComposite.setLayout(new GridLayout(2, false)); |
| warningComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, |
| true, false)); |
| warningLabel = new CLabel(warningComposite, SWT.NONE); |
| warningLabel.setImage(PlatformUI.getWorkbench().getSharedImages() |
| .getImage(ISharedImages.IMG_OBJS_WARN_TSK)); |
| warningLabel |
| .setToolTipText(UIText.GitHistoryPage_IncompleteListTooltip); |
| |
| Link preferencesLink = new Link(warningComposite, SWT.NONE); |
| preferencesLink.setText(UIText.GitHistoryPage_PreferencesLink); |
| preferencesLink.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| String preferencePageId = "org.eclipse.egit.ui.internal.preferences.HistoryPreferencePage"; //$NON-NLS-1$ |
| PreferenceDialog dialog = PreferencesUtil |
| .createPreferenceDialogOn(getSite().getShell(), preferencePageId, |
| new String[] { preferencePageId }, null); |
| dialog.open(); |
| } |
| }); |
| |
| GridDataFactory.fillDefaults().grab(true, true).applyTo(historyControl); |
| graphDetailSplit = new SashForm(historyControl, SWT.VERTICAL); |
| GridDataFactory.fillDefaults().grab(true, true).applyTo( |
| graphDetailSplit); |
| graph = new CommitGraphTable(graphDetailSplit, getSite(), popupMgr, |
| this, resources); |
| |
| Activator.getDefault().getPreferenceStore() |
| .addPropertyChangeListener(listener); |
| |
| revInfoSplit = new SashForm(graphDetailSplit, SWT.HORIZONTAL); |
| |
| commentAndDiffScrolledComposite = new ScrolledComposite(revInfoSplit, |
| SWT.H_SCROLL | SWT.V_SCROLL); |
| commentAndDiffScrolledComposite.setExpandHorizontal(true); |
| commentAndDiffScrolledComposite.setExpandVertical(true); |
| |
| commentAndDiffComposite = new Composite(commentAndDiffScrolledComposite, SWT.NONE); |
| commentAndDiffScrolledComposite.setContent(commentAndDiffComposite); |
| commentAndDiffComposite.setLayout(GridLayoutFactory.fillDefaults() |
| .create()); |
| |
| commentViewer = new CommitMessageViewer(commentAndDiffComposite, |
| getPartSite()); |
| commentViewer.getControl().setLayoutData( |
| GridDataFactory.fillDefaults().grab(true, false).create()); |
| |
| commentViewer.addTextListener(new ITextListener() { |
| @Override |
| public void textChanged(TextEvent event) { |
| resizeCommentAndDiffScrolledComposite(); |
| } |
| }); |
| |
| commentAndDiffComposite.setBackground(commentViewer.getControl() |
| .getBackground()); |
| |
| |
| HyperlinkSourceViewer.Configuration configuration = new HyperlinkSourceViewer.Configuration( |
| EditorsUI.getPreferenceStore()) { |
| |
| @Override |
| public int getHyperlinkStateMask(ISourceViewer sourceViewer) { |
| return SWT.NONE; |
| } |
| |
| @Override |
| protected IHyperlinkDetector[] internalGetHyperlinkDetectors( |
| ISourceViewer sourceViewer) { |
| IHyperlinkDetector[] registered = super.internalGetHyperlinkDetectors( |
| sourceViewer); |
| // Always add our special detector for commit hyperlinks; we |
| // want those to show always. |
| if (registered == null) { |
| return new IHyperlinkDetector[] { |
| new CommitMessageViewer.KnownHyperlinksDetector() }; |
| } else { |
| IHyperlinkDetector[] result = new IHyperlinkDetector[registered.length |
| + 1]; |
| System.arraycopy(registered, 0, result, 0, |
| registered.length); |
| result[registered.length] = new CommitMessageViewer.KnownHyperlinksDetector(); |
| return result; |
| } |
| } |
| |
| @Override |
| public String[] getConfiguredContentTypes( |
| ISourceViewer sourceViewer) { |
| return new String[] { IDocument.DEFAULT_CONTENT_TYPE, |
| CommitMessageViewer.HEADER_CONTENT_TYPE, |
| CommitMessageViewer.FOOTER_CONTENT_TYPE }; |
| } |
| |
| @Override |
| public IPresentationReconciler getPresentationReconciler( |
| ISourceViewer viewer) { |
| PresentationReconciler reconciler = new PresentationReconciler(); |
| reconciler.setDocumentPartitioning( |
| getConfiguredDocumentPartitioning(viewer)); |
| DefaultDamagerRepairer hyperlinkDamagerRepairer = new DefaultDamagerRepairer( |
| new HyperlinkTokenScanner(this, viewer)); |
| reconciler.setDamager(hyperlinkDamagerRepairer, |
| IDocument.DEFAULT_CONTENT_TYPE); |
| reconciler.setRepairer(hyperlinkDamagerRepairer, |
| IDocument.DEFAULT_CONTENT_TYPE); |
| TextAttribute headerDefault = new TextAttribute( |
| PlatformUI.getWorkbench().getDisplay() |
| .getSystemColor(SWT.COLOR_DARK_GRAY)); |
| DefaultDamagerRepairer headerDamagerRepairer = new DefaultDamagerRepairer( |
| new HyperlinkTokenScanner(this, viewer, headerDefault)); |
| reconciler.setDamager(headerDamagerRepairer, |
| CommitMessageViewer.HEADER_CONTENT_TYPE); |
| reconciler.setRepairer(headerDamagerRepairer, |
| CommitMessageViewer.HEADER_CONTENT_TYPE); |
| DefaultDamagerRepairer footerDamagerRepairer = new DefaultDamagerRepairer( |
| new FooterTokenScanner(this, viewer)); |
| reconciler.setDamager(footerDamagerRepairer, |
| CommitMessageViewer.FOOTER_CONTENT_TYPE); |
| reconciler.setRepairer(footerDamagerRepairer, |
| CommitMessageViewer.FOOTER_CONTENT_TYPE); |
| return reconciler; |
| } |
| |
| }; |
| |
| commentViewer.configure(configuration); |
| |
| diffViewer = new DiffViewer(commentAndDiffComposite, null, SWT.NONE); |
| diffViewer.configure( |
| new DiffViewer.Configuration(EditorsUI.getPreferenceStore())); |
| diffViewer.getControl().setLayoutData( |
| GridDataFactory.fillDefaults().grab(true, false).create()); |
| |
| setWrap(store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_COMMENT_WRAP)); |
| |
| commentAndDiffScrolledComposite.addControlListener(new ControlAdapter() { |
| @Override |
| public void controlResized(ControlEvent e) { |
| if (!resizing && commentViewer.getTextWidget() |
| .getWordWrap()) { |
| resizeCommentAndDiffScrolledComposite(); |
| } |
| } |
| }); |
| |
| fileViewer = new CommitFileDiffViewer(revInfoSplit, getSite()); |
| fileViewer.addSelectionChangedListener(new ISelectionChangedListener() { |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| ISelection selection = event.getSelection(); |
| List<FileDiff> diffs = new ArrayList<>(); |
| if (selection instanceof IStructuredSelection) { |
| IStructuredSelection sel = (IStructuredSelection) selection; |
| for (Object obj : sel.toList()) |
| if (obj instanceof FileDiff) |
| diffs.add((FileDiff) obj); |
| } |
| formatDiffs(diffs); |
| } |
| }); |
| |
| layoutSashForm(graphDetailSplit, |
| UIPreferences.RESOURCEHISTORY_GRAPH_SPLIT); |
| layoutSashForm(revInfoSplit, UIPreferences.RESOURCEHISTORY_REV_SPLIT); |
| |
| attachCommitSelectionChanged(); |
| initActions(); |
| |
| getSite().setSelectionProvider( |
| new RepositorySelectionProvider(graph.getTableView(), () -> { |
| HistoryPageInput myInput = getInputInternal(); |
| return myInput != null ? myInput.getRepository() : null; |
| })); |
| getSite().registerContextMenu(POPUP_ID, popupMgr, graph.getTableView()); |
| // Track which of our controls has the focus, so that we can focus the |
| // last focused one in setFocus(). |
| focusTracker = new FocusTracker(); |
| trackFocus(graph.getTable()); |
| trackFocus(diffViewer.getControl()); |
| trackFocus(commentViewer.getControl()); |
| trackFocus(fileViewer.getControl()); |
| layout(); |
| |
| myRefsChangedHandle = Repository.getGlobalListenerList() |
| .addRefsChangedListener(this); |
| |
| IToolBarManager manager = getSite().getActionBars().getToolBarManager(); |
| searchBar = new SearchBar(GitHistoryPage.class.getName() + ".searchBar", //$NON-NLS-1$ |
| graph, actions.findAction, getSite().getActionBars()); |
| manager.prependToGroup("org.eclipse.team.ui.historyView", searchBar); //$NON-NLS-1$ |
| getSite().getActionBars().updateActionBars(); |
| if (trace) |
| GitTraceLocation.getTrace().traceExit( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| } |
| |
| private void trackFocus(Control control) { |
| if (control != null) { |
| focusTracker.addToFocusTracking(control); |
| } |
| } |
| |
| private void layoutSashForm(final SashForm sf, final String key) { |
| sf.addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| final int[] w = sf.getWeights(); |
| store.putValue(key, UIPreferences.intArrayToString(w)); |
| if (store.needsSaving()) |
| try { |
| store.save(); |
| } catch (IOException e1) { |
| Activator.handleError(e1.getMessage(), e1, false); |
| } |
| |
| } |
| }); |
| int[] weights = UIPreferences.stringToIntArray(store.getString(key), 2); |
| if (weights == null) { |
| // Corrupted preferences? |
| weights = UIPreferences |
| .stringToIntArray(store.getDefaultString(key), 2); |
| } |
| sf.setWeights(weights); |
| } |
| |
| private Composite createMainPanel(final Composite parent) { |
| topControl = new Composite(parent, SWT.NONE); |
| StackLayout layout = new StackLayout(); |
| topControl.setLayout(layout); |
| |
| final Composite c = new Composite(topControl, SWT.NULL); |
| layout.topControl = c; |
| // shown instead of the splitter if an error message was set |
| errorText = new StyledText(topControl, SWT.NONE); |
| // use the same font as in message viewer |
| errorText.setFont(UIUtils |
| .getFont(UIPreferences.THEME_CommitMessageFont)); |
| |
| final GridLayout parentLayout = new GridLayout(); |
| parentLayout.marginHeight = 0; |
| parentLayout.marginWidth = 0; |
| parentLayout.verticalSpacing = 0; |
| c.setLayout(parentLayout); |
| return c; |
| } |
| |
| private void layout() { |
| final boolean showComment = store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_REV_COMMENT); |
| final boolean showFiles = store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_REV_DETAIL); |
| |
| if (showComment && showFiles) { |
| graphDetailSplit.setMaximizedControl(null); |
| revInfoSplit.setMaximizedControl(null); |
| } else if (showComment && !showFiles) { |
| graphDetailSplit.setMaximizedControl(null); |
| revInfoSplit.setMaximizedControl(commentViewer.getControl()); |
| } else if (!showComment && showFiles) { |
| graphDetailSplit.setMaximizedControl(null); |
| revInfoSplit.setMaximizedControl(fileViewer.getControl()); |
| } else if (!showComment && !showFiles) |
| graphDetailSplit.setMaximizedControl(graph.getControl()); |
| historyControl.layout(); |
| } |
| |
| private void attachCommitSelectionChanged() { |
| graph.addSelectionChangedListener(new ISelectionChangedListener() { |
| @Override |
| public void selectionChanged(final SelectionChangedEvent event) { |
| final ISelection s = event.getSelection(); |
| if (s.isEmpty() || !(s instanceof IStructuredSelection)) { |
| commentViewer.setInput(null); |
| fileViewer.setInput(null); |
| return; |
| } |
| |
| final IStructuredSelection sel = ((IStructuredSelection) s); |
| if (sel.size() > 1) { |
| commentViewer.setInput(null); |
| fileViewer.setInput(null); |
| return; |
| } |
| if (input == null) { |
| return; |
| } |
| final SWTCommit c = (SWTCommit) sel.getFirstElement(); |
| if (c == commentViewer.getInput()) { |
| return; |
| } |
| commentViewer.setInput(c); |
| try (RevWalk walk = new RevWalk(input.getRepository())) { |
| final RevCommit unfilteredCommit = walk.parseCommit(c); |
| for (RevCommit parent : unfilteredCommit.getParents()) |
| walk.parseBody(parent); |
| fileViewer.setInput(new FileDiffInput(input.getRepository(), |
| fileDiffWalker, unfilteredCommit, |
| fileViewerInterestingPaths, |
| input.getSingleFile() != null)); |
| } catch (IOException e) { |
| fileViewer.setInput(new FileDiffInput(input.getRepository(), |
| fileDiffWalker, c, fileViewerInterestingPaths, |
| input.getSingleFile() != null)); |
| } |
| } |
| }); |
| commentViewer |
| .addCommitNavigationListener(new CommitNavigationListener() { |
| @Override |
| public void showCommit(final RevCommit c) { |
| graph.selectCommit(c); |
| } |
| }); |
| } |
| |
| /** |
| * Attaches the selection tracker to the workbench page containing this page. |
| */ |
| private void attachSelectionTracker() { |
| if (selectionTracker == null) { |
| selectionTracker = new GitHistorySelectionTracker(); |
| selectionTracker.attach(getSite().getPage()); |
| } |
| } |
| |
| /** |
| * Detaches the selection tracker from the workbench page, if necessary. |
| */ |
| private void detachSelectionTracker() { |
| if (selectionTracker != null) { |
| selectionTracker.detach(getSite().getPage()); |
| } |
| } |
| |
| private void initActions() { |
| try { |
| showAllFilter = ShowFilter.valueOf(Activator.getDefault() |
| .getPreferenceStore().getString(PREF_SHOWALLFILTER)); |
| } catch (IllegalArgumentException e) { |
| showAllFilter = ShowFilter.SHOWALLRESOURCE; |
| } |
| |
| actions = new GitHistoryPageActions(this); |
| setupToolBar(); |
| setupViewMenu(); |
| } |
| |
| private void setupToolBar() { |
| IToolBarManager mgr = getSite().getActionBars().getToolBarManager(); |
| mgr.add(actions.findAction); |
| mgr.add(actions.switchRepositoryAction); |
| mgr.add(new Separator()); |
| mgr.add(actions.showAllRepoVersionsAction); |
| mgr.add(actions.showAllProjectVersionsAction); |
| mgr.add(actions.showAllFolderVersionsAction); |
| mgr.add(actions.showAllResourceVersionsAction); |
| mgr.add(new Separator()); |
| mgr.add(actions.compareModeAction); |
| mgr.add(actions.showAllBranchesAction); |
| } |
| |
| private class ColumnAction extends Action implements IUpdate { |
| |
| private final int columnIndex; |
| |
| public ColumnAction(String text, int idx) { |
| super(text, IAction.AS_CHECK_BOX); |
| columnIndex = idx; |
| update(); |
| } |
| |
| @Override |
| public void run() { |
| graph.setVisible(columnIndex, isChecked()); |
| } |
| |
| @Override |
| public void update() { |
| setChecked(graph.getTableView().getTable().getColumn(columnIndex) |
| .getWidth() > 0); |
| } |
| } |
| |
| private void setupViewMenu() { |
| IMenuManager viewMenuMgr = getSite().getActionBars().getMenuManager(); |
| viewMenuMgr.add(actions.refreshAction); |
| |
| viewMenuMgr.add(new Separator()); |
| IMenuManager columnsMenuMgr = new MenuManager( |
| UIText.GitHistoryPage_ColumnsSubMenuLabel); |
| viewMenuMgr.add(columnsMenuMgr); |
| TableColumn[] columns = graph.getTableView().getTable().getColumns(); |
| for (int i = 0; i < columns.length; i++) { |
| if (i != 1) { |
| ColumnAction action = new ColumnAction(columns[i].getText(), i); |
| columnsMenuMgr.add(action); |
| columns[i].addListener(SWT.Resize, event -> { |
| action.update(); |
| }); |
| } |
| } |
| IMenuManager showSubMenuMgr = new MenuManager( |
| UIText.GitHistoryPage_ShowSubMenuLabel); |
| viewMenuMgr.add(showSubMenuMgr); |
| showSubMenuMgr.add(actions.showAllBranchesAction); |
| showSubMenuMgr.add(actions.showAdditionalRefsAction); |
| showSubMenuMgr.add(actions.showNotesAction); |
| showSubMenuMgr.add(actions.followRenamesAction); |
| showSubMenuMgr.add(new Separator()); |
| showSubMenuMgr.add(actions.findAction); |
| showSubMenuMgr.add(actions.showCommentAction); |
| showSubMenuMgr.add(actions.showFilesAction); |
| showSubMenuMgr.add(new Separator()); |
| showSubMenuMgr.add(actions.showRelativeDateAction); |
| showSubMenuMgr.add(actions.showEmailAddressesAction); |
| |
| IMenuManager showInMessageManager = new MenuManager( |
| UIText.GitHistoryPage_InRevisionCommentSubMenuLabel); |
| showSubMenuMgr.add(showInMessageManager); |
| showInMessageManager.add(actions.showBranchSequenceAction); |
| showInMessageManager.add(actions.showTagSequenceAction); |
| showInMessageManager.add(actions.wrapCommentAction); |
| showInMessageManager.add(actions.fillCommentAction); |
| |
| IMenuManager filterSubMenuMgr = new MenuManager( |
| UIText.GitHistoryPage_FilterSubMenuLabel); |
| viewMenuMgr.add(filterSubMenuMgr); |
| filterSubMenuMgr.add(actions.showAllRepoVersionsAction); |
| filterSubMenuMgr.add(actions.showAllProjectVersionsAction); |
| filterSubMenuMgr.add(actions.showAllFolderVersionsAction); |
| filterSubMenuMgr.add(actions.showAllResourceVersionsAction); |
| |
| viewMenuMgr.add(new Separator()); |
| viewMenuMgr.add(actions.compareModeAction); |
| viewMenuMgr.add(actions.reuseCompareEditorAction); |
| } |
| |
| @Override |
| public void dispose() { |
| trace = GitTraceLocation.HISTORYVIEW.isActive(); |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| |
| if (focusTracker != null) { |
| focusTracker.dispose(); |
| focusTracker = null; |
| } |
| detachSelectionTracker(); |
| |
| Activator.getDefault().getPreferenceStore() |
| .removePropertyChangeListener(listener); |
| |
| if (myRefsChangedHandle != null) { |
| myRefsChangedHandle.remove(); |
| myRefsChangedHandle = null; |
| } |
| |
| resources.dispose(); |
| |
| // dispose of the actions (the history framework doesn't do this for us) |
| for (IWorkbenchAction action : actions.actionsToDispose) |
| action.dispose(); |
| actions.actionsToDispose.clear(); |
| releaseGenerateHistoryJob(); |
| if (popupMgr != null) { |
| for (final IContributionItem i : popupMgr.getItems()) |
| if (i instanceof IWorkbenchAction) |
| ((IWorkbenchAction) i).dispose(); |
| for (final IContributionItem i : getSite().getActionBars() |
| .getMenuManager().getItems()) |
| if (i instanceof IWorkbenchAction) |
| ((IWorkbenchAction) i).dispose(); |
| } |
| renameTracker.reset(null); |
| if (job != null) { |
| job.cancel(); |
| job = null; |
| } |
| Job.getJobManager().cancel(JobFamilies.HISTORY_DIFF); |
| super.dispose(); |
| } |
| |
| @Override |
| public void setFocus() { |
| if (repoHasBeenRemoved(currentRepo)) { |
| clearHistoryPage(); |
| graph.getControl().setFocus(); |
| } else { |
| Control control = focusTracker.getLastFocusControl(); |
| if (control == null) { |
| control = graph.getControl(); |
| } |
| control.setFocus(); |
| } |
| } |
| |
| private boolean repoHasBeenRemoved(final Repository repo) { |
| return (repo != null && repo.getDirectory() != null && !repo |
| .getDirectory().exists()); |
| } |
| |
| private void clearHistoryPage() { |
| currentRepo = null; |
| name = ""; //$NON-NLS-1$ |
| input = null; |
| commentViewer.setInput(null); |
| fileViewer.setInput(null); |
| setInput(null); |
| } |
| |
| private void clearViewers() { |
| TableViewer viewer = graph.getTableView(); |
| viewer.setInput(new SWTCommit[0]); |
| // Force a selection changed event |
| viewer.setSelection(viewer.getSelection()); |
| } |
| |
| @Override |
| public Control getControl() { |
| return topControl; |
| } |
| |
| @Override |
| public void refresh() { |
| if (repoHasBeenRemoved(currentRepo)) { |
| clearHistoryPage(); |
| } |
| this.input = null; |
| inputSet(); |
| } |
| |
| /** |
| * @param compareMode |
| * switch compare mode button of the view on / off |
| */ |
| public void setCompareMode(boolean compareMode) { |
| store.setValue(UIPreferences.RESOURCEHISTORY_COMPARE_MODE, compareMode); |
| } |
| |
| /** |
| * @return the selection provider |
| */ |
| public ISelectionProvider getSelectionProvider() { |
| return graph.getTableView(); |
| } |
| |
| @Override |
| public void onRefsChanged(final RefsChangedEvent e) { |
| if (input == null || e.getRepository() != input.getRepository()) |
| return; |
| |
| if (getControl().isDisposed()) |
| return; |
| |
| synchronized (this) { |
| if (refschangedRunnable == null) { |
| refschangedRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (!getControl().isDisposed()) { |
| if (GitTraceLocation.HISTORYVIEW.isActive()) |
| GitTraceLocation |
| .getTrace() |
| .trace( |
| GitTraceLocation.HISTORYVIEW |
| .getLocation(), |
| "Executing async repository changed event"); //$NON-NLS-1$ |
| refschangedRunnable = null; |
| initAndStartRevWalk( |
| !(e instanceof FetchHeadChangedEvent)); |
| } |
| } |
| }; |
| getControl().getDisplay().asyncExec(refschangedRunnable); |
| } |
| } |
| } |
| |
| /** |
| * Returns the last, tracked selection. If no selection has been tracked, |
| * returns the current selection in the active part. |
| * |
| * @return selection |
| */ |
| private IStructuredSelection getSelection() { |
| if (selectionTracker != null |
| && selectionTracker.getSelection() != null) { |
| return selectionTracker.getSelection(); |
| } |
| // fallback to current selection of the active part |
| ISelection selection = getSite().getPage().getSelection(); |
| if (selection != null) { |
| return SelectionUtils.getStructuredSelection(selection); |
| } |
| return null; |
| } |
| |
| /** |
| * <p> |
| * Determines the |
| * {@link SelectionUtils#getMostFittingInput(IStructuredSelection, Object) |
| * most fitting} HistoryPageInput for the {@link #getSelection() last |
| * selection} and the given object. Most fitting means that the input will |
| * contain all selected resources which are contained in the same repository |
| * as the given object. If no most fitting input can be determined, the |
| * given object is returned as is. |
| * </p> |
| * <p> |
| * This is a workaround for the limitation of the GenericHistoryView that |
| * only forwards the first part of a selection and adapts it immediately to |
| * an {@link IResource}. |
| * </p> |
| * |
| * @param object |
| * The object to which the HistoryPageInput is tailored |
| * @return the most fitting history input |
| * @see SelectionUtils#getMostFittingInput(IStructuredSelection, Object) |
| */ |
| private Object getMostFittingInput(Object object) { |
| IStructuredSelection selection = getSelection(); |
| if (selection != null && !selection.isEmpty()) { |
| HistoryPageInput mostFittingInput = SelectionUtils |
| .getMostFittingInput(selection, object); |
| if (mostFittingInput != null) { |
| return mostFittingInput; |
| } |
| } |
| return object; |
| } |
| |
| @Override |
| public boolean setInput(Object object) { |
| try { |
| Object useAsInput = getMostFittingInput(object); |
| // reset tracked selection after it has been used to avoid wrong behavior |
| if (selectionTracker != null) { |
| selectionTracker.clearSelection(); |
| } |
| // hide the warning text initially |
| setWarningText(null); |
| trace = GitTraceLocation.HISTORYVIEW.isActive(); |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation(), useAsInput); |
| |
| if (useAsInput == getInput()) |
| return true; |
| this.input = null; |
| return super.setInput(useAsInput); |
| } finally { |
| if (trace) |
| GitTraceLocation.getTrace().traceExit( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| } |
| } |
| |
| @Override |
| public boolean inputSet() { |
| try { |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| if (this.input != null) |
| return true; |
| |
| Object o = super.getInput(); |
| if (o == null) { |
| setErrorMessage(UIText.GitHistoryPage_NoInputMessage); |
| return false; |
| } |
| |
| boolean showHead = false; |
| boolean showRef = false; |
| boolean showTag = false; |
| Repository repo = null; |
| RevCommit selection = null; |
| Ref ref = null; |
| if (o instanceof IResource) { |
| RepositoryMapping mapping = RepositoryMapping |
| .getMapping((IResource) o); |
| if (mapping != null) { |
| repo = mapping.getRepository(); |
| input = new HistoryPageInput(repo, |
| new IResource[] { (IResource) o }); |
| showHead = true; |
| } |
| } else if (o instanceof RepositoryTreeNode) { |
| RepositoryTreeNode repoNode = (RepositoryTreeNode) o; |
| repo = repoNode.getRepository(); |
| switch (repoNode.getType()) { |
| case FILE: |
| File file = ((FileNode) repoNode).getObject(); |
| input = new HistoryPageInput(repo, new File[] { file }); |
| showHead = true; |
| break; |
| case FOLDER: |
| File folder = ((FolderNode) repoNode).getObject(); |
| input = new HistoryPageInput(repo, new File[] { folder }); |
| showHead = true; |
| break; |
| case REF: |
| input = new HistoryPageInput(repo); |
| ref = ((RefNode) repoNode).getObject(); |
| showRef = true; |
| break; |
| case ADDITIONALREF: |
| input = new HistoryPageInput(repo); |
| ref = ((AdditionalRefNode) repoNode).getObject(); |
| if (ref.getObjectId() == null) { |
| ref = null; |
| } |
| showRef = ref != null; |
| break; |
| case TAG: |
| input = new HistoryPageInput(repo); |
| ref = ((TagNode) repoNode).getObject(); |
| showTag = true; |
| break; |
| default: |
| input = new HistoryPageInput(repo); |
| showHead = true; |
| break; |
| } |
| } else if (o instanceof HistoryPageInput) { |
| input = (HistoryPageInput) o; |
| } else { |
| IResource resource = AdapterUtils.adaptToAnyResource(o); |
| if (resource != null) { |
| RepositoryMapping mapping = RepositoryMapping |
| .getMapping(resource); |
| if (mapping != null) { |
| repo = mapping.getRepository(); |
| input = new HistoryPageInput(repo, |
| new IResource[] { resource }); |
| } |
| } |
| } |
| if (repo == null) { |
| repo = AdapterUtils.adapt(o, Repository.class); |
| if (repo != null) { |
| File file = AdapterUtils.adapt(o, File.class); |
| if (file == null) { |
| input = new HistoryPageInput(repo); |
| } else { |
| input = new HistoryPageInput(repo, new File[] { file }); |
| } |
| } |
| } |
| selection = AdapterUtils.adapt(o, RevCommit.class); |
| |
| if (input == null) { |
| this.name = ""; //$NON-NLS-1$ |
| setErrorMessage(UIText.GitHistoryPage_NoInputMessage); |
| return false; |
| } |
| |
| final IResource[] inResources = input.getItems(); |
| final File[] inFiles = input.getFileList(); |
| |
| this.name = calculateName(input); |
| |
| // disable the filters if we have a Repository as input |
| boolean filtersActive = inResources != null || inFiles != null; |
| actions.showAllRepoVersionsAction.setEnabled(filtersActive); |
| actions.showAllProjectVersionsAction.setEnabled(filtersActive); |
| // the repository itself has no notion of projects |
| actions.showAllFolderVersionsAction.setEnabled(inResources != null); |
| actions.showAllResourceVersionsAction.setEnabled(filtersActive); |
| |
| setErrorMessage(null); |
| try { |
| initAndStartRevWalk(false); |
| } catch (IllegalStateException e) { |
| Activator.handleError(e.getMessage(), e, true); |
| return false; |
| } |
| |
| if (showHead) |
| showHead(repo); |
| if (showRef) |
| showRef(ref, repo); |
| if (showTag) |
| showTag(ref, repo); |
| if (selection != null) |
| graph.selectCommitStored(selection); |
| |
| return true; |
| } finally { |
| if (trace) |
| GitTraceLocation.getTrace().traceExit( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| } |
| } |
| |
| private void showHead(Repository repo) { |
| try (RevWalk rw = new RevWalk(repo)) { |
| ObjectId head = repo.resolve(Constants.HEAD); |
| if (head == null) |
| return; |
| RevCommit c = rw.parseCommit(head); |
| graph.selectCommitStored(c); |
| } catch (IOException e) { |
| Activator.handleError(e.getMessage(), e, true); |
| } |
| } |
| |
| private void showRef(Ref ref, Repository repo) { |
| try (RevWalk rw = new RevWalk(repo)) { |
| RevCommit c = rw.parseCommit(ref.getLeaf().getObjectId()); |
| graph.selectCommit(c); |
| } catch (IOException e) { |
| Activator.handleError(e.getMessage(), e, true); |
| } |
| } |
| |
| private void showTag(Ref ref, Repository repo) { |
| try (RevWalk rw = new RevWalk(repo)) { |
| RevCommit c = null; |
| RevObject any = rw.parseAny(ref.getLeaf().getObjectId()); |
| if (any instanceof RevCommit) |
| c = (RevCommit) any; |
| else if (any instanceof RevTag) { |
| RevTag t = rw.parseTag(any); |
| Object anyCommit = rw.parseAny(t.getObject()); |
| if (anyCommit instanceof RevCommit) |
| c = (RevCommit) anyCommit; |
| } |
| if (c != null) |
| graph.selectCommit(c); |
| } catch (IOException e) { |
| Activator.handleError(e.getMessage(), e, true); |
| } |
| } |
| |
| private static String calculateName(HistoryPageInput in) { |
| // we always visualize the current input in the form |
| // <type>: <path> [<respository name>] |
| // in order to give the user an understanding which context |
| // menus they can expect with the current input |
| // we show the filter hint only upon getDescription() |
| // as it wrongly pollutes the navigation history |
| final String repositoryName = Activator.getDefault() |
| .getRepositoryUtil().getRepositoryName(in.getRepository()); |
| if (in.getItems() == null && in.getFileList() == null) |
| // plain repository, no files specified |
| return NLS.bind(UIText.GitHistoryPage_RepositoryNamePattern, |
| repositoryName); |
| else if (in.getItems() != null && in.getItems().length == 1) { |
| // single resource |
| IResource resource = in.getItems()[0]; |
| final String type; |
| switch (resource.getType()) { |
| case IResource.FILE: |
| type = UIText.GitHistoryPage_FileType; |
| break; |
| case IResource.PROJECT: |
| type = UIText.GitHistoryPage_ProjectType; |
| break; |
| default: |
| type = UIText.GitHistoryPage_FolderType; |
| break; |
| } |
| String path = resource.getFullPath().makeRelative().toString(); |
| if (resource.getType() == IResource.FOLDER) |
| path = path + '/'; |
| return NLS.bind(NAME_PATTERN, new Object[] { type, path, |
| repositoryName }); |
| } else if (in.getFileList() != null && in.getFileList().length == 1) { |
| // single file from Repository |
| File resource = in.getFileList()[0]; |
| String path; |
| final String type; |
| if (resource.isDirectory()) { |
| type = UIText.GitHistoryPage_FolderType; |
| path = resource.getPath() + IPath.SEPARATOR; |
| } else { |
| type = UIText.GitHistoryPage_FileType; |
| path = resource.getPath(); |
| } |
| return NLS.bind(NAME_PATTERN, new Object[] { type, path, |
| repositoryName }); |
| } else { |
| // user has selected multiple resources and then hits Team->Show in |
| // History (the generic history view cannot deal with multiple |
| // selection) |
| int count = 0; |
| StringBuilder b = new StringBuilder(); |
| if (in.getItems() != null) { |
| count = in.getItems().length; |
| for (IResource res : in.getItems()) { |
| b.append(res.getFullPath()); |
| if (res.getType() == IResource.FOLDER) |
| b.append('/'); |
| // limit the total length |
| if (b.length() > 100) { |
| b.append("... "); //$NON-NLS-1$ |
| break; |
| } |
| b.append(", "); //$NON-NLS-1$ |
| } |
| } |
| if (in.getFileList() != null) { |
| count = in.getFileList().length; |
| for (File file : in.getFileList()) { |
| b.append(getRepoRelativePath(in.getRepository(), file)); |
| if (file.isDirectory()) |
| b.append('/'); |
| // limit the total length |
| if (b.length() > 100) { |
| b.append("... "); //$NON-NLS-1$ |
| break; |
| } |
| b.append(", "); //$NON-NLS-1$ |
| } |
| } |
| // trim off the last ", " (or " " if total length exceeded) |
| if (b.length() > 2) |
| b.setLength(b.length() - 2); |
| String multiResourcePrefix = NLS.bind( |
| UIText.GitHistoryPage_MultiResourcesType, Integer |
| .valueOf(count)); |
| return NLS.bind(NAME_PATTERN, new Object[] { multiResourcePrefix, |
| b.toString(), repositoryName }); |
| } |
| } |
| |
| private static String getRepoRelativePath(Repository repo, File file) { |
| IPath workdirPath = new Path(repo.getWorkTree().getPath()); |
| IPath filePath = new Path(file.getPath()).setDevice(null); |
| return filePath.removeFirstSegments(workdirPath.segmentCount()) |
| .toString(); |
| } |
| |
| /** |
| * @param message |
| * the message to display instead of the control |
| */ |
| public void setErrorMessage(final String message) { |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation(), message); |
| getHistoryPageSite().getShell().getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (topControl.isDisposed()) |
| return; |
| StackLayout layout = (StackLayout) topControl.getLayout(); |
| if (message != null) { |
| errorText.setText(message); |
| layout.topControl = errorText; |
| } else { |
| errorText.setText(""); //$NON-NLS-1$ |
| layout.topControl = historyControl; |
| } |
| topControl.layout(); |
| } |
| }); |
| if (trace) |
| GitTraceLocation.getTrace().traceExit( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| } |
| |
| @Override |
| public boolean isValidInput(final Object object) { |
| return canShowHistoryFor(object); |
| } |
| |
| @Override |
| public Object getAdapter(final Class adapter) { |
| return null; |
| } |
| |
| @Override |
| public String getDescription() { |
| // this doesn't seem to be rendered anywhere, but still... |
| String filterHint = null; |
| switch (showAllFilter) { |
| case SHOWALLREPO: |
| filterHint = UIText.GitHistoryPage_AllChangesInRepoHint; |
| break; |
| case SHOWALLPROJECT: |
| filterHint = UIText.GitHistoryPage_AllChangesInProjectHint; |
| break; |
| case SHOWALLFOLDER: |
| filterHint = UIText.GitHistoryPage_AllChangesInFolderHint; |
| break; |
| case SHOWALLRESOURCE: |
| filterHint = UIText.GitHistoryPage_AllChangesOfResourceHint; |
| break; |
| } |
| return NLS.bind(DESCRIPTION_PATTERN, getName(), filterHint); |
| } |
| |
| @Override |
| public String getName() { |
| return this.name; |
| } |
| |
| /** |
| * @return the internal input object, or <code>null</code> |
| */ |
| public HistoryPageInput getInputInternal() { |
| return this.input; |
| } |
| |
| void setWarningTextInUIThread(final Job j) { |
| graph.getControl().getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (!graph.getControl().isDisposed() && job == j) { |
| setWarningText(UIText.GitHistoryPage_ListIncompleteWarningMessage); |
| } |
| } |
| }); |
| } |
| |
| @SuppressWarnings("boxing") |
| void showCommitList(final Job j, final SWTCommitList list, |
| final SWTCommit[] asArray, final RevCommit toSelect, final boolean incomplete, final RevFlag highlightFlag) { |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation(), |
| new Object[] { list.size()}); |
| if (job != j || graph.getControl().isDisposed()) |
| return; |
| |
| graph.getControl().getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (!graph.getControl().isDisposed() && job == j) { |
| graph.setInput(highlightFlag, list, asArray, input, true); |
| if (toSelect != null) |
| graph.selectCommit(toSelect); |
| if (getFollowRenames()) |
| updateInterestingPathsOfFileViewer(); |
| if (trace) |
| GitTraceLocation.getTrace().trace( |
| GitTraceLocation.HISTORYVIEW.getLocation(), |
| "Setting input to table"); //$NON-NLS-1$ |
| final Object currentInput = getInput(); |
| searchBar.setInput(new ICommitsProvider() { |
| |
| @Override |
| public Object getSearchContext() { |
| return currentInput; |
| } |
| |
| @Override |
| public SWTCommit[] getCommits() { |
| return asArray; |
| } |
| |
| @Override |
| public RevFlag getHighlight() { |
| return highlightFlag; |
| } |
| }); |
| actions.findAction.setEnabled(true); |
| if (store.getBoolean( |
| UIPreferences.RESOURCEHISTORY_SHOW_FINDTOOLBAR)) { |
| searchBar.setVisible(true); |
| } |
| if (incomplete) |
| setWarningText(UIText.GitHistoryPage_ListIncompleteWarningMessage); |
| else |
| setWarningText(null); |
| setErrorMessage(null); |
| } |
| } |
| }); |
| if (trace) |
| GitTraceLocation.getTrace().traceExit( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| } |
| |
| private void updateInterestingPathsOfFileViewer() { |
| fileViewer.setInterestingPaths(fileViewerInterestingPaths); |
| fileViewer.refresh(); |
| } |
| |
| private void setWarningText(String warning) { |
| if (warningComposite == null || warningComposite.isDisposed()) |
| return; |
| GridData gd = (GridData) warningComposite.getLayoutData(); |
| gd.exclude = warning == null; |
| warningComposite.setVisible(!gd.exclude); |
| if (warning != null) |
| warningLabel.setText(warning); |
| else |
| warningLabel.setText(""); //$NON-NLS-1$ |
| warningComposite.getParent().layout(true); |
| } |
| |
| void initAndStartRevWalk(boolean forceNewWalk) throws IllegalStateException { |
| try { |
| if (trace) |
| GitTraceLocation.getTrace().traceEntry( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| |
| if (input == null) |
| return; |
| Repository db = input.getRepository(); |
| if (repoHasBeenRemoved(db)) { |
| clearHistoryPage(); |
| return; |
| } |
| |
| AnyObjectId headId = resolveHead(db, true); |
| if (headId == null) { |
| currentHeadId = null; |
| currentFetchHeadId = null; |
| currentRepo = db; |
| clearViewers(); |
| return; |
| } |
| AnyObjectId fetchHeadId = resolveFetchHead(db); |
| |
| List<FilterPath> paths = buildFilterPaths(input.getItems(), input |
| .getFileList(), db); |
| |
| boolean repoChanged = false; |
| if (!db.equals(currentRepo)) { |
| repoChanged = true; |
| currentRepo = db; |
| } |
| |
| if (forceNewWalk || repoChanged |
| || shouldRedraw(headId, fetchHeadId, paths)) { |
| releaseGenerateHistoryJob(); |
| |
| if (repoChanged) { |
| // Clear all viewers. Otherwise it may be possible that the |
| // user invokes a context menu command and due to to the |
| // highly asynchronous loading we end up with inconsistent |
| // diff computations trying to find the diff for a commit in |
| // the wrong repository. |
| clearViewers(); |
| } |
| |
| SWTWalk walk = createNewWalk(db, headId, fetchHeadId); |
| |
| fileDiffWalker = createFileWalker(walk, db, paths); |
| |
| loadInitialHistory(walk); |
| } else { |
| // needed for context menu and double click |
| graph.setHistoryPageInput(input); |
| } |
| } finally { |
| if (trace) |
| GitTraceLocation.getTrace().traceExit( |
| GitTraceLocation.HISTORYVIEW.getLocation()); |
| |
| } |
| } |
| |
| private boolean shouldRedraw(AnyObjectId headId, AnyObjectId fetchHeadId, |
| List<FilterPath> paths) { |
| boolean pathChanged = pathChanged(pathFilters, paths); |
| boolean headChanged = headId == null || !headId.equals(currentHeadId); |
| |
| boolean allBranchesChanged = currentShowAllBranches != store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_ALL_BRANCHES); |
| currentShowAllBranches = store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_ALL_BRANCHES); |
| |
| boolean additionalRefsChange = currentShowAdditionalRefs != store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS); |
| currentShowAdditionalRefs = store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS); |
| boolean fetchHeadChanged = currentShowAdditionalRefs |
| && fetchHeadId != null |
| && !fetchHeadId.equals(currentFetchHeadId); |
| |
| boolean showNotesChanged = currentShowNotes != store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_NOTES); |
| currentShowNotes = store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_NOTES); |
| boolean followRenamesChanged = currentFollowRenames != getFollowRenames(); |
| currentFollowRenames = getFollowRenames(); |
| |
| return pathChanged || headChanged || fetchHeadChanged |
| || allBranchesChanged || additionalRefsChange |
| || showNotesChanged || followRenamesChanged; |
| } |
| |
| /** |
| * @return whether following renames is currently enabled |
| */ |
| protected boolean getFollowRenames() { |
| return store.getBoolean(UIPreferences.RESOURCEHISTORY_FOLLOW_RENAMES); |
| } |
| |
| private AnyObjectId resolveHead(Repository db, boolean acceptNull) { |
| AnyObjectId headId; |
| try { |
| headId = db.resolve(Constants.HEAD); |
| } catch (IOException e) { |
| throw new IllegalStateException(NLS.bind( |
| UIText.GitHistoryPage_errorParsingHead, Activator |
| .getDefault().getRepositoryUtil() |
| .getRepositoryName(db)), e); |
| } |
| if (headId == null && !acceptNull) |
| throw new IllegalStateException(NLS.bind( |
| UIText.GitHistoryPage_errorParsingHead, Activator |
| .getDefault().getRepositoryUtil() |
| .getRepositoryName(db))); |
| return headId; |
| } |
| |
| private AnyObjectId resolveFetchHead(Repository db) { |
| try { |
| return db.resolve(Constants.FETCH_HEAD); |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| |
| private ArrayList<FilterPath> buildFilterPaths(final IResource[] inResources, |
| final File[] inFiles, final Repository db) |
| throws IllegalStateException { |
| final ArrayList<FilterPath> paths; |
| if (inResources != null) { |
| paths = new ArrayList<>(inResources.length); |
| for (final IResource r : inResources) { |
| final RepositoryMapping map = RepositoryMapping.getMapping(r); |
| if (map == null) |
| continue; |
| if (db != map.getRepository()) |
| throw new IllegalStateException( |
| UIText.RepositoryAction_multiRepoSelection); |
| |
| if (showAllFilter == ShowFilter.SHOWALLFOLDER) { |
| final String path; |
| // if the resource's parent is the workspace root, we will |
| // get nonsense from map.getRepoRelativePath(), so we |
| // check here and use the project instead |
| if (r.getParent() instanceof IWorkspaceRoot) |
| path = map.getRepoRelativePath(r.getProject()); |
| else |
| path = map.getRepoRelativePath(r.getParent()); |
| if (path != null && path.length() > 0) |
| paths.add(new FilterPath(path, false)); |
| } else if (showAllFilter == ShowFilter.SHOWALLPROJECT) { |
| final String path = map.getRepoRelativePath(r.getProject()); |
| if (path != null && path.length() > 0) |
| paths.add(new FilterPath(path, false)); |
| } else if (showAllFilter == ShowFilter.SHOWALLREPO) { |
| // nothing |
| } else /* if (showAllFilter == ShowFilter.SHOWALLRESOURCE) */{ |
| final String path = map.getRepoRelativePath(r); |
| if (path != null && path.length() > 0) |
| paths.add(new FilterPath(path, r.getType() == IResource.FILE)); |
| } |
| } |
| } else if (inFiles != null) { |
| IPath workdirPath = new Path(db.getWorkTree().getPath()); |
| IPath gitDirPath = new Path(db.getDirectory().getPath()); |
| int segmentCount = workdirPath.segmentCount(); |
| paths = new ArrayList<>(inFiles.length); |
| for (File file : inFiles) { |
| IPath filePath; |
| boolean isRegularFile; |
| if (showAllFilter == ShowFilter.SHOWALLFOLDER) { |
| filePath = new Path(file.getParentFile().getPath()); |
| isRegularFile = false; |
| } else if (showAllFilter == ShowFilter.SHOWALLPROJECT |
| || showAllFilter == ShowFilter.SHOWALLREPO) |
| // we don't know of projects here -> treat as SHOWALLREPO |
| continue; |
| else /* if (showAllFilter == ShowFilter.SHOWALLRESOURCE) */{ |
| filePath = new Path(file.getPath()); |
| isRegularFile = file.isFile(); |
| } |
| |
| if (gitDirPath.isPrefixOf(filePath)) |
| throw new IllegalStateException( |
| NLS |
| .bind( |
| UIText.GitHistoryPage_FileOrFolderPartOfGitDirMessage, |
| filePath.toOSString())); |
| |
| IPath pathToAdd = filePath.removeFirstSegments(segmentCount) |
| .setDevice(null); |
| if (!pathToAdd.isEmpty()) |
| paths.add(new FilterPath(pathToAdd.toString(), isRegularFile)); |
| } |
| } else |
| paths = new ArrayList<>(0); |
| return paths; |
| } |
| |
| private boolean pathChanged(final List<FilterPath> o, final List<FilterPath> n) { |
| if (o == null) |
| return !n.isEmpty(); |
| return !o.equals(n); |
| } |
| |
| private @NonNull SWTWalk createNewWalk(Repository db, AnyObjectId headId, |
| AnyObjectId fetchHeadId) { |
| currentHeadId = headId; |
| currentFetchHeadId = fetchHeadId; |
| SWTWalk walk = new GitHistoryWalk(db, headId); |
| try { |
| if (store |
| .getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_ADDITIONAL_REFS)) |
| walk.addAdditionalRefs(db.getRefDatabase() |
| .getAdditionalRefs()); |
| walk.addAdditionalRefs(db.getRefDatabase() |
| .getRefsByPrefix(Constants.R_NOTES)); |
| } catch (IOException e) { |
| throw new IllegalStateException(NLS.bind( |
| UIText.GitHistoryPage_errorReadingAdditionalRefs, Activator |
| .getDefault().getRepositoryUtil() |
| .getRepositoryName(db)), e); |
| } |
| walk.sort(RevSort.COMMIT_TIME_DESC, true); |
| walk.sort(RevSort.BOUNDARY, true); |
| walk.setRetainBody(false); |
| return walk; |
| } |
| |
| private void formatDiffs(final List<FileDiff> diffs) { |
| Job.getJobManager().cancel(JobFamilies.HISTORY_DIFF); |
| if (diffs.isEmpty()) { |
| if (UIUtils.isUsable(diffViewer)) { |
| IDocument document = new Document(); |
| diffViewer.setDocument(document); |
| resizeCommentAndDiffScrolledComposite(); |
| } |
| return; |
| } |
| |
| Job formatJob = new Job(UIText.GitHistoryPage_FormatDiffJobName) { |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| int maxLines = Activator.getDefault().getPreferenceStore() |
| .getInt(UIPreferences.HISTORY_MAX_DIFF_LINES); |
| final DiffDocument document = new DiffDocument(); |
| try (DiffRegionFormatter formatter = new DiffRegionFormatter( |
| document, document.getLength(), maxLines)) { |
| SubMonitor progress = SubMonitor.convert(monitor, |
| diffs.size()); |
| for (FileDiff diff : diffs) { |
| if (progress.isCanceled() |
| || diff.getCommit().getParentCount() > 1 |
| || document.getNumberOfLines() > maxLines) { |
| break; |
| } |
| progress.subTask(diff.getPath()); |
| try { |
| formatter.write(diff); |
| } catch (IOException ignore) { |
| // Ignored |
| } |
| progress.worked(1); |
| } |
| if (progress.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| document.connect(formatter); |
| } |
| UIJob uiJob = new UIJob(UIText.GitHistoryPage_FormatDiffJobName) { |
| @Override |
| public IStatus runInUIThread(IProgressMonitor uiMonitor) { |
| if (uiMonitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| if (UIUtils.isUsable(diffViewer)) { |
| diffViewer.setDocument(document); |
| resizeCommentAndDiffScrolledComposite(); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return JobFamilies.HISTORY_DIFF.equals(family); |
| } |
| }; |
| uiJob.setRule(pageSchedulingRule); |
| GitHistoryPage.this.schedule(uiJob); |
| return Status.OK_STATUS; |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return JobFamilies.HISTORY_DIFF.equals(family); |
| } |
| }; |
| formatJob.setRule(pageSchedulingRule); |
| schedule(formatJob); |
| } |
| |
| private void setWrap(boolean wrap) { |
| commentViewer.getTextWidget().setWordWrap(wrap); |
| diffViewer.getTextWidget().setWordWrap(wrap); |
| resizeCommentAndDiffScrolledComposite(); |
| } |
| |
| private void resizeCommentAndDiffScrolledComposite() { |
| resizing = true; |
| long start = 0; |
| int lines = 0; |
| if (trace) { |
| IDocument document = diffViewer.getDocument(); |
| lines = document != null ? document.getNumberOfLines() : 0; |
| System.out.println("Lines: " + lines); //$NON-NLS-1$ |
| if (lines > 1) { |
| new Exception("resizeCommentAndDiffScrolledComposite") //$NON-NLS-1$ |
| .printStackTrace(System.out); |
| } |
| start = System.currentTimeMillis(); |
| } |
| |
| Point size = commentAndDiffComposite |
| .computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| commentAndDiffComposite.layout(); |
| commentAndDiffScrolledComposite.setMinSize(size); |
| resizing = false; |
| |
| if (trace) { |
| long stop = System.currentTimeMillis(); |
| long time = stop - start; |
| long lps = (lines * 1000) / (time + 1); |
| System.out |
| .println("Resize + diff: " + time + " ms, line/s: " + lps); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private TreeWalk createFileWalker(RevWalk walk, Repository db, List<FilterPath> paths) { |
| final TreeWalk fileWalker = new TreeWalk(db); |
| fileWalker.setRecursive(true); |
| fileWalker.setFilter(TreeFilter.ANY_DIFF); |
| if (store.getBoolean(UIPreferences.RESOURCEHISTORY_FOLLOW_RENAMES) |
| && !paths.isEmpty() |
| && allRegularFiles(paths)) { |
| pathFilters = paths; |
| |
| List<String> selectedPaths = new ArrayList<>(paths.size()); |
| for (FilterPath filterPath : paths) |
| selectedPaths.add(filterPath.getPath()); |
| |
| fileViewerInterestingPaths = new HashSet<>(selectedPaths); |
| TreeFilter followFilter = createFollowFilterFor(selectedPaths); |
| walk.setTreeFilter(followFilter); |
| walk.setRevFilter(renameTracker.getFilter()); |
| |
| } else if (paths.size() > 0) { |
| pathFilters = paths; |
| List<String> stringPaths = new ArrayList<>(paths.size()); |
| for (FilterPath p : paths) |
| stringPaths.add(p.getPath()); |
| |
| walk.setTreeFilter(AndTreeFilter.create(PathFilterGroup |
| .createFromStrings(stringPaths), TreeFilter.ANY_DIFF)); |
| fileViewerInterestingPaths = new HashSet<>(stringPaths); |
| } else { |
| pathFilters = null; |
| walk.setTreeFilter(TreeFilter.ALL); |
| fileViewerInterestingPaths = null; |
| } |
| return fileWalker; |
| } |
| |
| /** |
| * Creates a filter for the given files, will make sure that renames/copies |
| * of all files will be followed. |
| * @param paths the list of files to follow, must not be <code>null</code> or empty |
| * @return the ORed list of {@link FollowFilter follow filters} |
| */ |
| private TreeFilter createFollowFilterFor(List<String> paths) { |
| if (paths == null || paths.isEmpty()) |
| throw new IllegalArgumentException("paths must not be null nor empty"); //$NON-NLS-1$ |
| |
| DiffConfig diffConfig = currentRepo.getConfig().get(DiffConfig.KEY); |
| |
| List<TreeFilter> followFilters = new ArrayList<>(paths.size()); |
| for (String path : paths) |
| followFilters.add(createFollowFilter(path, diffConfig)); |
| |
| if (followFilters.size() == 1) { |
| FollowFilter followFilter = (FollowFilter) followFilters.get(0); |
| renameTracker.reset(followFilter.getPath()); |
| return followFilters.get(0); |
| } |
| |
| // TODO: this scenario is not supported by JGit: RewriteTreeFilter |
| // can not handle composite TreeFilters and expects a plain |
| // FollowFilter for rename detection. |
| return OrTreeFilter.create(followFilters); |
| } |
| |
| private FollowFilter createFollowFilter(String path, DiffConfig diffConfig) { |
| FollowFilter followFilter = FollowFilter.create(path, diffConfig); |
| followFilter.setRenameCallback(new RenameCallback() { |
| @Override |
| public void renamed(DiffEntry entry) { |
| renameTracker.getCallback().renamed(entry); |
| if (fileViewerInterestingPaths != null) { |
| fileViewerInterestingPaths.add(entry.getOldPath()); |
| fileViewerInterestingPaths.add(entry.getNewPath()); |
| } |
| } |
| }); |
| return followFilter; |
| } |
| |
| /** |
| * @return Returns <code>true</code> if <b>all</b> filterpaths refer to plain files, |
| * or if the list is empty. |
| * @param paths the paths to check |
| */ |
| private boolean allRegularFiles(List<FilterPath> paths) { |
| for (FilterPath filterPath : paths) |
| if (!filterPath.isRegularFile()) |
| return false; |
| return true; |
| } |
| |
| @Override |
| public void loadItem(int item) { |
| if (job != null && job.loadMoreItemsThreshold() < item) { |
| loadHistory(item); |
| } |
| } |
| |
| @Override |
| public void loadCommit(RevCommit c) { |
| if (job == null) |
| return; |
| job.setLoadHint(c); |
| if (trace) |
| GitTraceLocation.getTrace().trace( |
| GitTraceLocation.HISTORYVIEW.getLocation(), |
| "Scheduling GenerateHistoryJob"); //$NON-NLS-1$ |
| schedule(job); |
| } |
| |
| /** |
| * Load initial history items |
| * |
| * @param walk |
| * the revwalk, non null |
| */ |
| private void loadInitialHistory(@NonNull RevWalk walk) { |
| job = new GenerateHistoryJob(this, graph.getControl(), walk, resources); |
| job.setRule(pageSchedulingRule); |
| job.setLoadHint(INITIAL_ITEM); |
| if (trace) |
| GitTraceLocation.getTrace().trace( |
| GitTraceLocation.HISTORYVIEW.getLocation(), |
| "Scheduling initial GenerateHistoryJob"); //$NON-NLS-1$ |
| schedule(job); |
| } |
| |
| /** |
| * Load history items incrementally |
| * |
| * @param itemToLoad |
| * hint for index of item that should be loaded |
| */ |
| private void loadHistory(final int itemToLoad) { |
| if (job == null) { |
| return; |
| } |
| job.setLoadHint(itemToLoad); |
| if (trace) |
| GitTraceLocation.getTrace().trace( |
| GitTraceLocation.HISTORYVIEW.getLocation(), |
| "Scheduling incremental GenerateHistoryJob"); //$NON-NLS-1$ |
| schedule(job); |
| } |
| |
| private IWorkbenchPartSite getPartSite() { |
| final IWorkbenchPart part = getHistoryPageSite().getPart(); |
| IWorkbenchPartSite site = null; |
| if (part != null) |
| site = part.getSite(); |
| return site; |
| } |
| |
| private void schedule(final Job j) { |
| IWorkbenchPartSite site = getPartSite(); |
| if (site != null) { |
| final IWorkbenchSiteProgressService p; |
| p = AdapterUtils.adapt(site, IWorkbenchSiteProgressService.class); |
| if (p != null) { |
| p.schedule(j, 0, true /* use half-busy cursor */); |
| return; |
| } |
| } |
| j.schedule(); |
| } |
| |
| |
| private void releaseGenerateHistoryJob() { |
| if (job != null) { |
| if (job.getState() != Job.NONE) |
| job.cancel(); |
| job.release(); |
| job = null; |
| } |
| } |
| |
| @Override |
| public ShowInContext getShowInContext() { |
| if (fileViewer != null && fileViewer.getControl().isFocusControl()) |
| return fileViewer.getShowInContext(); |
| else |
| return null; |
| } |
| |
| @Override |
| public String[] getShowInTargetIds() { |
| return new String[] { IHistoryView.VIEW_ID }; |
| } |
| |
| /** |
| * Get renamed path in given commit with initial starting path |
| * |
| * @param path |
| * @param commit |
| * @return actual path in commit |
| */ |
| public String getRenamedPath(String path, ObjectId commit) { |
| return renameTracker.getPath(commit, path); |
| } |
| |
| private static class FooterTokenScanner extends HyperlinkTokenScanner { |
| |
| private static final Pattern ITALIC_LINE = Pattern |
| .compile("^[A-Z](?:[A-Za-z]+-)+by: "); //$NON-NLS-1$ |
| |
| private final IToken italicToken; |
| |
| public FooterTokenScanner(SourceViewerConfiguration configuration, |
| ISourceViewer viewer) { |
| super(configuration, viewer); |
| Object defaults = defaultToken.getData(); |
| TextAttribute italic; |
| if (defaults instanceof TextAttribute) { |
| TextAttribute defaultAttribute = (TextAttribute) defaults; |
| int style = defaultAttribute.getStyle() ^ SWT.ITALIC; |
| italic = new TextAttribute(defaultAttribute.getForeground(), |
| defaultAttribute.getBackground(), style, |
| defaultAttribute.getFont()); |
| } else { |
| italic = new TextAttribute(null, null, SWT.ITALIC); |
| } |
| italicToken = new Token(italic); |
| } |
| |
| @Override |
| protected IToken scanToken() { |
| // If we're at a "Signed-off-by" or similar footer line, make it |
| // italic. |
| try { |
| IRegion region = document |
| .getLineInformationOfOffset(currentOffset); |
| if (currentOffset == region.getOffset()) { |
| String line = document.get(currentOffset, |
| region.getLength()); |
| Matcher m = ITALIC_LINE.matcher(line); |
| if (m.find()) { |
| currentOffset = Math.min(endOfRange, |
| currentOffset + region.getLength()); |
| return italicToken; |
| } |
| } |
| } catch (BadLocationException e) { |
| // Ignore and return null below. |
| } |
| return null; |
| } |
| } |
| } |