[git] don't delete stashed changelist from disk.
[idea/community.git] / plugins / git4idea / src / git4idea / update / GitStashUtils.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package git4idea.update;
17
18 import com.intellij.openapi.application.ModalityState;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.progress.AsynchronousExecution;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.vcs.AbstractVcsHelper;
23 import com.intellij.openapi.vcs.FilePath;
24 import com.intellij.openapi.vcs.FileStatus;
25 import com.intellij.openapi.vcs.VcsException;
26 import com.intellij.openapi.vcs.changes.Change;
27 import com.intellij.openapi.vcs.changes.ChangeListManager;
28 import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode;
29 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
30 import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager;
31 import com.intellij.openapi.vcs.changes.shelf.ShelvedBinaryFile;
32 import com.intellij.openapi.vcs.changes.shelf.ShelvedChange;
33 import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList;
34 import com.intellij.openapi.vfs.LocalFileSystem;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.util.Consumer;
37 import com.intellij.util.continuation.ContinuationContext;
38 import com.intellij.util.continuation.TaskDescriptor;
39 import com.intellij.util.continuation.Where;
40 import com.intellij.vcsUtil.VcsUtil;
41 import git4idea.GitUtil;
42 import git4idea.GitVcs;
43 import git4idea.commands.GitCommand;
44 import git4idea.commands.GitFileUtils;
45 import git4idea.commands.GitSimpleHandler;
46 import git4idea.commands.StringScanner;
47 import git4idea.config.GitConfigUtil;
48 import git4idea.config.GitVersion;
49 import git4idea.ui.GitUIUtil;
50 import git4idea.ui.StashInfo;
51 import git4idea.vfs.GitVFSListener;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
54
55 import javax.swing.event.ChangeEvent;
56 import java.io.File;
57 import java.io.IOException;
58 import java.nio.charset.Charset;
59 import java.util.*;
60
61 /**
62  * The class contains utilities for creating and removing stashes.
63  */
64 public class GitStashUtils {
65   /**
66    * The version when quiet stash supported
67    */
68   private final static GitVersion QUIET_STASH_SUPPORTED = new GitVersion(1, 6, 4, 0);
69   private static final Logger LOG = Logger.getInstance(GitStashUtils.class.getName());
70
71   private GitStashUtils() {
72   }
73
74   /**
75    * Create stash for later use
76    *
77    * @param project the project to use
78    * @param root    the root
79    * @param message the message for the stash
80    * @return true if the stash was created, false otherwise
81    */
82   public static boolean saveStash(@NotNull Project project, @NotNull VirtualFile root, final String message) throws VcsException {
83     GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.STASH);
84     handler.setNoSSH(true);
85     handler.addParameters("save", message);
86     String output = handler.run();
87     return !output.startsWith("No local changes to save");
88   }
89
90   public static void loadStashStack(@NotNull Project project, @NotNull VirtualFile root, Consumer<StashInfo> consumer) {
91     loadStashStack(project, root, Charset.forName(GitConfigUtil.getLogEncoding(project, root)), consumer);
92   }
93
94   public static void loadStashStack(@NotNull Project project, @NotNull VirtualFile root, final Charset charset,
95                                     final Consumer<StashInfo> consumer) {
96     GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.STASH);
97     h.setSilent(true);
98     h.setNoSSH(true);
99     h.addParameters("list");
100     String out;
101     try {
102       h.setCharset(charset);
103       out = h.run();
104     }
105     catch (VcsException e) {
106       GitUIUtil.showOperationError(project, e, h.printableCommandLine());
107       return;
108     }
109     for (StringScanner s = new StringScanner(out); s.hasMoreData();) {
110       consumer.consume(new StashInfo(s.boundedToken(':'), s.boundedToken(':'), s.line().trim()));
111     }
112   }
113
114   /**
115    * Perform system level unshelve operation
116    *
117    * @param project           the project
118    * @param shelvedChangeList the shelved change list
119    * @param shelveManager     the shelve manager
120    * @param restoreListsRunnable
121    */
122   @AsynchronousExecution
123   public static void doSystemUnshelve(final Project project,
124                                       final ShelvedChangeList shelvedChangeList,
125                                       final ShelveChangesManager shelveManager,
126                                       @NotNull final Runnable restoreListsRunnable,
127                                       final @NotNull ContinuationContext context) {
128     VirtualFile baseDir = project.getBaseDir();
129     assert baseDir != null;
130     final String projectPath = baseDir.getPath() + "/";
131
132     context.next(new TaskDescriptor("Refreshing files before unshelve", Where.POOLED) {
133         @Override
134         public void run(ContinuationContext context) {
135           LOG.info("doSystemUnshelve ");
136           // The changes are temporary copied to the first local change list, the next operation will restore them back
137           // Refresh files that might be affected by unshelve
138           refreshFilesBeforeUnshelve(shelvedChangeList, projectPath);
139
140           LOG.info("doSystemUnshelve files refreshed. unshelving in AWT thread.");
141         }
142       }, new TaskDescriptor("", Where.AWT) {
143         @Override
144         public void run(ContinuationContext context) {
145           GitVFSListener l = GitVcs.getInstance(project).getVFSListener();
146           l.setEventsSuppressed(true);
147
148           LOG.info("Unshelving in UI thread. shelvedChangeList: " + shelvedChangeList);
149           // we pass null as target change list for Patch Applier to do NOTHING with change lists
150           shelveManager.scheduleUnshelveChangeList(shelvedChangeList, shelvedChangeList.getChanges(),
151                                                    shelvedChangeList.getBinaryFiles(), null, false, context);
152         }
153       }, new TaskDescriptor("", Where.AWT) {
154       @Override
155       public void run(ContinuationContext context) {
156         GitVcs.getInstance(project).getVFSListener().setEventsSuppressed(false);
157         addFilesAfterUnshelve(project, shelvedChangeList, projectPath, context);
158         ChangeListManager.getInstance(project).invokeAfterUpdate(new Runnable() {
159             @Override
160             public void run() {
161               restoreListsRunnable.run();
162             }
163           }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE_NOT_AWT, "Restoring changelists", ModalityState.NON_MODAL);
164       }
165     });
166   }
167
168   private static void addFilesAfterUnshelve(Project project,
169                                             ShelvedChangeList shelvedChangeList,
170                                             String projectPath, ContinuationContext context) {
171     Collection<FilePath> paths = new ArrayList<FilePath>();
172     for (ShelvedChange c : shelvedChangeList.getChanges()) {
173       if (c.getBeforePath() == null || !c.getBeforePath().equals(c.getAfterPath()) || c.getFileStatus() == FileStatus.ADDED) {
174         paths.add(VcsUtil.getFilePath(projectPath + c.getAfterPath()));
175       }
176     }
177     for (ShelvedBinaryFile f : shelvedChangeList.getBinaryFiles()) {
178       if (f.BEFORE_PATH == null || !f.BEFORE_PATH.equals(f.AFTER_PATH) || f.getFileStatus() == FileStatus.ADDED) {
179         paths.add(VcsUtil.getFilePath(projectPath + f.AFTER_PATH));
180       }
181     }
182     final VcsDirtyScopeManager dsm = VcsDirtyScopeManager.getInstance(project);
183     Map<VirtualFile, List<FilePath>> map = GitUtil.sortGitFilePathsByGitRoot(paths);
184     for (Map.Entry<VirtualFile, List<FilePath>> e : map.entrySet()) {
185       try {
186         GitFileUtils.addPaths(project, e.getKey(), e.getValue());
187         dsm.filePathsDirty(e.getValue(), null);
188       }
189       catch (VcsException e1) {
190         if (! context.handleException(e1)) {
191           AbstractVcsHelper.getInstance(project).showError(e1, "Can not add file to Git");
192           LOG.error("Vcs Exception not handled");
193         }
194       }
195     }
196   }
197
198   private static void refreshFilesBeforeUnshelve(ShelvedChangeList shelvedChangeList, String projectPath) {
199     HashSet<File> filesToRefresh = new HashSet<File>();
200     for (ShelvedChange c : shelvedChangeList.getChanges()) {
201       if (c.getBeforePath() != null) {
202         filesToRefresh.add(new File(projectPath + c.getBeforePath()));
203       }
204       if (c.getAfterPath() != null) {
205         filesToRefresh.add(new File(projectPath + c.getAfterPath()));
206       }
207     }
208     for (ShelvedBinaryFile f : shelvedChangeList.getBinaryFiles()) {
209       if (f.BEFORE_PATH != null) {
210         filesToRefresh.add(new File(projectPath + f.BEFORE_PATH));
211       }
212       if (f.AFTER_PATH != null) {
213         filesToRefresh.add(new File(projectPath + f.BEFORE_PATH));
214       }
215     }
216     LocalFileSystem.getInstance().refreshIoFiles(filesToRefresh);
217   }
218
219   /**
220    * Shelve changes
221    *
222    * @param project       the context project
223    * @param shelveManager the shelve manager
224    * @param changes       the changes to process
225    * @param description   the description of for the shelve
226    * @param exceptions    the generated exceptions
227    * @return created shelved change list or null in case failure
228    */
229   @Nullable
230   public static ShelvedChangeList shelveChanges(final Project project, final ShelveChangesManager shelveManager, Collection<Change> changes,
231                                                 final String description,
232                                                 final List<VcsException> exceptions) {
233     try {
234       ShelvedChangeList shelve = shelveManager.shelveChanges(changes, description);
235       project.getMessageBus().syncPublisher(ShelveChangesManager.SHELF_TOPIC).stateChanged(new ChangeEvent(GitStashUtils.class));
236       return shelve;
237     }
238     catch (IOException e) {
239       //noinspection ThrowableInstanceNeverThrown
240       exceptions.add(new VcsException("Shelving changes failed: " + description, e));
241       return null;
242     }
243     catch (VcsException e) {
244       exceptions.add(e);
245       return null;
246     }
247   }
248 }