/**
  * Sort changes by roots
  *
  * @param changes a change list
  * @param exceptions exceptions to collect
  * @return sorted changes
  */
 private static Map<VirtualFile, Collection<Change>> sortChangesByGitRoot(
     @NotNull List<Change> changes, List<VcsException> exceptions) {
   Map<VirtualFile, Collection<Change>> result = new HashMap<VirtualFile, Collection<Change>>();
   for (Change change : changes) {
     final ContentRevision afterRevision = change.getAfterRevision();
     final ContentRevision beforeRevision = change.getBeforeRevision();
     // nothing-to-nothing change cannot happen.
     assert beforeRevision != null || afterRevision != null;
     // note that any path will work, because changes could happen within single vcs root
     final FilePath filePath =
         afterRevision != null ? afterRevision.getFile() : beforeRevision.getFile();
     final VirtualFile vcsRoot;
     try {
       // the parent paths for calculating roots in order to account for submodules that contribute
       // to the parent change. The path "." is never is valid change, so there should be no
       // problem
       // with it.
       vcsRoot = GitUtil.getGitRoot(filePath.getParentPath());
     } catch (VcsException e) {
       exceptions.add(e);
       continue;
     }
     Collection<Change> changeList = result.get(vcsRoot);
     if (changeList == null) {
       changeList = new ArrayList<Change>();
       result.put(vcsRoot, changeList);
     }
     changeList.add(change);
   }
   return result;
 }
  public ShelvedChangeList shelveChanges(
      final Collection<Change> changes, final String commitMessage, final boolean rollback)
      throws IOException, VcsException {
    final List<Change> textChanges = new ArrayList<Change>();
    final List<ShelvedBinaryFile> binaryFiles = new ArrayList<ShelvedBinaryFile>();
    for (Change change : changes) {
      if (ChangesUtil.getFilePath(change).isDirectory()) {
        continue;
      }
      if (change.getBeforeRevision() instanceof BinaryContentRevision
          || change.getAfterRevision() instanceof BinaryContentRevision) {
        binaryFiles.add(shelveBinaryFile(change));
      } else {
        textChanges.add(change);
      }
    }

    final ShelvedChangeList changeList;
    try {
      File patchPath = getPatchPath(commitMessage);
      ProgressManager.checkCanceled();
      final List<FilePatch> patches =
          IdeaTextPatchBuilder.buildPatch(
              myProject, textChanges, myProject.getBaseDir().getPresentableUrl(), false);
      ProgressManager.checkCanceled();

      CommitContext commitContext = new CommitContext();
      baseRevisionsOfDvcsIntoContext(textChanges, commitContext);
      myFileProcessor.savePathFile(
          new CompoundShelfFileProcessor.ContentProvider() {
            public void writeContentTo(final Writer writer, CommitContext commitContext)
                throws IOException {
              UnifiedDiffWriter.write(myProject, patches, writer, "\n", commitContext);
            }
          },
          patchPath,
          commitContext);

      changeList =
          new ShelvedChangeList(
              patchPath.toString(), commitMessage.replace('\n', ' '), binaryFiles);
      myShelvedChangeLists.add(changeList);
      ProgressManager.checkCanceled();

      if (rollback) {
        new RollbackWorker(myProject, false)
            .doRollback(changes, true, null, VcsBundle.message("shelve.changes.action"));
      }
    } finally {
      notifyStateChanged();
    }

    return changeList;
  }
 private void baseRevisionsOfDvcsIntoContext(
     List<Change> textChanges, CommitContext commitContext) {
   ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
   if (vcsManager.dvcsUsedInProject()
       && VcsConfiguration.getInstance(myProject).INCLUDE_TEXT_INTO_SHELF) {
     final Set<Change> big = SelectFilesToAddTextsToPatchPanel.getBig(textChanges);
     final ArrayList<FilePath> toKeep = new ArrayList<FilePath>();
     for (Change change : textChanges) {
       if (change.getBeforeRevision() == null || change.getAfterRevision() == null) continue;
       if (big.contains(change)) continue;
       FilePath filePath = ChangesUtil.getFilePath(change);
       final AbstractVcs vcs = vcsManager.getVcsFor(filePath);
       if (vcs != null && VcsType.distibuted.equals(vcs.getType())) {
         toKeep.add(filePath);
       }
     }
     commitContext.putUserData(BaseRevisionTextPatchEP.ourPutBaseRevisionTextKey, true);
     commitContext.putUserData(BaseRevisionTextPatchEP.ourBaseRevisionPaths, toKeep);
   }
 }
  private ShelvedBinaryFile shelveBinaryFile(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) {
      String shelvedName = FileUtil.getNameWithoutExtension(afterFile.getName());
      String shelvedExt = FileUtil.getExtension(afterFile.getName());
      File shelvedFile =
          FileUtil.findSequentNonexistentFile(
              myFileProcessor.getBaseIODir(), shelvedName, shelvedExt);

      myFileProcessor.saveFile(afterRevision.getFile().getIOFile(), shelvedFile);

      shelvedPath = shelvedFile.getPath();
    }
    String beforePath = ChangesUtil.getProjectRelativePath(myProject, beforeFile);
    String afterPath = ChangesUtil.getProjectRelativePath(myProject, afterFile);
    return new ShelvedBinaryFile(beforePath, afterPath, shelvedPath);
  }
 public List<VcsException> commit(
     @NotNull List<Change> changes,
     @NotNull String message,
     @NotNull NullableFunction<Object, Object> parametersHolder,
     Set<String> feedback) {
   List<VcsException> exceptions = new ArrayList<VcsException>();
   Map<VirtualFile, Collection<Change>> sortedChanges = sortChangesByGitRoot(changes, exceptions);
   log.assertTrue(
       !sortedChanges.isEmpty(), "Trying to commit an empty list of changes: " + changes);
   for (Map.Entry<VirtualFile, Collection<Change>> entry : sortedChanges.entrySet()) {
     final VirtualFile root = entry.getKey();
     try {
       File messageFile = createMessageFile(root, message);
       try {
         final Set<FilePath> added = new HashSet<FilePath>();
         final Set<FilePath> removed = new HashSet<FilePath>();
         for (Change change : entry.getValue()) {
           switch (change.getType()) {
             case NEW:
             case MODIFICATION:
               added.add(change.getAfterRevision().getFile());
               break;
             case DELETED:
               removed.add(change.getBeforeRevision().getFile());
               break;
             case MOVED:
               FilePath afterPath = change.getAfterRevision().getFile();
               FilePath beforePath = change.getBeforeRevision().getFile();
               added.add(afterPath);
               if (!GitFileUtils.shouldIgnoreCaseChange(
                   afterPath.getPath(), beforePath.getPath())) {
                 removed.add(beforePath);
               }
               break;
             default:
               throw new IllegalStateException("Unknown change type: " + change.getType());
           }
         }
         try {
           try {
             Set<FilePath> files = new HashSet<FilePath>();
             files.addAll(added);
             files.addAll(removed);
             commit(
                 myProject,
                 root,
                 files,
                 messageFile,
                 myNextCommitAuthor,
                 myNextCommitAmend,
                 myNextCommitAuthorDate);
           } catch (VcsException ex) {
             PartialOperation partialOperation = isMergeCommit(ex);
             if (partialOperation == PartialOperation.NONE) {
               throw ex;
             }
             if (!mergeCommit(
                 myProject,
                 root,
                 added,
                 removed,
                 messageFile,
                 myNextCommitAuthor,
                 exceptions,
                 partialOperation)) {
               throw ex;
             }
           }
         } finally {
           if (!messageFile.delete()) {
             log.warn("Failed to remove temporary file: " + messageFile);
           }
         }
       } catch (VcsException e) {
         exceptions.add(e);
       }
     } catch (IOException ex) {
       //noinspection ThrowableInstanceNeverThrown
       exceptions.add(new VcsException("Creation of commit message file failed", ex));
     }
   }
   if (myNextCommitIsPushed != null
       && myNextCommitIsPushed.booleanValue()
       && exceptions.isEmpty()) {
     // push
     UIUtil.invokeLaterIfNeeded(
         new Runnable() {
           public void run() {
             GitPusher.showPushDialogAndPerformPush(
                 myProject, ServiceManager.getService(myProject, GitPlatformFacade.class));
           }
         });
   }
   return exceptions;
 }