백기선 2013-10-30
Merge pull request #184 from npcode/yobi refs/heads/fix/comment-on-noChangeBetween
@5ee54bb8f4932308ae006cb6cbb9afa5803678b1
app/assets/stylesheets/less/_page.less
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
@@ -2734,7 +2734,6 @@
 
                     .yobicon-paperclip {vertical-align:middle;margin-top: 4px;}
 
-
                 }
                 .comment-body {
                     font-size: 12px;
@@ -2749,6 +2748,7 @@
                         .border-radius(2px);
                     }
                 }
+
             }
             .attach-preview-wrap {
                 margin-top: 10px;
@@ -2762,8 +2762,32 @@
                 .meta-info { background-color:#f8f8f8; }
 
                 .attach { background-color:#f8f8f8; }
-            }        
+            }
+        }
 
+        .reply {
+            padding:0 20px;
+            .media-body {
+                border-top:0px;
+                .border-radius(0 0 0 0);
+                &:before {
+                    content:none;
+                }
+            }
+        }
+
+        .hasReply {
+            padding-bottom:0px;
+            .media-body {
+                .border-radius(3px 3px 0 0);
+            }
+        }
+
+        .last {
+            padding-bottom:10px;
+            .media-body {
+                .border-radius(0 0 3px 3px);
+            }
         }
 
         .event {
@@ -2771,7 +2795,6 @@
             padding:15px 0 15px 75px;
             font-size:14px;
             line-height:14px;
-
             .state { 
                 color:#FFF;
                 padding: 6px 0;
@@ -4585,4 +4608,4 @@
 
 div.diff-body[data-outdated="true"] tr:hover .icon-comment {
     visibility: hidden;
-}
(No newline at end of file)
+}
app/controllers/CodeApp.java
--- app/controllers/CodeApp.java
+++ app/controllers/CodeApp.java
@@ -70,8 +70,8 @@
      * @param branch 브랜치 이름
      * @param path 파일 경로
      */
-	public static Result codeBrowserWithBranch(String userName, String projectName, String branch, String path)
-	    throws UnsupportedOperationException, IOException, SVNException, GitAPIException, ServletException, Exception {
+    public static Result codeBrowserWithBranch(String userName, String projectName, String branch, String path)
+        throws UnsupportedOperationException, IOException, SVNException, GitAPIException, ServletException, Exception {
         Project project = ProjectApp.getProject(userName, projectName);
 
         if (!RepositoryService.VCS_GIT.equals(project.vcs) && !RepositoryService.VCS_SUBVERSION.equals(project.vcs)) {
@@ -93,14 +93,14 @@
         
         return ok(view.render(project, branches, recursiveData, branch, path));
     }
-	
-	/**
-	 * AJAX 호출로 지정한 프로젝트 지정한 경로의 정보를 얻고자 할 때 사용된다
-	 * 
-	 * @param userName 프로젝트 소유자 이름
-	 * @param projectName 프로젝트 이름
-	 * @param path 파일 또는 폴더의 경로
-	 */
+    
+    /**
+     * AJAX 호출로 지정한 프로젝트 지정한 경로의 정보를 얻고자 할 때 사용된다
+     * 
+     * @param userName 프로젝트 소유자 이름
+     * @param projectName 프로젝트 이름
+     * @param path 파일 또는 폴더의 경로
+     */
     public static Result ajaxRequest(String userName, String projectName, String path) throws Exception{
         PlayRepository repository = RepositoryService.getRepository(userName, projectName);
         ObjectNode fileInfo = repository.getMetaDataFromPath(path);
app/controllers/HelpApp.java
--- app/controllers/HelpApp.java
+++ app/controllers/HelpApp.java
@@ -5,7 +5,7 @@
 import views.html.help.*;
 
 public class HelpApp extends Controller {
-	public static Result help() {
-		return ok(toc.render("title.help"));
-	}
+    public static Result help() {
+        return ok(toc.render("title.help"));
+    }
 }
app/controllers/ProjectApp.java
--- app/controllers/ProjectApp.java
+++ app/controllers/ProjectApp.java
@@ -44,21 +44,21 @@
  */
 public class ProjectApp extends Controller {
 
-	private static final int LOGO_FILE_LIMIT_SIZE = 1024*1000*5; //5M
+    private static final int LOGO_FILE_LIMIT_SIZE = 1024*1000*5; //5M
 
-	/** 프로젝트 로고로 사용할 수 있는 이미지 확장자 */
+    /** 프로젝트 로고로 사용할 수 있는 이미지 확장자 */
     public static final String[] LOGO_TYPE = {"jpg", "jpeg", "png", "gif", "bmp"};
 
     /** 자동완성에서 보여줄 최대 프로젝트 개수 */
     private static final int MAX_FETCH_PROJECTS = 1000;
 
-	private static final int COMMIT_HISTORY_PAGE = 0;
+    private static final int COMMIT_HISTORY_PAGE = 0;
 
-	private static final int COMMIT_HISTORY_SHOW_LIMIT = 10;
+    private static final int COMMIT_HISTORY_SHOW_LIMIT = 10;
 
-	private static final int RECENLTY_ISSUE_SHOW_LIMIT = 10;
+    private static final int RECENLTY_ISSUE_SHOW_LIMIT = 10;
 
-	private static final int RECENLTY_POSTING_SHOW_LIMIT = 10;
+    private static final int RECENLTY_POSTING_SHOW_LIMIT = 10;
 
     private static final int RECENT_PULL_REQUEST_SHOW_LIMIT = 10;
 
@@ -100,7 +100,7 @@
 
         if (project == null) {
             return notFound(ErrorViews.NotFound.render("error.notfound"));
-			// No project matches given parameters'" + loginId + "' and project_name '" + projectName + "'"));
+            // No project matches given parameters'" + loginId + "' and project_name '" + projectName + "'"));
         }
 
         project.fixInvalidForkData();
@@ -113,9 +113,9 @@
 
         List<Commit> commits = null;
         try {
-			commits = repository.getHistory(COMMIT_HISTORY_PAGE, COMMIT_HISTORY_SHOW_LIMIT, null, null);
+            commits = repository.getHistory(COMMIT_HISTORY_PAGE, COMMIT_HISTORY_SHOW_LIMIT, null, null);
         } catch (NoHeadException e) {
-		// NOOP
+        // NOOP
         }
 
         List<Issue> issues = Issue.findRecentlyCreated(project, RECENLTY_ISSUE_SHOW_LIMIT);
@@ -127,7 +127,7 @@
         return ok(overview.render("title.projectHome", project, histories));
     }
 
-	/**
+    /**
      * 신규 프로젝트 생성 페이지로 이동한다.<p />
      *
      * 비로그인 상태({@link models.User#anonymous})이면 로그인 경고메세지와 함께 로그인페이지로 redirect 된다.<br />
@@ -273,9 +273,9 @@
      * @param filePart
      * @return {@code filePart}가 null이면 true, {@code filename}이 null이면 true, {@code fileLength}가 0 이하이면 true
      */
-	private static boolean isEmptyFilePart(FilePart filePart) {
-		return filePart == null || filePart.getFilename() == null || filePart.getFilename().length() <= 0;
-	}
+    private static boolean isEmptyFilePart(FilePart filePart) {
+        return filePart == null || filePart.getFilename() == null || filePart.getFilename().length() <= 0;
+    }
 
     /**
      * {@code filename}의 확장자를 체크하여 이미지인지 확인한다.<p />
@@ -321,7 +321,7 @@
     /**
      * 프로젝트를 삭제한다.<p />
      *
-	 * {@code loginId}와 {@code projectName}으로 프로젝트 정보를 가져온다.<br />
+     * {@code loginId}와 {@code projectName}으로 프로젝트 정보를 가져온다.<br />
      * 삭제 권한이 없을 경우는 경고 메시지와 함께 설정페이지로 redirect된다. <br />
      *
      * @param loginId the user login id
app/controllers/PullRequestApp.java
--- app/controllers/PullRequestApp.java
+++ app/controllers/PullRequestApp.java
@@ -174,11 +174,11 @@
 
     /**
      * {@code userName}과 {@code projectName}에 해당하는 프로젝트의 원본 프로젝트로 코드를 보낼 수 있는 코드 보내기 폼을 보여준다.
-     * 
+     *
      * 코드 보내기 폼에서 보내려는 브랜치과 코드를 받을 브랜치를 선택할 수 있도록 브랜치 목록을 보여준다.
      * 보내는 브랜치(fromBranch)와 받는 브랜치(toBranch) 파라미터가 있을 경우 두개의 브랜치를 merge해보고 결과를 반환한다.
      * ajax 요청일 경우 partial로 렌더링한다.
-     * 
+     *
      * @param userName
      * @param projectName
      * @return
@@ -195,29 +195,29 @@
 
         List<String> fromBranches = RepositoryService.getRepository(project).getBranches();
         List<String> toBranches = RepositoryService.getRepository(project.originalProject).getBranches();
-    
+
         PullRequest pullRequest = new PullRequest();
         pullRequest.toProject = project.originalProject;
         pullRequest.fromProject = project;
         pullRequest.fromBranch = request().getQueryString("fromBranch");
         pullRequest.toBranch = request().getQueryString("toBranch");
-        
+
         PullRequestMergeResult mergeResult = null;
-        
+
         if (!StringUtils.isEmpty(pullRequest.fromBranch) && !StringUtils.isEmpty(pullRequest.toBranch)) {
             mergeResult = pullRequest.attemptMerge();
             Map<String, String> suggestText = suggestTitleAndBodyFromDiffCommit(mergeResult.getGitCommits());
             pullRequest.title = suggestText.get("title");
             pullRequest.body = suggestText.get("body");
-        } 
-    
+        }
+
         String xRequested = request().getHeader("X-Requested-With");
-        
+
         if (!StringUtils.isEmpty(xRequested)) {
             response().setHeader("Cache-Control", "no-cache, no-store");
             return ok(partial_diff.render(new Form<>(PullRequest.class).fill(pullRequest), project, mergeResult, pullRequest));
-        } 
-        
+        }
+
         return ok(create.render("title.newPullRequest", new Form<>(PullRequest.class).fill(pullRequest), project, fromBranches, toBranches, mergeResult, pullRequest));
     }
 
@@ -271,24 +271,24 @@
         Attachment.moveAll(UserApp.currentUser().asResource(), pullRequest.asResource());
 
         Call pullRequestCall = routes.PullRequestApp.pullRequest(originalProject.owner, originalProject.name, pullRequest.number);
-        
+
         NotificationEvent notiEvent = NotificationEvent.addNewPullRequest(pullRequestCall, request(), pullRequest);
         PullRequestEvent.addEvent(notiEvent, pullRequest);
- 
+
         PullRequestEventMessage message = new PullRequestEventMessage(
                 UserApp.currentUser(), request(), pullRequest.toProject, pullRequest.toBranch);
         Akka.system().actorOf(new Props(PullRequestEventActor.class)).tell(message, null);
-        
+
         return redirect(pullRequestCall);
     }
-    
+
     /**
      * diff commit message로 pull request의 title과 body를 채운다.<br>
      * <br>
      * case 1 : commit이 한개이고 message가 한줄일 경우 title에 추가한다.<br>
      * case 2 : commit이 한개이고 message가 여러줄일 경우 첫번째줄 mesage는 title 나머지 message는 body에 추가<br>
      * case 3 : commit이 여러개일 경우 각 commit의 첫번째줄 message들을 모아서 body에 추가 <br>
-     * 
+     *
      * @param commits
      * @return
      */
@@ -299,21 +299,21 @@
 
         if (commits.isEmpty()) {
             return messageMap;
-            
+
         } else if (commits.size() == 1) {
-            message = commits.get(0).getMessage();            
+            message = commits.get(0).getMessage();
             String[] messages = message.split(NEW_LINE_DELIMETER);
 
             if (messages.length > 1) {
                 String[] msgs = Arrays.copyOfRange(messages, 1, messages.length);
                 messageMap.put("title", messages[0]);
                 messageMap.put("body", StringUtils.join(msgs, NEW_LINE_DELIMETER));
-                         
+
             } else {
                 messageMap.put("title", messages[0]);
                 messageMap.put("body", StringUtils.EMPTY);
             }
-            
+
         } else {
             String[] firstMessages = new String[commits.size()];
             for (int i = 0; i < commits.size(); i++) {
@@ -321,13 +321,13 @@
                 firstMessages[i] = messages[0];
             }
             messageMap.put("body", StringUtils.join(firstMessages, NEW_LINE_DELIMETER));
-            
+
         }
 
         return messageMap;
     }
-    
-    
+
+
     private static void validateForm(Form<PullRequest> form) {
         Map<String, String> data = form.data();
         ValidationUtils.rejectIfEmpty(flash(), data.get("fromBranch"), "pullRequest.fromBranch.required");
@@ -424,10 +424,10 @@
         if(activeTab == null && !isValid(activeTab)) {
             activeTab = "info";
         }
-        
+
         boolean canDeleteBranch = false;
         boolean canRestoreBranch = false;
-        
+
         if (pullRequest.isClosed()) {
             canDeleteBranch = GitRepository.canDeleteFromBranch(pullRequest);
             canRestoreBranch = GitRepository.canRestoreBranch(pullRequest);
@@ -470,8 +470,39 @@
             return result;
         }
 
-        List<GitCommit> commits = GitRepository.getPullingCommits(pullRequest);
-        return ok(viewCommits.render(project, pullRequest, commits));
+        return ok(viewCommits.render(project, pullRequest));
+    }
+
+    /**
+     * {@code userName}과 {@code projectName}에 해당하는 프로젝트로 들어온
+     * {@code pullRequestId}에 해당하는 코드 요청의 변경내역을 조회한다.
+     *
+     * @param userName
+     * @param projectName
+     * @param pullRequestNumber
+     * @return
+     */
+    public static Result pullRequestChanges(String userName, String projectName, long pullRequestNumber) {
+        Project project = Project.findByOwnerAndProjectName(userName, projectName);
+        PullRequest pullRequest = PullRequest.findOne(project, pullRequestNumber);
+
+        Result result = validatePullRequest(project, pullRequest, userName, projectName, pullRequestNumber);
+        if(result != null) {
+            return result;
+        }
+
+        List<PullRequestComment> comments = new ArrayList<>();
+
+        for (PullRequestComment comment : pullRequest.comments) {
+            if (comment.hasValidCommitId()) {
+                if (comment.commitId == null) {
+                    comment.commitId = comment.commitB;
+                }
+                comments.add(comment);
+            }
+        }
+
+        return ok(viewChanges.render(project, pullRequest, comments));
     }
 
     /**
@@ -494,7 +525,7 @@
         }
 
         pullRequest.merge();
-        
+
         // merge이후 관련 pullRequest의 상태를 체크한다.
         PullRequestEventMessage message = new PullRequestEventMessage(
                 UserApp.currentUser(), request(), project, pullRequest.toBranch);
app/controllers/StatisticsApp.java
--- app/controllers/StatisticsApp.java
+++ app/controllers/StatisticsApp.java
@@ -7,11 +7,11 @@
 import views.html.project.statistics;
 
 public class StatisticsApp extends Controller {
-	public static Result statistics(String userName, String projectName) {
-		Project project = Project.findByOwnerAndProjectName(userName, projectName);
-		if (project == null) {
-		    return notFound(ErrorViews.NotFound.render("error.notfound"));
-		}
-		return ok(statistics.render("statistics", project));
-	}
+    public static Result statistics(String userName, String projectName) {
+        Project project = Project.findByOwnerAndProjectName(userName, projectName);
+        if (project == null) {
+            return notFound(ErrorViews.NotFound.render("error.notfound"));
+        }
+        return ok(statistics.render("statistics", project));
+    }
 }
app/models/AbstractPosting.java
--- app/models/AbstractPosting.java
+++ app/models/AbstractPosting.java
@@ -161,26 +161,26 @@
 
     public Resource asResource(final ResourceType type) {
         return new Resource() {
-	        @Override
-	        public String getId() {
-	            return id.toString();
-	        }
+            @Override
+            public String getId() {
+                return id.toString();
+            }
 
-	        @Override
-	        public Project getProject() {
-	            return project;
-	        }
+            @Override
+            public Project getProject() {
+                return project;
+            }
 
-	        @Override
-	        public ResourceType getType() {
+            @Override
+            public ResourceType getType() {
                 return type;
-	        }
+            }
 
             @Override
             public Long getAuthorId() {
                 return authorId;
             }
-	    };
+        };
     }
 
     /**
app/models/CommitComment.java
--- app/models/CommitComment.java
+++ app/models/CommitComment.java
@@ -14,6 +14,8 @@
     private static final long serialVersionUID = 1L;
     public static final Finder<Long, CommitComment> find = new Finder<>(Long.class, CommitComment.class);
 
+    public List<CommitComment> replies = new ArrayList<>();
+    
     public CommitComment() {
         super();
     }
@@ -80,4 +82,16 @@
         }
         return list;
     }
+
+    /**
+     * CommitComment의 groupKey를 반환한다.
+     * commitId, path, line정보를 조한한 키가 일치할 경우 동일한 내용에 대한
+     * 코멘트로 간주한다.
+     * 
+     * @return
+     */
+    public String groupKey() {
+        return new StringBuilder().append(this.commitId)
+                .append(this.path).append(this.line).toString();
+    }
 }
app/models/Issue.java
--- app/models/Issue.java
+++ app/models/Issue.java
@@ -253,13 +253,13 @@
     // FIXME 이것이 없이 테스트는 잘 작동하나, view에서 댓글이 달린 이슈들을 필터링하는 라디오버튼을 작동시에 이 메쏘드에서
     // 시행하는 동기화 작업 없이는 작동을 하지 않는다.
 
-	public boolean isOpen() {
-	    return this.state == State.OPEN;
-	}
+    public boolean isOpen() {
+        return this.state == State.OPEN;
+    }
 
-	public boolean isClosed() {
-	    return this.state == State.CLOSED;
-	}
+    public boolean isClosed() {
+        return this.state == State.CLOSED;
+    }
 
     @Override
     public Resource asResource() {
app/models/Milestone.java
--- app/models/Milestone.java
+++ app/models/Milestone.java
@@ -65,11 +65,11 @@
     }
 
     public int getNumClosedIssues() {
-    	return Issue.finder.where().eq("milestone", this).eq("state", State.CLOSED).findRowCount();
+        return Issue.finder.where().eq("milestone", this).eq("state", State.CLOSED).findRowCount();
     }
 
     public int getNumOpenIssues() {
-    	return Issue.finder.where().eq("milestone", this).eq("state", State.OPEN).findRowCount();
+        return Issue.finder.where().eq("milestone", this).eq("state", State.OPEN).findRowCount();
     }
 
     public List<Issue> sortedByNumberOfIssue(){
@@ -182,11 +182,11 @@
     public static List<Milestone> findMilestones(Long projectId,
                                                  State state, String sort, final Direction direction) {
 
-    	OrderParams orderParams = new OrderParams();
+        OrderParams orderParams = new OrderParams();
 
-    	if(!"completionRate".equals(sort)) {
-		orderParams.add(sort, direction);
-    	}
+        if(!"completionRate".equals(sort)) {
+        orderParams.add(sort, direction);
+        }
 
         SearchParams searchParams = new SearchParams().add("project.id", projectId, Matching.EQUALS);
         if(state != null && state != State.ALL) {
@@ -196,19 +196,19 @@
         List<Milestone> milestones = FinderTemplate.findBy(orderParams, searchParams, find);
 
         if("completionRate".equals(sort)) {
-        	Collections.sort(milestones, new Comparator<Milestone>() {
-				@Override
-				public int compare(Milestone o1, Milestone o2) {
-					int o1CompletionRate = o1.getCompletionRate();
-					int o2CompletionRate = o2.getCompletionRate();
+            Collections.sort(milestones, new Comparator<Milestone>() {
+                @Override
+                public int compare(Milestone o1, Milestone o2) {
+                    int o1CompletionRate = o1.getCompletionRate();
+                    int o2CompletionRate = o2.getCompletionRate();
 
-					if(direction == Direction.ASC) {
-				        return (o1CompletionRate < o2CompletionRate ? -1 : (o1CompletionRate == o2CompletionRate ? 0 : 1));
-					} else {
-						return (o1CompletionRate < o2CompletionRate ? 1 : (o1CompletionRate == o2CompletionRate ? 0 : -1));
-					}
-				}
-			});
+                    if(direction == Direction.ASC) {
+                        return (o1CompletionRate < o2CompletionRate ? -1 : (o1CompletionRate == o2CompletionRate ? 0 : 1));
+                    } else {
+                        return (o1CompletionRate < o2CompletionRate ? 1 : (o1CompletionRate == o2CompletionRate ? 0 : -1));
+                    }
+                }
+            });
         }
 
         return milestones;
app/models/PullRequest.java
--- app/models/PullRequest.java
+++ app/models/PullRequest.java
@@ -11,31 +11,15 @@
 import models.resource.Resource;
 import models.resource.ResourceConvertible;
 
-import org.apache.commons.lang.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.jgit.api.MergeResult;
 import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryBuilder;
 
-import org.joda.time.DateTimeConstants;
-import org.eclipse.jgit.diff.*;
-import org.eclipse.jgit.lib.ConfigConstants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.io.NullOutputStream;
 import org.joda.time.Duration;
 
-import com.avaje.ebean.Ebean;
 import com.avaje.ebean.Expr;
-import com.avaje.ebean.RawSql;
-import com.avaje.ebean.RawSqlBuilder;
-import com.avaje.ebean.SqlQuery;
-import com.avaje.ebean.SqlRow;
 
 import play.data.validation.Constraints;
 import play.db.ebean.Model;
@@ -48,29 +32,14 @@
 import playRepository.GitRepository.CloneAndFetch;
 import utils.AccessControl;
 import utils.Constants;
-import utils.GravatarUtil;
 import utils.JodaDateUtil;
 import utils.WatchService;
 
 import javax.persistence.*;
 import javax.validation.constraints.Size;
 
+import java.io.IOException;
 import java.util.*;
-import java.util.regex.Matcher;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.io.IOException;
-import java.io.OutputStream;
 import java.io.File;
 
 @Entity
@@ -610,7 +579,7 @@
      * @return
      */
     @Transient
-    public List<CommitComment> getCodeComments() {
+    public List<CommitComment> getCommitComments() {
         return CommitComment.findByCommits(fromProject, pullRequestCommits);
     }
     
@@ -625,15 +594,17 @@
     
     /**
      * pull request의 모든 코멘트 정보를 가져오고 시간순으로 정렬 후 반환한다. (코멘트 + 코드코멘트 + 이벤트 )
+     * 
      * @return
      */
     @Transient
     public List<TimelineItem> getTimelineComments() {
-        List<CommitComment> codeComments = getCodeComments();
-        
+        List<CommitComment> commitComment 
+                        = computeCommitCommentReplies(getCommitComments());
+
         List<TimelineItem> timelineComments = new ArrayList<>();
         timelineComments.addAll(comments);
-        timelineComments.addAll(codeComments);
+        timelineComments.addAll(commitComment);
         timelineComments.addAll(pullRequestEvents);
 
         Collections.sort(timelineComments, new Comparator<TimelineItem>() {
@@ -641,12 +612,97 @@
             public int compare(TimelineItem o1, TimelineItem o2) {
                 return o1.getDate().compareTo(o2.getDate());
             }
-            
+
         });
-        
+
         return timelineComments;
     }
 
+    /**
+     * 전체 코멘트중 부모글과 답글 정보를 재할당한다.
+     * @param commitComments
+     * @return
+     */
+    private List<CommitComment> computeCommitCommentReplies(
+            List<CommitComment> commitComments) {
+        return reAssignReplyComments(sameTopicCommentGroups(commitComments));
+    }
+
+    /**
+     * 답글목록을 부모글의 필드로 재할당한다.
+     * 
+     * commentGroup은 등록일순으로 오름차순 정렬되어 있는 상태이며
+     * 목록의 첫번째 코멘트를 부모글로 판단한다.
+     * 
+     * @param commentGroup
+     * @return
+     */
+    private List<CommitComment> reAssignReplyComments(
+            Map<String, List<CommitComment>> commentGroup) {
+        List<CommitComment> parentCommitComments = new ArrayList<>();
+
+        for (List<CommitComment> commitComments : commentGroup.values()) {
+            CommitComment parentComment = commitComments.get(0);
+            if (hasReply(commitComments)) {
+                parentComment.replies = replies(commitComments);
+            }
+            parentCommitComments.add(parentComment);
+        }
+        return parentCommitComments;
+    }
+
+    /**
+     * 답글 목록을 반환한다.
+     * @param commitComments
+     * @return
+     */
+    private List<CommitComment> replies(List<CommitComment> commitComments) {
+        return commitComments.subList(1, commitComments.size());
+    }
+
+    /**
+     * 답글 유무를 체크한다.
+     * @param commitComments
+     * @return
+     */
+    private boolean hasReply(List<CommitComment> commitComments) {
+        return commitComments.size() > 1;
+    }
+
+    /**
+     * groupKey를 통해 같은 코멘트그룹 목록을 반환한다.
+     * (같은 커밋, 같은 파일, 같은 라인의 덧글들)
+     * @param commitComments
+     * @return
+     */
+    private Map<String, List<CommitComment>> sameTopicCommentGroups(
+            List<CommitComment> commitComments) {
+        Map<String, List<CommitComment>> commentGroup = new HashMap<>();
+        for (CommitComment commitComment : commitComments) {
+            commentGroup.put(
+                    commitComment.groupKey(),
+                    commitCommentsGroupByKey(commitComment.groupKey(),
+                            commitComments));
+        }
+        return commentGroup;
+    }
+
+    /**
+     * groupKey를 통해 같은 코멘트그룹을 반환한다.
+     * @param groupKey
+     * @param codeComments
+     * @return
+     */
+    private List<CommitComment> commitCommentsGroupByKey(String groupKey,
+            List<CommitComment> codeComments) {
+        List<CommitComment> commitCommentGroups = new ArrayList<CommitComment>();
+        for (CommitComment commitComment : codeComments) {
+            if (commitComment.groupKey().equals(groupKey)) {
+                commitCommentGroups.add(commitComment);
+            }
+        }
+        return commitCommentGroups;
+    }
 
     /**
      * 보낸 코드를 병합해보고 결과 정보를 반환한다.
app/models/support/Options.java
--- app/models/support/Options.java
+++ app/models/support/Options.java
@@ -3,11 +3,11 @@
 import java.util.*;
 
 public class Options extends LinkedHashMap<String, String> {
-	private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 1L;
 
-	public Options(String... args) {
-		for (int idx = 0; idx < args.length; idx++) {
-			this.put(String.valueOf(idx + 1), args[idx]);
-		}
-	}
+    public Options(String... args) {
+        for (int idx = 0; idx < args.length; idx++) {
+            this.put(String.valueOf(idx + 1), args[idx]);
+        }
+    }
 }
app/playRepository/FileDiff.java
--- app/playRepository/FileDiff.java
+++ app/playRepository/FileDiff.java
@@ -29,81 +29,81 @@
     /**
      * Get list of hunks
      *
-	 * @throws java.io.IOException
-	 */
-	public List<Hunk> getHunks()
-			throws IOException {
+     * @throws java.io.IOException
+     */
+    public List<Hunk> getHunks()
+            throws IOException {
 
         List<Hunk> hunks = new ArrayList<>();
 
-		for (int curIdx = 0; curIdx < editList.size();) {
+        for (int curIdx = 0; curIdx < editList.size();) {
             Hunk hunk = new Hunk();
-			Edit curEdit = editList.get(curIdx);
-			final int endIdx = findCombinedEnd(editList, curIdx);
-			final Edit endEdit = editList.get(endIdx);
+            Edit curEdit = editList.get(curIdx);
+            final int endIdx = findCombinedEnd(editList, curIdx);
+            final Edit endEdit = editList.get(endIdx);
 
-			int aCur = Math.max(0, curEdit.getBeginA() - context);
-			int bCur = Math.max(0, curEdit.getBeginB() - context);
-			final int aEnd = Math.min(a.size(), endEdit.getEndA() + context);
-			final int bEnd = Math.min(b.size(), endEdit.getEndB() + context);
+            int aCur = Math.max(0, curEdit.getBeginA() - context);
+            int bCur = Math.max(0, curEdit.getBeginB() - context);
+            final int aEnd = Math.min(a.size(), endEdit.getEndA() + context);
+            final int bEnd = Math.min(b.size(), endEdit.getEndB() + context);
 
             hunk.beginA = aCur;
             hunk.endA = aEnd;
             hunk.beginB = bCur;
             hunk.endB = bEnd;
 
-			while (aCur < aEnd || bCur < bEnd) {
-				if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
+            while (aCur < aEnd || bCur < bEnd) {
+                if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
                     hunk.lines.add(new DiffLine(this, DiffLineType.CONTEXT, aCur, bCur,
                             a.getString(aCur)));
-					isEndOfLineMissing = checkEndOfLineMissing(a, aCur);
-					aCur++;
-					bCur++;
-				} else if (aCur < curEdit.getEndA()) {
+                    isEndOfLineMissing = checkEndOfLineMissing(a, aCur);
+                    aCur++;
+                    bCur++;
+                } else if (aCur < curEdit.getEndA()) {
                     hunk.lines.add(new DiffLine(this, DiffLineType.REMOVE, aCur, bCur,
                             a.getString(aCur)));
                     isEndOfLineMissing = checkEndOfLineMissing(a, aCur);
-					aCur++;
-				} else if (bCur < curEdit.getEndB()) {
+                    aCur++;
+                } else if (bCur < curEdit.getEndB()) {
                     hunk.lines.add(new DiffLine(this, DiffLineType.ADD, aCur, bCur,
                             b.getString(bCur)));
                     isEndOfLineMissing = checkEndOfLineMissing(a, aCur);
-					bCur++;
-				}
+                    bCur++;
+                }
 
-				if (end(curEdit, aCur, bCur) && ++curIdx < editList.size())
-					curEdit = editList.get(curIdx);
-			}
+                if (end(curEdit, aCur, bCur) && ++curIdx < editList.size())
+                    curEdit = editList.get(curIdx);
+            }
 
             hunks.add(hunk);
-		}
+        }
 
         return hunks;
-	}
+    }
 
     private int findCombinedEnd(final List<Edit> edits, final int i) {
-		int end = i + 1;
-		while (end < edits.size()
-				&& (combineA(edits, end) || combineB(edits, end)))
-			end++;
-		return end - 1;
-	}
+        int end = i + 1;
+        while (end < edits.size()
+                && (combineA(edits, end) || combineB(edits, end)))
+            end++;
+        return end - 1;
+    }
 
     private boolean combineA(final List<Edit> e, final int i) {
-		return e.get(i).getBeginA() - e.get(i - 1).getEndA() <= 2 * context;
-	}
+        return e.get(i).getBeginA() - e.get(i - 1).getEndA() <= 2 * context;
+    }
 
-	private boolean combineB(final List<Edit> e, final int i) {
-		return e.get(i).getBeginB() - e.get(i - 1).getEndB() <= 2 * context;
-	}
+    private boolean combineB(final List<Edit> e, final int i) {
+        return e.get(i).getBeginB() - e.get(i - 1).getEndB() <= 2 * context;
+    }
 
-	private static boolean end(final Edit edit, final int a, final int b) {
-		return edit.getEndA() <= a && edit.getEndB() <= b;
-	}
+    private static boolean end(final Edit edit, final int a, final int b) {
+        return edit.getEndA() <= a && edit.getEndB() <= b;
+    }
 
     private boolean checkEndOfLineMissing(final RawText text, final int line) {
-		return line + 1 == text.size() && text.isMissingNewlineAtEnd();
-	}
+        return line + 1 == text.size() && text.isMissingNewlineAtEnd();
+    }
 
     /**
      * 주어진 줄 번호와 관련된 diff만 남기고 나머지는 모두 버린다.
app/utils/Constants.java
--- app/utils/Constants.java
+++ app/utils/Constants.java
@@ -1,9 +1,9 @@
 package utils;
 
 public class Constants {
-	public static final String WARNING = "warning";
-	public static final String INFO = "info";
-	public static final String SUCCESS = "success";
+    public static final String WARNING = "warning";
+    public static final String INFO = "info";
+    public static final String SUCCESS = "success";
 
     public static final String DEFAULT_LOGO_PATH = "public/uploadFiles/";
 
app/utils/MenuType.java
--- app/utils/MenuType.java
+++ app/utils/MenuType.java
@@ -1,7 +1,7 @@
 package utils;
 
 public enum MenuType {
-	SITE_HOME(1), NEW_PROJECT(2), PROJECTS(3), HELP(4), SITE_SETTING(5), USER(6),
+    SITE_HOME(1), NEW_PROJECT(2), PROJECTS(3), HELP(4), SITE_SETTING(5), USER(6),
     PROJECT_HOME(100), BOARD(101), CODE(102), ISSUE(103), TASK(104), PROJECT_SETTING(105), MILESTONE(106), PULL_REQUEST(107), NONE(0);
 
     private int type;
app/utils/TemplateHelper.scala
--- app/utils/TemplateHelper.scala
+++ app/utils/TemplateHelper.scala
@@ -80,7 +80,7 @@
   }
 
   def getJSPath(): String = {
-	routes.Assets.at("javascripts/").toString
+    routes.Assets.at("javascripts/").toString
   }
 
   def nullOrEquals(a: String, b: String) = (a == null || b == null) ||  a.equals(b)
app/views/code/history.scala.html
--- app/views/code/history.scala.html
+++ app/views/code/history.scala.html
@@ -165,7 +165,7 @@
                                 case (user: User, _, _) if !user.isAnonymous => {
 
                                     <a href="@routes.UserApp.userInfo(commit.getAuthor.loginId)" class="avatar-wrap">
-                                    @if( user.avatarUrl == UserApp.DEFAULT_AVATAR_URL ){
+                                    @if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL){
                                         @if(commit.getAuthorEmail != null) {
                                             <img src="@urlToPicture(commit.getAuthorEmail, 32)">
                                         } else {
app/views/git/create.scala.html
--- app/views/git/create.scala.html
+++ app/views/git/create.scala.html
@@ -57,67 +57,67 @@
                 </div>
             </div>
             <div class="pull-request-wrap">
-	            <div class="option request-from">
-	                <div class="option-label">@Messages("pullRequest.from"):
+                <div class="option request-from">
+                    <div class="option-label">@Messages("pullRequest.from"):
                         <code>@project.owner/@project.name</code>
                     </div>
-	                <div class="option-desc mt5">
-	                    <div class="btn-group branches" data-name="fromBranch">
-	                        <button class="btn dropdown-toggle auto" data-toggle="dropdown">
-	                            <span class="d-label">@Messages("pullRequest.select.branch")</span>
-	                            <span class="d-caret"><span class="caret"></span></span>
-	                        </button>
-	                        <ul class="dropdown-menu">
-	                            @for(branch <- fromBranches) {
-	                                @if(!branch.equals("HEAD") && !branch.contains("refs/remotes/")) {
-	                                    @makeBranchItem(branch, pullRequest.fromBranch)
-	                                }
-	                            }
-	                        </ul>
-	                    </div>
-	                </div>
-	            </div>
-
-                <div class="arrow">
-	               <i class="yobicon-right"></i>
+                    <div class="option-desc mt5">
+                        <div class="btn-group branches" data-name="fromBranch">
+                            <button class="btn dropdown-toggle auto" data-toggle="dropdown">
+                                <span class="d-label">@Messages("pullRequest.select.branch")</span>
+                                <span class="d-caret"><span class="caret"></span></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                @for(branch <- fromBranches) {
+                                    @if(!branch.equals("HEAD") && !branch.contains("refs/remotes/")) {
+                                        @makeBranchItem(branch, pullRequest.fromBranch)
+                                    }
+                                }
+                            </ul>
+                        </div>
+                    </div>
                 </div>
 
-	            <div class="option request-to">
-	                <div class="option-label">@Messages("pullRequest.to"):
+                <div class="arrow">
+                   <i class="yobicon-right"></i>
+                </div>
+
+                <div class="option request-to">
+                    <div class="option-label">@Messages("pullRequest.to"):
                         <code>@project.originalProject.owner/@project.originalProject.name</code>
                     </div>
-	                <div class="option-desc mt5">
-	                    <div class="btn-group branches" data-name="toBranch">
-	                        <button class="btn dropdown-toggle auto" data-toggle="dropdown">
-	                            <span class="d-label">@Messages("pullRequest.select.branch")</span>
-	                            <span class="d-caret"><span class="caret"></span></span>
-	                        </button>
-	                        <ul class="dropdown-menu">
-	                            @for(branch <- toBranches) {
-	                                @if(!branch.equals("HEAD") && !branch.contains("refs/remotes/")) {
-	                                    @makeBranchItem(branch, pullRequest.toBranch)
-	                                }
-	                            }
-	                        </ul>
-	                    </div>
-	                </div>
-	            </div>
+                    <div class="option-desc mt5">
+                        <div class="btn-group branches" data-name="toBranch">
+                            <button class="btn dropdown-toggle auto" data-toggle="dropdown">
+                                <span class="d-label">@Messages("pullRequest.select.branch")</span>
+                                <span class="d-caret"><span class="caret"></span></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                @for(branch <- toBranches) {
+                                    @if(!branch.equals("HEAD") && !branch.contains("refs/remotes/")) {
+                                        @makeBranchItem(branch, pullRequest.toBranch)
+                                    }
+                                }
+                            </ul>
+                        </div>
+                    </div>
+                </div>
             </div>
 
             <div id="frmWrap">
-			@views.html.git.partial_diff(form, project, mergeResult, pullRequest)
-			</div>
-			<div id="compare" class="modal hide compare-wrap" tabindex="-1" role="dialog">
-			    <h4 class="path">
-			        <span></span>
-			        <button type="button" class="ybtn pull-right" data-dismiss="modal" style="margin-right:20px;">@Messages("button.confirm")</button>
-			    </h4>
-			    <div class="row-fluid">
-			        <div class="span6 compare-from"></div>
-			        <div class="span6 compare-to"></div>
-			    </div>
-			    <div id="mergely" class="mergely-wrap"></div>
-			</div>
+            @views.html.git.partial_diff(form, project, mergeResult, pullRequest)
+            </div>
+            <div id="compare" class="modal hide compare-wrap" tabindex="-1" role="dialog">
+                <h4 class="path">
+                    <span></span>
+                    <button type="button" class="ybtn pull-right" data-dismiss="modal" style="margin-right:20px;">@Messages("button.confirm")</button>
+                </h4>
+                <div class="row-fluid">
+                    <div class="span6 compare-from"></div>
+                    <div class="span6 compare-to"></div>
+                </div>
+                <div id="mergely" class="mergely-wrap"></div>
+            </div>
         }
     </div>
 </div>
@@ -131,9 +131,9 @@
 <script type="text/javascript">
 $(document).ready(function() {
     $yobi.loadModule("git.Write", {
-        "sFormURL"     : "@routes.PullRequestApp.newPullRequestForm(project.owner, project.name)",
-        "welFromBranch": $("div[data-name='fromBranch']"),
-        "welToBranch"  : $("div[data-name='toBranch']")
+        "sFormURL"      : "@routes.PullRequestApp.newPullRequestForm(project.owner, project.name)",
+        "welFromBranch" : $("div[data-name='fromBranch']"),
+        "welToBranch"   : $("div[data-name='toBranch']")
     });
 });
 </script>
app/views/git/diff.scala.html
--- app/views/git/diff.scala.html
+++ app/views/git/diff.scala.html
@@ -31,10 +31,10 @@
 <div class="page">
     <div class="code-browse-wrap">
         <div class="actrow">
-            <a href="@routes.PullRequestApp.pullRequest(pull.toProject.owner, pull.toProject.name, pull.number)?activeTab=commits" class="ybtn">
+            <a href="@routes.PullRequestApp.pullRequestCommits(pull.toProject.owner, pull.toProject.name, pull.number)" class="ybtn">
                 <i class="yobicon-arrow-right"></i> @Messages("pullRequest.back.to.the.pullrequest")
             </a>
-            
+
             <div class="pull-right">
                 <button id="watch-button" type="button" class="ybtn ybtn-info @if(commit.getWatchers(pull.fromProject).contains(UserApp.currentUser())) { active }" data-toggle="button">@Messages("notification.watch")</button>
             </div>
@@ -100,11 +100,11 @@
                     <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
                         <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId">
                     </a>
-                </div>    
+                </div>
                 <div class="media-body">
                     <div class="meta-info">
                         <span class="comment_author pull-left">
-                            <i class="yobicon-comment"></i> 
+                            <i class="yobicon-comment"></i>
                             <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
                                 <strong>@comment.authorLoginId </strong>
                             </a>
@@ -116,9 +116,9 @@
                         </span>
                         }
                     </div>
-                    
+
                     <div class="comment-body markdown-wrap markdown-before" markdown="true">@comment.contents</div>
-                    
+
                     <div class="attachments" data-resourceType="@ResourceType.COMMIT_COMMENT" data-resourceId="@comment.id"></div>
                 </div>
             </li>
@@ -126,11 +126,11 @@
         </ul>
         }
         }
-        
+
         @common.commentForm(pull.fromProject, ResourceType.COMMIT_COMMENT, routes.CodeHistoryApp.newComment(pull.fromProject.owner, pull.fromProject.name, commit.getId).toString())
     </div>
     @** // Comment **@
-    
+
     <div id="minimap" class="minimap-outer">
         <div class="minimap-wrap">
             <div class="minimap-curr"></div>
app/views/git/edit.scala.html
--- app/views/git/edit.scala.html
+++ app/views/git/edit.scala.html
@@ -13,17 +13,17 @@
     }
 }
 
-@makeBranchItem(branch:String, selectedBranch:String) = {
+@makeBranchItem(branch:String) = {
     @defining(branch.split('/')){ names =>
         @if(names(0).equals("refs")){
-            <li data-value="@branch" @if(branch.equals(selectedBranch)){data-selected="true" class="active"}>
+            <li data-value="@branch" data-selected="true" class="active">
                 <a href="#">
                     <span class="label @branchItemType(names(1))">@branchItemType(names(1))</span>
                     @branch.replace("refs/" + names(1) + "/", "")
                 </a>
             </li>
         } else {
-            <li data-value="@branch" @if(branch.equals(selectedBranch)){data-selected="true" class="active"}><a href="#">@branch</a></li>
+            <li data-value="@branch" data-selected="true" class="active"><a href="#">@branch</a></li>
         }
     }
 }
@@ -55,51 +55,43 @@
                 </div>
             </div>
             <div class="pull-request-wrap">
-	            <div class="option request-from">
-	                <div class="option-label">@Messages("pullRequest.from"):
+                <div class="option request-from">
+                    <div class="option-label">@Messages("pullRequest.from"):
                         <code>@project.owner/@project.name</code>
                     </div>
-	                <div class="option-desc">
-	                    <div class="btn-group branches" data-name="fromBranch">
-	                        <button class="btn dropdown-toggle auto" data-toggle="dropdown">
-	                            <span class="d-label">@Messages("pullRequest.select.branch")</span>
-	                            <span class="d-caret"><span class="caret"></span></span>
-	                        </button>
-	                        <ul class="dropdown-menu">
-	                            @for(branch <- fromBranches) {
-	                                @if(!branch.equals("HEAD") && !branch.contains("refs/remotes/")) {
-	                                    @makeBranchItem(branch, form("fromBranch").value())
-	                                }
-	                            }
-	                        </ul>
-	                    </div>
-	                </div>
-	            </div>
-
-                <div class="arrow">
-	               <i class="yobicon-right"></i>
+                    <div class="option-desc">
+                        <div class="btn-group branches" data-name="fromBranch">
+                            <button class="btn dropdown-toggle auto" data-toggle="dropdown">
+                                <span class="d-label">@Messages("pullRequest.select.branch")</span>
+                                <span class="d-caret"><span class="caret"></span></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                @makeBranchItem(form("fromBranch").value())
+                            </ul>
+                        </div>
+                    </div>
                 </div>
 
-	            <div class="option request-to">
-	                <div class="option-label">@Messages("pullRequest.to"):
+                <div class="arrow">
+                   <i class="yobicon-right"></i>
+                </div>
+
+                <div class="option request-to">
+                    <div class="option-label">@Messages("pullRequest.to"):
                         <code>@project.originalProject.owner/@project.originalProject.name</code>
                     </div>
-	                <div class="option-desc">
-	                    <div class="btn-group branches" data-name="toBranch">
-	                        <button class="btn dropdown-toggle auto" data-toggle="dropdown">
-	                            <span class="d-label">@Messages("pullRequest.select.branch")</span>
-	                            <span class="d-caret"><span class="caret"></span></span>
-	                        </button>
-	                        <ul class="dropdown-menu">
-	                            @for(branch <- toBranches) {
-	                                @if(!branch.equals("HEAD") && !branch.contains("refs/remotes/")) {
-	                                    @makeBranchItem(branch, form("toBranch").value())
-	                                }
-	                            }
-	                        </ul>
-	                    </div>
-	                </div>
-	            </div>
+                    <div class="option-desc">
+                        <div class="btn-group branches" data-name="toBranch">
+                            <button class="btn dropdown-toggle auto" data-toggle="dropdown">
+                                <span class="d-label">@Messages("pullRequest.select.branch")</span>
+                                <span class="d-caret"><span class="caret"></span></span>
+                            </button>
+                            <ul class="dropdown-menu">
+                                @makeBranchItem(form("toBranch").value())
+                            </ul>
+                        </div>
+                    </div>
+                </div>
             </div>
 
             @helper.inputText(form("title"), 'class->"text title", 'maxlength -> "250", 'tabindex -> 1, 'placeholder->"Title")
app/views/git/partial_comments.scala.html
--- app/views/git/partial_comments.scala.html
+++ app/views/git/partial_comments.scala.html
@@ -122,7 +122,7 @@
 @renderCommitComment(comment: CommitComment) = {
   @defining(PullRequestCommit.getStateByCommitId(pull, comment.commitId)) { state =>
     @if(state != null) {
-        <li class="comment @isAuthorComment(comment.authorLoginId)" id="comment-@comment.id">
+        <li class="comment @isAuthorComment(comment.authorLoginId) @if(!comment.replies.isEmpty) {hasReply}" id="comment-@comment.id">
             <div class="comment-avatar">
                 <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="avatar-wrap" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
                     <img src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="32" height="32" alt="@comment.authorLoginId">
@@ -133,7 +133,7 @@
                     <span class="comment_author pull-left">
                         <i class="yobicon-comment"></i>
                         <a href="@routes.UserApp.userInfo(comment.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@comment.authorName">
-                            <strong>@comment.authorLoginId </strong>
+                            <strong>@comment.authorLoginId</strong>
                         </a>
                     </span>
                     <span class="ago">
@@ -155,6 +155,36 @@
                 <div class="attachments pull-right" data-resourceType="@ResourceType.COMMIT_COMMENT" data-resourceId="@comment.id"></div>
             </div>
         </li>
+        @for((reply, index) <- comment.replies.zipWithIndex) {
+        <li class="comment @isAuthorComment(reply.authorLoginId) reply @if(index == (comment.replies.size - 1)) {last}" id="comment-@reply.id">
+            <div class="media-body">
+                <div class="meta-info">
+                    <span class="comment_author pull-left">
+                        <i class="yobicon-comment"></i>
+                        <a href="@routes.UserApp.userInfo(reply.authorLoginId)" data-toggle="tooltip" data-placement="top" title="@reply.authorName">
+                            <strong>@reply.authorLoginId</strong>
+                        </a>
+                    </span>
+                    <span class="ago">
+                    @if(PullRequestCommit.State.PRIOR == state) {
+						@utils.TemplateHelper.agoString(ago(reply.createdDate)) - <strike>@Messages("pullRequest.code.replied")</strike>
+					} else {
+						@utils.TemplateHelper.agoString(ago(reply.createdDate)) - @Messages("pullRequest.code.replied")
+					}
+                    </span>
+                    @if(isAllowed(UserApp.currentUser(), reply.asResource(), Operation.DELETE)){
+                    <span class="edit pull-right">
+                        <button type="button" class="btn-transparent close" data-request-method="delete" data-request-uri="@routes.CodeHistoryApp.deleteComment(pull.fromProject.owner, pull.fromProject.name, reply.commitId, reply.id)"><i class="yobicon-trash"></i></button>
+                    </span>
+                    }
+                </div>
+
+                <div class="comment-body markdown-wrap markdown-before" markdown="true">@reply.contents</div>
+
+                <div class="attachments pull-right" data-resourceType="@ResourceType.COMMIT_COMMENT" data-resourceId="@reply.id"></div>
+            </div>
+        </li>
+        }
     }
   }
 }
 
app/views/git/partial_conflict_message.scala.html (added)
+++ app/views/git/partial_conflict_message.scala.html
@@ -0,0 +1,52 @@
+@(project: Project, pull: PullRequest)
+
+@import utils.JodaDateUtil._
+@import utils.TemplateHelper._
+@import utils.AccessControl._
+@import models.enumeration
+@import scala.collection.JavaConversions._
+
+@branchName(branch:String) = @{
+    branch.replace("refs/heads/", "")
+}
+
+@getCodeURL(project: Project) = @{
+    if(session == null){
+        CodeApp.getURL(project.owner, project.name)
+    } else {
+        defining(ProjectUser.roleOf(session.get("loginId"), project)) { role =>
+            if(role == "manager" || role == "member"){
+                CodeApp.getURL(project.owner, project.name).replace("://", "://" + session.get("loginId") + "@")
+            } else {
+                CodeApp.getURL(project.owner, project.name)
+            }
+        }
+    }
+}
+
+<div class="alert alert-error">
+    <h5>@Messages("pullRequest.is.not.safe")</h5>
+    <div>
+        <h5>@Messages("pullRequest.conflict.files")</h5>
+        <ul class="unstyled conflict-files">
+            @for(file <- pull.getConflictFiles) {
+            <li><i class="yobicon-file"></i> @file</li>
+            }
+        </ul>
+    </div>
+    <div>
+        <h5>@Messages("pullRequest.resolve.conflict")</h5>
+        <ol>
+            <li>@Messages("pullRequest.resolver.step1") <code>git checkout @branchName(pull.fromBranch)</code></li>
+            <li>@Messages("pullRequest.resolver.step2") <code>git remote add upstream @getCodeURL(pull.toProject)</code></li>
+            <li>@Messages("pullRequest.resolver.step3") <code>git fetch upstream</code></li>
+            <li>@Messages("pullRequest.resolver.step4") <code>git rebase upstream/@branchName(pull.toBranch)</code></li>
+            <li>@Messages("pullRequest.resolver.step5") </li>
+            <li>@Messages("pullRequest.resolver.step6") <code>git add 충돌을_해결한_파일</code></li>
+            <li>@Messages("pullRequest.resolver.step7") <code>git rebase --continue</code></li>
+            <li>@Messages("pullRequest.resolver.step8") </li>
+            <li>@Messages("pullRequest.resolver.step9") <code>git push -f origin @branchName(pull.fromBranch)</code></li>
+            <li>@Messages("pullRequest.resolver.step10") <a href="@routes.PullRequestApp.pullRequest(project.owner, project.name, pull.number)" class="ybtn ybtn-info ybtn-small">@Messages("button.page.refresh")</a>@Messages("pullRequest.resolver.step11")</li>
+        </ol>
+    </div>
+</div>(No newline at end of file)
app/views/git/partial_diff.scala.html
--- app/views/git/partial_diff.scala.html
+++ app/views/git/partial_diff.scala.html
@@ -54,12 +54,12 @@
             </div>
             }
         </div>
-    
+
         <ul class="nav nav-tabs nm">
             <li class="active"><a href="#__commits" data-toggle="tab">@Messages("pullRequest.menu.commit")</a></li>
             <li><a href="#__changes" data-toggle="tab">@Messages("pullRequest.menu.changes")</a></li>
         </ul>
-        <div class="tab-content">    
+        <div class="tab-content">
             <div id="__commits" class="code-browse-wrap tab-pane active">
                 <div id="history" class="commit-wrap mt20">
                     <table class="code-table commits">
@@ -80,14 +80,12 @@
                                     <i class="yobicon-right"></i>
                                 </a>
                             </td>
-                
                             <td class="messages">
                                 @defining(CommitComment.count(pullRequest.fromProject, commit.getId, null)){ numOfComment =>
                                     @if(numOfComment > 0) {
                                     <span class="number-of-comments"><i class="yobicon-comments"></i> @numOfComment</span>
                                     }
                                 }
-            
                                 @defining(commit.getMessage()){ commitMsg =>
                                 <a href="@routes.CodeHistoryApp.show(pullRequest.fromProject.owner, pullRequest.fromProject.name, commit.getId())">
                                     @commitMsg.split("\n")(0)
@@ -118,10 +116,11 @@
                     </table>
                 </div>
             </div>
-               
             <div id="__changes" class="tab-pane">
                 <div class="diff-body">
+                @if(result.getGitConflicts() == null) {
                 @views.html.partial_diff(pullRequest.getDiff)
+                }
                 </div>
             </div>
         </div>
@@ -141,4 +140,4 @@
         "sTplRawURLB" : "@routes.CodeApp.showRawFile(pullRequest.fromProject.owner, pullRequest.fromProject.name, "${commitId}", "${path}")"
     });
 });
-</script>
(No newline at end of file)
+</script>
 
app/views/git/partial_info.scala.html (added)
+++ app/views/git/partial_info.scala.html
@@ -0,0 +1,61 @@
+@(project: Project, pull: PullRequest)
+
+@import utils.JodaDateUtil._
+@import utils.TemplateHelper._
+@import utils.AccessControl._
+@import models.enumeration
+@import scala.collection.JavaConversions._
+
+@branchName(branch:String) = @{
+    branch.replace("refs/heads/", "")
+}
+
+<div class="pullreq-info">
+    <div class="pull-right">
+        <button id="helpBtn" class="ybtn ybtn-inverse ybtn-mini mt5">
+            <i class="yobicon-question-sign yobicon-large"></i> @Messages("title.help")
+        </button>
+    </div>
+    <div>
+        <a href="@routes.UserApp.userInfo(pull.contributor.loginId)" class="avatar-wrap">
+            <img src="@pull.contributor.avatarUrl" width="32" height="32">
+        </a>
+        <a href="@routes.UserApp.userInfo(pull.contributor.loginId)">
+            <strong>@pull.contributor.name (@pull.contributor.loginId)</strong>
+        </a>
+        @Messages("pullRequest.merge.requested")
+
+        <div class="pullRequest-branch ml10">
+            <span data-toggle="tooltip" data-original-title="@Messages("pullRequest.from")">
+                <code class="from">
+                    <i class="yobicon-branch"></i>
+                    <a href="@routes.UserApp.userInfo(pull.fromProject.owner)">@pull.fromProject.owner</a>/<!--
+                 --><a href="@routes.ProjectApp.project(pull.fromProject.owner, pull.fromProject.name)">@pull.fromProject.name</a>:
+                    @branchName(pull.fromBranch)
+                </code>
+            </span>
+            <i class="yobicon-right ml10"></i>
+            <span class="ml10" data-toggle="tooltip" data-original-title="@Messages("pullRequest.to")">
+                <code class="to">
+                    <i class="yobicon-branch"></i>
+                    <a href="@routes.UserApp.userInfo(pull.toProject.owner)">@pull.toProject.owner</a>/<!--
+                 --><a href="@routes.ProjectApp.project(pull.toProject.owner, pull.toProject.name)">@pull.toProject.name</a>:
+                    @branchName(pull.toBranch)
+                </code>
+            </span>
+        </div>
+    </div>
+    <div id="helpMessage" class="well" style="display:none;">
+        <div class="row-fluid">
+        <div class="pull-left">
+            <img class="img-polaroid" src="@routes.Assets.at("images/fork-pull/merge.jpg")"><br>
+        </div>
+        <div class="pull-left help-messages">
+            <p class="lead">@Messages("pullRequest.merge.help.1")</p>
+            <p>@Messages("pullRequest.merge.help.2")</p>
+            <p>@Messages("pullRequest.merge.help.3")</p>
+            <p>@Messages("pullRequest.merge.help.4")</p>
+        </div>
+        </div>
+    </div>
+</div>(No newline at end of file)
app/views/git/view.scala.html
--- app/views/git/view.scala.html
+++ app/views/git/view.scala.html
@@ -6,287 +6,103 @@
 @import models.enumeration
 @import scala.collection.JavaConversions._
 
-@branchName(branch:String) = @{
-    branch.replace("refs/heads/", "")
-}
-
-@getCodeURL(project: Project) = @{
-    if(session == null){
-        CodeApp.getURL(project.owner, project.name)
-    } else {
-        defining(ProjectUser.roleOf(session.get("loginId"), project)) { role =>
-            if(role == "manager" || role == "member"){
-                CodeApp.getURL(project.owner, project.name).replace("://", "://" + session.get("loginId") + "@")
-            } else {
-                CodeApp.getURL(project.owner, project.name)
-            }
-        }
-    }
-}
-
-@conflictMessage = {
-    <div class="alert alert-error">
-        <h5>@Messages("pullRequest.is.not.safe")</h5>
-        <div>
-            <h5>@Messages("pullRequest.conflict.files")</h5>
-            <ul class="unstyled conflict-files">
-                @for(file <- pull.getConflictFiles) {
-                <li><i class="yobicon-file"></i> @file</li>
-                }
-            </ul>
-        </div>
-        <div>
-            <h5>@Messages("pullRequest.resolve.conflict")</h5>
-            <ol>
-                <li>@Messages("pullRequest.resolver.step1") <code>git checkout @branchName(pull.fromBranch)</code></li>
-                <li>@Messages("pullRequest.resolver.step2") <code>git remote add upstream @getCodeURL(pull.toProject)</code></li>
-                <li>@Messages("pullRequest.resolver.step3") <code>git fetch upstream</code></li>
-                <li>@Messages("pullRequest.resolver.step4") <code>git rebase upstream/@branchName(pull.toBranch)</code></li>
-                <li>@Messages("pullRequest.resolver.step5") </li>
-                <li>@Messages("pullRequest.resolver.step6") <code>git add 충돌을_해결한_파일</code></li>
-                <li>@Messages("pullRequest.resolver.step7") <code>git rebase --continue</code></li>
-                <li>@Messages("pullRequest.resolver.step8") </li>
-                <li>@Messages("pullRequest.resolver.step9") <code>git push -f origin @branchName(pull.fromBranch)</code></li>
-                <li>@Messages("pullRequest.resolver.step10") <a href="@routes.PullRequestApp.pullRequest(project.owner, project.name, pull.number)" class="ybtn ybtn-info ybtn-small">@Messages("button.page.refresh")</a>@Messages("pullRequest.resolver.step11")</li>
-            </ol>
-        </div>
-    </div>
-}
-
 @projectLayout(Messages("menu.pullRequest"), project, utils.MenuType.PULL_REQUEST) {
     <div class="page">
         @projectMenu(project, utils.MenuType.PULL_REQUEST, "main-menu-only")
 
-        <div class="pullreq-info">
-            <div class="pull-right">
-                <button id="helpBtn" class="ybtn ybtn-inverse ybtn-mini mt5">
-                    <i class="yobicon-question-sign yobicon-large"></i> @Messages("title.help")
-                </button>
-            </div>
-            <div>
-                <a href="@routes.UserApp.userInfo(pull.contributor.loginId)" class="avatar-wrap">
-                    <img src="@pull.contributor.avatarUrl" width="32" height="32">
-                </a>
-                <a href="@routes.UserApp.userInfo(pull.contributor.loginId)">
-                    <strong>@pull.contributor.name (@pull.contributor.loginId)</strong>
-                </a>
-                @Messages("pullRequest.merge.requested")
-
-                <div class="pullRequest-branch ml10">
-                    <span data-toggle="tooltip" data-original-title="@Messages("pullRequest.from")">
-                        <code class="from">
-                            <i class="yobicon-branch"></i>
-                            <a href="@routes.UserApp.userInfo(pull.fromProject.owner)">@pull.fromProject.owner</a>/<!--
-                         --><a href="@routes.ProjectApp.project(pull.fromProject.owner, pull.fromProject.name)">@pull.fromProject.name</a>:
-                            @branchName(pull.fromBranch)
-                        </code>
-                    </span>
-                    <i class="yobicon-right ml10"></i>
-                    <span class="ml10" data-toggle="tooltip" data-original-title="@Messages("pullRequest.to")">
-                        <code class="to">
-                            <i class="yobicon-branch"></i>
-                            <a href="@routes.UserApp.userInfo(pull.toProject.owner)">@pull.toProject.owner</a>/<!--
-                         --><a href="@routes.ProjectApp.project(pull.toProject.owner, pull.toProject.name)">@pull.toProject.name</a>:
-                            @branchName(pull.toBranch)
-                        </code>
-                    </span>
-                </div>
-            </div>
-            <div id="helpMessage" class="well" style="display:none;">
-                <div class="row-fluid">
-                <div class="pull-left">
-                    <img class="img-polaroid" src="@routes.Assets.at("images/fork-pull/merge.jpg")"><br>
-                </div>
-                <div class="pull-left help-messages">
-                    <p class="lead">@Messages("pullRequest.merge.help.1")</p>
-                    <p>@Messages("pullRequest.merge.help.2")</p>
-                    <p>@Messages("pullRequest.merge.help.3")</p>
-                    <p>@Messages("pullRequest.merge.help.4")</p>
-                </div>
-                </div>
-            </div>
-        </div>
+        @partial_info(project, pull)
 
         <ul class="nav nav-tabs nm">
-            <li @if(activeTab == "info"){ class="active" }><a href="#__info" data-toggle="tab">@Messages("pullRequest.menu.overview")</a></li>
-            <li @if(activeTab == "commits"){ class="active" }><a href="#__commits" data-toggle="tab">@Messages("pullRequest.menu.commit")</a></li>
-            <li @if(activeTab == "changes"){ class="active" }><a href="#__changes" data-toggle="tab">@Messages("pullRequest.menu.changes")</a></li>
+            <li class="active"><a href="@routes.PullRequestApp.pullRequest(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.overview")</a></li>
+            <li><a href="@routes.PullRequestApp.pullRequestCommits(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.commit")</a></li>
+            <li><a href="@routes.PullRequestApp.pullRequestChanges(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.changes")</a></li>
         </ul>
-        <div class="tab-content">
-            <div id="__info" class="tab-pane @if(activeTab == "info"){ active }">
-                @**<!-- Pull Request Info -->**@
-                <div class="board-header issue" style="border-top:none;">
-                    <div class="title pull-left">
-                        <strong class="board-id">#@pull.number</strong> @pull.title
-                    </div>
-                    <div class="pull-right">
-                        <div class="date">@agoString(pull.createdAgo)</div>
-                        <span class="badge badge-issue-@pull.state.state.toLowerCase">@Messages("issue.state." + pull.state.state)</span>
-                    </div>
-                </div>
 
-                <div class="board-body">
-                    <div class="content markdown-wrap markdown-before" markdown="true">@pull.body</div>
+        <div id="__info">
+            @**<!-- Pull Request Info -->**@
+            <div class="board-header issue" style="border-top:none;">
+                <div class="title pull-left">
+                    <strong class="board-id">#@pull.number</strong> @pull.title
                 </div>
+                <div class="pull-right">
+                    <div class="date">@agoString(pull.createdAgo)</div>
+                    <span class="badge badge-issue-@pull.state.state.toLowerCase">@Messages("issue.state." + pull.state.state)</span>
+                </div>
+            </div>
 
+            <div class="board-body">
+                <div class="content markdown-wrap markdown-before" markdown="true">@pull.body</div>
+            </div>
+
+            @if(!pull.isMerging && pull.isOpen()){
+                @if(!pull.isConflict){
+                <div class="alert alert-success">
+                    <h5>@Messages("pullRequest.is.safe")</h5>
+                </div>
+                } else {
+                    @partial_conflict_message(project, pull)
+                }
+            }
+
+            @if(pull.isMerging) {
+                <div class="alert alert-warnning">
+                    <h5>@Messages("pullRequest.is.merging")</h5>
+                </div>
+            }
+
+            <div id="attachments" class="attachments" data-resourceType="@ResourceType.PULL_REQUEST" data-resourceId="@pull.id"></div>
+
+            <div class="board-footer board-actrow">
+                <button id="watch-button" type="button" class="ybtn @if(pull.getWatchers.contains(UserApp.currentUser())) { active }" data-toggle="button">
+                    @if(pull.getWatchers.contains(UserApp.currentUser())) {
+                        @Messages("project.unwatch")
+                    } else {
+                        @Messages("project.watch")
+                    }
+                </button>
                 @if(!pull.isMerging && pull.isOpen()){
-                    @if(!pull.isConflict){
-                    <div class="alert alert-success">
-                        <h5>@Messages("pullRequest.is.safe")</h5>
-                    </div>
-                    } else {
-                        @conflictMessage
+                    @if(!pull.isConflict && isAllowed(UserApp.currentUser(), pull.asResource(), Operation.ACCEPT)){
+                        <a href="@routes.PullRequestApp.accept(project.owner, project.name, pull.number)" class="ybtn ybtn-success">@Messages("pullRequest.accept")</a>
+                    }
+                    @if(isAllowed(UserApp.currentUser(), pull.asResource(), Operation.REJECT)) {
+                        <a href="@routes.PullRequestApp.reject(project.owner, project.name, pull.number)" class="ybtn">@Messages("pullRequest.reject")</a>
+                    }
+                    @if(isAllowed(UserApp.currentUser(), pull.asResource(), Operation.UPDATE)) {
+                        <a href="@routes.PullRequestApp.editPullRequestForm(pull.toProject.owner, pull.toProject.name, pull.number)" class="ybtn">@Messages("button.edit")</a>
                     }
                 }
-                 
-                @if(pull.isMerging) {
-                	<div class="alert alert-warnning">
-                        <h5>@Messages("pullRequest.is.merging")</h5>
-                    </div>
-                }
 
-                <div id="attachments" class="attachments" data-resourceType="@ResourceType.PULL_REQUEST" data-resourceId="@pull.id"></div>
-                
-                <div class="board-footer board-actrow">
-		            <button id="watch-button" type="button" class="ybtn @if(pull.getWatchers.contains(UserApp.currentUser())) { active }" data-toggle="button">
-		                @if(pull.getWatchers.contains(UserApp.currentUser())) {
-		                    @Messages("project.unwatch")
-		                } else {
-		                    @Messages("project.watch")
-		                }
-		            </button>
-		            @if(!pull.isMerging && pull.isOpen()){
-		                @if(!pull.isConflict && isAllowed(UserApp.currentUser(), pull.asResource(), Operation.ACCEPT)){
-		                    <a href="@routes.PullRequestApp.accept(project.owner, project.name, pull.number)" class="ybtn ybtn-success">@Messages("pullRequest.accept")</a>
-		                }
-		                @if(isAllowed(UserApp.currentUser(), pull.asResource(), Operation.REJECT)) {
-		                    <a href="@routes.PullRequestApp.reject(project.owner, project.name, pull.number)" class="ybtn">@Messages("pullRequest.reject")</a>
-		                }
-		                @if(isAllowed(UserApp.currentUser(), pull.asResource(), Operation.UPDATE)) {
-		                    <a href="@routes.PullRequestApp.editPullRequestForm(pull.toProject.owner, pull.toProject.name, pull.number)" class="ybtn">@Messages("button.edit")</a>
-		                }
-		            }
-		
-		            @if(!pull.isMerging && pull.isRejected()){
-		                @if(isAllowed(UserApp.currentUser(), pull.asResource(), Operation.REOPEN)) {
-		                    <a href="@routes.PullRequestApp.open(project.owner, project.name, pull.number)" class="ybtn">@Messages("pullRequest.reopen")</a>
-		                }
-		            }
-		        </div>
-		        @if(pull.isClosed()) {
-		        <div class="alert alert-info">
-		            <a href="@routes.UserApp.userInfo(pull.receiver.loginId)"><img src="@pull.receiver.avatarUrl" width="25" height="25" class="img-rounded"></a>
-		            <a href="@routes.UserApp.userInfo(pull.receiver.loginId)">@pull.receiver.name</a>@Messages("pullRequest.merged.the.pullrequest")
-		            @if(UserApp.currentUser().equals(pull.contributor)) {
-		                @if(canDeleteBranch) {
-						<code>@pull.fromBranch</code> @Messages("pullRequest.delete.frombranch.message")
-		                <button class="ybtn ybtn-danger ybtn-mini pull-right" data-request-method="delete" data-request-uri="@routes.PullRequestApp.deleteFromBranch(project.owner, project.name, pull.number)">@Messages("pullRequest.delete.branch")</button>		                
-		                }
-		                @if(canRestoreBranch) {
-						<code>@pull.fromBranch</code> @Messages("pullRequest.restore.frombranch.message")
-						<a href="@routes.PullRequestApp.restoreFromBranch(project.owner, project.name, pull.id)" class="ybtn ybtn-info ybtn-mini pull-right">@Messages("pullRequest.restore.branch")</a>		                
-		                }
-		            }
-		        </div>
-		        }
-		        <div class="board-comment-wrap">
-		            <div class="comment-header"><i class="yobicon-comments"></i><strong>@Messages("common.comment")</strong> <strong class="num">@{pull.comments.size() + pull.getCodeComments.size()}</strong></div>
-
-		            @if(pull.getTimelineComments.size > 0) {
-		            <ul class="comments" id="comments">
-                    @partial_comments(pull, pull.getTimelineComments.toList)
-					</ul>
-					}
-					@common.commentForm(project, ResourceType.PULL_REQUEST_COMMENT, routes.PullRequestCommentApp.newComment(project.owner, project.name, pull.id).toString())
-				</div>
-            </div>
-
-            <div id="__commits" class="code-browse-wrap tab-pane @if(activeTab == "commits"){ active }">
-                <div id="history" class="commit-wrap mt20">
-                    <table class="code-table commits">
-                        <thead class="thead">
-                        <tr>
-                            <td class="commit-id"><strong>@{"@"}</strong></td>
-                            <td class="messages"><strong>@Messages("code.commitMsg")</strong></td>
-                            <td class="date"><strong>@Messages("code.commitDate")</strong></td>
-                            <td class="author"><strong>@Messages("code.author")</strong></td>
-                        </tr>
-                        </thead>
-                        <tbody class="tbody">
-                        @for(commit <- pull.getCurrentCommits) {
-                        <tr>
-                            <td class="commit-id">
-                                <a href="@routes.PullRequestApp.commitView(pull.toProject.owner, pull.toProject.name, pull.number, commit.getCommitId())">
-                                    @commit.getCommitShortId()
-                                    <i class="yobicon-right"></i>
-                                </a>
-                            </td>
-
-                            <td class="messages">
-
-                                @defining(CommitComment.count(pull.fromProject, commit.getCommitId, null)){ numOfComment =>
-                                    @if(numOfComment > 0) {
-                                    <span class="number-of-comments"><i class="yobicon-comments"></i> @numOfComment</span>
-                                    }
-                                }
-
-								@defining(commit.getCommitMessage()){ commitMsg =>
-	                                <a href="@routes.PullRequestApp.commitView(pull.toProject.owner, pull.toProject.name, pull.number, commit.getCommitId())">
-	                                    @commitMsg.split("\n")(0)
-	                                </a>
-
-									@if(commitMsg.split("\n").length > 1){
-									<button type="button" class="more">&hellip;</button>
-									<pre class="hidden">@commitMsg.replace(commitMsg.split("\n")(0)+"\n", "")</pre>
-									}
-                                }
-                            </td>
-                            <td class="date">@agoString(ago(commit.getAuthorDate()))</td>
-                            <td class="author @commit.getAuthorEmail">
-                                @defining(User.find.where.eq("email", commit.getAuthorEmail).findUnique) { user =>
-                                @if(user != null) {
-                                <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap">
-                                    <img src="@user.avatarUrl" alt="@user.name" width="32" height="32"/>
-                                </a>
-                                } else {
-                                <div class="avatar-wrap">
-                                    <img src="@urlToPicture(commit.getAuthorEmail(), 32)" width="32" height="32"/>
-                                </div>
-                                }
-                                }
-                            </td>
-                        </tr>
-                        }
-                        </tbody>
-                    </table>
-                </div>
-            </div>
-
-            <div id="__changes" class="tab-pane @if(activeTab == "changes"){ active }">
-                <div class="diff-body">
-                    @if(pull.isMerging) {
-                      <div class="alert alert-warnning">
-                        <h5>@Messages("pullRequest.is.merging")</h5>
-                      </div>
-                    } else {
-                      @if(pull.isConflict) {
-                        @conflictMessage
-                      } else {
-                        @views.html.partial_diff(pull.getDiff, comments.filter(v => !v.isOutdated))
-                      }
+                @if(!pull.isMerging && pull.isRejected()){
+                    @if(isAllowed(UserApp.currentUser(), pull.asResource(), Operation.REOPEN)) {
+                        <a href="@routes.PullRequestApp.open(project.owner, project.name, pull.number)" class="ybtn">@Messages("pullRequest.reopen")</a>
                     }
-                </div>
-                <div id="compare" class="modal hide compare-wrap" tabindex="-1" role="dialog">
-                    <h4 class="path">
-                        <span></span>
-                        <button type="button" class="ybtn pull-right" data-dismiss="modal" style="margin-right:20px;">@Messages("button.confirm")</button>
-                    </h4>
-                    <div class="row-fluid">
-                        <div class="span6 compare-from"></div>
-                        <div class="span6 compare-to"></div>
-                    </div>
-                    <div id="mergely" class="mergely-wrap"></div>
-                </div>
+                }
+            </div>
+            @if(pull.isClosed()) {
+            <div class="alert alert-info">
+                <a href="@routes.UserApp.userInfo(pull.receiver.loginId)"><img src="@pull.receiver.avatarUrl" width="25" height="25" class="img-rounded"></a>
+                <a href="@routes.UserApp.userInfo(pull.receiver.loginId)">@pull.receiver.name</a>@Messages("pullRequest.merged.the.pullrequest")
+                @if(UserApp.currentUser().equals(pull.contributor)) {
+                    @if(canDeleteBranch) {
+                    <code>@pull.fromBranch</code> @Messages("pullRequest.delete.frombranch.message")
+                    <button class="ybtn ybtn-danger ybtn-mini pull-right" data-request-method="delete" data-request-uri="@routes.PullRequestApp.deleteFromBranch(project.owner, project.name, pull.number)">@Messages("pullRequest.delete.branch")</button>
+                    }
+                    @if(canRestoreBranch) {
+                    <code>@pull.fromBranch</code> @Messages("pullRequest.restore.frombranch.message")
+                    <a href="@routes.PullRequestApp.restoreFromBranch(project.owner, project.name, pull.id)" class="ybtn ybtn-info ybtn-mini pull-right">@Messages("pullRequest.restore.branch")</a>
+                    }
+                }
+            </div>
+            }
+            <div class="board-comment-wrap">
+                <div class="comment-header"><i class="yobicon-comments"></i><strong>@Messages("common.comment")</strong> <strong class="num">@{pull.comments.size() + pull.getCommitComments.size()}</strong></div>
+
+                @if(pull.getTimelineComments.size > 0) {
+                <ul class="comments" id="comments">
+                @partial_comments(pull, pull.getTimelineComments.toList)
+                </ul>
+                }
+                @common.commentForm(project, ResourceType.PULL_REQUEST_COMMENT, routes.PullRequestCommentApp.newComment(project.owner, project.name, pull.id).toString())
             </div>
         </div>
     </div>
@@ -314,15 +130,6 @@
             } else {
                 url : "@Html(routes.ProjectApp.mentionListAtPullRequest(pull.toProject.owner, pull.toProject.name, pull.getCurrentCommits.get(0).getCommitId, pull.id).toString)"
             }
-        });
-
-        $yobi.loadModule("code.Diff", {
-            "welDiff"      : $("#pull-request-changes"),
-            "bCommentable" : "@isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.PULL_REQUEST_COMMENT)",
-            "sTplFileURLA" : "@routes.CodeApp.codeBrowserWithBranch(pull.toProject.owner, pull.toProject.name, "${commitId}", "${path}")",
-            "sTplFileURLB" : "@routes.CodeApp.codeBrowserWithBranch(pull.fromProject.owner, pull.fromProject.name, "${commitId}", "${path}")",
-            "sTplRawURLA"  : "@routes.CodeApp.showRawFile(pull.toProject.owner, pull.toProject.name, "${commitId}", "${path}")",
-            "sTplRawURLB"  : "@routes.CodeApp.showRawFile(pull.fromProject.owner, pull.fromProject.name, "${commitId}", "${path}")"
         });
     });
 </script>
 
app/views/git/viewChanges.scala.html (added)
+++ app/views/git/viewChanges.scala.html
@@ -0,0 +1,80 @@
+@(project: Project, pull: PullRequest, comments: List[PullRequestComment])
+
+@import utils.JodaDateUtil._
+@import utils.TemplateHelper._
+@import utils.AccessControl._
+@import models.enumeration
+@import scala.collection.JavaConversions._
+
+@projectLayout(Messages("menu.pullRequest"), project, utils.MenuType.PULL_REQUEST) {
+    <div class="page">
+        @projectMenu(project, utils.MenuType.PULL_REQUEST, "main-menu-only")
+
+        @partial_info(project, pull)
+
+        <ul class="nav nav-tabs nm">
+            <li><a href="@routes.PullRequestApp.pullRequest(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.overview")</a></li>
+            <li><a href="@routes.PullRequestApp.pullRequestCommits(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.commit")</a></li>
+            <li class="active"><a href="@routes.PullRequestApp.pullRequestChanges(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.changes")</a></li>
+        </ul>
+
+        <div id="__changes">
+            <div class="diff-body">
+                @if(pull.isMerging) {
+                  <div class="alert alert-warnning">
+                    <h5>@Messages("pullRequest.is.merging")</h5>
+                  </div>
+                } else {
+                  @if(pull.isConflict) {
+                    @partial_conflict_message(project, pull)
+                  } else {
+                    @views.html.partial_diff(pull.getDiff, comments.filter(v => !v.isOutdated))
+                  }
+                }
+            </div>
+            <div id="compare" class="modal hide compare-wrap" tabindex="-1" role="dialog">
+                <h4 class="path">
+                    <span></span>
+                    <button type="button" class="ybtn pull-right" data-dismiss="modal" style="margin-right:20px;">@Messages("button.confirm")</button>
+                </h4>
+                <div class="row-fluid">
+                    <div class="span6 compare-from"></div>
+                    <div class="span6 compare-to"></div>
+                </div>
+                <div id="mergely" class="mergely-wrap"></div>
+            </div>
+        </div>
+        @common.commentForm(project, ResourceType.PULL_REQUEST_COMMENT, routes.PullRequestCommentApp.newComment(project.owner, project.name, pull.id).toString())
+    </div>
+
+@common.markdown(project)
+
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mergely/codemirror.css")">
+<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/mergely/mergely.css")">
+<link rel="stylesheet" type="text/css" media="screen" href="@getCSSLink("mention")">
+<script type="text/javascript" src="@getJSLink("lib/mentionjs/mention")"></script>
+<script type="text/javascript" src="@getJSLink("lib/diff")"></script>
+<script type="text/javascript" src="@getJSLink("lib/mergely/codemirror.min")"></script>
+<script type="text/javascript" src="@getJSLink("lib/mergely/mergely")"></script>
+<script type="text/javascript">
+    $(document).ready(function() {
+        yobi.Mention({
+            target:'comment-editor',
+            @if(pull.getCurrentCommits.isEmpty()) {
+                url : "@Html(routes.ProjectApp.mentionListAtPullRequest(pull.toProject.owner, pull.toProject.name, "", pull.id).toString)"
+            } else {
+                url : "@Html(routes.ProjectApp.mentionListAtPullRequest(pull.toProject.owner, pull.toProject.name, pull.getCurrentCommits.get(0).getCommitId, pull.id).toString)"
+            }
+        });
+
+        $yobi.loadModule("code.Diff", {
+            "welDiff"      : $("#pull-request-changes"),
+            "bCommentable" : "@isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.PULL_REQUEST_COMMENT)",
+            "sTplFileURLA" : "@routes.CodeApp.codeBrowserWithBranch(pull.toProject.owner, pull.toProject.name, "${commitId}", "${path}")",
+            "sTplFileURLB" : "@routes.CodeApp.codeBrowserWithBranch(pull.fromProject.owner, pull.fromProject.name, "${commitId}", "${path}")",
+            "sTplRawURLA"  : "@routes.CodeApp.showRawFile(pull.toProject.owner, pull.toProject.name, "${commitId}", "${path}")",
+            "sTplRawURLB"  : "@routes.CodeApp.showRawFile(pull.fromProject.owner, pull.fromProject.name, "${commitId}", "${path}")"
+        });
+    });
+</script>
+}
app/views/git/viewCommits.scala.html
--- app/views/git/viewCommits.scala.html
+++ app/views/git/viewCommits.scala.html
@@ -1,44 +1,25 @@
-@(project: Project, pull: PullRequest, commits: List[playRepository.GitCommit])
+@(project: Project, pull: PullRequest)
 
 @import utils.JodaDateUtil._
 @import utils.TemplateHelper._
-@import java.net.URLEncoder
+@import utils.AccessControl._
+@import models.enumeration
+@import scala.collection.JavaConversions._
 
 @projectLayout(Messages("menu.pullRequest"), project, utils.MenuType.PULL_REQUEST) {
     <div class="page">
         @projectMenu(project, utils.MenuType.PULL_REQUEST, "main-menu-only")
 
-        <p>
-            @Messages("pullRequest.from")
-            <span class="label label-info">
-                <a href="@routes.UserApp.userInfo(pull.fromProject.owner)">@pull.fromProject.owner</a> /
-                <a href="@routes.ProjectApp.project(pull.fromProject.owner, pull.fromProject.name)">@pull.fromProject.name</a>
-            </span>
-            <a href="@routes.CodeApp.codeBrowserWithBranch(pull.fromProject.owner, pull.fromProject.name, pull.fromBranch, "")" class="label label-success">@pull.fromBranch</a>
+        @partial_info(project, pull)
 
-            @Messages("pullRequest.to")
-            <span class="label label-info">
-                <a href="@routes.UserApp.userInfo(pull.toProject.owner)">@pull.toProject.owner</a> /
-                <a href="@routes.ProjectApp.project(pull.toProject.owner, pull.toProject.name)">@pull.toProject.name</a>
-            </span>
-            <a href="@routes.CodeApp.codeBrowserWithBranch(pull.toProject.owner, pull.toProject.name, pull.toBranch, "")" class="label label-success">@pull.toBranch</a>
-        </p>
-
-        <ul class="nav nav-tabs yobi-tabs">
-            <li>
-                <a href="@routes.PullRequestApp.pullRequest(project.owner, project.name, pull.id)">
-                    @Messages("pullRequest.menu.overview")
-                </a>
-            </li>
-            <li class="active">
-                <a href="@routes.PullRequestApp.pullRequestCommits(project.owner, project.name, pull.id)">
-                    @Messages("pullRequest.menu.commit")
-                </a>
-            </li>
+        <ul class="nav nav-tabs nm">
+            <li><a href="@routes.PullRequestApp.pullRequest(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.overview")</a></li>
+            <li class="active"><a href="@routes.PullRequestApp.pullRequestCommits(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.commit")</a></li>
+            <li><a href="@routes.PullRequestApp.pullRequestChanges(project.owner, project.name, pull.number)">@Messages("pullRequest.menu.changes")</a></li>
         </ul>
 
-        <div class="code-browse-wrap">
-            <div id="history" class="commit-wrap">
+        <div id="__commits" class="code-browse-wrap">
+            <div id="history" class="commit-wrap mt20">
                 <table class="code-table commits">
                     <thead class="thead">
                     <tr>
@@ -49,18 +30,37 @@
                     </tr>
                     </thead>
                     <tbody class="tbody">
-                    @for(commit <- commits.iterator()) {
+                    @for(commit <- pull.getCurrentCommits) {
                     <tr>
                         <td class="commit-id">
-                            <a href="@routes.CodeHistoryApp.show(pull.fromProject.owner, pull.fromProject.name, commit.getId())?branch=@URLEncoder.encode(pull.fromBranch, "UTF-8")">
-                                @commit.getShortId()
+                            <a href="@routes.PullRequestApp.commitView(pull.toProject.owner, pull.toProject.name, pull.number, commit.getCommitId())">
+                                @commit.getCommitShortId()
                                 <i class="yobicon-right"></i>
                             </a>
                         </td>
-                        <td class="messages">@commit.getMessage()</td>
+
+                        <td class="messages">
+
+                            @defining(CommitComment.count(pull.fromProject, commit.getCommitId, null)){ numOfComment =>
+                                @if(numOfComment > 0) {
+                                <span class="number-of-comments"><i class="yobicon-comments"></i> @numOfComment</span>
+                                }
+                            }
+
+                            @defining(commit.getCommitMessage()){ commitMsg =>
+                                <a href="@routes.PullRequestApp.commitView(pull.toProject.owner, pull.toProject.name, pull.number, commit.getCommitId())">
+                                    @commitMsg.split("\n")(0)
+                                </a>
+
+