/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.vcs.log.impl; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.CalledInAwt; import com.intellij.openapi.vcs.VcsRoot; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.vcs.log.VcsLogFilter; import com.intellij.vcs.log.VcsLogProvider; import com.intellij.vcs.log.VcsLogRefresher; import com.intellij.vcs.log.data.*; import com.intellij.vcs.log.ui.VcsLogColorManagerImpl; import com.intellij.vcs.log.ui.VcsLogPanel; import com.intellij.vcs.log.ui.VcsLogUiImpl; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collection; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; public class VcsLogManager implements Disposable { public static final ExtensionPointName<VcsLogProvider> LOG_PROVIDER_EP = ExtensionPointName.create("com.intellij.logProvider"); private static final Logger LOG = Logger.getInstance(VcsLogManager.class); @NotNull private final Project myProject; @NotNull private final VcsLogTabsProperties myUiProperties; @Nullable private final Runnable myRecreateMainLogHandler; @NotNull private final VcsLogData myLogData; @NotNull private final VcsLogColorManagerImpl myColorManager; @NotNull private final VcsLogTabsWatcher myTabsLogRefresher; @NotNull private final PostponableLogRefresher myPostponableRefresher; private boolean myInitialized = false; public VcsLogManager(@NotNull Project project, @NotNull VcsLogTabsProperties uiProperties, @NotNull Collection<VcsRoot> roots) { this(project, uiProperties, roots, true, null); } public VcsLogManager(@NotNull Project project, @NotNull VcsLogTabsProperties uiProperties, @NotNull Collection<VcsRoot> roots, boolean scheduleRefreshImmediately, @Nullable Runnable recreateHandler) { myProject = project; myUiProperties = uiProperties; myRecreateMainLogHandler = recreateHandler; Map<VirtualFile, VcsLogProvider> logProviders = findLogProviders(roots, myProject); myLogData = new VcsLogData(myProject, logProviders, new MyFatalErrorsHandler()); myPostponableRefresher = new PostponableLogRefresher(myLogData); myTabsLogRefresher = new VcsLogTabsWatcher(myProject, myPostponableRefresher, myLogData); refreshLogOnVcsEvents(logProviders, myPostponableRefresher, myLogData); myColorManager = new VcsLogColorManagerImpl(logProviders.keySet()); if (scheduleRefreshImmediately) { scheduleInitialization(); } Disposer.register(project, this); } @CalledInAwt public void scheduleInitialization() { if (!myInitialized) { myInitialized = true; myLogData.initialize(); } } @CalledInAwt public boolean isLogVisible() { return myPostponableRefresher.isLogVisible(); } @NotNull public VcsLogData getDataManager() { return myLogData; } @NotNull public JComponent createLogPanel(@NotNull String logId, @Nullable String contentTabName) { VcsLogUiImpl ui = createLogUi(logId, contentTabName, null); return new VcsLogPanel(this, ui); } @NotNull public VcsLogUiImpl createLogUi(@NotNull String logId, @Nullable String contentTabName, @Nullable VcsLogFilter filter) { MainVcsLogUiProperties properties = myUiProperties.createProperties(logId); VcsLogFiltererImpl filterer = new VcsLogFiltererImpl(myProject, myLogData, properties.get(MainVcsLogUiProperties.BEK_SORT_TYPE)); VcsLogUiImpl ui = new VcsLogUiImpl(myLogData, myProject, myColorManager, properties, filterer); if (filter != null) { ui.getFilterUi().setFilter(filter); } Disposable disposable; if (contentTabName != null) { disposable = myTabsLogRefresher.addTabToWatch(contentTabName, ui.getFilterer()); } else { disposable = myPostponableRefresher.addLogWindow(ui.getFilterer()); } Disposer.register(ui, disposable); ui.requestFocus(); return ui; } private static void refreshLogOnVcsEvents(@NotNull Map<VirtualFile, VcsLogProvider> logProviders, @NotNull VcsLogRefresher refresher, @NotNull Disposable disposableParent) { MultiMap<VcsLogProvider, VirtualFile> providers2roots = MultiMap.create(); for (Map.Entry<VirtualFile, VcsLogProvider> entry : logProviders.entrySet()) { providers2roots.putValue(entry.getValue(), entry.getKey()); } for (Map.Entry<VcsLogProvider, Collection<VirtualFile>> entry : providers2roots.entrySet()) { Disposable disposable = entry.getKey().subscribeToRootRefreshEvents(entry.getValue(), refresher); Disposer.register(disposableParent, disposable); } } @NotNull public static Map<VirtualFile, VcsLogProvider> findLogProviders(@NotNull Collection<VcsRoot> roots, @NotNull Project project) { Map<VirtualFile, VcsLogProvider> logProviders = ContainerUtil.newHashMap(); VcsLogProvider[] allLogProviders = Extensions.getExtensions(LOG_PROVIDER_EP, project); for (VcsRoot root : roots) { AbstractVcs vcs = root.getVcs(); VirtualFile path = root.getPath(); if (vcs == null || path == null) { LOG.error("Skipping invalid VCS root: " + root); continue; } for (VcsLogProvider provider : allLogProviders) { if (provider.getSupportedVcs().equals(vcs.getKeyInstanceMethod())) { logProviders.put(path, provider); break; } } } return logProviders; } public void disposeLog() { Disposer.dispose(myLogData); } /* * Use VcsLogProjectManager to get main log. * Left here for upsource plugin. * */ @Nullable @Deprecated public static VcsLogManager getInstance(@NotNull Project project) { return ServiceManager.getService(project, VcsProjectLog.class).getLogManager(); } /* * Use VcsLogProjectManager.getMainLogUi to get main log ui. * Left here for upsource plugin. * */ @Nullable @Deprecated public VcsLogUiImpl getMainLogUi() { return ServiceManager.getService(myProject, VcsProjectLog.class).getMainLogUi(); } @Override public void dispose() { disposeLog(); } private class MyFatalErrorsHandler implements FatalErrorHandler { private final AtomicBoolean myIsBroken = new AtomicBoolean(false); @Override public void consume(@Nullable Object source, @NotNull final Exception e) { if (myIsBroken.compareAndSet(false, true)) { processError(source, e); } else { LOG.debug(e); } } protected void processError(@Nullable Object source, @NotNull Exception e) { if (myRecreateMainLogHandler != null) { ApplicationManager.getApplication().invokeLater(() -> { String message = "Fatal error, VCS Log re-created: " + e.getMessage(); if (isLogVisible()) { LOG.info(e); displayFatalErrorMessage(message); } else { LOG.error(message, e); } myRecreateMainLogHandler.run(); }); } else { LOG.error(e); } if (source instanceof VcsLogStorage) { myLogData.getIndex().markCorrupted(); } } @Override public void displayFatalErrorMessage(@NotNull String message) { VcsBalloonProblemNotifier.showOverChangesView(myProject, message, MessageType.ERROR); } } }