| /******************************************************************************* |
| * Copyright (c) 2009, 2013 Shawn O. Pearce and others. |
| * 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.fetch; |
| |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.egit.ui.Activator; |
| import org.eclipse.egit.ui.UIUtils; |
| import org.eclipse.egit.ui.internal.CommonUtils; |
| import org.eclipse.egit.ui.internal.DecorationOverlayDescriptor; |
| import org.eclipse.egit.ui.internal.UIIcons; |
| import org.eclipse.egit.ui.internal.UIText; |
| import org.eclipse.egit.ui.internal.WorkbenchStyledLabelProvider; |
| import org.eclipse.egit.ui.internal.commit.CommitEditor; |
| import org.eclipse.egit.ui.internal.commit.RepositoryCommit; |
| import org.eclipse.egit.ui.internal.history.FileDiff; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.util.OpenStrategy; |
| import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; |
| import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; |
| import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; |
| import org.eclipse.jface.viewers.IDecoration; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StyledString; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerComparator; |
| import org.eclipse.jgit.lib.ConfigConstants; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectReader; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.notes.NoteMap; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.transport.FetchResult; |
| import org.eclipse.jgit.transport.TrackingRefUpdate; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.OpenAndLinkWithEditorHelper; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.model.WorkbenchAdapter; |
| import org.eclipse.ui.model.WorkbenchContentProvider; |
| |
| /** |
| * Component displaying table with results of fetch operation. |
| */ |
| class FetchResultTable { |
| |
| private class FetchResultAdapter extends WorkbenchAdapter { |
| |
| private final TrackingRefUpdate update; |
| |
| private Object[] children; |
| |
| public FetchResultAdapter(TrackingRefUpdate update) { |
| this.update = update; |
| } |
| |
| @Override |
| public String getLabel(Object object) { |
| return getStyledText(object).getString(); |
| } |
| |
| @Override |
| public ImageDescriptor getImageDescriptor(Object object) { |
| switch (update.getResult()) { |
| case IO_FAILURE: |
| case LOCK_FAILURE: |
| case REJECTED_CURRENT_BRANCH: |
| case REJECTED: |
| return PlatformUI.getWorkbench().getSharedImages() |
| .getImageDescriptor(ISharedImages.IMG_OBJS_ERROR_TSK); |
| case FORCED: |
| if (isPruned()) { |
| ImageDescriptor icon = UIIcons.BRANCH; |
| if (update.getLocalName().startsWith(Constants.R_TAGS)) |
| icon = UIIcons.TAG; |
| if (update.getLocalName().startsWith(Constants.R_NOTES)) |
| icon = UIIcons.NOTE; |
| return new DecorationOverlayDescriptor(icon, |
| UIIcons.OVR_STAGED_REMOVE, IDecoration.TOP_RIGHT); |
| } |
| // else |
| //$FALL-THROUGH$ |
| case RENAMED: |
| case FAST_FORWARD: |
| if (update.getRemoteName().startsWith(Constants.R_HEADS)) |
| return UIIcons.BRANCH; |
| if (update.getLocalName().startsWith(Constants.R_TAGS)) |
| return UIIcons.TAG; |
| if (update.getLocalName().startsWith(Constants.R_NOTES)) |
| return UIIcons.NOTE; |
| break; |
| case NEW: |
| if (update.getRemoteName().startsWith(Constants.R_HEADS)) |
| return UIIcons.CREATE_BRANCH; |
| if (update.getLocalName().startsWith(Constants.R_TAGS)) |
| return UIIcons.CREATE_TAG; |
| if (update.getLocalName().startsWith(Constants.R_NOTES)) |
| return UIIcons.NOTE; |
| break; |
| default: |
| return super.getImageDescriptor(object); |
| } |
| return super.getImageDescriptor(object); |
| } |
| |
| private void addCommits(StyledString styled, String separator) { |
| styled.append('[', StyledString.DECORATIONS_STYLER); |
| styled.append(safeAbbreviate(update.getNewObjectId()), |
| StyledString.DECORATIONS_STYLER); |
| styled.append(separator, StyledString.DECORATIONS_STYLER); |
| styled.append(safeAbbreviate(update.getOldObjectId()), |
| StyledString.DECORATIONS_STYLER); |
| styled.append(']', StyledString.DECORATIONS_STYLER); |
| |
| styled.append(MessageFormat.format( |
| UIText.FetchResultTable_counterCommits, |
| Integer.valueOf(getChildren(this).length)), |
| StyledString.COUNTER_STYLER); |
| } |
| |
| @Override |
| public Object[] getChildren(Object object) { |
| if (children != null) |
| return children; |
| |
| switch (update.getResult()) { |
| case FORCED: |
| if (isPruned()) |
| return NO_CHILDREN; |
| // else |
| //$FALL-THROUGH$ |
| case FAST_FORWARD: |
| try (RevWalk walk = new RevWalk(reader)) { |
| walk.setRetainBody(true); |
| walk.markStart(walk.parseCommit(update.getNewObjectId())); |
| walk.markUninteresting(walk.parseCommit(update |
| .getOldObjectId())); |
| List<RepositoryCommit> commits = new ArrayList<>(); |
| for (RevCommit commit : walk) |
| commits.add(new RepositoryCommit(repo, commit)); |
| children = commits.toArray(); |
| break; |
| } catch (IOException e) { |
| Activator.logError( |
| "Error parsing commits from fetch result", e); //$NON-NLS-1$ |
| } |
| //$FALL-THROUGH$ |
| default: |
| children = super.getChildren(object); |
| } |
| return children; |
| } |
| |
| /** |
| * Shorten ref name |
| * |
| * @param ref |
| * @return shortened ref name |
| */ |
| protected String shortenRef(final String ref) { |
| return NoteMap.shortenRefName(Repository.shortenRefName(ref)); |
| } |
| |
| @Override |
| public StyledString getStyledText(Object object) { |
| StyledString styled = new StyledString(); |
| final String remote = update.getRemoteName(); |
| final String local = update.getLocalName(); |
| styled.append(shortenRef(remote)); |
| styled.append(" : ", StyledString.QUALIFIER_STYLER); //$NON-NLS-1$ |
| styled.append(shortenRef(local), StyledString.QUALIFIER_STYLER); |
| styled.append(' '); |
| switch (update.getResult()) { |
| case LOCK_FAILURE: |
| styled.append(UIText.FetchResultTable_statusLockFailure, |
| StyledString.DECORATIONS_STYLER); |
| break; |
| case IO_FAILURE: |
| styled.append(UIText.FetchResultTable_statusIOError, |
| StyledString.DECORATIONS_STYLER); |
| break; |
| case NEW: |
| if (remote.startsWith(Constants.R_HEADS)) |
| styled.append(UIText.FetchResultTable_statusNewBranch, |
| StyledString.DECORATIONS_STYLER); |
| else if (local.startsWith(Constants.R_TAGS)) |
| styled.append(UIText.FetchResultTable_statusNewTag, |
| StyledString.DECORATIONS_STYLER); |
| else |
| styled.append(UIText.FetchResultTable_statusNew, |
| StyledString.DECORATIONS_STYLER); |
| break; |
| case FORCED: |
| if (isPruned()) |
| styled.append(UIText.FetchResultTable_statusPruned, |
| StyledString.DECORATIONS_STYLER); |
| else |
| addCommits(styled, "..."); //$NON-NLS-1$ |
| break; |
| case FAST_FORWARD: |
| addCommits(styled, ".."); //$NON-NLS-1$ |
| break; |
| case REJECTED: |
| styled.append(UIText.FetchResultTable_statusRejected, |
| StyledString.DECORATIONS_STYLER); |
| break; |
| case NO_CHANGE: |
| styled.append(UIText.FetchResultTable_statusUpToDate, |
| StyledString.DECORATIONS_STYLER); |
| break; |
| default: |
| break; |
| } |
| return styled; |
| } |
| |
| private boolean isPruned() { |
| return update.getNewObjectId().equals(ObjectId.zeroId()); |
| } |
| |
| public boolean isRemoteBranch(String branchName) { |
| return update.getRemoteName().equals(branchName); |
| } |
| } |
| |
| private final Composite treePanel; |
| |
| private final TreeViewer treeViewer; |
| |
| private Repository repo; |
| |
| private ObjectReader reader; |
| |
| private Map<ObjectId, String> abbrevations; |
| |
| private String remoteBranchOfCurrentBranch; |
| |
| @SuppressWarnings("unused") |
| FetchResultTable(final Composite parent) { |
| treePanel = new Composite(parent, SWT.NONE); |
| GridLayoutFactory.swtDefaults().numColumns(2).applyTo(treePanel); |
| treeViewer = new TreeViewer(treePanel); |
| treeViewer.setAutoExpandLevel(2); |
| |
| addToolbar(treePanel); |
| |
| final IStyledLabelProvider styleProvider = new WorkbenchStyledLabelProvider() { |
| |
| @Override |
| public StyledString getStyledText(Object element) { |
| // TODO Replace with use of IWorkbenchAdapter3 when is no longer |
| // supported |
| if (element instanceof FetchResultAdapter) |
| return ((FetchResultAdapter) element) |
| .getStyledText(element); |
| if (element instanceof RepositoryCommit) |
| return ((RepositoryCommit) element).getStyledText(element); |
| |
| return super.getStyledText(element); |
| } |
| }; |
| treeViewer.setLabelProvider(new DelegatingStyledCellLabelProvider( |
| styleProvider) { |
| |
| @Override |
| public String getToolTipText(final Object element) { |
| if (element instanceof FetchResultAdapter) { |
| switch (((FetchResultAdapter) element).update.getResult()) { |
| case FAST_FORWARD: |
| return UIText.FetchResultTable_statusDetailFastForward; |
| case FORCED: |
| case REJECTED: |
| return UIText.FetchResultTable_statusDetailNonFastForward; |
| case IO_FAILURE: |
| return UIText.FetchResultTable_statusDetailIOError; |
| case LOCK_FAILURE: |
| return UIText.FetchResultTable_statusDetailCouldntLock; |
| default: |
| return super.getToolTipText(element); |
| } |
| } |
| return super.getToolTipText(element); |
| } |
| |
| }); |
| treeViewer.setComparator(new ViewerComparator() { |
| |
| @Override |
| public int compare(Viewer viewer, Object e1, Object e2) { |
| // top level: branches and tags |
| if (e1 instanceof FetchResultAdapter |
| && e2 instanceof FetchResultAdapter) { |
| FetchResultAdapter f1 = (FetchResultAdapter) e1; |
| FetchResultAdapter f2 = (FetchResultAdapter) e2; |
| |
| // branches come before tags |
| if (f1.getChildren(f1).length > 0 |
| && f2.getChildren(f2).length == 0) |
| return -1; |
| if (f1.getChildren(f1).length == 0 |
| && f2.getChildren(f2).length > 0) |
| return 1; |
| |
| // currently checked out branch comes before other branches |
| if (f1.isRemoteBranch(remoteBranchOfCurrentBranch)) { |
| return -1; |
| } |
| if (f2.isRemoteBranch(remoteBranchOfCurrentBranch)) { |
| return 1; |
| } |
| |
| // otherwise sort by name |
| return CommonUtils.STRING_ASCENDING_COMPARATOR |
| .compare(f1.getLabel(f1), f2.getLabel(f2)); |
| } |
| |
| // nested inside of branches: don't change commit order |
| if (e1 instanceof RepositoryCommit |
| && e2 instanceof RepositoryCommit) |
| return 0; |
| |
| // nested inside commits: sort by path |
| if (e1 instanceof FileDiff && e2 instanceof FileDiff) { |
| FileDiff f1 = (FileDiff) e1; |
| FileDiff f2 = (FileDiff) e2; |
| return f1.getPath().compareTo(f2.getPath()); |
| } |
| |
| return super.compare(viewer, e1, e2); |
| } |
| |
| }); |
| ColumnViewerToolTipSupport.enableFor(treeViewer); |
| final Tree tree = treeViewer.getTree(); |
| GridDataFactory.fillDefaults().grab(true, true).applyTo(tree); |
| |
| treePanel.addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| if (reader != null) |
| reader.close(); |
| } |
| }); |
| |
| treeViewer.setContentProvider(new WorkbenchContentProvider() { |
| |
| @Override |
| public Object[] getElements(Object inputElement) { |
| if (inputElement == null) |
| return new FetchResultAdapter[0]; |
| |
| final FetchResult result = (FetchResult) inputElement; |
| TrackingRefUpdate[] updates = result.getTrackingRefUpdates() |
| .toArray(new TrackingRefUpdate[0]); |
| FetchResultAdapter[] elements = new FetchResultAdapter[updates.length]; |
| for (int i = 0; i < elements.length; i++) |
| elements[i] = new FetchResultAdapter(updates[i]); |
| return elements; |
| } |
| |
| @Override |
| public Object[] getChildren(Object element) { |
| if (element instanceof RepositoryCommit) { |
| return ((RepositoryCommit) element).getDiffs(); |
| } |
| return super.getChildren(element); |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| if (element instanceof RepositoryCommit) { |
| // always return true for commits to avoid commit diff |
| // calculation in UI thread, see bug 458839 |
| return true; |
| } |
| return super.hasChildren(element); |
| } |
| }); |
| |
| new OpenAndLinkWithEditorHelper(treeViewer) { |
| @Override |
| protected void linkToEditor(ISelection selection) { |
| // Not supported |
| |
| } |
| @Override |
| protected void open(ISelection selection, boolean activate) { |
| handleOpen(selection, OpenStrategy.activateOnOpen()); |
| } |
| @Override |
| protected void activate(ISelection selection) { |
| handleOpen(selection, true); |
| } |
| private void handleOpen(ISelection selection, boolean activateOnOpen) { |
| if (selection instanceof IStructuredSelection) |
| for (Object element : ((IStructuredSelection) selection) |
| .toArray()) |
| if (element instanceof RepositoryCommit) |
| CommitEditor.openQuiet((RepositoryCommit) element, activateOnOpen); |
| } |
| }; |
| } |
| |
| private void addToolbar(Composite parent) { |
| ToolBar toolbar = new ToolBar(parent, SWT.VERTICAL); |
| GridDataFactory.fillDefaults().grab(false, true).applyTo(toolbar); |
| UIUtils.addExpansionItems(toolbar, treeViewer); |
| } |
| |
| void setData(final Repository db, final FetchResult fetchResult) { |
| treeViewer.setInput(null); |
| repo = db; |
| reader = db.newObjectReader(); |
| abbrevations = new HashMap<>(); |
| try { |
| String branch = repo.getBranch(); |
| if (branch != null) { |
| remoteBranchOfCurrentBranch = repo.getConfig().getString( |
| ConfigConstants.CONFIG_BRANCH_SECTION, branch, |
| ConfigConstants.CONFIG_KEY_MERGE); |
| } |
| } catch (IOException e) { |
| remoteBranchOfCurrentBranch = null; |
| } |
| treeViewer.setInput(fetchResult); |
| } |
| |
| private String safeAbbreviate(ObjectId id) { |
| String abbrev = abbrevations.get(id); |
| if (abbrev == null) { |
| try { |
| abbrev = reader.abbreviate(id).name(); |
| } catch (IOException cannotAbbreviate) { |
| abbrev = id.name(); |
| } |
| abbrevations.put(id, abbrev); |
| } |
| return abbrev; |
| } |
| |
| Control getControl() { |
| return treePanel; |
| } |
| |
| } |