diff --git a/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java b/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java index d5413283309f..12e9a1cf0f88 100644 --- a/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java +++ b/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java @@ -30,8 +30,11 @@ import java.util.List; public interface RepositoryManager { /** - * Returns the {@link Repository} which tracks the Git or Hg repository located in the given directory, - * or {@code null} if the given file is not a vcs root known to this {@link com.intellij.openapi.project.Project}. + * Returns the Repository instance which tracks the VCS repository located in the given root directory, + * or {@code null} if the given root is not a valid registered vcs root. + *

+ * The method checks both project roots and external roots previously registered + * via {@link #addExternalRepository(VirtualFile, Repository)}. */ @Nullable T getRepositoryForRoot(@Nullable VirtualFile root); @@ -54,6 +57,16 @@ public interface RepositoryManager { @NotNull List getRepositories(); + /** + * Registers a repository which doesn't belong to the project. + */ + void addExternalRepository(@NotNull VirtualFile root, @NotNull T repository); + + /** + * Removes the repository not from the project, when it is not interesting anymore. + */ + void removeExternalRepository(@NotNull VirtualFile root); + boolean moreThanOneRoot(); /** diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java b/platform/dvcs-impl/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java index 992654248e83..bea3508bc8c0 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/repo/AbstractRepositoryManager.java @@ -8,6 +8,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,7 +31,8 @@ public abstract class AbstractRepositoryManager extends Ab @NotNull private final AbstractVcs myVcs; @NotNull private final String myRepoDirName; - @NotNull protected final Map myRepositories = new HashMap(); + @NotNull protected final Map myRepositories = ContainerUtil.newHashMap(); + @NotNull protected final Map myExternalRepositories = ContainerUtil.newHashMap(); @NotNull protected final ReentrantReadWriteLock REPO_LOCK = new ReentrantReadWriteLock(); @NotNull private final CountDownLatch myInitializationWaiter = new CountDownLatch(1); @@ -73,13 +75,36 @@ public abstract class AbstractRepositoryManager extends Ab } try { REPO_LOCK.readLock().lock(); - return myRepositories.get(root); + T repo = myRepositories.get(root); + return repo != null ? repo : myExternalRepositories.get(root); } finally { REPO_LOCK.readLock().unlock(); } } + @Override + public void addExternalRepository(@NotNull VirtualFile root, @NotNull T repository) { + REPO_LOCK.writeLock().lock(); + try { + myExternalRepositories.put(root, repository); + } + finally { + REPO_LOCK.writeLock().unlock(); + } + } + + @Override + public void removeExternalRepository(@NotNull VirtualFile root) { + REPO_LOCK.writeLock().lock(); + try { + myExternalRepositories.remove(root); + } + finally { + REPO_LOCK.writeLock().unlock(); + } + } + @Override @Nullable public T getRepositoryForFile(@NotNull VirtualFile file) { diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java b/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java index 7e2385430994..d2ee39a24c09 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java @@ -340,7 +340,7 @@ public class FrameWrapper implements Disposable, DataProvider { myTitle = title; } - public void addDisposable(Disposable disposable) { + public void addDisposable(@NotNull Disposable disposable) { Disposer.register(this, disposable); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/FrameDialogWrapper.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/FrameDialogWrapper.java index f89e28ea8b6b..b801608533cd 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/FrameDialogWrapper.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/FrameDialogWrapper.java @@ -1,8 +1,10 @@ package com.intellij.openapi.diff.impl.dir; +import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.FrameWrapper; +import com.intellij.openapi.util.Disposer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,15 +41,22 @@ public abstract class FrameDialogWrapper { return Mode.MODAL; } + @Nullable + protected Disposable getDisposable() { + return null; + } + public void show() { switch (getMode()) { case FRAME: - new MyFrameWrapper(getProject(), getMode(), getPanel(), getPreferredFocusedComponent(), getTitle(), getDimensionServiceKey()) + new MyFrameWrapper(getProject(), getMode(), getPanel(), getPreferredFocusedComponent(), getTitle(), getDimensionServiceKey(), + getDisposable()) .show(); return; case MODAL: case NON_MODAL: - new MyDialogWrapper(getProject(), getMode(), getPanel(), getPreferredFocusedComponent(), getTitle(), getDimensionServiceKey()) + new MyDialogWrapper(getProject(), getMode(), getPanel(), getPreferredFocusedComponent(), getTitle(), getDimensionServiceKey(), + getDisposable()) .show(); return; default: @@ -65,7 +74,8 @@ public abstract class FrameDialogWrapper { @NotNull JComponent component, @Nullable JComponent preferredFocusedComponent, @Nullable String title, - @Nullable String dimensionServiceKey) { + @Nullable String dimensionServiceKey, + @Nullable Disposable disposable) { super(project, true); myComponent = component; myPreferredFocusedComponent = preferredFocusedComponent; @@ -85,6 +95,10 @@ public abstract class FrameDialogWrapper { throw new IllegalArgumentException(mode.toString()); } + if (disposable != null) { + Disposer.register(getDisposable(), disposable); + } + init(); } @@ -119,7 +133,8 @@ public abstract class FrameDialogWrapper { @NotNull JComponent component, @Nullable JComponent preferredFocusedComponent, @Nullable String title, - @Nullable String dimensionServiceKey) { + @Nullable String dimensionServiceKey, + @Nullable Disposable disposable) { super(project, dimensionServiceKey); assert mode == Mode.FRAME; @@ -130,6 +145,9 @@ public abstract class FrameDialogWrapper { setComponent(component); setPreferredFocusedComponent(preferredFocusedComponent); closeOnEsc(); + if (disposable != null) { + addDisposable(disposable); + } } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogContentProvider.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogContentProvider.java index 101b0703904f..ee8a5813d0dc 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogContentProvider.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogContentProvider.java @@ -91,9 +91,6 @@ public class VcsLogContentProvider implements ChangesViewContentProvider, NotNul @NotNull @Override public Boolean fun(Project project) { - if (!Registry.is("git.new.log")) { - return false; - } return !myLogManager.findLogProviders(Arrays.asList(myVcsManager.getAllVcsRoots())).isEmpty(); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogManager.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogManager.java index f6732c7e1300..9cc4ac990efe 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogManager.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogManager.java @@ -47,7 +47,7 @@ public class VcsLogManager implements Disposable { @NotNull private final VcsLogSettings mySettings; @NotNull private final VcsLogUiProperties myUiProperties; - private PostponeableLogRefresher myLogRefresher; + private VcsLogRefresher myLogRefresher; private volatile VcsLogUiImpl myUi; public VcsLogManager(@NotNull Project project, @@ -60,7 +60,7 @@ public class VcsLogManager implements Disposable { } @NotNull - public JComponent initContent(@NotNull Collection roots, @NotNull String contentTabName) { + public JComponent initContent(@NotNull Collection roots, @Nullable String contentTabName) { final Map logProviders = findLogProviders(roots); Consumer visiblePackConsumer = new Consumer() { @@ -76,10 +76,20 @@ public class VcsLogManager implements Disposable { }); } }; - VcsLogDataHolder logDataHolder = new VcsLogDataHolder(myProject, this, logProviders, mySettings, myUiProperties, visiblePackConsumer); + final VcsLogDataHolder logDataHolder = new VcsLogDataHolder(myProject, this, logProviders, mySettings, myUiProperties, visiblePackConsumer); myUi = new VcsLogUiImpl(logDataHolder, myProject, mySettings, new VcsLogColorManagerImpl(logProviders.keySet()), myUiProperties, logDataHolder.getFilterer()); - myLogRefresher = new PostponeableLogRefresher(myProject, logDataHolder, contentTabName); + if (contentTabName != null) { + myLogRefresher = new PostponeableLogRefresher(myProject, logDataHolder, contentTabName); + } + else { + myLogRefresher = new VcsLogRefresher() { + @Override + public void refresh(@NotNull VirtualFile root) { + logDataHolder.refresh(Collections.singletonList(root)); + } + }; + } refreshLogOnVcsEvents(logProviders); logDataHolder.initialize(); @@ -143,7 +153,7 @@ public class VcsLogManager implements Disposable { private static class PostponeableLogRefresher implements VcsLogRefresher, Disposable { - private static final String TOOLWINDOW_ID = ChangesViewContentManager.TOOLWINDOW_ID; + private static final String TOOLWINDOW_ID = ChangesViewContentManager.TOOLWINDOW_ID; @NotNull private final VcsLogDataHolder myDataHolder; @NotNull private final ToolWindowManagerImpl myToolWindowManager; diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index 9e6aea260cea..dade9daf7f34 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -28,7 +28,7 @@ - + diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/SelectRepositoryAndShowLogAction.java b/plugins/git4idea/src/git4idea/history/wholeTree/SelectRepositoryAndShowLogAction.java deleted file mode 100644 index 9257f1fbd27a..000000000000 --- a/plugins/git4idea/src/git4idea/history/wholeTree/SelectRepositoryAndShowLogAction.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright 2000-2012 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 git4idea.history.wholeTree; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; -import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; -import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.ui.content.Content; -import com.intellij.ui.content.ContentFactory; -import com.intellij.ui.content.ContentManager; -import com.intellij.util.Function; -import com.intellij.util.SmartList; -import com.intellij.util.ui.AdjustComponentWhenShown; -import git4idea.GitUtil; -import git4idea.GitVcs; -import git4idea.config.GitVersion; -import git4idea.history.browser.GitProjectLogManager; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.io.File; -import java.util.Arrays; -import java.util.List; - -/** - * @author irengrig - */ -public class SelectRepositoryAndShowLogAction extends AnAction { - public static final String ourTitle = "Show Git Repository Log"; - public static final int MAX_REPOS = 10; - - public SelectRepositoryAndShowLogAction() { - super(ourTitle + "..."); - } - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); - project = project == null ? ProjectManager.getInstance().getDefaultProject() : project; - final Project finalProject = project; - - final FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, true, false, true); - final VirtualFile[] virtualFiles = FileChooser.chooseFiles(descriptor, project, null); - if (virtualFiles.length == 0) return; - if (virtualFiles.length > MAX_REPOS) { - VcsBalloonProblemNotifier.showOverVersionControlView(project, "Too many roots (more than " + MAX_REPOS + - ") selected.", MessageType.ERROR); - return; - } - final List wrongRoots = new SmartList(); - final List correctRoots = new SmartList(); - for (VirtualFile vf : virtualFiles) { - if (! GitUtil.isGitRoot(new File(vf.getPath()))) { - wrongRoots.add(vf); - } else { - correctRoots.add(vf); - } - } - if (! wrongRoots.isEmpty()) { - VcsBalloonProblemNotifier.showOverVersionControlView(project, "These files are not Git repository roots:\n" + - StringUtil.join(wrongRoots, new Function() { - @Override - public String fun(VirtualFile virtualFile) { - return virtualFile.getPath(); - } - }, "\n"), MessageType.ERROR); - } - - if (wrongRoots.size() != virtualFiles.length) { - if (project == null || project.isDefault()) { - ProgressManager.getInstance().run(new MyPrepareToShowForDefaultProject(null, correctRoots)); - return; - } - - final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ChangesViewContentManager.TOOLWINDOW_ID); - - final Runnable showContent = new Runnable() { - @Override - public void run() { - ContentManager cm = window.getContentManager(); - if (checkForProjectScope(cm, finalProject, correctRoots)) return; - - int cnt = 0; - Content[] contents = cm.getContents(); - for (Content content : contents) { - final JComponent component = content.getComponent(); - if (component instanceof MyContentComponent) { - cnt = Math.max(cnt, ((MyContentComponent)component).getCount()); - List roots = ((MyContentComponent)component).getRoots(); - if (Comparing.equal(roots, correctRoots)) { - cm.setSelectedContent(content); - alreadyOpened(finalProject); - return; - } - } - } - - LogFactoryService logFactoryService = LogFactoryService.getInstance(finalProject); - final GitLog gitLog = logFactoryService.createComponent(false); - gitLog.rootsChanged(correctRoots); - final ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); - ++cnt; - MyContentComponent contentComponent = new MyContentComponent(new BorderLayout(), cnt); - contentComponent.setRoots(correctRoots); - contentComponent.add(gitLog.getVisualComponent(), BorderLayout.CENTER); - final Content content = contentFactory.createContent(contentComponent, "Log (" + cnt + ")", false); - content.setDescription("Log for " + StringUtil.join(correctRoots, new Function() { - @Override - public String fun(VirtualFile file) { - return file.getPath(); - } - }, "\n")); - content.setCloseable(true); - Disposer.register(content, gitLog); - cm.addContent(content); - cm.setSelectedContent(content); - } - }; - - ProgressManager.getInstance().run(new MyCheckVersion(project) { - @Override - public void onSuccess() { - if (myVersion == null) return; - if (! window.isVisible()) { - window.activate(showContent, true); - } else { - showContent.run(); - } - } - }); - } - } - - private static void alreadyOpened(final Project project) { - VcsBalloonProblemNotifier.showOverChangesView(project, "Already opened", MessageType.INFO); - } - - private boolean checkForProjectScope(ContentManager cm, Project project, List correctRoots) { - VirtualFile[] rootsUnderVcs = ProjectLevelVcsManager.getInstance(project).getRootsUnderVcs(GitVcs.getInstance(project)); - if (Comparing.equal(correctRoots, Arrays.asList(rootsUnderVcs))) { - Content[] contents = cm.getContents(); - for (Content content : contents) { - if (GitProjectLogManager.CONTENT_KEY.equals(content.getDisplayName())) { - cm.setSelectedContent(content); - alreadyOpened(project); - return true; - } - } - } - return false; - } - - private static class MyContentComponent extends JPanel { - private List myRoots; - private int myCount; - - private MyContentComponent(LayoutManager layout, boolean isDoubleBuffered, int count) { - super(layout, isDoubleBuffered); - myCount = count; - } - - private MyContentComponent(LayoutManager layout, int count) { - super(layout); - myCount = count; - } - - private MyContentComponent(boolean isDoubleBuffered, int count) { - super(isDoubleBuffered); - myCount = count; - } - - public int getCount() { - return myCount; - } - - private MyContentComponent(int count) { - myCount = count; - } - - public void setRoots(List roots) { - myRoots = roots; - } - - public List getRoots() { - return myRoots; - } - } - - private static class MyPrepareToShowForDefaultProject extends Task.Backgroundable { - private Project myProject; - private final List myCorrectRoots; - private MyCheckVersion myVersion; - - private MyPrepareToShowForDefaultProject(@Nullable Project project, List correctRoots) { - super(project, ourTitle, true, BackgroundFromStartOption.getInstance()); - myCorrectRoots = correctRoots; - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - myProject = ProjectManager.getInstance().getDefaultProject(); - myVersion = new MyCheckVersion(myProject); - myVersion.run(indicator); - } - - @Override - public void onSuccess() { - if (myVersion.myVersion.isNull()) return; - if (myProject.isDisposed()) return; - new MyDialog(myProject, myCorrectRoots).show(); - } - } - - private static class MyCheckVersion extends Task.Backgroundable { - protected GitVersion myVersion; - - private MyCheckVersion(@Nullable Project project) { - super(project, ourTitle, true, BackgroundFromStartOption.getInstance()); - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - GitVcs vcs = GitVcs.getInstance(myProject); - if (vcs == null) { return; } - myVersion = vcs.getVersion(); - if (myVersion.isNull()) { - vcs.checkVersion(); - myVersion = vcs.getVersion(); - } - } - } - - private static class MyDialog extends DialogWrapper { - private GitLog myGitLog; - private final Project myProject; - private final List myVirtualFiles; - - private MyDialog(Project project, final List virtualFiles) { - super(project, true); - myProject = project; - myVirtualFiles = virtualFiles; - myGitLog = new LogFactoryService(myProject, ServiceManager.getService(GitCommitsSequentially.class)).createComponent(false); - myGitLog.rootsChanged(myVirtualFiles); - Disposer.register(getDisposable(), myGitLog); - new AdjustComponentWhenShown() { - @Override - protected boolean init() { - myGitLog.setModalityState(ModalityState.current()); - return true; - } - }.install(myGitLog.getVisualComponent()); - setTitle("Git Log"); - init(); - } - - @Override - protected JComponent createCenterPanel() { - return myGitLog.getVisualComponent(); - } - - @NotNull - @Override - protected Action[] createActions() { - return new Action[0]; - } - } -} diff --git a/plugins/git4idea/src/git4idea/log/GitShowExternalLogAction.java b/plugins/git4idea/src/git4idea/log/GitShowExternalLogAction.java new file mode 100644 index 000000000000..911d7e237c74 --- /dev/null +++ b/plugins/git4idea/src/git4idea/log/GitShowExternalLogAction.java @@ -0,0 +1,313 @@ +/* + * Copyright 2000-2012 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 git4idea.log; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.diff.impl.dir.FrameDialogWrapper; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsRoot; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import com.intellij.ui.content.ContentManager; +import com.intellij.util.Function; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.vcs.log.VcsLogSettings; +import com.intellij.vcs.log.data.VcsLogUiProperties; +import com.intellij.vcs.log.impl.VcsLogContentProvider; +import com.intellij.vcs.log.impl.VcsLogManager; +import git4idea.GitPlatformFacade; +import git4idea.GitUtil; +import git4idea.GitVcs; +import git4idea.config.GitVersion; +import git4idea.repo.GitRepositoryImpl; +import git4idea.repo.GitRepositoryManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class GitShowExternalLogAction extends AnAction { + + private static final Logger LOG = Logger.getInstance(GitShowExternalLogAction.class); + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + e.getPresentation().setEnabledAndVisible(e.getProject() != null && GitVcs.getInstance(e.getProject()) != null); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final Project project = e.getRequiredData(CommonDataKeys.PROJECT); + final GitVcs vcs = ObjectUtils.assertNotNull(GitVcs.getInstance(project)); + final List roots = getGitRootsFromUser(project); + if (roots.isEmpty()) { + return; + } + + if (project.isDefault() || !ProjectLevelVcsManager.getInstance(project).hasActiveVcss()) { + ProgressManager.getInstance().run(new ShowLogInDialogTask(project, roots, vcs)); + return; + } + + final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(ChangesViewContentManager.TOOLWINDOW_ID); + final Runnable showContent = new Runnable() { + @Override + public void run() { + ContentManager cm = window.getContentManager(); + if (checkIfProjectLogMatches(project, vcs, cm, roots) || checkIfAlreadyOpened(cm, roots)) { + return; + } + + String tabName = calcTabName(cm, roots); + MyContentComponent component = createManagerAndContent(project, vcs, roots, tabName); + Content content = ContentFactory.SERVICE.getInstance().createContent(component, tabName, false); + content.setDisposer(component.myDisposable); + content.setDescription("Log for " + StringUtil.join(roots, new Function() { + @Override + public String fun(VirtualFile file) { + return file.getPath(); + } + }, "\n")); + content.setCloseable(true); + cm.addContent(content); + cm.setSelectedContent(content); + } + }; + + if (!window.isVisible()) { + window.activate(showContent, true); + } + else { + showContent.run(); + } + } + + @NotNull + private static MyContentComponent createManagerAndContent(@NotNull Project project, + @NotNull final GitVcs vcs, + @NotNull final List roots, + @Nullable String tabName) { + final GitRepositoryManager repositoryManager = ServiceManager.getService(project, GitRepositoryManager.class); + GitPlatformFacade facade = ServiceManager.getService(GitPlatformFacade.class); + for (VirtualFile root : roots) { + repositoryManager.addExternalRepository(root, GitRepositoryImpl.getFullInstance(root, project, facade, project)); + } + VcsLogManager manager = new VcsLogManager(project, ServiceManager.getService(project, VcsLogSettings.class), + ServiceManager.getService(project, VcsLogUiProperties.class)); + Collection vcsRoots = ContainerUtil.map(roots, new Function() { + @Override + public VcsRoot fun(VirtualFile root) { + return new VcsRoot(vcs, root); + } + }); + return new MyContentComponent(manager.initContent(vcsRoots, tabName), roots, new Disposable() { + @Override + public void dispose() { + for (VirtualFile root : roots) { + repositoryManager.removeExternalRepository(root); + } + } + }); + } + + @NotNull + private static String calcTabName(@NotNull ContentManager cm, @NotNull List roots) { + String name = VcsLogContentProvider.TAB_NAME + " (" + roots.get(0).getName(); + if (roots.size() > 1) { + name += "+"; + } + name += ")"; + + String candidate = name; + int cnt = 1; + while (hasContentsWithName(cm, candidate)) { + candidate = name + "-" + cnt; + cnt++; + } + return candidate; + } + + private static boolean hasContentsWithName(@NotNull ContentManager cm, @NotNull final String candidate) { + return ContainerUtil.exists(cm.getContents(), new Condition() { + @Override + public boolean value(Content content) { + return content.getDisplayName().equals(candidate); + } + }); + } + + @NotNull + private static List getGitRootsFromUser(@NotNull Project project) { + FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, true, false, true); + VirtualFile[] virtualFiles = FileChooser.chooseFiles(descriptor, project, null); + if (virtualFiles.length == 0) { + return Collections.emptyList(); + } + + List correctRoots = ContainerUtil.newArrayList(); + for (VirtualFile vf : virtualFiles) { + if (GitUtil.isGitRoot(new File(vf.getPath()))) { + correctRoots.add(vf); + } + } + return correctRoots; + } + + private static boolean checkIfProjectLogMatches(@NotNull Project project, + @NotNull GitVcs vcs, + @NotNull ContentManager cm, + @NotNull List requestedRoots) { + VirtualFile[] projectRoots = ProjectLevelVcsManager.getInstance(project).getRootsUnderVcs(vcs); + if (Comparing.haveEqualElements(requestedRoots, Arrays.asList(projectRoots))) { + Content[] contents = cm.getContents(); + for (Content content : contents) { + if (VcsLogContentProvider.TAB_NAME.equals(content.getDisplayName())) { + cm.setSelectedContent(content); + return true; + } + } + } + return false; + } + + private static boolean checkIfAlreadyOpened(@NotNull ContentManager cm, @NotNull Collection roots) { + for (Content content : cm.getContents()) { + final JComponent component = content.getComponent(); + if (component instanceof MyContentComponent) { + if (Comparing.haveEqualElements(roots, ((MyContentComponent)component).myRoots)) { + cm.setSelectedContent(content); + return true; + } + } + } + return false; + } + + private static class MyContentComponent extends JPanel { + @NotNull private final Collection myRoots; + @NotNull private final Disposable myDisposable; + + MyContentComponent(@NotNull JComponent actualComponent, @NotNull Collection roots, @NotNull Disposable disposable) { + super(new BorderLayout()); + myDisposable = disposable; + myRoots = roots; + add(actualComponent); + } + } + + private static class ShowLogInDialogTask extends Task.Backgroundable { + @NotNull private final Project myProject; + @NotNull private final List myRoots; + @NotNull private final GitVcs myVcs; + private GitVersion myVersion; + + private ShowLogInDialogTask(@NotNull Project project, @NotNull List roots, @NotNull GitVcs vcs) { + super(project, "Loading Git Log...", true, BackgroundFromStartOption.getInstance()); + myProject = project; + myRoots = roots; + myVcs = vcs; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + myVersion = myVcs.getVersion(); + if (myVersion.isNull()) { + myVcs.checkVersion(); + myVersion = myVcs.getVersion(); + } + } + + @Override + public void onSuccess() { + if (!myVersion.isNull() && !myProject.isDisposed()) { + new MyDialog(myProject, myVcs, myRoots).show(); + } + } + } + + private static class MyDialog extends FrameDialogWrapper { + @NotNull private final MyContentComponent myContent; + @NotNull private final Project myProject; + + private MyDialog(@NotNull Project project, @NotNull GitVcs vcs, @NotNull final List roots) { + myProject = project; + myContent = createManagerAndContent(project, vcs, roots, null); + } + + @NotNull + @Override + protected Mode getMode() { + return Mode.FRAME; + } + + @NotNull + @Override + public Project getProject() { + return myProject; + } + + @Nullable + @Override + protected String getTitle() { + return "Git Log"; + } + + @NotNull + @Override + protected JComponent getPanel() { + return myContent; + } + + @Nullable + @Override + protected JComponent getPreferredFocusedComponent() { + return myContent; + } + + @Nullable + @Override + protected Disposable getDisposable() { + return myContent.myDisposable; + } + } +}