/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.git.client.history; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.IsWidget; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.git.gwt.client.GitServiceClient; import org.eclipse.che.api.git.shared.LogResponse; import org.eclipse.che.api.git.shared.Revision; import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.event.project.CloseCurrentProjectEvent; import org.eclipse.che.ide.api.event.project.CloseCurrentProjectHandler; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.parts.PartPresenter; import org.eclipse.che.ide.api.parts.PartStackType; import org.eclipse.che.ide.api.parts.WorkspaceAgent; import org.eclipse.che.ide.api.parts.base.BasePresenter; import org.eclipse.che.ide.api.project.node.HasStorablePath; import org.eclipse.che.ide.api.selection.Selection; import org.eclipse.che.ide.api.selection.SelectionAgent; import org.eclipse.che.ide.ext.git.client.DateTimeFormatter; import org.eclipse.che.ide.ext.git.client.GitLocalizationConstant; import org.eclipse.che.ide.ext.git.client.GitResources; import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsole; import org.eclipse.che.ide.ext.git.client.outputconsole.GitOutputConsoleFactory; import org.eclipse.che.ide.extension.machine.client.processes.ConsolesPanelPresenter; import org.eclipse.che.ide.rest.AsyncRequestCallback; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.rest.StringUnmarshaller; import org.vectomatic.dom.svg.ui.SVGResource; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static org.eclipse.che.api.git.shared.DiffRequest.DiffType.RAW; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; /** * Presenter for showing git history. * This presenter must implements ActivePartChangedHandler and PropertyListener to be able to get the changes for the selected resource * and change a dedicated resource with the history-window open * * @author Ann Zhuleva * @author Vlad Zhukovskyi */ @Singleton public class HistoryPresenter extends BasePresenter implements HistoryView.ActionDelegate { public static final String LOG_COMMAND_NAME = "Git log"; public static final String DIFF_COMMAND_NAME = "Git diff"; private final DtoUnmarshallerFactory dtoUnmarshallerFactory; private final HistoryView view; private final GitServiceClient service; private final GitLocalizationConstant constant; private final GitResources resources; private final AppContext appContext; private final WorkspaceAgent workspaceAgent; private final DateTimeFormatter dateTimeFormatter; private final GitOutputConsoleFactory gitOutputConsoleFactory; private final ConsolesPanelPresenter consolesPanelPresenter; private final String workspaceId; /** If <code>true</code> then show all changes in project, if <code>false</code> then show changes of the selected resource. */ private boolean showChangesInProject; private DiffWith diffType; private boolean isViewClosed = true; private List<Revision> revisions; private SelectionAgent selectionAgent; private Revision selectedRevision; private NotificationManager notificationManager; @Inject public HistoryPresenter(final HistoryView view, EventBus eventBus, GitResources resources, GitServiceClient service, final WorkspaceAgent workspaceAgent, GitLocalizationConstant constant, AppContext appContext, NotificationManager notificationManager, DtoUnmarshallerFactory dtoUnmarshallerFactory, DateTimeFormatter dateTimeFormatter, SelectionAgent selectionAgent, GitOutputConsoleFactory gitOutputConsoleFactory, ConsolesPanelPresenter consolesPanelPresenter) { this.view = view; this.dateTimeFormatter = dateTimeFormatter; this.gitOutputConsoleFactory = gitOutputConsoleFactory; this.consolesPanelPresenter = consolesPanelPresenter; this.view.setDelegate(this); this.view.setTitle(constant.historyTitle()); this.resources = resources; this.service = service; this.workspaceAgent = workspaceAgent; this.constant = constant; this.appContext = appContext; this.notificationManager = notificationManager; this.dtoUnmarshallerFactory = dtoUnmarshallerFactory; this.selectionAgent = selectionAgent; this.workspaceId = appContext.getWorkspaceId(); eventBus.addHandler(CloseCurrentProjectEvent.TYPE, new CloseCurrentProjectHandler() { @Override public void onCloseCurrentProject(CloseCurrentProjectEvent event) { isViewClosed = true; workspaceAgent.hidePart(HistoryPresenter.this); view.clear(); } }); } /** Show dialog. */ public void showDialog() { ProjectConfigDto project = appContext.getCurrentProject().getRootProject(); getCommitsLog(project); selectedRevision = null; view.selectProjectChangesButton(true); view.selectResourceChangesButton(false); showChangesInProject = true; view.selectDiffWithPrevVersionButton(true); diffType = DiffWith.DIFF_WITH_PREV_VERSION; displayCommitA(null); displayCommitB(null); view.setDiffContext(""); view.setCompareType(constant.historyNothingToDisplay()); if (isViewClosed) { workspaceAgent.openPart(this, PartStackType.TOOLING); isViewClosed = false; } PartPresenter activePart = partStack.getActivePart(); if (activePart == null || !activePart.equals(this)) { partStack.setActivePart(this); } } /** Get the log of the commits. If successfully received, then display in revision grid, otherwise - show error in output panel. */ private void getCommitsLog(final ProjectConfigDto project) { service.log(workspaceId, project, null, false, new AsyncRequestCallback<LogResponse>(dtoUnmarshallerFactory.newUnmarshaller(LogResponse.class)) { @Override protected void onSuccess(LogResponse result) { revisions = result.getCommits(); view.setRevisions(revisions); } @Override protected void onFailure(Throwable exception) { nothingToDisplay(null); String errorMessage = exception.getMessage() != null ? exception.getMessage() : constant.logFailed(); GitOutputConsole console = gitOutputConsoleFactory.create(LOG_COMMAND_NAME); console.printError(errorMessage); consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console); notificationManager.notify(constant.logFailed(), FAIL, true, project); } }); } /** * Clear the comparance result, when there is nothing to compare. * * @param revision */ private void nothingToDisplay(@Nullable Revision revision) { displayCommitA(revision); displayCommitB(null); view.setCompareType(constant.historyNothingToDisplay()); view.setDiffContext(""); } /** * Display information about commit A. * * @param revision * revision what need to display */ private void displayCommitA(@Nullable Revision revision) { if (revision == null) { view.setCommitADate(""); view.setCommitARevision(""); } else { view.setCommitADate(dateTimeFormatter.getFormattedDate(revision.getCommitTime())); view.setCommitARevision(revision.getId()); } } /** * Display information about commit B. * * @param revision * revision what need to display */ private void displayCommitB(@Nullable Revision revision) { boolean isEmpty = revision == null; if (isEmpty) { view.setCommitBDate(""); view.setCommitBRevision(""); } else { view.setCommitBDate(dateTimeFormatter.getFormattedDate(revision.getCommitTime())); view.setCommitBRevision(revision.getId()); } view.setCommitBPanelVisible(!isEmpty); } /** {@inheritDoc} */ @Override public void onRefreshClicked() { ProjectConfigDto project = appContext.getCurrentProject().getRootProject(); getCommitsLog(project); } /** {@inheritDoc} */ @Override public void onProjectChangesClicked() { if (showChangesInProject) { return; } showChangesInProject = true; view.selectProjectChangesButton(true); view.selectResourceChangesButton(false); update(); } /** {@inheritDoc} */ @Override public void onResourceChangesClicked() { if (!showChangesInProject) { return; } showChangesInProject = false; view.selectProjectChangesButton(false); view.selectResourceChangesButton(true); update(); } /** {@inheritDoc} */ @Override public void onDiffWithIndexClicked() { if (DiffWith.DIFF_WITH_INDEX.equals(diffType)) { return; } diffType = DiffWith.DIFF_WITH_INDEX; view.selectDiffWithIndexButton(true); view.selectDiffWithPrevVersionButton(false); view.selectDiffWithWorkingTreeButton(false); update(); } /** {@inheritDoc} */ @Override public void onDiffWithWorkTreeClicked() { if (DiffWith.DIFF_WITH_WORK_TREE.equals(diffType)) { return; } diffType = DiffWith.DIFF_WITH_WORK_TREE; view.selectDiffWithIndexButton(false); view.selectDiffWithPrevVersionButton(false); view.selectDiffWithWorkingTreeButton(true); update(); } /** {@inheritDoc} */ @Override public void onDiffWithPrevCommitClicked() { if (DiffWith.DIFF_WITH_PREV_VERSION.equals(diffType)) { return; } diffType = DiffWith.DIFF_WITH_PREV_VERSION; view.selectDiffWithIndexButton(false); view.selectDiffWithPrevVersionButton(true); view.selectDiffWithWorkingTreeButton(false); update(); } /** {@inheritDoc} */ @Override public void onRevisionSelected(@NotNull Revision revision) { selectedRevision = revision; update(); } /** Update content. */ private void update() { getDiff(); ProjectConfigDto project = appContext.getCurrentProject().getRootProject(); getCommitsLog(project); } /** Get the changes between revisions. On success - display diff in text format, otherwise - show the error message in output panel. */ private void getDiff() { String pattern = ""; ProjectConfigDto project = appContext.getCurrentProject().getRootProject(); if (!showChangesInProject && project != null) { String path; Selection<HasStorablePath> selection = (Selection<HasStorablePath>)selectionAgent.getSelection(); if (selection == null || selection.getHeadElement() == null) { path = project.getPath(); } else { path = selection.getHeadElement().getStorablePath(); } pattern = path.replaceFirst(project.getPath(), ""); pattern = (pattern.startsWith("/")) ? pattern.replaceFirst("/", "") : pattern; } if (DiffWith.DIFF_WITH_INDEX.equals(diffType) || DiffWith.DIFF_WITH_WORK_TREE.equals(diffType)) { boolean isCached = DiffWith.DIFF_WITH_INDEX.equals(diffType); doDiffWithNotCommitted((pattern.length() > 0) ? new ArrayList<>(Arrays.asList(pattern)) : new ArrayList<String>(), selectedRevision, isCached); } else { doDiffWithPrevVersion((pattern.length() > 0) ? new ArrayList<>(Arrays.asList(pattern)) : new ArrayList<String>(), selectedRevision); } } /** * Perform diff between pointed revision and index or working tree. * * @param filePatterns * patterns for which to show diff * @param revision * revision to compare with * @param isCached * if <code>true</code> compare with index, else - with working tree */ private void doDiffWithNotCommitted(@NotNull List<String> filePatterns, @Nullable final Revision revision, final boolean isCached) { if (revision == null) { return; } final ProjectConfigDto project = appContext.getCurrentProject().getRootProject(); service.diff(workspaceId, project, filePatterns, RAW, false, 0, revision.getId(), isCached, new AsyncRequestCallback<String>(new StringUnmarshaller()) { @Override protected void onSuccess(String result) { view.setDiffContext(result); String text = isCached ? constant.historyDiffIndexState() : constant.historyDiffTreeState(); displayCommitA(revision); view.setCompareType(text); } @Override protected void onFailure(Throwable exception) { nothingToDisplay(revision); String errorMessage = exception.getMessage() != null ? exception.getMessage() : constant.diffFailed(); GitOutputConsole console = gitOutputConsoleFactory.create(DIFF_COMMAND_NAME); console.printError(errorMessage); consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console); notificationManager.notify(constant.diffFailed(), FAIL, true, project); } }); } /** * Perform diff between selected commit and previous one. * * @param filePatterns * patterns for which to show diff * @param revisionB * selected commit */ private void doDiffWithPrevVersion(@NotNull List<String> filePatterns, @Nullable final Revision revisionB) { if (revisionB == null) { return; } int index = revisions.indexOf(revisionB); if (index + 1 < revisions.size()) { final Revision revisionA = revisions.get(index + 1); final ProjectConfigDto project = appContext.getCurrentProject().getRootProject(); service.diff(workspaceId, project, filePatterns, RAW, false, 0, revisionA.getId(), revisionB.getId(), new AsyncRequestCallback<String>(new StringUnmarshaller()) { @Override protected void onSuccess(String result) { view.setDiffContext(result); view.setCompareType(""); displayCommitA(revisionA); displayCommitB(revisionB); } @Override protected void onFailure(Throwable exception) { nothingToDisplay(revisionB); String errorMessage = exception.getMessage() != null ? exception.getMessage() : constant.diffFailed(); GitOutputConsole console = gitOutputConsoleFactory.create(DIFF_COMMAND_NAME); console.printError(errorMessage); consolesPanelPresenter.addCommandOutput(appContext.getDevMachineId(), console); notificationManager.notify(constant.diffFailed(), FAIL, true, project); } }); } else { nothingToDisplay(revisionB); } } /** {@inheritDoc} */ @Override public String getTitle() { return constant.historyTitle(); } /** {@inheritDoc} */ @Override public ImageResource getTitleImage() { return null; } /** {@inheritDoc} */ @Override public SVGResource getTitleSVGImage() { return resources.history(); } /** {@inheritDoc} */ @Override public String getTitleToolTip() { return constant.historyTitle(); } /** {@inheritDoc} */ @Override public void go(AcceptsOneWidget container) { container.setWidget(view); } /** {@inheritDoc} */ @Override public int getSize() { return 450; } protected enum DiffWith { DIFF_WITH_INDEX, DIFF_WITH_WORK_TREE, DIFF_WITH_PREV_VERSION } @Override public void setVisible(boolean visible) { view.setVisible(visible); } @Override public IsWidget getView() { return view; } }