Merge branch 'nz/shared_shelf'
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / shelf / ShelveChangesManager.java
index 577e8f488169b2fed7b1deae428dcfe99166d818..da65f1d9741b0e5c7e9f0e71ad8dfced5795ae5a 100644 (file)
  */
 package com.intellij.openapi.vcs.changes.shelf;
 
  */
 package com.intellij.openapi.vcs.changes.shelf;
 
-import com.intellij.ide.impl.ProjectUtil;
 import com.intellij.lifecycle.PeriodicalTasksCloser;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.application.impl.LaterInvocator;
 import com.intellij.openapi.components.AbstractProjectComponent;
 import com.intellij.lifecycle.PeriodicalTasksCloser;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.application.impl.LaterInvocator;
 import com.intellij.openapi.components.AbstractProjectComponent;
+import com.intellij.openapi.components.PathMacroManager;
+import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.impl.patch.*;
 import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase;
 import com.intellij.openapi.diff.impl.patch.formove.CustomBinaryPatchApplier;
 import com.intellij.openapi.diff.impl.patch.formove.PatchApplier;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.impl.patch.*;
 import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase;
 import com.intellij.openapi.diff.impl.patch.formove.CustomBinaryPatchApplier;
 import com.intellij.openapi.diff.impl.patch.formove.PatchApplier;
+import com.intellij.openapi.options.BaseSchemeProcessor;
+import com.intellij.openapi.options.SchemesManager;
+import com.intellij.openapi.options.SchemesManagerFactory;
+import com.intellij.openapi.progress.AsynchronousExecution;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.*;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.*;
 import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.io.FileUtilRt;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.changes.*;
 import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.changes.*;
 import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor;
@@ -52,13 +57,16 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.Consumer;
 import com.intellij.util.PathUtil;
 import com.intellij.util.SmartList;
 import com.intellij.util.Consumer;
 import com.intellij.util.PathUtil;
 import com.intellij.util.SmartList;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.messages.MessageBus;
 import com.intellij.util.messages.Topic;
 import com.intellij.util.text.CharArrayCharSequence;
 import com.intellij.util.messages.MessageBus;
 import com.intellij.util.messages.Topic;
 import com.intellij.util.text.CharArrayCharSequence;
+import com.intellij.util.text.UniqueNameGenerator;
 import com.intellij.util.ui.UIUtil;
 import com.intellij.vcsUtil.FilesProgress;
 import org.jdom.Element;
 import org.jetbrains.annotations.CalledInAny;
 import com.intellij.util.ui.UIUtil;
 import com.intellij.vcsUtil.FilesProgress;
 import org.jdom.Element;
 import org.jetbrains.annotations.CalledInAny;
+import org.jdom.Parent;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -68,39 +76,59 @@ import javax.swing.event.ChangeListener;
 import java.io.*;
 import java.util.*;
 
 import java.io.*;
 import java.util.*;
 
-import static com.intellij.openapi.vcs.changes.shelf.CompoundShelfFileProcessor.SHELF_DIR_NAME;
-
 public class ShelveChangesManager extends AbstractProjectComponent implements JDOMExternalizable {
   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager");
 public class ShelveChangesManager extends AbstractProjectComponent implements JDOMExternalizable {
   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager");
+  @NonNls private static final String ELEMENT_CHANGELIST = "changelist";
+  @NonNls private static final String ELEMENT_RECYCLED_CHANGELIST = "recycled_changelist";
+  @NonNls private static final String DEFAULT_PATCH_NAME = "shelved";
+
+  @NotNull private final TrackingPathMacroSubstitutor myPathMacroSubstitutor;
+  @NotNull private final SchemesManager<ShelvedChangeList, ShelvedChangeList> mySchemeManager;
 
   public static ShelveChangesManager getInstance(Project project) {
     return PeriodicalTasksCloser.getInstance().safeGetComponent(project, ShelveChangesManager.class);
   }
 
 
   public static ShelveChangesManager getInstance(Project project) {
     return PeriodicalTasksCloser.getInstance().safeGetComponent(project, ShelveChangesManager.class);
   }
 
+  private static final String SHELVE_MANAGER_DIR_PATH = "shelf";
   private final MessageBus myBus;
   private final MessageBus myBus;
-  private final List<ShelvedChangeList> myShelvedChangeLists = new ArrayList<ShelvedChangeList>();
-  private final List<ShelvedChangeList> myRecycledShelvedChangeLists = new ArrayList<ShelvedChangeList>();
 
   @NonNls private static final String ATTRIBUTE_SHOW_RECYCLED = "show_recycled";
 
   @NonNls private static final String ATTRIBUTE_SHOW_RECYCLED = "show_recycled";
-  private final CompoundShelfFileProcessor myFileProcessor;
+  @NotNull private final CompoundShelfFileProcessor myFileProcessor;
 
   public static final Topic<ChangeListener> SHELF_TOPIC = new Topic<ChangeListener>("shelf updates", ChangeListener.class);
   private boolean myShowRecycled;
 
   public ShelveChangesManager(final Project project, final MessageBus bus) {
     super(project);
 
   public static final Topic<ChangeListener> SHELF_TOPIC = new Topic<ChangeListener>("shelf updates", ChangeListener.class);
   private boolean myShowRecycled;
 
   public ShelveChangesManager(final Project project, final MessageBus bus) {
     super(project);
+    myPathMacroSubstitutor = PathMacroManager.getInstance(myProject).createTrackingSubstitutor();
     myBus = bus;
     myBus = bus;
-    if (project.isDefault()) {
-      myFileProcessor = new CompoundShelfFileProcessor(null, PathManager.getConfigPath() + File.separator + SHELF_DIR_NAME);
-    }
-    else if (ProjectUtil.isDirectoryBased(project)) {
-      VirtualFile dir = project.getBaseDir();
-      String shelfBaseDirPath = dir == null ? "" : dir.getPath() + File.separator + Project.DIRECTORY_STORE_FOLDER;
-      myFileProcessor = new CompoundShelfFileProcessor(shelfBaseDirPath);
-    }
-    else {
-      myFileProcessor = new CompoundShelfFileProcessor();
-    }
+    mySchemeManager =
+      SchemesManagerFactory.getInstance(project).create(SHELVE_MANAGER_DIR_PATH, new BaseSchemeProcessor<ShelvedChangeList>() {
+        @Nullable
+        @Override
+        public ShelvedChangeList readScheme(@NotNull Element element) throws InvalidDataException {
+          return readOneShelvedChangeList(element);
+        }
+
+        @Override
+        public Parent writeScheme(@NotNull ShelvedChangeList scheme) throws WriteExternalException {
+          Element child = new Element(ELEMENT_CHANGELIST);
+          scheme.writeExternal(child);
+          myPathMacroSubstitutor.collapsePaths(child);
+          return child;
+        }
+      });
+    myFileProcessor = new CompoundShelfFileProcessor(mySchemeManager.getRootDirectory());
+    ChangeListManager.getInstance(project).addDirectoryToIgnoreImplicitly(mySchemeManager.getRootDirectory().getAbsolutePath());
+    mySchemeManager.loadSchemes();
+  }
+
+  @NotNull
+  private ShelvedChangeList readOneShelvedChangeList(@NotNull Element element) throws InvalidDataException {
+    ShelvedChangeList data = new ShelvedChangeList();
+    myPathMacroSubstitutor.expandPaths(element);
+    data.readExternal(element);
+    return data;
   }
 
   @Override
   }
 
   @Override
@@ -112,8 +140,6 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
 
   @Override
   public void readExternal(Element element) throws InvalidDataException {
 
   @Override
   public void readExternal(Element element) throws InvalidDataException {
-    //noinspection unchecked
-
     final String showRecycled = element.getAttributeValue(ATTRIBUTE_SHOW_RECYCLED);
     if (showRecycled != null) {
       myShowRecycled = Boolean.parseBoolean(showRecycled);
     final String showRecycled = element.getAttributeValue(ATTRIBUTE_SHOW_RECYCLED);
     if (showRecycled != null) {
       myShowRecycled = Boolean.parseBoolean(showRecycled);
@@ -121,25 +147,91 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     else {
       myShowRecycled = true;
     }
     else {
       myShowRecycled = true;
     }
+    migrateOldShelfInfo(element, true);
+    migrateOldShelfInfo(element, false);
+  }
 
 
-    readExternal(element, myShelvedChangeLists, myRecycledShelvedChangeLists);
+  //load old shelf information from workspace.xml without moving .patch and binary files into new directory
+  private void migrateOldShelfInfo(@NotNull Element element, boolean recycled) throws InvalidDataException {
+    for (Element changeSetElement : element.getChildren(recycled ? ELEMENT_RECYCLED_CHANGELIST : ELEMENT_CHANGELIST)) {
+      ShelvedChangeList list = readOneShelvedChangeList(changeSetElement);
+      File uniqueDir = generateUniqueSchemePatchDir(list.DESCRIPTION, false);
+      list.setName(uniqueDir.getName());
+      list.setRecycled(recycled);
+      mySchemeManager.addNewScheme(list, false);
+    }
   }
 
   }
 
-  public static void readExternal(final Element element, final List<ShelvedChangeList> changes, final List<ShelvedChangeList> recycled)
-    throws InvalidDataException {
-    changes.addAll(ShelvedChangeList.readChanges(element, false, true));
+  /**
+   * Should be called only once: when Settings Repository plugin runs first time
+   *
+   * @return collection of non-migrated or not deleted files to show a error somewhere outside
+   */
+  @NotNull
+  public Collection<String> checkAndMigrateOldPatchResourcesToNewSchemeStorage() {
+    Collection<String> nonMigratedPaths = ContainerUtil.newArrayList();
+    for (ShelvedChangeList list : mySchemeManager.getAllSchemes()) {
+      File patchDir = new File(myFileProcessor.getBaseDir(), list.getName());
+      nonMigratedPaths.addAll(migrateIfNeededToSchemeDir(list, patchDir));
+    }
+    return nonMigratedPaths;
+  }
+
+  @NotNull
+  private static Collection<String> migrateIfNeededToSchemeDir(@NotNull ShelvedChangeList list, @NotNull File targetDirectory) {
+    // it should be enough for migration to check if resource directory exists. If any bugs appeared add isAncestor checks for each path
+    if (targetDirectory.exists() || !targetDirectory.mkdirs()) return ContainerUtil.emptyList();
+    Collection<String> nonMigratedPaths = ContainerUtil.newArrayList();
+    //try to move .patch file
+    File patchFile = new File(list.PATH);
+    if (patchFile.exists()) {
+      File newPatchFile = getPatchFileInConfigDir(targetDirectory);
+      try {
+        FileUtil.copy(patchFile, newPatchFile);
+        list.PATH = newPatchFile.getPath();
+        FileUtil.delete(patchFile);
+      }
+      catch (IOException e) {
+        nonMigratedPaths.add(list.PATH);
+      }
+    }
 
 
-    recycled.addAll(ShelvedChangeList.readChanges(element, true, true));
+    for (ShelvedBinaryFile file : list.getBinaryFiles()) {
+      if (file.SHELVED_PATH != null) {
+        File shelvedFile = new File(file.SHELVED_PATH);
+        if (!StringUtil.isEmptyOrSpaces(file.AFTER_PATH) && shelvedFile.exists()) {
+          File newShelvedFile = new File(targetDirectory, PathUtil.getFileName(file.AFTER_PATH));
+          try {
+            FileUtil.copy(shelvedFile, newShelvedFile);
+            file.SHELVED_PATH = newShelvedFile.getPath();
+            FileUtil.delete(shelvedFile);
+          }
+          catch (IOException e) {
+            nonMigratedPaths.add(shelvedFile.getPath());
+          }
+        }
+      }
+    }
+    return nonMigratedPaths;
   }
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
     element.setAttribute(ATTRIBUTE_SHOW_RECYCLED, Boolean.toString(myShowRecycled));
   }
 
   @Override
   public void writeExternal(Element element) throws WriteExternalException {
     element.setAttribute(ATTRIBUTE_SHOW_RECYCLED, Boolean.toString(myShowRecycled));
-    ShelvedChangeList.writeChanges(myShelvedChangeLists, myRecycledShelvedChangeLists, element);
   }
 
   public List<ShelvedChangeList> getShelvedChangeLists() {
   }
 
   public List<ShelvedChangeList> getShelvedChangeLists() {
-    return Collections.unmodifiableList(myShelvedChangeLists);
+    return getRecycled(false);
+  }
+
+  @NotNull
+  private List<ShelvedChangeList> getRecycled(final boolean recycled) {
+    return ContainerUtil.newUnmodifiableList(ContainerUtil.filter(mySchemeManager.getAllSchemes(), new Condition<ShelvedChangeList>() {
+      @Override
+      public boolean value(ShelvedChangeList list) {
+        return recycled ? list.isRecycled() : !list.isRecycled();
+      }
+    }));
   }
 
   public ShelvedChangeList shelveChanges(final Collection<Change> changes, final String commitMessage, final boolean rollback)
   }
 
   public ShelvedChangeList shelveChanges(final Collection<Change> changes, final String commitMessage, final boolean rollback)
@@ -148,6 +240,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     if (progressIndicator != null) {
       progressIndicator.setText(VcsBundle.message("shelve.changes.progress.title"));
     }
     if (progressIndicator != null) {
       progressIndicator.setText(VcsBundle.message("shelve.changes.progress.title"));
     }
+    File schemePatchDir = generateUniqueSchemePatchDir(commitMessage, true);
     final List<Change> textChanges = new ArrayList<Change>();
     final List<ShelvedBinaryFile> binaryFiles = new ArrayList<ShelvedBinaryFile>();
     for (Change change : changes) {
     final List<Change> textChanges = new ArrayList<Change>();
     final List<ShelvedBinaryFile> binaryFiles = new ArrayList<ShelvedBinaryFile>();
     for (Change change : changes) {
@@ -155,7 +248,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
         continue;
       }
       if (change.getBeforeRevision() instanceof BinaryContentRevision || change.getAfterRevision() instanceof BinaryContentRevision) {
         continue;
       }
       if (change.getBeforeRevision() instanceof BinaryContentRevision || change.getAfterRevision() instanceof BinaryContentRevision) {
-        binaryFiles.add(shelveBinaryFile(change));
+        binaryFiles.add(shelveBinaryFile(schemePatchDir, change));
       }
       else {
         textChanges.add(change);
       }
       else {
         textChanges.add(change);
@@ -164,7 +257,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
 
     final ShelvedChangeList changeList;
     try {
 
     final ShelvedChangeList changeList;
     try {
-      File patchPath = getPatchPath(commitMessage);
+      File patchPath = getPatchFileInConfigDir(schemePatchDir);
       ProgressManager.checkCanceled();
       final List<FilePatch> patches =
         IdeaTextPatchBuilder.buildPatch(myProject, textChanges, myProject.getBaseDir().getPresentableUrl(), false);
       ProgressManager.checkCanceled();
       final List<FilePatch> patches =
         IdeaTextPatchBuilder.buildPatch(myProject, textChanges, myProject.getBaseDir().getPresentableUrl(), false);
@@ -182,8 +275,9 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
         patchPath, commitContext);
 
       changeList = new ShelvedChangeList(patchPath.toString(), commitMessage.replace('\n', ' '), binaryFiles);
         patchPath, commitContext);
 
       changeList = new ShelvedChangeList(patchPath.toString(), commitMessage.replace('\n', ' '), binaryFiles);
+      changeList.setName(schemePatchDir.getName());
       ProgressManager.checkCanceled();
       ProgressManager.checkCanceled();
-      myShelvedChangeLists.add(changeList);
+      mySchemeManager.addNewScheme(changeList, false);
 
       if (rollback) {
         final String operationName = UIUtil.removeMnemonic(RollbackChangesDialog.operationNameByChanges(myProject, changes));
 
       if (rollback) {
         final String operationName = UIUtil.removeMnemonic(RollbackChangesDialog.operationNameByChanges(myProject, changes));
@@ -202,6 +296,11 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     return changeList;
   }
 
     return changeList;
   }
 
+  @NotNull
+  private static File getPatchFileInConfigDir(@NotNull File schemePatchDir) {
+    return new File(schemePatchDir, DEFAULT_PATCH_NAME + "." + VcsConfiguration.PATCH);
+  }
+
   private void baseRevisionsOfDvcsIntoContext(List<Change> textChanges, CommitContext commitContext) {
     ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
     if (vcsManager.dvcsUsedInProject() && VcsConfiguration.getInstance(myProject).INCLUDE_TEXT_INTO_SHELF) {
   private void baseRevisionsOfDvcsIntoContext(List<Change> textChanges, CommitContext commitContext) {
     ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
     if (vcsManager.dvcsUsedInProject() && VcsConfiguration.getInstance(myProject).INCLUDE_TEXT_INTO_SHELF) {
@@ -224,7 +323,8 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
   public ShelvedChangeList importFilePatches(final String fileName, final List<FilePatch> patches, final PatchEP[] patchTransitExtensions)
     throws IOException {
     try {
   public ShelvedChangeList importFilePatches(final String fileName, final List<FilePatch> patches, final PatchEP[] patchTransitExtensions)
     throws IOException {
     try {
-      final File patchPath = getPatchPath(fileName);
+      File schemePatchDir = generateUniqueSchemePatchDir(fileName, true);
+      File patchPath = getPatchFileInConfigDir(schemePatchDir);
       myFileProcessor.savePathFile(
         new CompoundShelfFileProcessor.ContentProvider() {
           @Override
       myFileProcessor.savePathFile(
         new CompoundShelfFileProcessor.ContentProvider() {
           @Override
@@ -236,7 +336,8 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
 
       final ShelvedChangeList changeList =
         new ShelvedChangeList(patchPath.toString(), fileName.replace('\n', ' '), new SmartList<ShelvedBinaryFile>());
 
       final ShelvedChangeList changeList =
         new ShelvedChangeList(patchPath.toString(), fileName.replace('\n', ' '), new SmartList<ShelvedBinaryFile>());
-      myShelvedChangeLists.add(changeList);
+      changeList.setName(schemePatchDir.getName());
+      mySchemeManager.addNewScheme(changeList, false);
       return changeList;
     }
     finally {
       return changeList;
     }
     finally {
@@ -271,15 +372,17 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
       for (VirtualFile file : files) {
         filesProgress.updateIndicator(file);
         final String description = file.getNameWithoutExtension().replace('_', ' ');
       for (VirtualFile file : files) {
         filesProgress.updateIndicator(file);
         final String description = file.getNameWithoutExtension().replace('_', ' ');
-        final File patchPath = getPatchPath(description);
+        File schemeNameDir = generateUniqueSchemePatchDir(description, true);
+        final File patchPath = getPatchFileInConfigDir(schemeNameDir);
         final ShelvedChangeList list = new ShelvedChangeList(patchPath.getPath(), description, new SmartList<ShelvedBinaryFile>(),
                                                              file.getTimeStamp());
         final ShelvedChangeList list = new ShelvedChangeList(patchPath.getPath(), description, new SmartList<ShelvedBinaryFile>(),
                                                              file.getTimeStamp());
+        list.setName(schemeNameDir.getName());
         try {
           final List<TextFilePatch> patchesList = loadPatches(myProject, file.getPath(), new CommitContext());
           if (!patchesList.isEmpty()) {
             FileUtil.copy(new File(file.getPath()), patchPath);
             // add only if ok to read patch
         try {
           final List<TextFilePatch> patchesList = loadPatches(myProject, file.getPath(), new CommitContext());
           if (!patchesList.isEmpty()) {
             FileUtil.copy(new File(file.getPath()), patchPath);
             // add only if ok to read patch
-            myShelvedChangeLists.add(list);
+            mySchemeManager.addNewScheme(list, false);
             result.add(list);
           }
         }
             result.add(list);
           }
         }
@@ -297,19 +400,15 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     return result;
   }
 
     return result;
   }
 
-  private ShelvedBinaryFile shelveBinaryFile(final Change change) throws IOException {
+  private ShelvedBinaryFile shelveBinaryFile(@NotNull File schemePatchDir, final Change change) throws IOException {
     final ContentRevision beforeRevision = change.getBeforeRevision();
     final ContentRevision afterRevision = change.getAfterRevision();
     File beforeFile = beforeRevision == null ? null : beforeRevision.getFile().getIOFile();
     File afterFile = afterRevision == null ? null : afterRevision.getFile().getIOFile();
     String shelvedPath = null;
     if (afterFile != null) {
     final ContentRevision beforeRevision = change.getBeforeRevision();
     final ContentRevision afterRevision = change.getAfterRevision();
     File beforeFile = beforeRevision == null ? null : beforeRevision.getFile().getIOFile();
     File afterFile = afterRevision == null ? null : afterRevision.getFile().getIOFile();
     String shelvedPath = null;
     if (afterFile != null) {
-      String shelvedName = FileUtil.getNameWithoutExtension(afterFile.getName());
-      String shelvedExt = FileUtilRt.getExtension(afterFile.getName());
-      File shelvedFile = FileUtil.findSequentNonexistentFile(myFileProcessor.getBaseIODir(), shelvedName, shelvedExt);
-
-      myFileProcessor.saveFile(afterRevision.getFile().getIOFile(), shelvedFile);
-
+      File shelvedFile = new File(schemePatchDir, afterFile.getName());
+      FileUtil.copy(afterRevision.getFile().getIOFile(), shelvedFile);
       shelvedPath = shelvedFile.getPath();
     }
     String beforePath = ChangesUtil.getProjectRelativePath(myProject, beforeFile);
       shelvedPath = shelvedFile.getPath();
     }
     String beforePath = ChangesUtil.getProjectRelativePath(myProject, beforeFile);
@@ -323,25 +422,22 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     }
   }
 
     }
   }
 
-  private File getPatchPath(@NonNls final String commitMessage) {
-    File file = myFileProcessor.getBaseIODir();
-    if (!file.exists()) {
+  @NotNull
+  private File generateUniqueSchemePatchDir(@NotNull final String defaultName, boolean createResourceDirectory) {
+    String uniqueName = UniqueNameGenerator
+      .generateUniqueName(shortenAndSanitize(defaultName), mySchemeManager.getAllSchemeNames());
+    File dir = new File(myFileProcessor.getBaseDir(), uniqueName);
+    if (createResourceDirectory && !dir.exists()) {
       //noinspection ResultOfMethodCallIgnored
       //noinspection ResultOfMethodCallIgnored
-      file.mkdirs();
+      dir.mkdirs();
     }
     }
-
-    return suggestPatchName(myProject, commitMessage.length() > PatchNameChecker.MAX ? commitMessage.substring(0, PatchNameChecker.MAX) :
-                                       commitMessage, file, VcsConfiguration.PATCH);
+    return dir;
   }
 
   }
 
-  public static File suggestPatchName(Project project, final String commitMessage, final File file, String extension) {
-    @NonNls String defaultPath = PathUtil.suggestFileName(commitMessage);
-    if (defaultPath.isEmpty()) {
-      defaultPath = "unnamed";
-    }
-    if (defaultPath.length() > PatchNameChecker.MAX - 10) {
-      defaultPath = defaultPath.substring(0, PatchNameChecker.MAX - 10);
-    }
+  @NotNull
+  // for create patch only; todo move or unify with unique directory creation
+  public static File suggestPatchName(Project project, @NotNull final String commitMessage, final File file, String extension) {
+    @NonNls String defaultPath = shortenAndSanitize(commitMessage);
     while (true) {
       final File nonexistentFile = FileUtil.findSequentNonexistentFile(file, defaultPath,
                                                                        extension == null
     while (true) {
       final File nonexistentFile = FileUtil.findSequentNonexistentFile(file, defaultPath,
                                                                        extension == null
@@ -355,6 +451,18 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     }
   }
 
     }
   }
 
+  @NotNull
+  private static String shortenAndSanitize(@NotNull String commitMessage) {
+    @NonNls String defaultPath = FileUtil.sanitizeFileName(commitMessage);
+    if (defaultPath.isEmpty()) {
+      defaultPath = "unnamed";
+    }
+    if (defaultPath.length() > PatchNameChecker.MAX - 10) {
+      defaultPath = defaultPath.substring(0, PatchNameChecker.MAX - 10);
+    }
+    return defaultPath;
+  }
+
   @CalledInAny
   public void unshelveChangeList(final ShelvedChangeList changeList,
                                  @Nullable final List<ShelvedChange> changes,
   @CalledInAny
   public void unshelveChangeList(final ShelvedChangeList changeList,
                                  @Nullable final List<ShelvedChange> changes,
@@ -545,7 +653,8 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
 
   void saveRemainingPatches(final ShelvedChangeList changeList, final List<FilePatch> remainingPatches,
                             final List<ShelvedBinaryFile> remainingBinaries, CommitContext commitContext) {
 
   void saveRemainingPatches(final ShelvedChangeList changeList, final List<FilePatch> remainingPatches,
                             final List<ShelvedBinaryFile> remainingBinaries, CommitContext commitContext) {
-    final File newPath = getPatchPath(changeList.DESCRIPTION);
+    final File newPatchDir = generateUniqueSchemePatchDir(changeList.DESCRIPTION, true);
+    final File newPath = getPatchFileInConfigDir(newPatchDir);
     try {
       FileUtil.copy(new File(changeList.PATH), newPath);
     }
     try {
       FileUtil.copy(new File(changeList.PATH), newPath);
     }
@@ -555,6 +664,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     }
     final ShelvedChangeList listCopy = new ShelvedChangeList(newPath.getAbsolutePath(), changeList.DESCRIPTION,
                                                              new ArrayList<ShelvedBinaryFile>(changeList.getBinaryFiles()));
     }
     final ShelvedChangeList listCopy = new ShelvedChangeList(newPath.getAbsolutePath(), changeList.DESCRIPTION,
                                                              new ArrayList<ShelvedBinaryFile>(changeList.getBinaryFiles()));
+    listCopy.setName(newPatchDir.getName());
     listCopy.DATE = changeList.DATE == null ? null : new Date(changeList.DATE.getTime());
 
     writePatchesToFile(myProject, changeList.PATH, remainingPatches, commitContext);
     listCopy.DATE = changeList.DATE == null ? null : new Date(changeList.DATE.getTime());
 
     writePatchesToFile(myProject, changeList.PATH, remainingPatches, commitContext);
@@ -562,29 +672,32 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
     changeList.getBinaryFiles().retainAll(remainingBinaries);
     changeList.clearLoadedChanges();
     recycleChangeList(listCopy, changeList);
     changeList.getBinaryFiles().retainAll(remainingBinaries);
     changeList.clearLoadedChanges();
     recycleChangeList(listCopy, changeList);
+    // all newly create ShelvedChangeList have to be added to SchemesManger as new scheme
+    mySchemeManager.addNewScheme(listCopy, false);
     notifyStateChanged();
   }
 
     notifyStateChanged();
   }
 
-  public void restoreList(final ShelvedChangeList changeList) {
-    myShelvedChangeLists.add(changeList);
-    myRecycledShelvedChangeLists.remove(changeList);
-    changeList.setRecycled(false);
+  public void restoreList(@NotNull final ShelvedChangeList changeList) {
+    ShelvedChangeList list = mySchemeManager.findSchemeByName(changeList.getName());
+    if (list != null) {
+      list.setRecycled(false);
+    }
     notifyStateChanged();
   }
 
   public List<ShelvedChangeList> getRecycledShelvedChangeLists() {
     notifyStateChanged();
   }
 
   public List<ShelvedChangeList> getRecycledShelvedChangeLists() {
-    return myRecycledShelvedChangeLists;
+    return getRecycled(true);
   }
 
   public void clearRecycled() {
   }
 
   public void clearRecycled() {
-    for (ShelvedChangeList list : myRecycledShelvedChangeLists) {
+    for (ShelvedChangeList list : getRecycledShelvedChangeLists()) {
       deleteListImpl(list);
       deleteListImpl(list);
+      mySchemeManager.removeScheme(list);
     }
     }
-    myRecycledShelvedChangeLists.clear();
     notifyStateChanged();
   }
 
     notifyStateChanged();
   }
 
-  private void recycleChangeList(final ShelvedChangeList listCopy, final ShelvedChangeList newList) {
+  private void recycleChangeList(@NotNull final ShelvedChangeList listCopy, @Nullable final ShelvedChangeList newList) {
     if (newList != null) {
       for (Iterator<ShelvedBinaryFile> shelvedChangeListIterator = listCopy.getBinaryFiles().iterator();
            shelvedChangeListIterator.hasNext(); ) {
     if (newList != null) {
       for (Iterator<ShelvedBinaryFile> shelvedChangeListIterator = listCopy.getBinaryFiles().iterator();
            shelvedChangeListIterator.hasNext(); ) {
@@ -627,39 +740,23 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD
 
     if (!listCopy.getBinaryFiles().isEmpty() || !listCopy.getChanges(myProject).isEmpty()) {
       listCopy.setRecycled(true);
 
     if (!listCopy.getBinaryFiles().isEmpty() || !listCopy.getChanges(myProject).isEmpty()) {
       listCopy.setRecycled(true);
-      myRecycledShelvedChangeLists.add(listCopy);
       notifyStateChanged();
     }
   }
 
       notifyStateChanged();
     }
   }
 
-  private void recycleChangeList(final ShelvedChangeList changeList) {
+  private void recycleChangeList(@NotNull final ShelvedChangeList changeList) {
     recycleChangeList(changeList, null);
     recycleChangeList(changeList, null);
-    myShelvedChangeLists.remove(changeList);
     notifyStateChanged();
   }
 
     notifyStateChanged();
   }
 
-  public void deleteChangeList(final ShelvedChangeList changeList) {
+  public void deleteChangeList(@NotNull final ShelvedChangeList changeList) {
     deleteListImpl(changeList);
     deleteListImpl(changeList);
-    if (!changeList.isRecycled()) {
-      myShelvedChangeLists.remove(changeList);
-    }
-    else {
-      myRecycledShelvedChangeLists.remove(changeList);
-    }
+    mySchemeManager.removeScheme(changeList);
     notifyStateChanged();
   }
 
     notifyStateChanged();
   }
 
-  private void deleteListImpl(final ShelvedChangeList changeList) {
-    File file = new File(changeList.PATH);
-    myFileProcessor.delete(file.getName());
-
-    for (ShelvedBinaryFile binaryFile : changeList.getBinaryFiles()) {
-      final String path = binaryFile.SHELVED_PATH;
-      if (path != null) {
-        File binFile = new File(path);
-        myFileProcessor.delete(binFile.getName());
-      }
-    }
+  private void deleteListImpl(@NotNull final ShelvedChangeList changeList) {
+    FileUtil.delete(new File(myFileProcessor.getBaseDir(), changeList.getName()));
   }
 
   public void renameChangeList(final ShelvedChangeList changeList, final String newName) {
   }
 
   public void renameChangeList(final ShelvedChangeList changeList, final String newName) {