commithgraph / timeline (#428)

* Add model and tests for graph

* Add route and router for graph

* Add assets for graph

* Add template for graph
This commit is contained in:
Kjell Kvinge
2016-12-29 00:44:32 +01:00
committed by Lunny Xiao
parent 35d9378e4e
commit 22e1bd31c6
10 changed files with 673 additions and 2 deletions

View File

@ -547,6 +547,7 @@ func runWeb(ctx *cli.Context) error {
m.Get("/src/*", repo.SetEditorconfigIfExists, repo.Home)
m.Get("/raw/*", repo.SingleDownload)
m.Get("/commits/*", repo.RefCommits)
m.Get("/graph", repo.Graph)
m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
m.Get("/forks", repo.Forks)
}, context.RepoRef())

108
models/graph.go Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2016 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 models
import (
"fmt"
"strings"
"code.gitea.io/git"
)
// 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) (GraphItems, error) {
var Commitgraph []GraphItem
format := "DATA:|%d|%H|%ad|%an|%ae|%h|%s"
graphCmd := git.NewCommand("log")
graphCmd.AddArguments("--graph",
"--date-order",
"--all",
"-C",
"-M",
"-n 100",
"--date=iso",
fmt.Sprintf("--pretty=format:%s", format),
)
graph, err := graphCmd.RunInDir(r.Path)
if err != nil {
return Commitgraph, err
}
Commitgraph = make([]GraphItem, 0, 100)
for _, s := range strings.Split(graph, "\n") {
GraphItem, err := graphItemFromString(s, r)
if err != nil {
return Commitgraph, err
}
Commitgraph = append(Commitgraph, GraphItem)
}
return Commitgraph, nil
}
func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
var ascii string
var data = "|||||||"
lines := strings.Split(s, "DATA:")
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.Split(data, "|")
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 refered to, only relation in current line.
}
return gi, nil
}

41
models/graph_test.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2016 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 models
import (
"testing"
"code.gitea.io/git"
)
func BenchmarkGetCommitGraph(b *testing.B) {
currentRepo, err := git.OpenRepository(".")
if err != nil {
b.Error("Could not open repository")
}
graph, err := GetCommitGraph(currentRepo)
if err != nil {
b.Error("Could get commit graph")
}
if len(graph) < 100 {
b.Error("Should get 100 log lines.")
}
}
func BenchmarkParseCommitString(b *testing.B) {
testString := "* DATA:||4e61bacab44e9b4730e44a6615d04098dd3a8eaf|2016-12-20 21:10:41 +0100|Kjell Kvinge|kjell@kvinge.biz|4e61bac|Add route for graph"
graphItem, err := graphItemFromString(testString, nil)
if err != nil {
b.Error("could not parse teststring")
}
if graphItem.Author != "Kjell Kvinge" {
b.Error("Did not get expected data")
}
}

15
public/css/gitgraph.css Normal file
View File

@ -0,0 +1,15 @@
body {font:13.34px/1.4 helvetica,arial,freesans,clean,sans-serif;}
em {font-style:normal;}
#git-graph-container, #rel-container {float:left;}
#git-graph-container {}
#git-graph-container li {list-style-type:none;height:20px;line-height:20px;overflow:hidden;}
#git-graph-container li .node-relation {font-family:'Bitstream Vera Sans Mono', 'Courier', monospace;}
#git-graph-container li .author {color:#666666;}
#git-graph-container li .time {color:#999999;font-size:80%}
#git-graph-container li a {color:#000000;}
#git-graph-container li a:hover {text-decoration:underline;}
#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;}
#rev-container {width:80%}
#rev-list {margin:0;padding:0 5px 0 0;width:80%}
#graph-raw-list {margin:0px;}

17
public/js/draw.js Normal file
View File

@ -0,0 +1,17 @@
$(document).ready(function () {
var graphList = [];
if (!document.getElementById('graph-canvas')) {
return;
}
$("#graph-raw-list li span.node-relation").each(function () {
graphList.push($(this).text());
})
gitGraph(document.getElementById('graph-canvas'), graphList);
if ($("#rev-container")) {
$("#rev-container").css("width", document.body.clientWidth - document.getElementById('graph-canvas').width);
}
})

399
public/js/libs/gitgraph.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ import (
const (
tplCommits base.TplName = "repo/commits"
tplGraph base.TplName = "repo/graph"
tplDiff base.TplName = "repo/diff/page"
)
@ -75,6 +76,32 @@ func Commits(ctx *context.Context) {
ctx.HTML(200, tplCommits)
}
// Graph render commit graph - show commits from all branches.
func Graph(ctx *context.Context) {
ctx.Data["PageIsCommits"] = true
commitsCount, err := ctx.Repo.Commit.CommitsCount()
if err != nil {
ctx.Handle(500, "GetCommitsCount", err)
return
}
graph, err := models.GetCommitGraph(ctx.Repo.GitRepo)
if err != nil {
ctx.Handle(500, "GetCommitGraph", err)
return
}
ctx.Data["Graph"] = graph
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.Data["CommitCount"] = commitsCount
ctx.Data["Branch"] = ctx.Repo.BranchName
ctx.Data["RequireGitGraph"] = true
ctx.HTML(200, tplGraph)
}
// SearchCommits render commits filtered by keyword
func SearchCommits(ctx *context.Context) {
ctx.Data["PageIsCommits"] = true

View File

@ -31,6 +31,13 @@
</script>
{{end}}
{{if .RequireGitGraph}}
<!-- graph -->
<script src="{{AppSubUrl}}/js/libs/gitgraph.js"></script>
<script src="{{AppSubUrl}}/js/draw.js"></script>
<link rel="stylesheet" href="{{AppSubUrl}}/css/gitgraph.css">
{{end}}
<!-- Stylesheet -->
<link rel="stylesheet" href="{{AppSubUrl}}/css/semantic-2.2.1.min.css">
<link rel="stylesheet" href="{{AppSubUrl}}/css/index.css?v={{MD5 AppVer}}">

View File

@ -2,8 +2,20 @@
<div class="repository commits">
{{template "repo/header" .}}
<div class="ui container">
{{template "repo/branch_dropdown" .}}
{{template "repo/commits_table" .}}
<div class="ui secondary menu">
{{template "repo/branch_dropdown" .}}
<div class="fitted item">
<div class="ui breadcrumb">
<a href="{{.RepoLink}}/graph">
<span class="text">
<i class="octicon octicon-git-branch"></i>
</span>
commit graph
</a>
</div>
</div>
</div>
{{template "repo/commits_table" .}}
</div>
</div>
{{template "base/footer" .}}

44
templates/repo/graph.tmpl Normal file
View File

@ -0,0 +1,44 @@
{{template "base/head" .}}
<div class="repository commits">
{{template "repo/header" .}}
<div class="ui container">
<div id="git-graph-container">
<div id="rel-container">
<canvas id="graph-canvas">
<ul id="graph-raw-list">
{{ range .Graph }}
<li><span class="node-relation">{{ .GraphAcii -}}</span></li>
{{ end }}
</ul>
</canvas>
</div>
<div id="rev-container">
<ul id="rev-list">
{{ range .Graph }}
<li>
{{ if .OnlyRelation }}
<span />
{{ else }}
<code id="{{.ShortRev}}">
<a href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.Rev}}">{{ .ShortRev}}</a>
</code>
<strong> {{.Branch}}</strong>
<em>{{.Subject}}</em> by
<span class="author">
{{.Author}}
</span>
<span class="time">{{.Date}}</span>
{{ end }}
</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}