[Notice] Announcing the End of Demo Server [Read me]
김덕홍 2014-03-27
Merge branch 'fix/diff-unknown-error' of npcode/yobi
from pull request 733
@18801bdbfb221c2a3df36622bd106ef9b741ec75
app/playRepository/FileDiff.java
--- app/playRepository/FileDiff.java
+++ app/playRepository/FileDiff.java
@@ -17,7 +17,7 @@
 public class FileDiff {
     public static final int SIZE_LIMIT = 500 * 1024;
     public static final int LINE_LIMIT = 5000;
-    private Error error;
+    private Set<Error> errors = new HashSet<>();
     public enum Error {A_SIZE_EXCEEDED, B_SIZE_EXCEEDED, DIFF_SIZE_EXCEEDED, OTHERS_SIZE_EXCEEDED }
     public RawText a;
     public RawText b;
@@ -226,21 +226,44 @@
         return oldMode.getBits() != newMode.getBits();
     }
 
-    public void setError(Error error) {
-        this.error = error;
+    public void addError(Error error) {
+        this.errors.add(error);
     }
 
-    public Error getError() {
-        if (error != null) {
-            return error;
-        } else if (a != null && isRawTextSizeExceeds(a)) {
-            return Error.A_SIZE_EXCEEDED;
-        } else if (b != null && isRawTextSizeExceeds(b)) {
-            return Error.B_SIZE_EXCEEDED;
-        } else if (getHunks() instanceof SizeExceededHunks) {
-            return Error.DIFF_SIZE_EXCEEDED;
-        } else {
-            return null;
+    public boolean hasAnyError(Error ... errors) {
+        refreshErrors();
+
+        for (Error error : errors) {
+            if (this.errors.contains(error)) {
+                return true;
+            }
         }
+
+        return false;
+    }
+
+    private void refreshErrors() {
+        if (getHunks() instanceof SizeExceededHunks) {
+            addError(Error.DIFF_SIZE_EXCEEDED);
+        }
+
+        // If editList is already produced, there is no need to concern about
+        // the size of the raw text a or b
+        if (editList == null && a != null && isRawTextSizeExceeds(a)) {
+            addError(Error.A_SIZE_EXCEEDED);
+        }
+        if (editList == null && b != null && isRawTextSizeExceeds(b)) {
+            addError(Error.B_SIZE_EXCEEDED);
+        }
+    }
+
+    public boolean hasError(Error error) {
+        refreshErrors();
+        return this.errors.contains(error);
+    }
+
+    public boolean hasError() {
+        refreshErrors();
+        return !this.errors.isEmpty();
     }
 }
app/playRepository/GitRepository.java
--- app/playRepository/GitRepository.java
+++ app/playRepository/GitRepository.java
@@ -678,6 +678,16 @@
         return getFileDiffs(repository, repository, commitIdA, commit.getId());
     }
 
+    public static List<FileDiff> getDiff(Repository repository, RevCommit commit) throws IOException {
+        ObjectId commitIdA = null;
+        if (commit.getParentCount() > 0) {
+            commitIdA = commit.getParent(0).getId();
+        }
+
+        return getFileDiffs(repository, repository, commitIdA, commit.getId());
+    }
+
+
     /**
      * {@code untilRevName}에 해당하는 리비전까지의 커밋 목록을 반환한다.
      * {@code untilRevName}이 null이면 HEAD 까지의 커밋 목록을 반환한다.
@@ -1696,17 +1706,15 @@
                     && Arrays.asList(DELETE, MODIFY, RENAME, COPY).contains(diff.getChangeType())) {
                 TreeWalk t1 = TreeWalk.forPath(repositoryA, pathA, treeA);
                 ObjectId blobA = t1.getObjectId(0);
+                fileDiff.pathA = pathA;
 
                 try {
                     rawA = repositoryA.open(blobA).getBytes();
+                    fileDiff.isBinaryA = RawText.isBinary(rawA);
+                    fileDiff.a = fileDiff.isBinaryA ? null : new RawText(rawA);
                 } catch (org.eclipse.jgit.errors.LargeObjectException e) {
-                    result.add(fileDiff);
-                    continue;
+                    fileDiff.addError(FileDiff.Error.A_SIZE_EXCEEDED);
                 }
-
-                fileDiff.isBinaryA = RawText.isBinary(rawA);
-                fileDiff.a = fileDiff.isBinaryA ? null : new RawText(rawA);
-                fileDiff.pathA = pathA;
             }
 
             byte[] rawB = null;
@@ -1714,26 +1722,28 @@
                     && Arrays.asList(ADD, MODIFY, RENAME, COPY).contains(diff.getChangeType())) {
                 TreeWalk t2 = TreeWalk.forPath(repositoryB, pathB, treeB);
                 ObjectId blobB = t2.getObjectId(0);
+                fileDiff.pathB = pathB;
 
                 try {
                     rawB = repositoryB.open(blobB).getBytes();
+                    fileDiff.isBinaryB = RawText.isBinary(rawB);
+                    fileDiff.b = fileDiff.isBinaryB ? null : new RawText(rawB);
                 } catch (org.eclipse.jgit.errors.LargeObjectException e) {
-                    result.add(fileDiff);
-                    continue;
+                    fileDiff.addError(FileDiff.Error.B_SIZE_EXCEEDED);
                 }
-
-                fileDiff.isBinaryB = RawText.isBinary(rawB);
-                fileDiff.b = fileDiff.isBinaryB ? null : new RawText(rawB);
-                fileDiff.pathB = pathB;
             }
 
             if (size > DIFF_SIZE_LIMIT || lines > DIFF_LINE_LIMIT) {
-                fileDiff.setError(FileDiff.Error.OTHERS_SIZE_EXCEEDED);
+                fileDiff.addError(FileDiff.Error.OTHERS_SIZE_EXCEEDED);
                 result.add(fileDiff);
                 continue;
             }
 
-            if (!(fileDiff.isBinaryA || fileDiff.isBinaryB) && Arrays.asList(MODIFY, RENAME).contains(diff.getChangeType())) {
+            // Get diff if necessary
+            if (fileDiff.a != null
+                    && fileDiff.b != null
+                    && !(fileDiff.isBinaryA || fileDiff.isBinaryB)
+                    && Arrays.asList(MODIFY, RENAME).contains(diff.getChangeType())) {
                 DiffAlgorithm diffAlgorithm = DiffAlgorithm.getAlgorithm(
                         repositoryB.getConfig().getEnum(
                                 ConfigConstants.CONFIG_DIFF_SECTION, null,
@@ -1745,16 +1755,19 @@
                 lines += fileDiff.getHunks().lines;
             }
 
-            if (!fileDiff.isBinaryB && diff.getChangeType().equals(ADD)) {
+            // update lines and sizes
+            if (fileDiff.b != null && !fileDiff.isBinaryB && diff.getChangeType().equals(ADD)) {
                 lines += fileDiff.b.size();
                 size += rawB.length;
             }
 
-            if (!fileDiff.isBinaryA && diff.getChangeType().equals(DELETE)) {
+            // update lines and sizes
+            if (fileDiff.a != null && !fileDiff.isBinaryA && diff.getChangeType().equals(DELETE)) {
                 lines += fileDiff.a.size();
-                 size += rawA.length;
+                size += rawA.length;
             }
 
+            // Stop if exceeds the limit for total number of files
             if (result.size() > DIFF_FILE_LIMIT) {
                 break;
             }
app/views/partial_filediff.scala.html
--- app/views/partial_filediff.scala.html
+++ app/views/partial_filediff.scala.html
@@ -12,6 +12,7 @@
 @import scala.collection.immutable.List
 @import scala.collection.JavaConversions._
 @import org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+@import FileDiff.Error._
 
 @**
  * 변경 내용(Diff) 표시
@@ -23,10 +24,12 @@
   @if(diff.isFileModeChanged) {
     <tr><td class="linenum"><div class="line-number" data-line-num="@diff.oldMode"></div><span class="hidden">@diff.oldMode</span></td><td class="linenum"><div class="line-number" data-line-num="@diff.newMode"></div><span class="hidden">@diff.newMode</span></td><td class="isBinary">@Messages("code.fileModeChanged")</td></tr>
   }
-  @diff.getError match {
-    case FileDiff.Error.OTHERS_SIZE_EXCEEDED => { <tr><td colspan=3>@Messages("code.skipDiff")</td></tr> }
-    case FileDiff.Error.DIFF_SIZE_EXCEEDED => { <tr><td colspan=3>@Messages("code.tooBigDiff")</td></tr> }
-    case null => {
+  @null match {
+    case _ if diff.hasError(OTHERS_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.skipDiff")</td></tr> }
+    case _ if diff.hasAnyError(A_SIZE_EXCEEDED, B_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.tooBigFile")</td></tr> }
+    case _ if diff.hasError(DIFF_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.tooBigDiff")</td></tr> }
+    case _ if diff.hasError => { <tr><td colspan=3>@Messages("code.unknownError")</td></tr> }
+    case _ => {
       @diff.getHunks match {
         case hunks if (hunks.size <= 0) => {
           @diff.isFileModeChanged match {
@@ -42,7 +45,6 @@
         }
       }
     }
-    case _ => { <tr><td colspan=3>@Messages("code.unknownError")</td></tr> }
   }
 }
 
@@ -96,14 +98,14 @@
  * @param isBinaryOverwritten 기존 데이터가 바이너리인 경우 true
 **@
 @renderAddedLines(diff: FileDiff, comments:Map[String, List[_ <: CodeComment]], isBinaryOverwritten: Boolean = false) = {
-  @diff.getError match {
-    case FileDiff.Error.OTHERS_SIZE_EXCEEDED => { <tr><td colspan=3>@Messages("code.skipDiff")</td></tr> }
-    case FileDiff.Error.B_SIZE_EXCEEDED => { <tr><td colspan=3>@Messages("code.tooBigDiff")</td></tr> }
+  @null match {
+    case _ if diff.hasError(OTHERS_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.skipDiff")</td></tr> }
+    case _ if diff.hasError(B_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.tooBigFile")</td></tr> }
+    case _ if diff.hasError => { <tr><td colspan=3>@Messages("code.unknownError")</td></tr> }
     case null => { <tr class="range"><td class="linenum"><div class="line-number" data-line-num="..."><span class="hidden">...</span></div></td><td class="linenum"><div class="line-number" data-line-num="..."><span class="hidden">...</span></div></td><td class="hunk">@@@@ -0,0 +1,@diff.b.size @@@@</td></tr>
       @if(isBinaryOverwritten ) { @renderCodeIsBinary("remove", "-") }
       @renderRawFile("add", diff.pathB, diff.b, CodeComment.Side.B, "+", comments)
     }
-    case _ => { <tr><td colspan=3>@Messages("code.unknownError")</td></tr> }
   }
 }
 
@@ -115,14 +117,14 @@
  * @param isOverwrittenByBinary 새 데이터가 바이너리인 경우 true
 **@
 @renderRemovedLines(diff: FileDiff, comments:Map[String, List[_ <: CodeComment]], isOverwrittenByBinary: Boolean = false) = {
-  @diff.getError match {
-    case FileDiff.Error.OTHERS_SIZE_EXCEEDED => { <tr><td colspan=3>@Messages("code.skipDiff")</td></tr> }
-    case FileDiff.Error.A_SIZE_EXCEEDED => { <tr><td colspan=3>@Messages("code.tooBigDiff")</td></tr> }
-    case null => { <tr class="range"><td class="linenum"><div class="line-number" data-line-num="..."><span class="hidden">...</span></div></td><td class="linenum"><div class="line-number" data-line-num="..."><span class="hidden">...</span></div></td><td class="hunk">@@@@ -1,@diff.a.size +0,0 @@@@</td></tr>
+  @null match {
+    case _ if diff.hasError(OTHERS_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.skipDiff")</td></tr> }
+    case _ if diff.hasError(A_SIZE_EXCEEDED) => { <tr><td colspan=3>@Messages("code.tooBigFile")</td></tr> }
+    case _ if diff.hasError => { <tr><td colspan=3>@Messages("code.unknownError")</td></tr> }
+    case _ => { <tr class="range"><td class="linenum"><div class="line-number" data-line-num="..."><span class="hidden">...</span></div></td><td class="linenum"><div class="line-number" data-line-num="..."><span class="hidden">...</span></div></td><td class="hunk">@@@@ -1,@diff.a.size +0,0 @@@@</td></tr>
       @renderRawFile("remove", diff.pathA, diff.a, CodeComment.Side.A, "-", comments)
       @if(isOverwrittenByBinary) { @renderCodeIsBinary("add", "+") }
     }
-    case _ => { <tr><td colspan=3>@Messages("code.unknownError")</td></tr> }
   }
 }
 
conf/messages
--- conf/messages
+++ conf/messages
@@ -112,6 +112,7 @@
 code.showcomments = Show Comments
 code.skipDiff = This diff is skipped because others are too many.
 code.tooBigDiff = This diff is too big to show.
+code.tooBigFile = This file is too big to show.
 code.unknownError = Unknown error
 common.attach.attachIfYouSave = will be attached when you save
 common.attach.clickToPost = Click to post
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -112,6 +112,7 @@
 code.showcomments = 댓글 표시하기
 code.skipDiff = 다른 파일의 변경내역이 너무 많아서 이 파일의 변경내역은 생략합니다.
 code.tooBigDiff = 이 파일의 변경내역은 너무 커서 보여드릴 수 없습니다.
+code.tooBigFile = 이 파일은 너무 커서 보여드릴 수 없습니다.
 code.unknownError = 알 수 없는 에러가 발생하였습니다.
 common.attach.attachIfYouSave = 표시된 파일은 글을 저장하면 첨부됩니다.
 common.attach.clickToPost = 본문에 넣기
test/playRepository/GitRepositoryTest.java
--- test/playRepository/GitRepositoryTest.java
+++ test/playRepository/GitRepositoryTest.java
@@ -685,4 +685,173 @@
             return repository.open(treeWalk.getObjectId(0)).getBytes();
         }
     }
+
+    @Test
+    public void getDiff_bigFile() throws IOException, GitAPIException {
+        // given
+        String userName = "yobi";
+        String projectName = "testProject";
+        String wcPath = GitRepository.getRepoPrefix() + userName + "/" + projectName;
+
+        String repoPath = wcPath + "/.git";
+        File repoDir = new File(repoPath);
+        Repository repo = new RepositoryBuilder().setGitDir(repoDir).build();
+        repo.create(false);
+
+        Git git = new Git(repo);
+        String testFilePath = wcPath + "/readme.txt";
+        BufferedWriter out = new BufferedWriter(new FileWriter(testFilePath));
+
+        char[] cbuf = new char[FileDiff.SIZE_LIMIT + 1];
+        java.util.Arrays.fill(cbuf, 'a');
+        out.write(cbuf); // Add a big file
+        out.flush();
+        git.add().addFilepattern("readme.txt").call();
+        RevCommit commit = git.commit().setMessage("commit 1").call();
+
+        // When
+        FileDiff diff = GitRepository.getDiff(repo, commit).get(0);
+
+        // Then
+        assertThat(diff.a).describedAs("a").isNull();
+        assertThat(diff.b).describedAs("b").isNotNull();
+        assertThat(diff.hasError(FileDiff.Error.A_SIZE_EXCEEDED))
+            .describedAs("a exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.B_SIZE_EXCEEDED))
+            .describedAs("b exceeds the size limit.").isTrue();
+        assertThat(diff.hasError(FileDiff.Error.DIFF_SIZE_EXCEEDED))
+            .describedAs("The diff exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.OTHERS_SIZE_EXCEEDED))
+            .describedAs("The others exceeds the size limit.").isFalse();
+    }
+
+    @Test
+    public void getDiff_manyLines() throws IOException, GitAPIException {
+        // given
+        String userName = "yobi";
+        String projectName = "testProject";
+        String wcPath = GitRepository.getRepoPrefix() + userName + "/" + projectName;
+
+        String repoPath = wcPath + "/.git";
+        File repoDir = new File(repoPath);
+        Repository repo = new RepositoryBuilder().setGitDir(repoDir).build();
+        repo.create(false);
+
+        Git git = new Git(repo);
+        String testFilePath = wcPath + "/readme.txt";
+        BufferedWriter out = new BufferedWriter(new FileWriter(testFilePath));
+
+        for (int i = 0; i < FileDiff.LINE_LIMIT + 1; i++) {
+            out.write("a\n"); // Add a big file
+        }
+        out.flush();
+        git.add().addFilepattern("readme.txt").call();
+        RevCommit commit = git.commit().setMessage("commit 1").call();
+
+        // When
+        FileDiff diff = GitRepository.getDiff(repo, commit).get(0);
+
+        // Then
+        assertThat(diff.hasError(FileDiff.Error.A_SIZE_EXCEEDED))
+            .describedAs("a exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.B_SIZE_EXCEEDED))
+            .describedAs("b exceeds the size limit.").isTrue();
+        assertThat(diff.hasError(FileDiff.Error.DIFF_SIZE_EXCEEDED))
+            .describedAs("The diff exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.OTHERS_SIZE_EXCEEDED))
+            .describedAs("The others exceeds the size limit.").isFalse();
+
+    }
+
+    @Test
+    public void getDiff_smallChangeOfBigFile() throws IOException, GitAPIException {
+        // given
+        String userName = "yobi";
+        String projectName = "testProject";
+        String wcPath = GitRepository.getRepoPrefix() + userName + "/" + projectName;
+
+        String repoPath = wcPath + "/.git";
+        File repoDir = new File(repoPath);
+        Repository repo = new RepositoryBuilder().setGitDir(repoDir).build();
+        repo.create(false);
+
+        Git git = new Git(repo);
+        String testFilePath = wcPath + "/readme.txt";
+        BufferedWriter out = new BufferedWriter(new FileWriter(testFilePath));
+
+        // Commit a big file
+        for (int i = 0; i < FileDiff.LINE_LIMIT + 1; i++) {
+            out.write("a\n"); // Add a big file
+        }
+        out.flush();
+        git.add().addFilepattern("readme.txt").call();
+        git.commit().setMessage("commit 1").call();
+
+        // Modify the file
+        out.write("b\n");
+        out.flush();
+        git.add().addFilepattern("readme.txt").call();
+        RevCommit commit = git.commit().setMessage("commit 2").call();
+
+        // When
+        FileDiff diff = GitRepository.getDiff(repo, commit).get(0);
+
+        // Then
+        assertThat(diff.hasError(FileDiff.Error.A_SIZE_EXCEEDED))
+            .describedAs("a exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.B_SIZE_EXCEEDED))
+            .describedAs("b exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.DIFF_SIZE_EXCEEDED))
+            .describedAs("The diff exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.OTHERS_SIZE_EXCEEDED))
+            .describedAs("The others exceeds the size limit.").isFalse();
+    }
+
+    @Test
+    public void getDiff_manyFiles() throws IOException, GitAPIException {
+        // given
+        String userName = "yobi";
+        String projectName = "testProject";
+        String wcPath = GitRepository.getRepoPrefix() + userName + "/" + projectName;
+
+        String repoPath = wcPath + "/.git";
+        File repoDir = new File(repoPath);
+        Repository repo = new RepositoryBuilder().setGitDir(repoDir).build();
+        repo.create(false);
+
+        Git git = new Git(repo);
+
+        // Add four big files
+        for(int i = 0; i < 4; i++) {
+            String testFilePath = wcPath + "/" + i + ".txt";
+            BufferedWriter out = new BufferedWriter(new FileWriter(testFilePath));
+            char[] cbuf = new char[FileDiff.SIZE_LIMIT - 1];
+            java.util.Arrays.fill(cbuf, 'a');
+            out.write(cbuf);
+            out.flush();
+            git.add().addFilepattern(i + ".txt").call();
+        }
+
+        // Add a small file
+        String testFilePath = wcPath + "/readme.txt";
+        BufferedWriter out = new BufferedWriter(new FileWriter(testFilePath));
+        out.write("hello");
+        out.flush();
+        git.add().addFilepattern("readme.txt").call();
+        RevCommit commit = git.commit().setMessage("commit 1").call();
+
+        // When
+        List<FileDiff> diffs = GitRepository.getDiff(repo, commit);
+        FileDiff diff = diffs.get(4);
+
+        // Then
+        assertThat(diff.hasError(FileDiff.Error.A_SIZE_EXCEEDED))
+            .describedAs("a exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.B_SIZE_EXCEEDED))
+            .describedAs("b exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.DIFF_SIZE_EXCEEDED))
+            .describedAs("The diff exceeds the size limit.").isFalse();
+        assertThat(diff.hasError(FileDiff.Error.OTHERS_SIZE_EXCEEDED))
+            .describedAs("The others exceeds the size limit.").isTrue();
+    }
 }
Add a comment
List