/* * Copyright 2000-2015 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. */ /* * Created by IntelliJ IDEA. * User: yole * Date: 22.11.2006 * Time: 19:59:36 */ package com.intellij.openapi.vcs.changes.shelf; import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.application.ApplicationManager; 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.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.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.changes.patch.PatchFileType; import com.intellij.openapi.vcs.changes.patch.PatchNameChecker; import com.intellij.openapi.vcs.changes.ui.RollbackChangesDialog; import com.intellij.openapi.vcs.changes.ui.RollbackWorker; import com.intellij.openapi.vfs.CharsetToolkit; 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.containers.ContainerUtil; import com.intellij.util.continuation.*; 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.jdom.Parent; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.io.*; import java.util.*; 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 mySchemeManager; 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; @NonNls private static final String ATTRIBUTE_SHOW_RECYCLED = "show_recycled"; @NotNull private final CompoundShelfFileProcessor myFileProcessor; public static final Topic SHELF_TOPIC = new Topic("shelf updates", ChangeListener.class); private boolean myShowRecycled; public ShelveChangesManager(final Project project, final MessageBus bus) { super(project); myPathMacroSubstitutor = PathMacroManager.getInstance(myProject).createTrackingSubstitutor(); myBus = bus; mySchemeManager = SchemesManagerFactory.getInstance(project).create(SHELVE_MANAGER_DIR_PATH, new BaseSchemeProcessor() { @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 @NonNls @NotNull public String getComponentName() { return "ShelveChangesManager"; } @Override public void readExternal(Element element) throws InvalidDataException { final String showRecycled = element.getAttributeValue(ATTRIBUTE_SHOW_RECYCLED); if (showRecycled != null) { myShowRecycled = Boolean.parseBoolean(showRecycled); } else { myShowRecycled = true; } migrateOldShelfInfo(element, true); migrateOldShelfInfo(element, false); } //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); } } /** * 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 checkAndMigrateOldPatchResourcesToNewSchemeStorage() { Collection 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 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 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); } } 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)); } public List getShelvedChangeLists() { return getRecycled(false); } @NotNull private List getRecycled(final boolean recycled) { return ContainerUtil.newUnmodifiableList(ContainerUtil.filter(mySchemeManager.getAllSchemes(), new Condition() { @Override public boolean value(ShelvedChangeList list) { return recycled ? list.isRecycled() : !list.isRecycled(); } })); } public ShelvedChangeList shelveChanges(final Collection changes, final String commitMessage, final boolean rollback) throws IOException, VcsException { final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (progressIndicator != null) { progressIndicator.setText(VcsBundle.message("shelve.changes.progress.title")); } File schemePatchDir = generateUniqueSchemePatchDir(commitMessage, true); final List textChanges = new ArrayList(); final List binaryFiles = new ArrayList(); for (Change change : changes) { if (ChangesUtil.getFilePath(change).isDirectory()) { continue; } if (change.getBeforeRevision() instanceof BinaryContentRevision || change.getAfterRevision() instanceof BinaryContentRevision) { binaryFiles.add(shelveBinaryFile(schemePatchDir, change)); } else { textChanges.add(change); } } final ShelvedChangeList changeList; try { File patchPath = getPatchFileInConfigDir(schemePatchDir); ProgressManager.checkCanceled(); final List patches = IdeaTextPatchBuilder.buildPatch(myProject, textChanges, myProject.getBaseDir().getPresentableUrl(), false); ProgressManager.checkCanceled(); CommitContext commitContext = new CommitContext(); baseRevisionsOfDvcsIntoContext(textChanges, commitContext); myFileProcessor.savePathFile( new CompoundShelfFileProcessor.ContentProvider() { @Override 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); changeList.setName(schemePatchDir.getName()); ProgressManager.checkCanceled(); mySchemeManager.addNewScheme(changeList, false); if (rollback) { final String operationName = UIUtil.removeMnemonic(RollbackChangesDialog.operationNameByChanges(myProject, changes)); boolean modalContext = ApplicationManager.getApplication().isDispatchThread() && LaterInvocator.isInModalContext(); if (progressIndicator != null) { progressIndicator.startNonCancelableSection(); } new RollbackWorker(myProject, operationName, modalContext). doRollback(changes, true, null, VcsBundle.message("shelve.changes.action")); } } finally { notifyStateChanged(); } return changeList; } @NotNull private static File getPatchFileInConfigDir(@NotNull File schemePatchDir) { return new File(schemePatchDir, DEFAULT_PATCH_NAME + "." + VcsConfiguration.PATCH); } private void baseRevisionsOfDvcsIntoContext(List textChanges, CommitContext commitContext) { ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); if (vcsManager.dvcsUsedInProject() && VcsConfiguration.getInstance(myProject).INCLUDE_TEXT_INTO_SHELF) { final Set big = SelectFilesToAddTextsToPatchPanel.getBig(textChanges); final ArrayList toKeep = new ArrayList(); 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.distributed.equals(vcs.getType())) { toKeep.add(filePath); } } commitContext.putUserData(BaseRevisionTextPatchEP.ourPutBaseRevisionTextKey, true); commitContext.putUserData(BaseRevisionTextPatchEP.ourBaseRevisionPaths, toKeep); } } public ShelvedChangeList importFilePatches(final String fileName, final List patches, final PatchEP[] patchTransitExtensions) throws IOException { try { File schemePatchDir = generateUniqueSchemePatchDir(fileName, true); File patchPath = getPatchFileInConfigDir(schemePatchDir); myFileProcessor.savePathFile( new CompoundShelfFileProcessor.ContentProvider() { @Override public void writeContentTo(final Writer writer, CommitContext commitContext) throws IOException { UnifiedDiffWriter.write(myProject, patches, writer, "\n", patchTransitExtensions, commitContext); } }, patchPath, new CommitContext()); final ShelvedChangeList changeList = new ShelvedChangeList(patchPath.toString(), fileName.replace('\n', ' '), new SmartList()); changeList.setName(schemePatchDir.getName()); mySchemeManager.addNewScheme(changeList, false); return changeList; } finally { notifyStateChanged(); } } public List gatherPatchFiles(final Collection files) { final List result = new ArrayList(); final LinkedList filesQueue = new LinkedList(files); while (!filesQueue.isEmpty()) { ProgressManager.checkCanceled(); final VirtualFile file = filesQueue.removeFirst(); if (file.isDirectory()) { filesQueue.addAll(Arrays.asList(file.getChildren())); continue; } if (PatchFileType.NAME.equals(file.getFileType().getName())) { result.add(file); } } return result; } public List importChangeLists(final Collection files, final Consumer exceptionConsumer) { final List result = new ArrayList(files.size()); try { final FilesProgress filesProgress = new FilesProgress(files.size(), "Processing "); for (VirtualFile file : files) { filesProgress.updateIndicator(file); final String description = file.getNameWithoutExtension().replace('_', ' '); File schemeNameDir = generateUniqueSchemePatchDir(description, true); final File patchPath = getPatchFileInConfigDir(schemeNameDir); final ShelvedChangeList list = new ShelvedChangeList(patchPath.getPath(), description, new SmartList(), file.getTimeStamp()); list.setName(schemeNameDir.getName()); try { final List patchesList = loadPatches(myProject, file.getPath(), new CommitContext()); if (!patchesList.isEmpty()) { FileUtil.copy(new File(file.getPath()), patchPath); // add only if ok to read patch mySchemeManager.addNewScheme(list, false); result.add(list); } } catch (IOException e) { exceptionConsumer.consume(new VcsException(e)); } catch (PatchSyntaxException e) { exceptionConsumer.consume(new VcsException(e)); } } } finally { notifyStateChanged(); } return result; } 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) { File shelvedFile = new File(schemePatchDir, afterFile.getName()); FileUtil.copy(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); } private void notifyStateChanged() { if (!myProject.isDisposed()) { myBus.syncPublisher(SHELF_TOPIC).stateChanged(new ChangeEvent(this)); } } @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 dir.mkdirs(); } return dir; } @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 ? VcsConfiguration.getInstance(project).getPatchFileExtension() : extension); if (nonexistentFile.getName().length() >= PatchNameChecker.MAX) { defaultPath = defaultPath.substring(0, defaultPath.length() - 1); continue; } return nonexistentFile; } } @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; } public void unshelveChangeList(@NotNull final ShelvedChangeList changeList, @Nullable final List changes, @Nullable final List binaryFiles, final LocalChangeList targetChangeList) { unshelveChangeList(changeList, changes, binaryFiles, targetChangeList, true); } @AsynchronousExecution private void unshelveChangeList(@NotNull final ShelvedChangeList changeList, @Nullable final List changes, @Nullable final List binaryFiles, @Nullable final LocalChangeList targetChangeList, boolean showSuccessNotification) { final Continuation continuation = Continuation.createForCurrentProgress(myProject, true, "Unshelve changes"); final GatheringContinuationContext initContext = new GatheringContinuationContext(); scheduleUnshelveChangeList(changeList, changes, binaryFiles, targetChangeList, showSuccessNotification, initContext, false, false, null, null); continuation.run(initContext.getList()); } @AsynchronousExecution public void scheduleUnshelveChangeList(@NotNull final ShelvedChangeList changeList, @Nullable final List changes, @Nullable final List binaryFiles, @Nullable final LocalChangeList targetChangeList, final boolean showSuccessNotification, final ContinuationContext context, final boolean systemOperation, final boolean reverse, final String leftConflictTitle, final String rightConflictTitle) { context.next(new TaskDescriptor("", Where.AWT) { @Override public void run(ContinuationContext contextInner) { final List remainingPatches = new ArrayList(); final CommitContext commitContext = new CommitContext(); final List textFilePatches; try { textFilePatches = loadTextPatches(myProject, changeList, changes, remainingPatches, commitContext); } catch (IOException e) { LOG.info(e); PatchApplier.showError(myProject, "Cannot load patch(es): " + e.getMessage(), true); return; } catch (PatchSyntaxException e) { PatchApplier.showError(myProject, "Cannot load patch(es): " + e.getMessage(), true); LOG.info(e); return; } final List patches = new ArrayList(textFilePatches); final List remainingBinaries = new ArrayList(); final List binaryFilesToUnshelve = getBinaryFilesToUnshelve(changeList, binaryFiles, remainingBinaries); for (final ShelvedBinaryFile shelvedBinaryFile : binaryFilesToUnshelve) { patches.add(new ShelvedBinaryFilePatch(shelvedBinaryFile)); } final BinaryPatchApplier binaryPatchApplier = new BinaryPatchApplier(); final PatchApplier patchApplier = new PatchApplier(myProject, myProject.getBaseDir(), patches, targetChangeList, binaryPatchApplier, commitContext, reverse, leftConflictTitle, rightConflictTitle); patchApplier.setIsSystemOperation(systemOperation); // after patch applier part contextInner.next(new TaskDescriptor("", Where.AWT) { @Override public void run(ContinuationContext context) { remainingPatches.addAll(patchApplier.getRemainingPatches()); if (remainingPatches.isEmpty() && remainingBinaries.isEmpty()) { recycleChangeList(changeList); } else { saveRemainingPatches(changeList, remainingPatches, remainingBinaries, commitContext); } } }); patchApplier.scheduleSelf(showSuccessNotification, contextInner, systemOperation); } }); } private static List loadTextPatches(final Project project, final ShelvedChangeList changeList, final List changes, final List remainingPatches, final CommitContext commitContext) throws IOException, PatchSyntaxException { final List textFilePatches = loadPatches(project, changeList.PATH, commitContext); if (changes != null) { final Iterator iterator = textFilePatches.iterator(); while (iterator.hasNext()) { TextFilePatch patch = iterator.next(); if (!needUnshelve(patch, changes)) { remainingPatches.add(patch); iterator.remove(); } } } return textFilePatches; } private class BinaryPatchApplier implements CustomBinaryPatchApplier { private final List myAppliedPatches; private BinaryPatchApplier() { myAppliedPatches = new ArrayList(); } @Override @NotNull public ApplyPatchStatus apply(final List>> patches) throws IOException { for (Pair> patch : patches) { final ShelvedBinaryFilePatch shelvedPatch = patch.getSecond().getPatch(); unshelveBinaryFile(shelvedPatch.getShelvedBinaryFile(), patch.getFirst()); myAppliedPatches.add(shelvedPatch); } return ApplyPatchStatus.SUCCESS; } @Override @NotNull public List getAppliedPatches() { return myAppliedPatches; } } private static List getBinaryFilesToUnshelve(final ShelvedChangeList changeList, final List binaryFiles, final List remainingBinaries) { if (binaryFiles == null) { return new ArrayList(changeList.getBinaryFiles()); } ArrayList result = new ArrayList(); for (ShelvedBinaryFile file : changeList.getBinaryFiles()) { if (binaryFiles.contains(file)) { result.add(file); } else { remainingBinaries.add(file); } } return result; } private void unshelveBinaryFile(final ShelvedBinaryFile file, @NotNull final VirtualFile patchTarget) throws IOException { final Ref ex = new Ref(); final Ref patchedFileRef = new Ref(); final File shelvedFile = file.SHELVED_PATH == null ? null : new File(file.SHELVED_PATH); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { try { if (shelvedFile == null) { patchTarget.delete(this); } else { patchTarget.setBinaryContent(FileUtil.loadFileBytes(shelvedFile)); patchedFileRef.set(patchTarget); } } catch (IOException e) { ex.set(e); } } }); if (!ex.isNull()) { throw ex.get(); } } private static boolean needUnshelve(final FilePatch patch, final List changes) { for (ShelvedChange change : changes) { if (Comparing.equal(patch.getBeforeName(), change.getBeforePath())) { return true; } } return false; } private static void writePatchesToFile(final Project project, final String path, final List remainingPatches, CommitContext commitContext) { try { OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(path), CharsetToolkit.UTF8_CHARSET); try { UnifiedDiffWriter.write(project, remainingPatches, writer, "\n", commitContext); } finally { writer.close(); } } catch (IOException e) { LOG.error(e); } } void saveRemainingPatches(final ShelvedChangeList changeList, final List remainingPatches, final List remainingBinaries, CommitContext commitContext) { final File newPatchDir = generateUniqueSchemePatchDir(changeList.DESCRIPTION, true); final File newPath = getPatchFileInConfigDir(newPatchDir); try { FileUtil.copy(new File(changeList.PATH), newPath); } catch (IOException e) { // do not delete if cannot recycle return; } final ShelvedChangeList listCopy = new ShelvedChangeList(newPath.getAbsolutePath(), changeList.DESCRIPTION, new ArrayList(changeList.getBinaryFiles())); listCopy.setName(newPatchDir.getName()); listCopy.DATE = changeList.DATE == null ? null : new Date(changeList.DATE.getTime()); writePatchesToFile(myProject, changeList.PATH, remainingPatches, commitContext); 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(); } public void restoreList(@NotNull final ShelvedChangeList changeList) { ShelvedChangeList list = mySchemeManager.findSchemeByName(changeList.getName()); if (list != null) { list.setRecycled(false); } notifyStateChanged(); } public List getRecycledShelvedChangeLists() { return getRecycled(true); } public void clearRecycled() { for (ShelvedChangeList list : getRecycledShelvedChangeLists()) { deleteListImpl(list); mySchemeManager.removeScheme(list); } notifyStateChanged(); } private void recycleChangeList(@NotNull final ShelvedChangeList listCopy, @Nullable final ShelvedChangeList newList) { if (newList != null) { for (Iterator shelvedChangeListIterator = listCopy.getBinaryFiles().iterator(); shelvedChangeListIterator.hasNext(); ) { final ShelvedBinaryFile binaryFile = shelvedChangeListIterator.next(); for (ShelvedBinaryFile newBinary : newList.getBinaryFiles()) { if (Comparing.equal(newBinary.BEFORE_PATH, binaryFile.BEFORE_PATH) && Comparing.equal(newBinary.AFTER_PATH, binaryFile.AFTER_PATH)) { shelvedChangeListIterator.remove(); } } } for (Iterator iterator = listCopy.getChanges(myProject).iterator(); iterator.hasNext(); ) { final ShelvedChange change = iterator.next(); for (ShelvedChange newChange : newList.getChanges(myProject)) { if (Comparing.equal(change.getBeforePath(), newChange.getBeforePath()) && Comparing.equal(change.getAfterPath(), newChange.getAfterPath())) { iterator.remove(); } } } // needed only if partial unshelve try { final CommitContext commitContext = new CommitContext(); final List patches = new ArrayList(); for (ShelvedChange change : listCopy.getChanges(myProject)) { patches.add(change.loadFilePatch(myProject, commitContext)); } writePatchesToFile(myProject, listCopy.PATH, patches, commitContext); } catch (IOException e) { LOG.info(e); // left file as is } catch (PatchSyntaxException e) { LOG.info(e); // left file as is } } if (!listCopy.getBinaryFiles().isEmpty() || !listCopy.getChanges(myProject).isEmpty()) { listCopy.setRecycled(true); notifyStateChanged(); } } private void recycleChangeList(@NotNull final ShelvedChangeList changeList) { recycleChangeList(changeList, null); notifyStateChanged(); } public void deleteChangeList(@NotNull final ShelvedChangeList changeList) { deleteListImpl(changeList); mySchemeManager.removeScheme(changeList); notifyStateChanged(); } private void deleteListImpl(@NotNull final ShelvedChangeList changeList) { FileUtil.delete(new File(myFileProcessor.getBaseDir(), changeList.getName())); } public void renameChangeList(final ShelvedChangeList changeList, final String newName) { changeList.DESCRIPTION = newName; notifyStateChanged(); } @NotNull public static List loadPatches(Project project, final String patchPath, CommitContext commitContext) throws IOException, PatchSyntaxException { return loadPatches(project, patchPath, commitContext, true); } @NotNull static List loadPatchesWithoutContent(Project project, final String patchPath, CommitContext commitContext) throws IOException, PatchSyntaxException { return loadPatches(project, patchPath, commitContext, false); } private static List loadPatches(Project project, final String patchPath, CommitContext commitContext, boolean loadContent) throws IOException, PatchSyntaxException { char[] text = FileUtil.loadFileText(new File(patchPath), CharsetToolkit.UTF8); PatchReader reader = new PatchReader(new CharArrayCharSequence(text), loadContent); final List textFilePatches = reader.readAllPatches(); final TransparentlyFailedValueI>, PatchSyntaxException> additionalInfo = reader.getAdditionalInfo( null); ApplyPatchDefaultExecutor.applyAdditionalInfoBefore(project, additionalInfo, commitContext); return textFilePatches; } public static class ShelvedBinaryFilePatch extends FilePatch { private final ShelvedBinaryFile myShelvedBinaryFile; public ShelvedBinaryFilePatch(final ShelvedBinaryFile shelvedBinaryFile) { myShelvedBinaryFile = shelvedBinaryFile; setBeforeName(myShelvedBinaryFile.BEFORE_PATH); setAfterName(myShelvedBinaryFile.AFTER_PATH); } public static ShelvedBinaryFilePatch patchCopy(@NotNull final ShelvedBinaryFilePatch patch) { return new ShelvedBinaryFilePatch(patch.getShelvedBinaryFile()); } @Override public String getBeforeFileName() { return getFileName(myShelvedBinaryFile.BEFORE_PATH); } @Override public String getAfterFileName() { return getFileName(myShelvedBinaryFile.AFTER_PATH); } @Nullable private static String getFileName(String filePath) { return filePath != null ? PathUtil.getFileName(filePath) : null; } @Override public boolean isNewFile() { return myShelvedBinaryFile.BEFORE_PATH == null; } @Override public boolean isDeletedFile() { return myShelvedBinaryFile.AFTER_PATH == null; } public ShelvedBinaryFile getShelvedBinaryFile() { return myShelvedBinaryFile; } } public boolean isShowRecycled() { return myShowRecycled; } public void setShowRecycled(final boolean showRecycled) { myShowRecycled = showRecycled; notifyStateChanged(); } }