Fix bugs with the new lightweight decorator for RepositoryTreeNodes
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / repository / RepositoryTreeNodeDecorator.java
blob139f990beeb2b3c7bcd7c4cb5fbfba530e537fd2
1 /*******************************************************************************
2 * Copyright (c) 2018 Thomas Wolf <thomas.wolf@paranor.ch>
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11 package org.eclipse.egit.ui.internal.repository;
13 import java.io.IOException;
14 import java.text.MessageFormat;
16 import org.eclipse.core.commands.IStateListener;
17 import org.eclipse.core.commands.State;
18 import org.eclipse.egit.core.RepositoryUtil;
19 import org.eclipse.egit.ui.Activator;
20 import org.eclipse.egit.ui.internal.CommonUtils;
21 import org.eclipse.egit.ui.internal.GitLabels;
22 import org.eclipse.egit.ui.internal.UIText;
23 import org.eclipse.egit.ui.internal.decorators.GitDecorator;
24 import org.eclipse.egit.ui.internal.repository.tree.AdditionalRefNode;
25 import org.eclipse.egit.ui.internal.repository.tree.RefNode;
26 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNode;
27 import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNodeType;
28 import org.eclipse.egit.ui.internal.repository.tree.StashedCommitNode;
29 import org.eclipse.egit.ui.internal.repository.tree.TagNode;
30 import org.eclipse.egit.ui.internal.repository.tree.command.ToggleBranchCommitCommand;
31 import org.eclipse.jface.viewers.IDecoration;
32 import org.eclipse.jgit.annotations.NonNull;
33 import org.eclipse.jgit.lib.BranchTrackingStatus;
34 import org.eclipse.jgit.lib.Constants;
35 import org.eclipse.jgit.lib.ObjectId;
36 import org.eclipse.jgit.lib.Ref;
37 import org.eclipse.jgit.lib.Repository;
38 import org.eclipse.jgit.lib.RepositoryState;
39 import org.eclipse.jgit.revwalk.RevCommit;
40 import org.eclipse.jgit.revwalk.RevWalk;
41 import org.eclipse.jgit.submodule.SubmoduleWalk;
42 import org.eclipse.ui.PlatformUI;
43 import org.eclipse.ui.commands.ICommandService;
45 /**
46 * Lightweight decorator for {@link RepositoryTreeNode}s. Note that this
47 * decorator does <em>not</em> listen on "references changed" events to fire
48 * {@link org.eclipse.jface.viewers.LabelProviderChangedEvent
49 * LabelProviderChangedEvent}s -- the RepositoriesView does so and refreshes
50 * itself completely.
52 public class RepositoryTreeNodeDecorator extends GitDecorator
53 implements IStateListener {
55 private final State verboseBranchModeState;
57 private boolean verboseBranchMode = false;
59 /**
60 * Constructs a repositories view label provider
62 public RepositoryTreeNodeDecorator() {
63 ICommandService srv = CommonUtils.getService(PlatformUI.getWorkbench(), ICommandService.class);
64 verboseBranchModeState = srv.getCommand(ToggleBranchCommitCommand.ID)
65 .getState(ToggleBranchCommitCommand.TOGGLE_STATE);
66 verboseBranchModeState.addListener(this);
67 try {
68 this.verboseBranchMode = ((Boolean) verboseBranchModeState
69 .getValue()).booleanValue();
70 } catch (Exception e) {
71 Activator.logError(e.getMessage(), e);
76 @Override
77 public void dispose() {
78 verboseBranchModeState.removeListener(this);
79 super.dispose();
82 @Override
83 public void handleStateChange(State state, Object oldValue) {
84 try {
85 boolean newValue = ((Boolean) state.getValue())
86 .booleanValue();
87 if (newValue != verboseBranchMode) {
88 verboseBranchMode = newValue;
89 postLabelEvent();
91 } catch (Exception e) {
92 Activator.logError(e.getMessage(), e);
96 @Override
97 public void decorate(Object element, IDecoration decoration) {
98 RepositoryTreeNode<?> node = (RepositoryTreeNode) element;
99 Repository repository = node.getRepository();
100 if (repository != null) {
101 try {
102 decorateText(node, repository, decoration);
103 } catch (IOException e) {
104 Activator.logError(MessageFormat.format(
105 UIText.GitLabelProvider_UnableToRetrieveLabel,
106 element.toString()), e);
111 private void decorateText(RepositoryTreeNode<?> node,
112 @NonNull Repository repository, IDecoration decoration)
113 throws IOException {
114 boolean decorated = false;
115 switch (node.getType()) {
116 case REPO:
117 decorated = decorateRepository(node, repository, decoration);
118 break;
119 case ADDITIONALREF:
120 decorated = decorateAdditionalRef((AdditionalRefNode) node,
121 decoration);
122 break;
123 case REF:
124 decorated = decorateRef((RefNode) node, decoration);
125 break;
126 case TAG:
127 decorated = decorateTag((TagNode) node, decoration);
128 break;
129 case STASHED_COMMIT:
130 decorated = decorateStash((StashedCommitNode) node, decoration);
131 break;
132 case SUBMODULES:
133 decorated = decorateSubmodules(repository, decoration);
134 break;
135 default:
136 return;
138 if (!decorated) {
139 // Ensure the caching of last labels in
140 // RepositoryTreeNodeLabelProvider works
141 decoration.addSuffix(" "); //$NON-NLS-1$
145 private boolean decorateAdditionalRef(AdditionalRefNode node,
146 IDecoration decoration) {
147 Ref ref = node.getObject();
148 StringBuilder suffix = new StringBuilder();
149 if (ref.isSymbolic()) {
150 suffix.append(" [").append(ref.getLeaf().getName()).append(']'); //$NON-NLS-1$
152 ObjectId refId = ref.getObjectId();
153 suffix.append(' ');
154 RevCommit commit = getLatestCommit(node);
155 if (commit != null) {
156 suffix.append(abbreviate(commit)).append(' ')
157 .append(commit.getShortMessage());
158 } else if (!ref.isSymbolic() || refId != null) {
159 suffix.append(abbreviate(refId));
160 } else {
161 suffix.append(
162 UIText.RepositoriesViewLabelProvider_UnbornBranchText);
164 decoration.addSuffix(suffix.toString());
165 return true;
168 private boolean decorateRef(RefNode node, IDecoration decoration) {
169 if (verboseBranchMode) {
170 RevCommit latest = getLatestCommit(node);
171 if (latest != null) {
172 decoration.addSuffix(" " + abbreviate(latest) + ' ' //$NON-NLS-1$
173 + latest.getShortMessage());
174 return true;
177 return false;
180 private boolean decorateRepository(RepositoryTreeNode<?> node,
181 @NonNull Repository repository, IDecoration decoration)
182 throws IOException {
183 boolean isSubModule = node.getParent() != null && node.getParent()
184 .getType() == RepositoryTreeNodeType.SUBMODULES;
185 if (RepositoryUtil.hasChanges(repository)) {
186 decoration.addPrefix("> "); //$NON-NLS-1$
188 StringBuilder suffix = new StringBuilder();
189 if (isSubModule) {
190 Ref head = repository.exactRef(Constants.HEAD);
191 if (head == null) {
192 return false;
194 suffix.append(" ["); //$NON-NLS-1$
195 if (head.isSymbolic()) {
196 suffix.append(
197 Repository.shortenRefName(head.getLeaf().getName()));
198 } else if (head.getObjectId() != null) {
199 suffix.append(abbreviate(head.getObjectId()));
201 suffix.append(']');
202 if (verboseBranchMode && head.getObjectId() != null) {
203 try (RevWalk walk = new RevWalk(repository)) {
204 RevCommit commit = walk.parseCommit(head.getObjectId());
205 suffix.append(' ').append(commit.getShortMessage());
206 } catch (IOException ignored) {
207 // Ignored
210 } else {
211 // Not a submodule
212 String branch = Activator.getDefault().getRepositoryUtil()
213 .getShortBranch(repository);
214 if (branch == null) {
215 return false;
217 suffix.append(" ["); //$NON-NLS-1$
218 suffix.append(branch);
220 BranchTrackingStatus trackingStatus = BranchTrackingStatus
221 .of(repository, branch);
222 if (trackingStatus != null && (trackingStatus.getAheadCount() != 0
223 || trackingStatus.getBehindCount() != 0)) {
224 String formattedTrackingStatus = GitLabels
225 .formatBranchTrackingStatus(trackingStatus);
226 suffix.append(' ').append(formattedTrackingStatus);
229 RepositoryState repositoryState = repository.getRepositoryState();
230 if (repositoryState != RepositoryState.SAFE) {
231 suffix.append(" - ") //$NON-NLS-1$
232 .append(repositoryState.getDescription());
234 suffix.append(']');
236 decoration.addSuffix(suffix.toString());
237 return true;
240 private boolean decorateStash(StashedCommitNode node,
241 IDecoration decoration) {
242 RevCommit commit = node.getObject();
243 decoration.addSuffix(
244 " [" + abbreviate(commit) + "] " + commit.getShortMessage()); //$NON-NLS-1$ //$NON-NLS-2$
245 return true;
248 private boolean decorateSubmodules(@NonNull Repository repository,
249 IDecoration decoration) throws IOException {
250 if (haveSubmoduleChanges(repository)) {
251 decoration.addPrefix("> "); //$NON-NLS-1$
252 return true;
254 return false;
257 private boolean decorateTag(TagNode node, IDecoration decoration) {
258 if (verboseBranchMode && node.getCommitId() != null
259 && node.getCommitId().length() > 0) {
260 decoration.addSuffix(" " + node.getCommitId().substring(0, 7) + ' ' //$NON-NLS-1$
261 + node.getCommitShortMessage());
262 return true;
264 return false;
267 private RevCommit getLatestCommit(RepositoryTreeNode node) {
268 Ref ref = (Ref) node.getObject();
269 ObjectId id;
270 if (ref.isSymbolic()) {
271 id = ref.getLeaf().getObjectId();
272 } else {
273 id = ref.getObjectId();
275 if (id == null) {
276 return null;
278 try (RevWalk walk = new RevWalk(node.getRepository())) {
279 walk.setRetainBody(true);
280 return walk.parseCommit(id);
281 } catch (IOException ignored) {
282 return null;
286 private String abbreviate(final ObjectId id) {
287 if (id != null) {
288 return id.abbreviate(7).name();
289 } else {
290 return ObjectId.zeroId().abbreviate(7).name();
294 private boolean haveSubmoduleChanges(@NonNull Repository repository)
295 throws IOException {
296 boolean hasChanges = false;
297 try (SubmoduleWalk walk = SubmoduleWalk.forIndex(repository)) {
298 while (!hasChanges && walk.next()) {
299 Repository submodule = walk.getRepository();
300 if (submodule != null) {
301 Repository cached = org.eclipse.egit.core.Activator
302 .getDefault().getRepositoryCache().lookupRepository(
303 submodule.getDirectory().getAbsoluteFile());
304 hasChanges = cached != null
305 && RepositoryUtil.hasChanges(cached);
306 submodule.close();
310 return hasChanges;
313 @Override
314 protected String getName() {
315 return UIText.RepositoryTreeNodeDecorator_name;