/******************************************************************************* * Copyright (c) 2010, 2013 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mathias Kinzler (SAP AG) - initial implementation * Chris Aniszczyk <caniszczyk@gmail.com> - added styled label support *******************************************************************************/ package org.eclipse.egit.ui.internal.repository; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import org.eclipse.core.commands.IStateListener; import org.eclipse.core.commands.State; import org.eclipse.core.runtime.IPath; import org.eclipse.egit.core.Activator; import org.eclipse.egit.core.RepositoryUtil; import org.eclipse.egit.ui.internal.CommonUtils; import org.eclipse.egit.ui.internal.GitLabels; import org.eclipse.egit.ui.internal.ResourcePropertyTester; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.repository.tree.AdditionalRefNode; import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode; import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNodeType; import org.eclipse.egit.ui.internal.repository.tree.StashedCommitNode; import org.eclipse.egit.ui.internal.repository.tree.SubmodulesNode; import org.eclipse.egit.ui.internal.repository.tree.TagNode; import org.eclipse.egit.ui.internal.repository.tree.command.ToggleBranchCommitCommand; import org.eclipse.jface.resource.CompositeImageDescriptor; 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.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; import org.eclipse.jface.viewers.StyledString; 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.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; /** * Label Provider for the Git Repositories View */ public class RepositoriesViewLabelProvider extends ColumnLabelProvider implements IStateListener, IStyledLabelProvider { /** * A map of regular images to their decorated counterpart. */ private Map<Image, Image> decoratedImages = new HashMap<>(); private ResourceManager resourceManager = new LocalResourceManager( JFaceResources.getResources()); private Image annotatedTagImage = resourceManager .createImage(UIIcons.TAG_ANNOTATED); private Image gerritRepoImage = resourceManager .createImage(UIIcons.REPOSITORY_GERRIT); private final State verboseBranchModeState; private boolean verboseBranchMode = false; /** * Constructs a repositories view label provider */ public RepositoriesViewLabelProvider() { ICommandService srv = CommonUtils.getService(PlatformUI.getWorkbench(), ICommandService.class); verboseBranchModeState = srv.getCommand(ToggleBranchCommitCommand.ID) .getState(ToggleBranchCommitCommand.TOGGLE_STATE); verboseBranchModeState.addListener(this); try { this.verboseBranchMode = ((Boolean) verboseBranchModeState .getValue()).booleanValue(); } catch (Exception e) { Activator.logError(e.getMessage(), e); } } @Override public Image getImage(Object element) { RepositoryTreeNode node = (RepositoryTreeNode) element; RepositoryTreeNodeType type = node.getType(); if (type == RepositoryTreeNodeType.TAG) { TagNode tagNode = (TagNode) node; if (tagNode.isAnnotated()) return decorateImage(annotatedTagImage, element); } else if (type == RepositoryTreeNodeType.FILE) { Object object = node.getObject(); if (object instanceof File) { ImageDescriptor descriptor = PlatformUI.getWorkbench() .getEditorRegistry() .getImageDescriptor(((File) object).getName()); return decorateImage((Image) resourceManager.get(descriptor), element); } } else if (type == RepositoryTreeNodeType.REPO) { Object object = node.getObject(); if (object instanceof Repository) { Repository r = (Repository) object; if (ResourcePropertyTester.hasGerritConfiguration(r)) return gerritRepoImage; } } return decorateImage(node.getType().getIcon(), element); } @Override public String getText(Object element) { if (!(element instanceof RepositoryTreeNode)) return null; RepositoryTreeNode node = (RepositoryTreeNode) element; return getSimpleText(node); } @Override public void dispose() { verboseBranchModeState.removeListener(this); // dispose of our decorated images for (Image image : decoratedImages.values()) { image.dispose(); } resourceManager.dispose(); decoratedImages.clear(); super.dispose(); } private Image decorateImage(final Image image, Object element) { RepositoryTreeNode node = (RepositoryTreeNode) element; switch (node.getType()) { case TAG: // fall through case ADDITIONALREF: // fall through case REF: // if the branch or tag is checked out, // we want to decorate the corresponding // node with a little check indicator String refName = ((Ref) node.getObject()).getName(); Ref leaf = ((Ref) node.getObject()).getLeaf(); String branchName; String compareString; try { branchName = node.getRepository().getFullBranch(); if (branchName == null) return image; if (refName.startsWith(Constants.R_HEADS)) { // local branch: HEAD would be on the branch compareString = refName; } else if (refName.startsWith(Constants.R_TAGS)) { // tag: HEAD would be on the commit id to which the tag is // pointing TagNode tagNode = (TagNode) node; compareString = tagNode.getCommitId(); } else if (refName.startsWith(Constants.R_REMOTES)) { // remote branch: HEAD would be on the commit id to which // the branch is pointing ObjectId id = node.getRepository().resolve(refName); if (id == null) return image; try (RevWalk rw = new RevWalk(node.getRepository())) { RevCommit commit = rw.parseCommit(id); compareString = commit.getId().name(); } } else if (refName.equals(Constants.HEAD)) { return getDecoratedImage(image); } else { String leafname = leaf.getName(); if (leafname.startsWith(Constants.R_REFS) && leafname.equals(node.getRepository() .getFullBranch())) { return getDecoratedImage(image); } ObjectId objectId = leaf.getObjectId(); if (objectId != null && objectId.equals( node.getRepository().resolve(Constants.HEAD))) { return getDecoratedImage(image); } // some other symbolic reference return image; } } catch (IOException e1) { return image; } if (compareString != null && compareString.equals(branchName)) { return getDecoratedImage(image); } return image; default: return image; } } private Image getDecoratedImage(final Image image) { // check if we have a decorated image yet or not Image decoratedImage = decoratedImages.get(image); if (decoratedImage == null) { // create one CompositeImageDescriptor cd = new CompositeImageDescriptor() { @Override protected Point getSize() { Rectangle bounds = image.getBounds(); return new Point(bounds.width, bounds.height); } @Override protected void drawCompositeImage(int width, int height) { drawImage(image.getImageData(), 0, 0); drawImage(UIIcons.OVR_CHECKEDOUT.getImageData(), 0, 0); } }; decoratedImage = cd.createImage(); // store it decoratedImages.put(image, decoratedImage); } return decoratedImage; } private RevCommit getLatestCommit(RepositoryTreeNode node) { Ref ref = (Ref) node.getObject(); ObjectId id; if (ref.isSymbolic()) id = ref.getLeaf().getObjectId(); else id = ref.getObjectId(); if (id == null) return null; try (RevWalk walk = new RevWalk(node.getRepository())) { walk.setRetainBody(true); return walk.parseCommit(id); } catch (IOException ignored) { return null; } } private String abbreviate(final ObjectId id) { if (id != null) return id.abbreviate(7).name(); else return ObjectId.zeroId().abbreviate(7).name(); } /** * Get styled text for submodule repository node * * @param node * @return styled string */ protected StyledString getStyledTextForSubmodule(RepositoryTreeNode node) { Repository repository = (Repository) node.getObject(); if (repository == null) { return new StyledString(); } StyledString string = GitLabels.getChangedPrefix(repository); String path = Repository.stripWorkDir(node.getParent().getRepository() .getWorkTree(), repository.getWorkTree()); string.append(path); Ref head; try { head = repository.exactRef(Constants.HEAD); } catch (IOException e) { return string; } if (head != null) { string.append(' '); string.append('[', StyledString.DECORATIONS_STYLER); if (head.isSymbolic()) string.append( Repository.shortenRefName(head.getLeaf().getName()), StyledString.DECORATIONS_STYLER); else if (head.getObjectId() != null) string.append(abbreviate(head.getObjectId()), StyledString.DECORATIONS_STYLER); string.append(']', StyledString.DECORATIONS_STYLER); if (verboseBranchMode && head.getObjectId() != null) { RevCommit commit; try (RevWalk walk = new RevWalk(repository)) { commit = walk.parseCommit(head.getObjectId()); string.append(' '); string.append(commit.getShortMessage(), StyledString.QUALIFIER_STYLER); } catch (IOException ignored) { // Ignored } } } return string; } /** * Get styled text for commit node * * @param node * @return styled string */ protected StyledString getStyledTextForCommit(StashedCommitNode node) { StyledString string = new StyledString(); RevCommit commit = node.getObject(); string.append(MessageFormat.format("{0}@'{'{1}'}'", //$NON-NLS-1$ Constants.STASH, Integer.valueOf(node.getIndex()))); string.append(' '); string.append('[', StyledString.DECORATIONS_STYLER); string.append(abbreviate(commit), StyledString.DECORATIONS_STYLER); string.append(']', StyledString.DECORATIONS_STYLER); string.append(' '); string.append(commit.getShortMessage(), StyledString.QUALIFIER_STYLER); return string; } /** * Gets the {@link StyledString} for a {@link SubmodulesNode}. * * @param node * to get the text for * @return the {@link StyledString} */ protected StyledString getStyledTextForSubmodules(SubmodulesNode node) { String label = getSimpleText(node); if (label == null) { return new StyledString(); } StyledString styled = new StyledString(label); Repository repository = node.getRepository(); if (repository != null) { boolean hasChanges = false; try (SubmoduleWalk walk = SubmoduleWalk.forIndex(repository)) { while (!hasChanges && walk.next()) { Repository submodule = walk.getRepository(); if (submodule != null) { Repository cached = org.eclipse.egit.core.Activator .getDefault().getRepositoryCache() .lookupRepository(submodule.getDirectory() .getAbsoluteFile()); hasChanges = cached != null && RepositoryUtil.hasChanges(cached); submodule.close(); } } } catch (IOException e) { hasChanges = false; } if (hasChanges) { StyledString prefixed = new StyledString(); prefixed.append('>', StyledString.DECORATIONS_STYLER); prefixed.append(' ').append(styled); return prefixed; } } return styled; } @Override public StyledString getStyledText(Object element) { if (!(element instanceof RepositoryTreeNode)) return null; RepositoryTreeNode node = (RepositoryTreeNode) element; switch (node.getType()) { case REPO: if (node.getParent() != null && node.getParent().getType() == RepositoryTreeNodeType.SUBMODULES) return getStyledTextForSubmodule(node); return GitLabels.getStyledLabelExtendedSafe(node.getObject()); case ADDITIONALREF: Ref ref = (Ref) node.getObject(); // shorten the name StyledString refName = new StyledString( Repository.shortenRefName(ref.getName())); ObjectId refId; if (ref.isSymbolic()) { refName.append(' '); refName.append('[', StyledString.DECORATIONS_STYLER); refName.append(ref.getLeaf().getName(), StyledString.DECORATIONS_STYLER); refName.append(']', StyledString.DECORATIONS_STYLER); refId = ref.getLeaf().getObjectId(); } else refId = ref.getObjectId(); refName.append(' '); RevCommit commit = getLatestCommit(node); if (commit != null) refName.append(abbreviate(commit), StyledString.QUALIFIER_STYLER) .append(' ') .append(commit.getShortMessage(), StyledString.QUALIFIER_STYLER); else refName.append(abbreviate(refId), StyledString.QUALIFIER_STYLER); return refName; case WORKINGDIR: StyledString dirString = new StyledString( UIText.RepositoriesView_WorkingDir_treenode); dirString.append(" - ", StyledString.QUALIFIER_STYLER); //$NON-NLS-1$ dirString.append(node.getRepository().getWorkTree() .getAbsolutePath(), StyledString.QUALIFIER_STYLER); return dirString; case REF: StyledString styled = null; String nodeText = getSimpleText(node); if (nodeText != null) { styled = new StyledString(nodeText); if (verboseBranchMode) { RevCommit latest = getLatestCommit(node); if (latest != null) styled.append(' ') .append(abbreviate(latest), StyledString.QUALIFIER_STYLER) .append(' ') .append(latest.getShortMessage(), StyledString.QUALIFIER_STYLER); } } return styled; case TAG: return getStyledTextForTag((TagNode) node); case STASHED_COMMIT: return getStyledTextForCommit((StashedCommitNode) node); case SUBMODULES: return getStyledTextForSubmodules((SubmodulesNode) node); case PUSH: // fall through case FETCH: // fall through case FILE: // fall through case FOLDER: // fall through case BRANCHES: // fall through case LOCAL: // fall through case REMOTETRACKING: // fall through case BRANCHHIERARCHY: // fall through case TAGS: // fall through; case ADDITIONALREFS: // fall through case REMOTES: // fall through case REMOTE: // fall through case STASH: // fall through case ERROR: { String label = getSimpleText(node); if (label != null) return new StyledString(label); } } return null; } private StyledString getStyledTextForTag(TagNode node) { String tagText = getSimpleText(node); if (tagText != null) { StyledString styled = new StyledString(tagText); if (verboseBranchMode) { if (node.getCommitId() != null && node.getCommitId().length() > 0) styled.append(' ') .append(node.getCommitId().substring(0, 7), StyledString.QUALIFIER_STYLER) .append(' ') .append(node.getCommitShortMessage(), StyledString.QUALIFIER_STYLER); } return styled; } else { return null; } } @Override public String getToolTipText(Object element) { if (element instanceof AdditionalRefNode) { AdditionalRefNode additionalRefNode = (AdditionalRefNode) element; Ref ref = additionalRefNode.getObject(); return GitLabels.getRefDescription(ref); } return null; } private String getSimpleText(RepositoryTreeNode node) { switch (node.getType()) { case REPO: Repository repository = (Repository) node.getObject(); return GitLabels.getPlainShortLabel(repository); case FILE: // fall through case FOLDER: return ((File) node.getObject()).getName(); case BRANCHES: return UIText.RepositoriesView_Branches_Nodetext; case LOCAL: return UIText.RepositoriesViewLabelProvider_LocalNodetext; case REMOTETRACKING: return UIText.RepositoriesViewLabelProvider_RemoteTrackingNodetext; case BRANCHHIERARCHY: IPath fullPath = (IPath) node.getObject(); return fullPath.lastSegment(); case TAGS: return UIText.RepositoriesViewLabelProvider_TagsNodeText; case ADDITIONALREFS: return UIText.RepositoriesViewLabelProvider_SymbolicRefNodeText; case REMOTES: return UIText.RepositoriesView_RemotesNodeText; case SUBMODULES: return UIText.RepositoriesViewLabelProvider_SubmodulesNodeText; case STASH: return UIText.RepositoriesViewLabelProvider_StashNodeText; case STASHED_COMMIT: return MessageFormat.format( "{0}@'{'{1}'}'", //$NON-NLS-1$ Constants.STASH, Integer.valueOf(((StashedCommitNode) node).getIndex())); case REF: // fall through case TAG: { Ref ref = (Ref) node.getObject(); // shorten the name String refName = Repository.shortenRefName(ref.getName()); if (node.getParent().getType() == RepositoryTreeNodeType.BRANCHHIERARCHY) { int index = refName.lastIndexOf('/'); refName = refName.substring(index + 1); } return refName; } case ADDITIONALREF: { Ref ref = (Ref) node.getObject(); // shorten the name String refName = Repository.shortenRefName(ref.getName()); if (ref.isSymbolic()) { refName = refName + " - " //$NON-NLS-1$ + ref.getLeaf().getName() + " - " + ObjectId.toString(ref.getLeaf().getObjectId()); //$NON-NLS-1$ } else { refName = refName + " - " //$NON-NLS-1$ + ObjectId.toString(ref.getObjectId()); } return refName; } case WORKINGDIR: return UIText.RepositoriesView_WorkingDir_treenode + " - " //$NON-NLS-1$ + node.getRepository().getWorkTree().getAbsolutePath(); case REMOTE: // fall through case PUSH: // fall through case FETCH: // fall through case ERROR: return (String) node.getObject(); } return null; } /** * @see org.eclipse.core.commands.IStateListener#handleStateChange(org.eclipse.core.commands.State, * java.lang.Object) */ @Override public void handleStateChange(State state, Object oldValue) { try { this.verboseBranchMode = ((Boolean) state.getValue()) .booleanValue(); } catch (Exception e) { Activator.logError(e.getMessage(), e); } } }