[git tests] refresh correctly: with marking dirty
[idea/community.git] / plugins / git4idea / tests / git4idea / tests / GitChangeProviderTest.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package git4idea.tests;
17
18 import com.intellij.openapi.progress.EmptyProgressIndicator;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.vcs.*;
21 import com.intellij.openapi.vcs.changes.Change;
22 import com.intellij.openapi.vcs.changes.ChangeListManager;
23 import com.intellij.openapi.vcs.changes.ContentRevision;
24 import com.intellij.openapi.vcs.changes.VcsModifiableDirtyScope;
25 import com.intellij.openapi.vfs.VfsUtil;
26 import com.intellij.openapi.vfs.VfsUtilCore;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.testFramework.vcs.MockChangeListManagerGate;
29 import com.intellij.testFramework.vcs.MockChangelistBuilder;
30 import com.intellij.testFramework.vcs.MockDirtyScope;
31 import com.intellij.vcsUtil.VcsUtil;
32 import git4idea.GitVcs;
33 import git4idea.status.GitChangeProvider;
34 import git4idea.test.GitSingleRepoTest;
35 import git4idea.test.GitTestUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.io.File;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44
45 import static com.intellij.openapi.vcs.Executor.touch;
46 import static com.intellij.openapi.vcs.VcsTestUtil.*;
47 import static git4idea.test.GitExecutor.*;
48
49 /**
50  * Tests GitChangeProvider functionality. Scenario is the same for all tests:
51  * 1. Modifies files on disk (creates, edits, deletes, etc.)
52  * 2. Manually adds them to a dirty scope.
53  * 3. Calls ChangeProvider.getChanges() and checks that the changes are there.
54  */
55 public abstract class GitChangeProviderTest extends GitSingleRepoTest {
56
57   protected GitChangeProvider myChangeProvider;
58   protected VcsModifiableDirtyScope myDirtyScope;
59   protected VirtualFile myRootDir;
60   protected VirtualFile mySubDir;
61   protected GitVcs myVcs;
62
63   protected VirtualFile atxt;
64   protected VirtualFile btxt;
65   protected VirtualFile dir_ctxt;
66   protected VirtualFile subdir_dtxt;
67
68   @Override
69   protected void setUp() throws Exception {
70     super.setUp();
71     try {
72       initTest();
73     }
74     catch (Exception e) {
75       super.tearDown();
76       throw e;
77     }
78   }
79
80   private void initTest() {
81     myVcs = GitVcs.getInstance(myProject);
82     assertNotNull(myVcs);
83     myChangeProvider = (GitChangeProvider) myVcs.getChangeProvider();
84
85     GitTestUtil.createFileStructure(myProjectRoot, "a.txt", "b.txt", "dir/c.txt", "dir/subdir/d.txt");
86     addCommit("initial");
87
88     atxt = getVirtualFile("a.txt");
89     btxt = getVirtualFile("b.txt");
90     dir_ctxt = getVirtualFile("dir/c.txt");
91     subdir_dtxt = getVirtualFile("dir/subdir/d.txt");
92
93     myRootDir = myProjectRoot;
94     mySubDir = myRootDir.findChild("dir");
95
96     myDirtyScope = new MockDirtyScope(myProject, myVcs);
97
98     cd(myProjectPath);
99   }
100
101   @Override
102   protected boolean makeInitialCommit() {
103     return false;
104   }
105
106   @Nullable
107   private VirtualFile getVirtualFile(@NotNull String relativePath) {
108     return VfsUtil.findFileByIoFile(new File(myProjectPath, relativePath), true);
109   }
110
111   protected void modifyFileInBranches(String filename, FileAction masterAction, FileAction featureAction) throws Exception {
112     git("checkout -b feature");
113     performActionOnFileAndRecordToIndex(filename, "feature", featureAction);
114     commit("commit to feature");
115     checkout("master");
116     refresh();
117     performActionOnFileAndRecordToIndex(filename, "master", masterAction);
118     commit("commit to master");
119     git("merge feature", true);
120     refresh();
121   }
122
123   protected enum FileAction {
124     CREATE, MODIFY, DELETE, RENAME
125   }
126
127   private void performActionOnFileAndRecordToIndex(String filename, String branchName, FileAction action) throws Exception {
128     VirtualFile file = myRootDir.findChild(filename);
129     if (action != FileAction.CREATE) { // file doesn't exist yet
130       assertNotNull("VirtualFile is null: " + filename, file);
131     }
132     switch (action) {
133       case CREATE:
134         File f = touch(filename, "initial content in branch " + branchName);
135         final VirtualFile createdFile = VfsUtil.findFileByIoFile(f, true);
136         dirty(createdFile);
137         add(filename);
138         break;
139       case MODIFY:
140         //noinspection ConstantConditions
141         overwrite(VfsUtilCore.virtualToIoFile(file), "new content in branch " + branchName);
142         dirty(file);
143         add(filename);
144         break;
145       case DELETE:
146         dirty(file);
147         git("rm " + filename);
148         break;
149       case RENAME:
150         String newName = filename + "_" + branchName.replaceAll("\\s", "_") + "_new";
151         dirty(file);
152         mv(filename, newName);
153         myRootDir.refresh(false, true);
154         dirty(myRootDir.findChild(newName));
155         break;
156       default:
157         break;
158     }
159   }
160
161   /**
162    * Checks that the given files have respective statuses in the change list retrieved from myChangesProvider.
163    * Pass null in the fileStatuses array to indicate that proper file has not changed.
164    */
165   protected void assertChanges(VirtualFile[] virtualFiles, FileStatus[] fileStatuses) throws VcsException {
166     Map<FilePath, Change> result = getChanges(virtualFiles);
167     for (int i = 0; i < virtualFiles.length; i++) {
168       FilePath fp = VcsUtil.getFilePath(virtualFiles[i]);
169       FileStatus status = fileStatuses[i];
170       if (status == null) {
171         assertFalse("File [" + tos(fp) + " shouldn't be in the changelist, but it was.", result.containsKey(fp));
172         continue;
173       }
174       assertTrue("File [" + tos(fp) + "] didn't change. Changes: " + tos(result), result.containsKey(fp));
175       assertEquals("File statuses don't match for file [" + tos(fp) + "]", result.get(fp).getFileStatus(), status);
176     }
177   }
178
179   protected void assertChanges(VirtualFile virtualFile, FileStatus fileStatus) throws VcsException {
180     assertChanges(new VirtualFile[] { virtualFile }, new FileStatus[] { fileStatus });
181   }
182
183   /**
184    * Marks the given files dirty in myDirtyScope, gets changes from myChangeProvider and groups the changes in the map.
185    * Assumes that only one change for a file has happened.
186    */
187   protected Map<FilePath, Change> getChanges(VirtualFile... changedFiles) throws VcsException {
188     final List<FilePath> changedPaths = ObjectsConvertor.vf2fp(Arrays.asList(changedFiles));
189
190     // get changes
191     MockChangelistBuilder builder = new MockChangelistBuilder();
192     myChangeProvider.getChanges(myDirtyScope, builder, new EmptyProgressIndicator(), new MockChangeListManagerGate(ChangeListManager.getInstance(myProject)));
193     List<Change> changes = builder.getChanges();
194
195     // get changes for files
196     final Map<FilePath, Change> result = new HashMap<FilePath, Change>();
197     for (Change change : changes) {
198       VirtualFile file = change.getVirtualFile();
199       FilePath filePath = null;
200       if (file == null) { // if a file was deleted, just find the reference in the original list of files and use it. 
201         String path = change.getBeforeRevision().getFile().getPath();
202         for (FilePath fp : changedPaths) {
203           if (FileUtil.pathsEqual(fp.getPath(), path)) {
204             filePath = fp;
205             break;
206           }
207         }
208       } else {
209         filePath = VcsUtil.getFilePath(file);
210       }
211       result.put(filePath, change);
212     }
213     return result;
214   }
215
216   protected VirtualFile create(VirtualFile parent, String name) {
217     return create(parent, name, false);
218   }
219
220   protected VirtualFile createDir(VirtualFile parent, String name) {
221     return create(parent, name, true);
222   }
223
224   private VirtualFile create(VirtualFile parent, String name, boolean dir) {
225     final VirtualFile file = dir ?
226                              VcsTestUtil.findOrCreateDir(myProject, parent, name) :
227                              createFile(myProject, parent, name, "content" + Math.random());
228     dirty(file);
229     return file;
230   }
231
232   protected void edit(VirtualFile file, String content) {
233     editFileInCommand(myProject, file, content);
234     dirty(file);
235   }
236
237   protected void moveFile(VirtualFile file, VirtualFile newParent) {
238     dirty(file);
239     moveFileInCommand(myProject, file, newParent);
240     dirty(file);
241   }
242
243   protected VirtualFile copy(VirtualFile file, VirtualFile newParent) {
244     dirty(file);
245     VirtualFile newFile = copyFileInCommand(myProject, file, newParent, file.getName());
246     dirty(newFile);
247     return newFile;
248   }
249
250   protected void deleteFile(VirtualFile file) {
251     dirty(file);
252     deleteFileInCommand(myProject, file);
253   }
254
255   private void dirty(VirtualFile file) {
256     myDirtyScope.addDirtyFile(VcsUtil.getFilePath(file));
257   }
258
259   protected String tos(FilePath fp) {
260     return FileUtil.getRelativePath(new File(myProjectPath), fp.getIOFile());
261   }
262
263   protected String tos(Change change) {
264     switch (change.getType()) {
265       case NEW: return "A: " + tos(change.getAfterRevision());
266       case DELETED: return "D: " + tos(change.getBeforeRevision());
267       case MOVED: return "M: " + tos(change.getBeforeRevision()) + " -> " + tos(change.getAfterRevision());
268       case MODIFICATION: return "M: " + tos(change.getAfterRevision());
269       default: return "~: " +  tos(change.getBeforeRevision()) + " -> " + tos(change.getAfterRevision());
270     }
271   }
272
273   protected String tos(ContentRevision revision) {
274     return tos(revision.getFile());
275   }
276
277   protected String tos(Map<FilePath, Change> changes) {
278     StringBuilder stringBuilder = new StringBuilder("[");
279     for (Change change : changes.values()) {
280       stringBuilder.append(tos(change)).append(", ");
281     }
282     stringBuilder.append("]");
283     return stringBuilder.toString();
284   }
285
286 }