Skip to content

Commit 2c1ae6c

Browse files
zeripathsilverwind
andauthored
Render the git graph on the server (#12333)
Rendering the git graph on the server means that we can properly track flows and switch from the Canvas implementation to a SVG implementation. * This implementation provides a 16 limited color selection * The uniqued color numbers are also provided * And there is also a monochrome version *In addition is a hover highlight that allows users to highlight commits on the same flow. Closes #12209 Signed-off-by: Andrew Thornton [email protected] Co-authored-by: silverwind <[email protected]>
1 parent f1a42f5 commit 2c1ae6c

File tree

19 files changed

+1666
-696
lines changed

19 files changed

+1666
-696
lines changed

.stylelintrc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
extends: stylelint-config-standard
22

3-
ignoreFiles:
4-
- web_src/less/vendor/**/*
5-
63
rules:
74
at-rule-empty-line-before: null
85
block-closing-brace-empty-line-before: null

modules/gitgraph/graph.go

Lines changed: 31 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,9 @@ import (
1616
"code.gitea.io/gitea/modules/setting"
1717
)
1818

19-
// GraphItem represent one commit, or one relation in timeline
20-
type GraphItem struct {
21-
GraphAcii string
22-
Relation string
23-
Branch string
24-
Rev string
25-
Date string
26-
Author string
27-
AuthorEmail string
28-
ShortRev string
29-
Subject string
30-
OnlyRelation bool
31-
}
32-
33-
// GraphItems is a list of commits from all branches
34-
type GraphItems []GraphItem
35-
3619
// GetCommitGraph return a list of commit (GraphItems) from all branches
37-
func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
38-
format := "DATA:|%d|%H|%ad|%an|%ae|%h|%s"
20+
func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int) (*Graph, error) {
21+
format := "DATA:%d|%H|%ad|%an|%ae|%h|%s"
3922

4023
if page == 0 {
4124
page = 1
@@ -51,7 +34,8 @@ func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
5134
"--date=iso",
5235
fmt.Sprintf("--pretty=format:%s", format),
5336
)
54-
commitGraph := make([]GraphItem, 0, 100)
37+
graph := NewGraph()
38+
5539
stderr := new(strings.Builder)
5640
stdoutReader, stdoutWriter, err := os.Pipe()
5741
if err != nil {
@@ -64,86 +48,56 @@ func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
6448
if err := graphCmd.RunInDirTimeoutEnvFullPipelineFunc(nil, -1, r.Path, stdoutWriter, stderr, nil, func(ctx context.Context, cancel context.CancelFunc) error {
6549
_ = stdoutWriter.Close()
6650
defer stdoutReader.Close()
51+
parser := &Parser{}
52+
parser.firstInUse = -1
53+
parser.maxAllowedColors = maxAllowedColors
54+
if maxAllowedColors > 0 {
55+
parser.availableColors = make([]int, maxAllowedColors)
56+
for i := range parser.availableColors {
57+
parser.availableColors[i] = i + 1
58+
}
59+
} else {
60+
parser.availableColors = []int{1, 2}
61+
}
6762
for commitsToSkip > 0 && scanner.Scan() {
6863
line := scanner.Bytes()
6964
dataIdx := bytes.Index(line, []byte("DATA:"))
65+
if dataIdx < 0 {
66+
dataIdx = len(line)
67+
}
7068
starIdx := bytes.IndexByte(line, '*')
7169
if starIdx >= 0 && starIdx < dataIdx {
7270
commitsToSkip--
7371
}
72+
parser.ParseGlyphs(line[:dataIdx])
7473
}
74+
75+
row := 0
76+
7577
// Skip initial non-commit lines
7678
for scanner.Scan() {
77-
if bytes.IndexByte(scanner.Bytes(), '*') >= 0 {
78-
line := scanner.Text()
79-
graphItem, err := graphItemFromString(line, r)
80-
if err != nil {
79+
line := scanner.Bytes()
80+
if bytes.IndexByte(line, '*') >= 0 {
81+
if err := parser.AddLineToGraph(graph, row, line); err != nil {
8182
cancel()
8283
return err
8384
}
84-
commitGraph = append(commitGraph, graphItem)
8585
break
8686
}
87+
parser.ParseGlyphs(line)
8788
}
8889

8990
for scanner.Scan() {
90-
line := scanner.Text()
91-
graphItem, err := graphItemFromString(line, r)
92-
if err != nil {
91+
row++
92+
line := scanner.Bytes()
93+
if err := parser.AddLineToGraph(graph, row, line); err != nil {
9394
cancel()
9495
return err
9596
}
96-
commitGraph = append(commitGraph, graphItem)
9797
}
9898
return scanner.Err()
9999
}); err != nil {
100-
return commitGraph, err
101-
}
102-
103-
return commitGraph, nil
104-
}
105-
106-
func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
107-
108-
var ascii string
109-
var data = "|||||||"
110-
lines := strings.SplitN(s, "DATA:", 2)
111-
112-
switch len(lines) {
113-
case 1:
114-
ascii = lines[0]
115-
case 2:
116-
ascii = lines[0]
117-
data = lines[1]
118-
default:
119-
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s. Expect 1 or two fields", s)
120-
}
121-
122-
rows := strings.SplitN(data, "|", 8)
123-
if len(rows) < 8 {
124-
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s - Should containt 8 datafields", s)
125-
}
126-
127-
/* // see format in getCommitGraph()
128-
0 Relation string
129-
1 Branch string
130-
2 Rev string
131-
3 Date string
132-
4 Author string
133-
5 AuthorEmail string
134-
6 ShortRev string
135-
7 Subject string
136-
*/
137-
gi := GraphItem{ascii,
138-
rows[0],
139-
rows[1],
140-
rows[2],
141-
rows[3],
142-
rows[4],
143-
rows[5],
144-
rows[6],
145-
rows[7],
146-
len(rows[2]) == 0, // no commits referred to, only relation in current line.
100+
return graph, err
147101
}
148-
return gi, nil
102+
return graph, nil
149103
}

modules/gitgraph/graph_models.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package gitgraph
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
)
11+
12+
// NewGraph creates a basic graph
13+
func NewGraph() *Graph {
14+
graph := &Graph{}
15+
graph.relationCommit = &Commit{
16+
Row: -1,
17+
Column: -1,
18+
}
19+
graph.Flows = map[int64]*Flow{}
20+
return graph
21+
}
22+
23+
// Graph represents a collection of flows
24+
type Graph struct {
25+
Flows map[int64]*Flow
26+
Commits []*Commit
27+
MinRow int
28+
MinColumn int
29+
MaxRow int
30+
MaxColumn int
31+
relationCommit *Commit
32+
}
33+
34+
// Width returns the width of the graph
35+
func (graph *Graph) Width() int {
36+
return graph.MaxColumn - graph.MinColumn + 1
37+
}
38+
39+
// Height returns the height of the graph
40+
func (graph *Graph) Height() int {
41+
return graph.MaxRow - graph.MinRow + 1
42+
}
43+
44+
// AddGlyph adds glyph to flows
45+
func (graph *Graph) AddGlyph(row, column int, flowID int64, color int, glyph byte) {
46+
flow, ok := graph.Flows[flowID]
47+
if !ok {
48+
flow = NewFlow(flowID, color, row, column)
49+
graph.Flows[flowID] = flow
50+
}
51+
flow.AddGlyph(row, column, glyph)
52+
53+
if row < graph.MinRow {
54+
graph.MinRow = row
55+
}
56+
if row > graph.MaxRow {
57+
graph.MaxRow = row
58+
}
59+
if column < graph.MinColumn {
60+
graph.MinColumn = column
61+
}
62+
if column > graph.MaxColumn {
63+
graph.MaxColumn = column
64+
}
65+
}
66+
67+
// AddCommit adds a commit at row, column on flowID with the provided data
68+
func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error {
69+
commit, err := NewCommit(row, column, data)
70+
if err != nil {
71+
return err
72+
}
73+
commit.Flow = flowID
74+
graph.Commits = append(graph.Commits, commit)
75+
76+
graph.Flows[flowID].Commits = append(graph.Flows[flowID].Commits, commit)
77+
return nil
78+
}
79+
80+
// NewFlow creates a new flow
81+
func NewFlow(flowID int64, color, row, column int) *Flow {
82+
return &Flow{
83+
ID: flowID,
84+
ColorNumber: color,
85+
MinRow: row,
86+
MinColumn: column,
87+
MaxRow: row,
88+
MaxColumn: column,
89+
}
90+
}
91+
92+
// Flow represents a series of glyphs
93+
type Flow struct {
94+
ID int64
95+
ColorNumber int
96+
Glyphs []Glyph
97+
Commits []*Commit
98+
MinRow int
99+
MinColumn int
100+
MaxRow int
101+
MaxColumn int
102+
}
103+
104+
// Color16 wraps the color numbers around mod 16
105+
func (flow *Flow) Color16() int {
106+
return flow.ColorNumber % 16
107+
}
108+
109+
// AddGlyph adds glyph at row and column
110+
func (flow *Flow) AddGlyph(row, column int, glyph byte) {
111+
if row < flow.MinRow {
112+
flow.MinRow = row
113+
}
114+
if row > flow.MaxRow {
115+
flow.MaxRow = row
116+
}
117+
if column < flow.MinColumn {
118+
flow.MinColumn = column
119+
}
120+
if column > flow.MaxColumn {
121+
flow.MaxColumn = column
122+
}
123+
124+
flow.Glyphs = append(flow.Glyphs, Glyph{
125+
row,
126+
column,
127+
glyph,
128+
})
129+
}
130+
131+
// Glyph represents a co-ordinate and glyph
132+
type Glyph struct {
133+
Row int
134+
Column int
135+
Glyph byte
136+
}
137+
138+
// RelationCommit represents an empty relation commit
139+
var RelationCommit = &Commit{
140+
Row: -1,
141+
}
142+
143+
// NewCommit creates a new commit from a provided line
144+
func NewCommit(row, column int, line []byte) (*Commit, error) {
145+
data := bytes.SplitN(line, []byte("|"), 7)
146+
if len(data) < 7 {
147+
return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line))
148+
}
149+
return &Commit{
150+
Row: row,
151+
Column: column,
152+
// 0 matches git log --pretty=format:%d => ref names, like the --decorate option of git-log(1)
153+
Branch: string(data[0]),
154+
// 1 matches git log --pretty=format:%H => commit hash
155+
Rev: string(data[1]),
156+
// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
157+
Date: string(data[2]),
158+
// 3 matches git log --pretty=format:%an => author name
159+
Author: string(data[3]),
160+
// 4 matches git log --pretty=format:%ae => author email
161+
AuthorEmail: string(data[4]),
162+
// 5 matches git log --pretty=format:%h => abbreviated commit hash
163+
ShortRev: string(data[5]),
164+
// 6 matches git log --pretty=format:%s => subject
165+
Subject: string(data[6]),
166+
}, nil
167+
}
168+
169+
// Commit represents a commit at co-ordinate X, Y with the data
170+
type Commit struct {
171+
Flow int64
172+
Row int
173+
Column int
174+
Branch string
175+
Rev string
176+
Date string
177+
Author string
178+
AuthorEmail string
179+
ShortRev string
180+
Subject string
181+
}
182+
183+
// OnlyRelation returns whether this a relation only commit
184+
func (c *Commit) OnlyRelation() bool {
185+
return c.Row == -1
186+
}

0 commit comments

Comments
 (0)