e89f1b3ba74649777ba5142f82d8f5cb1e577a09
[idea/community.git] / plugins / git4idea / tests / git4idea / tests / GitChangeProviderTest.java
1 /*
2  * Copyright 2000-2010 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.application.ApplicationManager;
19 import com.intellij.openapi.progress.EmptyProgressIndicator;
20 import com.intellij.openapi.util.io.FileUtil;
21 import com.intellij.openapi.vcs.*;
22 import com.intellij.openapi.vcs.changes.*;
23 import com.intellij.openapi.vcs.changes.pending.MockChangeListManagerGate;
24 import com.intellij.openapi.vfs.VirtualFile;
25 import com.intellij.testFramework.vcs.MockChangelistBuilder;
26 import com.intellij.ui.GuiUtils;
27 import git4idea.GitVcs;
28 import git4idea.changes.GitChangeProvider;
29 import org.testng.annotations.BeforeMethod;
30 import org.testng.annotations.Test;
31
32 import java.io.File;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37
38 import static com.intellij.openapi.vcs.FileStatus.*;
39 import static org.testng.Assert.*;
40
41 /**
42  * Tests GitChangeProvider functionality. Scenario is the same for all tests:
43  * 1. Modifies files on disk (creates, edits, deletes, etc.)
44  * 2. Manually adds them to a dirty scope (better to use VcsDirtyScopeManagerImpl, but it's too asynchronous - couldn't overcome this for now.
45  * 3. Calls ChangeProvider.getChanges() and checks that the changes are there.
46  * @author Kirill Likhodedov
47  */
48 public class GitChangeProviderTest extends GitSingleUserTest {
49
50   private GitChangeProvider myChangeProvider;
51   private VcsModifiableDirtyScope myDirtyScope;
52   private Map<String, VirtualFile> myFiles;
53   private VirtualFile afile;
54
55   @BeforeMethod
56   @Override
57   protected void setUp() throws Exception {
58     super.setUp();
59     myChangeProvider = (GitChangeProvider) GitVcs.getInstance(myProject).getChangeProvider();
60     myDirtyScope = new VcsDirtyScopeImpl(GitVcs.getInstance(myProject), myProject);
61
62     myFiles = GitTestUtil.createFileStructure(myProject, myRepo, "a.txt", "b.txt", "dir/c.txt", "dir/subdir/d.txt");
63     afile = myFiles.get("a.txt"); // the file is commonly used, so save it in a field.
64     myRepo.commit();
65   }
66
67   @Test
68   public void testCreateFile() throws Exception {
69     VirtualFile bfile = myRepo.createFile("new.txt");
70     assertChanges(bfile, ADDED);
71   }
72
73   @Test
74   public void testCreateFileInDir() throws Exception {
75     VirtualFile dir = createDirInCommand(myRepo.getDir(), "newdir");
76     VirtualFile bfile = createFileInCommand(dir, "new.txt", "initial b");
77     assertChanges(new VirtualFile[] {bfile, dir}, new FileStatus[] { ADDED, null} );
78   }
79
80   @Test
81   public void testEditFile() throws Exception {
82     editFileInCommand(afile, "new content");
83     assertChanges(afile, MODIFIED);
84   }
85
86   @Test
87   public void testDeleteFile() throws Exception {
88     deleteFileInCommand(afile);
89     assertChanges(afile, DELETED);
90   }
91
92   @Test
93   public void testDeleteDirRecursively() throws Exception {
94     GuiUtils.runOrInvokeAndWait(new Runnable() {
95       @Override
96       public void run() {
97         ApplicationManager.getApplication().runWriteAction(new Runnable() {
98           @Override
99           public void run() {
100             FileUtil.delete(new File(myRepo.getDir().getPath(), "dir"));
101           }
102         });
103       }
104     });
105     assertChanges(new VirtualFile[] { myFiles.get("dir/c.txt"), myFiles.get("dir/subdir/d.txt") }, new FileStatus[] { DELETED, DELETED });
106   }
107
108   @Test
109   public void testSimultaneousOperationsOnMultipleFiles() throws Exception {
110     VirtualFile dfile = myFiles.get("dir/subdir/d.txt");
111     VirtualFile cfile = myFiles.get("dir/c.txt");
112
113     editFileInCommand(afile, "new content");
114     editFileInCommand(cfile, "new content");
115     deleteFileInCommand(dfile);
116     VirtualFile newfile = createFileInCommand("newfile.txt", "new content");
117
118     assertChanges(new VirtualFile[] {afile, cfile, dfile, newfile}, new FileStatus[] {MODIFIED, MODIFIED, DELETED, ADDED});
119   }
120
121   /**
122    * "modify-modify" merge conflict.
123    * 1. Create a file and commit it.
124    * 2. Create new branch and switch to it.
125    * 3. Edit the file in that branch and commit.
126    * 4. Switch to master, conflictly edit the file and commit.
127    * 5. Merge the branch on master.
128    * Merge conflict "modify-modify" happens.
129    */
130   @Test
131   public void testConflictMM() throws Exception {
132     modifyFileInBranches("a.txt", FileAction.MODIFY, FileAction.MODIFY);
133     assertChanges(afile, FileStatus.MERGED_WITH_CONFLICTS);
134   }
135
136   /**
137    * Modify-Delete conflict.
138    */
139   @Test
140   public void testConflictMD() throws Exception {
141     modifyFileInBranches("a.txt", FileAction.MODIFY, FileAction.DELETE);
142     assertChanges(afile, FileStatus.MERGED_WITH_CONFLICTS);
143   }
144
145   /**
146    * Delete-Modify conflict.
147    */
148   @Test
149   public void testConflictDM() throws Exception {
150     modifyFileInBranches("a.txt", FileAction.DELETE, FileAction.MODIFY);
151     assertChanges(afile, FileStatus.MERGED_WITH_CONFLICTS);
152   }
153
154   /**
155    * Create a file with conflicting content.
156    */
157   @Test
158   public void testConflictCC() throws Exception {
159     modifyFileInBranches("z.txt", FileAction.CREATE, FileAction.CREATE);
160     VirtualFile zfile = myRepo.getDir().findChild("z.txt");
161     assertChanges(zfile, FileStatus.MERGED_WITH_CONFLICTS);
162   }
163
164   @Test
165   public void testConflictRD() throws Exception {
166     modifyFileInBranches("a.txt", FileAction.RENAME, FileAction.DELETE);
167     VirtualFile newfile = myRepo.getDir().findChild("a.txt_master_new"); // renamed in master
168     assertChanges(newfile, FileStatus.MERGED_WITH_CONFLICTS);
169   }
170
171   @Test
172   public void testConflictDR() throws Exception {
173     modifyFileInBranches("a.txt", FileAction.DELETE, FileAction.RENAME);
174     VirtualFile newFile = myRepo.getDir().findChild("a.txt_feature_new"); // deleted in master, renamed in feature
175     assertChanges(newFile, FileStatus.MERGED_WITH_CONFLICTS);
176   }
177
178   private void modifyFileInBranches(String filename, FileAction masterAction, FileAction featureAction) throws Exception {
179     myRepo.createBranch("feature");
180     performActionOnFileAndRecordToIndex(filename, "feature", featureAction);
181     myRepo.commit();
182     myRepo.checkout("master");
183     performActionOnFileAndRecordToIndex(filename, "master", masterAction);
184     myRepo.commit();
185     myRepo.merge("feature");
186     myRepo.refresh();
187   }
188
189   private enum FileAction {
190     CREATE, MODIFY, DELETE, RENAME
191   }
192
193   private void performActionOnFileAndRecordToIndex(String filename, String branchName, FileAction action) throws Exception {
194     VirtualFile file = myRepo.getDir().findChild(filename);
195     switch (action) {
196       case CREATE:
197         createFileInCommand(filename, "initial content in branch " + branchName);
198         myRepo.add(filename);
199         break;
200       case MODIFY:
201         editFileInCommand(file, "new content in branch " + branchName);
202         myRepo.add(filename);
203         break;
204       case DELETE:
205         myRepo.rm(filename);
206         break;
207       case RENAME:
208         String name = filename + "_" + branchName.replaceAll("\\s", "_") + "_new";
209         myRepo.mv(filename, name);
210         break;
211       default:
212         break;
213     }
214   }
215
216   /**
217    * Checks that the given files have respective statuses in the change list retrieved from myChangesProvider.
218    * Pass null in the fileStatuses array to indicate that proper file has not changed.
219    */
220   private void assertChanges(VirtualFile[] virtualFiles, FileStatus[] fileStatuses) throws VcsException {
221     Map<FilePath, Change> result = getChanges(virtualFiles);
222     for (int i = 0; i < virtualFiles.length; i++) {
223       FilePath fp = new FilePathImpl(virtualFiles[i]);
224       FileStatus status = fileStatuses[i];
225       if (status == null) {
226         assertFalse(result.containsKey(fp), "File [" + fp + " shouldn't be in the change list, but it was.");
227         continue;
228       }
229       assertTrue(result.containsKey(fp), "File [" + fp + "] didn't change. Changes: " + result);
230       assertEquals(result.get(fp).getFileStatus(), status, "File statuses don't match for file [" + fp + "]");
231     }
232   }
233
234   private void assertChanges(VirtualFile virtualFile, FileStatus fileStatus) throws VcsException {
235     assertChanges(new VirtualFile[] { virtualFile }, new FileStatus[] { fileStatus });
236   }
237
238   /**
239    * Marks the given files dirty in myDirtyScope, gets changes from myChangeProvider and groups the changes in the map.
240    * Assumes that only one change for a file happened.
241    */
242   private Map<FilePath, Change> getChanges(VirtualFile... changedFiles) throws VcsException {
243     final List<FilePath> changedPaths = ObjectsConvertor.vf2fp(Arrays.asList(changedFiles));
244
245     // populate dirty scope
246     //for (FilePath path : changedPaths) {
247     //  myDirtyScope.addDirtyFile(path);
248     //}
249     VcsDirtyScopeManagerImpl.getInstance(myProject).markEverythingDirty();
250     myDirtyScope.addDirtyDirRecursively(new FilePathImpl(myRepo.getDir()));
251
252     // get changes
253     MockChangelistBuilder builder = new MockChangelistBuilder();
254     myChangeProvider.getChanges(myDirtyScope, builder, new EmptyProgressIndicator(), new MockChangeListManagerGate(ChangeListManager.getInstance(myProject)));
255     List<Change> changes = builder.getChanges();
256
257     // get changes for files
258     final Map<FilePath, Change> result = new HashMap<FilePath, Change>();
259     for (Change change : changes) {
260       VirtualFile file = change.getVirtualFile();
261       FilePath filePath = null;
262       if (file == null) { // if a file was deleted, just find the reference in the original list of files and use it. 
263         String path = change.getBeforeRevision().getFile().getPath();
264         for (FilePath fp : changedPaths) {
265           if (fp.getPath().equals(path)) {
266             filePath = fp;
267             break;
268           }
269         }
270       } else {
271         filePath = new FilePathImpl(file);
272       }
273       result.put(filePath, change);
274     }
275     return result;
276   }
277
278 }