123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- // 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"
- )
-
- // Parser represents a git graph parser. It is stateful containing the previous
- // glyphs, detected flows and color assignments.
- type Parser struct {
- glyphs []byte
- oldGlyphs []byte
- flows []int64
- oldFlows []int64
- maxFlow int64
- colors []int
- oldColors []int
- availableColors []int
- nextAvailable int
- firstInUse int
- firstAvailable int
- maxAllowedColors int
- }
-
- // Reset resets the internal parser state.
- func (parser *Parser) Reset() {
- parser.glyphs = parser.glyphs[0:0]
- parser.oldGlyphs = parser.oldGlyphs[0:0]
- parser.flows = parser.flows[0:0]
- parser.oldFlows = parser.oldFlows[0:0]
- parser.maxFlow = 0
- parser.colors = parser.colors[0:0]
- parser.oldColors = parser.oldColors[0:0]
- parser.availableColors = parser.availableColors[0:0]
- parser.availableColors = append(parser.availableColors, 1, 2)
- parser.nextAvailable = 0
- parser.firstInUse = -1
- parser.firstAvailable = 0
- parser.maxAllowedColors = 0
- }
-
- // AddLineToGraph adds the line as a row to the graph
- func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error {
- idx := bytes.Index(line, []byte("DATA:"))
- if idx < 0 {
- parser.ParseGlyphs(line)
- } else {
- parser.ParseGlyphs(line[:idx])
- }
-
- var err error
- commitDone := false
-
- for column, glyph := range parser.glyphs {
- if glyph == ' ' {
- continue
- }
-
- flowID := parser.flows[column]
-
- graph.AddGlyph(row, column, flowID, parser.colors[column], glyph)
-
- if glyph == '*' {
- if commitDone {
- if err != nil {
- err = fmt.Errorf("double commit on line %d: %s. %w", row, string(line), err)
- } else {
- err = fmt.Errorf("double commit on line %d: %s", row, string(line))
- }
- }
- commitDone = true
- if idx < 0 {
- if err != nil {
- err = fmt.Errorf("missing data section on line %d with commit: %s. %w", row, string(line), err)
- } else {
- err = fmt.Errorf("missing data section on line %d with commit: %s", row, string(line))
- }
- continue
- }
- err2 := graph.AddCommit(row, column, flowID, line[idx+5:])
- if err != nil && err2 != nil {
- err = fmt.Errorf("%v %w", err2, err)
- continue
- } else if err2 != nil {
- err = err2
- continue
- }
- }
- }
- if !commitDone {
- graph.Commits = append(graph.Commits, RelationCommit)
- }
- return err
- }
-
- func (parser *Parser) releaseUnusedColors() {
- if parser.firstInUse > -1 {
- // Here we step through the old colors, searching for them in the
- // "in-use" section of availableColors (that is, the colors between
- // firstInUse and firstAvailable)
- // Ensure that the benchmarks are not worsened with proposed changes
- stepstaken := 0
- position := parser.firstInUse
- for _, color := range parser.oldColors {
- if color == 0 {
- continue
- }
- found := false
- i := position
- for j := stepstaken; i != parser.firstAvailable && j < len(parser.availableColors); j++ {
- colorToCheck := parser.availableColors[i]
- if colorToCheck == color {
- found = true
- break
- }
- i = (i + 1) % len(parser.availableColors)
- }
- if !found {
- // Duplicate color
- continue
- }
- // Swap them around
- parser.availableColors[position], parser.availableColors[i] = parser.availableColors[i], parser.availableColors[position]
- stepstaken++
- position = (parser.firstInUse + stepstaken) % len(parser.availableColors)
- if position == parser.firstAvailable || stepstaken == len(parser.availableColors) {
- break
- }
- }
- if stepstaken == len(parser.availableColors) {
- parser.firstAvailable = -1
- } else {
- parser.firstAvailable = position
- if parser.nextAvailable == -1 {
- parser.nextAvailable = parser.firstAvailable
- }
- }
- }
- }
-
- // ParseGlyphs parses the provided glyphs and sets the internal state
- func (parser *Parser) ParseGlyphs(glyphs []byte) {
-
- // Clean state for parsing this row
- parser.glyphs, parser.oldGlyphs = parser.oldGlyphs, parser.glyphs
- parser.glyphs = parser.glyphs[0:0]
- parser.flows, parser.oldFlows = parser.oldFlows, parser.flows
- parser.flows = parser.flows[0:0]
- parser.colors, parser.oldColors = parser.oldColors, parser.colors
-
- // Ensure we have enough flows and colors
- parser.colors = parser.colors[0:0]
- for range glyphs {
- parser.flows = append(parser.flows, 0)
- parser.colors = append(parser.colors, 0)
- }
-
- // Copy the provided glyphs in to state.glyphs for safekeeping
- parser.glyphs = append(parser.glyphs, glyphs...)
-
- // release unused colors
- parser.releaseUnusedColors()
-
- for i := len(glyphs) - 1; i >= 0; i-- {
- glyph := glyphs[i]
- switch glyph {
- case '|':
- fallthrough
- case '*':
- parser.setUpFlow(i)
- case '/':
- parser.setOutFlow(i)
- case '\\':
- parser.setInFlow(i)
- case '_':
- parser.setRightFlow(i)
- case '.':
- fallthrough
- case '-':
- parser.setLeftFlow(i)
- case ' ':
- // no-op
- default:
- parser.newFlow(i)
- }
- }
- }
-
- func (parser *Parser) takePreviousFlow(i, j int) {
- if j < len(parser.oldFlows) && parser.oldFlows[j] > 0 {
- parser.flows[i] = parser.oldFlows[j]
- parser.oldFlows[j] = 0
- parser.colors[i] = parser.oldColors[j]
- parser.oldColors[j] = 0
- } else {
- parser.newFlow(i)
- }
- }
-
- func (parser *Parser) takeCurrentFlow(i, j int) {
- if j < len(parser.flows) && parser.flows[j] > 0 {
- parser.flows[i] = parser.flows[j]
- parser.colors[i] = parser.colors[j]
- } else {
- parser.newFlow(i)
- }
- }
-
- func (parser *Parser) newFlow(i int) {
- parser.maxFlow++
- parser.flows[i] = parser.maxFlow
-
- // Now give this flow a color
- if parser.nextAvailable == -1 {
- next := len(parser.availableColors)
- if parser.maxAllowedColors < 1 || next < parser.maxAllowedColors {
- parser.nextAvailable = next
- parser.firstAvailable = next
- parser.availableColors = append(parser.availableColors, next+1)
- }
- }
- parser.colors[i] = parser.availableColors[parser.nextAvailable]
- if parser.firstInUse == -1 {
- parser.firstInUse = parser.nextAvailable
- }
- parser.availableColors[parser.firstAvailable], parser.availableColors[parser.nextAvailable] = parser.availableColors[parser.nextAvailable], parser.availableColors[parser.firstAvailable]
-
- parser.nextAvailable = (parser.nextAvailable + 1) % len(parser.availableColors)
- parser.firstAvailable = (parser.firstAvailable + 1) % len(parser.availableColors)
-
- if parser.nextAvailable == parser.firstInUse {
- parser.nextAvailable = parser.firstAvailable
- }
- if parser.nextAvailable == parser.firstInUse {
- parser.nextAvailable = -1
- parser.firstAvailable = -1
- }
- }
-
- // setUpFlow handles '|' or '*'
- func (parser *Parser) setUpFlow(i int) {
- // In preference order:
- //
- // Previous Row: '\? ' ' |' ' /'
- // Current Row: ' | ' ' |' ' | '
- if i > 0 && i-1 < len(parser.oldGlyphs) && parser.oldGlyphs[i-1] == '\\' {
- parser.takePreviousFlow(i, i-1)
- } else if i < len(parser.oldGlyphs) && (parser.oldGlyphs[i] == '|' || parser.oldGlyphs[i] == '*') {
- parser.takePreviousFlow(i, i)
- } else if i+1 < len(parser.oldGlyphs) && parser.oldGlyphs[i+1] == '/' {
- parser.takePreviousFlow(i, i+1)
- } else {
- parser.newFlow(i)
- }
- }
-
- // setOutFlow handles '/'
- func (parser *Parser) setOutFlow(i int) {
- // In preference order:
- //
- // Previous Row: ' |/' ' |_' ' |' ' /' ' _' '\'
- // Current Row: '/| ' '/| ' '/ ' '/ ' '/ ' '/'
- if i+2 < len(parser.oldGlyphs) &&
- (parser.oldGlyphs[i+1] == '|' || parser.oldGlyphs[i+1] == '*') &&
- (parser.oldGlyphs[i+2] == '/' || parser.oldGlyphs[i+2] == '_') &&
- i+1 < len(parser.glyphs) &&
- (parser.glyphs[i+1] == '|' || parser.glyphs[i+1] == '*') {
- parser.takePreviousFlow(i, i+2)
- } else if i+1 < len(parser.oldGlyphs) &&
- (parser.oldGlyphs[i+1] == '|' || parser.oldGlyphs[i+1] == '*' ||
- parser.oldGlyphs[i+1] == '/' || parser.oldGlyphs[i+1] == '_') {
- parser.takePreviousFlow(i, i+1)
- if parser.oldGlyphs[i+1] == '/' {
- parser.glyphs[i] = '|'
- }
- } else if i < len(parser.oldGlyphs) && parser.oldGlyphs[i] == '\\' {
- parser.takePreviousFlow(i, i)
- } else {
- parser.newFlow(i)
- }
- }
-
- // setInFlow handles '\'
- func (parser *Parser) setInFlow(i int) {
- // In preference order:
- //
- // Previous Row: '| ' '-. ' '| ' '\ ' '/' '---'
- // Current Row: '|\' ' \' ' \' ' \' '\' ' \ '
- if i > 0 && i-1 < len(parser.oldGlyphs) &&
- (parser.oldGlyphs[i-1] == '|' || parser.oldGlyphs[i-1] == '*') &&
- (parser.glyphs[i-1] == '|' || parser.glyphs[i-1] == '*') {
- parser.newFlow(i)
- } else if i > 0 && i-1 < len(parser.oldGlyphs) &&
- (parser.oldGlyphs[i-1] == '|' || parser.oldGlyphs[i-1] == '*' ||
- parser.oldGlyphs[i-1] == '.' || parser.oldGlyphs[i-1] == '\\') {
- parser.takePreviousFlow(i, i-1)
- if parser.oldGlyphs[i-1] == '\\' {
- parser.glyphs[i] = '|'
- }
- } else if i < len(parser.oldGlyphs) && parser.oldGlyphs[i] == '/' {
- parser.takePreviousFlow(i, i)
- } else {
- parser.newFlow(i)
- }
- }
-
- // setRightFlow handles '_'
- func (parser *Parser) setRightFlow(i int) {
- // In preference order:
- //
- // Current Row: '__' '_/' '_|_' '_|/'
- if i+1 < len(parser.glyphs) &&
- (parser.glyphs[i+1] == '_' || parser.glyphs[i+1] == '/') {
- parser.takeCurrentFlow(i, i+1)
- } else if i+2 < len(parser.glyphs) &&
- (parser.glyphs[i+1] == '|' || parser.glyphs[i+1] == '*') &&
- (parser.glyphs[i+2] == '_' || parser.glyphs[i+2] == '/') {
- parser.takeCurrentFlow(i, i+2)
- } else {
- parser.newFlow(i)
- }
- }
-
- // setLeftFlow handles '----.'
- func (parser *Parser) setLeftFlow(i int) {
- if parser.glyphs[i] == '.' {
- parser.newFlow(i)
- } else if i+1 < len(parser.glyphs) &&
- (parser.glyphs[i+1] == '-' || parser.glyphs[i+1] == '.') {
- parser.takeCurrentFlow(i, i+1)
- } else {
- parser.newFlow(i)
- }
- }
|