Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /*
  2. * Copyright (C) 2011, 2013 Dariusz Luksza <dariusz@luksza.org> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.diff;
  11. import static org.eclipse.jgit.diff.DiffEntry.DEV_NULL;
  12. import static org.eclipse.jgit.util.FileUtils.delete;
  13. import static org.hamcrest.CoreMatchers.is;
  14. import static org.hamcrest.CoreMatchers.notNullValue;
  15. import static org.hamcrest.MatcherAssert.assertThat;
  16. import static org.junit.Assert.assertEquals;
  17. import static org.junit.Assert.assertFalse;
  18. import static org.junit.Assert.assertTrue;
  19. import java.io.File;
  20. import java.util.List;
  21. import org.eclipse.jgit.api.Git;
  22. import org.eclipse.jgit.diff.DiffEntry.ChangeType;
  23. import org.eclipse.jgit.dircache.DirCache;
  24. import org.eclipse.jgit.dircache.DirCacheEditor;
  25. import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
  26. import org.eclipse.jgit.dircache.DirCacheEntry;
  27. import org.eclipse.jgit.internal.storage.file.FileRepository;
  28. import org.eclipse.jgit.junit.JGitTestUtil;
  29. import org.eclipse.jgit.junit.RepositoryTestCase;
  30. import org.eclipse.jgit.lib.FileMode;
  31. import org.eclipse.jgit.lib.Repository;
  32. import org.eclipse.jgit.revwalk.RevCommit;
  33. import org.eclipse.jgit.treewalk.EmptyTreeIterator;
  34. import org.eclipse.jgit.treewalk.FileTreeIterator;
  35. import org.eclipse.jgit.treewalk.TreeWalk;
  36. import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
  37. import org.eclipse.jgit.treewalk.filter.TreeFilter;
  38. import org.eclipse.jgit.util.FileUtils;
  39. import org.junit.Test;
  40. public class DiffEntryTest extends RepositoryTestCase {
  41. @Test
  42. public void shouldListAddedFileInInitialCommit() throws Exception {
  43. // given
  44. writeTrashFile("a.txt", "content");
  45. try (Git git = new Git(db);
  46. TreeWalk walk = new TreeWalk(db)) {
  47. git.add().addFilepattern("a.txt").call();
  48. RevCommit c = git.commit().setMessage("initial commit").call();
  49. // when
  50. walk.addTree(new EmptyTreeIterator());
  51. walk.addTree(c.getTree());
  52. List<DiffEntry> result = DiffEntry.scan(walk);
  53. // then
  54. assertThat(result, notNullValue());
  55. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
  56. DiffEntry entry = result.get(0);
  57. assertThat(entry.getChangeType(), is(ChangeType.ADD));
  58. assertThat(entry.getNewPath(), is("a.txt"));
  59. assertThat(entry.getOldPath(), is(DEV_NULL));
  60. }
  61. }
  62. @Test
  63. public void shouldListAddedFileBetweenTwoCommits() throws Exception {
  64. // given
  65. try (Git git = new Git(db);
  66. TreeWalk walk = new TreeWalk(db)) {
  67. RevCommit c1 = git.commit().setMessage("initial commit").call();
  68. writeTrashFile("a.txt", "content");
  69. git.add().addFilepattern("a.txt").call();
  70. RevCommit c2 = git.commit().setMessage("second commit").call();
  71. // when
  72. walk.addTree(c1.getTree());
  73. walk.addTree(c2.getTree());
  74. List<DiffEntry> result = DiffEntry.scan(walk);
  75. // then
  76. assertThat(result, notNullValue());
  77. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
  78. DiffEntry entry = result.get(0);
  79. assertThat(entry.getChangeType(), is(ChangeType.ADD));
  80. assertThat(entry.getNewPath(), is("a.txt"));
  81. assertThat(entry.getOldPath(), is(DEV_NULL));
  82. }
  83. }
  84. @Test
  85. public void shouldListModificationBetweenTwoCommits() throws Exception {
  86. // given
  87. try (Git git = new Git(db);
  88. TreeWalk walk = new TreeWalk(db)) {
  89. File file = writeTrashFile("a.txt", "content");
  90. git.add().addFilepattern("a.txt").call();
  91. RevCommit c1 = git.commit().setMessage("initial commit").call();
  92. write(file, "new content");
  93. RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
  94. .call();
  95. // when
  96. walk.addTree(c1.getTree());
  97. walk.addTree(c2.getTree());
  98. List<DiffEntry> result = DiffEntry.scan(walk);
  99. // then
  100. assertThat(result, notNullValue());
  101. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
  102. DiffEntry entry = result.get(0);
  103. assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
  104. assertThat(entry.getNewPath(), is("a.txt"));
  105. }
  106. }
  107. @Test
  108. public void shouldListDeletionBetweenTwoCommits() throws Exception {
  109. // given
  110. try (Git git = new Git(db);
  111. TreeWalk walk = new TreeWalk(db)) {
  112. File file = writeTrashFile("a.txt", "content");
  113. git.add().addFilepattern("a.txt").call();
  114. RevCommit c1 = git.commit().setMessage("initial commit").call();
  115. delete(file);
  116. RevCommit c2 = git.commit().setAll(true).setMessage("delete a.txt")
  117. .call();
  118. // when
  119. walk.addTree(c1.getTree());
  120. walk.addTree(c2.getTree());
  121. List<DiffEntry> result = DiffEntry.scan(walk);
  122. // then
  123. assertThat(result, notNullValue());
  124. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
  125. DiffEntry entry = result.get(0);
  126. assertThat(entry.getOldPath(), is("a.txt"));
  127. assertThat(entry.getNewPath(), is(DEV_NULL));
  128. assertThat(entry.getChangeType(), is(ChangeType.DELETE));
  129. }
  130. }
  131. @Test
  132. public void shouldListModificationInDirWithoutModifiedTrees()
  133. throws Exception {
  134. // given
  135. try (Git git = new Git(db);
  136. TreeWalk walk = new TreeWalk(db)) {
  137. File tree = new File(new File(db.getWorkTree(), "a"), "b");
  138. FileUtils.mkdirs(tree);
  139. File file = new File(tree, "c.txt");
  140. FileUtils.createNewFile(file);
  141. write(file, "content");
  142. git.add().addFilepattern("a").call();
  143. RevCommit c1 = git.commit().setMessage("initial commit").call();
  144. write(file, "new line");
  145. RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
  146. .call();
  147. // when
  148. walk.addTree(c1.getTree());
  149. walk.addTree(c2.getTree());
  150. walk.setRecursive(true);
  151. List<DiffEntry> result = DiffEntry.scan(walk);
  152. // then
  153. assertThat(result, notNullValue());
  154. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
  155. DiffEntry entry = result.get(0);
  156. assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
  157. assertThat(entry.getNewPath(), is("a/b/c.txt"));
  158. }
  159. }
  160. @Test
  161. public void shouldListModificationInDirWithModifiedTrees() throws Exception {
  162. // given
  163. try (Git git = new Git(db);
  164. TreeWalk walk = new TreeWalk(db)) {
  165. File tree = new File(new File(db.getWorkTree(), "a"), "b");
  166. FileUtils.mkdirs(tree);
  167. File file = new File(tree, "c.txt");
  168. FileUtils.createNewFile(file);
  169. write(file, "content");
  170. git.add().addFilepattern("a").call();
  171. RevCommit c1 = git.commit().setMessage("initial commit").call();
  172. write(file, "new line");
  173. RevCommit c2 = git.commit().setAll(true).setMessage("second commit")
  174. .call();
  175. // when
  176. walk.addTree(c1.getTree());
  177. walk.addTree(c2.getTree());
  178. List<DiffEntry> result = DiffEntry.scan(walk, true);
  179. // then
  180. assertThat(result, notNullValue());
  181. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(3)));
  182. DiffEntry entry = result.get(0);
  183. assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
  184. assertThat(entry.getNewPath(), is("a"));
  185. entry = result.get(1);
  186. assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
  187. assertThat(entry.getNewPath(), is("a/b"));
  188. entry = result.get(2);
  189. assertThat(entry.getChangeType(), is(ChangeType.MODIFY));
  190. assertThat(entry.getNewPath(), is("a/b/c.txt"));
  191. }
  192. }
  193. @Test
  194. public void shouldListChangesInWorkingTree() throws Exception {
  195. // given
  196. writeTrashFile("a.txt", "content");
  197. try (Git git = new Git(db);
  198. TreeWalk walk = new TreeWalk(db)) {
  199. git.add().addFilepattern("a.txt").call();
  200. RevCommit c = git.commit().setMessage("initial commit").call();
  201. writeTrashFile("b.txt", "new line");
  202. // when
  203. walk.addTree(c.getTree());
  204. walk.addTree(new FileTreeIterator(db));
  205. List<DiffEntry> result = DiffEntry.scan(walk, true);
  206. // then
  207. assertThat(Integer.valueOf(result.size()), is(Integer.valueOf(1)));
  208. DiffEntry entry = result.get(0);
  209. assertThat(entry.getChangeType(), is(ChangeType.ADD));
  210. assertThat(entry.getNewPath(), is("b.txt"));
  211. }
  212. }
  213. @Test
  214. public void shouldMarkEntriesWhenGivenMarkTreeFilter() throws Exception {
  215. // given
  216. try (Git git = new Git(db);
  217. TreeWalk walk = new TreeWalk(db)) {
  218. RevCommit c1 = git.commit().setMessage("initial commit").call();
  219. FileUtils.mkdir(new File(db.getWorkTree(), "b"));
  220. writeTrashFile("a.txt", "a");
  221. writeTrashFile("b/1.txt", "b1");
  222. writeTrashFile("b/2.txt", "b2");
  223. writeTrashFile("c.txt", "c");
  224. git.add().addFilepattern("a.txt").addFilepattern("b")
  225. .addFilepattern("c.txt").call();
  226. RevCommit c2 = git.commit().setMessage("second commit").call();
  227. TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt");
  228. TreeFilter filterB = PathFilterGroup.createFromStrings("b");
  229. TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt");
  230. // when
  231. walk.addTree(c1.getTree());
  232. walk.addTree(c2.getTree());
  233. List<DiffEntry> result = DiffEntry.scan(walk, true, new TreeFilter[] {
  234. filterA, filterB, filterB2 });
  235. // then
  236. assertThat(result, notNullValue());
  237. assertEquals(5, result.size());
  238. DiffEntry entryA = result.get(0);
  239. DiffEntry entryB = result.get(1);
  240. DiffEntry entryB1 = result.get(2);
  241. DiffEntry entryB2 = result.get(3);
  242. DiffEntry entryC = result.get(4);
  243. assertThat(entryA.getNewPath(), is("a.txt"));
  244. assertTrue(entryA.isMarked(0));
  245. assertFalse(entryA.isMarked(1));
  246. assertFalse(entryA.isMarked(2));
  247. assertEquals(1, entryA.getTreeFilterMarks());
  248. assertThat(entryB.getNewPath(), is("b"));
  249. assertFalse(entryB.isMarked(0));
  250. assertTrue(entryB.isMarked(1));
  251. assertTrue(entryB.isMarked(2));
  252. assertEquals(6, entryB.getTreeFilterMarks());
  253. assertThat(entryB1.getNewPath(), is("b/1.txt"));
  254. assertFalse(entryB1.isMarked(0));
  255. assertTrue(entryB1.isMarked(1));
  256. assertFalse(entryB1.isMarked(2));
  257. assertEquals(2, entryB1.getTreeFilterMarks());
  258. assertThat(entryB2.getNewPath(), is("b/2.txt"));
  259. assertFalse(entryB2.isMarked(0));
  260. assertTrue(entryB2.isMarked(1));
  261. assertTrue(entryB2.isMarked(2));
  262. assertEquals(6, entryB2.getTreeFilterMarks());
  263. assertThat(entryC.getNewPath(), is("c.txt"));
  264. assertFalse(entryC.isMarked(0));
  265. assertFalse(entryC.isMarked(1));
  266. assertFalse(entryC.isMarked(2));
  267. assertEquals(0, entryC.getTreeFilterMarks());
  268. }
  269. }
  270. @Test(expected = IllegalArgumentException.class)
  271. public void shouldThrowIAEWhenTreeWalkHasLessThanTwoTrees()
  272. throws Exception {
  273. // given - we don't need anything here
  274. // when
  275. try (TreeWalk walk = new TreeWalk(db)) {
  276. walk.addTree(new EmptyTreeIterator());
  277. DiffEntry.scan(walk);
  278. }
  279. }
  280. @Test(expected = IllegalArgumentException.class)
  281. public void shouldThrowIAEWhenTreeWalkHasMoreThanTwoTrees()
  282. throws Exception {
  283. // given - we don't need anything here
  284. // when
  285. try (TreeWalk walk = new TreeWalk(db)) {
  286. walk.addTree(new EmptyTreeIterator());
  287. walk.addTree(new EmptyTreeIterator());
  288. walk.addTree(new EmptyTreeIterator());
  289. DiffEntry.scan(walk);
  290. }
  291. }
  292. @Test(expected = IllegalArgumentException.class)
  293. public void shouldThrowIAEWhenScanShouldIncludeTreesAndWalkIsRecursive()
  294. throws Exception {
  295. // given - we don't need anything here
  296. // when
  297. try (TreeWalk walk = new TreeWalk(db)) {
  298. walk.addTree(new EmptyTreeIterator());
  299. walk.addTree(new EmptyTreeIterator());
  300. walk.setRecursive(true);
  301. DiffEntry.scan(walk, true);
  302. }
  303. }
  304. @Test
  305. public void shouldReportFileModeChange() throws Exception {
  306. writeTrashFile("a.txt", "content");
  307. try (Git git = new Git(db);
  308. TreeWalk walk = new TreeWalk(db)) {
  309. git.add().addFilepattern("a.txt").call();
  310. RevCommit c1 = git.commit().setMessage("initial commit").call();
  311. DirCache cache = db.lockDirCache();
  312. DirCacheEditor editor = cache.editor();
  313. walk.addTree(c1.getTree());
  314. walk.setRecursive(true);
  315. assertTrue(walk.next());
  316. editor.add(new PathEdit("a.txt") {
  317. @Override
  318. public void apply(DirCacheEntry ent) {
  319. ent.setFileMode(FileMode.EXECUTABLE_FILE);
  320. ent.setObjectId(walk.getObjectId(0));
  321. }
  322. });
  323. assertTrue(editor.commit());
  324. RevCommit c2 = git.commit().setMessage("second commit").call();
  325. walk.reset();
  326. walk.addTree(c1.getTree());
  327. walk.addTree(c2.getTree());
  328. List<DiffEntry> diffs = DiffEntry.scan(walk, false);
  329. assertEquals(1, diffs.size());
  330. DiffEntry diff = diffs.get(0);
  331. assertEquals(ChangeType.MODIFY,diff.getChangeType());
  332. assertEquals(diff.getOldId(), diff.getNewId());
  333. assertEquals("a.txt", diff.getOldPath());
  334. assertEquals(diff.getOldPath(), diff.getNewPath());
  335. assertEquals(FileMode.EXECUTABLE_FILE, diff.getNewMode());
  336. assertEquals(FileMode.REGULAR_FILE, diff.getOldMode());
  337. }
  338. }
  339. @Test
  340. public void shouldReportSubmoduleReplacedByFileMove() throws Exception {
  341. // Create a submodule
  342. FileRepository submoduleStandalone = createWorkRepository();
  343. JGitTestUtil.writeTrashFile(submoduleStandalone, "fileInSubmodule",
  344. "submodule");
  345. Git submoduleStandaloneGit = Git.wrap(submoduleStandalone);
  346. submoduleStandaloneGit.add().addFilepattern("fileInSubmodule").call();
  347. submoduleStandaloneGit.commit().setMessage("add file to submodule")
  348. .call();
  349. Repository submodule_db = Git.wrap(db).submoduleAdd()
  350. .setPath("modules/submodule")
  351. .setURI(submoduleStandalone.getDirectory().toURI().toString())
  352. .call();
  353. File submodule_trash = submodule_db.getWorkTree();
  354. addRepoToClose(submodule_db);
  355. writeTrashFile("fileInRoot", "root");
  356. Git rootGit = Git.wrap(db);
  357. rootGit.add().addFilepattern("fileInRoot").call();
  358. rootGit.commit().setMessage("add submodule and root file").call();
  359. // Dummy change on fileInRoot
  360. writeTrashFile("fileInRoot", "changed");
  361. rootGit.add().addFilepattern("fileInRoot").call();
  362. RevCommit firstCommit = rootGit.commit().setMessage("change root file")
  363. .call();
  364. // Remove the submodule again and move fileInRoot into that subfolder
  365. rootGit.rm().setCached(true).addFilepattern("modules/submodule").call();
  366. recursiveDelete(submodule_trash);
  367. JGitTestUtil.deleteTrashFile(db, "fileInRoot");
  368. // Move the fileInRoot file
  369. writeTrashFile("modules/submodule/fileInRoot", "changed");
  370. rootGit.rm().addFilepattern("fileInRoot").addFilepattern("modules/")
  371. .call();
  372. rootGit.add().addFilepattern("modules/").call();
  373. RevCommit secondCommit = rootGit.commit()
  374. .setMessage("remove submodule and move root file")
  375. .call();
  376. // Diff should report submodule having been deleted and file moved
  377. // (deleted and added)
  378. try (TreeWalk walk = new TreeWalk(db)) {
  379. walk.addTree(firstCommit.getTree());
  380. walk.addTree(secondCommit.getTree());
  381. walk.setRecursive(true);
  382. List<DiffEntry> diffs = DiffEntry.scan(walk);
  383. assertEquals(3, diffs.size());
  384. DiffEntry e = diffs.get(0);
  385. assertEquals(DiffEntry.ChangeType.DELETE, e.getChangeType());
  386. assertEquals("fileInRoot", e.getOldPath());
  387. e = diffs.get(1);
  388. assertEquals(DiffEntry.ChangeType.DELETE, e.getChangeType());
  389. assertEquals("modules/submodule", e.getOldPath());
  390. assertEquals(FileMode.GITLINK, e.getOldMode());
  391. e = diffs.get(2);
  392. assertEquals(DiffEntry.ChangeType.ADD, e.getChangeType());
  393. assertEquals("modules/submodule/fileInRoot", e.getNewPath());
  394. }
  395. }
  396. }