2 * Copyright 2000-2015 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 * Created by IntelliJ IDEA.
23 package com.intellij.openapi.vcs.changes.shelf;
25 import com.intellij.lifecycle.PeriodicalTasksCloser;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.impl.LaterInvocator;
28 import com.intellij.openapi.components.AbstractProjectComponent;
29 import com.intellij.openapi.components.PathMacroManager;
30 import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.diff.impl.patch.*;
33 import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase;
34 import com.intellij.openapi.diff.impl.patch.formove.CustomBinaryPatchApplier;
35 import com.intellij.openapi.diff.impl.patch.formove.PatchApplier;
36 import com.intellij.openapi.options.BaseSchemeProcessor;
37 import com.intellij.openapi.options.SchemesManager;
38 import com.intellij.openapi.options.SchemesManagerFactory;
39 import com.intellij.openapi.progress.AsynchronousExecution;
40 import com.intellij.openapi.progress.ProgressIndicator;
41 import com.intellij.openapi.progress.ProgressManager;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.util.*;
44 import com.intellij.openapi.util.io.FileUtil;
45 import com.intellij.openapi.util.text.StringUtil;
46 import com.intellij.openapi.vcs.*;
47 import com.intellij.openapi.vcs.changes.*;
48 import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor;
49 import com.intellij.openapi.vcs.changes.patch.PatchFileType;
50 import com.intellij.openapi.vcs.changes.patch.PatchNameChecker;
51 import com.intellij.openapi.vcs.changes.ui.RollbackChangesDialog;
52 import com.intellij.openapi.vcs.changes.ui.RollbackWorker;
53 import com.intellij.openapi.vfs.CharsetToolkit;
54 import com.intellij.openapi.vfs.VirtualFile;
55 import com.intellij.util.Consumer;
56 import com.intellij.util.PathUtil;
57 import com.intellij.util.SmartList;
58 import com.intellij.util.containers.ContainerUtil;
59 import com.intellij.util.continuation.*;
60 import com.intellij.util.messages.MessageBus;
61 import com.intellij.util.messages.Topic;
62 import com.intellij.util.text.CharArrayCharSequence;
63 import com.intellij.util.text.UniqueNameGenerator;
64 import com.intellij.util.ui.UIUtil;
65 import com.intellij.vcsUtil.FilesProgress;
66 import org.jdom.Element;
67 import org.jdom.Parent;
68 import org.jetbrains.annotations.NonNls;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
72 import javax.swing.event.ChangeEvent;
73 import javax.swing.event.ChangeListener;
77 public class ShelveChangesManager extends AbstractProjectComponent implements JDOMExternalizable {
78 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager");
79 @NonNls private static final String ELEMENT_CHANGELIST = "changelist";
80 @NonNls private static final String ELEMENT_RECYCLED_CHANGELIST = "recycled_changelist";
81 @NonNls private static final String DEFAULT_PATCH_NAME = "shelved";
83 @NotNull private final TrackingPathMacroSubstitutor myPathMacroSubstitutor;
84 @NotNull private final SchemesManager<ShelvedChangeList, ShelvedChangeList> mySchemeManager;
86 public static ShelveChangesManager getInstance(Project project) {
87 return PeriodicalTasksCloser.getInstance().safeGetComponent(project, ShelveChangesManager.class);
90 private static final String SHELVE_MANAGER_DIR_PATH = "shelf";
91 private final MessageBus myBus;
93 @NonNls private static final String ATTRIBUTE_SHOW_RECYCLED = "show_recycled";
94 @NotNull private final CompoundShelfFileProcessor myFileProcessor;
96 public static final Topic<ChangeListener> SHELF_TOPIC = new Topic<ChangeListener>("shelf updates", ChangeListener.class);
97 private boolean myShowRecycled;
99 public ShelveChangesManager(final Project project, final MessageBus bus) {
101 myPathMacroSubstitutor = PathMacroManager.getInstance(myProject).createTrackingSubstitutor();
104 SchemesManagerFactory.getInstance(project).create(SHELVE_MANAGER_DIR_PATH, new BaseSchemeProcessor<ShelvedChangeList>() {
107 public ShelvedChangeList readScheme(@NotNull Element element) throws InvalidDataException {
108 return readOneShelvedChangeList(element);
112 public Parent writeScheme(@NotNull ShelvedChangeList scheme) throws WriteExternalException {
113 Element child = new Element(ELEMENT_CHANGELIST);
114 scheme.writeExternal(child);
115 myPathMacroSubstitutor.collapsePaths(child);
119 myFileProcessor = new CompoundShelfFileProcessor(mySchemeManager.getRootDirectory());
120 ChangeListManager.getInstance(project).addDirectoryToIgnoreImplicitly(mySchemeManager.getRootDirectory().getAbsolutePath());
121 mySchemeManager.loadSchemes();
125 private ShelvedChangeList readOneShelvedChangeList(@NotNull Element element) throws InvalidDataException {
126 ShelvedChangeList data = new ShelvedChangeList();
127 myPathMacroSubstitutor.expandPaths(element);
128 data.readExternal(element);
135 public String getComponentName() {
136 return "ShelveChangesManager";
140 public void readExternal(Element element) throws InvalidDataException {
141 final String showRecycled = element.getAttributeValue(ATTRIBUTE_SHOW_RECYCLED);
142 if (showRecycled != null) {
143 myShowRecycled = Boolean.parseBoolean(showRecycled);
146 myShowRecycled = true;
148 migrateOldShelfInfo(element, true);
149 migrateOldShelfInfo(element, false);
152 //load old shelf information from workspace.xml without moving .patch and binary files into new directory
153 private void migrateOldShelfInfo(@NotNull Element element, boolean recycled) throws InvalidDataException {
154 for (Element changeSetElement : element.getChildren(recycled ? ELEMENT_RECYCLED_CHANGELIST : ELEMENT_CHANGELIST)) {
155 ShelvedChangeList list = readOneShelvedChangeList(changeSetElement);
156 File uniqueDir = generateUniqueSchemePatchDir(list.DESCRIPTION, false);
157 list.setName(uniqueDir.getName());
158 list.setRecycled(recycled);
159 mySchemeManager.addNewScheme(list, false);
164 * Should be called only once: when Settings Repository plugin runs first time
166 * @return collection of non-migrated or not deleted files to show a error somewhere outside
169 public Collection<String> checkAndMigrateOldPatchResourcesToNewSchemeStorage() {
170 Collection<String> nonMigratedPaths = ContainerUtil.newArrayList();
171 for (ShelvedChangeList list : mySchemeManager.getAllSchemes()) {
172 File patchDir = new File(myFileProcessor.getBaseDir(), list.getName());
173 nonMigratedPaths.addAll(migrateIfNeededToSchemeDir(list, patchDir));
175 return nonMigratedPaths;
179 private static Collection<String> migrateIfNeededToSchemeDir(@NotNull ShelvedChangeList list, @NotNull File targetDirectory) {
180 // it should be enough for migration to check if resource directory exists. If any bugs appeared add isAncestor checks for each path
181 if (targetDirectory.exists() || !targetDirectory.mkdirs()) return ContainerUtil.emptyList();
182 Collection<String> nonMigratedPaths = ContainerUtil.newArrayList();
183 //try to move .patch file
184 File patchFile = new File(list.PATH);
185 if (patchFile.exists()) {
186 File newPatchFile = getPatchFileInConfigDir(targetDirectory);
188 FileUtil.copy(patchFile, newPatchFile);
189 list.PATH = newPatchFile.getPath();
190 FileUtil.delete(patchFile);
192 catch (IOException e) {
193 nonMigratedPaths.add(list.PATH);
197 for (ShelvedBinaryFile file : list.getBinaryFiles()) {
198 if (file.SHELVED_PATH != null) {
199 File shelvedFile = new File(file.SHELVED_PATH);
200 if (!StringUtil.isEmptyOrSpaces(file.AFTER_PATH) && shelvedFile.exists()) {
201 File newShelvedFile = new File(targetDirectory, PathUtil.getFileName(file.AFTER_PATH));
203 FileUtil.copy(shelvedFile, newShelvedFile);
204 file.SHELVED_PATH = newShelvedFile.getPath();
205 FileUtil.delete(shelvedFile);
207 catch (IOException e) {
208 nonMigratedPaths.add(shelvedFile.getPath());
213 return nonMigratedPaths;
217 public void writeExternal(Element element) throws WriteExternalException {
218 element.setAttribute(ATTRIBUTE_SHOW_RECYCLED, Boolean.toString(myShowRecycled));
221 public List<ShelvedChangeList> getShelvedChangeLists() {
222 return getRecycled(false);
226 private List<ShelvedChangeList> getRecycled(final boolean recycled) {
227 return ContainerUtil.newUnmodifiableList(ContainerUtil.filter(mySchemeManager.getAllSchemes(), new Condition<ShelvedChangeList>() {
229 public boolean value(ShelvedChangeList list) {
230 return recycled ? list.isRecycled() : !list.isRecycled();
235 public ShelvedChangeList shelveChanges(final Collection<Change> changes, final String commitMessage, final boolean rollback)
236 throws IOException, VcsException {
237 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
238 if (progressIndicator != null) {
239 progressIndicator.setText(VcsBundle.message("shelve.changes.progress.title"));
241 File schemePatchDir = generateUniqueSchemePatchDir(commitMessage, true);
242 final List<Change> textChanges = new ArrayList<Change>();
243 final List<ShelvedBinaryFile> binaryFiles = new ArrayList<ShelvedBinaryFile>();
244 for (Change change : changes) {
245 if (ChangesUtil.getFilePath(change).isDirectory()) {
248 if (change.getBeforeRevision() instanceof BinaryContentRevision || change.getAfterRevision() instanceof BinaryContentRevision) {
249 binaryFiles.add(shelveBinaryFile(schemePatchDir, change));
252 textChanges.add(change);
256 final ShelvedChangeList changeList;
258 File patchPath = getPatchFileInConfigDir(schemePatchDir);
259 ProgressManager.checkCanceled();
260 final List<FilePatch> patches =
261 IdeaTextPatchBuilder.buildPatch(myProject, textChanges, myProject.getBaseDir().getPresentableUrl(), false);
262 ProgressManager.checkCanceled();
264 CommitContext commitContext = new CommitContext();
265 baseRevisionsOfDvcsIntoContext(textChanges, commitContext);
266 myFileProcessor.savePathFile(
267 new CompoundShelfFileProcessor.ContentProvider() {
269 public void writeContentTo(final Writer writer, CommitContext commitContext) throws IOException {
270 UnifiedDiffWriter.write(myProject, patches, writer, "\n", commitContext);
273 patchPath, commitContext);
275 changeList = new ShelvedChangeList(patchPath.toString(), commitMessage.replace('\n', ' '), binaryFiles);
276 changeList.setName(schemePatchDir.getName());
277 ProgressManager.checkCanceled();
278 mySchemeManager.addNewScheme(changeList, false);
281 final String operationName = UIUtil.removeMnemonic(RollbackChangesDialog.operationNameByChanges(myProject, changes));
282 boolean modalContext = ApplicationManager.getApplication().isDispatchThread() && LaterInvocator.isInModalContext();
283 if (progressIndicator != null) {
284 progressIndicator.startNonCancelableSection();
286 new RollbackWorker(myProject, operationName, modalContext).
287 doRollback(changes, true, null, VcsBundle.message("shelve.changes.action"));
291 notifyStateChanged();
298 private static File getPatchFileInConfigDir(@NotNull File schemePatchDir) {
299 return new File(schemePatchDir, DEFAULT_PATCH_NAME + "." + VcsConfiguration.PATCH);
302 private void baseRevisionsOfDvcsIntoContext(List<Change> textChanges, CommitContext commitContext) {
303 ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject);
304 if (vcsManager.dvcsUsedInProject() && VcsConfiguration.getInstance(myProject).INCLUDE_TEXT_INTO_SHELF) {
305 final Set<Change> big = SelectFilesToAddTextsToPatchPanel.getBig(textChanges);
306 final ArrayList<FilePath> toKeep = new ArrayList<FilePath>();
307 for (Change change : textChanges) {
308 if (change.getBeforeRevision() == null || change.getAfterRevision() == null) continue;
309 if (big.contains(change)) continue;
310 FilePath filePath = ChangesUtil.getFilePath(change);
311 final AbstractVcs vcs = vcsManager.getVcsFor(filePath);
312 if (vcs != null && VcsType.distributed.equals(vcs.getType())) {
313 toKeep.add(filePath);
316 commitContext.putUserData(BaseRevisionTextPatchEP.ourPutBaseRevisionTextKey, true);
317 commitContext.putUserData(BaseRevisionTextPatchEP.ourBaseRevisionPaths, toKeep);
321 public ShelvedChangeList importFilePatches(final String fileName, final List<FilePatch> patches, final PatchEP[] patchTransitExtensions)
324 File schemePatchDir = generateUniqueSchemePatchDir(fileName, true);
325 File patchPath = getPatchFileInConfigDir(schemePatchDir);
326 myFileProcessor.savePathFile(
327 new CompoundShelfFileProcessor.ContentProvider() {
329 public void writeContentTo(final Writer writer, CommitContext commitContext) throws IOException {
330 UnifiedDiffWriter.write(myProject, patches, writer, "\n", patchTransitExtensions, commitContext);
333 patchPath, new CommitContext());
335 final ShelvedChangeList changeList =
336 new ShelvedChangeList(patchPath.toString(), fileName.replace('\n', ' '), new SmartList<ShelvedBinaryFile>());
337 changeList.setName(schemePatchDir.getName());
338 mySchemeManager.addNewScheme(changeList, false);
342 notifyStateChanged();
346 public List<VirtualFile> gatherPatchFiles(final Collection<VirtualFile> files) {
347 final List<VirtualFile> result = new ArrayList<VirtualFile>();
349 final LinkedList<VirtualFile> filesQueue = new LinkedList<VirtualFile>(files);
350 while (!filesQueue.isEmpty()) {
351 ProgressManager.checkCanceled();
352 final VirtualFile file = filesQueue.removeFirst();
353 if (file.isDirectory()) {
354 filesQueue.addAll(Arrays.asList(file.getChildren()));
357 if (PatchFileType.NAME.equals(file.getFileType().getName())) {
365 public List<ShelvedChangeList> importChangeLists(final Collection<VirtualFile> files,
366 final Consumer<VcsException> exceptionConsumer) {
367 final List<ShelvedChangeList> result = new ArrayList<ShelvedChangeList>(files.size());
369 final FilesProgress filesProgress = new FilesProgress(files.size(), "Processing ");
370 for (VirtualFile file : files) {
371 filesProgress.updateIndicator(file);
372 final String description = file.getNameWithoutExtension().replace('_', ' ');
373 File schemeNameDir = generateUniqueSchemePatchDir(description, true);
374 final File patchPath = getPatchFileInConfigDir(schemeNameDir);
375 final ShelvedChangeList list = new ShelvedChangeList(patchPath.getPath(), description, new SmartList<ShelvedBinaryFile>(),
376 file.getTimeStamp());
377 list.setName(schemeNameDir.getName());
379 final List<TextFilePatch> patchesList = loadPatches(myProject, file.getPath(), new CommitContext());
380 if (!patchesList.isEmpty()) {
381 FileUtil.copy(new File(file.getPath()), patchPath);
382 // add only if ok to read patch
383 mySchemeManager.addNewScheme(list, false);
387 catch (IOException e) {
388 exceptionConsumer.consume(new VcsException(e));
390 catch (PatchSyntaxException e) {
391 exceptionConsumer.consume(new VcsException(e));
396 notifyStateChanged();
401 private ShelvedBinaryFile shelveBinaryFile(@NotNull File schemePatchDir, final Change change) throws IOException {
402 final ContentRevision beforeRevision = change.getBeforeRevision();
403 final ContentRevision afterRevision = change.getAfterRevision();
404 File beforeFile = beforeRevision == null ? null : beforeRevision.getFile().getIOFile();
405 File afterFile = afterRevision == null ? null : afterRevision.getFile().getIOFile();
406 String shelvedPath = null;
407 if (afterFile != null) {
408 File shelvedFile = new File(schemePatchDir, afterFile.getName());
409 FileUtil.copy(afterRevision.getFile().getIOFile(), shelvedFile);
410 shelvedPath = shelvedFile.getPath();
412 String beforePath = ChangesUtil.getProjectRelativePath(myProject, beforeFile);
413 String afterPath = ChangesUtil.getProjectRelativePath(myProject, afterFile);
414 return new ShelvedBinaryFile(beforePath, afterPath, shelvedPath);
417 private void notifyStateChanged() {
418 if (!myProject.isDisposed()) {
419 myBus.syncPublisher(SHELF_TOPIC).stateChanged(new ChangeEvent(this));
424 private File generateUniqueSchemePatchDir(@NotNull final String defaultName, boolean createResourceDirectory) {
425 String uniqueName = UniqueNameGenerator
426 .generateUniqueName(shortenAndSanitize(defaultName), mySchemeManager.getAllSchemeNames());
427 File dir = new File(myFileProcessor.getBaseDir(), uniqueName);
428 if (createResourceDirectory && !dir.exists()) {
429 //noinspection ResultOfMethodCallIgnored
436 // for create patch only; todo move or unify with unique directory creation
437 public static File suggestPatchName(Project project, @NotNull final String commitMessage, final File file, String extension) {
438 @NonNls String defaultPath = shortenAndSanitize(commitMessage);
440 final File nonexistentFile = FileUtil.findSequentNonexistentFile(file, defaultPath,
442 ? VcsConfiguration.getInstance(project).getPatchFileExtension()
444 if (nonexistentFile.getName().length() >= PatchNameChecker.MAX) {
445 defaultPath = defaultPath.substring(0, defaultPath.length() - 1);
448 return nonexistentFile;
453 private static String shortenAndSanitize(@NotNull String commitMessage) {
454 @NonNls String defaultPath = FileUtil.sanitizeFileName(commitMessage);
455 if (defaultPath.isEmpty()) {
456 defaultPath = "unnamed";
458 if (defaultPath.length() > PatchNameChecker.MAX - 10) {
459 defaultPath = defaultPath.substring(0, PatchNameChecker.MAX - 10);
464 public void unshelveChangeList(@NotNull final ShelvedChangeList changeList, @Nullable final List<ShelvedChange> changes,
465 @Nullable final List<ShelvedBinaryFile> binaryFiles, final LocalChangeList targetChangeList) {
466 unshelveChangeList(changeList, changes, binaryFiles, targetChangeList, true);
469 @AsynchronousExecution
470 private void unshelveChangeList(@NotNull final ShelvedChangeList changeList,
471 @Nullable final List<ShelvedChange> changes,
472 @Nullable final List<ShelvedBinaryFile> binaryFiles,
473 @Nullable final LocalChangeList targetChangeList,
474 boolean showSuccessNotification) {
475 final Continuation continuation = Continuation.createForCurrentProgress(myProject, true, "Unshelve changes");
476 final GatheringContinuationContext initContext = new GatheringContinuationContext();
477 scheduleUnshelveChangeList(changeList, changes, binaryFiles, targetChangeList, showSuccessNotification, initContext, false,
479 continuation.run(initContext.getList());
482 @AsynchronousExecution
483 public void scheduleUnshelveChangeList(@NotNull final ShelvedChangeList changeList,
484 @Nullable final List<ShelvedChange> changes,
485 @Nullable final List<ShelvedBinaryFile> binaryFiles,
486 @Nullable final LocalChangeList targetChangeList,
487 final boolean showSuccessNotification,
488 final ContinuationContext context,
489 final boolean systemOperation,
490 final boolean reverse,
491 final String leftConflictTitle,
492 final String rightConflictTitle) {
493 context.next(new TaskDescriptor("", Where.AWT) {
495 public void run(ContinuationContext contextInner) {
496 final List<FilePatch> remainingPatches = new ArrayList<FilePatch>();
498 final CommitContext commitContext = new CommitContext();
499 final List<TextFilePatch> textFilePatches;
501 textFilePatches = loadTextPatches(myProject, changeList, changes, remainingPatches, commitContext);
503 catch (IOException e) {
505 PatchApplier.showError(myProject, "Cannot load patch(es): " + e.getMessage(), true);
508 catch (PatchSyntaxException e) {
509 PatchApplier.showError(myProject, "Cannot load patch(es): " + e.getMessage(), true);
514 final List<FilePatch> patches = new ArrayList<FilePatch>(textFilePatches);
516 final List<ShelvedBinaryFile> remainingBinaries = new ArrayList<ShelvedBinaryFile>();
517 final List<ShelvedBinaryFile> binaryFilesToUnshelve = getBinaryFilesToUnshelve(changeList, binaryFiles, remainingBinaries);
519 for (final ShelvedBinaryFile shelvedBinaryFile : binaryFilesToUnshelve) {
520 patches.add(new ShelvedBinaryFilePatch(shelvedBinaryFile));
523 final BinaryPatchApplier binaryPatchApplier = new BinaryPatchApplier();
524 final PatchApplier<ShelvedBinaryFilePatch> patchApplier =
525 new PatchApplier<ShelvedBinaryFilePatch>(myProject, myProject.getBaseDir(),
526 patches, targetChangeList, binaryPatchApplier, commitContext, reverse, leftConflictTitle,
528 patchApplier.setIsSystemOperation(systemOperation);
530 // after patch applier part
531 contextInner.next(new TaskDescriptor("", Where.AWT) {
533 public void run(ContinuationContext context) {
534 remainingPatches.addAll(patchApplier.getRemainingPatches());
536 if (remainingPatches.isEmpty() && remainingBinaries.isEmpty()) {
537 recycleChangeList(changeList);
540 saveRemainingPatches(changeList, remainingPatches, remainingBinaries, commitContext);
545 patchApplier.scheduleSelf(showSuccessNotification, contextInner, systemOperation);
550 private static List<TextFilePatch> loadTextPatches(final Project project,
551 final ShelvedChangeList changeList,
552 final List<ShelvedChange> changes,
553 final List<FilePatch> remainingPatches,
554 final CommitContext commitContext)
555 throws IOException, PatchSyntaxException {
556 final List<TextFilePatch> textFilePatches = loadPatches(project, changeList.PATH, commitContext);
558 if (changes != null) {
559 final Iterator<TextFilePatch> iterator = textFilePatches.iterator();
560 while (iterator.hasNext()) {
561 TextFilePatch patch = iterator.next();
562 if (!needUnshelve(patch, changes)) {
563 remainingPatches.add(patch);
568 return textFilePatches;
571 private class BinaryPatchApplier implements CustomBinaryPatchApplier<ShelvedBinaryFilePatch> {
572 private final List<FilePatch> myAppliedPatches;
574 private BinaryPatchApplier() {
575 myAppliedPatches = new ArrayList<FilePatch>();
580 public ApplyPatchStatus apply(final List<Pair<VirtualFile, ApplyFilePatchBase<ShelvedBinaryFilePatch>>> patches) throws IOException {
581 for (Pair<VirtualFile, ApplyFilePatchBase<ShelvedBinaryFilePatch>> patch : patches) {
582 final ShelvedBinaryFilePatch shelvedPatch = patch.getSecond().getPatch();
583 unshelveBinaryFile(shelvedPatch.getShelvedBinaryFile(), patch.getFirst());
584 myAppliedPatches.add(shelvedPatch);
586 return ApplyPatchStatus.SUCCESS;
591 public List<FilePatch> getAppliedPatches() {
592 return myAppliedPatches;
596 private static List<ShelvedBinaryFile> getBinaryFilesToUnshelve(final ShelvedChangeList changeList,
597 final List<ShelvedBinaryFile> binaryFiles,
598 final List<ShelvedBinaryFile> remainingBinaries) {
599 if (binaryFiles == null) {
600 return new ArrayList<ShelvedBinaryFile>(changeList.getBinaryFiles());
602 ArrayList<ShelvedBinaryFile> result = new ArrayList<ShelvedBinaryFile>();
603 for (ShelvedBinaryFile file : changeList.getBinaryFiles()) {
604 if (binaryFiles.contains(file)) {
608 remainingBinaries.add(file);
614 private void unshelveBinaryFile(final ShelvedBinaryFile file, @NotNull final VirtualFile patchTarget) throws IOException {
615 final Ref<IOException> ex = new Ref<IOException>();
616 final Ref<VirtualFile> patchedFileRef = new Ref<VirtualFile>();
617 final File shelvedFile = file.SHELVED_PATH == null ? null : new File(file.SHELVED_PATH);
619 ApplicationManager.getApplication().runWriteAction(new Runnable() {
623 if (shelvedFile == null) {
624 patchTarget.delete(this);
627 patchTarget.setBinaryContent(FileUtil.loadFileBytes(shelvedFile));
628 patchedFileRef.set(patchTarget);
631 catch (IOException e) {
641 private static boolean needUnshelve(final FilePatch patch, final List<ShelvedChange> changes) {
642 for (ShelvedChange change : changes) {
643 if (Comparing.equal(patch.getBeforeName(), change.getBeforePath())) {
650 private static void writePatchesToFile(final Project project,
652 final List<FilePatch> remainingPatches,
653 CommitContext commitContext) {
655 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(path), CharsetToolkit.UTF8_CHARSET);
657 UnifiedDiffWriter.write(project, remainingPatches, writer, "\n", commitContext);
663 catch (IOException e) {
668 void saveRemainingPatches(final ShelvedChangeList changeList, final List<FilePatch> remainingPatches,
669 final List<ShelvedBinaryFile> remainingBinaries, CommitContext commitContext) {
670 final File newPatchDir = generateUniqueSchemePatchDir(changeList.DESCRIPTION, true);
671 final File newPath = getPatchFileInConfigDir(newPatchDir);
673 FileUtil.copy(new File(changeList.PATH), newPath);
675 catch (IOException e) {
676 // do not delete if cannot recycle
679 final ShelvedChangeList listCopy = new ShelvedChangeList(newPath.getAbsolutePath(), changeList.DESCRIPTION,
680 new ArrayList<ShelvedBinaryFile>(changeList.getBinaryFiles()));
681 listCopy.setName(newPatchDir.getName());
682 listCopy.DATE = changeList.DATE == null ? null : new Date(changeList.DATE.getTime());
684 writePatchesToFile(myProject, changeList.PATH, remainingPatches, commitContext);
686 changeList.getBinaryFiles().retainAll(remainingBinaries);
687 changeList.clearLoadedChanges();
688 recycleChangeList(listCopy, changeList);
689 // all newly create ShelvedChangeList have to be added to SchemesManger as new scheme
690 mySchemeManager.addNewScheme(listCopy, false);
691 notifyStateChanged();
694 public void restoreList(@NotNull final ShelvedChangeList changeList) {
695 ShelvedChangeList list = mySchemeManager.findSchemeByName(changeList.getName());
697 list.setRecycled(false);
699 notifyStateChanged();
702 public List<ShelvedChangeList> getRecycledShelvedChangeLists() {
703 return getRecycled(true);
706 public void clearRecycled() {
707 for (ShelvedChangeList list : getRecycledShelvedChangeLists()) {
708 deleteListImpl(list);
709 mySchemeManager.removeScheme(list);
711 notifyStateChanged();
714 private void recycleChangeList(@NotNull final ShelvedChangeList listCopy, @Nullable final ShelvedChangeList newList) {
715 if (newList != null) {
716 for (Iterator<ShelvedBinaryFile> shelvedChangeListIterator = listCopy.getBinaryFiles().iterator();
717 shelvedChangeListIterator.hasNext(); ) {
718 final ShelvedBinaryFile binaryFile = shelvedChangeListIterator.next();
719 for (ShelvedBinaryFile newBinary : newList.getBinaryFiles()) {
720 if (Comparing.equal(newBinary.BEFORE_PATH, binaryFile.BEFORE_PATH)
721 && Comparing.equal(newBinary.AFTER_PATH, binaryFile.AFTER_PATH)) {
722 shelvedChangeListIterator.remove();
726 for (Iterator<ShelvedChange> iterator = listCopy.getChanges(myProject).iterator(); iterator.hasNext(); ) {
727 final ShelvedChange change = iterator.next();
728 for (ShelvedChange newChange : newList.getChanges(myProject)) {
729 if (Comparing.equal(change.getBeforePath(), newChange.getBeforePath()) &&
730 Comparing.equal(change.getAfterPath(), newChange.getAfterPath())) {
736 // needed only if partial unshelve
738 final CommitContext commitContext = new CommitContext();
739 final List<FilePatch> patches = new ArrayList<FilePatch>();
740 for (ShelvedChange change : listCopy.getChanges(myProject)) {
741 patches.add(change.loadFilePatch(myProject, commitContext));
743 writePatchesToFile(myProject, listCopy.PATH, patches, commitContext);
745 catch (IOException e) {
749 catch (PatchSyntaxException e) {
755 if (!listCopy.getBinaryFiles().isEmpty() || !listCopy.getChanges(myProject).isEmpty()) {
756 listCopy.setRecycled(true);
757 notifyStateChanged();
761 private void recycleChangeList(@NotNull final ShelvedChangeList changeList) {
762 recycleChangeList(changeList, null);
763 notifyStateChanged();
766 public void deleteChangeList(@NotNull final ShelvedChangeList changeList) {
767 deleteListImpl(changeList);
768 mySchemeManager.removeScheme(changeList);
769 notifyStateChanged();
772 private void deleteListImpl(@NotNull final ShelvedChangeList changeList) {
773 FileUtil.delete(new File(myFileProcessor.getBaseDir(), changeList.getName()));
776 public void renameChangeList(final ShelvedChangeList changeList, final String newName) {
777 changeList.DESCRIPTION = newName;
778 notifyStateChanged();
782 public static List<TextFilePatch> loadPatches(Project project,
783 final String patchPath,
784 CommitContext commitContext) throws IOException, PatchSyntaxException {
785 return loadPatches(project, patchPath, commitContext, true);
789 static List<? extends FilePatch> loadPatchesWithoutContent(Project project,
790 final String patchPath,
791 CommitContext commitContext) throws IOException, PatchSyntaxException {
792 return loadPatches(project, patchPath, commitContext, false);
795 private static List<TextFilePatch> loadPatches(Project project,
796 final String patchPath,
797 CommitContext commitContext,
798 boolean loadContent) throws IOException, PatchSyntaxException {
799 char[] text = FileUtil.loadFileText(new File(patchPath), CharsetToolkit.UTF8);
800 PatchReader reader = new PatchReader(new CharArrayCharSequence(text), loadContent);
801 final List<TextFilePatch> textFilePatches = reader.readAllPatches();
802 final TransparentlyFailedValueI<Map<String, Map<String, CharSequence>>, PatchSyntaxException> additionalInfo = reader.getAdditionalInfo(
804 ApplyPatchDefaultExecutor.applyAdditionalInfoBefore(project, additionalInfo, commitContext);
805 return textFilePatches;
808 public static class ShelvedBinaryFilePatch extends FilePatch {
809 private final ShelvedBinaryFile myShelvedBinaryFile;
811 public ShelvedBinaryFilePatch(final ShelvedBinaryFile shelvedBinaryFile) {
812 myShelvedBinaryFile = shelvedBinaryFile;
813 setBeforeName(myShelvedBinaryFile.BEFORE_PATH);
814 setAfterName(myShelvedBinaryFile.AFTER_PATH);
817 public static ShelvedBinaryFilePatch patchCopy(@NotNull final ShelvedBinaryFilePatch patch) {
818 return new ShelvedBinaryFilePatch(patch.getShelvedBinaryFile());
822 public String getBeforeFileName() {
823 return getFileName(myShelvedBinaryFile.BEFORE_PATH);
827 public String getAfterFileName() {
828 return getFileName(myShelvedBinaryFile.AFTER_PATH);
832 private static String getFileName(String filePath) {
833 return filePath != null ? PathUtil.getFileName(filePath) : null;
837 public boolean isNewFile() {
838 return myShelvedBinaryFile.BEFORE_PATH == null;
842 public boolean isDeletedFile() {
843 return myShelvedBinaryFile.AFTER_PATH == null;
846 public ShelvedBinaryFile getShelvedBinaryFile() {
847 return myShelvedBinaryFile;
851 public boolean isShowRecycled() {
852 return myShowRecycled;
855 public void setShowRecycled(final boolean showRecycled) {
856 myShowRecycled = showRecycled;
857 notifyStateChanged();