forgejo/services/repository/gitgraph/graph.go
Bram Hagens 60fb59a7a0
Some checks are pending
Integration tests for the release process / release-simulation (push) Waiting to run
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
fix: display orphan branches separately in commit graph (#10484)
Fixes #2705.
Fixes #7635.

This PR fixes the commit graph showing false connections for orphan/root commits. Connection lines are now shown only when a parent/child relationship exists. Visible relationships are determined using `git log`'s `%P` output by the new `ComputeGlyphConnectivity` function. The SVG template is adapted to render vertical lines conditionally.

Unit tests for `ComputeGlyphConnectivity` cover regular linear commit history, orphan commits, merge commits, and non-commit glyphs (`|`, `/`, `\`). Unit tests also cover the changes to the `git log` parsing. The SVG template was verified manually.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10484
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Bram Hagens <bram@bramh.me>
Co-committed-by: Bram Hagens <bram@bramh.me>
2025-12-29 02:53:54 +01:00

119 lines
2.7 KiB
Go

// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gitgraph
import (
"bufio"
"bytes"
"context"
"os"
"strings"
"forgejo.org/modules/git"
"forgejo.org/modules/setting"
)
// GetCommitGraph return a list of commit (GraphItems) from all branches
func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bool, branches, files []string) (*Graph, error) {
format := "DATA:%P|%D|%H|%aD|%h|%s"
if page == 0 {
page = 1
}
graphCmd := git.NewCommand(r.Ctx, "log", "--graph", "--date-order", "--decorate=full")
if hidePRRefs {
graphCmd.AddArguments("--exclude=" + git.PullPrefix + "*")
}
if len(branches) == 0 {
graphCmd.AddArguments("--all")
}
graphCmd.AddArguments("-C", "-M", "--date=iso").
AddOptionFormat("-n %d", setting.UI.GraphMaxCommitNum*page).
AddOptionFormat("--pretty=format:%s", format)
if len(branches) > 0 {
graphCmd.AddDynamicArguments(branches...)
}
if len(files) > 0 {
graphCmd.AddDashesAndList(files...)
}
graph := NewGraph()
stderr := new(strings.Builder)
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
return nil, err
}
commitsToSkip := setting.UI.GraphMaxCommitNum * (page - 1)
scanner := bufio.NewScanner(stdoutReader)
if err := graphCmd.Run(&git.RunOpts{
Dir: r.Path,
Stdout: stdoutWriter,
Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
defer stdoutReader.Close()
parser := &Parser{}
parser.firstInUse = -1
parser.maxAllowedColors = maxAllowedColors
if maxAllowedColors > 0 {
parser.availableColors = make([]int, maxAllowedColors)
for i := range parser.availableColors {
parser.availableColors[i] = i + 1
}
} else {
parser.availableColors = []int{1, 2}
}
for commitsToSkip > 0 && scanner.Scan() {
line := scanner.Bytes()
dataIdx := bytes.Index(line, []byte("DATA:"))
if dataIdx < 0 {
dataIdx = len(line)
}
starIdx := bytes.IndexByte(line, '*')
if starIdx >= 0 && starIdx < dataIdx {
commitsToSkip--
}
parser.ParseGlyphs(line[:dataIdx])
}
row := 0
// Skip initial non-commit lines
for scanner.Scan() {
line := scanner.Bytes()
if bytes.IndexByte(line, '*') >= 0 {
if err := parser.AddLineToGraph(graph, row, line); err != nil {
cancel()
return err
}
break
}
parser.ParseGlyphs(line)
}
for scanner.Scan() {
row++
line := scanner.Bytes()
if err := parser.AddLineToGraph(graph, row, line); err != nil {
cancel()
return err
}
}
return scanner.Err()
},
}); err != nil {
return graph, err
}
graph.ComputeGlyphConnectivity()
return graph, nil
}