summaryrefslogtreecommitdiffstats
path: root/models/git_diff.go
diff options
context:
space:
mode:
authorLauris BH <lauris@nix.lv>2018-08-06 07:43:22 +0300
committerJonas Franz <info@jonasfranz.software>2018-08-06 06:43:21 +0200
commit6e64f9db8eb889f9cc7e8c9576b2f9c89750927e (patch)
treed76b927295a1d2f69ea73a78e83d9264ce1510f2 /models/git_diff.go
parent9c354a539ab498ebfdebf7395cf17f95f8b24ac8 (diff)
downloadgitea-6e64f9db8eb889f9cc7e8c9576b2f9c89750927e.tar.gz
gitea-6e64f9db8eb889f9cc7e8c9576b2f9c89750927e.zip
Pull request review/approval and comment on code (#3748)
* Initial ui components for pull request review * Add Review Add IssueComment types Signed-off-by: Jonas Franz <info@jonasfranz.software> (cherry picked from commit 2b4daab) Signed-off-by: Jonas Franz <info@jonasfranz.software> * Replace ReviewComment with Content Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add load functions Add ReviewID to findComments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add create review comment implementation Add migration for review Other small changes Signed-off-by: Jonas Franz <info@jonasfranz.software> * Simplified create and find functions for review Signed-off-by: Jonas Franz <info@jonasfranz.software> * Moved "Pending" to first position Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add GetCurrentReview to simplify fetching current review Signed-off-by: Jonas Franz <info@jonasfranz.software> * Preview for listing comments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Move new comment form to its own file Signed-off-by: Jonas Franz <info@jonasfranz.software> * Implement Review form Show Review comments on comment stream Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add support for single comments Showing buttons in context Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add pending tag to pending review comments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add unit tests for Review Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fetch all review ids at once Add unit tests Signed-off-by: Jonas Franz <info@jonasfranz.software> * gofmt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Improved comment rendering in "Files" view by adding Comments to DiffLine Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add support for invalidating comments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Switched back to code.gitea.io/git Signed-off-by: Jonas Franz <info@jonasfranz.software> * Moved review migration from v64 to v65 Signed-off-by: Jonas Franz <info@jonasfranz.software> * Rebuild css Signed-off-by: Jonas Franz <info@jonasfranz.software> * gofmt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Improve translations Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix unit tests by updating fixtures and updating outdated test Signed-off-by: Jonas Franz <info@jonasfranz.software> * Comments will be shown at the right place now Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add support for deleting CodeComments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix problems caused by files in subdirectories Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add support for showing code comments of reviews in conversation Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add support for "Show/Hide outdated" Signed-off-by: Jonas Franz <info@jonasfranz.software> * Update code.gitea.io/git Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add support for new webhooks Signed-off-by: Jonas Franz <info@jonasfranz.software> * Update comparison Signed-off-by: Jonas Franz <info@jonasfranz.software> * Resolve conflicts Signed-off-by: Jonas Franz <info@jonasfranz.software> * Minor UI improvements * update code.gitea.io/git * Fix ui bug reported by @lunny causing wrong position of add button Add functionality to "Cancel" button Add scale effects to add button Hide "Cancel" button for existing comments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Prepare solving conflicts Signed-off-by: Jonas Franz <info@jonasfranz.software> * Show add button only if no comments already exist for the line Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add missing vendor files Signed-off-by: Jonas Franz <info@jonasfranz.software> * Check if reviewer is nil Signed-off-by: Jonas Franz <info@jonasfranz.software> * Show forms only to users who are logged in Signed-off-by: Jonas Franz <info@jonasfranz.software> * Revert "Show forms only to users who are logged in" This reverts commit c083682 Signed-off-by: Jonas Franz <info@jonasfranz.software> * Save patch in comment Render patch for code comments Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add link to comment in code Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add reply form to comment list Show forms only to signed in users Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add 'Reply' as translatable Add CODE_COMMENT_LINES setting Signed-off-by: Jonas Franz <info@jonasfranz.software> * gofmt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix problems introduced by checking for singed in user Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add v70 Signed-off-by: Jonas Franz <info@jonasfranz.software> * Update generated stylesheet Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix preview Beginn with new review comment patch system Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add new algo to generate diff for line range Remove old algo used for cutting big diffs (it was very buggy) * Add documentation and example for CutDiffAroundLine * Fix example of CutDiffAroundLine * Fix some comment UI rendering bugs * Add code comment edit mode * Send notifications / actions to users until review gets published Fix diff generation bug Fix wrong hashtag * Fix vet errors * Send notifications also for single comments * Fix some notification bugs, fix link * Fix: add comment icon is only shown on code lines * Add lint comment * Add unit tests for git diff * Add more error messages * Regenerated css Signed-off-by: Jonas Franz <info@jonasfranz.software> * fmt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Regenerated CSS with latest less version Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix test by updating comment type to new ID Signed-off-by: Jonas Franz <info@jonasfranz.software> * Introducing CodeComments as type for map[string]map[int64][]*Comment Other minor code improvements Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix data-tab issues Signed-off-by: Jonas Franz <info@jonasfranz.software> * Remove unnecessary change Signed-off-by: Jonas Franz <info@jonasfranz.software> * refactored checkForInvalidation Signed-off-by: Jonas Franz <info@jonasfranz.software> * Append comments instead of setting Signed-off-by: Jonas Franz <info@jonasfranz.software> * Use HeadRepo instead of BaseRepo Signed-off-by: Jonas Franz <info@jonasfranz.software> * Update migration Signed-off-by: Jonas Franz <info@jonasfranz.de> * Regenerated CSS Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add copyright Signed-off-by: Jonas Franz <info@jonasfranz.software> * Update index.css Signed-off-by: Jonas Franz <info@jonasfranz.software>
Diffstat (limited to 'models/git_diff.go')
-rw-r--r--models/git_diff.go206
1 files changed, 195 insertions, 11 deletions
diff --git a/models/git_diff.go b/models/git_diff.go
index 7b0b672ff1..006238cd06 100644
--- a/models/git_diff.go
+++ b/models/git_diff.go
@@ -14,6 +14,8 @@ import (
"io/ioutil"
"os"
"os/exec"
+ "regexp"
+ "sort"
"strconv"
"strings"
@@ -57,6 +59,7 @@ type DiffLine struct {
RightIdx int
Type DiffLineType
Content string
+ Comments []*Comment
}
// GetType returns the type of a DiffLine.
@@ -64,6 +67,19 @@ func (d *DiffLine) GetType() int {
return int(d.Type)
}
+// CanComment returns whether or not a line can get commented
+func (d *DiffLine) CanComment() bool {
+ return len(d.Comments) == 0 && d.Type != DiffLineSection
+}
+
+// GetCommentSide returns the comment side of the first comment, if not set returns empty string
+func (d *DiffLine) GetCommentSide() string {
+ if len(d.Comments) == 0 {
+ return ""
+ }
+ return d.Comments[0].DiffSide()
+}
+
// DiffSection represents a section of a DiffFile.
type DiffSection struct {
Name string
@@ -225,11 +241,167 @@ type Diff struct {
IsIncomplete bool
}
+// LoadComments loads comments into each line
+func (diff *Diff) LoadComments(issue *Issue, currentUser *User) error {
+ allComments, err := FetchCodeComments(issue, currentUser)
+ if err != nil {
+ return err
+ }
+ for _, file := range diff.Files {
+ if lineCommits, ok := allComments[file.Name]; ok {
+ for _, section := range file.Sections {
+ for _, line := range section.Lines {
+ if comments, ok := lineCommits[int64(line.LeftIdx*-1)]; ok {
+ line.Comments = append(line.Comments, comments...)
+ }
+ if comments, ok := lineCommits[int64(line.RightIdx)]; ok {
+ line.Comments = append(line.Comments, comments...)
+ }
+ sort.SliceStable(line.Comments, func(i, j int) bool {
+ return line.Comments[i].CreatedUnix < line.Comments[j].CreatedUnix
+ })
+ }
+ }
+ }
+ }
+ return nil
+}
+
// NumFiles returns number of files changes in a diff.
func (diff *Diff) NumFiles() int {
return len(diff.Files)
}
+// Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9]
+var hunkRegex = regexp.MustCompile(`^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@`)
+
+func isHeader(lof string) bool {
+ return strings.HasPrefix(lof, cmdDiffHead) || strings.HasPrefix(lof, "---") || strings.HasPrefix(lof, "+++")
+}
+
+// CutDiffAroundLine cuts a diff of a file in way that only the given line + numberOfLine above it will be shown
+// it also recalculates hunks and adds the appropriate headers to the new diff.
+// Warning: Only one-file diffs are allowed.
+func CutDiffAroundLine(originalDiff io.Reader, line int64, old bool, numbersOfLine int) string {
+ if line == 0 || numbersOfLine == 0 {
+ // no line or num of lines => no diff
+ return ""
+ }
+ scanner := bufio.NewScanner(originalDiff)
+ hunk := make([]string, 0)
+ // begin is the start of the hunk containing searched line
+ // end is the end of the hunk ...
+ // currentLine is the line number on the side of the searched line (differentiated by old)
+ // otherLine is the line number on the opposite side of the searched line (differentiated by old)
+ var begin, end, currentLine, otherLine int64
+ var headerLines int
+ for scanner.Scan() {
+ lof := scanner.Text()
+ // Add header to enable parsing
+ if isHeader(lof) {
+ hunk = append(hunk, lof)
+ headerLines++
+ }
+ if currentLine > line {
+ break
+ }
+ // Detect "hunk" with contains commented lof
+ if strings.HasPrefix(lof, "@@") {
+ // Already got our hunk. End of hunk detected!
+ if len(hunk) > headerLines {
+ break
+ }
+ groups := hunkRegex.FindStringSubmatch(lof)
+ if old {
+ begin = com.StrTo(groups[1]).MustInt64()
+ end = com.StrTo(groups[2]).MustInt64()
+ // init otherLine with begin of opposite side
+ otherLine = com.StrTo(groups[3]).MustInt64()
+ } else {
+ begin = com.StrTo(groups[3]).MustInt64()
+ end = com.StrTo(groups[4]).MustInt64()
+ // init otherLine with begin of opposite side
+ otherLine = com.StrTo(groups[1]).MustInt64()
+ }
+ end += begin // end is for real only the number of lines in hunk
+ // lof is between begin and end
+ if begin <= line && end >= line {
+ hunk = append(hunk, lof)
+ currentLine = begin
+ continue
+ }
+ } else if len(hunk) > headerLines {
+ hunk = append(hunk, lof)
+ // Count lines in context
+ switch lof[0] {
+ case '+':
+ if !old {
+ currentLine++
+ } else {
+ otherLine++
+ }
+ case '-':
+ if old {
+ currentLine++
+ } else {
+ otherLine++
+ }
+ default:
+ currentLine++
+ otherLine++
+ }
+ }
+ }
+
+ // No hunk found
+ if currentLine == 0 {
+ return ""
+ }
+ // headerLines + hunkLine (1) = totalNonCodeLines
+ if len(hunk)-headerLines-1 <= numbersOfLine {
+ // No need to cut the hunk => return existing hunk
+ return strings.Join(hunk, "\n")
+ }
+ var oldBegin, oldNumOfLines, newBegin, newNumOfLines int64
+ if old {
+ oldBegin = currentLine
+ newBegin = otherLine
+ } else {
+ oldBegin = otherLine
+ newBegin = currentLine
+ }
+ // headers + hunk header
+ newHunk := make([]string, headerLines)
+ // transfer existing headers
+ for idx, lof := range hunk[:headerLines] {
+ newHunk[idx] = lof
+ }
+ // transfer last n lines
+ for _, lof := range hunk[len(hunk)-numbersOfLine-1:] {
+ newHunk = append(newHunk, lof)
+ }
+ // calculate newBegin, ... by counting lines
+ for i := len(hunk) - 1; i >= len(hunk)-numbersOfLine; i-- {
+ switch hunk[i][0] {
+ case '+':
+ newBegin--
+ newNumOfLines++
+ case '-':
+ oldBegin--
+ oldNumOfLines++
+ default:
+ oldBegin--
+ newBegin--
+ newNumOfLines++
+ oldNumOfLines++
+ }
+ }
+ // construct the new hunk header
+ newHunk[headerLines] = fmt.Sprintf("@@ -%d,%d +%d,%d @@",
+ oldBegin, oldNumOfLines, newBegin, newNumOfLines)
+ return strings.Join(newHunk, "\n")
+}
+
const cmdDiffHead = "diff --git "
// ParsePatch builds a Diff object from a io.Reader and some
@@ -307,7 +479,6 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
if curFileLinesCount >= maxLines {
curFile.IsIncomplete = true
}
-
switch {
case line[0] == ' ':
diffLine := &DiffLine{Type: DiffLinePlain, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
@@ -524,32 +695,46 @@ const (
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
// TODO: move this function to gogits/git-module
func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
+ return GetRawDiffForFile(repoPath, "", commitID, diffType, "", writer)
+}
+
+// GetRawDiffForFile dumps diff results of file in given commit ID to io.Writer.
+// TODO: move this function to gogits/git-module
+func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error {
repo, err := git.OpenRepository(repoPath)
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
}
- commit, err := repo.GetCommit(commitID)
+ commit, err := repo.GetCommit(endCommit)
if err != nil {
return fmt.Errorf("GetCommit: %v", err)
}
-
+ fileArgs := make([]string, 0)
+ if len(file) > 0 {
+ fileArgs = append(fileArgs, "--", file)
+ }
var cmd *exec.Cmd
switch diffType {
case RawDiffNormal:
- if commit.ParentCount() == 0 {
- cmd = exec.Command("git", "show", commitID)
+ if len(startCommit) != 0 {
+ cmd = exec.Command("git", append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
+ } else if commit.ParentCount() == 0 {
+ cmd = exec.Command("git", append([]string{"show", endCommit}, fileArgs...)...)
} else {
c, _ := commit.Parent(0)
- cmd = exec.Command("git", "diff", "-M", c.ID.String(), commitID)
+ cmd = exec.Command("git", append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
}
case RawDiffPatch:
- if commit.ParentCount() == 0 {
- cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", "--root", commitID)
+ if len(startCommit) != 0 {
+ query := fmt.Sprintf("%s...%s", endCommit, startCommit)
+ cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
+ } else if commit.ParentCount() == 0 {
+ cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
} else {
c, _ := commit.Parent(0)
- query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
- cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", query)
+ query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
+ cmd = exec.Command("git", append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
}
default:
return fmt.Errorf("invalid diffType: %s", diffType)
@@ -560,7 +745,6 @@ func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Write
cmd.Dir = repoPath
cmd.Stdout = writer
cmd.Stderr = stderr
-
if err = cmd.Run(); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr)
}