* 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 warnmaster
@@ -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/setting" | ||||
"code.gitea.io/gitea/modules/structs" | "code.gitea.io/gitea/modules/structs" | ||||
"code.gitea.io/gitea/modules/timeutil" | "code.gitea.io/gitea/modules/timeutil" | ||||
"code.gitea.io/gitea/services/gitdiff" | |||||
gouuid "github.com/satori/go.uuid" | gouuid "github.com/satori/go.uuid" | ||||
) | ) | ||||
@@ -783,19 +782,22 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { | |||||
} | } | ||||
for _, comment := range review.Comments { | for _, comment := range review.Comments { | ||||
_, _, line, _ := git.ParseDiffHunkString(comment.DiffHunk) | |||||
headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName()) | headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName()) | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) | return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) | ||||
} | } | ||||
var patch string | |||||
patchBuf := new(bytes.Buffer) | 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{ | var c = models.Comment{ | ||||
Type: models.CommentTypeCode, | Type: models.CommentTypeCode, | ||||
PosterID: comment.PosterID, | 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 | // RawDiff dumps diff results of repository in given commit ID to io.Writer | ||||
func RawDiff(ctx *context.Context) { | func RawDiff(ctx *context.Context) { | ||||
if err := gitdiff.GetRawDiff( | |||||
if err := git.GetRawDiff( | |||||
models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name), | models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name), | ||||
ctx.Params(":sha"), | ctx.Params(":sha"), | ||||
gitdiff.RawDiffType(ctx.Params(":ext")), | |||||
git.RawDiffType(ctx.Params(":ext")), | |||||
ctx.Resp, | ctx.Resp, | ||||
); err != nil { | ); err != nil { | ||||
ctx.ServerError("GetRawDiff", err) | ctx.ServerError("GetRawDiff", err) | ||||
@@ -17,7 +17,6 @@ import ( | |||||
"net/url" | "net/url" | ||||
"os" | "os" | ||||
"os/exec" | "os/exec" | ||||
"regexp" | |||||
"sort" | "sort" | ||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
@@ -31,7 +30,6 @@ import ( | |||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"github.com/sergi/go-diff/diffmatchpatch" | "github.com/sergi/go-diff/diffmatchpatch" | ||||
"github.com/unknwon/com" | |||||
stdcharset "golang.org/x/net/html/charset" | stdcharset "golang.org/x/net/html/charset" | ||||
"golang.org/x/text/transform" | "golang.org/x/text/transform" | ||||
) | ) | ||||
@@ -149,31 +147,8 @@ func (d *DiffLine) GetExpandDirection() DiffLineExpandDirection { | |||||
return DiffLineExpandSingle | 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 { | func getDiffLineSectionInfo(treePath, line string, lastLeftIdx, lastRightIdx int) *DiffLineSectionInfo { | ||||
leftLine, leftHunk, rightLine, righHunk := ParseDiffHunkString(line) | |||||
leftLine, leftHunk, rightLine, righHunk := git.ParseDiffHunkString(line) | |||||
return &DiffLineSectionInfo{ | return &DiffLineSectionInfo{ | ||||
Path: treePath, | Path: treePath, | ||||
@@ -428,143 +403,6 @@ func (diff *Diff) NumFiles() int { | |||||
return len(diff.Files) | 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 " | const cmdDiffHead = "diff --git " | ||||
// ParsePatch builds a Diff object from a io.Reader and some | // ParsePatch builds a Diff object from a io.Reader and some | ||||
@@ -881,82 +719,6 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID | |||||
return diff, nil | 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. | // GetDiffCommit builds a Diff representing the given commitID. | ||||
func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) { | func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacters, maxFiles int) (*Diff, error) { | ||||
return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles) | return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacters, maxFiles) | ||||
@@ -41,68 +41,6 @@ func TestDiffToHTML(t *testing.T) { | |||||
}, DiffLineDel)) | }, 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) { | func TestParsePatch(t *testing.T) { | ||||
var diff = `diff --git "a/README.md" "b/README.md" | var diff = `diff --git "a/README.md" "b/README.md" | ||||
--- a/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/git" | ||||
"code.gitea.io/gitea/modules/notification" | "code.gitea.io/gitea/modules/notification" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/services/gitdiff" | |||||
) | ) | ||||
// CreateCodeComment creates a comment on the code line | // 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) | return nil, fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) | ||||
} | } | ||||
patchBuf := new(bytes.Buffer) | 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) | 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{ | return models.CreateComment(&models.CreateCommentOptions{ | ||||
Type: models.CommentTypeCode, | Type: models.CommentTypeCode, | ||||