Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render the git graph on the server #12333

Merged
merged 14 commits into from
Aug 6, 2020
Merged
3 changes: 0 additions & 3 deletions .stylelintrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
extends: stylelint-config-standard

ignoreFiles:
- web_src/less/vendor/**/*

rules:
at-rule-empty-line-before: null
block-closing-brace-empty-line-before: null
Expand Down
108 changes: 31 additions & 77 deletions modules/gitgraph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,9 @@ import (
"code.gitea.io/gitea/modules/setting"
)

// GraphItem represent one commit, or one relation in timeline
type GraphItem struct {
GraphAcii string
Relation string
Branch string
Rev string
Date string
Author string
AuthorEmail string
ShortRev string
Subject string
OnlyRelation bool
}

// GraphItems is a list of commits from all branches
type GraphItems []GraphItem

// GetCommitGraph return a list of commit (GraphItems) from all branches
func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
format := "DATA:|%d|%H|%ad|%an|%ae|%h|%s"
func GetCommitGraph(r *git.Repository, page int, maxAllowedColors int) (*Graph, error) {
format := "DATA:%d|%H|%ad|%an|%ae|%h|%s"

if page == 0 {
page = 1
Expand All @@ -51,7 +34,8 @@ func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
"--date=iso",
fmt.Sprintf("--pretty=format:%s", format),
)
commitGraph := make([]GraphItem, 0, 100)
graph := NewGraph()

stderr := new(strings.Builder)
stdoutReader, stdoutWriter, err := os.Pipe()
if err != nil {
Expand All @@ -64,86 +48,56 @@ func GetCommitGraph(r *git.Repository, page int) (GraphItems, error) {
if err := graphCmd.RunInDirTimeoutEnvFullPipelineFunc(nil, -1, r.Path, stdoutWriter, stderr, nil, 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() {
if bytes.IndexByte(scanner.Bytes(), '*') >= 0 {
line := scanner.Text()
graphItem, err := graphItemFromString(line, r)
if err != nil {
line := scanner.Bytes()
if bytes.IndexByte(line, '*') >= 0 {
if err := parser.AddLineToGraph(graph, row, line); err != nil {
cancel()
return err
}
commitGraph = append(commitGraph, graphItem)
break
}
parser.ParseGlyphs(line)
}

for scanner.Scan() {
line := scanner.Text()
graphItem, err := graphItemFromString(line, r)
if err != nil {
row++
line := scanner.Bytes()
if err := parser.AddLineToGraph(graph, row, line); err != nil {
cancel()
return err
}
commitGraph = append(commitGraph, graphItem)
}
return scanner.Err()
}); err != nil {
return commitGraph, err
}

return commitGraph, nil
}

func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {

var ascii string
var data = "|||||||"
lines := strings.SplitN(s, "DATA:", 2)

switch len(lines) {
case 1:
ascii = lines[0]
case 2:
ascii = lines[0]
data = lines[1]
default:
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s. Expect 1 or two fields", s)
}

rows := strings.SplitN(data, "|", 8)
if len(rows) < 8 {
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s - Should containt 8 datafields", s)
}

/* // see format in getCommitGraph()
0 Relation string
1 Branch string
2 Rev string
3 Date string
4 Author string
5 AuthorEmail string
6 ShortRev string
7 Subject string
*/
gi := GraphItem{ascii,
rows[0],
rows[1],
rows[2],
rows[3],
rows[4],
rows[5],
rows[6],
rows[7],
len(rows[2]) == 0, // no commits referred to, only relation in current line.
return graph, err
}
return gi, nil
return graph, nil
}
186 changes: 186 additions & 0 deletions modules/gitgraph/graph_models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package gitgraph

import (
"bytes"
"fmt"
)

// NewGraph creates a basic graph
func NewGraph() *Graph {
graph := &Graph{}
graph.relationCommit = &Commit{
Row: -1,
Column: -1,
}
graph.Flows = map[int64]*Flow{}
return graph
}

// Graph represents a collection of flows
type Graph struct {
Flows map[int64]*Flow
Commits []*Commit
MinRow int
MinColumn int
MaxRow int
MaxColumn int
relationCommit *Commit
}

// Width returns the width of the graph
func (graph *Graph) Width() int {
return graph.MaxColumn - graph.MinColumn + 1
}

// Height returns the height of the graph
func (graph *Graph) Height() int {
return graph.MaxRow - graph.MinRow + 1
}

// AddGlyph adds glyph to flows
func (graph *Graph) AddGlyph(row, column int, flowID int64, color int, glyph byte) {
flow, ok := graph.Flows[flowID]
if !ok {
flow = NewFlow(flowID, color, row, column)
graph.Flows[flowID] = flow
}
flow.AddGlyph(row, column, glyph)

if row < graph.MinRow {
graph.MinRow = row
}
if row > graph.MaxRow {
graph.MaxRow = row
}
if column < graph.MinColumn {
graph.MinColumn = column
}
if column > graph.MaxColumn {
graph.MaxColumn = column
}
}

// AddCommit adds a commit at row, column on flowID with the provided data
func (graph *Graph) AddCommit(row, column int, flowID int64, data []byte) error {
commit, err := NewCommit(row, column, data)
if err != nil {
return err
}
commit.Flow = flowID
graph.Commits = append(graph.Commits, commit)

graph.Flows[flowID].Commits = append(graph.Flows[flowID].Commits, commit)
return nil
}

// NewFlow creates a new flow
func NewFlow(flowID int64, color, row, column int) *Flow {
return &Flow{
ID: flowID,
ColorNumber: color,
MinRow: row,
MinColumn: column,
MaxRow: row,
MaxColumn: column,
}
}

// Flow represents a series of glyphs
type Flow struct {
ID int64
ColorNumber int
Glyphs []Glyph
Commits []*Commit
MinRow int
MinColumn int
MaxRow int
MaxColumn int
}

// Color16 wraps the color numbers around mod 16
func (flow *Flow) Color16() int {
return flow.ColorNumber % 16
}

// AddGlyph adds glyph at row and column
func (flow *Flow) AddGlyph(row, column int, glyph byte) {
if row < flow.MinRow {
flow.MinRow = row
}
if row > flow.MaxRow {
flow.MaxRow = row
}
if column < flow.MinColumn {
flow.MinColumn = column
}
if column > flow.MaxColumn {
flow.MaxColumn = column
}

flow.Glyphs = append(flow.Glyphs, Glyph{
row,
column,
glyph,
})
}

// Glyph represents a co-ordinate and glyph
type Glyph struct {
Row int
Column int
Glyph byte
}

// RelationCommit represents an empty relation commit
var RelationCommit = &Commit{
Row: -1,
}

// NewCommit creates a new commit from a provided line
func NewCommit(row, column int, line []byte) (*Commit, error) {
data := bytes.SplitN(line, []byte("|"), 7)
if len(data) < 7 {
return nil, fmt.Errorf("malformed data section on line %d with commit: %s", row, string(line))
}
return &Commit{
Row: row,
Column: column,
// 0 matches git log --pretty=format:%d => ref names, like the --decorate option of git-log(1)
Branch: string(data[0]),
// 1 matches git log --pretty=format:%H => commit hash
Rev: string(data[1]),
// 2 matches git log --pretty=format:%ad => author date (format respects --date= option)
Date: string(data[2]),
// 3 matches git log --pretty=format:%an => author name
Author: string(data[3]),
// 4 matches git log --pretty=format:%ae => author email
AuthorEmail: string(data[4]),
// 5 matches git log --pretty=format:%h => abbreviated commit hash
ShortRev: string(data[5]),
// 6 matches git log --pretty=format:%s => subject
Subject: string(data[6]),
}, nil
}

// Commit represents a commit at co-ordinate X, Y with the data
type Commit struct {
Flow int64
Row int
Column int
Branch string
Rev string
Date string
Author string
AuthorEmail string
ShortRev string
Subject string
}

// OnlyRelation returns whether this a relation only commit
func (c *Commit) OnlyRelation() bool {
return c.Row == -1
}
Loading