// AddComment adds the given comment to the review. func (r *Review) AddComment(c comment.Comment) error { commentNote, err := c.Write() if err != nil { return err } r.Repo.AppendNote(comment.Ref, r.Revision, commentNote) return nil }
func TestCommentsOverlap(t *testing.T) { reviewLevelComment := comment.Comment{ Timestamp: "00000000", Author: "*****@*****.**", Location: &comment.Location{ Commit: "ABCDEFG", }, Description: "Please fix so and so...", } if !CommentsOverlap(reviewLevelComment, reviewLevelComment) { t.Fatal("Erroneous distinction drawn between identical review-level comments") } repeatedReviewLevelComment := comment.Comment{ Timestamp: "00000000", Author: "*****@*****.**", Location: &comment.Location{ Commit: "ABCDEFH", }, Description: "Please fix so and so...", } if CommentsOverlap(reviewLevelComment, repeatedReviewLevelComment) { t.Fatal("Failed to distinguish between review comments at different commits") } issueComment := comment.Comment{ Timestamp: "FFFFFFFF", Author: "*****@*****.**", Description: "Please fix so and so...", } if !CommentsOverlap(reviewLevelComment, issueComment) { t.Fatal("Erroneous distinction drawn between a review-level comment and an issue comment") } reviewLevelCommentHash, err := reviewLevelComment.Hash() if err != nil { t.Fatal(err) } reviewLevelChildComment := comment.Comment{ Timestamp: "FFFFFFFG", Author: "*****@*****.**", Parent: reviewLevelCommentHash, Location: &comment.Location{ Commit: "ABCDEFG", }, Description: "Done", } issueChildComment := comment.Comment{ Timestamp: "FFFFFFFH", Author: "*****@*****.**", Description: "Done", } if !CommentsOverlap(reviewLevelChildComment, issueChildComment) { t.Fatal("Erroneous distinction drawn between a review-level child comment and an issue comment") } }
func TestResolvedOverlaps(t *testing.T) { reject := false accept := true blankComment := comment.Comment{ Timestamp: "012345", Author: "*****@*****.**", Resolved: &reject, } blankComment2 := comment.Comment{ Timestamp: "012345", Author: "*****@*****.**", Resolved: &accept, } // should not overlap because resolved bits are set for both // and different even though with same timestamp if Overlaps(blankComment, blankComment2) { t.Errorf("%v and %v overlap", blankComment, blankComment2) } blankComment2.Resolved = &reject // should overlap because resolved bits are set for both and the same with the same timestamp if !Overlaps(blankComment, blankComment2) { t.Errorf("%v and %v do not overlap", blankComment, blankComment2) } blankComment2.Resolved = &accept // should not overlap because resolved bits are set for both and the timestamps are different if Overlaps(blankComment, blankComment2) { t.Errorf("%v and %v overlap", blankComment, blankComment2) } blankComment2.Timestamp = "012345" blankComment2.Resolved = nil // should not overlap because resolved bit is nil for one if Overlaps(blankComment, blankComment2) { t.Errorf("%v and %v overlap", blankComment, blankComment2) } blankComment.Resolved = nil // should overlap because resolved bit is nil for both and there is no other descriptor // seperating them apart if !Overlaps(blankComment, blankComment2) { t.Errorf("%v and %v do not overlap", blankComment, blankComment2) } }
func getHash(c comment.Comment) string { cHash, error := c.Hash() if error != nil { } return cHash }
func TestBuildCommentThreads(t *testing.T) { rejected := false accepted := true root := comment.Comment{ Timestamp: "012345", Resolved: nil, Description: "root", } rootHash, err := root.Hash() if err != nil { t.Fatal(err) } child := comment.Comment{ Timestamp: "012346", Resolved: &rejected, Parent: rootHash, Description: "child", } childHash, err := child.Hash() if err != nil { t.Fatal(err) } leaf := comment.Comment{ Timestamp: "012347", Resolved: &accepted, Parent: childHash, Description: "leaf", } leafHash, err := leaf.Hash() if err != nil { t.Fatal(err) } commentsByHash := map[string]comment.Comment{ rootHash: root, childHash: child, leafHash: leaf, } threads := buildCommentThreads(commentsByHash) if len(threads) != 1 { t.Fatalf("Unexpected threads: %v", threads) } rootThread := threads[0] if rootThread.Comment.Description != "root" { t.Fatalf("Unexpected root thread: %v", rootThread) } if len(rootThread.Children) != 1 { t.Fatalf("Unexpected root children: %v", rootThread.Children) } rootChild := rootThread.Children[0] if rootChild.Comment.Description != "child" { t.Fatalf("Unexpected child: %v", rootChild) } if len(rootChild.Children) != 1 { t.Fatalf("Unexpected leaves: %v", rootChild.Children) } threadLeaf := rootChild.Children[0] if threadLeaf.Comment.Description != "leaf" { t.Fatalf("Unexpected leaf: %v", threadLeaf) } if len(threadLeaf.Children) != 0 { t.Fatalf("Unexpected leaf children: %v", threadLeaf.Children) } }
func LoadComments(review DifferentialReview, readTransactions ReadTransactions, readTransactionComment ReadTransactionComment, lookupUser UserLookup) []comment.Comment { allTransactions, err := readTransactions(review.PHID) if err != nil { log.Fatal(err) } var comments []comment.Comment commentsByPHID := make(map[string]comment.Comment) rejectionCommentsByUser := make(map[string][]string) log.Printf("LOADCOMMENTS: Returning %d transactions", len(allTransactions)) for _, transaction := range allTransactions { author, err := lookupUser(transaction.AuthorPHID) if err != nil { log.Fatal(err) } c := comment.Comment{ Author: author.Email, Timestamp: fmt.Sprintf("%d", transaction.DateCreated), } if author.Email != "" { c.Author = author.Email } else { c.Author = author.UserName } if transaction.CommentPHID != nil { transactionComment, err := readTransactionComment(transaction.PHID) if err != nil { log.Fatal(err) } if transactionComment.FileName != "" { c.Location = &comment.Location{ Commit: transactionComment.Commit, Path: transactionComment.FileName, } if transactionComment.LineNumber != 0 { c.Location.Range = &comment.Range{ StartLine: transactionComment.LineNumber, } } } c.Description = transactionComment.Content if transactionComment.ReplyToCommentPHID != nil { // We assume that the parent has to have been processed before the child, // and enforce that by ordering the transactions in our queries. if replyTo, ok := commentsByPHID[*transactionComment.ReplyToCommentPHID]; ok { parentHash, err := replyTo.Hash() if err != nil { log.Fatal(err) } c.Parent = parentHash } } } // Set the resolved bit based on whether the change was approved or not. if transaction.Type == "differential:action" && transaction.NewValue != nil { action := *transaction.NewValue var resolved bool if action == "\"accept\"" { resolved = true c.Resolved = &resolved // Add child comments to all previous rejects by this user and make them accepts for _, rejectionCommentHash := range rejectionCommentsByUser[author.UserName] { approveComment := comment.Comment{ Author: c.Author, Timestamp: c.Timestamp, Resolved: &resolved, Parent: rejectionCommentHash, } comments = append(comments, approveComment) log.Printf("LOADCOMMENTS: Received approval. Adding child comment %v with parent hash %x", approveComment, rejectionCommentHash) } } else if action == "\"reject\"" { resolved = false c.Resolved = &resolved } } // Phabricator only publishes inline comments when you publish a top-level comment. // This results in a lot of empty top-level comments, which we do not want to mirror. // To work around this, we only return comments that are non-empty. if c.Parent != "" || c.Location != nil || c.Description != "" || c.Resolved != nil { comments = append(comments, c) commentsByPHID[transaction.PHID] = c //If this was a rejection comment, add it to ordered comment hash if c.Resolved != nil && *c.Resolved == false { commentHash, err := c.Hash() if err != nil { log.Fatal(err) } log.Printf("LOADCOMMENTS: Received rejection. Adding comment %v with hash %x", c, commentHash) rejectionCommentsByUser[author.UserName] = append(rejectionCommentsByUser[author.UserName], commentHash) } } } log.Printf("LOADCOMMENTS: Returning %d comments", len(comments)) return comments }