Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDariusz Luksza2012-01-25 23:01:06 +0000
committerMatthias Sohn2012-01-25 23:01:06 +0000
commit95981aca30a59b6e560e2e82d7b74d3bd0f8874b (patch)
treea60f96dc84878648ea50e14c2c54518dd9d48230
parentdff0f1a7317d8e29479bd430f21fde5cd2a56917 (diff)
downloadegit-95981aca30a59b6e560e2e82d7b74d3bd0f8874b.tar.gz
egit-95981aca30a59b6e560e2e82d7b74d3bd0f8874b.tar.xz
egit-95981aca30a59b6e560e2e82d7b74d3bd0f8874b.zip
[sync] Add cache provider for Git Change Set model
Base commit for improving Git Change Set model performance and reducing memory footprint. It simply reduces number of created TreeWalks and instead of keeping full JGit objects it contains only basic data needed by synchronize view. This implementation can change over time. It is possible that during further refactoring of current Git Change Set implementation more data will be needed in cache in such case I'll amend this commit. Requires-JGit: I6fc62c8e6626f907e544b5bbe5d64e864a2c323f Change-Id: If8dfa0251797aca56ddc825619500dc21885ba26 Signed-off-by: Dariusz Luksza <dariusz@luksza.org> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
-rw-r--r--org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/ChangeTest.java136
-rw-r--r--org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/GitCommitsModelCacheTest.java560
-rw-r--r--org.eclipse.egit.core/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelCache.java343
-rw-r--r--org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelDirectionException.java30
5 files changed, 1070 insertions, 0 deletions
diff --git a/org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/ChangeTest.java b/org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/ChangeTest.java
new file mode 100644
index 0000000000..0b3502d6b5
--- /dev/null
+++ b/org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/ChangeTest.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.egit.core.synchronize;
+
+import static org.eclipse.egit.core.synchronize.GitCommitsModelCache.ZERO_ID;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.eclipse.egit.core.synchronize.GitCommitsModelCache.Change;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.junit.Test;
+
+public class ChangeTest {
+
+ private static final AbbreviatedObjectId MISC_ID = AbbreviatedObjectId
+ .fromString("63448b851ae8831a1ad007f588508d3246ec7ace");
+
+ @Test
+ public void shouldNotBeEqualWithNullRefference() {
+ // given
+ Change change = new Change();
+
+ // when
+ boolean result = change.equals(null);
+
+ // then
+ assertFalse(result);
+ }
+
+ @Test
+ public void shouldNotBeEqualWithDifferentType() {
+ // given
+ Change change = new Change();
+
+ // when
+ boolean result = change.equals(new Object());
+
+ // then
+ assertFalse(result);
+ }
+
+ @Test
+ public void shouldBeEqualWhenBothIdsAreNull() {
+ // given
+ Change change = new Change();
+
+ // when
+ boolean result = change.equals(new Change());
+
+ // then
+ assertTrue(result);
+ }
+
+ @Test
+ public void shouldNotBeEqualWhenOneObjectIdIsNull() {
+ // given
+ Change change = new Change();
+ change.objectId = ZERO_ID;
+
+ // when
+ boolean result = change.equals(new Change());
+
+ // then
+ assertFalse(result);
+ }
+
+ @Test
+ public void shouldBeEqualWhenBothObjectIdsAreTheSame() {
+ // given
+ Change c1 = new Change();
+ Change c2 = new Change();
+ c1.objectId = c2.objectId = MISC_ID;
+
+ // when
+ boolean result = c1.equals(c2);
+
+ // then
+ assertTrue(result);
+ assertTrue(c1.hashCode() == c2.hashCode());
+ }
+
+ @Test
+ public void shouldNotBeEqualWhenObjectIdsAreDifferent() {
+ // given
+ Change c1 = new Change();
+ Change c2 = new Change();
+ c1.objectId = ZERO_ID;
+ c2.objectId = MISC_ID;
+
+ // when
+ boolean result = c1.equals(c2);
+
+ // then
+ assertFalse(result);
+ assertFalse(c1.hashCode() == c2.hashCode());
+ }
+
+ @Test
+ public void shouldNotBeEqualWhenOneRemoteObjectIsNull() {
+ // given
+ Change c1 = new Change();
+ Change c2 = new Change();
+ c1.objectId = c2.commitId = ZERO_ID;
+ c1.remoteObjectId = MISC_ID;
+
+ // when
+ boolean result = c1.equals(c2);
+
+ // then
+ assertFalse(result);
+ assertFalse(c1.hashCode() == c2.hashCode());
+ }
+
+ @Test
+ public void shouldBeEqualWhenBothObjectIdsAndRemoteIdsAreSame() {
+ // given
+ Change c1 = new Change();
+ Change c2 = new Change();
+ c1.objectId = c2.objectId = ZERO_ID;
+ c1.remoteObjectId = c2.remoteObjectId = MISC_ID;
+
+ // when
+ boolean result = c1.equals(c2);
+
+ // then
+ assertTrue(result);
+ assertTrue(c1.hashCode() == c2.hashCode());
+ }
+
+}
diff --git a/org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/GitCommitsModelCacheTest.java b/org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/GitCommitsModelCacheTest.java
new file mode 100644
index 0000000000..62c16592db
--- /dev/null
+++ b/org.eclipse.egit.core.test/src/org/eclipse/egit/core/synchronize/GitCommitsModelCacheTest.java
@@ -0,0 +1,560 @@
+/*******************************************************************************
+ * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.egit.core.synchronize;
+
+import static org.eclipse.compare.structuremergeviewer.Differencer.ADDITION;
+import static org.eclipse.compare.structuremergeviewer.Differencer.CHANGE;
+import static org.eclipse.compare.structuremergeviewer.Differencer.DELETION;
+import static org.eclipse.compare.structuremergeviewer.Differencer.LEFT;
+import static org.eclipse.compare.structuremergeviewer.Differencer.RIGHT;
+import static org.eclipse.jgit.junit.JGitTestUtil.deleteTrashFile;
+import static org.eclipse.jgit.junit.JGitTestUtil.writeTrashFile;
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.egit.core.synchronize.GitCommitsModelCache.Change;
+import org.eclipse.egit.core.synchronize.GitCommitsModelCache.Commit;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.errors.AmbiguousObjectException;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepository;
+import org.junit.Before;
+import org.junit.Test;
+
+@SuppressWarnings("boxing")
+public class GitCommitsModelCacheTest extends LocalDiskRepositoryTestCase {
+
+ private FileRepository db;
+
+ private static final String INITIAL_TAG = "initial-tag";
+
+ private static final AbbreviatedObjectId ZERO_ID = AbbreviatedObjectId
+ .fromObjectId(zeroId());
+
+ @Before
+ @Override
+ // copied from org.eclipse.jgit.lib.RepositoryTestCase
+ public void setUp() throws Exception {
+ super.setUp();
+ db = createWorkRepository();
+ Git git = new Git(db);
+ git.commit().setMessage("initial commit").call();
+ git.tag().setName(INITIAL_TAG).call();
+ }
+
+ @Test
+ public void shouldReturnEmptyListForSameSrcAndDstCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ RevCommit c = commit(git, "second commit");
+
+ // when
+ List<Commit> result = GitCommitsModelCache.build(db, c, c);
+
+ // then
+ assertThat(result, notNullValue());
+ assertThat(result.size(), is(0));
+ }
+
+ @Test
+ public void shouldListOneEmptyCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ RevCommit c = commit(git, "second commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, initialTagId(), c);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c, initialTagId());
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertThat(leftResult.size(), is(1));
+ assertEmptyCommit(leftResult.get(0), c, LEFT);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertThat(rightResult.size(), is(1));
+ assertEmptyCommit(rightResult.get(0), c, RIGHT);
+ }
+
+ @Test
+ public void shouldListTwoEmptyCommits() throws Exception {
+ // given
+ Git git = new Git(db);
+ RevCommit c1 = commit(git, "second commit");
+ RevCommit c2 = commit(git, "third commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, initialTagId(), c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, initialTagId());
+
+ // then
+ assertThat(leftResult, notNullValue());
+ assertThat(leftResult.size(), is(2));
+ assertEmptyCommit(leftResult.get(0), c2, LEFT);
+ assertEmptyCommit(leftResult.get(1), c1, LEFT);
+
+ assertThat(rightResult, notNullValue());
+ assertThat(rightResult.size(), is(2));
+ assertEmptyCommit(rightResult.get(0), c2, RIGHT);
+ assertEmptyCommit(rightResult.get(1), c1, RIGHT);
+ }
+
+ @Test
+ public void shouldListAdditionOrDeletionInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "a.txt", "content");
+ git.add().addFilepattern("a.txt").call();
+ RevCommit c = commit(git, "first commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, initialTagId(), c);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c, initialTagId());
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertCommit(leftResult.get(0), c, 1);
+ assertFileDeletion(c, leftResult.get(0).getChildren().get("a.txt"), "a.txt", LEFT | DELETION);
+
+ // right assertions, after changing sides addition becomes deletion
+ assertThat(rightResult, notNullValue());
+ assertCommit(rightResult.get(0), c, 1);
+ assertFileAddition(c, rightResult.get(0).getChildren().get("a.txt"), "a.txt", RIGHT | ADDITION);
+ }
+
+ @Test
+ public void shouldListAdditionOrDeletionInsideFolderInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "content");
+ git.add().addFilepattern("folder/a.txt").call();
+ RevCommit c = commit(git, "first commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, initialTagId(), c);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c, initialTagId());
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertCommit(leftResult.get(0), c, 1);
+ assertThat(leftResult.get(0).getChildren().size(), is(1));
+ assertFileDeletion(c, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | DELETION);
+
+ // right assertions, after changing sides addition becomes deletion
+ assertThat(rightResult, notNullValue());
+ assertCommit(rightResult.get(0), c, 1);
+ assertThat(rightResult.get(0).getChildren().size(), is(1));
+ assertFileAddition(c, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | ADDITION);
+ }
+
+ @Test
+ public void shouldListAdditionsOrDeletionsInsideSeparateFoldersInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "content");
+ writeTrashFile(db, "folder2/b.txt", "b content");
+ git.add().addFilepattern("folder/a.txt").call();
+ git.add().addFilepattern("folder2/b.txt").call();
+ RevCommit c = commit(git, "first commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, initialTagId(), c);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c, initialTagId());
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertThat(Integer.valueOf(leftResult.size()), is(Integer.valueOf(1)));
+ assertThat(leftResult.get(0).getShortMessage(), is("first commit"));
+
+ assertThat(leftResult.get(0).getChildren(), notNullValue());
+ assertThat(leftResult.get(0).getChildren().size(), is(2));
+
+ assertFileDeletion(c, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | DELETION);
+ assertFileDeletion(c, leftResult.get(0).getChildren().get("folder2/b.txt"), "b.txt",LEFT | DELETION);
+
+ // right assertions, after changing sides addition becomes deletion
+ assertThat(rightResult, notNullValue());
+ assertThat(Integer.valueOf(rightResult.size()), is(Integer.valueOf(1)));
+ assertThat(rightResult.get(0).getShortMessage(), is("first commit"));
+
+ assertThat(rightResult.get(0).getChildren(), notNullValue());
+ assertThat(rightResult.get(0).getChildren().size(), is(2));
+
+ assertFileAddition(c, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | ADDITION);
+ assertFileAddition(c, rightResult.get(0).getChildren().get("folder2/b.txt"), "b.txt",RIGHT | ADDITION);
+ }
+
+ @Test
+ public void shouldListAdditionsOrDeletionsInsideFolderInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "content");
+ writeTrashFile(db, "folder/b.txt", "b content");
+ git.add().addFilepattern("folder").call();
+ RevCommit c = commit(git, "first commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, initialTagId(), c);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c, initialTagId());
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertThat(Integer.valueOf(leftResult.size()), is(Integer.valueOf(1)));
+ assertCommit(leftResult.get(0), c, 2);
+
+ assertThat(leftResult.get(0).getChildren().size(), is(2));
+
+ assertFileDeletion(c, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | DELETION);
+ assertFileDeletion(c, leftResult.get(0).getChildren().get("folder/b.txt"), "b.txt", LEFT | DELETION);
+
+ // right assertions, after changing sides addition becomes deletion
+ assertThat(rightResult, notNullValue());
+ assertThat(Integer.valueOf(rightResult.size()), is(Integer.valueOf(1)));
+ assertCommit(rightResult.get(0), c, 2);
+
+ assertThat(rightResult.get(0).getChildren().size(), is(2));
+
+ assertFileAddition(c, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | ADDITION);
+ assertFileAddition(c, rightResult.get(0).getChildren().get("folder/b.txt"), "b.txt", RIGHT | ADDITION);
+ }
+
+ @Test
+ public void shouldListChangeInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "a.txt", "content");
+ git.add().addFilepattern("a.txt").call();
+ RevCommit c1 = commit(git, "first commit");
+ writeTrashFile(db, "a.txt", "new content");
+ RevCommit c2 = commit(git, "second commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertCommit(leftResult.get(0), c2, 1);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("a.txt"), "a.txt", LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertCommit(rightResult.get(0), c2, 1);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("a.txt"), "a.txt", RIGHT | CHANGE);
+ }
+
+ @Test
+ public void shouldListChangeInsideFolderInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "content");
+ git.add().addFilepattern("folder/a.txt").call();
+ RevCommit c1 = commit(git, "first commit");
+ writeTrashFile(db, "folder/a.txt", "new content");
+ RevCommit c2 = commit(git, "second commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertCommit(leftResult.get(0), c2, 1);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertCommit(rightResult.get(0), c2, 1);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | CHANGE);
+ }
+
+ @Test
+ public void shouldListChangesInsideSeparateFoldersInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "content");
+ writeTrashFile(db, "folder2/b.txt", "b content");
+ git.add().addFilepattern("folder/a.txt").call();
+ git.add().addFilepattern("folder2/b.txt").call();
+ RevCommit c1 = commit(git, "first commit");
+ writeTrashFile(db, "folder/a.txt", "new content");
+ writeTrashFile(db, "folder2/b.txt", "new b content");
+ RevCommit c2 = commit(git, "second commit");
+
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertThat(Integer.valueOf(leftResult.size()), is(Integer.valueOf(1)));
+ assertThat(leftResult.get(0).getShortMessage(), is("second commit"));
+
+ assertThat(leftResult.get(0).getChildren(), notNullValue());
+ assertThat(leftResult.get(0).getChildren().size(), is(2));
+
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | CHANGE);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder2/b.txt"), "b.txt",LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertThat(Integer.valueOf(rightResult.size()), is(Integer.valueOf(1)));
+ assertThat(rightResult.get(0).getShortMessage(), is("second commit"));
+
+ assertThat(rightResult.get(0).getChildren().size(), is(2));
+
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | CHANGE);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder2/b.txt"), "b.txt",RIGHT | CHANGE);
+ }
+
+ @Test
+ public void shouldListChangesInsideFolderInCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "content");
+ writeTrashFile(db, "folder/b.txt", "b content");
+ git.add().addFilepattern("folder").call();
+ RevCommit c1 = commit(git, "first commit");
+ writeTrashFile(db, "folder/a.txt", "new content");
+ writeTrashFile(db, "folder/b.txt", "new b content");
+ RevCommit c2 = commit(git, "second commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertThat(Integer.valueOf(leftResult.size()), is(Integer.valueOf(1)));
+ assertCommit(leftResult.get(0), c2, 2);
+
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | CHANGE);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder/b.txt"), "b.txt", LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertThat(Integer.valueOf(rightResult.size()), is(Integer.valueOf(1)));
+ assertCommit(rightResult.get(0), c2, 2);
+
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | CHANGE);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder/b.txt"), "b.txt", RIGHT | CHANGE);
+ }
+
+ @Test
+ public void shouldListAllTypeOfChangesInOneCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "a.txt", "a content");
+ writeTrashFile(db, "c.txt", "c content");
+ git.add().addFilepattern("a.txt").call();
+ git.add().addFilepattern("c.txt").call();
+ RevCommit c1 = commit(git, "first commit");
+ deleteTrashFile(db, "a.txt");
+ writeTrashFile(db, "b.txt", "b content");
+ writeTrashFile(db, "c.txt", "new c content");
+ git.add().addFilepattern("b.txt").call();
+ RevCommit c2 = commit(git, "second commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertCommit(leftResult.get(0), c2, 3);
+ assertFileAddition(c2, c1, leftResult.get(0).getChildren().get("a.txt"), "a.txt", LEFT | ADDITION);
+ assertFileDeletion(c2, c1, leftResult.get(0).getChildren().get("b.txt"), "b.txt", LEFT | DELETION);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("c.txt"), "c.txt", LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertCommit(rightResult.get(0), c2, 3);
+ assertFileDeletion(c2, c1, rightResult.get(0).getChildren().get("a.txt"), "a.txt", RIGHT | DELETION);
+ assertFileAddition(c2, c1, rightResult.get(0).getChildren().get("b.txt"), "b.txt", RIGHT | ADDITION);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("c.txt"), "c.txt", RIGHT | CHANGE);
+ }
+
+ @Test
+ public void shouldListAllTypeOfChangesInsideFolderInOneCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "a content");
+ writeTrashFile(db, "folder/c.txt", "c content");
+ git.add().addFilepattern("folder/a.txt").call();
+ git.add().addFilepattern("folder/c.txt").call();
+ RevCommit c1 = commit(git, "first commit");
+ deleteTrashFile(db, "folder/a.txt");
+ writeTrashFile(db, "folder/b.txt", "b content");
+ writeTrashFile(db, "folder/c.txt", "new c content");
+ git.add().addFilepattern("folder/b.txt").call();
+ RevCommit c2 = commit(git, "second commit");
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertCommit(leftResult.get(0), c2, 3);
+ assertFileAddition(c2, c1, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | ADDITION);
+ assertFileDeletion(c2, c1, leftResult.get(0).getChildren().get("folder/b.txt"), "b.txt", LEFT | DELETION);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder/c.txt"), "c.txt", LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertCommit(rightResult.get(0), c2, 3);
+ assertFileDeletion(c2, c1, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | DELETION);
+ assertFileAddition(c2, c1, rightResult.get(0).getChildren().get("folder/b.txt"), "b.txt", RIGHT | ADDITION);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder/c.txt"), "c.txt", RIGHT | CHANGE);
+ }
+
+ @Test
+ public void shouldListAllTypeOfChangesInsideSeparateFoldersInOneCommit() throws Exception {
+ // given
+ Git git = new Git(db);
+ writeTrashFile(db, "folder/a.txt", "a content");
+ writeTrashFile(db, "folder2/c.txt", "c content");
+ git.add().addFilepattern("folder/a.txt").call();
+ git.add().addFilepattern("folder2/c.txt").call();
+ RevCommit c1 = commit(git, "first commit");
+ deleteTrashFile(db, "folder/a.txt");
+ writeTrashFile(db, "folder1/b.txt", "b content");
+ writeTrashFile(db, "folder2/c.txt", "new c content");
+ git.add().addFilepattern("folder1/b.txt").call();
+ RevCommit c2 = commit(git, "second commit");
+
+
+ // when
+ List<Commit> leftResult = GitCommitsModelCache.build(db, c1, c2);
+ List<Commit> rightResult = GitCommitsModelCache.build(db, c2, c1);
+
+ // then
+ // left assertions
+ assertThat(leftResult, notNullValue());
+ assertThat(Integer.valueOf(leftResult.size()), is(Integer.valueOf(1)));
+ assertThat(leftResult.get(0).getShortMessage(), is("second commit"));
+
+ assertThat(leftResult.get(0).getChildren(), notNullValue());
+ assertThat(leftResult.get(0).getChildren().size(), is(3));
+
+ assertFileAddition(c2, c1, leftResult.get(0).getChildren().get("folder/a.txt"), "a.txt", LEFT | ADDITION);
+ assertFileDeletion(c2, c1, leftResult.get(0).getChildren().get("folder1/b.txt"), "b.txt", LEFT | DELETION);
+ assertFileChange(c2, c1, leftResult.get(0).getChildren().get("folder2/c.txt"), "c.txt", LEFT | CHANGE);
+
+ // right assertions
+ assertThat(rightResult, notNullValue());
+ assertThat(Integer.valueOf(rightResult.size()), is(Integer.valueOf(1)));
+ assertThat(rightResult.get(0).getShortMessage(), is("second commit"));
+
+ assertThat(rightResult.get(0).getChildren(), notNullValue());
+ assertThat(rightResult.get(0).getChildren().size(), is(3));
+
+ assertFileDeletion(c2, c1, rightResult.get(0).getChildren().get("folder/a.txt"), "a.txt", RIGHT | DELETION);
+ assertFileAddition(c2, c1, rightResult.get(0).getChildren().get("folder1/b.txt"), "b.txt",RIGHT | ADDITION);
+ assertFileChange(c2, c1, rightResult.get(0).getChildren().get("folder2/c.txt"), "c.txt", RIGHT | CHANGE);
+ }
+
+ private RevCommit commit(Git git, String msg) throws Exception {
+ tick();
+ return git.commit().setAll(true).setMessage(msg).setCommitter(committer).call();
+ }
+
+ private ObjectId initialTagId() throws AmbiguousObjectException, IOException {
+ return db.resolve(INITIAL_TAG);
+ }
+
+ private void assertEmptyCommit(Commit commit, RevCommit actualCommit, int direction) {
+ commonCommitAsserts(commit, actualCommit);
+ assertThat(commit.getChildren(), nullValue());
+ assertThat(commit.getDirection(), is(direction));
+ }
+
+ private void assertCommit(Commit commit, RevCommit actualCommit, int childrenCount) {
+ commonCommitAsserts(commit, actualCommit);
+ assertThat(commit.getChildren(), notNullValue());
+ assertThat(commit.getChildren().size(), is(childrenCount));
+
+ }
+
+ private void commonCommitAsserts(Commit commit, RevCommit actualCommit) {
+ assertThat(commit.getShortMessage(), is(actualCommit.getShortMessage()));
+ assertThat(commit.getId().toObjectId(), is(actualCommit.getId()));
+ assertThat(commit.getAuthorName(), is(actualCommit.getAuthorIdent().getName()));
+ assertThat(commit.getCommitterName(), is(actualCommit.getCommitterIdent().getName()));
+ assertThat(commit.getCommitDate(), is(actualCommit.getAuthorIdent().getWhen()));
+ }
+
+ private void assertFileChange(RevCommit actual, RevCommit parent,
+ Change change, String name, int direction) {
+ commonFileAssertions(actual, parent, change, name, direction);
+ assertThat(change.getObjectId(), not(ZERO_ID));
+ assertThat(change.getRemoteObjectId(), not(ZERO_ID));
+ }
+
+ private void assertFileAddition(RevCommit actual, Change change,
+ String name, int direction) {
+ assertFileAddition(actual, null, change, name, direction);
+ }
+
+ private void assertFileAddition(RevCommit actual, RevCommit parent,
+ Change change, String name, int direction) {
+ commonFileAssertions(actual, parent, change, name, direction);
+ assertThat(change.getObjectId(), not(ZERO_ID));
+ assertThat(change.getRemoteObjectId(), nullValue());
+ }
+
+ private void assertFileDeletion(RevCommit actual, Change change,
+ String name, int direction) {
+ assertFileDeletion(actual, null, change, name, direction);
+
+ }
+
+ private void assertFileDeletion(RevCommit actual, RevCommit parent,
+ Change change, String name, int direction) {
+ commonFileAssertions(actual, parent, change, name, direction);
+ assertThat(change.getObjectId(), nullValue());
+ assertThat(change.getRemoteObjectId(), not(ZERO_ID));
+ }
+
+ private void commonFileAssertions(RevCommit actual, RevCommit parent,
+ Change change, String name, int direction) {
+ assertThat(change, notNullValue());
+ assertThat(change.getCommitId().toObjectId(), is(actual.getId()));
+ if (parent != null)
+ assertThat(change.getRemoteCommitId().toObjectId(),
+ is(parent.getId()));
+ assertThat(change.getName(), is(name));
+ assertThat(change.getKind(), is(direction));
+ }
+
+}
diff --git a/org.eclipse.egit.core/META-INF/MANIFEST.MF b/org.eclipse.egit.core/META-INF/MANIFEST.MF
index c6bc12d408..5c158f1645 100644
--- a/org.eclipse.egit.core/META-INF/MANIFEST.MF
+++ b/org.eclipse.egit.core/META-INF/MANIFEST.MF
@@ -8,6 +8,7 @@ Bundle-Vendor: %provider_name
Bundle-Localization: plugin
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.4.0,4.0.0)",
org.eclipse.team.core;bundle-version="[3.4.0,4.0.0)",
+ org.eclipse.compare;bundle-version="[3.4.0,4.0.0)",
org.eclipse.core.resources;bundle-version="[3.4.0,4.0.0)",
org.eclipse.core.filesystem;bundle-version="[1.2.0,2.0.0)",
org.eclipse.equinox.security;bundle-version="[1.0.0,2.0.0)"
diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelCache.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelCache.java
new file mode 100644
index 0000000000..c655ee68af
--- /dev/null
+++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelCache.java
@@ -0,0 +1,343 @@
+/*******************************************************************************
+ * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.egit.core.synchronize;
+
+import static org.eclipse.compare.structuremergeviewer.Differencer.LEFT;
+import static org.eclipse.compare.structuremergeviewer.Differencer.RIGHT;
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevFlagSet;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+
+/**
+ * Retrieves list of commits and the changes associated with each commit
+ */
+public class GitCommitsModelCache {
+
+ /**
+ * Corresponds to {@link RevCommit} object, but contains only those data
+ * that are required by Synchronize view Change Set
+ */
+ public static class Commit {
+ private int direction;
+
+ private String shortMessage;
+
+ private AbbreviatedObjectId commitId;
+
+ private Date commitDate;
+
+ private String authorName;
+
+ private String committerName;
+
+ private Map<String, Change> children;
+
+ private Commit() {
+ // reduce the visibility of the default constructor
+ }
+
+ /**
+ * Indicates if this commit is incoming or outgoing. Returned value
+ * corresponds to {@link Differencer#LEFT} for incoming and
+ * {@link Differencer#RIGHT} for outgoing changes
+ *
+ * @return change direction
+ */
+ public int getDirection() {
+ return direction;
+ }
+
+ /**
+ * @return commit id
+ */
+ public AbbreviatedObjectId getId() {
+ return commitId;
+ }
+
+ /**
+ * @return commit author
+ */
+ public String getAuthorName() {
+ return authorName;
+ }
+
+ /**
+ * @return the committer name
+ */
+ public String getCommitterName() {
+ return committerName;
+ }
+
+ /**
+ * @return commit date
+ */
+ public Date getCommitDate() {
+ return commitDate;
+ }
+
+ /**
+ * @return commit short message
+ */
+ public String getShortMessage() {
+ return shortMessage;
+ }
+
+ /**
+ * @return list of changes made by this commit or {@code null} when
+ * commit doesn't have any changes
+ */
+ public Map<String, Change> getChildren() {
+ return children;
+ }
+
+ }
+
+ /**
+ * Describes single tree or blob change in commit.
+ */
+ public static class Change {
+ int kind;
+
+ String name;
+
+ AbbreviatedObjectId objectId;
+
+ AbbreviatedObjectId commitId;
+
+ AbbreviatedObjectId remoteCommitId;
+
+ AbbreviatedObjectId remoteObjectId;
+
+ Change() {
+ // reduce the visibility of the default constructor
+ }
+
+ /**
+ * Describes if this change is incoming/outgoing addition, deletion or
+ * change.
+ *
+ * It uses static values of LEFT, RIGHT, ADDITION, DELETION, CHANGE from
+ * {@link Differencer} class.
+ *
+ * @return kind
+ */
+ public int getKind() {
+ return kind;
+ }
+
+ /**
+ * @return object name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return id of commit containing this change
+ */
+ public AbbreviatedObjectId getCommitId() {
+ return commitId;
+ }
+
+ /**
+ * @return id of parent commit
+ */
+ public AbbreviatedObjectId getRemoteCommitId() {
+ return remoteCommitId;
+ }
+
+ /**
+ * @return object id
+ */
+ public AbbreviatedObjectId getObjectId() {
+ return objectId;
+ }
+
+ /**
+ * @return remote object id
+ */
+ public AbbreviatedObjectId getRemoteObjectId() {
+ return remoteObjectId;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((objectId == null) ? 0 : objectId.hashCode());
+ result = prime
+ * result
+ + ((remoteObjectId == null) ? 0 : remoteObjectId.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Change other = (Change) obj;
+ if (objectId == null) {
+ if (other.objectId != null)
+ return false;
+ } else if (!objectId.equals(other.objectId))
+ return false;
+ if (remoteObjectId == null) {
+ if (other.remoteObjectId != null)
+ return false;
+ } else if (!remoteObjectId.equals(other.remoteObjectId))
+ return false;
+ return true;
+ }
+
+ }
+
+ static final AbbreviatedObjectId ZERO_ID = AbbreviatedObjectId
+ .fromObjectId(zeroId());
+
+ /**
+ * Scans given {@code repo} and build list of commits between two given
+ * RevCommit objectId's. Each commit contains list of changed resources
+ *
+ * @param repo
+ * repository that should be scanned
+ * @param srcId
+ * RevCommit id that git history traverse will start from
+ * @param dstId
+ * RevCommit id that git history traverse will end
+ * @return list of {@link Commit} object's between {@code srcId} and
+ * {@code dstId}
+ * @throws IOException
+ */
+ public static List<Commit> build(Repository repo, ObjectId srcId,
+ ObjectId dstId) throws IOException {
+ if (dstId.equals(srcId))
+ return new ArrayList<Commit>(0);
+
+ final RevWalk rw = new RevWalk(repo);
+
+ final RevFlag localFlag = rw.newFlag("local"); //$NON-NLS-1$
+ final RevFlag remoteFlag = rw.newFlag("remote"); //$NON-NLS-1$
+ final RevFlagSet allFlags = new RevFlagSet();
+ allFlags.add(localFlag);
+ allFlags.add(remoteFlag);
+ rw.carry(allFlags);
+
+ RevCommit srcCommit = rw.parseCommit(srcId);
+ srcCommit.add(localFlag);
+ rw.markStart(srcCommit);
+ srcCommit = null; // free not needed resources
+
+ RevCommit dstCommit = rw.parseCommit(dstId);
+ dstCommit.add(remoteFlag);
+ rw.markStart(dstCommit);
+ dstCommit = null; // free not needed resources
+
+ List<Commit> result = new ArrayList<Commit>();
+ for (RevCommit revCommit : rw) {
+ if (revCommit.hasAll(allFlags))
+ break;
+
+ Commit commit = new Commit();
+ commit.shortMessage = revCommit.getShortMessage();
+ commit.commitId = AbbreviatedObjectId.fromObjectId(revCommit);
+ commit.authorName = revCommit.getAuthorIdent().getName();
+ commit.committerName = revCommit.getCommitterIdent().getName();
+ commit.commitDate = revCommit.getAuthorIdent().getWhen();
+
+ if (revCommit.has(localFlag))
+ commit.direction = RIGHT;
+ else if (revCommit.has(remoteFlag))
+ commit.direction = LEFT;
+ else
+ throw new GitCommitsModelDirectionException();
+
+ RevCommit[] parents = revCommit.getParents();
+ if (parents.length == 1) // don't show changes in merge commits
+ commit.children = getChangedObjects(repo, revCommit,
+ parents[0], commit.direction);
+
+ result.add(commit);
+ }
+ rw.dispose();
+
+ return result;
+ }
+
+ private static Map<String, Change> getChangedObjects(Repository repo,
+ RevCommit parentCommit, RevCommit remoteCommit, final int direction)
+ throws IOException {
+ final TreeWalk tw = new TreeWalk(repo);
+ tw.addTree(parentCommit.getTree());
+ tw.addTree(remoteCommit.getTree());
+ tw.setFilter(TreeFilter.ANY_DIFF);
+ tw.setRecursive(true);
+
+ final int localTreeId = direction == LEFT ? 1 : 0;
+ final int remoteTreeId = direction == LEFT ? 0 : 1;
+ final Map<String, Change> result = new HashMap<String, GitCommitsModelCache.Change>();
+ final AbbreviatedObjectId actualCommit = AbbreviatedObjectId
+ .fromObjectId(parentCommit);
+ final AbbreviatedObjectId remoteCommitAbb = AbbreviatedObjectId
+ .fromObjectId(remoteCommit);
+
+ MutableObjectId idBuf = new MutableObjectId();
+ while (tw.next()) {
+ Change change = new Change();
+ change.commitId = actualCommit;
+ change.remoteCommitId = remoteCommitAbb;
+ change.name = tw.getNameString();
+ tw.getObjectId(idBuf, localTreeId);
+ change.objectId = AbbreviatedObjectId.fromObjectId(idBuf);
+ tw.getObjectId(idBuf, remoteTreeId);
+ change.remoteObjectId = AbbreviatedObjectId.fromObjectId(idBuf);
+
+ calculateAndSetChangeKind(direction, change);
+
+ result.put(tw.getPathString(), change);
+ }
+ tw.release();
+
+ return result.size() > 0 ? result : null;
+ }
+
+ static void calculateAndSetChangeKind(final int direction,
+ Change change) {
+ if (ZERO_ID.equals(change.objectId)) {
+ change.objectId = null; // clear zero id;
+ change.kind = direction | Differencer.DELETION;
+ } else if (ZERO_ID.equals(change.remoteObjectId)) {
+ change.remoteObjectId = null; // clear zero id;
+ change.kind = direction | Differencer.ADDITION;
+ } else
+ change.kind = direction | Differencer.CHANGE;
+ }
+
+}
diff --git a/org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelDirectionException.java b/org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelDirectionException.java
new file mode 100644
index 0000000000..a81737a091
--- /dev/null
+++ b/org.eclipse.egit.core/src/org/eclipse/egit/core/synchronize/GitCommitsModelDirectionException.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (C) 2012, Dariusz Luksza <dariusz@luksza.org>
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.egit.core.synchronize;
+
+/**
+ * Thrown when commit direction cannot be determined in
+ * {@link GitCommitsModelCache#build(org.eclipse.jgit.lib.Repository, org.eclipse.jgit.lib.ObjectId, org.eclipse.jgit.lib.ObjectId)}
+ *
+ */
+public class GitCommitsModelDirectionException extends RuntimeException {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 7867729888561453855L;
+
+ /**
+ * Creates exception instance with default message
+ */
+ public GitCommitsModelDirectionException() {
+ super("Unknown commit direction"); //$NON-NLS-1$
+ }
+
+}

Back to the top