* Some refactor on git diff and ignore getting commit information failed on migrating pull request review comments * fix test * fix lint * Change error log to warntags/v1.21.12.1
| @@ -0,0 +1,260 @@ | |||
| // Copyright 2020 The Gitea Authors. All rights reserved. | |||
| // Use of this source code is governed by a MIT-style | |||
| // license that can be found in the LICENSE file. | |||
| package git | |||
| import ( | |||
| "bufio" | |||
| "bytes" | |||
| "context" | |||
| "fmt" | |||
| "io" | |||
| "os/exec" | |||
| "regexp" | |||
| "strconv" | |||
| "strings" | |||
| "code.gitea.io/gitea/modules/process" | |||
| ) | |||
| // RawDiffType type of a raw diff. | |||
| type RawDiffType string | |||
| // RawDiffType possible values. | |||
| const ( | |||
| RawDiffNormal RawDiffType = "diff" | |||
| RawDiffPatch RawDiffType = "patch" | |||
| ) | |||
| // GetRawDiff dumps diff results of repository in given commit ID to io.Writer. | |||
| 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. | |||
| func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { | |||
| repo, err := OpenRepository(repoPath) | |||
| if err != nil { | |||
| return fmt.Errorf("OpenRepository: %v", err) | |||
| } | |||
| defer repo.Close() | |||
| return GetRepoRawDiffForFile(repo, startCommit, endCommit, diffType, file, writer) | |||
| } | |||
| // GetRepoRawDiffForFile dumps diff results of file in given commit ID to io.Writer according given repository | |||
| func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { | |||
| 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) | |||
| } | |||
| // FIXME: graceful: These commands should have a timeout | |||
| ctx, cancel := context.WithCancel(DefaultContext) | |||
| defer cancel() | |||
| var cmd *exec.Cmd | |||
| switch diffType { | |||
| case RawDiffNormal: | |||
| if len(startCommit) != 0 { | |||
| cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...) | |||
| } else if commit.ParentCount() == 0 { | |||
| cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"show", endCommit}, fileArgs...)...) | |||
| } else { | |||
| c, _ := commit.Parent(0) | |||
| cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...) | |||
| } | |||
| case RawDiffPatch: | |||
| if len(startCommit) != 0 { | |||
| query := fmt.Sprintf("%s...%s", endCommit, startCommit) | |||
| cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...) | |||
| } else if commit.ParentCount() == 0 { | |||
| cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...) | |||
| } else { | |||
| c, _ := commit.Parent(0) | |||
| query := fmt.Sprintf("%s...%s", endCommit, c.ID.String()) | |||
| cmd = exec.CommandContext(ctx, GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...) | |||
| } | |||
| default: | |||
| return fmt.Errorf("invalid diffType: %s", diffType) | |||
| } | |||
| stderr := new(bytes.Buffer) | |||
| cmd.Dir = repo.Path | |||
| cmd.Stdout = writer | |||
| cmd.Stderr = stderr | |||
| pid := process.GetManager().Add(fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repo.Path), cancel) | |||
| defer process.GetManager().Remove(pid) | |||
| if err = cmd.Run(); err != nil { | |||
| return fmt.Errorf("Run: %v - %s", err, stderr) | |||
| } | |||
| return nil | |||
| } | |||
| // ParseDiffHunkString parse the diffhunk content and return | |||
| func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHunk int) { | |||
| ss := strings.Split(diffhunk, "@@") | |||
| ranges := strings.Split(ss[1][1:], " ") | |||
| leftRange := strings.Split(ranges[0], ",") | |||
| leftLine, _ = strconv.Atoi(leftRange[0][1:]) | |||
| if len(leftRange) > 1 { | |||
| leftHunk, _ = strconv.Atoi(leftRange[1]) | |||
| } | |||
| if len(ranges) > 1 { | |||
| rightRange := strings.Split(ranges[1], ",") | |||
| rightLine, _ = strconv.Atoi(rightRange[0]) | |||
| if len(rightRange) > 1 { | |||
| righHunk, _ = strconv.Atoi(rightRange[1]) | |||
| } | |||
| } else { | |||
| log("Parse line number failed: %v", diffhunk) | |||
| rightLine = leftLine | |||
| righHunk = leftHunk | |||
| } | |||
| return | |||
| } | |||
| // Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9] | |||
| var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+))? \+(?P<beginNew>[0-9]+)(,(?P<endNew>[0-9]+))? @@`) | |||
| const cmdDiffHead = "diff --git " | |||
| 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 | |||
| } | |||
| // A map with named groups of our regex to recognize them later more easily | |||
| submatches := hunkRegex.FindStringSubmatch(lof) | |||
| groups := make(map[string]string) | |||
| for i, name := range hunkRegex.SubexpNames() { | |||
| if i != 0 && name != "" { | |||
| groups[name] = submatches[i] | |||
| } | |||
| } | |||
| if old { | |||
| begin, _ = strconv.ParseInt(groups["beginOld"], 10, 64) | |||
| end, _ = strconv.ParseInt(groups["endOld"], 10, 64) | |||
| // init otherLine with begin of opposite side | |||
| otherLine, _ = strconv.ParseInt(groups["beginNew"], 10, 64) | |||
| } else { | |||
| begin, _ = strconv.ParseInt(groups["beginNew"], 10, 64) | |||
| if groups["endNew"] != "" { | |||
| end, _ = strconv.ParseInt(groups["endNew"], 10, 64) | |||
| } else { | |||
| end = 0 | |||
| } | |||
| // init otherLine with begin of opposite side | |||
| otherLine, _ = strconv.ParseInt(groups["beginOld"], 10, 64) | |||
| } | |||
| 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 | |||
| copy(newHunk, hunk[:headerLines]) | |||
| // transfer last n lines | |||
| newHunk = append(newHunk, hunk[len(hunk)-numbersOfLine-1:]...) | |||
| // 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") | |||
| } | |||
| @@ -0,0 +1,82 @@ | |||
| // Copyright 2020 The Gitea Authors. All rights reserved. | |||
| // Use of this source code is governed by a MIT-style | |||
| // license that can be found in the LICENSE file. | |||
| package git | |||
| import ( | |||
| "strings" | |||
| "testing" | |||
| "github.com/stretchr/testify/assert" | |||
| ) | |||
| const exampleDiff = `diff --git a/README.md b/README.md | |||
| --- a/README.md | |||
| +++ b/README.md | |||
| @@ -1,3 +1,6 @@ | |||
| # gitea-github-migrator | |||
| + | |||
| + Build Status | |||
| - Latest Release | |||
| Docker Pulls | |||
| + cut off | |||
| + cut off` | |||
| func TestCutDiffAroundLine(t *testing.T) { | |||
| result := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3) | |||
| resultByLine := strings.Split(result, "\n") | |||
| assert.Len(t, resultByLine, 7) | |||
| // Check if headers got transferred | |||
| assert.Equal(t, "diff --git a/README.md b/README.md", resultByLine[0]) | |||
| assert.Equal(t, "--- a/README.md", resultByLine[1]) | |||
| assert.Equal(t, "+++ b/README.md", resultByLine[2]) | |||
| // Check if hunk header is calculated correctly | |||
| assert.Equal(t, "@@ -2,2 +3,2 @@", resultByLine[3]) | |||
| // Check if line got transferred | |||
| assert.Equal(t, "+ Build Status", resultByLine[4]) | |||
| // Must be same result as before since old line 3 == new line 5 | |||
| newResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3) | |||
| assert.Equal(t, result, newResult, "Must be same result as before since old line 3 == new line 5") | |||
| newResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300) | |||
| assert.Equal(t, exampleDiff, newResult) | |||
| emptyResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0) | |||
| assert.Empty(t, emptyResult) | |||
| // Line is out of scope | |||
| emptyResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0) | |||
| assert.Empty(t, emptyResult) | |||
| } | |||
| func BenchmarkCutDiffAroundLine(b *testing.B) { | |||
| for n := 0; n < b.N; n++ { | |||
| CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3) | |||
| } | |||
| } | |||
| func ExampleCutDiffAroundLine() { | |||
| const diff = `diff --git a/README.md b/README.md | |||
| --- a/README.md | |||
| +++ b/README.md | |||
| @@ -1,3 +1,6 @@ | |||
| # gitea-github-migrator | |||
| + | |||
| + Build Status | |||
| - Latest Release | |||
| Docker Pulls | |||
| + cut off | |||
| + cut off` | |||
| result := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3) | |||
| println(result) | |||
| } | |||
| func TestParseDiffHunkString(t *testing.T) { | |||
| leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER") | |||
| assert.EqualValues(t, 19, leftLine) | |||
| assert.EqualValues(t, 3, leftHunk) | |||
| assert.EqualValues(t, 19, rightLine) | |||
| assert.EqualValues(t, 5, rightHunk) | |||
| } | |||
| @@ -28,7 +28,6 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/modules/structs" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "code.gitea.io/gitea/services/gitdiff" | |||
| gouuid "github.com/satori/go.uuid" | |||
| ) | |||
| @@ -783,19 +782,22 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { | |||
| } | |||
| for _, comment := range review.Comments { | |||
| _, _, line, _ := git.ParseDiffHunkString(comment.DiffHunk) | |||
| headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName()) | |||
| if err != nil { | |||
| return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) | |||
| } | |||
| var patch string | |||
| patchBuf := new(bytes.Buffer) | |||
| if err := gitdiff.GetRawDiffForFile(g.gitRepo.Path, pr.MergeBase, headCommitID, gitdiff.RawDiffNormal, comment.TreePath, patchBuf); err != nil { | |||
| return fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err) | |||
| if err := git.GetRepoRawDiffForFile(g.gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, comment.TreePath, patchBuf); err != nil { | |||
| // We should ignore the error since the commit maybe removed when force push to the pull request | |||
| log.Warn("GetRepoRawDiffForFile failed when migrating [%s, %s, %s, %s]: %v", g.gitRepo.Path, pr.MergeBase, headCommitID, comment.TreePath, err) | |||
| } else { | |||
| patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) | |||
| } | |||
| _, _, line, _ := gitdiff.ParseDiffHunkString(comment.DiffHunk) | |||
| patch := gitdiff.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: int64(line + comment.Position - 1)}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) | |||
| var c = models.Comment{ | |||
| Type: models.CommentTypeCode, | |||
| PosterID: comment.PosterID, | |||
| @@ -292,10 +292,10 @@ func Diff(ctx *context.Context) { | |||
| // RawDiff dumps diff results of repository in given commit ID to io.Writer | |||
| func RawDiff(ctx *context.Context) { | |||
| if err := gitdiff.GetRawDiff( | |||
| if err := git.GetRawDiff( | |||
| models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name), | |||
| ctx.Params(":sha"), | |||
| gitdiff.RawDiffType(ctx.Params(":ext")), | |||
| git.RawDiffType(ctx.Params(":ext")), | |||
| ctx.Resp, | |||
| ); err != nil { | |||
| ctx.ServerError("GetRawDiff", err) | |||
| @@ -17,7 +17,6 @@ import ( | |||
| "net/url" | |||
| "os" | |||
| "os/exec" | |||
| "regexp" | |||
| "sort" | |||
| "strconv" | |||
| "strings" | |||
| @@ -31,7 +30,6 @@ import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "github.com/sergi/go-diff/diffmatchpatch" | |||
| "github.com/unknwon/com" | |||
| stdcharset "golang.org/x/net/html/charset" | |||
| "golang.org/x/text/transform" | |||
| ) | |||
| @@ -149,31 +147,8 @@ func (d *DiffLine) GetExpandDirection() DiffLineExpandDirection { | |||
| return DiffLineExpandSingle | |||
| } | |||
| // ParseDiffHunkString parse the diffhunk content and return | |||
| func ParseDiffHunkString(diffhunk string) (leftLine, leftHunk, rightLine, righHunk int) { | |||
| ss := strings.Split(diffhunk, "@@") | |||
| ranges := strings.Split(ss[1][1:], " ") | |||
| leftRange := strings.Split(ranges[0], ",") | |||
| leftLine, _ = com.StrTo(leftRange[0][1:]).Int() | |||
| if len(leftRange) > 1 { | |||
| leftHunk, _ = com.StrTo(leftRange[1]).Int() | |||
| } | |||
| if len(ranges) > 1 { | |||
| rightRange := strings.Split(ranges[1], ",") | |||
| rightLine, _ = com.StrTo(rightRange[0]).Int() | |||
| if len(rightRange) > 1 { | |||
| righHunk, _ = com.StrTo(rightRange[1]).Int() | |||
| } | |||
| } else { | |||
| log.Warn("Parse line number failed: %v", diffhunk) | |||
| rightLine = leftLine | |||
| righHunk = leftHunk | |||
| } | |||
| return | |||
| } | |||
| func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo { | |||
| leftLine, leftHunk, rightLine, righHunk := ParseDiffHunkString(line) | |||
| leftLine, leftHunk, rightLine, righHunk := git.ParseDiffHunkString(line) | |||
| return &DiffLineSectionInfo{ | |||
| Path: treePath, | |||
| @@ -428,143 +403,6 @@ func (diff *Diff) NumFiles() int { | |||
| return len(diff.Files) | |||
| } | |||
| // Example: @@ -1,8 +1,9 @@ => [..., 1, 8, 1, 9] | |||
| var hunkRegex = regexp.MustCompile(`^@@ -(?P<beginOld>[0-9]+)(,(?P<endOld>[0-9]+))? \+(?P<beginNew>[0-9]+)(,(?P<endNew>[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 | |||
| } | |||
| // A map with named groups of our regex to recognize them later more easily | |||
| submatches := hunkRegex.FindStringSubmatch(lof) | |||
| groups := make(map[string]string) | |||
| for i, name := range hunkRegex.SubexpNames() { | |||
| if i != 0 && name != "" { | |||
| groups[name] = submatches[i] | |||
| } | |||
| } | |||
| if old { | |||
| begin = com.StrTo(groups["beginOld"]).MustInt64() | |||
| end = com.StrTo(groups["endOld"]).MustInt64() | |||
| // init otherLine with begin of opposite side | |||
| otherLine = com.StrTo(groups["beginNew"]).MustInt64() | |||
| } else { | |||
| begin = com.StrTo(groups["beginNew"]).MustInt64() | |||
| if groups["endNew"] != "" { | |||
| end = com.StrTo(groups["endNew"]).MustInt64() | |||
| } else { | |||
| end = 0 | |||
| } | |||
| // init otherLine with begin of opposite side | |||
| otherLine = com.StrTo(groups["beginOld"]).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 | |||
| copy(newHunk, hunk[:headerLines]) | |||
| // transfer last n lines | |||
| newHunk = append(newHunk, hunk[len(hunk)-numbersOfLine-1:]...) | |||
| // 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 | |||
| @@ -881,82 +719,6 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID | |||
| return diff, nil | |||
| } | |||
| // RawDiffType type of a raw diff. | |||
| type RawDiffType string | |||
| // RawDiffType possible values. | |||
| const ( | |||
| RawDiffNormal RawDiffType = "diff" | |||
| RawDiffPatch RawDiffType = "patch" | |||
| ) | |||
| // 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) | |||
| } | |||
| defer repo.Close() | |||
| 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) | |||
| } | |||
| // FIXME: graceful: These commands should have a timeout | |||
| ctx, cancel := context.WithCancel(git.DefaultContext) | |||
| defer cancel() | |||
| var cmd *exec.Cmd | |||
| switch diffType { | |||
| case RawDiffNormal: | |||
| if len(startCommit) != 0 { | |||
| cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...) | |||
| } else if commit.ParentCount() == 0 { | |||
| cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"show", endCommit}, fileArgs...)...) | |||
| } else { | |||
| c, _ := commit.Parent(0) | |||
| cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...) | |||
| } | |||
| case RawDiffPatch: | |||
| if len(startCommit) != 0 { | |||
| query := fmt.Sprintf("%s...%s", endCommit, startCommit) | |||
| cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...) | |||
| } else if commit.ParentCount() == 0 { | |||
| cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...) | |||
| } else { | |||
| c, _ := commit.Parent(0) | |||
| query := fmt.Sprintf("%s...%s", endCommit, c.ID.String()) | |||
| cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...) | |||
| } | |||
| default: | |||
| return fmt.Errorf("invalid diffType: %s", diffType) | |||
| } | |||
| stderr := new(bytes.Buffer) | |||
| cmd.Dir = repoPath | |||
| cmd.Stdout = writer | |||
| cmd.Stderr = stderr | |||
| pid := process.GetManager().Add(fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repoPath), cancel) | |||
| defer process.GetManager().Remove(pid) | |||
| if err = cmd.Run(); err != nil { | |||
| return fmt.Errorf("Run: %v - %s", err, stderr) | |||
| } | |||
| return nil | |||
| } | |||
| // GetDiffCommit builds a Diff representing the given commitID. | |||
| func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) { | |||
| return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles) | |||
| @@ -41,68 +41,6 @@ func TestDiffToHTML(t *testing.T) { | |||
| }, DiffLineDel)) | |||
| } | |||
| const exampleDiff = `diff --git a/README.md b/README.md | |||
| --- a/README.md | |||
| +++ b/README.md | |||
| @@ -1,3 +1,6 @@ | |||
| # gitea-github-migrator | |||
| + | |||
| + Build Status | |||
| - Latest Release | |||
| Docker Pulls | |||
| + cut off | |||
| + cut off` | |||
| func TestCutDiffAroundLine(t *testing.T) { | |||
| result := CutDiffAroundLine(strings.NewReader(exampleDiff), 4, false, 3) | |||
| resultByLine := strings.Split(result, "\n") | |||
| assert.Len(t, resultByLine, 7) | |||
| // Check if headers got transferred | |||
| assert.Equal(t, "diff --git a/README.md b/README.md", resultByLine[0]) | |||
| assert.Equal(t, "--- a/README.md", resultByLine[1]) | |||
| assert.Equal(t, "+++ b/README.md", resultByLine[2]) | |||
| // Check if hunk header is calculated correctly | |||
| assert.Equal(t, "@@ -2,2 +3,2 @@", resultByLine[3]) | |||
| // Check if line got transferred | |||
| assert.Equal(t, "+ Build Status", resultByLine[4]) | |||
| // Must be same result as before since old line 3 == new line 5 | |||
| newResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3) | |||
| assert.Equal(t, result, newResult, "Must be same result as before since old line 3 == new line 5") | |||
| newResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 300) | |||
| assert.Equal(t, exampleDiff, newResult) | |||
| emptyResult := CutDiffAroundLine(strings.NewReader(exampleDiff), 6, false, 0) | |||
| assert.Empty(t, emptyResult) | |||
| // Line is out of scope | |||
| emptyResult = CutDiffAroundLine(strings.NewReader(exampleDiff), 434, false, 0) | |||
| assert.Empty(t, emptyResult) | |||
| } | |||
| func BenchmarkCutDiffAroundLine(b *testing.B) { | |||
| for n := 0; n < b.N; n++ { | |||
| CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3) | |||
| } | |||
| } | |||
| func ExampleCutDiffAroundLine() { | |||
| const diff = `diff --git a/README.md b/README.md | |||
| --- a/README.md | |||
| +++ b/README.md | |||
| @@ -1,3 +1,6 @@ | |||
| # gitea-github-migrator | |||
| + | |||
| + Build Status | |||
| - Latest Release | |||
| Docker Pulls | |||
| + cut off | |||
| + cut off` | |||
| result := CutDiffAroundLine(strings.NewReader(diff), 4, false, 3) | |||
| println(result) | |||
| } | |||
| func TestParsePatch(t *testing.T) { | |||
| var diff = `diff --git "a/README.md" "b/README.md" | |||
| --- a/README.md | |||
| @@ -209,11 +147,3 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { | |||
| } | |||
| } | |||
| } | |||
| func TestParseDiffHunkString(t *testing.T) { | |||
| leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER") | |||
| assert.EqualValues(t, 19, leftLine) | |||
| assert.EqualValues(t, 3, leftHunk) | |||
| assert.EqualValues(t, 19, rightLine) | |||
| assert.EqualValues(t, 5, rightHunk) | |||
| } | |||
| @@ -14,7 +14,6 @@ import ( | |||
| "code.gitea.io/gitea/modules/git" | |||
| "code.gitea.io/gitea/modules/notification" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "code.gitea.io/gitea/services/gitdiff" | |||
| ) | |||
| // CreateCodeComment creates a comment on the code line | |||
| @@ -140,10 +139,10 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models | |||
| return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) | |||
| } | |||
| patchBuf := new(bytes.Buffer) | |||
| if err := gitdiff.GetRawDiffForFile(gitRepo.Path, pr.MergeBase, headCommitID, gitdiff.RawDiffNormal, treePath, patchBuf); err != nil { | |||
| if err := git.GetRepoRawDiffForFile(gitRepo, pr.MergeBase, headCommitID, git.RawDiffNormal, treePath, patchBuf); err != nil { | |||
| return nil, fmt.Errorf("GetRawDiffForLine[%s, %s, %s, %s]: %v", err, gitRepo.Path, pr.MergeBase, headCommitID, treePath) | |||
| } | |||
| patch = gitdiff.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) | |||
| patch = git.CutDiffAroundLine(patchBuf, int64((&models.Comment{Line: line}).UnsignedLine()), line < 0, setting.UI.CodeCommentLines) | |||
| } | |||
| return models.CreateComment(&models.CreateCommentOptions{ | |||
| Type: models.CommentTypeCode, | |||