blob: 8e20a67fceabfd9c9a3ce199220c770220904060 [file] [log] [blame]
/*
* Copyright 2000-2011 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.stash;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.impl.LocalChangesUnderRoots;
import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.continuation.ContinuationContext;
import git4idea.GitPlatformFacade;
import git4idea.commands.Git;
import git4idea.commands.GitCommandResult;
import git4idea.commands.GitHandlerUtil;
import git4idea.commands.GitSimpleEventDetector;
import git4idea.merge.GitConflictResolver;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.ui.GitUnstashDialog;
import git4idea.util.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.util.*;
/**
* @author Kirill Likhodedov
*/
public class GitStashChangesSaver extends GitChangesSaver {
private static final Logger LOG = Logger.getInstance(GitStashChangesSaver.class);
private final Set<VirtualFile> myStashedRoots = new HashSet<VirtualFile>(); // save stashed roots to unstash only them
@NotNull private final GitRepositoryManager myRepositoryManager;
public GitStashChangesSaver(@NotNull Project project, @NotNull GitPlatformFacade platformFacade, @NotNull Git git,
@NotNull ProgressIndicator progressIndicator, @NotNull String stashMessage) {
super(project, platformFacade, git, progressIndicator, stashMessage);
myRepositoryManager = platformFacade.getRepositoryManager(project);
}
@Override
protected void save(Collection<VirtualFile> rootsToSave) throws VcsException {
LOG.info("save " + rootsToSave);
final Map<VirtualFile, Collection<Change>> changes =
new LocalChangesUnderRoots(myChangeManager, myPlatformFacade.getVcsManager(myProject)).getChangesUnderRoots(rootsToSave);
stash(changes.keySet());
}
@Override
protected void load(@NotNull ContinuationContext context) {
try {
load();
}
catch (VcsException e) {
context.handleException(e, false);
}
}
public void load() throws VcsException {
for (VirtualFile root : myStashedRoots) {
loadRoot(root);
}
boolean conflictsResolved = new UnstashConflictResolver(myProject, myPlatformFacade, myGit, myStashedRoots, myParams).merge();
LOG.info("load: conflicts resolved status is " + conflictsResolved + " in roots " + myStashedRoots);
}
@Override
protected boolean wereChangesSaved() {
return !myStashedRoots.isEmpty();
}
@Override
public String getSaverName() {
return "stash";
}
@Override
protected void showSavedChanges() {
GitUnstashDialog.showUnstashDialog(myProject, new ArrayList<VirtualFile>(myStashedRoots), myStashedRoots.iterator().next());
}
@Override
public void refresh() {
// we'll refresh more but this way we needn't compute what files under roots etc
LocalFileSystem.getInstance().refreshIoFiles(myChangeManager.getAffectedPaths());
}
private void stash(Collection<VirtualFile> roots) throws VcsException {
for (VirtualFile root : roots) {
final String message = GitHandlerUtil.formatOperationName("Stashing changes from", root);
LOG.info(message);
final String oldProgressTitle = myProgressIndicator.getText();
myProgressIndicator.setText(message);
GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
if (repository == null) {
LOG.error("Repository is null for root " + root);
}
else if (GitStashUtils.saveStash(myGit, repository, myStashMessage)) {
myStashedRoots.add(root);
}
myProgressIndicator.setText(oldProgressTitle);
}
}
/**
* Returns true if the root was loaded with conflict.
* False is returned in all other cases: in the case of success and in case of some other error.
*/
private boolean loadRoot(final VirtualFile root) throws VcsException {
LOG.info("loadRoot " + root);
myProgressIndicator.setText(GitHandlerUtil.formatOperationName("Unstashing changes to", root));
GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
if (repository == null) {
LOG.error("Repository is null for root " + root);
return false;
}
GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(GitSimpleEventDetector.Event.MERGE_CONFLICT_ON_UNSTASH);
GitCommandResult result = myGit.stashPop(repository, conflictDetector);
if (result.success()) {
return false;
}
else if (conflictDetector.hasHappened()) {
return true;
}
else {
LOG.info("unstash failed " + result.getErrorOutputAsJoinedString());
GitUIUtil.notifyImportantError(myProject, "Couldn't unstash", "<br/>" + result.getErrorOutputAsHtmlString());
return false;
}
}
private static class UnstashConflictResolver extends GitConflictResolver {
private final Set<VirtualFile> myStashedRoots;
public UnstashConflictResolver(@NotNull Project project, GitPlatformFacade platformFacade, @NotNull Git git,
@NotNull Set<VirtualFile> stashedRoots, @Nullable Params params) {
super(project, git, platformFacade, stashedRoots, makeParamsOrUse(params));
myStashedRoots = stashedRoots;
}
private static Params makeParamsOrUse(@Nullable Params givenParams) {
if (givenParams != null) {
return givenParams;
}
Params params = new Params();
params.setErrorNotificationTitle("Local changes were not restored");
params.setMergeDialogCustomizer(new UnstashMergeDialogCustomizer());
params.setReverse(true);
return params;
}
@Override
protected void notifyUnresolvedRemain() {
VcsNotifier.getInstance(myProject).notifyImportantWarning("Local changes were restored with conflicts",
"Your uncommitted changes were saved to <a href='saver'>stash</a>.<br/>" +
"Unstash is not complete, you have unresolved merges in your working tree<br/>" +
"<a href='resolve'>Resolve</a> conflicts and drop the stash.",
new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification,
@NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if (event.getDescription().equals("saver")) {
// we don't use #showSavedChanges to specify unmerged root first
GitUnstashDialog.showUnstashDialog(myProject,
new ArrayList<VirtualFile>(
myStashedRoots),
myStashedRoots.iterator().next()
);
}
else if (event.getDescription().equals("resolve")) {
mergeNoProceed();
}
}
}
}
);
}
}
private static class UnstashMergeDialogCustomizer extends MergeDialogCustomizer {
@Override
public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
return "Uncommitted changes that were stashed before update have conflicts with updated files.";
}
@Override
public String getLeftPanelTitle(VirtualFile file) {
return getConflictLeftPanelTitle();
}
@Override
public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
return getConflictRightPanelTitle();
}
}
}