package limitedwip.watchdog.components; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.CheckinProjectPanel; import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.CommitExecutor; import com.intellij.openapi.vcs.checkin.BeforeCheckinDialogHandler; import com.intellij.openapi.vcs.checkin.CheckinHandler; import com.intellij.openapi.vcs.checkin.VcsCheckinHandlerFactory; import com.intellij.openapi.vcs.impl.CheckinHandlersManager; import com.intellij.util.Function; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.util.List; import static java.util.Arrays.asList; class VcsIdeUtil { private static final Logger log = Logger.getInstance(VcsIdeUtil.class); @SuppressWarnings("unchecked") public static void registerBeforeCheckInListener(final CheckinListener listener) { // This is a hack caused by limitations of IntelliJ API. // - cannot use CheckinHandlerFactory because: // - CheckinHandler is used just before commit (and after displaying commit dialog) // - its CheckinHandlerFactory#createSystemReadyHandler() doesn't seem to be called // - cannot use VcsCheckinHandlerFactory through extension points because need to register // checkin handler for all VCSs available // - cannot use CheckinHandlersManager#registerCheckinHandlerFactory() because it doesn't properly // register VcsCheckinHandlerFactory // // Therefore, using reflection. accessField(CheckinHandlersManager.getInstance(), asList("a", "b", "myVcsMap"), MultiMap.class, new Function<MultiMap, Void>() { @Override public Void fun(MultiMap multiMap) { for (Object key : multiMap.keySet()) { multiMap.putValue(key, new DelegatingCheckinHandlerFactory((VcsKey) key, listener)); } return null; } }); } @SuppressWarnings("unchecked") private static void accessField(Object object, List<String> possibleFieldNames, Class aClass, Function function) { for (Field field : object.getClass().getDeclaredFields()) { if (possibleFieldNames.contains(field.getName()) && aClass.isAssignableFrom(field.getType())) { field.setAccessible(true); try { function.fun(field.get(object)); return; } catch (Exception ignored) { } } } log.warn("Failed to access fields: " + possibleFieldNames + " on '" + object + "'"); } public interface CheckinListener { boolean allowCheckIn(@NotNull Project project, @NotNull List<Change> changes); } private static class DelegatingCheckinHandlerFactory extends VcsCheckinHandlerFactory { private final CheckinListener listener; protected DelegatingCheckinHandlerFactory(@NotNull VcsKey key, CheckinListener listener) { super(key); this.listener = listener; } @Override public BeforeCheckinDialogHandler createSystemReadyHandler(@NotNull final Project project) { return new BeforeCheckinDialogHandler() { @Override public boolean beforeCommitDialogShown(@NotNull Project project, @NotNull List<Change> changes, @NotNull Iterable<CommitExecutor> executors, boolean showVcsCommit) { return listener.allowCheckIn(project, changes); } }; } @NotNull @Override protected CheckinHandler createVcsHandler(CheckinProjectPanel panel) { return new CheckinHandler() {}; } } }