src/net/sourceforge/transparent/Checkin/CCaseRollbackEnvironment.java (279 lines of code) (raw):
package net.sourceforge.transparent.Checkin;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
import com.intellij.openapi.vcs.rollback.RollbackEnvironment;
import com.intellij.openapi.vcs.rollback.RollbackProgressListener;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.vcsUtil.VcsUtil;
import net.sourceforge.transparent.Status;
import net.sourceforge.transparent.TransparentVcs;
import net.sourceforge.transparent.exceptions.ClearCaseException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import static com.intellij.util.containers.ContainerUtil.map;
import static com.intellij.util.containers.ContainerUtil.map2Array;
public class CCaseRollbackEnvironment implements RollbackEnvironment
{
@NonNls private static final String FILE_NOT_IN_VOB_SIG = "element name not found";
@NonNls private static final String UPDATE_SUCC_PREFIX_1 = "Processing dir";
@NonNls private static final String UPDATE_SUCC_PREFIX_2 = "Loading ";
@NonNls private static final String UPDATE_SUCC_PREFIX_3 = "End dir";
@NonNls private static final String UPDATE_SUCC_PREFIX_4 = "Done loading";
@NonNls private static final String UPDATE_SUCC_PREFIX_5 = "Log has been written";
@NonNls private static final String UPDATE_SUCC_PREFIX_6 = ".";
@NonNls private static final String UPDATE_SUCC_PREFIX_7= "Making dir";
private final Project project;
private final TransparentVcs host;
public CCaseRollbackEnvironment( Project project, TransparentVcs host )
{
this.project = project;
this.host = host;
}
public String getRollbackOperationName() {
return VcsBundle.message("changes.action.rollback.text");
}
public void rollbackChanges(List<Change> changes, final List<VcsException> errors, @NotNull final RollbackProgressListener listener)
{
HashSet<FilePath> processedFiles = new HashSet<>();
listener.determinate();
rollbackRenamedFolders( changes, processedFiles, listener);
rollbackNew( changes, processedFiles, listener);
rollbackDeleted( changes, processedFiles, errors, listener);
rollbackChanged( changes, processedFiles, errors, listener);
VfsUtil.markDirtyAndRefresh(true, true, false, map2Array(processedFiles, VirtualFile.class, FilePath::getVirtualFile));
VcsDirtyScopeManager.getInstance(project).filesDirty(map(processedFiles, FilePath::getVirtualFile), null);
}
private void rollbackRenamedFolders( List<Change> changes, HashSet<FilePath> processedFiles, @NotNull final RollbackProgressListener listener)
{
for( Change change : changes )
{
if( VcsUtil.isRenameChange( change ) && VcsUtil.isChangeForFolder( change ) )
{
listener.accept(change);
// The only thing which we can perform on this step is physical
// rename of the newFolderPath back to its former name, since we can't
// keep track of what consequent changes were done (due to Java
// semantics of the package rename).
FilePath newFolderPath = change.getAfterRevision().getFile();
File folderNew = newFolderPath.getIOFile();
FilePath oldFolderPath = change.getBeforeRevision().getFile();
File folderOld = oldFolderPath.getIOFile();
folderNew.renameTo( folderOld );
host.renamedFolders.remove( VcsUtil.getCanonicalLocalPath( folderNew.getPath() ) );
processedFiles.add( oldFolderPath );
LocalFileSystem.getInstance().refreshAndFindFileByPath(folderOld.getPath());
}
}
}
private void rollbackNew( List<Change> changes, HashSet<FilePath> processedFiles, @NotNull final RollbackProgressListener listener)
{
HashSet<FilePath> filesAndFolder = new HashSet<>();
collectNewChangesBack( changes, filesAndFolder, processedFiles );
VcsDirtyScopeManager mgr = VcsDirtyScopeManager.getInstance(project);
for( FilePath file : filesAndFolder )
{
listener.accept(file);
host.deleteNewFile( file.getVirtualFile() );
mgr.fileDirty( file );
}
}
/**
* For each accumulated (to be rolledback) folder - collect ALL files
* in the change lists with the status NEW (ADDED) which are UNDER this folder.
* This ensures that no file will be left in any change list with status NEW.
*/
private void collectNewChangesBack( List<Change> changes, HashSet<FilePath> newFilesAndfolders,
HashSet<FilePath> processedFiles )
{
HashSet<FilePath> foldersNew = new HashSet<>();
for( Change change : changes )
{
if( VcsUtil.isChangeForNew( change ) )
{
FilePath filePath = change.getAfterRevision().getFile();
if( !filePath.isDirectory() )
newFilesAndfolders.add( filePath );
else
foldersNew.add( filePath );
processedFiles.add( filePath );
}
}
ChangeListManager clMgr = ChangeListManager.getInstance(project);
FileStatusManager fsMgr = FileStatusManager.getInstance(project);
List<VirtualFile> allAffectedFiles = clMgr.getAffectedFiles();
for( VirtualFile file : allAffectedFiles )
{
FileStatus status = fsMgr.getStatus( file );
if( status == FileStatus.ADDED )
{
for( FilePath folder : foldersNew )
{
if( file.getPath().toLowerCase().startsWith( folder.getPath().toLowerCase() ))
{
FilePath path = clMgr.getChange( file ).getAfterRevision().getFile();
newFilesAndfolders.add( path );
}
}
}
}
newFilesAndfolders.addAll( foldersNew );
}
private void rollbackDeleted( List<Change> changes, HashSet<FilePath> processedFiles, List<VcsException> errors,
@NotNull final RollbackProgressListener listener)
{
for( Change change : changes )
{
if( VcsUtil.isChangeForDeleted( change ))
{
listener.accept(change);
FilePath filePath = change.getBeforeRevision().getFile();
rollbackMissingFileDeletion( filePath, errors );
processedFiles.add( filePath );
}
}
}
private void rollbackChanged( List<Change> changes, HashSet<FilePath> processedFiles, List<VcsException> errors,
@NotNull final RollbackProgressListener listener)
{
for( Change change : changes )
{
if( !VcsUtil.isChangeForNew( change ) &&
!VcsUtil.isChangeForDeleted(change))
{
final boolean isRenameChange = VcsUtil.isRenameChange(change);
if (VcsUtil.isChangeForFolder(change) && isRenameChange) {
final File wasFile = change.getBeforeRevision().getFile().getIOFile();
final Status status = host.getStatus(wasFile);
if (! (Status.CHECKED_OUT.equals(status) || Status.HIJACKED.equals(status))) {
continue;
}
}
FilePath filePath = change.getAfterRevision().getFile();
String path = filePath.getPath();
listener.accept(change);
if(isRenameChange)
{
// Track two different cases:
// - we delete the file which is already in the repository.
// Here we need to "Get" the latest version of the original
// file from the repository and delete the new file.
// - we delete the renamed file which is new and does not exist
// in the repository. We need to ignore the error message from
// the SourceSafe ("file not existing") and just delete the
// new file.
List<VcsException> localErrors = new ArrayList<>();
FilePath oldFile = change.getBeforeRevision().getFile();
host.undoCheckoutFile( oldFile.getIOFile(), localErrors );
if( localErrors.size() > 0 && !isUnknownFileError( localErrors ) ) {
errors.addAll( localErrors );
}
host.renamedFiles.remove( filePath.getPath() );
FileUtil.delete( new File( path ) );
}
else
{
final Status fileStatus = host.getStatusSafely(filePath.getIOFile());
if (! Status.HIJACKED.equals(fileStatus)) {
if (filePath.getVirtualFile() == null) {
host.undoCheckoutFile( filePath.getIOFile(), errors );
} else {
host.undoCheckoutFile( filePath.getVirtualFile(), errors );
}
} else {
updateFile( path, errors );
}
}
processedFiles.add( filePath );
}
}
}
public void rollbackMissingFileDeletion(List<FilePath> paths, final List<VcsException> exceptions, final RollbackProgressListener listener)
{
for( FilePath path : paths )
{
listener.accept(path);
rollbackMissingFileDeletion( path, exceptions );
}
}
private void rollbackMissingFileDeletion( FilePath path, List<VcsException> errors )
{
VcsDirtyScopeManager mgr = VcsDirtyScopeManager.getInstance( project );
String normPath = VcsUtil.getCanonicalLocalPath( path.getPath() );
if( host.isFolderRemoved( normPath ) || host.isFolderRemovedForVcs( normPath ) )
{
// For ClearCase to get back the locally removed folder, it is
// necessary to issue "Update" command. This will revert it to the
// state before the checking out on deletion.
updateFile( path.getPath(), errors );
// In the case we restoring the folder which IS NOT in the repository
// (e.g. it was inproperly put into the list of deleted or it was already
// removed from the VOB), it is not restored locally and no update is
// needed. BUT: since it is not created, its record in the hash of removed
// folders is not removed too - thus remove it explicitely here.
if( path.getIOFile().exists() )
{
// If that folder contained checked out files, then they are not
// reverted back after the parent folder is updated. For each of
// file we need to issue "undocheckout" command.
undocheckoutInFolder( path.getPath(), errors );
}
else
{
host.removeFolderFromDeleted( normPath );
}
}
else
{
// For ClearCase to get back the locally removed file:
// 1. Issue "Undo Checkout" command. This will revert it to the state
// before its checkout on deletion (if it was checked out previously).
// 2. Otherwise (we rollback the file which was not previusly checked
// out) perform "Update".
List<VcsException> localErrors = new ArrayList<>();
host.undoCheckoutFile( path.getIOFile(), localErrors );
if( localErrors.size() > 0 )
{
updateFile( path.getPath(), errors );
}
}
mgr.fileDirty( path );
}
private void undocheckoutInFolder( String path, List<VcsException> errors )
{
String output = TransparentVcs.cleartoolOnLocalPathWithOutput( path, "lsch", "-short", "-r" );
TransparentVcs.LOG.info( output );
String[] lines = LineTokenizer.tokenize( output, false );
for( String line : lines )
{
File file = new File( path, line );
host.undoCheckoutFile( file, errors );
}
}
public void rollbackModifiedWithoutCheckout(final List<VirtualFile> files, final List<VcsException> errors,
final RollbackProgressListener listener)
{
for( VirtualFile file : files )
{
listener.accept(file);
updateFile( file.getPath(), errors );
file.refresh( true, true );
}
}
public void rollbackIfUnchanged(VirtualFile file) {
}
private static void updateFile( String path, List<VcsException> errors )
{
try
{
String err = TransparentVcs.cleartoolWithOutput( "update", "-overwrite", "-force", path );
if( err != null )
{
String[] lines = LineTokenizer.tokenize( err, false );
for( String line : lines )
{
if( !lineStartsWithKnownPrefix( line ) )
{
VcsException e = new VcsException( line );
e.setIsWarning( true );
errors.add( e );
}
}
}
}
catch( ClearCaseException e ) { errors.add( new VcsException( e ) ); }
}
private static boolean isUnknownFileError( List<VcsException> errors )
{
for( VcsException exc : errors )
{
if( exc.getMessage().toLowerCase().indexOf( FILE_NOT_IN_VOB_SIG ) != -1 )
return true;
}
return false;
}
private static boolean lineStartsWithKnownPrefix( String line )
{
return line.startsWith( UPDATE_SUCC_PREFIX_1 ) || line.startsWith( UPDATE_SUCC_PREFIX_2 ) ||
line.startsWith( UPDATE_SUCC_PREFIX_3 ) || line.startsWith( UPDATE_SUCC_PREFIX_4 ) ||
line.startsWith( UPDATE_SUCC_PREFIX_5 ) || line.startsWith( UPDATE_SUCC_PREFIX_6 ) ||
line.startsWith( UPDATE_SUCC_PREFIX_7 );
}
}