mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	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 art27@cantab.net Co-authored-by: silverwind <me@silverwind.io>
		
			
				
	
	
		
			187 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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
 | 
						|
}
 |