From 0dd8734e72664d0d76b1fee70ac5ed79eaaf9b69 Mon Sep 17 00:00:00 2001 From: Mathias Kinzler Date: Tue, 18 May 2010 19:27:39 +0200 Subject: Git Label Decorations of "detached" HEAD In the case of "detached" HEAD, currently, there is only the commit ID (truncated) shown in the Project label decoration. This fix implements the suggestions collected in a mail thread on the egit-dev mailing list by adding the information which tag or remote branch is pointing to the currently checked out branch. Change-Id: I70c791f9552c6201b1c66488ab45f8186ec4c28d Signed-off-by: Mathias Kinzler --- .../src/org/eclipse/egit/ui/Activator.java | 11 ++ .../src/org/eclipse/egit/ui/RepositoryUtil.java | 219 +++++++++++++++++++++ .../decorators/DecoratableResourceAdapter.java | 19 +- 3 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 org.eclipse.egit.ui/src/org/eclipse/egit/ui/RepositoryUtil.java diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java index 19d4dcf641..4dd3cd8399 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/Activator.java @@ -69,6 +69,8 @@ public class Activator extends AbstractUIPlugin { */ public static final String DECORATORS_CHANGED = "org.eclipse.egit.ui.DECORATORS_CHANGED"; //$NON-NLS-1$ + private RepositoryUtil repositoryUtil; + /** * @return the {@link Activator} singleton. */ @@ -159,6 +161,7 @@ public class Activator extends AbstractUIPlugin { public void start(final BundleContext context) throws Exception { super.start(context); + repositoryUtil = new RepositoryUtil(); if (isDebugging()) { ServiceTracker debugTracker = new ServiceTracker(context, @@ -401,6 +404,8 @@ public class Activator extends AbstractUIPlugin { GitTraceLocation.getTrace().trace( GitTraceLocation.UI.getLocation(), "Jobs terminated"); //$NON-NLS-1$ + repositoryUtil.dispose(); + repositoryUtil = null; super.stop(context); plugin = null; } @@ -433,4 +438,10 @@ public class Activator extends AbstractUIPlugin { return new Status(IStatus.ERROR, getPluginId(), message, throwable); } + /** + * @return the {@link RepositoryUtil} instance + */ + public RepositoryUtil getRepositoryUtil() { + return repositoryUtil; + } } diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/RepositoryUtil.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/RepositoryUtil.java new file mode 100644 index 0000000000..8d3326dba5 --- /dev/null +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/RepositoryUtil.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (c) 2010 SAP AG. + * 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 + *******************************************************************************/ +package org.eclipse.egit.ui; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +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.lib.Tag; + +/** + * Utility class for handling Repositories in the UI. + */ +public class RepositoryUtil { + + private final Map> commitMappingCache = new HashMap>(); + + private final Map repositoryNameCache = new HashMap(); + + /** + * Clients should obtain an instance from {@link Activator} + */ + RepositoryUtil() { + // nothing + } + + /** + * Used by {@link Activator} + */ + void dispose() { + commitMappingCache.clear(); + repositoryNameCache.clear(); + } + + /** + * Tries to map a commit to a symbolic reference. + *

+ * This value will be cached for the given commit ID unless refresh is + * specified. The return value will be the full name, e.g. + * "refs/remotes/someBranch", "refs/tags/v.1.0" + *

+ * Since this mapping is not unique, the following precedence rules are + * used: + *

    + *
  • Tags take precedence over branches
  • + *
  • Local branches take preference over remote branches
  • + *
  • Newer references take precedence over older ones where time stamps + * are available
  • + *
  • If there are still ambiguities, the reference name with the highest + * lexicographic value will be returned
  • + *
+ * + * @param repository + * the {@link Repository} + * @param commitId + * a commit + * @param refresh + * if true, the cache will be invalidated + * @return the symbolic reference, or null if no such reference + * can be found + */ + public String mapCommitToRef(Repository repository, String commitId, + boolean refresh) { + + synchronized (commitMappingCache) { + + if (!ObjectId.isId(commitId)) { + return null; + } + + Map cacheEntry = commitMappingCache.get(repository + .getDirectory().toString()); + if (!refresh && cacheEntry != null + && cacheEntry.containsKey(commitId)) { + // this may be null in fact + return cacheEntry.get(commitId); + } + if (cacheEntry == null) { + cacheEntry = new HashMap(); + commitMappingCache.put(repository.getDirectory().getPath(), + cacheEntry); + } else { + cacheEntry.clear(); + } + + Map tagMap = new HashMap(); + try { + Map tags = repository.getRefDatabase().getRefs( + Constants.R_TAGS); + for (Ref tagRef : tags.values()) { + Tag tag = repository.mapTag(tagRef.getName()); + if (tag.getObjId().name().equals(commitId)) { + Date timestamp; + if (tag.getTagger() != null) { + timestamp = tag.getTagger().getWhen(); + } else { + timestamp = null; + } + tagMap.put(tagRef.getName(), timestamp); + } + } + } catch (IOException e) { + // ignore here + } + + String cacheValue = null; + + if (!tagMap.isEmpty()) { + // we try to obtain the "latest" tag + Date compareDate = new Date(0); + for (Map.Entry tagEntry : tagMap.entrySet()) { + if (tagEntry.getValue() != null + && tagEntry.getValue().after(compareDate)) { + compareDate = tagEntry.getValue(); + cacheValue = tagEntry.getKey(); + } + } + // if we don't have time stamps, we sort + if (cacheValue == null) { + String compareString = ""; //$NON-NLS-1$ + for (String tagName : tagMap.keySet()) { + if (tagName.compareTo(compareString) >= 0) { + cacheValue = tagName; + compareString = tagName; + } + } + } + } + + if (cacheValue == null) { + // we didnt't find a tag, so let's look for local branches + Set branchNames = new TreeSet(); + // put this into a sorted set + try { + Map remoteBranches = repository + .getRefDatabase().getRefs(Constants.R_HEADS); + for (Ref branch : remoteBranches.values()) { + if (branch.getObjectId().name().equals(commitId)) { + branchNames.add(branch.getName()); + } + } + } catch (IOException e) { + // ignore here + } + if (!branchNames.isEmpty()) { + // get the last (sorted) entry + cacheValue = branchNames.toArray(new String[branchNames + .size()])[branchNames.size() - 1]; + } + } + + if (cacheValue == null) { + // last try: remote branches + Set branchNames = new TreeSet(); + // put this into a sorted set + try { + Map remoteBranches = repository + .getRefDatabase().getRefs(Constants.R_REMOTES); + for (Ref branch : remoteBranches.values()) { + if (branch.getObjectId().name().equals(commitId)) { + branchNames.add(branch.getName()); + } + } + if (!branchNames.isEmpty()) { + // get the last (sorted) entry + cacheValue = branchNames.toArray(new String[branchNames + .size()])[branchNames.size() - 1]; + } + } catch (IOException e) { + // ignore here + } + } + cacheEntry.put(commitId, cacheValue); + return cacheValue; + } + } + + /** + * Return a cached UI "name" for a Repository + *

+ * This uses the name of the parent of the repository's directory. + * + * @param repository + * @return the name + */ + public String getRepositoryName(Repository repository) { + synchronized (repositoryNameCache) { + File gitDir = repository.getDirectory(); + if (gitDir != null) { + String name = repositoryNameCache.get(gitDir.getPath() + .toString()); + if (name != null) { + return name; + } + name = gitDir.getParentFile().getName(); + repositoryNameCache.put(gitDir.getPath().toString(), name); + return name; + } + } + return ""; //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/decorators/DecoratableResourceAdapter.java b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/decorators/DecoratableResourceAdapter.java index d480a730e8..f6fe2b087c 100644 --- a/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/decorators/DecoratableResourceAdapter.java +++ b/org.eclipse.egit.ui/src/org/eclipse/egit/ui/internal/decorators/DecoratableResourceAdapter.java @@ -91,13 +91,8 @@ class DecoratableResourceAdapter implements IDecoratableResource { headId = repository.resolve(Constants.HEAD); store = Activator.getDefault().getPreferenceStore(); + repositoryName = Activator.getDefault().getRepositoryUtil().getRepositoryName(repository); - File gitDir = repository.getDirectory(); - if (gitDir != null) - repositoryName = repository.getDirectory().getParentFile() - .getName(); - else - repositoryName = ""; //$NON-NLS-1$ branch = getShortBranch(); TreeWalk treeWalk = createThreeWayTreeWalk(); @@ -120,8 +115,16 @@ class DecoratableResourceAdapter implements IDecoratableResource { private String getShortBranch() throws IOException { Ref head = repository.getRef(Constants.HEAD); - if (head != null && !head.isSymbolic()) - return repository.getFullBranch().substring(0, 7) + "..."; //$NON-NLS-1$ + if (head != null && !head.isSymbolic()) { + String refString = Activator.getDefault().getRepositoryUtil() + .mapCommitToRef(repository, repository.getFullBranch(), + false); + if (refString != null) { + return repository.getFullBranch().substring(0, 7) + + "... (" + refString + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + } else + return repository.getFullBranch().substring(0, 7) + "..."; //$NON-NLS-1$ + } return repository.getBranch(); } -- cgit v1.2.3