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
;
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
52 public class RepositoryTreeNodeDecorator
extends GitDecorator
53 implements IStateListener
{
55 private final State verboseBranchModeState
;
57 private boolean verboseBranchMode
= false;
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);
68 this.verboseBranchMode
= ((Boolean
) verboseBranchModeState
69 .getValue()).booleanValue();
70 } catch (Exception e
) {
71 Activator
.logError(e
.getMessage(), e
);
77 public void dispose() {
78 verboseBranchModeState
.removeListener(this);
83 public void handleStateChange(State state
, Object oldValue
) {
85 boolean newValue
= ((Boolean
) state
.getValue())
87 if (newValue
!= verboseBranchMode
) {
88 verboseBranchMode
= newValue
;
91 } catch (Exception e
) {
92 Activator
.logError(e
.getMessage(), e
);
97 public void decorate(Object element
, IDecoration decoration
) {
98 RepositoryTreeNode
<?
> node
= (RepositoryTreeNode
) element
;
99 Repository repository
= node
.getRepository();
100 if (repository
!= null) {
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
)
114 boolean decorated
= false;
115 switch (node
.getType()) {
117 decorated
= decorateRepository(node
, repository
, decoration
);
120 decorated
= decorateAdditionalRef((AdditionalRefNode
) node
,
124 decorated
= decorateRef((RefNode
) node
, decoration
);
127 decorated
= decorateTag((TagNode
) node
, decoration
);
130 decorated
= decorateStash((StashedCommitNode
) node
, decoration
);
133 decorated
= decorateSubmodules(repository
, decoration
);
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();
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
));
162 UIText
.RepositoriesViewLabelProvider_UnbornBranchText
);
164 decoration
.addSuffix(suffix
.toString());
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());
180 private boolean decorateRepository(RepositoryTreeNode
<?
> node
,
181 @NonNull Repository repository
, IDecoration decoration
)
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();
190 Ref head
= repository
.exactRef(Constants
.HEAD
);
194 suffix
.append(" ["); //$NON-NLS-1$
195 if (head
.isSymbolic()) {
197 Repository
.shortenRefName(head
.getLeaf().getName()));
198 } else if (head
.getObjectId() != null) {
199 suffix
.append(abbreviate(head
.getObjectId()));
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
) {
212 String branch
= Activator
.getDefault().getRepositoryUtil()
213 .getShortBranch(repository
);
214 if (branch
== null) {
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());
236 decoration
.addSuffix(suffix
.toString());
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$
248 private boolean decorateSubmodules(@NonNull Repository repository
,
249 IDecoration decoration
) throws IOException
{
250 if (haveSubmoduleChanges(repository
)) {
251 decoration
.addPrefix("> "); //$NON-NLS-1$
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());
267 private RevCommit
getLatestCommit(RepositoryTreeNode node
) {
268 Ref ref
= (Ref
) node
.getObject();
270 if (ref
.isSymbolic()) {
271 id
= ref
.getLeaf().getObjectId();
273 id
= ref
.getObjectId();
278 try (RevWalk walk
= new RevWalk(node
.getRepository())) {
279 walk
.setRetainBody(true);
280 return walk
.parseCommit(id
);
281 } catch (IOException ignored
) {
286 private String
abbreviate(final ObjectId id
) {
288 return id
.abbreviate(7).name();
290 return ObjectId
.zeroId().abbreviate(7).name();
294 private boolean haveSubmoduleChanges(@NonNull Repository repository
)
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
);
314 protected String
getName() {
315 return UIText
.RepositoryTreeNodeDecorator_name
;