blob: 82411bd1563edfe014297b2e0ad287853f6482de [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
*
* 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.components;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.egit.core.RepositoryCache;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.UIIcons;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.Repository;
/**
* Provides a way to populate a menu with a list of repositories.
*/
public final class RepositoryMenuUtil {
private RepositoryMenuUtil() {
// Utility class shall not be instantiated
}
/**
* Populates the given {@link IMenuManager} with a list of repositories.
* Each currently known configured repository is shown with its repository
* name and the path to the .git directory as tooltip; when a menu item is
* selected, the given {@code action} is invoked. Bare repositories can be
* excluded from the list. Menu items are sorted by repository name and .git
* directory paths.
*
* @param menuManager
* to populate with the list of repositories
* @param includeBare
* {@code true} if bare repositories should be included in the
* list, {@code false} otherwise
* @param currentRepoDir
* git directory of a repository that is to be marked as
* "current"; may be {@code null}.
* @param action
* to perform on the chosen repository
*/
public static void fillRepositories(@NonNull IMenuManager menuManager,
boolean includeBare, @Nullable File currentRepoDir,
@NonNull Consumer<Repository> action) {
for (IAction item : getRepositoryActions(includeBare, currentRepoDir,
action)) {
menuManager.add(item);
}
}
/**
* Creates for each configured repository an {@link IAction} that will
* perform the given {@code action} when invoked.
*
* @param includeBare
* {@code true} if bare repositories should be included in the
* list, {@code false} otherwise
* @param currentRepoDir
* git directory of a repository that is to be marked as
* "current"; may be {@code null}.
* @param action
* to perform on the chosen repository
* @return the (possibly empty) list of actions
*/
@NonNull
public static Collection<IAction> getRepositoryActions(boolean includeBare,
@Nullable File currentRepoDir,
@NonNull Consumer<Repository> action) {
RepositoryUtil util = org.eclipse.egit.core.Activator.getDefault()
.getRepositoryUtil();
RepositoryCache cache = org.eclipse.egit.core.Activator.getDefault()
.getRepositoryCache();
Set<String> repositories = util.getRepositories();
Map<String, Set<File>> repos = new HashMap<>();
for (String repo : repositories) {
File gitDir = new File(repo);
String name = null;
try {
Repository r = cache.lookupRepository(gitDir);
if (!includeBare && r.isBare()) {
continue;
}
name = util.getRepositoryName(r);
} catch (IOException e) {
continue;
}
repos.computeIfAbsent(name, key -> new HashSet<>()).add(gitDir);
}
String[] repoNames = repos.keySet().toArray(new String[0]);
Arrays.sort(repoNames, CommonUtils.STRING_ASCENDING_COMPARATOR);
List<IAction> result = new ArrayList<>();
for (String repoName : repoNames) {
Set<File> files = repos.get(repoName);
File[] gitDirs = files.toArray(new File[0]);
Arrays.sort(gitDirs);
for (File f : gitDirs) {
IAction menuItem = new Action(repoName,
IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
try {
Repository r = cache.lookupRepository(f);
action.accept(r);
} catch (IOException e) {
Activator.showError(e.getLocalizedMessage(), e);
}
}
};
menuItem.setToolTipText(f.getPath());
if (f.equals(currentRepoDir)) {
menuItem.setChecked(true);
}
result.add(menuItem);
}
}
return result;
}
/**
* Utility class facilitating creating toolbar actions that show a drop-down
* menu of all registered repositories, performing a given action on a
* selected repository.
*/
public static class RepositoryToolbarAction extends DropDownMenuAction {
private final RepositoryUtil util = org.eclipse.egit.core.Activator
.getDefault().getRepositoryUtil();
private final IEclipsePreferences preferences = util.getPreferences();
private final IPreferenceChangeListener listener;
private final @NonNull Consumer<Repository> action;
private final @NonNull Supplier<Repository> currentRepo;
private final boolean includeBare;
/**
* Creates a new {@link RepositoryToolbarAction} with the given
* {@code action} and default text, image, and tooltip.
*
* @param includeBare
* {@code true} if bare repositories shall be included,
* {@code false} otherwise
* @param currentRepo
* supplying the "current" repository, if any, or
* {@code null} otherwise
* @param action
* to run when a repository is selected from the drop-down
* menu
*/
public RepositoryToolbarAction(boolean includeBare,
@NonNull Supplier<Repository> currentRepo,
@NonNull Consumer<Repository> action) {
this(UIText.RepositoryToolbarAction_label, UIIcons.REPOSITORY,
UIText.RepositoryToolbarAction_tooltip, includeBare,
currentRepo, action);
}
/**
* Creates a new {@link RepositoryToolbarAction} with the given text and
* the given {@code action}.
*
* @param text
* for the action
* @param image
* for the action
* @param tooltip
* for the action
* @param includeBare
* {@code true} if bare repositories shall be included,
* {@code false} otherwise
* @param currentRepo
* supplying the "current" repository, if any, or
* {@code null} otherwise
* @param action
* to run when a repository is selected from the drop-down
* menu
*/
public RepositoryToolbarAction(String text,
@Nullable ImageDescriptor image, @Nullable String tooltip,
boolean includeBare, @NonNull Supplier<Repository> currentRepo,
@NonNull Consumer<Repository> action) {
super(text);
setImageDescriptor(image);
setToolTipText(tooltip == null ? text : tooltip);
this.includeBare = includeBare;
this.currentRepo = currentRepo;
this.action = action;
this.listener = event -> {
if (RepositoryUtil.PREFS_DIRECTORIES_REL
.equals(event.getKey())) {
setEnabled(!util.getRepositories().isEmpty());
}
};
setEnabled(!util.getRepositories().isEmpty());
preferences.addPreferenceChangeListener(listener);
}
@Override
public Collection<IContributionItem> getActions() {
Repository current = currentRepo.get();
File gitDir = current == null ? null : current.getDirectory();
return RepositoryMenuUtil.getRepositoryActions(includeBare, gitDir,
action).stream().map(ActionContributionItem::new)
.collect(Collectors.toList());
}
@Override
public void dispose() {
preferences.removePreferenceChangeListener(listener);
super.dispose();
}
}
}