From bc141bfc74c4e9c25794afe4cec929e36a1376f1 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 23 Jan 2012 20:42:33 +0100 Subject: [PATCH] do everything possible to cache the unchanged file content to revert in p4 offline mode (IDEA-79768) --- .../vcs/impl/ContentRevisionCache.java | 6 +- .../vcs/changes/ChangeListManagerImpl.java | 70 +++++++++ .../openapi/vcs/changes/ChangeListWorker.java | 24 +-- .../changes/LastUnchangedContentTracker.java | 141 ++++++++++++++++++ 4 files changed, 218 insertions(+), 23 deletions(-) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LastUnchangedContentTracker.java diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/impl/ContentRevisionCache.java b/platform/vcs-api/src/com/intellij/openapi/vcs/impl/ContentRevisionCache.java index 3059bada685c..6f5c86c0a7d6 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/impl/ContentRevisionCache.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/impl/ContentRevisionCache.java @@ -121,6 +121,7 @@ public class ContentRevisionCache { } } + @Nullable public static String getOrLoadAsString(Project project, FilePath file, VcsRevisionNumber number, @@ -135,7 +136,7 @@ public class ContentRevisionCache { return CharsetToolkit.bytesToString(bytes, charset); } - private static String bytesToString(FilePath path, byte[] bytes) { + private static String bytesToString(FilePath path, @NotNull byte[] bytes) { Charset charset = null; if (path.getVirtualFile() != null) { charset = path.getVirtualFile().getCharset(); @@ -183,10 +184,13 @@ public class ContentRevisionCache { return bytes; } + @Nullable public static String getOrLoadAsString(final Project project, FilePath path, VcsRevisionNumber number, @NotNull VcsKey vcsKey, @NotNull UniqueType type, final Throwable2Computable loader) throws VcsException, IOException { byte[] bytes = getOrLoadAsBytes(project, path, number, vcsKey, type, loader); + if (bytes == null) return null; + return bytesToString(path, bytes); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java index 2973bd38895a..6d2c63326af6 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java @@ -47,6 +47,7 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.EditorNotifications; import com.intellij.util.*; +import com.intellij.util.concurrency.Semaphore; import com.intellij.util.containers.MultiMap; import com.intellij.util.continuation.ContinuationPause; import com.intellij.util.messages.Topic; @@ -447,6 +448,11 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec } } }); + + for (VcsDirtyScope scope : scopes) { + updateLastUnchangedContents(scope); + } + myChangesViewManager.scheduleRefresh(); } catch (DisposedException e) { @@ -620,6 +626,55 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec } } + private void updateLastUnchangedContents(VcsDirtyScope scope) { + scope.iterateExistingInsideScope(new Processor() { + @Override + public boolean process(VirtualFile file) { + if (!file.isDirectory() && getStatus(file) == FileStatus.NOT_CHANGED) { + LastUnchangedContentTracker.updateLastUnchangedContent(file); + } + + return true; + } + }); + } + + public void collectUnchangedFileContents(@Nullable ProgressIndicator indicator) { + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); + for (VcsDirectoryMapping mapping : vcsManager.getDirectoryMappings()) { + final AbstractVcs vcs = vcsManager.findVcsByName(mapping.getVcs()); + final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(mapping.getDirectory()); + if (vcs != null && file != null) { + if (indicator != null) { + indicator.setText(mapping.getDirectory()); + } + + final VcsDirtyScopeImpl scope = new VcsDirtyScopeImpl(vcs, myProject); + scope.addDirtyDirRecursively(new FilePathImpl(file)); + updateLastUnchangedContents(scope); + } + } + + for (LocalChangeList list : getChangeListsCopy()) { + for (Change change : list.getChanges()) { + final VirtualFile file = change.getVirtualFile(); + final ContentRevision before = change.getBeforeRevision(); + if (file != null && before != null && !LastUnchangedContentTracker.hasSavedContent(file)) { + try { + final String content = before.getContent(); + if (content != null) { + LastUnchangedContentTracker.forceSavedContent(file, content); + } + } + catch (VcsException e) { + LOG.info(e); + } + } + + } + } + } + private void checkIfDisposed() { if (myUpdater.isStopped()) throw new DisposedException(); } @@ -1292,6 +1347,20 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec @TestOnly public void waitUntilRefreshed() { myUpdater.waitUntilRefreshed(); + waitUpdateAlarm(); + } + + // this is for perforce tests to ensure that LastSuccessfulUpdateTracker receives the event it needs + private static void waitUpdateAlarm() { + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + ourUpdateAlarm.execute(new Runnable() { + @Override + public void run() { + semaphore.up(); + } + }); + semaphore.waitFor(); } /** @@ -1301,6 +1370,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec final EnsureUpToDateFromNonAWTThread worker = new EnsureUpToDateFromNonAWTThread(myProject); worker.execute(); myUpdater.waitUntilRefreshed(); + waitUpdateAlarm(); return worker.isDone(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListWorker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListWorker.java index 2d99fd1b1354..a5fbc72c29b9 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListWorker.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListWorker.java @@ -98,29 +98,10 @@ public class ChangeListWorker implements ChangeListsWriteOperations { } public void onAfterWorkerSwitch(@NotNull final ChangeListWorker previous) { - final boolean somethingChanged = myDelta.step(previous.myIdx, myIdx); - checkForMultipleCopiesNotMove(); + checkForMultipleCopiesNotMove(myDelta.step(previous.myIdx, myIdx)); } - public boolean takeData(@NotNull final ChangeListWorker worker) { - myMap.clear(); - myMap.putAll(worker.myMap); - myDefault = worker.myDefault; - - myListsToDisappear.clear(); - myListsToDisappear.addAll(worker.myListsToDisappear); - - final boolean somethingChanged = myDelta.step(myIdx, worker.myIdx); - myIdx = new ChangeListsIndexes(worker.myIdx); - checkForMultipleCopiesNotMove(); - - myLocallyDeleted.takeFrom(worker.myLocallyDeleted); - mySwitchedHolder.takeFrom(worker.mySwitchedHolder); - - return somethingChanged; - } - - private void checkForMultipleCopiesNotMove() { + private void checkForMultipleCopiesNotMove(boolean somethingChanged) { final MultiMap> moves = new MultiMap>() { protected Collection> createCollection() { return new LinkedList>(); @@ -139,7 +120,6 @@ public class ChangeListWorker implements ChangeListsWriteOperations { } } } - boolean somethingChanged = false; for (FilePath filePath : moves.keySet()) { final List> copies = (List>) moves.get(filePath); if (copies.size() == 1) continue; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LastUnchangedContentTracker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LastUnchangedContentTracker.java new file mode 100644 index 000000000000..f94da588ca6b --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/LastUnchangedContentTracker.java @@ -0,0 +1,141 @@ +/* + * Copyright 2000-2012 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. + */ +package com.intellij.openapi.vcs.changes; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.FileAttribute; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * @author peter + */ +public class LastUnchangedContentTracker { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.LastUnchangedContentTracker"); + private static final Key LAST_TS_KEY = Key.create("LAST_TS_KEY"); + private static final FileAttribute LAST_TS_ATTR = new FileAttribute("LAST_TS_ATTR", 0, true); + private static final FileAttribute ACQUIRED_CONTENT_ATTR = new FileAttribute("ACQUIRED_CONTENT_ATTR", 0, false); + + public static void updateLastUnchangedContent(@NotNull VirtualFile file) { + Long lastTs = getLastSavedStamp(file); + final long stamp = file.getModificationStamp(); + if (lastTs != null && stamp == lastTs) { + return; + } + + Integer oldContentId = getSavedContentId(file); + if (oldContentId != null) { + getFS().releaseContent(oldContentId); + } + + saveContentReference(file, getFS().acquireContent(file)); + } + + @Nullable + public static byte[] getLastUnchangedContent(@NotNull VirtualFile file) { + final Integer id = getSavedContentId(file); + try { + return id == null ? null : getFS().contentsToByteArray(id); + } + catch (IOException e) { + LOG.info(e); + } + return null; + } + + private static PersistentFS getFS() { + return (PersistentFS)ManagingFS.getInstance(); + } + + private static void saveContentReference(VirtualFile file, int contentId) { + if (ChangeListManagerImpl.DEBUG) { + System.out.println("LastUnchangedContentTracker.saveCurrentContent"); + try { + System.out.println("content = " + VfsUtil.loadText(file)); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + long stamp = file.getModificationStamp(); + try { + final DataOutputStream contentStream = ACQUIRED_CONTENT_ATTR.writeAttribute(file); + contentStream.writeInt(contentId); + contentStream.close(); + + final DataOutputStream tsStream = LAST_TS_ATTR.writeAttribute(file); + tsStream.writeLong(stamp); + tsStream.close(); + + file.putUserData(LAST_TS_KEY, stamp); + } + catch (IOException e) { + LOG.info(e); + } + } + + public static boolean hasSavedContent(VirtualFile file) { + return getSavedContentId(file) != null; + } + + public static void forceSavedContent(VirtualFile file, @NotNull String content) { + saveContentReference(file, getFS().storeUnlinkedContent(content.getBytes(file.getCharset()))); + } + + @Nullable + private static Integer getSavedContentId(VirtualFile file) { + Integer oldContentId = null; + try { + final DataInputStream stream = ACQUIRED_CONTENT_ATTR.readAttribute(file); + if (stream != null) { + oldContentId = stream.readInt(); + stream.close(); + } + } + catch (IOException e) { + LOG.info(e); + } + return oldContentId; + } + + @Nullable + private static Long getLastSavedStamp(VirtualFile file) { + Long l = file.getUserData(LAST_TS_KEY); + if (l == null) { + try { + final DataInputStream stream = LAST_TS_ATTR.readAttribute(file); + if (stream != null) { + l = stream.readLong(); + stream.close(); + } + } + catch (IOException e) { + LOG.info(e); + } + } + return l; + } +}