From 7d82df9ac6f35258115b993380854f29679c83f4 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 25 Jul 2024 15:24:56 -0700 Subject: [PATCH 01/19] working --- go/cmd/dolt/cli/arg_parser_helpers.go | 1 + go/cmd/dolt/cli/flags.go | 1 + go/cmd/dolt/commands/log.go | 2 + go/cmd/dolt/commands/log_graph.go | 466 ++++++++++++++++++++++++++ 4 files changed, 470 insertions(+) create mode 100644 go/cmd/dolt/commands/log_graph.go diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index fc8fe1acb0a..4adfc5bceff 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -280,6 +280,7 @@ func CreateLogArgParser(isTableFunction bool) *argparser.ArgParser { } else { ap.SupportsFlag(OneLineFlag, "", "Shows logs in a compact format.") ap.SupportsFlag(StatFlag, "", "Shows the diffstat for each commit.") + ap.SupportsFlag(GraphFlag, "", "Shows the commit graph.") } return ap } diff --git a/go/cmd/dolt/cli/flags.go b/go/cmd/dolt/cli/flags.go index b045808b7c3..441af53c0d1 100644 --- a/go/cmd/dolt/cli/flags.go +++ b/go/cmd/dolt/cli/flags.go @@ -36,6 +36,7 @@ const ( DepthFlag = "depth" DryRunFlag = "dry-run" ForceFlag = "force" + GraphFlag = "graph" HardResetParam = "hard" HostFlag = "host" InteractiveFlag = "interactive" diff --git a/go/cmd/dolt/commands/log.go b/go/cmd/dolt/commands/log.go index 2bbeeb1e921..cac7bf867b4 100644 --- a/go/cmd/dolt/commands/log.go +++ b/go/cmd/dolt/commands/log.go @@ -359,6 +359,8 @@ func logToStdOut(apr *argparser.ArgParseResults, commits []CommitInfo, sqlCtx *s defer pager.Stop() if apr.Contains(cli.OneLineFlag) { err = logCompact(pager, apr, commits, sqlCtx, queryist) + } else if apr.Contains(cli.GraphFlag) { + logGraph(pager, commits) } else { err = logDefault(pager, apr, commits, sqlCtx, queryist) } diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go new file mode 100644 index 00000000000..986d1f29eca --- /dev/null +++ b/go/cmd/dolt/commands/log_graph.go @@ -0,0 +1,466 @@ +package commands + +import ( + "fmt" + "math" + "strings" + + "github.com/dolthub/dolt/go/store/util/outputpager" +) + +// Define the structure of the commit data +type Commit struct { + Hash string + Parents []string + Children []string + Committer string + Message string + CommitDate string + CommitColor string + X int + Y int +} + +type CommitInfoWithChildren struct { + Commit CommitInfo + Children []string + X int + Y int + formattedMessage []string +} + +var branchColors = []string{ + "\033[31m", // Red + "\033[32m", // Green + "\033[34m", // Blue + "\033[35m", // Magenta + "\033[36m", // Cyan + "\033[39m", // Default + "\033[1;31m", // Bold Red + "\033[1;32m", // Bold Green + "\033[1;33m", // Bold Yellow + "\033[1;34m", // Bold Blue + "\033[1;35m", // Bold Magenta + "\033[1;36m", // Bold Cyan + "\033[1;37m", // Bold White + "\033[1;39m", // Bold Default + "\033[2;31m", // Faint Red + "\033[2;32m", // Faint Green + "\033[2;33m", // Faint Yellow + "\033[2;34m", // Faint Blue + "\033[2;35m", // Faint Magenta + "\033[2;36m", // Faint Cyan + "\033[2;37m", // Faint White + "\033[2;39m", // Faint Default + "\033[41m", // Background Red + "\033[42m", // Background Green + "\033[43m", // Background Yellow + "\033[44m", // Background Blue + "\033[45m", // Background Magenta + "\033[46m", // Background Cyan + "\033[47m", // Background White + "\033[49m", // Background Default + "\033[2m", // Faint + "\033[2;3m", // Faint Italic + "\033[7m", // Reverse +} + +// get the children of commits, and initialize the x and y coordinates of the commits +func formatCommits(commits []CommitInfo) []CommitInfoWithChildren { + childrenMap := make(map[string][]string) + for _, commit := range commits { + for _, parent := range commit.parentHashes { + childrenMap[parent] = append(childrenMap[parent], commit.commitHash) + } + } + + var commitsWithChildren []CommitInfoWithChildren + for y, commit := range commits { + commitsWithChildren = append(commitsWithChildren, CommitInfoWithChildren{ + Commit: commit, + Children: childrenMap[commit.commitHash], + X: -1, + Y: y, + }) + } + + return commitsWithChildren +} + +type BranchPathType struct { + Start int + End int + EndCommitHash string + BranchOrder int +} + +func computeColumns(commits []CommitInfoWithChildren) []CommitInfoWithChildren { + commitsMap := make(map[string]CommitInfoWithChildren) + for _, commit := range commits { + commitsMap[commit.Commit.commitHash] = commit + } + + columns := [][]BranchPathType{} + commitYs := make(map[string]int) + + commitXs := make(map[string]int) + for index, commit := range commits { + commitXs[commit.Commit.commitHash] = index + } + + updateColumns := func(col, end int, endCommitHash string) { + columns[col][len(columns[col])-1].End = end + columns[col][len(columns[col])-1].EndCommitHash = endCommitHash + } + + branchOrder := 0 + + for index, commit := range commits { + var branchChildren []string + for _, child := range commit.Children { + if commitsMap[child].Commit.parentHashes[0] == commit.Commit.commitHash { + branchChildren = append(branchChildren, child) + } + } + + isLastCommitOnBranch := len(commit.Children) == 0 + isChildOfNonMergeCommit := len(branchChildren) > 0 + + commitY := -1 + + isFirstCommit := len(commit.Commit.parentHashes) == 0 + + if isLastCommitOnBranch { + columns = append(columns, []BranchPathType{ + { + Start: index, + End: func() int { + if isFirstCommit { + return index + } else { + return math.MaxInt + } + }(), + EndCommitHash: commit.Commit.commitHash, + BranchOrder: branchOrder, + }, + }) + branchOrder++ + commitY = len(columns) - 1 + } else if isChildOfNonMergeCommit { + var branchChildrenYs []int + for _, childHash := range branchChildren { + if y, ok := commitYs[childHash]; ok { + branchChildrenYs = append(branchChildrenYs, y) + } + } + + commitY = min(branchChildrenYs...) + + updateColumns(commitY, func() int { + if isFirstCommit { + return index + } else { + return math.MaxInt + } + }(), commit.Commit.commitHash) + + for _, childY := range branchChildrenYs { + if childY != commitY { + updateColumns(childY, index-1, commit.Commit.commitHash) + } + } + } else { + minChildX := math.MaxInt + maxChildY := -1 + + for _, child := range commit.Children { + childX := commitXs[child] + childY := commitYs[child] + + if childX < minChildX { + minChildX = childX + } + + if childY > maxChildY { + maxChildY = childY + } + } + + colFitAtEnd := -1 + for i := maxChildY + 1; i < len(columns); i++ { + if minChildX >= columns[i][len(columns[i])-1].End { + colFitAtEnd = i - (maxChildY + 1) + break + } + } + + col := -1 + if colFitAtEnd != -1 { + col = maxChildY + 1 + colFitAtEnd + } + + if col == -1 { + columns = append(columns, []BranchPathType{ + { + Start: minChildX + 1, + End: func() int { + if isFirstCommit { + return index + } else { + return math.MaxInt + } + }(), + EndCommitHash: commit.Commit.commitHash, + BranchOrder: branchOrder, + }, + }) + branchOrder++ + commitY = len(columns) - 1 + } else { + commitY = col + columns[col] = append(columns[col], BranchPathType{ + Start: minChildX + 1, + End: func() int { + if isFirstCommit { + return index + } else { + return math.MaxInt + } + }(), + EndCommitHash: commit.Commit.commitHash, + BranchOrder: branchOrder, + }) + branchOrder++ + } + } + + commitYs[commit.Commit.commitHash] = commitY + commits[index].X = commitY + } + return commits +} + +func min(values ...int) int { + if len(values) == 0 { + return math.MaxInt + } + minVal := values[0] + for _, v := range values { + if v < minVal { + minVal = v + } + } + return minVal +} + +// print the commit message in a constrained width +func wrapTextOnWidth(text string, width int) (int, []string) { + lines := strings.Split(text, "\n") + totalRows := 0 + wrappedLines := make([]string, 0) + + for _, line := range lines { + words := strings.Fields(line) + currentLine := "" + for _, word := range words { + if len(currentLine)+len(word)+1 > width { + wrappedLines = append(wrappedLines, currentLine) + totalRows++ + currentLine = word + } else { + if currentLine != "" { + currentLine += " " + } + currentLine += word + } + } + if currentLine != "" { + wrappedLines = append(wrappedLines, currentLine) + totalRows++ + } + } + return totalRows, wrappedLines +} + +func printCommitMetadata(graph [][]string, graphWithMessage []string, posY, posX int, commit CommitInfoWithChildren) []string { + firstLine := strings.Join(graph[posY], "") + emptySpace := strings.Repeat(" ", posX-len(graph[posY])) + graphWithMessage[posY] = fmt.Sprintf("%s%s\033[33m commit %s", firstLine, emptySpace, commit.Commit.commitHash) + + secondLine := strings.Join(graph[posY+1], "") + emptySpace = strings.Repeat(" ", posX-len(graph[posY+1])) + graphWithMessage[posY+1] = fmt.Sprintf("%s%s\033[37m Author: %s", secondLine, emptySpace, commit.Commit.commitMeta.Name) + + thirdLine := strings.Join(graph[posY+2], "") + emptySpace = strings.Repeat(" ", posX-len(graph[posY+2])) + graphWithMessage[posY+2] = fmt.Sprintf("%s%s\033[37m Date: %s", thirdLine, emptySpace, commit.Commit.commitMeta.FormatTS()) + + fourthLine := strings.Join(graph[posY+3], "") + graphWithMessage[posY+3] = fourthLine + + return graphWithMessage +} + +// print the commit messages in the graph matrix +// the graph is a 2D slice of strings, each element in the graph matrix is a string of length 1 (either "|", "/", "\", "-", or " ") +// the graphWithMessage is a 1D slice of strings, each element is a line of the graph with the commit message appended +func appendMessage(graph [][]string, commits []CommitInfoWithChildren) []string { + graphWithMessage := make([]string, len(graph)) + + // start from the last commit + last_commit_y := commits[len(commits)-1].Y + graphWithMessage = printCommitMetadata(graph, graphWithMessage, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1]) + + for i, line := range commits[len(commits)-1].formattedMessage { + y := last_commit_y + 4 + i + graphWithMessage[y] = fmt.Sprintf(" \033[37m%s", line) + } + for i := len(commits) - 2; i >= 0; i-- { + startY := commits[i].Y + endY := commits[i+1].Y + startX := commits[i].X + 1 + for j := startY; j < endY; j++ { + if len(graph[j]) > startX { + startX = len(graph[j]) + } + } + + graphWithMessage = printCommitMetadata(graph, graphWithMessage, startY, startX, commits[i]) + + for i, line := range commits[i].formattedMessage { + y := startY + 4 + i + lineContent := strings.Join(graph[y], "") + emptySpace := strings.Repeat(" ", startX-len(graph[y])) + graphWithMessage[y] = fmt.Sprintf("%s%s\033[37m %s", lineContent, emptySpace, line) + } + for j := startY + 4 + len(commits[i].formattedMessage); j < endY; j++ { + graphWithMessage[j] = strings.Join(graph[j], "") + } + } + return graphWithMessage +} + +func expandGraph(commits []CommitInfoWithChildren, width int) []CommitInfoWithChildren { + expandedCommits := make([]CommitInfoWithChildren, 0) + posY := 0 + // Iterate over the commits in the original graph + for i, commit := range commits { + commit.X = commit.X * 2 + commit.Y = posY + rowNum, formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) + maxDistanceFromParent := float64(0) + // make sure there is enough space for the diagonal line connecting to the parent + // this is an approximation, assume that there will be enough space if parent is not the next commit + if i < len(commits)-1 && commits[i+1].Commit.commitHash == commit.Commit.parentHashes[0] { + maxDistanceFromParent = math.Max(math.Abs(float64(commits[i+1].X-commit.X)), maxDistanceFromParent) + } + posY += int(math.Max(float64(5+rowNum), maxDistanceFromParent)) + commit.formattedMessage = formattedMessage + expandedCommits = append(expandedCommits, commit) + } + + return expandedCommits +} + +func trimTrailing(row []string) []string { + lastIndex := len(row) - 1 + + // Find the last non-empty string in the row + for lastIndex >= 0 && strings.TrimSpace(row[lastIndex]) == "" { + lastIndex-- + } + + // Return the trimmed row + return row[:lastIndex+1] +} + +// the height that a commit will take up in the graph +// 4 lines for commit metadata (commit hash, author, date, and an empty line) + number of lines in the commit message +func getHeightOfCommit(commit CommitInfoWithChildren) int { + return 4 + len(commit.formattedMessage) +} + +func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { + commits := formatCommits(commitInfos) + commits = computeColumns(commits) + + commits = expandGraph(commits, 80) + // Determine the width and height of the graph matrix + maxX, maxY := 0, 0 + for _, commit := range commits { + if commit.X > maxX { + maxX = commit.X + } + if commit.Y > maxY { + maxY = commit.Y + } + } + + // Create a 2D slice to represent the graph + heightOfLastCommit := getHeightOfCommit(commits[len(commits)-1]) + graph := make([][]string, maxY+heightOfLastCommit) + for i := range graph { + graph[i] = make([]string, maxX+2) + for j := range graph[i] { + graph[i][j] = " " + } + } + + // Draw the commits and paths + for _, commit := range commits { + x := commit.X + y := commit.Y + graph[y][x] = "\033[37m*" + + for _, parentHash := range commit.Commit.parentHashes { + for _, parent := range commits { + if parent.Commit.commitHash == parentHash { + if parent.X == commit.X { + for yy := commit.Y + 1; yy < parent.Y; yy++ { + if graph[yy][x] == " " { + graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) + } + } + } + if parent.X < commit.X { + for xx := parent.X + 1; xx < commit.X; xx++ { + if graph[parent.Y+parent.X+1-xx][xx] == " " { + graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", branchColors[x/2]) + } + } + for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { + if graph[yy][x] == " " { + graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) + } + } + } + if parent.X > commit.X { + for xx := commit.X + 1; xx < parent.X; xx++ { + if graph[commit.Y+xx-commit.X-1][xx] == " " { + graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", branchColors[parent.X/2]) + } + } + for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { + if graph[yy][parent.X] == " " { + graph[yy][parent.X] = fmt.Sprintf("%s|", branchColors[parent.X/2]) + } + } + } + } + } + } + } + + for i, line := range graph { + line = trimTrailing(line) + graph[i] = line + } + + graphWithMessage := appendMessage(graph, commits) + for _, line := range graphWithMessage { + pager.Writer.Write([]byte(line)) + pager.Writer.Write([]byte("\n")) + } + +} From 26b261f3260681363e394068c73e96027c0ed6b2 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Fri, 26 Jul 2024 14:13:47 -0700 Subject: [PATCH 02/19] refactor, todo: computeColumns --- go/cmd/dolt/commands/log_graph.go | 294 +++++++++++++++--------------- 1 file changed, 146 insertions(+), 148 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 986d1f29eca..ced21ca948f 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -66,7 +66,7 @@ var branchColors = []string{ } // get the children of commits, and initialize the x and y coordinates of the commits -func formatCommits(commits []CommitInfo) []CommitInfoWithChildren { +func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithChildren { childrenMap := make(map[string][]string) for _, commit := range commits { for _, parent := range commit.parentHashes { @@ -74,13 +74,14 @@ func formatCommits(commits []CommitInfo) []CommitInfoWithChildren { } } - var commitsWithChildren []CommitInfoWithChildren + var commitsWithChildren []*CommitInfoWithChildren for y, commit := range commits { - commitsWithChildren = append(commitsWithChildren, CommitInfoWithChildren{ + commitsWithChildren = append(commitsWithChildren, &CommitInfoWithChildren{ Commit: commit, Children: childrenMap[commit.commitHash], X: -1, - Y: y, + // the y coordinate of the commit is initialized to the index of the commit as the commits are sorted + Y: y, }) } @@ -94,18 +95,28 @@ type BranchPathType struct { BranchOrder int } -func computeColumns(commits []CommitInfoWithChildren) []CommitInfoWithChildren { - commitsMap := make(map[string]CommitInfoWithChildren) - for _, commit := range commits { - commitsMap[commit.Commit.commitHash] = commit +func min(values ...int) int { + if len(values) == 0 { + return math.MaxInt } + minVal := values[0] + for _, v := range values { + if v < minVal { + minVal = v + } + } + return minVal +} +// compute the X coordinate of each commit +func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) { + // each column might have multiple branch paths, and the columns slice stores the branch paths of each column columns := [][]BranchPathType{} - commitYs := make(map[string]int) - commitXs := make(map[string]int) + + commitYs := make(map[string]int) for index, commit := range commits { - commitXs[commit.Commit.commitHash] = index + commitYs[commit.Commit.commitHash] = index } updateColumns := func(col, end int, endCommitHash string) { @@ -124,86 +135,76 @@ func computeColumns(commits []CommitInfoWithChildren) []CommitInfoWithChildren { } isLastCommitOnBranch := len(commit.Children) == 0 - isChildOfNonMergeCommit := len(branchChildren) > 0 + isBranchOutCommit := len(branchChildren) > 0 - commitY := -1 + commitX := -1 isFirstCommit := len(commit.Commit.parentHashes) == 0 if isLastCommitOnBranch { columns = append(columns, []BranchPathType{ { - Start: index, - End: func() int { - if isFirstCommit { - return index - } else { - return math.MaxInt - } - }(), + Start: index, + End: index, EndCommitHash: commit.Commit.commitHash, BranchOrder: branchOrder, }, }) branchOrder++ - commitY = len(columns) - 1 - } else if isChildOfNonMergeCommit { - var branchChildrenYs []int + commitX = len(columns) - 1 + } else if isBranchOutCommit { + // in the case of a branch out commit, the x coordinate of the commit is the minimum x coordinate of its children + var branchChildrenXs []int for _, childHash := range branchChildren { - if y, ok := commitYs[childHash]; ok { - branchChildrenYs = append(branchChildrenYs, y) + if x, ok := commitXs[childHash]; ok { + branchChildrenXs = append(branchChildrenXs, x) } } - commitY = min(branchChildrenYs...) + commitX = min(branchChildrenXs...) - updateColumns(commitY, func() int { - if isFirstCommit { - return index - } else { - return math.MaxInt - } - }(), commit.Commit.commitHash) + updateColumns(commitX, index, commit.Commit.commitHash) - for _, childY := range branchChildrenYs { - if childY != commitY { - updateColumns(childY, index-1, commit.Commit.commitHash) + // update the path that branches out from the current commit by setting their end to be one position before the current commit + for _, childX := range branchChildrenXs { + if childX != commitX { + updateColumns(childX, index-1, commit.Commit.commitHash) } } } else { - minChildX := math.MaxInt - maxChildY := -1 + minChildY := math.MaxInt + maxChildX := -1 for _, child := range commit.Children { - childX := commitXs[child] - childY := commitYs[child] + ChildY := commitYs[child] + childY := commitXs[child] - if childX < minChildX { - minChildX = childX + if ChildY < minChildY { + minChildY = ChildY } - if childY > maxChildY { - maxChildY = childY + if childY > maxChildX { + maxChildX = childY } } colFitAtEnd := -1 - for i := maxChildY + 1; i < len(columns); i++ { - if minChildX >= columns[i][len(columns[i])-1].End { - colFitAtEnd = i - (maxChildY + 1) + for i := maxChildX + 1; i < len(columns); i++ { + if minChildY >= columns[i][len(columns[i])-1].End { + colFitAtEnd = i - (maxChildX + 1) break } } col := -1 if colFitAtEnd != -1 { - col = maxChildY + 1 + colFitAtEnd + col = maxChildX + 1 + colFitAtEnd } if col == -1 { columns = append(columns, []BranchPathType{ { - Start: minChildX + 1, + Start: minChildY + 1, End: func() int { if isFirstCommit { return index @@ -216,11 +217,11 @@ func computeColumns(commits []CommitInfoWithChildren) []CommitInfoWithChildren { }, }) branchOrder++ - commitY = len(columns) - 1 + commitX = len(columns) - 1 } else { - commitY = col + commitX = col columns[col] = append(columns[col], BranchPathType{ - Start: minChildX + 1, + Start: minChildY + 1, End: func() int { if isFirstCommit { return index @@ -235,26 +236,12 @@ func computeColumns(commits []CommitInfoWithChildren) []CommitInfoWithChildren { } } - commitYs[commit.Commit.commitHash] = commitY - commits[index].X = commitY + commitXs[commit.Commit.commitHash] = commitX + commits[index].X = commitX } - return commits -} - -func min(values ...int) int { - if len(values) == 0 { - return math.MaxInt - } - minVal := values[0] - for _, v := range values { - if v < minVal { - minVal = v - } - } - return minVal } -// print the commit message in a constrained width +// wrap the commit message in a constrained width to better align the commit message with the graph func wrapTextOnWidth(text string, width int) (int, []string) { lines := strings.Split(text, "\n") totalRows := 0 @@ -283,110 +270,120 @@ func wrapTextOnWidth(text string, width int) (int, []string) { return totalRows, wrappedLines } -func printCommitMetadata(graph [][]string, graphWithMessage []string, posY, posX int, commit CommitInfoWithChildren) []string { - firstLine := strings.Join(graph[posY], "") +func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color string, printRef bool) { + graphLine := strings.Join(graph[posY], "") emptySpace := strings.Repeat(" ", posX-len(graph[posY])) - graphWithMessage[posY] = fmt.Sprintf("%s%s\033[33m commit %s", firstLine, emptySpace, commit.Commit.commitHash) + pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) + if printRef { + printRefs(pager, &commit, "") + } + pager.Writer.Write([]byte("\n")) +} + +func printCommitMetadata(graph [][]string, pager *outputpager.Pager, posY, posX int, commit *CommitInfoWithChildren) { + // print commit hash + printLine(graph, posX, posY, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", true) - secondLine := strings.Join(graph[posY+1], "") - emptySpace = strings.Repeat(" ", posX-len(graph[posY+1])) - graphWithMessage[posY+1] = fmt.Sprintf("%s%s\033[37m Author: %s", secondLine, emptySpace, commit.Commit.commitMeta.Name) + // print author + printLine(graph, posX, posY+1, pager, fmt.Sprintf("Author %s", commit.Commit.commitMeta.Name), commit.Commit, "\033[37m", false) - thirdLine := strings.Join(graph[posY+2], "") - emptySpace = strings.Repeat(" ", posX-len(graph[posY+2])) - graphWithMessage[posY+2] = fmt.Sprintf("%s%s\033[37m Date: %s", thirdLine, emptySpace, commit.Commit.commitMeta.FormatTS()) + // print date + printLine(graph, posX, posY+2, pager, fmt.Sprintf("Date %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", false) - fourthLine := strings.Join(graph[posY+3], "") - graphWithMessage[posY+3] = fourthLine + // print the line between the commit metadata and the commit message + pager.Writer.Write([]byte(strings.Join(graph[posY+3], ""))) + pager.Writer.Write([]byte("\n")) - return graphWithMessage } // print the commit messages in the graph matrix -// the graph is a 2D slice of strings, each element in the graph matrix is a string of length 1 (either "|", "/", "\", "-", or " ") -// the graphWithMessage is a 1D slice of strings, each element is a line of the graph with the commit message appended -func appendMessage(graph [][]string, commits []CommitInfoWithChildren) []string { - graphWithMessage := make([]string, len(graph)) - - // start from the last commit - last_commit_y := commits[len(commits)-1].Y - graphWithMessage = printCommitMetadata(graph, graphWithMessage, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1]) - - for i, line := range commits[len(commits)-1].formattedMessage { - y := last_commit_y + 4 + i - graphWithMessage[y] = fmt.Sprintf(" \033[37m%s", line) - } - for i := len(commits) - 2; i >= 0; i-- { +func appendMessage(graph [][]string, pager *outputpager.Pager, commits []*CommitInfoWithChildren) { + for i := 0; i < len(commits)-1; i++ { startY := commits[i].Y endY := commits[i+1].Y startX := commits[i].X + 1 + + // find the maximum x position of the graph in the range startY to endY + // this is used to align the commit message with the graph without overlapping with the graph for j := startY; j < endY; j++ { if len(graph[j]) > startX { startX = len(graph[j]) } } - graphWithMessage = printCommitMetadata(graph, graphWithMessage, startY, startX, commits[i]) + printCommitMetadata(graph, pager, startY, startX, commits[i]) + // print the graph with commit message for i, line := range commits[i].formattedMessage { y := startY + 4 + i - lineContent := strings.Join(graph[y], "") - emptySpace := strings.Repeat(" ", startX-len(graph[y])) - graphWithMessage[y] = fmt.Sprintf("%s%s\033[37m %s", lineContent, emptySpace, line) + printLine(graph, startX, y, pager, line, commits[i].Commit, "\033[37m", false) } + + // print the remaining lines of the graph of the current commit for j := startY + 4 + len(commits[i].formattedMessage); j < endY; j++ { - graphWithMessage[j] = strings.Join(graph[j], "") + pager.Writer.Write([]byte(strings.Join(graph[j], ""))) + pager.Writer.Write([]byte("\n")) } } - return graphWithMessage + + last_commit_y := commits[len(commits)-1].Y + printCommitMetadata(graph, pager, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1]) + + for _, line := range commits[len(commits)-1].formattedMessage { + pager.Writer.Write([]byte(fmt.Sprintf(" \033[37m%s", line))) + pager.Writer.Write([]byte("\n")) + } } -func expandGraph(commits []CommitInfoWithChildren, width int) []CommitInfoWithChildren { - expandedCommits := make([]CommitInfoWithChildren, 0) +// expand the graph based on the length of the commit message +func expandGraph(commits []*CommitInfoWithChildren, width int) { posY := 0 - // Iterate over the commits in the original graph for i, commit := range commits { + // one empty column between each branch path commit.X = commit.X * 2 commit.Y = posY rowNum, formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) - maxDistanceFromParent := float64(0) + // make sure there is enough space for the diagonal line connecting to the parent // this is an approximation, assume that there will be enough space if parent is not the next commit + maxDistanceFromParent := float64(0) if i < len(commits)-1 && commits[i+1].Commit.commitHash == commit.Commit.parentHashes[0] { maxDistanceFromParent = math.Max(math.Abs(float64(commits[i+1].X-commit.X)), maxDistanceFromParent) } + posY += int(math.Max(float64(5+rowNum), maxDistanceFromParent)) commit.formattedMessage = formattedMessage - expandedCommits = append(expandedCommits, commit) } - - return expandedCommits } func trimTrailing(row []string) []string { lastIndex := len(row) - 1 - // Find the last non-empty string in the row for lastIndex >= 0 && strings.TrimSpace(row[lastIndex]) == "" { lastIndex-- } - // Return the trimmed row return row[:lastIndex+1] } // the height that a commit will take up in the graph // 4 lines for commit metadata (commit hash, author, date, and an empty line) + number of lines in the commit message -func getHeightOfCommit(commit CommitInfoWithChildren) int { +func getHeightOfCommit(commit *CommitInfoWithChildren) int { return 4 + len(commit.formattedMessage) } func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { - commits := formatCommits(commitInfos) - commits = computeColumns(commits) + commits := mapCommitsWithChildrenAndPosition(commitInfos) + commitsMap := make(map[string]*CommitInfoWithChildren) + for _, commit := range commits { + commitsMap[commit.Commit.commitHash] = commit + } + computeColumns(commits, commitsMap) - commits = expandGraph(commits, 80) - // Determine the width and height of the graph matrix + expandGraph(commits, 80) + + // Create a 2D slice to represent the graph + // each element in the graph matrix is a string of length 1 (either "|", "/", "\", "-", or " ") maxX, maxY := 0, 0 for _, commit := range commits { if commit.X > maxX { @@ -396,8 +393,6 @@ func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { maxY = commit.Y } } - - // Create a 2D slice to represent the graph heightOfLastCommit := getHeightOfCommit(commits[len(commits)-1]) graph := make([][]string, maxY+heightOfLastCommit) for i := range graph { @@ -413,38 +408,44 @@ func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { y := commit.Y graph[y][x] = "\033[37m*" + // draw the path between the commit and its parent for _, parentHash := range commit.Commit.parentHashes { - for _, parent := range commits { - if parent.Commit.commitHash == parentHash { - if parent.X == commit.X { - for yy := commit.Y + 1; yy < parent.Y; yy++ { - if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) - } + if parent, ok := commitsMap[parentHash]; ok { + // the parent is on the same branch/column + if parent.X == commit.X { + for yy := commit.Y + 1; yy < parent.Y; yy++ { + if graph[yy][x] == " " { + graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) } } - if parent.X < commit.X { - for xx := parent.X + 1; xx < commit.X; xx++ { - if graph[parent.Y+parent.X+1-xx][xx] == " " { - graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", branchColors[x/2]) - } + } + // from parent to the current commit, a new branch path is created + // the first part is draw a diagonal line from the parent to the column of the current commit + // the second part is extending the path to the current commit along the y-axis + if parent.X < commit.X { + for xx := parent.X + 1; xx < commit.X; xx++ { + if graph[parent.Y+parent.X+1-xx][xx] == " " { + graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", branchColors[x/2]) } - for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { - if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) - } + } + for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { + if graph[yy][x] == " " { + graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) } } - if parent.X > commit.X { - for xx := commit.X + 1; xx < parent.X; xx++ { - if graph[commit.Y+xx-commit.X-1][xx] == " " { - graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", branchColors[parent.X/2]) - } + } + // the current commit is a merge commit + // the first part is draw a diagonal line from the current commit to the column of the parent commit + // the second part is extending the path to the parent commit along the y-axis + if parent.X > commit.X { + for xx := commit.X + 1; xx < parent.X; xx++ { + if graph[commit.Y+xx-commit.X-1][xx] == " " { + graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", branchColors[parent.X/2]) } - for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { - if graph[yy][parent.X] == " " { - graph[yy][parent.X] = fmt.Sprintf("%s|", branchColors[parent.X/2]) - } + } + for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { + if graph[yy][parent.X] == " " { + graph[yy][parent.X] = fmt.Sprintf("%s|", branchColors[parent.X/2]) } } } @@ -452,15 +453,12 @@ func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { } } + // trim the trailing empty space of each line so we can use the length of the line to align the commit message for i, line := range graph { line = trimTrailing(line) graph[i] = line } - graphWithMessage := appendMessage(graph, commits) - for _, line := range graphWithMessage { - pager.Writer.Write([]byte(line)) - pager.Writer.Write([]byte("\n")) - } + appendMessage(graph, pager, commits) } From d643de2f9bcd3ff68f3004048b0433906eea72dc Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Fri, 26 Jul 2024 14:43:23 -0700 Subject: [PATCH 03/19] refactor and comments --- go/cmd/dolt/commands/log_graph.go | 177 ++++++++++++------------------ 1 file changed, 69 insertions(+), 108 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index ced21ca948f..5e513df0cac 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -65,6 +65,11 @@ var branchColors = []string{ "\033[7m", // Reverse } +type BranchPathType struct { + Start int + End int +} + // get the children of commits, and initialize the x and y coordinates of the commits func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithChildren { childrenMap := make(map[string][]string) @@ -88,11 +93,33 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh return commitsWithChildren } -type BranchPathType struct { - Start int - End int - EndCommitHash string - BranchOrder int +// wrap the commit message in a constrained width to better align the commit message with the graph +func wrapTextOnWidth(text string, width int) (int, []string) { + lines := strings.Split(text, "\n") + totalRows := 0 + wrappedLines := make([]string, 0) + + for _, line := range lines { + words := strings.Fields(line) + currentLine := "" + for _, word := range words { + if len(currentLine)+len(word)+1 > width { + wrappedLines = append(wrappedLines, currentLine) + totalRows++ + currentLine = word + } else { + if currentLine != "" { + currentLine += " " + } + currentLine += word + } + } + if currentLine != "" { + wrappedLines = append(wrappedLines, currentLine) + totalRows++ + } + } + return totalRows, wrappedLines } func min(values ...int) int { @@ -112,20 +139,11 @@ func min(values ...int) int { func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) { // each column might have multiple branch paths, and the columns slice stores the branch paths of each column columns := [][]BranchPathType{} - commitXs := make(map[string]int) - - commitYs := make(map[string]int) - for index, commit := range commits { - commitYs[commit.Commit.commitHash] = index - } - updateColumns := func(col, end int, endCommitHash string) { + updateColumns := func(col, end int) { columns[col][len(columns[col])-1].End = end - columns[col][len(columns[col])-1].EndCommitHash = endCommitHash } - branchOrder := 0 - for index, commit := range commits { var branchChildren []string for _, child := range commit.Children { @@ -139,137 +157,80 @@ func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*Co commitX := -1 - isFirstCommit := len(commit.Commit.parentHashes) == 0 - if isLastCommitOnBranch { columns = append(columns, []BranchPathType{ { - Start: index, - End: index, - EndCommitHash: commit.Commit.commitHash, - BranchOrder: branchOrder, + Start: index, + End: index, }, }) - branchOrder++ commitX = len(columns) - 1 } else if isBranchOutCommit { // in the case of a branch out commit, the x coordinate of the commit is the minimum x coordinate of its children var branchChildrenXs []int for _, childHash := range branchChildren { - if x, ok := commitXs[childHash]; ok { - branchChildrenXs = append(branchChildrenXs, x) + if child, ok := commitsMap[childHash]; ok { + branchChildrenXs = append(branchChildrenXs, child.X) } } commitX = min(branchChildrenXs...) - updateColumns(commitX, index, commit.Commit.commitHash) + updateColumns(commitX, index) // update the path that branches out from the current commit by setting their end to be one position before the current commit for _, childX := range branchChildrenXs { if childX != commitX { - updateColumns(childX, index-1, commit.Commit.commitHash) + updateColumns(childX, index-1) } } } else { + // Find an available column so the commit can connect to its children (merge commit) without overlapping with existing branches on columns + // Otherwise, put the commit in a new column + + // minChildY is the highest pos of child commit, maxChildX is the right most pos of child commit minChildY := math.MaxInt maxChildX := -1 - for _, child := range commit.Children { - ChildY := commitYs[child] - childY := commitXs[child] - - if ChildY < minChildY { - minChildY = ChildY + if commitsMap[child].Y < minChildY { + minChildY = commitsMap[child].Y } - - if childY > maxChildX { - maxChildX = childY + if commitsMap[child].X > maxChildX { + maxChildX = commitsMap[child].X } } - colFitAtEnd := -1 + // find the first column that has no branches that overlap with the current commit + col := -1 for i := maxChildX + 1; i < len(columns); i++ { if minChildY >= columns[i][len(columns[i])-1].End { - colFitAtEnd = i - (maxChildX + 1) + col = i break } } - col := -1 - if colFitAtEnd != -1 { - col = maxChildX + 1 + colFitAtEnd - } - + // if no column is found, put the commit in a new column if col == -1 { columns = append(columns, []BranchPathType{ { Start: minChildY + 1, - End: func() int { - if isFirstCommit { - return index - } else { - return math.MaxInt - } - }(), - EndCommitHash: commit.Commit.commitHash, - BranchOrder: branchOrder, + End: index, }, }) - branchOrder++ commitX = len(columns) - 1 } else { commitX = col columns[col] = append(columns[col], BranchPathType{ Start: minChildY + 1, - End: func() int { - if isFirstCommit { - return index - } else { - return math.MaxInt - } - }(), - EndCommitHash: commit.Commit.commitHash, - BranchOrder: branchOrder, + End: index, }) - branchOrder++ } } - commitXs[commit.Commit.commitHash] = commitX commits[index].X = commitX } } -// wrap the commit message in a constrained width to better align the commit message with the graph -func wrapTextOnWidth(text string, width int) (int, []string) { - lines := strings.Split(text, "\n") - totalRows := 0 - wrappedLines := make([]string, 0) - - for _, line := range lines { - words := strings.Fields(line) - currentLine := "" - for _, word := range words { - if len(currentLine)+len(word)+1 > width { - wrappedLines = append(wrappedLines, currentLine) - totalRows++ - currentLine = word - } else { - if currentLine != "" { - currentLine += " " - } - currentLine += word - } - } - if currentLine != "" { - wrappedLines = append(wrappedLines, currentLine) - totalRows++ - } - } - return totalRows, wrappedLines -} - func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color string, printRef bool) { graphLine := strings.Join(graph[posY], "") emptySpace := strings.Repeat(" ", posX-len(graph[posY])) @@ -296,6 +257,22 @@ func printCommitMetadata(graph [][]string, pager *outputpager.Pager, posY, posX } +func trimTrailing(row []string) []string { + lastIndex := len(row) - 1 + + for lastIndex >= 0 && strings.TrimSpace(row[lastIndex]) == "" { + lastIndex-- + } + + return row[:lastIndex+1] +} + +// the height that a commit will take up in the graph +// 4 lines for commit metadata (commit hash, author, date, and an empty line) + number of lines in the commit message +func getHeightOfCommit(commit *CommitInfoWithChildren) int { + return 4 + len(commit.formattedMessage) +} + // print the commit messages in the graph matrix func appendMessage(graph [][]string, pager *outputpager.Pager, commits []*CommitInfoWithChildren) { for i := 0; i < len(commits)-1; i++ { @@ -356,22 +333,6 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { } } -func trimTrailing(row []string) []string { - lastIndex := len(row) - 1 - - for lastIndex >= 0 && strings.TrimSpace(row[lastIndex]) == "" { - lastIndex-- - } - - return row[:lastIndex+1] -} - -// the height that a commit will take up in the graph -// 4 lines for commit metadata (commit hash, author, date, and an empty line) + number of lines in the commit message -func getHeightOfCommit(commit *CommitInfoWithChildren) int { - return 4 + len(commit.formattedMessage) -} - func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { commits := mapCommitsWithChildrenAndPosition(commitInfos) commitsMap := make(map[string]*CommitInfoWithChildren) From 19440b714b8cf45f817f1d318dc42db2684b3a1b Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Fri, 26 Jul 2024 15:28:31 -0700 Subject: [PATCH 04/19] copyright, decoration --- go/cmd/dolt/commands/log.go | 2 +- go/cmd/dolt/commands/log_graph.go | 58 +++++++++++++++++-------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/go/cmd/dolt/commands/log.go b/go/cmd/dolt/commands/log.go index cac7bf867b4..5893b962695 100644 --- a/go/cmd/dolt/commands/log.go +++ b/go/cmd/dolt/commands/log.go @@ -360,7 +360,7 @@ func logToStdOut(apr *argparser.ArgParseResults, commits []CommitInfo, sqlCtx *s if apr.Contains(cli.OneLineFlag) { err = logCompact(pager, apr, commits, sqlCtx, queryist) } else if apr.Contains(cli.GraphFlag) { - logGraph(pager, commits) + logGraph(pager, apr, commits) } else { err = logDefault(pager, apr, commits, sqlCtx, queryist) } diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 5e513df0cac..577b834556f 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -1,3 +1,17 @@ +// Copyright 2019 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package commands import ( @@ -5,22 +19,11 @@ import ( "math" "strings" + "github.com/dolthub/dolt/go/cmd/dolt/cli" + "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/store/util/outputpager" ) -// Define the structure of the commit data -type Commit struct { - Hash string - Parents []string - Children []string - Committer string - Message string - CommitDate string - CommitColor string - X int - Y int -} - type CommitInfoWithChildren struct { Commit CommitInfo Children []string @@ -231,25 +234,25 @@ func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*Co } } -func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color string, printRef bool) { +func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color, decoration string) { graphLine := strings.Join(graph[posY], "") emptySpace := strings.Repeat(" ", posX-len(graph[posY])) pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) - if printRef { - printRefs(pager, &commit, "") + if decoration != "no" { + printRefs(pager, &commit, decoration) } pager.Writer.Write([]byte("\n")) } -func printCommitMetadata(graph [][]string, pager *outputpager.Pager, posY, posX int, commit *CommitInfoWithChildren) { +func printCommitMetadata(graph [][]string, pager *outputpager.Pager, posY, posX int, commit *CommitInfoWithChildren, decoration string) { // print commit hash - printLine(graph, posX, posY, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", true) + printLine(graph, posX, posY, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", decoration) // print author - printLine(graph, posX, posY+1, pager, fmt.Sprintf("Author %s", commit.Commit.commitMeta.Name), commit.Commit, "\033[37m", false) + printLine(graph, posX, posY+1, pager, fmt.Sprintf("Author %s", commit.Commit.commitMeta.Name), commit.Commit, "\033[37m", "no") // print date - printLine(graph, posX, posY+2, pager, fmt.Sprintf("Date %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", false) + printLine(graph, posX, posY+2, pager, fmt.Sprintf("Date %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", "no") // print the line between the commit metadata and the commit message pager.Writer.Write([]byte(strings.Join(graph[posY+3], ""))) @@ -274,7 +277,9 @@ func getHeightOfCommit(commit *CommitInfoWithChildren) int { } // print the commit messages in the graph matrix -func appendMessage(graph [][]string, pager *outputpager.Pager, commits []*CommitInfoWithChildren) { +func appendMessage(graph [][]string, pager *outputpager.Pager, apr *argparser.ArgParseResults, commits []*CommitInfoWithChildren) { + decoration := apr.GetValueOrDefault(cli.DecorateFlag, "auto") + for i := 0; i < len(commits)-1; i++ { startY := commits[i].Y endY := commits[i+1].Y @@ -288,12 +293,12 @@ func appendMessage(graph [][]string, pager *outputpager.Pager, commits []*Commit } } - printCommitMetadata(graph, pager, startY, startX, commits[i]) + printCommitMetadata(graph, pager, startY, startX, commits[i], decoration) // print the graph with commit message for i, line := range commits[i].formattedMessage { y := startY + 4 + i - printLine(graph, startX, y, pager, line, commits[i].Commit, "\033[37m", false) + printLine(graph, startX, y, pager, line, commits[i].Commit, "\033[37m", "no") } // print the remaining lines of the graph of the current commit @@ -304,7 +309,7 @@ func appendMessage(graph [][]string, pager *outputpager.Pager, commits []*Commit } last_commit_y := commits[len(commits)-1].Y - printCommitMetadata(graph, pager, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1]) + printCommitMetadata(graph, pager, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1], decoration) for _, line := range commits[len(commits)-1].formattedMessage { pager.Writer.Write([]byte(fmt.Sprintf(" \033[37m%s", line))) @@ -333,7 +338,7 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { } } -func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { +func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitInfos []CommitInfo) { commits := mapCommitsWithChildrenAndPosition(commitInfos) commitsMap := make(map[string]*CommitInfoWithChildren) for _, commit := range commits { @@ -420,6 +425,5 @@ func logGraph(pager *outputpager.Pager, commitInfos []CommitInfo) { graph[i] = line } - appendMessage(graph, pager, commits) - + appendMessage(graph, pager, apr, commits) } From 6208473df49493e926ac2f3641a1437339785e66 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Mon, 29 Jul 2024 12:08:11 -0700 Subject: [PATCH 05/19] clean up --- go/cmd/dolt/commands/log_graph.go | 41 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 577b834556f..01f4c5bb389 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -139,9 +139,12 @@ func min(values ...int) int { } // compute the X coordinate of each commit -func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) { +func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) ([]*CommitInfoWithChildren, map[string]*CommitInfoWithChildren) { // each column might have multiple branch paths, and the columns slice stores the branch paths of each column columns := [][]BranchPathType{} + xPositions := make(map[string]int) + newCommitMap := make(map[string]*CommitInfoWithChildren) + commitsWithXPos := make([]*CommitInfoWithChildren, len(commits)) updateColumns := func(col, end int) { columns[col][len(columns[col])-1].End = end @@ -172,8 +175,8 @@ func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*Co // in the case of a branch out commit, the x coordinate of the commit is the minimum x coordinate of its children var branchChildrenXs []int for _, childHash := range branchChildren { - if child, ok := commitsMap[childHash]; ok { - branchChildrenXs = append(branchChildrenXs, child.X) + if childX, ok := xPositions[childHash]; ok { + branchChildrenXs = append(branchChildrenXs, childX) } } @@ -198,8 +201,8 @@ func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*Co if commitsMap[child].Y < minChildY { minChildY = commitsMap[child].Y } - if commitsMap[child].X > maxChildX { - maxChildX = commitsMap[child].X + if xPositions[child] > maxChildX { + maxChildX = xPositions[child] } } @@ -229,9 +232,16 @@ func computeColumns(commits []*CommitInfoWithChildren, commitsMap map[string]*Co }) } } - - commits[index].X = commitX + xPositions[commit.Commit.commitHash] = commitX + commitsWithXPos[index] = &CommitInfoWithChildren{ + Commit: commit.Commit, + Children: commit.Children, + X: commitX, + Y: commit.Y, + } + newCommitMap[commit.Commit.commitHash] = commitsWithXPos[index] } + return commitsWithXPos, newCommitMap } func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color, decoration string) { @@ -325,6 +335,7 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { commit.X = commit.X * 2 commit.Y = posY rowNum, formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) + commit.formattedMessage = formattedMessage // make sure there is enough space for the diagonal line connecting to the parent // this is an approximation, assume that there will be enough space if parent is not the next commit @@ -334,7 +345,6 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { } posY += int(math.Max(float64(5+rowNum), maxDistanceFromParent)) - commit.formattedMessage = formattedMessage } } @@ -344,7 +354,7 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn for _, commit := range commits { commitsMap[commit.Commit.commitHash] = commit } - computeColumns(commits, commitsMap) + commits, commitsMap = computeColumnEnds(commits, commitsMap) expandGraph(commits, 80) @@ -379,9 +389,10 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn if parent, ok := commitsMap[parentHash]; ok { // the parent is on the same branch/column if parent.X == commit.X { + color := branchColors[x/2%len(branchColors)] for yy := commit.Y + 1; yy < parent.Y; yy++ { if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) + graph[yy][x] = fmt.Sprintf("%s|", color) } } } @@ -389,14 +400,15 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn // the first part is draw a diagonal line from the parent to the column of the current commit // the second part is extending the path to the current commit along the y-axis if parent.X < commit.X { + color := branchColors[x/2%len(branchColors)] for xx := parent.X + 1; xx < commit.X; xx++ { if graph[parent.Y+parent.X+1-xx][xx] == " " { - graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", branchColors[x/2]) + graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", color) } } for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", branchColors[x/2]) + graph[yy][x] = fmt.Sprintf("%s|", color) } } } @@ -404,14 +416,15 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn // the first part is draw a diagonal line from the current commit to the column of the parent commit // the second part is extending the path to the parent commit along the y-axis if parent.X > commit.X { + color := branchColors[parent.X/2%len(branchColors)] for xx := commit.X + 1; xx < parent.X; xx++ { if graph[commit.Y+xx-commit.X-1][xx] == " " { - graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", branchColors[parent.X/2]) + graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", color) } } for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { if graph[yy][parent.X] == " " { - graph[yy][parent.X] = fmt.Sprintf("%s|", branchColors[parent.X/2]) + graph[yy][parent.X] = fmt.Sprintf("%s|", color) } } } From 5afa7b25513cad4c41b1497a4cce3fcec741ed31 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Mon, 29 Jul 2024 15:49:07 -0700 Subject: [PATCH 06/19] add merge info, intendent --- go/cmd/dolt/commands/log_graph.go | 44 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 01f4c5bb389..c91ae05607e 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -97,7 +97,7 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh } // wrap the commit message in a constrained width to better align the commit message with the graph -func wrapTextOnWidth(text string, width int) (int, []string) { +func wrapTextOnWidth(text string, width int) []string { lines := strings.Split(text, "\n") totalRows := 0 wrappedLines := make([]string, 0) @@ -122,7 +122,7 @@ func wrapTextOnWidth(text string, width int) (int, []string) { totalRows++ } } - return totalRows, wrappedLines + return wrappedLines } func min(values ...int) int { @@ -255,17 +255,21 @@ func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line } func printCommitMetadata(graph [][]string, pager *outputpager.Pager, posY, posX int, commit *CommitInfoWithChildren, decoration string) { - // print commit hash printLine(graph, posX, posY, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", decoration) - // print author - printLine(graph, posX, posY+1, pager, fmt.Sprintf("Author %s", commit.Commit.commitMeta.Name), commit.Commit, "\033[37m", "no") + printMergeInfo := 0 + if len(commit.Commit.parentHashes) > 1 { + printMergeInfo = 1 + } + if printMergeInfo == 1 { + printLine(graph, posX, posY+1, pager, fmt.Sprintf("Merge: %s", strings.Join(commit.Commit.parentHashes, " ")), commit.Commit, "\033[37m", "no") + } - // print date - printLine(graph, posX, posY+2, pager, fmt.Sprintf("Date %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", "no") + printLine(graph, posX, posY+1+printMergeInfo, pager, fmt.Sprintf("Author: %s <%s>", commit.Commit.commitMeta.Name, commit.Commit.commitMeta.Email), commit.Commit, "\033[37m", "no") - // print the line between the commit metadata and the commit message - pager.Writer.Write([]byte(strings.Join(graph[posY+3], ""))) + printLine(graph, posX, posY+2+printMergeInfo, pager, fmt.Sprintf("Date: %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", "no") + + pager.Writer.Write([]byte(strings.Join(graph[posY+3+printMergeInfo], ""))) pager.Writer.Write([]byte("\n")) } @@ -282,8 +286,13 @@ func trimTrailing(row []string) []string { // the height that a commit will take up in the graph // 4 lines for commit metadata (commit hash, author, date, and an empty line) + number of lines in the commit message +// if the commit is a merge commit, add one more line for the "Merge:" line func getHeightOfCommit(commit *CommitInfoWithChildren) int { - return 4 + len(commit.formattedMessage) + height := 4 + len(commit.formattedMessage) + if len(commit.Commit.parentHashes) > 1 { + height = height + 1 + } + return height } // print the commit messages in the graph matrix @@ -305,14 +314,16 @@ func appendMessage(graph [][]string, pager *outputpager.Pager, apr *argparser.Ar printCommitMetadata(graph, pager, startY, startX, commits[i], decoration) + commitInfoHeight := getHeightOfCommit(commits[i]) + // print the graph with commit message for i, line := range commits[i].formattedMessage { - y := startY + 4 + i - printLine(graph, startX, y, pager, line, commits[i].Commit, "\033[37m", "no") + y := startY + commitInfoHeight - len(commits[i].formattedMessage) + i + printLine(graph, startX, y, pager, fmt.Sprintf("\t%s", line), commits[i].Commit, "\033[37m", "no") } // print the remaining lines of the graph of the current commit - for j := startY + 4 + len(commits[i].formattedMessage); j < endY; j++ { + for j := startY + commitInfoHeight; j < endY; j++ { pager.Writer.Write([]byte(strings.Join(graph[j], ""))) pager.Writer.Write([]byte("\n")) } @@ -334,7 +345,7 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { // one empty column between each branch path commit.X = commit.X * 2 commit.Y = posY - rowNum, formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) + formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) commit.formattedMessage = formattedMessage // make sure there is enough space for the diagonal line connecting to the parent @@ -344,7 +355,8 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { maxDistanceFromParent = math.Max(math.Abs(float64(commits[i+1].X-commit.X)), maxDistanceFromParent) } - posY += int(math.Max(float64(5+rowNum), maxDistanceFromParent)) + commitInfoHeight := getHeightOfCommit(commit) + posY += int(math.Max(float64(commitInfoHeight+1), maxDistanceFromParent)) } } @@ -354,8 +366,8 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn for _, commit := range commits { commitsMap[commit.Commit.commitHash] = commit } - commits, commitsMap = computeColumnEnds(commits, commitsMap) + commits, commitsMap = computeColumnEnds(commits, commitsMap) expandGraph(commits, 80) // Create a 2D slice to represent the graph From d6475f12e859ee2476bdee0571637b940e41952b Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Tue, 30 Jul 2024 16:50:24 -0700 Subject: [PATCH 07/19] bats --- go/cmd/dolt/commands/log_graph.go | 42 +++------------ integration-tests/bats/log.bats | 86 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 35 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index c91ae05607e..c3c0cd46f81 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -33,39 +33,12 @@ type CommitInfoWithChildren struct { } var branchColors = []string{ - "\033[31m", // Red - "\033[32m", // Green - "\033[34m", // Blue - "\033[35m", // Magenta - "\033[36m", // Cyan - "\033[39m", // Default - "\033[1;31m", // Bold Red - "\033[1;32m", // Bold Green - "\033[1;33m", // Bold Yellow - "\033[1;34m", // Bold Blue - "\033[1;35m", // Bold Magenta - "\033[1;36m", // Bold Cyan - "\033[1;37m", // Bold White - "\033[1;39m", // Bold Default - "\033[2;31m", // Faint Red - "\033[2;32m", // Faint Green - "\033[2;33m", // Faint Yellow - "\033[2;34m", // Faint Blue - "\033[2;35m", // Faint Magenta - "\033[2;36m", // Faint Cyan - "\033[2;37m", // Faint White - "\033[2;39m", // Faint Default - "\033[41m", // Background Red - "\033[42m", // Background Green - "\033[43m", // Background Yellow - "\033[44m", // Background Blue - "\033[45m", // Background Magenta - "\033[46m", // Background Cyan - "\033[47m", // Background White - "\033[49m", // Background Default - "\033[2m", // Faint - "\033[2;3m", // Faint Italic - "\033[7m", // Reverse + "\033[31m", // Red + "\033[32m", // Green + "\033[34m", // Blue + "\033[35m", // Magenta + "\033[36m", // Cyan + "\033[39m", // Default } type BranchPathType struct { @@ -331,9 +304,8 @@ func appendMessage(graph [][]string, pager *outputpager.Pager, apr *argparser.Ar last_commit_y := commits[len(commits)-1].Y printCommitMetadata(graph, pager, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1], decoration) - for _, line := range commits[len(commits)-1].formattedMessage { - pager.Writer.Write([]byte(fmt.Sprintf(" \033[37m%s", line))) + pager.Writer.Write([]byte(fmt.Sprintf("\t\033[37m%s", line))) pager.Writer.Write([]byte("\n")) } } diff --git a/integration-tests/bats/log.bats b/integration-tests/bats/log.bats index 2574cdd8e82..be8efa2415c 100755 --- a/integration-tests/bats/log.bats +++ b/integration-tests/bats/log.bats @@ -826,3 +826,89 @@ teardown() { [[ "$output" =~ "merge main" ]] || false [ "${#lines[@]}" -eq 5 ] } + +@test "log --graph: basic graph log" { + dolt sql -q "create table testtable (pk int PRIMARY KEY)" + dolt add . + dolt commit -m "commit 1" + + # Run the dolt log --graph command + run dolt log --graph + [ "$status" -eq 0 ] + + # Check the output with patterns + echo "${lines[0]}" + echo "${lines[1]}" + echo "${lines[2]}" + echo "${lines[3]}" + echo "${lines[4]}" + echo "${lines[5]}" + echo "${lines[6]}" + echo "${lines[7]}" + echo "${lines[8]}" + echo "${lines[9]}" + echo "${lines[10]}" + + [[ "${lines[0]}" =~ \* ]] || false + [[ $(echo "${lines[0]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* commit " ]] || false # * commit xxx + [[ $(echo "${lines[1]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Author:" ]] || false # | Author: + [[ $(echo "${lines[2]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Date:" ]] || false # | Date: + [[ $(echo "${lines[3]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | + [[ $(echo "${lines[4]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 1" ]] || false # | commit 1 + [[ $(echo "${lines[5]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | + [[ $(echo "${lines[6]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* commit " ]] || false # * commit xxx + [[ $(echo "${lines[7]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Author:" ]] || false # Author: + [[ $(echo "${lines[8]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Date:" ]] || false # Date: + [[ $(echo "${lines[9]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Initialize data repository" ]] || false # Initialize data repository +} + + +@test "log --graph: graph with merges" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi + + dolt sql -q "create table testtable (pk int PRIMARY KEY)" + dolt add . + dolt commit -m "commit 1 MAIN" + dolt checkout -b branchA + dolt commit --allow-empty -m "commit 1 BRANCHA" + dolt checkout main + dolt commit --allow-empty -m "commit 2 MAIN" + dolt merge branchA -m "Merge branchA into main" + + run dolt log --graph + [ "$status" -eq 0 ] + + # Check the output with patterns + [[ "${lines[0]}" =~ \* ]] || false + [[ $(echo "${lines[0]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "*\ commit " ]] || false # *\ commit xxx + [[ $(echo "${lines[1]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Merge:" ]] || false # | | Merge: + [[ $(echo "${lines[2]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Author:" ]] || false # | | Author: + [[ $(echo "${lines[3]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Date:" ]] || false # | | Date: + [[ $(echo "${lines[4]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | + [[ $(echo "${lines[5]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Merge branchA into main" ]] || false # | | Merge branchA into main + [[ $(echo "${lines[6]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | + [[ $(echo "${lines[7]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* | commit " ]] || false # * | commit xxx + [[ $(echo "${lines[8]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Author:" ]] || false # | | Author: + [[ $(echo "${lines[9]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Date:" ]] || false # | | Date: + [[ $(echo "${lines[10]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | + [[ $(echo "${lines[11]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 2 MAIN" ]] || false # | | commit 2 MAIN + [[ $(echo "${lines[12]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | + [[ $(echo "${lines[13]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| * commit " ]] || false # | * commit xxx + [[ $(echo "${lines[14]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Author:" ]] || false # | | Author: + [[ $(echo "${lines[15]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Date:" ]] || false # | | Date: + [[ $(echo "${lines[16]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | + [[ $(echo "${lines[17]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 1 BRANCHA" ]] || false # | | commit 1 BRANCHA + [[ $(echo "${lines[18]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | + [[ $(echo "${lines[19]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "*/ commit" ]] || false # */ commit xxx + [[ $(echo "${lines[20]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Author:" ]] || false # | Author: + [[ $(echo "${lines[21]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Date:" ]] || false # | Date: + [[ $(echo "${lines[22]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | + [[ $(echo "${lines[23]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 1 MAIN" ]] || false # | commit 1 MAIN + [[ $(echo "${lines[24]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | + [[ $(echo "${lines[25]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* commit" ]] || false # * commit + [[ $(echo "${lines[26]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Author:" ]] || false # Author: + [[ $(echo "${lines[27]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Date:" ]] || false # Date: + [[ $(echo "${lines[28]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Initialize data repository" ]] || false # Initialize data repository +} \ No newline at end of file From b0fcbd40cc5fa593387b8e90232bd9fdac1c9d5c Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Wed, 31 Jul 2024 09:55:38 -0700 Subject: [PATCH 08/19] remove color codes --- go/cmd/dolt/commands/log_graph.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index c3c0cd46f81..0255342624d 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -17,6 +17,7 @@ package commands import ( "fmt" "math" + "regexp" "strings" "github.com/dolthub/dolt/go/cmd/dolt/cli" @@ -69,6 +70,12 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh return commitsWithChildren } +// RemoveColorCodes removes ANSI color codes from a string +func RemoveColorCodes(input string) string { + re := regexp.MustCompile(`\x1b\[[0-9;]*m`) + return re.ReplaceAllString(input, "") +} + // wrap the commit message in a constrained width to better align the commit message with the graph func wrapTextOnWidth(text string, width int) []string { lines := strings.Split(text, "\n") @@ -219,8 +226,16 @@ func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string] func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color, decoration string) { graphLine := strings.Join(graph[posY], "") - emptySpace := strings.Repeat(" ", posX-len(graph[posY])) - pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) + if len(graph[posY]) > posX { + fmt.Println("Error: graph line is shorter than the x position of the commit") + fmt.Println("graph line:", graph[posY], "end") + fmt.Println("x position: ", posX) + fmt.Println("length of graph line: ", len(graph[posY])) + } else { + emptySpace := strings.Repeat(" ", posX-len(graph[posY])) + pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) + + } if decoration != "no" { printRefs(pager, &commit, decoration) } From abacfb24846c998651419cc12c42cdaff398294a Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Wed, 31 Jul 2024 11:46:03 -0700 Subject: [PATCH 09/19] wrong index, bats --- go/cmd/dolt/commands/log_graph.go | 23 ++----- integration-tests/bats/log.bats | 107 ++++++++++++++---------------- 2 files changed, 55 insertions(+), 75 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 0255342624d..4cac9315d19 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -17,7 +17,6 @@ package commands import ( "fmt" "math" - "regexp" "strings" "github.com/dolthub/dolt/go/cmd/dolt/cli" @@ -70,12 +69,6 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh return commitsWithChildren } -// RemoveColorCodes removes ANSI color codes from a string -func RemoveColorCodes(input string) string { - re := regexp.MustCompile(`\x1b\[[0-9;]*m`) - return re.ReplaceAllString(input, "") -} - // wrap the commit message in a constrained width to better align the commit message with the graph func wrapTextOnWidth(text string, width int) []string { lines := strings.Split(text, "\n") @@ -226,16 +219,10 @@ func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string] func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color, decoration string) { graphLine := strings.Join(graph[posY], "") - if len(graph[posY]) > posX { - fmt.Println("Error: graph line is shorter than the x position of the commit") - fmt.Println("graph line:", graph[posY], "end") - fmt.Println("x position: ", posX) - fmt.Println("length of graph line: ", len(graph[posY])) - } else { - emptySpace := strings.Repeat(" ", posX-len(graph[posY])) - pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) - } + emptySpace := strings.Repeat(" ", posX-len(graph[posY])) + pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) + if decoration != "no" { printRefs(pager, &commit, decoration) } @@ -305,8 +292,8 @@ func appendMessage(graph [][]string, pager *outputpager.Pager, apr *argparser.Ar commitInfoHeight := getHeightOfCommit(commits[i]) // print the graph with commit message - for i, line := range commits[i].formattedMessage { - y := startY + commitInfoHeight - len(commits[i].formattedMessage) + i + for j, line := range commits[i].formattedMessage { + y := startY + commitInfoHeight - len(commits[i].formattedMessage) + j printLine(graph, startX, y, pager, fmt.Sprintf("\t%s", line), commits[i].Commit, "\033[37m", "no") } diff --git a/integration-tests/bats/log.bats b/integration-tests/bats/log.bats index be8efa2415c..bfe954d096a 100755 --- a/integration-tests/bats/log.bats +++ b/integration-tests/bats/log.bats @@ -761,7 +761,7 @@ teardown() { dolt commit -Am "insert into test" run dolt log --stat head -n=1 [ "$status" -eq 0 ] - out=$(echo "$output" | sed -E 's/\x1b\[[0-9;]*m//g') # remove special characters for color + out=$(remove_color_codes "$output") # remove special characters for color [[ "$out" =~ " test | 1 +" ]] || false [[ "$out" =~ " 1 tables changed, 1 rows added(+), 0 rows modified(*), 0 rows deleted(-)" ]] || false @@ -769,7 +769,7 @@ teardown() { dolt commit -Am "update test" run dolt log --stat head -n=1 [ "$status" -eq 0 ] - out=$(echo "$output" | sed -E 's/\x1b\[[0-9;]*m//g') # remove special characters for color + out=$(remove_color_codes "$output") # remove special characters for color [[ "$out" =~ " test | 1 *" ]] || false [[ "$out" =~ " 1 tables changed, 0 rows added(+), 1 rows modified(*), 0 rows deleted(-)" ]] || false @@ -777,7 +777,7 @@ teardown() { dolt commit -Am "delete from test" run dolt log --stat head -n=1 [ "$status" -eq 0 ] - out=$(echo "$output" | sed -E 's/\x1b\[[0-9;]*m//g') # remove special characters for color + out=$(remove_color_codes "$output") # remove special characters for color [[ "$out" =~ " test | 1 -" ]] || false [[ "$out" =~ " 1 tables changed, 0 rows added(+), 0 rows modified(*), 1 rows deleted(-)" ]] || false @@ -797,11 +797,11 @@ teardown() { run dolt log --stat --oneline [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 6 ] - l1=$(echo "${lines[1]}" | sed -E 's/\x1b\[[0-9;]*m//g') # remove special characters for color + l1=$(remove_color_codes "${lines[1]}") # remove special characters for color [[ "$l1" =~ " test | 1 +" ]] || false - l2=$(echo "${lines[2]}" | sed -E 's/\x1b\[[0-9;]*m//g') # remove special characters for color + l2=$(remove_color_codes "${lines[2]}") # remove special characters for color [[ "$output" =~ " 1 tables changed, 1 rows added(+), 0 rows modified(*), 0 rows deleted(-)" ]] || false - l3=$(echo "${lines[4]}" | sed -E 's/\x1b\[[0-9;]*m//g') # remove special characters for color + l3=$(remove_color_codes "${lines[4]}") # remove special characters for color [[ "$l3" =~ " test added" ]] || false } @@ -827,6 +827,11 @@ teardown() { [ "${#lines[@]}" -eq 5 ] } +remove_color_codes() { + echo "$1" | sed -E 's/\x1b\[[0-9;]*m//g' +} + + @test "log --graph: basic graph log" { dolt sql -q "create table testtable (pk int PRIMARY KEY)" dolt add . @@ -837,29 +842,17 @@ teardown() { [ "$status" -eq 0 ] # Check the output with patterns - echo "${lines[0]}" - echo "${lines[1]}" - echo "${lines[2]}" - echo "${lines[3]}" - echo "${lines[4]}" - echo "${lines[5]}" - echo "${lines[6]}" - echo "${lines[7]}" - echo "${lines[8]}" - echo "${lines[9]}" - echo "${lines[10]}" - [[ "${lines[0]}" =~ \* ]] || false - [[ $(echo "${lines[0]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* commit " ]] || false # * commit xxx - [[ $(echo "${lines[1]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Author:" ]] || false # | Author: - [[ $(echo "${lines[2]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Date:" ]] || false # | Date: - [[ $(echo "${lines[3]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | - [[ $(echo "${lines[4]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 1" ]] || false # | commit 1 - [[ $(echo "${lines[5]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | - [[ $(echo "${lines[6]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* commit " ]] || false # * commit xxx - [[ $(echo "${lines[7]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Author:" ]] || false # Author: - [[ $(echo "${lines[8]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Date:" ]] || false # Date: - [[ $(echo "${lines[9]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Initialize data repository" ]] || false # Initialize data repository + [[ $(remove_color_codes "${lines[0]}") =~ "* commit " ]] || false # * commit xxx + [[ $(remove_color_codes "${lines[1]}") =~ "| Author:" ]] || false # | Author: + [[ $(remove_color_codes "${lines[2]}") =~ "| Date:" ]] || false # | Date: + [[ $(remove_color_codes "${lines[3]}") =~ "|" ]] || false # | + [[ $(remove_color_codes "${lines[4]}") =~ "commit 1" ]] || false # | commit 1 + [[ $(remove_color_codes "${lines[5]}") =~ "|" ]] || false # | + [[ $(remove_color_codes "${lines[6]}") =~ "* commit " ]] || false # * commit xxx + [[ $(remove_color_codes "${lines[7]}") =~ "Author:" ]] || false # Author: + [[ $(remove_color_codes "${lines[8]}") =~ "Date:" ]] || false # Date: + [[ $(remove_color_codes "${lines[9]}") =~ "Initialize data repository" ]] || false # Initialize data repository } @@ -882,33 +875,33 @@ teardown() { # Check the output with patterns [[ "${lines[0]}" =~ \* ]] || false - [[ $(echo "${lines[0]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "*\ commit " ]] || false # *\ commit xxx - [[ $(echo "${lines[1]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Merge:" ]] || false # | | Merge: - [[ $(echo "${lines[2]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Author:" ]] || false # | | Author: - [[ $(echo "${lines[3]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Date:" ]] || false # | | Date: - [[ $(echo "${lines[4]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | - [[ $(echo "${lines[5]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Merge branchA into main" ]] || false # | | Merge branchA into main - [[ $(echo "${lines[6]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | - [[ $(echo "${lines[7]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* | commit " ]] || false # * | commit xxx - [[ $(echo "${lines[8]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Author:" ]] || false # | | Author: - [[ $(echo "${lines[9]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Date:" ]] || false # | | Date: - [[ $(echo "${lines[10]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | - [[ $(echo "${lines[11]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 2 MAIN" ]] || false # | | commit 2 MAIN - [[ $(echo "${lines[12]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | - [[ $(echo "${lines[13]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| * commit " ]] || false # | * commit xxx - [[ $(echo "${lines[14]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Author:" ]] || false # | | Author: - [[ $(echo "${lines[15]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| | Date:" ]] || false # | | Date: - [[ $(echo "${lines[16]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | - [[ $(echo "${lines[17]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 1 BRANCHA" ]] || false # | | commit 1 BRANCHA - [[ $(echo "${lines[18]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| |" ]] || false # | | - [[ $(echo "${lines[19]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "*/ commit" ]] || false # */ commit xxx - [[ $(echo "${lines[20]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Author:" ]] || false # | Author: - [[ $(echo "${lines[21]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "| Date:" ]] || false # | Date: - [[ $(echo "${lines[22]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | - [[ $(echo "${lines[23]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "commit 1 MAIN" ]] || false # | commit 1 MAIN - [[ $(echo "${lines[24]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "|" ]] || false # | - [[ $(echo "${lines[25]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "* commit" ]] || false # * commit - [[ $(echo "${lines[26]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Author:" ]] || false # Author: - [[ $(echo "${lines[27]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Date:" ]] || false # Date: - [[ $(echo "${lines[28]}" | sed -E 's/\x1b\[[0-9;]*m//g') =~ "Initialize data repository" ]] || false # Initialize data repository + [[ $(remove_color_codes "${lines[0]}") =~ "*\ commit " ]] || false # *\ commit xxx + [[ $(remove_color_codes "${lines[1]}") =~ "| | Merge:" ]] || false # | | Merge: + [[ $(remove_color_codes "${lines[2]}") =~ "| | Author:" ]] || false # | | Author: + [[ $(remove_color_codes "${lines[3]}") =~ "| | Date:" ]] || false # | | Date: + [[ $(remove_color_codes "${lines[4]}") =~ "| |" ]] || false # | | + [[ $(remove_color_codes "${lines[5]}") =~ "Merge branchA into main" ]] || false # | | Merge branchA into main + [[ $(remove_color_codes "${lines[6]}") =~ "| |" ]] || false # | | + [[ $(remove_color_codes "${lines[7]}") =~ "* | commit " ]] || false # * | commit xxx + [[ $(remove_color_codes "${lines[8]}") =~ "| | Author:" ]] || false # | | Author: + [[ $(remove_color_codes "${lines[9]}") =~ "| | Date:" ]] || false # | | Date: + [[ $(remove_color_codes "${lines[10]}") =~ "| |" ]] || false # | | + [[ $(remove_color_codes "${lines[11]}") =~ "commit 2 MAIN" ]] || false # | | commit 2 MAIN + [[ $(remove_color_codes "${lines[12]}") =~ "| |" ]] || false # | | + [[ $(remove_color_codes "${lines[13]}") =~ "| * commit " ]] || false # | * commit xxx + [[ $(remove_color_codes "${lines[14]}") =~ "| | Author:" ]] || false # | | Author: + [[ $(remove_color_codes "${lines[15]}") =~ "| | Date:" ]] || false # | | Date: + [[ $(remove_color_codes "${lines[16]}") =~ "| |" ]] || false # | | + [[ $(remove_color_codes "${lines[17]}") =~ "commit 1 BRANCHA" ]] || false # | | commit 1 BRANCHA + [[ $(remove_color_codes "${lines[18]}") =~ "| |" ]] || false # | | + [[ $(remove_color_codes "${lines[19]}") =~ "*/ commit" ]] || false # */ commit xxx + [[ $(remove_color_codes "${lines[20]}") =~ "| Author:" ]] || false # | Author: + [[ $(remove_color_codes "${lines[21]}") =~ "| Date:" ]] || false # | Date: + [[ $(remove_color_codes "${lines[22]}") =~ "|" ]] || false # | + [[ $(remove_color_codes "${lines[23]}") =~ "commit 1 MAIN" ]] || false # | commit 1 MAIN + [[ $(remove_color_codes "${lines[24]}") =~ "|" ]] || false # | + [[ $(remove_color_codes "${lines[25]}") =~ "* commit" ]] || false # * commit + [[ $(remove_color_codes "${lines[26]}") =~ "Author:" ]] || false # Author: + [[ $(remove_color_codes "${lines[27]}") =~ "Date:" ]] || false # Date: + [[ $(remove_color_codes "${lines[28]}") =~ "Initialize data repository" ]] || false # Initialize data repository } \ No newline at end of file From a0a8dab11bd90a9d16e56579b8b3ab85a7523b13 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Wed, 31 Jul 2024 15:01:38 -0700 Subject: [PATCH 10/19] handle situation when there are a lot of branches --- go/cmd/dolt/commands/log_graph.go | 79 ++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 4cac9315d19..598407de8ee 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -315,22 +315,14 @@ func appendMessage(graph [][]string, pager *outputpager.Pager, apr *argparser.Ar // expand the graph based on the length of the commit message func expandGraph(commits []*CommitInfoWithChildren, width int) { posY := 0 - for i, commit := range commits { + for _, commit := range commits { // one empty column between each branch path commit.X = commit.X * 2 commit.Y = posY formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) commit.formattedMessage = formattedMessage - // make sure there is enough space for the diagonal line connecting to the parent - // this is an approximation, assume that there will be enough space if parent is not the next commit - maxDistanceFromParent := float64(0) - if i < len(commits)-1 && commits[i+1].Commit.commitHash == commit.Commit.parentHashes[0] { - maxDistanceFromParent = math.Max(math.Abs(float64(commits[i+1].X-commit.X)), maxDistanceFromParent) - } - - commitInfoHeight := getHeightOfCommit(commit) - posY += int(math.Max(float64(commitInfoHeight+1), maxDistanceFromParent)) + posY += getHeightOfCommit(commit) + 1 } } @@ -387,14 +379,35 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn // the second part is extending the path to the current commit along the y-axis if parent.X < commit.X { color := branchColors[x/2%len(branchColors)] - for xx := parent.X + 1; xx < commit.X; xx++ { - if graph[parent.Y+parent.X+1-xx][xx] == " " { + distanceX := commit.X - parent.X + distanceY := parent.Y - commit.Y + if distanceX > distanceY { + // * child commit + // / + // / + // *--/ parent commit + for xx := 0; xx < distanceY; xx++ { + graph[parent.Y-xx][parent.X+distanceX-distanceY+xx] = fmt.Sprintf("%s/", color) + } + for xx := parent.X; xx < parent.X+(distanceX-distanceY); xx++ { + if graph[parent.Y][xx] == " " { + graph[parent.Y][xx] = fmt.Sprintf("%s-", color) + } + } + } else { + // * child commit + // | + // | + // / + // / + // */ parent commit + for xx := parent.X + 1; xx < commit.X; xx++ { graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", color) } - } - for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { - if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", color) + for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { + if graph[yy][x] == " " { + graph[yy][x] = fmt.Sprintf("%s|", color) + } } } } @@ -403,14 +416,36 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn // the second part is extending the path to the parent commit along the y-axis if parent.X > commit.X { color := branchColors[parent.X/2%len(branchColors)] - for xx := commit.X + 1; xx < parent.X; xx++ { - if graph[commit.Y+xx-commit.X-1][xx] == " " { + distanceX := parent.X - commit.X + distanceY := parent.Y - commit.Y + if distanceY > distanceX { + // * child commit + // \ + // \ + // | + // | + // * parent commit + for xx := commit.X + 1; xx < parent.X; xx++ { graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", color) } - } - for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { - if graph[yy][parent.X] == " " { - graph[yy][parent.X] = fmt.Sprintf("%s|", color) + for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { + if graph[yy][parent.X] == " " { + graph[yy][parent.X] = fmt.Sprintf("%s|", color) + } + } + } else { + // *---\ child commit + // \ + // \ + // \ + // * parent commit + for yy := 0; yy < distanceY; yy++ { + graph[parent.Y-yy][parent.X-yy] = fmt.Sprintf("%s\\", color) + } + for xx := commit.X + 1; xx < parent.X-distanceY; xx++ { + if graph[commit.Y][xx] == " " { + graph[commit.Y][xx] = fmt.Sprintf("%s-", color) + } } } } From 17c2a164217450c50d7ba1024b7105ba3644cc0a Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 1 Aug 2024 12:02:46 -0700 Subject: [PATCH 11/19] move diagonal line down, commes, algorithm doc --- go/cmd/dolt/commands/log_graph.go | 444 ++++++++++++++++++------------ integration-tests/bats/log.bats | 4 +- 2 files changed, 275 insertions(+), 173 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 598407de8ee..11173a13fdb 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -24,11 +24,145 @@ import ( "github.com/dolthub/dolt/go/store/util/outputpager" ) +/** +* The graph API is used to draw a text-based representation of the commit +* history. The API generates the graph in a line-by-line fashion. +* +* The Algorithm +* ---------------- +* +* - Calculate the positions of the commits in the graph. +* The vertical position of each commit is determined by the order of the commits, but should be adjusted to the length of the commit message. +* +* The calculation of horizontal position is more complex, but mostly depends on the parent-child relationship of the commits. +* +* Create a 2D matrix to store the branch paths, this matrix will help us find the available column for the commits. +* In each column, there will be multiple branch paths, and each path is represented by a pair of positions of start and end commits on the branch. +* For example: +* * Head commit (main branch) +* * Merge branch A into main +* * Commit on branch A +* * Parent commit 1 +* * Merge branch B into main +* * Commit on branch B +* * Parent commit 2 +* Branches in this will be stored in columns as: +* Column 0: [[0,6]], there's only one branch path in this column, from the head commit to the parent commit 2 +* Column 1: [[2,2], [5,5]], there are two branch paths in this column +* +* Note that we are calculating the positions in the sorted order, so the child commits are always calculated before their parents. +* There are 3 types of commits: +* 1. The commit has no children, this is a head commit on a branch. It will be put in a new column +* 2. The commit has branch children (the children has this commit as first parent), the x coordinate of the commit is the minimum x coordinate (leftmost position) of its children. +* 3. The commit has no branch children but merge children, searching in the columns matrix to find the first column that has no branches that will overlap with the current commit. +* +* +* - Draw the graph. +* Once we have the positions of the commits, we can draw the graph. +* For each commit, draw the commit and the path to its parent(s). +* Same as the calculation part, we have 3 types of paths: +* 1. The parent is on the same branch/column, draw a vertical line from the parent to the current commit. +* 2. The parent is on the left side of the current commit. Draw a diagonal line from the parent to the column of the current commit, then draw a horizontal line or a vertical line depending on the vertical and horizontal distance of the two. +* a. the horizontal distance is greater than the vertical distance b. the vertical distance is greater than the horizontal distance +* * child commit * child commit +* / | +* / / +* *---- parent commit * parent commit +* +* 3. The parent is on the right side of the current commit, draw a diagonal line from the parent to the column of the current commit, then draw a horizontal line to the parent. +* a. the vertical distance is greater than the horizontal distance b. the horizontal distance is greater than the vertical distance +* * child commit * --- child commit +* \ \ +* \ \ +* | \ +* * parent commit * parent commit +* +* ------------ +* Sample output +* ------------- +* +* The following is an example of the output. +* ------------ + +* commit skcm452jteobhaqb2pngc8rbc37p0p0d(HEAD -> main) +|\ Merge: qcfirrj62mlvv6002bq6m9ep16t4ej61 tnenj0ntvhue15v3veq98ng4pi2vvqta +| | Author: liuliu +| | Date: Thu Aug 01 10:23:22 -0700 2024 +| | +| | Merge branch 'd' into main +| | +* | commit qcfirrj62mlvv6002bq6m9ep16t4ej61 +|\| Merge: nlmgt75rl0a4j6ff8qp8afj2t70l3dfo 78vpv0ij6mr788vnm3k8r36m4k519ob1 +| \ Author: liuliu +| |\ Date: Thu Aug 01 10:23:19 -0700 2024 +| | | +| | | Merge branch 'c' into main +| | | +* | | commit nlmgt75rl0a4j6ff8qp8afj2t70l3dfo +|\| | Merge: l49as4tsfnph2v5edopu8a5cljs8vbc6 oj2frj1292visbj1ve14amhpcq0hn959 +| \ | Author: liuliu +| |\| Date: Thu Aug 01 10:23:16 -0700 2024 +| | \ +| | |\ Merge branch 'b' into main +| | | | +* | | | commit l49as4tsfnph2v5edopu8a5cljs8vbc6 +|\| | | Merge: dk7dd4v2fhj40q3bbv0no9s1vpq41qef 7723gb3u9b125gum67dgier1t67s5md0 +| \ | | Author: liuliu +| |\| | Date: Thu Aug 01 10:22:59 -0700 2024 +| | \ | +| | |\| Merge branch 'a' into main +| | | \ +* | | |\ commit dk7dd4v2fhj40q3bbv0no9s1vpq41qef +| | | | | Author: liuliu +| | | | | Date: Thu Aug 01 10:22:54 -0700 2024 +| | | | | +| | | | | change +| | | | | +| * | | | commit tnenj0ntvhue15v3veq98ng4pi2vvqta(d) +| | | | | Author: liuliu +| | | | | Date: Thu Aug 01 10:22:43 -0700 2024 +| | | | | +| | | | | d +| | | | | +| | * | | commit 78vpv0ij6mr788vnm3k8r36m4k519ob1(c) +| | | | | Author: liuliu +| | | | | Date: Thu Aug 01 10:22:30 -0700 2024 +| | | | | +| | | | | c +| | | | | +| | | * | commit oj2frj1292visbj1ve14amhpcq0hn959(b) +| | | | | Author: liuliu +| | | | | Date: Thu Aug 01 10:22:14 -0700 2024 +| | | | | +| | | | | b +| | | | | +| | | | * commit 7723gb3u9b125gum67dgier1t67s5md0(a) +| | |/ / Author: liuliu +| | / / Date: Thu Aug 01 10:21:57 -0700 2024 +| |/ / +| / / a +|/ / +*-- commit lqoem7sk4l1qjbdlf8k973o89bki0vlk +| Author: liuliu +| Date: Thu Aug 01 10:21:37 -0700 2024 +| +| main +| +* commit 87bg8tfrvjo8cfbak92flu595p5e4bbl + Author: liuliu + Date: Thu Aug 01 10:20:44 -0700 2024 + + Initialize data repository + +* ------------ +* +*/ + type CommitInfoWithChildren struct { Commit CommitInfo Children []string - X int - Y int + Col int + Row int formattedMessage []string } @@ -41,12 +175,12 @@ var branchColors = []string{ "\033[39m", // Default } -type BranchPathType struct { +type BranchPath struct { Start int End int } -// get the children of commits, and initialize the x and y coordinates of the commits +// mapCommitsWithChildrenAndPosition gets the children of commits, and initialize the x and y coordinates of the commits func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithChildren { childrenMap := make(map[string][]string) for _, commit := range commits { @@ -56,21 +190,20 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh } var commitsWithChildren []*CommitInfoWithChildren - for y, commit := range commits { + for index, commit := range commits { commitsWithChildren = append(commitsWithChildren, &CommitInfoWithChildren{ Commit: commit, Children: childrenMap[commit.commitHash], - X: -1, - // the y coordinate of the commit is initialized to the index of the commit as the commits are sorted - Y: y, + Col: -1, + Row: index, }) } return commitsWithChildren } -// wrap the commit message in a constrained width to better align the commit message with the graph -func wrapTextOnWidth(text string, width int) []string { +// wrapTextOnWidth wraps the commit message in a constrained width to better align the commit message with the graph +func indentWithWrap(text string, width int) []string { lines := strings.Split(text, "\n") totalRows := 0 wrappedLines := make([]string, 0) @@ -98,7 +231,7 @@ func wrapTextOnWidth(text string, width int) []string { return wrappedLines } -func min(values ...int) int { +func minVal(values ...int) int { if len(values) == 0 { return math.MaxInt } @@ -111,13 +244,12 @@ func min(values ...int) int { return minVal } -// compute the X coordinate of each commit +// computeColumnEnds compute the column coordinate of each commit func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) ([]*CommitInfoWithChildren, map[string]*CommitInfoWithChildren) { - // each column might have multiple branch paths, and the columns slice stores the branch paths of each column - columns := [][]BranchPathType{} - xPositions := make(map[string]int) + columns := [][]BranchPath{} + colPositions := make(map[string]int) newCommitMap := make(map[string]*CommitInfoWithChildren) - commitsWithXPos := make([]*CommitInfoWithChildren, len(commits)) + commitsWithColPos := make([]*CommitInfoWithChildren, len(commits)) updateColumns := func(col, end int) { columns[col][len(columns[col])-1].End = end @@ -134,93 +266,89 @@ func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string] isLastCommitOnBranch := len(commit.Children) == 0 isBranchOutCommit := len(branchChildren) > 0 - commitX := -1 + commitColInd := -1 if isLastCommitOnBranch { - columns = append(columns, []BranchPathType{ + columns = append(columns, []BranchPath{ { Start: index, End: index, }, }) - commitX = len(columns) - 1 + commitColInd = len(columns) - 1 } else if isBranchOutCommit { - // in the case of a branch out commit, the x coordinate of the commit is the minimum x coordinate of its children - var branchChildrenXs []int + // in the case of a branch out commit, the column index of the commit is the minimum column index of its children + var branchChildrenColIndexes []int for _, childHash := range branchChildren { - if childX, ok := xPositions[childHash]; ok { - branchChildrenXs = append(branchChildrenXs, childX) + if childColInd, ok := colPositions[childHash]; ok { + branchChildrenColIndexes = append(branchChildrenColIndexes, childColInd) } } - commitX = min(branchChildrenXs...) + commitColInd = minVal(branchChildrenColIndexes...) - updateColumns(commitX, index) + updateColumns(commitColInd, index) // update the path that branches out from the current commit by setting their end to be one position before the current commit - for _, childX := range branchChildrenXs { - if childX != commitX { - updateColumns(childX, index-1) + for _, childColInd := range branchChildrenColIndexes { + if childColInd != commitColInd { + updateColumns(childColInd, index-1) } } } else { - // Find an available column so the commit can connect to its children (merge commit) without overlapping with existing branches on columns - // Otherwise, put the commit in a new column - - // minChildY is the highest pos of child commit, maxChildX is the right most pos of child commit - minChildY := math.MaxInt - maxChildX := -1 + // minChildRowInd is the highest pos of child commit, maxChildColInd is the right most pos of child commit + minChildRowInd := math.MaxInt + maxChildColInd := -1 for _, child := range commit.Children { - if commitsMap[child].Y < minChildY { - minChildY = commitsMap[child].Y + if commitsMap[child].Row < minChildRowInd { + minChildRowInd = commitsMap[child].Row } - if xPositions[child] > maxChildX { - maxChildX = xPositions[child] + if colPositions[child] > maxChildColInd { + maxChildColInd = colPositions[child] } } // find the first column that has no branches that overlap with the current commit + // if no such column is found, put the commit in a new column col := -1 - for i := maxChildX + 1; i < len(columns); i++ { - if minChildY >= columns[i][len(columns[i])-1].End { + for i := maxChildColInd + 1; i < len(columns); i++ { + if minChildRowInd >= columns[i][len(columns[i])-1].End { col = i break } } - - // if no column is found, put the commit in a new column if col == -1 { - columns = append(columns, []BranchPathType{ + columns = append(columns, []BranchPath{ { - Start: minChildY + 1, + Start: minChildRowInd + 1, End: index, }, }) - commitX = len(columns) - 1 + commitColInd = len(columns) - 1 } else { - commitX = col - columns[col] = append(columns[col], BranchPathType{ - Start: minChildY + 1, + commitColInd = col + columns[col] = append(columns[col], BranchPath{ + Start: minChildRowInd + 1, End: index, }) } } - xPositions[commit.Commit.commitHash] = commitX - commitsWithXPos[index] = &CommitInfoWithChildren{ + colPositions[commit.Commit.commitHash] = commitColInd + commitsWithColPos[index] = &CommitInfoWithChildren{ Commit: commit.Commit, Children: commit.Children, - X: commitX, - Y: commit.Y, + Col: commitColInd, + Row: commit.Row, } - newCommitMap[commit.Commit.commitHash] = commitsWithXPos[index] + newCommitMap[commit.Commit.commitHash] = commitsWithColPos[index] } - return commitsWithXPos, newCommitMap + return commitsWithColPos, newCommitMap } -func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line string, commit CommitInfo, color, decoration string) { - graphLine := strings.Join(graph[posY], "") +func printLine(graph [][]string, col, row int, pager *outputpager.Pager, line string, commit CommitInfo, color, decoration string) { + graphLine := strings.Join(graph[row], "") - emptySpace := strings.Repeat(" ", posX-len(graph[posY])) + emptySpace := strings.Repeat(" ", col-len(graph[row])) pager.Writer.Write([]byte(fmt.Sprintf("%s%s%s %s", graphLine, emptySpace, color, line))) if decoration != "no" { @@ -229,22 +357,22 @@ func printLine(graph [][]string, posX, posY int, pager *outputpager.Pager, line pager.Writer.Write([]byte("\n")) } -func printCommitMetadata(graph [][]string, pager *outputpager.Pager, posY, posX int, commit *CommitInfoWithChildren, decoration string) { - printLine(graph, posX, posY, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", decoration) +func printCommitMetadata(graph [][]string, pager *outputpager.Pager, row, col int, commit *CommitInfoWithChildren, decoration string) { + printLine(graph, col, row, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", decoration) printMergeInfo := 0 if len(commit.Commit.parentHashes) > 1 { printMergeInfo = 1 } if printMergeInfo == 1 { - printLine(graph, posX, posY+1, pager, fmt.Sprintf("Merge: %s", strings.Join(commit.Commit.parentHashes, " ")), commit.Commit, "\033[37m", "no") + printLine(graph, col, row+1, pager, fmt.Sprintf("Merge: %s", strings.Join(commit.Commit.parentHashes, " ")), commit.Commit, "\033[37m", "no") } - printLine(graph, posX, posY+1+printMergeInfo, pager, fmt.Sprintf("Author: %s <%s>", commit.Commit.commitMeta.Name, commit.Commit.commitMeta.Email), commit.Commit, "\033[37m", "no") + printLine(graph, col, row+1+printMergeInfo, pager, fmt.Sprintf("Author: %s <%s>", commit.Commit.commitMeta.Name, commit.Commit.commitMeta.Email), commit.Commit, "\033[37m", "no") - printLine(graph, posX, posY+2+printMergeInfo, pager, fmt.Sprintf("Date: %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", "no") + printLine(graph, col, row+2+printMergeInfo, pager, fmt.Sprintf("Date: %s", commit.Commit.commitMeta.FormatTS()), commit.Commit, "\033[37m", "no") - pager.Writer.Write([]byte(strings.Join(graph[posY+3+printMergeInfo], ""))) + pager.Writer.Write([]byte(strings.Join(graph[row+3+printMergeInfo], ""))) pager.Writer.Write([]byte("\n")) } @@ -270,181 +398,141 @@ func getHeightOfCommit(commit *CommitInfoWithChildren) int { return height } -// print the commit messages in the graph matrix -func appendMessage(graph [][]string, pager *outputpager.Pager, apr *argparser.ArgParseResults, commits []*CommitInfoWithChildren) { +// printGraphAndCommitsInfo prints the commit messages in the graph matrix +func printGraphAndCommitsInfo(graph [][]string, pager *outputpager.Pager, apr *argparser.ArgParseResults, commits []*CommitInfoWithChildren) { decoration := apr.GetValueOrDefault(cli.DecorateFlag, "auto") for i := 0; i < len(commits)-1; i++ { - startY := commits[i].Y - endY := commits[i+1].Y - startX := commits[i].X + 1 + startRow := commits[i].Row + endRow := commits[i+1].Row + startCol := commits[i].Col + 1 - // find the maximum x position of the graph in the range startY to endY + // find the maximum width of the graph in the range startRow to endRow // this is used to align the commit message with the graph without overlapping with the graph - for j := startY; j < endY; j++ { - if len(graph[j]) > startX { - startX = len(graph[j]) + for j := startRow; j < endRow; j++ { + if len(graph[j]) > startCol { + startCol = len(graph[j]) } } - printCommitMetadata(graph, pager, startY, startX, commits[i], decoration) + printCommitMetadata(graph, pager, startRow, startCol, commits[i], decoration) commitInfoHeight := getHeightOfCommit(commits[i]) - // print the graph with commit message for j, line := range commits[i].formattedMessage { - y := startY + commitInfoHeight - len(commits[i].formattedMessage) + j - printLine(graph, startX, y, pager, fmt.Sprintf("\t%s", line), commits[i].Commit, "\033[37m", "no") + y := startRow + commitInfoHeight - len(commits[i].formattedMessage) + j + printLine(graph, startCol, y, pager, fmt.Sprintf("\t%s", line), commits[i].Commit, "\033[37m", "no") } // print the remaining lines of the graph of the current commit - for j := startY + commitInfoHeight; j < endY; j++ { + for j := startRow + commitInfoHeight; j < endRow; j++ { pager.Writer.Write([]byte(strings.Join(graph[j], ""))) pager.Writer.Write([]byte("\n")) } } - last_commit_y := commits[len(commits)-1].Y - printCommitMetadata(graph, pager, last_commit_y, len(graph[last_commit_y]), commits[len(commits)-1], decoration) + last_commit_row := commits[len(commits)-1].Row + printCommitMetadata(graph, pager, last_commit_row, len(graph[last_commit_row]), commits[len(commits)-1], decoration) for _, line := range commits[len(commits)-1].formattedMessage { pager.Writer.Write([]byte(fmt.Sprintf("\t\033[37m%s", line))) pager.Writer.Write([]byte("\n")) } } -// expand the graph based on the length of the commit message +// expandGraph expands the graph based on the length of the commit message func expandGraph(commits []*CommitInfoWithChildren, width int) { posY := 0 for _, commit := range commits { // one empty column between each branch path - commit.X = commit.X * 2 - commit.Y = posY - formattedMessage := wrapTextOnWidth(commit.Commit.commitMeta.Description, width) + commit.Col = commit.Col * 2 + commit.Row = posY + formattedMessage := indentWithWrap(commit.Commit.commitMeta.Description, width) commit.formattedMessage = formattedMessage posY += getHeightOfCommit(commit) + 1 } } -func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitInfos []CommitInfo) { - commits := mapCommitsWithChildrenAndPosition(commitInfos) - commitsMap := make(map[string]*CommitInfoWithChildren) - for _, commit := range commits { - commitsMap[commit.Commit.commitHash] = commit - } - - commits, commitsMap = computeColumnEnds(commits, commitsMap) - expandGraph(commits, 80) - - // Create a 2D slice to represent the graph - // each element in the graph matrix is a string of length 1 (either "|", "/", "\", "-", or " ") - maxX, maxY := 0, 0 +func drawCommitDotsAndBranchPaths(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) [][]string { + maxWidth, maxHeigh := 0, 0 for _, commit := range commits { - if commit.X > maxX { - maxX = commit.X + if commit.Col > maxWidth { + maxWidth = commit.Col } - if commit.Y > maxY { - maxY = commit.Y + if commit.Row > maxHeigh { + maxHeigh = commit.Row } } heightOfLastCommit := getHeightOfCommit(commits[len(commits)-1]) - graph := make([][]string, maxY+heightOfLastCommit) + graph := make([][]string, maxHeigh+heightOfLastCommit) for i := range graph { - graph[i] = make([]string, maxX+2) + graph[i] = make([]string, maxWidth+2) for j := range graph[i] { graph[i][j] = " " } } - // Draw the commits and paths for _, commit := range commits { - x := commit.X - y := commit.Y - graph[y][x] = "\033[37m*" + col := commit.Col + row := commit.Row + graph[row][col] = "\033[37m*" - // draw the path between the commit and its parent for _, parentHash := range commit.Commit.parentHashes { if parent, ok := commitsMap[parentHash]; ok { - // the parent is on the same branch/column - if parent.X == commit.X { - color := branchColors[x/2%len(branchColors)] - for yy := commit.Y + 1; yy < parent.Y; yy++ { - if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", color) + parentCol := parent.Col + parentRow := parent.Row + if parentCol == col { + color := branchColors[col/2%len(branchColors)] + for r := row + 1; r < parentRow; r++ { + if graph[r][col] == " " { + graph[r][col] = fmt.Sprintf("%s|", color) } } } - // from parent to the current commit, a new branch path is created - // the first part is draw a diagonal line from the parent to the column of the current commit - // the second part is extending the path to the current commit along the y-axis - if parent.X < commit.X { - color := branchColors[x/2%len(branchColors)] - distanceX := commit.X - parent.X - distanceY := parent.Y - commit.Y - if distanceX > distanceY { - // * child commit - // / - // / - // *--/ parent commit - for xx := 0; xx < distanceY; xx++ { - graph[parent.Y-xx][parent.X+distanceX-distanceY+xx] = fmt.Sprintf("%s/", color) + if parentCol < col { + color := branchColors[col/2%len(branchColors)] + horizontalDistance := col - parentCol + verticalDistance := parentRow - row + if horizontalDistance > verticalDistance { + for i := 1; i < verticalDistance; i++ { + graph[parentRow-i][parentCol+horizontalDistance-verticalDistance+i] = fmt.Sprintf("%s/", color) } - for xx := parent.X; xx < parent.X+(distanceX-distanceY); xx++ { - if graph[parent.Y][xx] == " " { - graph[parent.Y][xx] = fmt.Sprintf("%s-", color) + for i := parentCol; i < parentCol+(horizontalDistance-verticalDistance)+1; i++ { + if graph[parentRow][i] == " " { + graph[parentRow][i] = fmt.Sprintf("%s-", color) } } } else { - // * child commit - // | - // | - // / - // / - // */ parent commit - for xx := parent.X + 1; xx < commit.X; xx++ { - graph[parent.Y+parent.X+1-xx][xx] = fmt.Sprintf("%s/", color) + for i := parentCol + 1; i < col; i++ { + graph[parentRow+parentCol-i][i] = fmt.Sprintf("%s/", color) } - for yy := parent.Y + parent.X + 1 - commit.X; yy > commit.Y; yy-- { - if graph[yy][x] == " " { - graph[yy][x] = fmt.Sprintf("%s|", color) + for i := parentRow + parentCol - col; i > row; i-- { + if graph[i][col] == " " { + graph[i][col] = fmt.Sprintf("%s|", color) } } } } - // the current commit is a merge commit - // the first part is draw a diagonal line from the current commit to the column of the parent commit - // the second part is extending the path to the parent commit along the y-axis - if parent.X > commit.X { - color := branchColors[parent.X/2%len(branchColors)] - distanceX := parent.X - commit.X - distanceY := parent.Y - commit.Y - if distanceY > distanceX { - // * child commit - // \ - // \ - // | - // | - // * parent commit - for xx := commit.X + 1; xx < parent.X; xx++ { - graph[commit.Y+xx-commit.X-1][xx] = fmt.Sprintf("%s\\", color) + if parentCol > col { + color := branchColors[parentCol/2%len(branchColors)] + horizontalDistance := parentCol - col + verticalDistance := parentRow - row + if verticalDistance > horizontalDistance { + for i := col + 1; i < parentCol; i++ { + graph[row+i-col][i] = fmt.Sprintf("%s\\", color) } - for yy := commit.Y + parent.X - (commit.X + 1); yy < parent.Y; yy++ { - if graph[yy][parent.X] == " " { - graph[yy][parent.X] = fmt.Sprintf("%s|", color) + for i := row + parentCol - col; i < parentRow; i++ { + if graph[i][parent.Col] == " " { + graph[i][parent.Col] = fmt.Sprintf("%s|", color) } } } else { - // *---\ child commit - // \ - // \ - // \ - // * parent commit - for yy := 0; yy < distanceY; yy++ { - graph[parent.Y-yy][parent.X-yy] = fmt.Sprintf("%s\\", color) + for i := 0; i < verticalDistance-1; i++ { + graph[parentRow-i][parentCol-i] = fmt.Sprintf("%s\\", color) } - for xx := commit.X + 1; xx < parent.X-distanceY; xx++ { - if graph[commit.Y][xx] == " " { - graph[commit.Y][xx] = fmt.Sprintf("%s-", color) + for i := col + 1; i < parent.Col-verticalDistance+1; i++ { + if graph[row][i] == " " { + graph[row][i] = fmt.Sprintf("%s-", color) } } } @@ -452,6 +540,20 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn } } } + return graph +} + +func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitInfos []CommitInfo) { + commits := mapCommitsWithChildrenAndPosition(commitInfos) + commitsMap := make(map[string]*CommitInfoWithChildren) + for _, commit := range commits { + commitsMap[commit.Commit.commitHash] = commit + } + + commits, commitsMap = computeColumnEnds(commits, commitsMap) + expandGraph(commits, 80) + + graph := drawCommitDotsAndBranchPaths(commits, commitsMap) // trim the trailing empty space of each line so we can use the length of the line to align the commit message for i, line := range graph { @@ -459,5 +561,5 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn graph[i] = line } - appendMessage(graph, pager, apr, commits) + printGraphAndCommitsInfo(graph, pager, apr, commits) } diff --git a/integration-tests/bats/log.bats b/integration-tests/bats/log.bats index bfe954d096a..2a75b9f55bc 100755 --- a/integration-tests/bats/log.bats +++ b/integration-tests/bats/log.bats @@ -832,7 +832,7 @@ remove_color_codes() { } -@test "log --graph: basic graph log" { +@test "log: --graph: basic graph log" { dolt sql -q "create table testtable (pk int PRIMARY KEY)" dolt add . dolt commit -m "commit 1" @@ -856,7 +856,7 @@ remove_color_codes() { } -@test "log --graph: graph with merges" { +@test "log: --graph: graph with merges" { if [ "$SQL_ENGINE" = "remote-engine" ]; then skip "needs checkout which is unsupported for remote-engine" fi From 596df6af50df912dfd1926fe5aeab6b7b445723c Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 1 Aug 2024 12:13:07 -0700 Subject: [PATCH 12/19] remove indent function --- go/cmd/dolt/commands/log_graph.go | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 11173a13fdb..7a4054de178 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -202,35 +202,6 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh return commitsWithChildren } -// wrapTextOnWidth wraps the commit message in a constrained width to better align the commit message with the graph -func indentWithWrap(text string, width int) []string { - lines := strings.Split(text, "\n") - totalRows := 0 - wrappedLines := make([]string, 0) - - for _, line := range lines { - words := strings.Fields(line) - currentLine := "" - for _, word := range words { - if len(currentLine)+len(word)+1 > width { - wrappedLines = append(wrappedLines, currentLine) - totalRows++ - currentLine = word - } else { - if currentLine != "" { - currentLine += " " - } - currentLine += word - } - } - if currentLine != "" { - wrappedLines = append(wrappedLines, currentLine) - totalRows++ - } - } - return wrappedLines -} - func minVal(values ...int) int { if len(values) == 0 { return math.MaxInt @@ -446,7 +417,7 @@ func expandGraph(commits []*CommitInfoWithChildren, width int) { // one empty column between each branch path commit.Col = commit.Col * 2 commit.Row = posY - formattedMessage := indentWithWrap(commit.Commit.commitMeta.Description, width) + formattedMessage := strings.Split(commit.Commit.commitMeta.Description, "\n") commit.formattedMessage = formattedMessage posY += getHeightOfCommit(commit) + 1 From fe68da7bb6bb9e6773d230c1bbe99eb2fb846987 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 1 Aug 2024 12:31:19 -0700 Subject: [PATCH 13/19] white color, remove unused width --- go/cmd/dolt/commands/log_graph.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 7a4054de178..4ddc90594dc 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -172,7 +172,7 @@ var branchColors = []string{ "\033[34m", // Blue "\033[35m", // Magenta "\033[36m", // Cyan - "\033[39m", // Default + "\033[37m", // White } type BranchPath struct { @@ -411,7 +411,7 @@ func printGraphAndCommitsInfo(graph [][]string, pager *outputpager.Pager, apr *a } // expandGraph expands the graph based on the length of the commit message -func expandGraph(commits []*CommitInfoWithChildren, width int) { +func expandGraph(commits []*CommitInfoWithChildren) { posY := 0 for _, commit := range commits { // one empty column between each branch path @@ -522,7 +522,7 @@ func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitIn } commits, commitsMap = computeColumnEnds(commits, commitsMap) - expandGraph(commits, 80) + expandGraph(commits) graph := drawCommitDotsAndBranchPaths(commits, commitsMap) From a5f41cc52337d3eb5feff18909a0f3258539ca62 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 1 Aug 2024 12:56:13 -0700 Subject: [PATCH 14/19] draw the example with only spaces --- go/cmd/dolt/commands/log_graph.go | 41 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 4ddc90594dc..2c6d644dd22 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -39,13 +39,13 @@ import ( * Create a 2D matrix to store the branch paths, this matrix will help us find the available column for the commits. * In each column, there will be multiple branch paths, and each path is represented by a pair of positions of start and end commits on the branch. * For example: -* * Head commit (main branch) -* * Merge branch A into main -* * Commit on branch A -* * Parent commit 1 -* * Merge branch B into main -* * Commit on branch B -* * Parent commit 2 +* 0 * Head commit (main branch) +* 1 * Merge branch A into main +* 2 * Commit on branch A +* 3 * Parent commit 1 +* 4 * Merge branch B into main +* 5 * Commit on branch B +* 6 * Parent commit 2 * Branches in this will be stored in columns as: * Column 0: [[0,6]], there's only one branch path in this column, from the head commit to the parent commit 2 * Column 1: [[2,2], [5,5]], there are two branch paths in this column @@ -53,9 +53,10 @@ import ( * Note that we are calculating the positions in the sorted order, so the child commits are always calculated before their parents. * There are 3 types of commits: * 1. The commit has no children, this is a head commit on a branch. It will be put in a new column -* 2. The commit has branch children (the children has this commit as first parent), the x coordinate of the commit is the minimum x coordinate (leftmost position) of its children. +* 2. The commit has branch children (at least one child has this commit as first parent), this commit will put in the column of the left most branch children. * 3. The commit has no branch children but merge children, searching in the columns matrix to find the first column that has no branches that will overlap with the current commit. -* +* - In the last example, when determining where to put the commit on branch B, we should have the columns matrix as: [[[0,6]], [[2,2]]]. Potential column index options for this commit is 0, 1, or we can create a new column: 2. +* - Look at the children of this current commit, which is the merge commit and it's on column 0. So we can start from column 1,and since the path in column 1 ends at row 2, this commits can be put in column 1. * * - Draw the graph. * Once we have the positions of the commits, we can draw the graph. @@ -63,19 +64,19 @@ import ( * Same as the calculation part, we have 3 types of paths: * 1. The parent is on the same branch/column, draw a vertical line from the parent to the current commit. * 2. The parent is on the left side of the current commit. Draw a diagonal line from the parent to the column of the current commit, then draw a horizontal line or a vertical line depending on the vertical and horizontal distance of the two. -* a. the horizontal distance is greater than the vertical distance b. the vertical distance is greater than the horizontal distance -* * child commit * child commit -* / | -* / / -* *---- parent commit * parent commit +* a. the horizontal distance is greater than the vertical distance b. the vertical distance is greater than the horizontal distance +* * child commit * child commit +* / | +* / / +* *---- parent commit * parent commit * * 3. The parent is on the right side of the current commit, draw a diagonal line from the parent to the column of the current commit, then draw a horizontal line to the parent. -* a. the vertical distance is greater than the horizontal distance b. the horizontal distance is greater than the vertical distance -* * child commit * --- child commit -* \ \ -* \ \ -* | \ -* * parent commit * parent commit +* a. the vertical distance is greater than the horizontal distance b. the horizontal distance is greater than the vertical distance +* * child commit * --- child commit +* \ \ +* \ \ +* | \ +* * parent commit * parent commit * * ------------ * Sample output From e08f1201763beb3580d35c4b7ec5fa307c14858c Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 1 Aug 2024 13:47:54 -0700 Subject: [PATCH 15/19] fix bats test --- integration-tests/bats/log.bats | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/integration-tests/bats/log.bats b/integration-tests/bats/log.bats index 2a75b9f55bc..d5f6a2e4f76 100755 --- a/integration-tests/bats/log.bats +++ b/integration-tests/bats/log.bats @@ -831,7 +831,6 @@ remove_color_codes() { echo "$1" | sed -E 's/\x1b\[[0-9;]*m//g' } - @test "log: --graph: basic graph log" { dolt sql -q "create table testtable (pk int PRIMARY KEY)" dolt add . @@ -872,11 +871,11 @@ remove_color_codes() { run dolt log --graph [ "$status" -eq 0 ] - + # Check the output with patterns [[ "${lines[0]}" =~ \* ]] || false - [[ $(remove_color_codes "${lines[0]}") =~ "*\ commit " ]] || false # *\ commit xxx - [[ $(remove_color_codes "${lines[1]}") =~ "| | Merge:" ]] || false # | | Merge: + [[ $(remove_color_codes "${lines[0]}") =~ "* commit " ]] || false # * commit xxx + [[ $(remove_color_codes "${lines[1]}") =~ "|\ Merge:" ]] || false # |\ Merge: [[ $(remove_color_codes "${lines[2]}") =~ "| | Author:" ]] || false # | | Author: [[ $(remove_color_codes "${lines[3]}") =~ "| | Date:" ]] || false # | | Date: [[ $(remove_color_codes "${lines[4]}") =~ "| |" ]] || false # | | @@ -893,10 +892,10 @@ remove_color_codes() { [[ $(remove_color_codes "${lines[15]}") =~ "| | Date:" ]] || false # | | Date: [[ $(remove_color_codes "${lines[16]}") =~ "| |" ]] || false # | | [[ $(remove_color_codes "${lines[17]}") =~ "commit 1 BRANCHA" ]] || false # | | commit 1 BRANCHA - [[ $(remove_color_codes "${lines[18]}") =~ "| |" ]] || false # | | - [[ $(remove_color_codes "${lines[19]}") =~ "*/ commit" ]] || false # */ commit xxx - [[ $(remove_color_codes "${lines[20]}") =~ "| Author:" ]] || false # | Author: - [[ $(remove_color_codes "${lines[21]}") =~ "| Date:" ]] || false # | Date: + [[ $(remove_color_codes "${lines[18]}") =~ "|/" ]] || false # |/ + [[ $(remove_color_codes "${lines[19]}") =~ "* commit" ]] || false # * commit xxx + [[ $(remove_color_codes "${lines[20]}") =~ "| Author:" ]] || false # | Author: + [[ $(remove_color_codes "${lines[21]}") =~ "| Date:" ]] || false # | Date: [[ $(remove_color_codes "${lines[22]}") =~ "|" ]] || false # | [[ $(remove_color_codes "${lines[23]}") =~ "commit 1 MAIN" ]] || false # | commit 1 MAIN [[ $(remove_color_codes "${lines[24]}") =~ "|" ]] || false # | From 154d90cf12bfd91943c872bb2d45621c4dfda7f8 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Thu, 1 Aug 2024 17:08:12 -0700 Subject: [PATCH 16/19] tests --- go/cmd/dolt/commands/log_graph.go | 38 ++++---- go/cmd/dolt/commands/log_graph_test.go | 120 +++++++++++++++++++++++++ integration-tests/bats/log.bats | 102 +++++++++++++++++++++ 3 files changed, 241 insertions(+), 19 deletions(-) create mode 100644 go/cmd/dolt/commands/log_graph_test.go diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 2c6d644dd22..4d26a4e3e11 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -159,7 +159,7 @@ import ( * */ -type CommitInfoWithChildren struct { +type commitInfoWithChildren struct { Commit CommitInfo Children []string Col int @@ -176,13 +176,13 @@ var branchColors = []string{ "\033[37m", // White } -type BranchPath struct { +type branchPath struct { Start int End int } // mapCommitsWithChildrenAndPosition gets the children of commits, and initialize the x and y coordinates of the commits -func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithChildren { +func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*commitInfoWithChildren { childrenMap := make(map[string][]string) for _, commit := range commits { for _, parent := range commit.parentHashes { @@ -190,9 +190,9 @@ func mapCommitsWithChildrenAndPosition(commits []CommitInfo) []*CommitInfoWithCh } } - var commitsWithChildren []*CommitInfoWithChildren + var commitsWithChildren []*commitInfoWithChildren for index, commit := range commits { - commitsWithChildren = append(commitsWithChildren, &CommitInfoWithChildren{ + commitsWithChildren = append(commitsWithChildren, &commitInfoWithChildren{ Commit: commit, Children: childrenMap[commit.commitHash], Col: -1, @@ -217,11 +217,11 @@ func minVal(values ...int) int { } // computeColumnEnds compute the column coordinate of each commit -func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) ([]*CommitInfoWithChildren, map[string]*CommitInfoWithChildren) { - columns := [][]BranchPath{} +func computeColumnEnds(commits []*commitInfoWithChildren, commitsMap map[string]*commitInfoWithChildren) ([]*commitInfoWithChildren, map[string]*commitInfoWithChildren) { + columns := [][]branchPath{} colPositions := make(map[string]int) - newCommitMap := make(map[string]*CommitInfoWithChildren) - commitsWithColPos := make([]*CommitInfoWithChildren, len(commits)) + newCommitMap := make(map[string]*commitInfoWithChildren) + commitsWithColPos := make([]*commitInfoWithChildren, len(commits)) updateColumns := func(col, end int) { columns[col][len(columns[col])-1].End = end @@ -241,7 +241,7 @@ func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string] commitColInd := -1 if isLastCommitOnBranch { - columns = append(columns, []BranchPath{ + columns = append(columns, []branchPath{ { Start: index, End: index, @@ -290,7 +290,7 @@ func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string] } } if col == -1 { - columns = append(columns, []BranchPath{ + columns = append(columns, []branchPath{ { Start: minChildRowInd + 1, End: index, @@ -299,14 +299,14 @@ func computeColumnEnds(commits []*CommitInfoWithChildren, commitsMap map[string] commitColInd = len(columns) - 1 } else { commitColInd = col - columns[col] = append(columns[col], BranchPath{ + columns[col] = append(columns[col], branchPath{ Start: minChildRowInd + 1, End: index, }) } } colPositions[commit.Commit.commitHash] = commitColInd - commitsWithColPos[index] = &CommitInfoWithChildren{ + commitsWithColPos[index] = &commitInfoWithChildren{ Commit: commit.Commit, Children: commit.Children, Col: commitColInd, @@ -329,7 +329,7 @@ func printLine(graph [][]string, col, row int, pager *outputpager.Pager, line st pager.Writer.Write([]byte("\n")) } -func printCommitMetadata(graph [][]string, pager *outputpager.Pager, row, col int, commit *CommitInfoWithChildren, decoration string) { +func printCommitMetadata(graph [][]string, pager *outputpager.Pager, row, col int, commit *commitInfoWithChildren, decoration string) { printLine(graph, col, row, pager, fmt.Sprintf("commit %s", commit.Commit.commitHash), commit.Commit, "\033[33m", decoration) printMergeInfo := 0 @@ -362,7 +362,7 @@ func trimTrailing(row []string) []string { // the height that a commit will take up in the graph // 4 lines for commit metadata (commit hash, author, date, and an empty line) + number of lines in the commit message // if the commit is a merge commit, add one more line for the "Merge:" line -func getHeightOfCommit(commit *CommitInfoWithChildren) int { +func getHeightOfCommit(commit *commitInfoWithChildren) int { height := 4 + len(commit.formattedMessage) if len(commit.Commit.parentHashes) > 1 { height = height + 1 @@ -371,7 +371,7 @@ func getHeightOfCommit(commit *CommitInfoWithChildren) int { } // printGraphAndCommitsInfo prints the commit messages in the graph matrix -func printGraphAndCommitsInfo(graph [][]string, pager *outputpager.Pager, apr *argparser.ArgParseResults, commits []*CommitInfoWithChildren) { +func printGraphAndCommitsInfo(graph [][]string, pager *outputpager.Pager, apr *argparser.ArgParseResults, commits []*commitInfoWithChildren) { decoration := apr.GetValueOrDefault(cli.DecorateFlag, "auto") for i := 0; i < len(commits)-1; i++ { @@ -412,7 +412,7 @@ func printGraphAndCommitsInfo(graph [][]string, pager *outputpager.Pager, apr *a } // expandGraph expands the graph based on the length of the commit message -func expandGraph(commits []*CommitInfoWithChildren) { +func expandGraph(commits []*commitInfoWithChildren) { posY := 0 for _, commit := range commits { // one empty column between each branch path @@ -425,7 +425,7 @@ func expandGraph(commits []*CommitInfoWithChildren) { } } -func drawCommitDotsAndBranchPaths(commits []*CommitInfoWithChildren, commitsMap map[string]*CommitInfoWithChildren) [][]string { +func drawCommitDotsAndBranchPaths(commits []*commitInfoWithChildren, commitsMap map[string]*commitInfoWithChildren) [][]string { maxWidth, maxHeigh := 0, 0 for _, commit := range commits { if commit.Col > maxWidth { @@ -517,7 +517,7 @@ func drawCommitDotsAndBranchPaths(commits []*CommitInfoWithChildren, commitsMap func logGraph(pager *outputpager.Pager, apr *argparser.ArgParseResults, commitInfos []CommitInfo) { commits := mapCommitsWithChildrenAndPosition(commitInfos) - commitsMap := make(map[string]*CommitInfoWithChildren) + commitsMap := make(map[string]*commitInfoWithChildren) for _, commit := range commits { commitsMap[commit.Commit.commitHash] = commit } diff --git a/go/cmd/dolt/commands/log_graph_test.go b/go/cmd/dolt/commands/log_graph_test.go new file mode 100644 index 00000000000..a0440e928f4 --- /dev/null +++ b/go/cmd/dolt/commands/log_graph_test.go @@ -0,0 +1,120 @@ +package commands + +import ( + "testing" + + "github.com/dolthub/dolt/go/store/datas" + "github.com/stretchr/testify/require" +) + +func TestMapCommitsWithChildrenAndPosition(t *testing.T) { + commits := []CommitInfo{ + {commitHash: "hash1", parentHashes: []string{"hash2"}, commitMeta: &datas.CommitMeta{Description: "Commit 1"}}, + {commitHash: "hash2", parentHashes: []string{}, commitMeta: &datas.CommitMeta{Description: "Commit 2"}}, + } + + result := mapCommitsWithChildrenAndPosition(commits) + + require.Equal(t, 2, len(result)) + require.Equal(t, "hash1", result[0].Commit.commitHash) + require.Equal(t, "hash2", result[1].Commit.commitHash) + require.Equal(t, []string{"hash1"}, result[1].Children) + require.Nil(t, result[0].Children) +} + +func TestComputeColumnEnds(t *testing.T) { + // Test with two commits, one parent and one child + commits := []*commitInfoWithChildren{ + {Commit: CommitInfo{commitHash: "hash1", parentHashes: []string{"hash2"}}, Children: []string{}, Row: 0}, + {Commit: CommitInfo{commitHash: "hash2", parentHashes: []string{}}, Children: []string{"hash1"}, Row: 1}, + } + commitsMap := map[string]*commitInfoWithChildren{ + "hash1": commits[0], + "hash2": commits[1], + } + + result, _ := computeColumnEnds(commits, commitsMap) + require.Equal(t, 0, result[0].Col) + require.Equal(t, 0, result[1].Col) + + // Test four commits: + // + // + // 1A (branchA) + // / \ + // 1M - 2M - 3M (main) + commits = []*commitInfoWithChildren{ + {Commit: CommitInfo{commitHash: "3M", parentHashes: []string{"2M", "1A"}}, Children: []string{}, Row: 0}, + {Commit: CommitInfo{commitHash: "1A", parentHashes: []string{"2M"}}, Children: []string{"3M"}, Row: 1}, + {Commit: CommitInfo{commitHash: "2M", parentHashes: []string{"1M"}}, Children: []string{"1A", "3M"}, Row: 2}, + {Commit: CommitInfo{commitHash: "1M", parentHashes: []string{}}, Children: []string{"2M"}, Row: 3}, + } + commitsMap = map[string]*commitInfoWithChildren{ + "1M": commits[3], + "2M": commits[2], + "1A": commits[1], + "3M": commits[0], + } + result, _ = computeColumnEnds(commits, commitsMap) + require.Equal(t, 0, result[0].Col) + require.Equal(t, 1, result[1].Col) + require.Equal(t, 0, result[2].Col) + require.Equal(t, 0, result[3].Col) + +} + +func TestDrawCommitDotsAndBranchPaths(t *testing.T) { + // Test with two commits, one parent and one child + commits := []*commitInfoWithChildren{ + {Commit: CommitInfo{commitHash: "hash1", parentHashes: []string{"hash2"}, commitMeta: &datas.CommitMeta{Description: "Commit 1"}}, Children: []string{}, Row: 0}, + {Commit: CommitInfo{commitHash: "hash2", parentHashes: []string{}, commitMeta: &datas.CommitMeta{Description: "Commit 2"}}, Children: []string{"hash1"}, Row: 1}, + } + commitsMap := map[string]*commitInfoWithChildren{ + "hash1": commits[0], + "hash2": commits[1], + } + + commits, commitsMap = computeColumnEnds(commits, commitsMap) + expandGraph(commits) + + graph := drawCommitDotsAndBranchPaths(commits, commitsMap) + + require.Equal(t, "\x1b[37m*", graph[0][0]) + require.Equal(t, "\x1b[31m|", graph[1][0]) + require.Equal(t, "\x1b[31m|", graph[2][0]) + require.Equal(t, "\x1b[31m|", graph[3][0]) + require.Equal(t, "\x1b[31m|", graph[4][0]) + require.Equal(t, "\x1b[31m|", graph[5][0]) + require.Equal(t, "\x1b[37m*", graph[6][0]) + +} + +func TestExpandGraph(t *testing.T) { + commits := []*commitInfoWithChildren{ + { + Commit: CommitInfo{ + commitMeta: &datas.CommitMeta{ + Description: "This is a longer commit message\nthat spans multiple lines\nfor testing purposes", + }, + }, + Col: 0, + Row: 0, + }, + { + Commit: CommitInfo{ + commitMeta: &datas.CommitMeta{ + Description: "Short commit message", + }, + }, + Col: 1, + Row: 1, + }, + } + expandGraph(commits) + require.Equal(t, 0, commits[0].Col) + require.Equal(t, 0, commits[0].Row) + require.Equal(t, 3, len(commits[0].formattedMessage)) + require.Equal(t, 2, commits[1].Col) + require.Equal(t, 8, commits[1].Row) + require.Equal(t, 1, len(commits[1].formattedMessage)) +} diff --git a/integration-tests/bats/log.bats b/integration-tests/bats/log.bats index d5f6a2e4f76..e276e646a0d 100755 --- a/integration-tests/bats/log.bats +++ b/integration-tests/bats/log.bats @@ -903,4 +903,106 @@ remove_color_codes() { [[ $(remove_color_codes "${lines[26]}") =~ "Author:" ]] || false # Author: [[ $(remove_color_codes "${lines[27]}") =~ "Date:" ]] || false # Date: [[ $(remove_color_codes "${lines[28]}") =~ "Initialize data repository" ]] || false # Initialize data repository +} + + +@test "log: --graph: graph with multiple branches" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi + + dolt sql -q "create table testtable (pk int PRIMARY KEY)" + dolt add . + dolt commit -m "commit 1 MAIN" + dolt checkout -b branchA + dolt commit --allow-empty -m "commit 1 BRANCHA" + dolt checkout main + dolt checkout -b branchB + dolt commit --allow-empty -m "commit 1 branchB" + dolt checkout main + dolt checkout -b branchC + dolt commit --allow-empty -m "commit 1 branchC" + dolt checkout main + dolt checkout -b branchD + dolt commit --allow-empty -m "commit 1 branchD" + dolt checkout main + dolt sql -q "insert into testtable values (1)" + dolt commit -Am "insert into testtable" + dolt merge branchA -m "Merge branchA into main" + dolt merge branchB -m "Merge branchB into main" + dolt merge branchC -m "Merge branchC into main" + dolt merge branchD -m "Merge branchD into main" + + run dolt log --graph + [ "$status" -eq 0 ] + + # Check the output with patterns + [[ $(remove_color_codes "${lines[0]}") =~ "* commit" ]] || false + [[ $(remove_color_codes "${lines[1]}") =~ "|\ Merge:" ]] || false + [[ $(remove_color_codes "${lines[2]}") =~ "| | Author:" ]] || false + [[ $(remove_color_codes "${lines[3]}") =~ "| | Date:" ]] || false + [[ $(remove_color_codes "${lines[4]}") =~ "| |" ]] || false + [[ $(remove_color_codes "${lines[5]}") =~ "| |" ]] || false + [[ $(remove_color_codes "${lines[6]}") =~ "| |" ]] || false + [[ $(remove_color_codes "${lines[7]}") =~ "* | commit " ]] || false + [[ $(remove_color_codes "${lines[8]}") =~ "|\| Merge:" ]] || false + [[ $(remove_color_codes "${lines[9]}") =~ "| \ Author:" ]] || false + [[ $(remove_color_codes "${lines[10]}") =~ "| |\ Date:" ]] || false + [[ $(remove_color_codes "${lines[11]}") =~ "| | |" ]] || false + [[ $(remove_color_codes "${lines[12]}") =~ "| | |" ]] || false + [[ $(remove_color_codes "${lines[13]}") =~ "| | |" ]] || false + [[ $(remove_color_codes "${lines[14]}") =~ "* | | commit " ]] || false + [[ $(remove_color_codes "${lines[15]}") =~ "|\| | Merge:" ]] || false + [[ $(remove_color_codes "${lines[16]}") =~ "| \ | Author:" ]] || false + [[ $(remove_color_codes "${lines[17]}") =~ "| |\| Date:" ]] || false + [[ $(remove_color_codes "${lines[18]}") =~ '| | \' ]] || false + [[ $(remove_color_codes "${lines[19]}") =~ "| | |\ " ]] || false + [[ $(remove_color_codes "${lines[20]}") =~ "| | | |" ]] || false + [[ $(remove_color_codes "${lines[21]}") =~ "* | | | commit " ]] || false + [[ $(remove_color_codes "${lines[22]}") =~ "|\| | | Merge:" ]] || false + [[ $(remove_color_codes "${lines[23]}") =~ "| \ | | Author:" ]] || false + [[ $(remove_color_codes "${lines[24]}") =~ "| |\| | Date:" ]] || false + [[ $(remove_color_codes "${lines[25]}") =~ "| | \ |" ]] || false + [[ $(remove_color_codes "${lines[26]}") =~ "| | |\|" ]] || false + [[ $(remove_color_codes "${lines[27]}") =~ '| | | \' ]] || false + [[ $(remove_color_codes "${lines[28]}") =~ "* | | |\ commit " ]] || false + [[ $(remove_color_codes "${lines[29]}") =~ "| | | | | Author:" ]] || false + [[ $(remove_color_codes "${lines[30]}") =~ "| | | | | Date:" ]] || false + [[ $(remove_color_codes "${lines[31]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[32]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[33]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[34]}") =~ "| * | | | commit " ]] || false + [[ $(remove_color_codes "${lines[35]}") =~ "| | | | | Author:" ]] || false + [[ $(remove_color_codes "${lines[36]}") =~ "| | | | | Date:" ]] || false + [[ $(remove_color_codes "${lines[37]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[38]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[39]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[40]}") =~ "| | * | | commit" ]] || false + [[ $(remove_color_codes "${lines[41]}") =~ "| | | | | Author:" ]] || false + [[ $(remove_color_codes "${lines[42]}") =~ "| | | | | Date:" ]] || false + [[ $(remove_color_codes "${lines[43]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[44]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[45]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[46]}") =~ "| | | * | commit " ]] || false + [[ $(remove_color_codes "${lines[47]}") =~ "| | | | | Author:" ]] || false + [[ $(remove_color_codes "${lines[48]}") =~ "| | | | | Date:" ]] || false + [[ $(remove_color_codes "${lines[49]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[50]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[51]}") =~ "| | | | |" ]] || false + [[ $(remove_color_codes "${lines[52]}") =~ "| | | | * commit" ]] || false + [[ $(remove_color_codes "${lines[53]}") =~ "| | |/ / Author:" ]] || false + [[ $(remove_color_codes "${lines[54]}") =~ "| | / / Date:" ]] || false + [[ $(remove_color_codes "${lines[55]}") =~ "| |/ /" ]] || false + [[ $(remove_color_codes "${lines[56]}") =~ "| / /" ]] || false + [[ $(remove_color_codes "${lines[57]}") =~ "|/ /" ]] || false + [[ $(remove_color_codes "${lines[58]}") =~ "*-- commit " ]] || false + [[ $(remove_color_codes "${lines[59]}") =~ "| Author:" ]] || false + [[ $(remove_color_codes "${lines[60]}") =~ "| Date:" ]] || false + [[ $(remove_color_codes "${lines[61]}") =~ "|" ]] || false + [[ $(remove_color_codes "${lines[62]}") =~ "|" ]] || false + [[ $(remove_color_codes "${lines[63]}") =~ "|" ]] || false + [[ $(remove_color_codes "${lines[64]}") =~ "* commit " ]] || false + [[ $(remove_color_codes "${lines[65]}") =~ "Author:" ]] || false + [[ $(remove_color_codes "${lines[66]}") =~ "Date:" ]] || false + [[ $(remove_color_codes "${lines[67]}") =~ "Initialize data repository" ]] || false } \ No newline at end of file From 1d125b2d6cee77ed6e09240a3b64f01cf9a2d01c Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Fri, 2 Aug 2024 09:40:15 -0700 Subject: [PATCH 17/19] copyright header --- go/cmd/dolt/commands/log_graph_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/go/cmd/dolt/commands/log_graph_test.go b/go/cmd/dolt/commands/log_graph_test.go index a0440e928f4..6bf35a2b634 100644 --- a/go/cmd/dolt/commands/log_graph_test.go +++ b/go/cmd/dolt/commands/log_graph_test.go @@ -1,3 +1,17 @@ +// Copyright 2019 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package commands import ( From 2382bb3789447bb976fb96bec04eb41f55584482 Mon Sep 17 00:00:00 2001 From: LiuLiu Date: Fri, 2 Aug 2024 09:44:50 -0700 Subject: [PATCH 18/19] function names in description --- go/cmd/dolt/commands/log_graph.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go/cmd/dolt/commands/log_graph.go b/go/cmd/dolt/commands/log_graph.go index 4d26a4e3e11..983f1bdd1e4 100644 --- a/go/cmd/dolt/commands/log_graph.go +++ b/go/cmd/dolt/commands/log_graph.go @@ -34,7 +34,7 @@ import ( * - Calculate the positions of the commits in the graph. * The vertical position of each commit is determined by the order of the commits, but should be adjusted to the length of the commit message. * -* The calculation of horizontal position is more complex, but mostly depends on the parent-child relationship of the commits. +* The calculation of horizontal position is more complex, but mostly depends on the parent-child relationship of the commits. This is done in the function computeColumnEnds. * * Create a 2D matrix to store the branch paths, this matrix will help us find the available column for the commits. * In each column, there will be multiple branch paths, and each path is represented by a pair of positions of start and end commits on the branch. @@ -58,7 +58,9 @@ import ( * - In the last example, when determining where to put the commit on branch B, we should have the columns matrix as: [[[0,6]], [[2,2]]]. Potential column index options for this commit is 0, 1, or we can create a new column: 2. * - Look at the children of this current commit, which is the merge commit and it's on column 0. So we can start from column 1,and since the path in column 1 ends at row 2, this commits can be put in column 1. * -* - Draw the graph. +* After both the vertical and horizontal positions are calculated, we will expand the graph based on the length of the commit message, this part is done in the function expandGraph. +* +* - Draw the graph. The graph is drawn in function drawCommitDotsAndBranchPaths. * Once we have the positions of the commits, we can draw the graph. * For each commit, draw the commit and the path to its parent(s). * Same as the calculation part, we have 3 types of paths: From eed2184712957aa4b68612ef04ecbedc0bc96e80 Mon Sep 17 00:00:00 2001 From: liuliu-dev Date: Fri, 2 Aug 2024 16:53:32 +0000 Subject: [PATCH 19/19] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/cmd/dolt/commands/log_graph_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/cmd/dolt/commands/log_graph_test.go b/go/cmd/dolt/commands/log_graph_test.go index 6bf35a2b634..a13ea4adff3 100644 --- a/go/cmd/dolt/commands/log_graph_test.go +++ b/go/cmd/dolt/commands/log_graph_test.go @@ -17,8 +17,9 @@ package commands import ( "testing" - "github.com/dolthub/dolt/go/store/datas" "github.com/stretchr/testify/require" + + "github.com/dolthub/dolt/go/store/datas" ) func TestMapCommitsWithChildrenAndPosition(t *testing.T) {