2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 import com.intellij.notification.Notification;
19 import com.intellij.notification.NotificationListener;
20 import com.intellij.notification.NotificationType;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ModalityState;
23 import com.intellij.openapi.components.ServiceManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.progress.ProgressIndicator;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.progress.Task;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.ui.DialogWrapper;
30 import com.intellij.openapi.ui.Messages;
31 import com.intellij.openapi.util.Key;
32 import com.intellij.openapi.vcs.VcsException;
33 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
34 import com.intellij.openapi.vcs.merge.MergeDialogCustomizer;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.ui.DocumentAdapter;
37 import com.intellij.util.Consumer;
38 import git4idea.GitBranch;
39 import git4idea.GitRevisionNumber;
40 import git4idea.GitVcs;
41 import git4idea.PlatformFacade;
42 import git4idea.actions.GitShowAllSubmittedFilesAction;
43 import git4idea.commands.*;
44 import git4idea.config.GitVersionSpecialty;
45 import git4idea.i18n.GitBundle;
46 import git4idea.merge.GitConflictResolver;
47 import git4idea.stash.GitStashUtils;
48 import git4idea.util.GitUIUtil;
49 import git4idea.validators.GitBranchNameValidator;
50 import org.jetbrains.annotations.NotNull;
53 import javax.swing.event.DocumentEvent;
54 import javax.swing.event.HyperlinkEvent;
55 import javax.swing.event.ListSelectionEvent;
56 import javax.swing.event.ListSelectionListener;
57 import java.awt.event.ActionEvent;
58 import java.awt.event.ActionListener;
60 import java.util.concurrent.atomic.AtomicBoolean;
65 public class GitUnstashDialog extends DialogWrapper {
69 private JComboBox myGitRootComboBox;
71 * The current branch label
73 private JLabel myCurrentBranch;
75 * The view stash button
77 private JButton myViewButton;
79 * The drop stash button
81 private JButton myDropButton;
83 * The clear stashes button
85 private JButton myClearButton;
87 * The pop stash checkbox
89 private JCheckBox myPopStashCheckBox;
91 * The branch text field
93 private JTextField myBranchTextField;
95 * The root panel of the dialog
97 private JPanel myPanel;
101 private JList myStashList;
103 * If this checkbox is selected, the index is reinstated as well as working tree
105 private JCheckBox myReinstateIndexCheckBox;
107 * Set of branches for the current root
109 private final HashSet<String> myBranches = new HashSet<String>();
114 private final Project myProject;
115 private GitVcs myVcs;
116 private static final Logger LOG = Logger.getInstance(GitUnstashDialog.class);
121 * @param project the project
122 * @param roots the list of the roots
123 * @param defaultRoot the default root to select
125 public GitUnstashDialog(final Project project, final List<VirtualFile> roots, final VirtualFile defaultRoot) {
126 super(project, true);
129 myVcs = GitVcs.getInstance(project);
130 setTitle(GitBundle.getString("unstash.title"));
131 setOKButtonText(GitBundle.getString("unstash.button.apply"));
132 GitUIUtil.setupRootChooser(project, roots, defaultRoot, myGitRootComboBox, myCurrentBranch);
133 myStashList.setModel(new DefaultListModel());
135 myGitRootComboBox.addActionListener(new ActionListener() {
136 public void actionPerformed(final ActionEvent e) {
141 myStashList.addListSelectionListener(new ListSelectionListener() {
142 public void valueChanged(final ListSelectionEvent e) {
146 myBranchTextField.getDocument().addDocumentListener(new DocumentAdapter() {
147 protected void textChanged(final DocumentEvent e) {
151 myPopStashCheckBox.addActionListener(new ActionListener() {
152 public void actionPerformed(ActionEvent e) {
156 myClearButton.addActionListener(new ActionListener() {
157 public void actionPerformed(final ActionEvent e) {
158 if (Messages.YES == Messages.showYesNoDialog(GitUnstashDialog.this.getContentPane(),
159 GitBundle.message("git.unstash.clear.confirmation.message"),
160 GitBundle.message("git.unstash.clear.confirmation.title"), Messages.getWarningIcon())) {
161 GitLineHandler h = new GitLineHandler(myProject, getGitRoot(), GitCommand.STASH);
163 h.addParameters("clear");
164 GitHandlerUtil.doSynchronously(h, GitBundle.getString("unstash.clearing.stashes"), h.printableCommandLine());
170 myDropButton.addActionListener(new ActionListener() {
171 public void actionPerformed(final ActionEvent e) {
172 final StashInfo stash = getSelectedStash();
173 if (Messages.YES == Messages.showYesNoDialog(GitUnstashDialog.this.getContentPane(),
174 GitBundle.message("git.unstash.drop.confirmation.message", stash.getStash(), stash.getMessage()),
175 GitBundle.message("git.unstash.drop.confirmation.title", stash.getStash()), Messages.getQuestionIcon())) {
176 final ModalityState current = ModalityState.current();
177 ProgressManager.getInstance().run(new Task.Modal(myProject, "Removing stash " + stash.getStash(), false) {
179 public void run(@NotNull ProgressIndicator indicator) {
180 final GitSimpleHandler h = dropHandler(stash.getStash());
185 catch (final VcsException ex) {
186 ApplicationManager.getApplication().invokeLater(new Runnable() {
189 GitUIUtil.showOperationError(myProject, ex, h.printableCommandLine());
200 private GitSimpleHandler dropHandler(String stash) {
201 GitSimpleHandler h = new GitSimpleHandler(myProject, getGitRoot(), GitCommand.STASH);
203 h.addParameters("drop");
204 addStashParameter(h, stash);
208 myViewButton.addActionListener(new ActionListener() {
209 public void actionPerformed(final ActionEvent e) {
210 final VirtualFile root = getGitRoot();
211 String resolvedStash;
212 String selectedStash = getSelectedStash().getStash();
214 GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.REV_LIST);
217 h.addParameters("--timestamp", "--max-count=1");
218 addStashParameter(h, selectedStash);
220 final String output = h.run();
221 resolvedStash = GitRevisionNumber.parseRevlistOutputAsRevisionNumber(h, output).asString();
223 catch (VcsException ex) {
224 GitUIUtil.showOperationError(myProject, ex, "resolving revision");
227 GitShowAllSubmittedFilesAction.showSubmittedFiles(myProject, resolvedStash, root, true, false);
235 * Adds {@code stash@{x}} parameter to the handler, quotes it if needed.
237 private void addStashParameter(@NotNull GitHandler handler, @NotNull String stash) {
238 if (GitVersionSpecialty.NEEDS_QUOTES_IN_STASH_NAME.existsIn(myVcs.getVersion())) {
239 handler.addParameters("\"" + stash + "\"");
240 handler.dontEscapeQuotes();
243 handler.addParameters(stash);
248 * Update state dialog depending on the current state of the fields
250 private void updateDialogState() {
251 String branch = myBranchTextField.getText();
252 if (branch.length() != 0) {
253 setOKButtonText(GitBundle.getString("unstash.button.branch"));
254 myPopStashCheckBox.setEnabled(false);
255 myPopStashCheckBox.setSelected(true);
256 myReinstateIndexCheckBox.setEnabled(false);
257 myReinstateIndexCheckBox.setSelected(true);
258 if (!GitBranchNameValidator.INSTANCE.checkInput(branch)) {
259 setErrorText(GitBundle.getString("unstash.error.invalid.branch.name"));
260 setOKActionEnabled(false);
263 if (myBranches.contains(branch)) {
264 setErrorText(GitBundle.getString("unstash.error.branch.exists"));
265 setOKActionEnabled(false);
270 if (!myPopStashCheckBox.isEnabled()) {
271 myPopStashCheckBox.setSelected(false);
273 myPopStashCheckBox.setEnabled(true);
275 myPopStashCheckBox.isSelected() ? GitBundle.getString("unstash.button.pop") : GitBundle.getString("unstash.button.apply"));
276 if (!myReinstateIndexCheckBox.isEnabled()) {
277 myReinstateIndexCheckBox.setSelected(false);
279 myReinstateIndexCheckBox.setEnabled(true);
281 if (myStashList.getModel().getSize() == 0) {
282 myViewButton.setEnabled(false);
283 myDropButton.setEnabled(false);
284 myClearButton.setEnabled(false);
286 setOKActionEnabled(false);
290 myClearButton.setEnabled(true);
292 if (myStashList.getSelectedIndex() == -1) {
293 myViewButton.setEnabled(false);
294 myDropButton.setEnabled(false);
296 setOKActionEnabled(false);
300 myViewButton.setEnabled(true);
301 myDropButton.setEnabled(true);
304 setOKActionEnabled(true);
310 private void refreshStashList() {
311 final DefaultListModel listModel = (DefaultListModel)myStashList.getModel();
313 GitStashUtils.loadStashStack(myProject, getGitRoot(), new Consumer<StashInfo>() {
315 public void consume(StashInfo stashInfo) {
316 listModel.addElement(stashInfo);
321 GitBranch.listAsStrings(myProject, getGitRoot(), false, true, myBranches, null);
323 catch (VcsException e) {
326 myStashList.setSelectedIndex(0);
330 * @return the selected git root
332 private VirtualFile getGitRoot() {
333 return (VirtualFile)myGitRootComboBox.getSelectedItem();
337 * @return unstash handler
339 private GitLineHandler handler() {
340 GitLineHandler h = new GitLineHandler(myProject, getGitRoot(), GitCommand.STASH);
342 String branch = myBranchTextField.getText();
343 if (branch.length() == 0) {
344 h.addParameters(myPopStashCheckBox.isSelected() ? "pop" : "apply");
345 if (myReinstateIndexCheckBox.isSelected()) {
346 h.addParameters("--index");
350 h.addParameters("branch", branch);
352 String selectedStash = getSelectedStash().getStash();
353 addStashParameter(h, selectedStash);
358 * @return selected stash
359 * @throws NullPointerException if no stash is selected
361 private StashInfo getSelectedStash() {
362 return (StashInfo)myStashList.getSelectedValue();
368 protected JComponent createCenterPanel() {
376 protected String getDimensionServiceKey() {
377 return getClass().getName();
384 protected String getHelpId() {
385 return "reference.VersionControl.Git.Unstash";
389 public JComponent getPreferredFocusedComponent() {
394 protected void doOKAction() {
395 VirtualFile root = getGitRoot();
396 GitLineHandler h = handler();
397 final AtomicBoolean conflict = new AtomicBoolean();
399 h.addLineListener(new GitLineHandlerAdapter() {
400 public void onLineAvailable(String line, Key outputType) {
401 if (line.contains("Merge conflict")) {
406 int rc = GitHandlerUtil.doSynchronously(h, GitBundle.getString("unstash.unstashing"), h.printableCommandLine(), false);
407 root.refresh(true, true);
409 if (conflict.get()) {
410 boolean conflictsResolved = new UnstashConflictResolver(myProject, root, getSelectedStash()).merge();
411 LOG.info("loadRoot " + root + ", conflictsResolved: " + conflictsResolved);
412 } else if (rc != 0) {
413 GitUIUtil.showOperationErrors(myProject, h.errors(), h.printableCommandLine());
418 public static void showUnstashDialog(Project project, List<VirtualFile> gitRoots, VirtualFile defaultRoot) {
419 new GitUnstashDialog(project, gitRoots, defaultRoot).show();
420 // d is not modal=> everything else in doOKAction.
423 private static class UnstashConflictResolver extends GitConflictResolver {
425 private final VirtualFile myRoot;
426 private final StashInfo myStashInfo;
428 public UnstashConflictResolver(Project project, VirtualFile root, StashInfo stashInfo) {
429 super(project, ServiceManager.getService(Git.class), ServiceManager.getService(PlatformFacade.class),
430 Collections.singleton(root), makeParams(stashInfo));
432 myStashInfo = stashInfo;
435 private static Params makeParams(StashInfo stashInfo) {
436 Params params = new Params();
437 params.setErrorNotificationTitle("Unstashed with conflicts");
438 params.setMergeDialogCustomizer(new UnstashMergeDialogCustomizer(stashInfo));
443 protected void notifyUnresolvedRemain() {
444 GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification("Conflicts were not resolved during unstash",
445 "Unstash is not complete, you have unresolved merges in your working tree<br/>" +
446 "<a href='resolve'>Resolve</a> conflicts.",
447 NotificationType.WARNING, new NotificationListener() {
449 public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
450 if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
451 if (event.getDescription().equals("resolve")) {
452 new UnstashConflictResolver(myProject, myRoot, myStashInfo).mergeNoProceed();
456 }).notify(myProject);
460 private static class UnstashMergeDialogCustomizer extends MergeDialogCustomizer {
462 private final StashInfo myStashInfo;
464 public UnstashMergeDialogCustomizer(StashInfo stashInfo) {
465 myStashInfo = stashInfo;
469 public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
470 return "<html>Conflicts during unstashing <code>" + myStashInfo.getStash() + "\"" + myStashInfo.getMessage() + "\"</code></html>";
474 public String getLeftPanelTitle(VirtualFile file) {
475 return "Local changes";
479 public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
480 return "Changes from stash";