From 3bcc6e7a9e8b04fb0f50e34e0d1437749ac6252a Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Thu, 16 Jul 2020 20:24:36 +0100
Subject: [PATCH] Ensure that git commit tree continues properly over the page
 (#12142)

* Ensure that git commit tree continues properly over the page

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Avoid generating strings when skipping

Signed-off-by: Andrew Thornton <art27@cantab.net>

* skip initial non-commit-lines

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lauris BH <lauris@nix.lv>
---
 modules/gitgraph/graph.go | 67 +++++++++++++++++++++++++++++++--------
 1 file changed, 53 insertions(+), 14 deletions(-)

diff --git a/modules/gitgraph/graph.go b/modules/gitgraph/graph.go
index 0dd68ad5c5..4ba110c706 100644
--- a/modules/gitgraph/graph.go
+++ b/modules/gitgraph/graph.go
@@ -5,7 +5,11 @@
 package gitgraph
 
 import (
+	"bufio"
+	"bytes"
+	"context"
 	"fmt"
+	"os"
 	"strings"
 
 	"code.gitea.io/gitea/modules/git"
@@ -31,37 +35,72 @@ type GraphItems []GraphItem
 
 // GetCommitGraph return a list of commit (GraphItems) from all branches
 func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
-
-	var CommitGraph []GraphItem
-
 	format := "DATA:|%d|%H|%ad|%an|%ae|%h|%s"
 
+	if page == 0 {
+		page = 1
+	}
+
 	graphCmd := git.NewCommand("log")
 	graphCmd.AddArguments("--graph",
 		"--date-order",
 		"--all",
 		"-C",
 		"-M",
-		fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum),
-		fmt.Sprintf("--skip=%d", setting.UI.GraphMaxCommitNum*(page-1)),
+		fmt.Sprintf("-n %d", setting.UI.GraphMaxCommitNum*page),
 		"--date=iso",
 		fmt.Sprintf("--pretty=format:%s", format),
 	)
-	graph, err := graphCmd.RunInDir(r.Path)
+	commitGraph := make([]GraphItem, 0, 100)
+	stderr := new(strings.Builder)
+	stdoutReader, stdoutWriter, err := os.Pipe()
 	if err != nil {
-		return CommitGraph, err
+		return nil, err
 	}
+	commitsToSkip := setting.UI.GraphMaxCommitNum * (page - 1)
 
-	CommitGraph = make([]GraphItem, 0, 100)
-	for _, s := range strings.Split(graph, "\n") {
-		GraphItem, err := graphItemFromString(s, r)
-		if err != nil {
-			return CommitGraph, err
+	scanner := bufio.NewScanner(stdoutReader)
+
+	if err := graphCmd.RunInDirTimeoutEnvFullPipelineFunc(nil, -1, r.Path, stdoutWriter, stderr, nil, func(ctx context.Context, cancel context.CancelFunc) error {
+		_ = stdoutWriter.Close()
+		defer stdoutReader.Close()
+		for commitsToSkip > 0 && scanner.Scan() {
+			line := scanner.Bytes()
+			dataIdx := bytes.Index(line, []byte("DATA:"))
+			starIdx := bytes.IndexByte(line, '*')
+			if starIdx >= 0 && starIdx < dataIdx {
+				commitsToSkip--
+			}
 		}
-		CommitGraph = append(CommitGraph, GraphItem)
+		// Skip initial non-commit lines
+		for scanner.Scan() {
+			if bytes.IndexByte(scanner.Bytes(), '*') >= 0 {
+				line := scanner.Text()
+				graphItem, err := graphItemFromString(line, r)
+				if err != nil {
+					cancel()
+					return err
+				}
+				commitGraph = append(commitGraph, graphItem)
+				break
+			}
+		}
+
+		for scanner.Scan() {
+			line := scanner.Text()
+			graphItem, err := graphItemFromString(line, r)
+			if err != nil {
+				cancel()
+				return err
+			}
+			commitGraph = append(commitGraph, graphItem)
+		}
+		return scanner.Err()
+	}); err != nil {
+		return commitGraph, err
 	}
 
-	return CommitGraph, nil
+	return commitGraph, nil
 }
 
 func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {