blob: e255598e20468925bbe3664a6da21e79ea882841 [file] [log] [blame]
/*
* Copyright 2000-2009 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 git4idea.rollback;
import com.intellij.lifecycle.PeriodicalTasksCloser;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ContentRevision;
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.VirtualFile;
import com.intellij.vcsUtil.VcsFileUtil;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.GitUtil;
import git4idea.commands.GitCommand;
import git4idea.commands.GitHandlerUtil;
import git4idea.commands.GitSimpleHandler;
import git4idea.i18n.GitBundle;
import git4idea.repo.GitRepository;
import git4idea.repo.GitUntrackedFilesHolder;
import git4idea.util.GitFileUtils;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.*;
/**
* Git rollback/revert environment
*/
public class GitRollbackEnvironment implements RollbackEnvironment {
private final Project myProject;
public GitRollbackEnvironment(@NotNull Project project) {
myProject = project;
}
@NotNull
public String getRollbackOperationName() {
return GitBundle.getString("revert.action.name");
}
public void rollbackModifiedWithoutCheckout(@NotNull List<VirtualFile> files,
final List<VcsException> exceptions,
final RollbackProgressListener listener) {
throw new UnsupportedOperationException("Explicit file checkout is not supported by GIT.");
}
public void rollbackMissingFileDeletion(@NotNull List<FilePath> files,
final List<VcsException> exceptions,
final RollbackProgressListener listener) {
throw new UnsupportedOperationException("Missing file delete is not reported by GIT.");
}
public void rollbackIfUnchanged(@NotNull VirtualFile file) {
// do nothing
}
public void rollbackChanges(@NotNull List<Change> changes,
final List<VcsException> exceptions,
@NotNull final RollbackProgressListener listener) {
HashMap<VirtualFile, List<FilePath>> toUnindex = new HashMap<VirtualFile, List<FilePath>>();
HashMap<VirtualFile, List<FilePath>> toUnversion = new HashMap<VirtualFile, List<FilePath>>();
HashMap<VirtualFile, List<FilePath>> toRevert = new HashMap<VirtualFile, List<FilePath>>();
List<FilePath> toDelete = new ArrayList<FilePath>();
listener.determinate();
// collect changes to revert
for (Change c : changes) {
switch (c.getType()) {
case NEW:
// note that this the only change that could happen
// for HEAD-less working directories.
registerFile(toUnversion, c.getAfterRevision().getFile(), exceptions);
break;
case MOVED:
registerFile(toRevert, c.getBeforeRevision().getFile(), exceptions);
registerFile(toUnindex, c.getAfterRevision().getFile(), exceptions);
toDelete.add(c.getAfterRevision().getFile());
break;
case MODIFICATION:
// note that changes are also removed from index, if they got into index somehow
registerFile(toUnindex, c.getBeforeRevision().getFile(), exceptions);
registerFile(toRevert, c.getBeforeRevision().getFile(), exceptions);
break;
case DELETED:
registerFile(toRevert, c.getBeforeRevision().getFile(), exceptions);
break;
}
}
// unindex files
for (Map.Entry<VirtualFile, List<FilePath>> entry : toUnindex.entrySet()) {
listener.accept(entry.getValue());
try {
unindex(entry.getKey(), entry.getValue(), false);
}
catch (VcsException e) {
exceptions.add(e);
}
}
// unversion files
for (Map.Entry<VirtualFile, List<FilePath>> entry : toUnversion.entrySet()) {
listener.accept(entry.getValue());
try {
unindex(entry.getKey(), entry.getValue(), true);
}
catch (VcsException e) {
exceptions.add(e);
}
}
// delete files
for (FilePath file : toDelete) {
listener.accept(file);
try {
final File ioFile = file.getIOFile();
if (ioFile.exists()) {
if (!ioFile.delete()) {
//noinspection ThrowableInstanceNeverThrown
exceptions.add(new VcsException("Unable to delete file: " + file));
}
}
}
catch (Exception e) {
//noinspection ThrowableInstanceNeverThrown
exceptions.add(new VcsException("Unable to delete file: " + file, e));
}
}
// revert files from HEAD
GitUtil.workingTreeChangeStarted(myProject);
try {
for (Map.Entry<VirtualFile, List<FilePath>> entry : toRevert.entrySet()) {
listener.accept(entry.getValue());
try {
revert(entry.getKey(), entry.getValue());
}
catch (VcsException e) {
exceptions.add(e);
}
}
}
finally {
GitUtil.workingTreeChangeFinished(myProject);
}
LocalFileSystem lfs = LocalFileSystem.getInstance();
HashSet<File> filesToRefresh = new HashSet<File>();
for (Change c : changes) {
ContentRevision before = c.getBeforeRevision();
if (before != null) {
filesToRefresh.add(new File(before.getFile().getPath()));
}
ContentRevision after = c.getAfterRevision();
if (after != null) {
filesToRefresh.add(new File(after.getFile().getPath()));
}
}
lfs.refreshIoFiles(filesToRefresh);
for (GitRepository repo : GitUtil.getRepositoryManager(myProject).getRepositories()) {
repo.update();
}
}
/**
* Reverts the list of files we are passed.
*
* @param root the VCS root
* @param files The array of files to revert.
* @throws VcsException Id it breaks.
*/
public void revert(final VirtualFile root, final List<FilePath> files) throws VcsException {
for (List<String> paths : VcsFileUtil.chunkPaths(root, files)) {
GitSimpleHandler handler = new GitSimpleHandler(myProject, root, GitCommand.CHECKOUT);
handler.addParameters("HEAD");
handler.endOptions();
handler.addParameters(paths);
handler.run();
}
}
/**
* Remove file paths from index (git remove --cached).
*
* @param root a git root
* @param files files to remove from index.
* @param toUnversioned passed true if the file will be unversioned after unindexing, i.e. it was added before the revert operation.
* @throws VcsException if there is a problem with running git
*/
private void unindex(final VirtualFile root, final List<FilePath> files, boolean toUnversioned) throws VcsException {
GitFileUtils.delete(myProject, root, files, "--cached", "-f");
if (toUnversioned) {
final GitRepository repo = GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(root);
final GitUntrackedFilesHolder untrackedFilesHolder = (repo == null ? null : repo.getUntrackedFilesHolder());
for (FilePath path : files) {
final VirtualFile vf = VcsUtil.getVirtualFile(path.getIOFile());
if (untrackedFilesHolder != null && vf != null) {
untrackedFilesHolder.add(vf);
}
}
}
}
/**
* Register file in the map under appropriate root
*
* @param files a map to use
* @param file a file to register
* @param exceptions the list of exceptions to update
*/
private static void registerFile(Map<VirtualFile, List<FilePath>> files, FilePath file, List<VcsException> exceptions) {
final VirtualFile root;
try {
root = GitUtil.getGitRoot(file);
}
catch (VcsException e) {
exceptions.add(e);
return;
}
List<FilePath> paths = files.get(root);
if (paths == null) {
paths = new ArrayList<FilePath>();
files.put(root, paths);
}
paths.add(file);
}
/**
* Get instance of the service
*
* @param project a context project
* @return a project-specific instance of the service
*/
public static GitRollbackEnvironment getInstance(final Project project) {
return PeriodicalTasksCloser.getInstance().safeGetService(project, GitRollbackEnvironment.class);
}
public static void resetHardLocal(final Project project, final VirtualFile root) {
GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.RESET);
handler.addParameters("--hard");
handler.endOptions();
GitHandlerUtil.runInCurrentThread(handler, null);
}
}