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:
@ -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
108
models/graph.go
Normal 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
41
models/graph_test.go
Normal 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
15
public/css/gitgraph.css
Normal 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
17
public/js/draw.js
Normal 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
399
public/js/libs/gitgraph.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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}}">
|
||||
|
@ -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
44
templates/repo/graph.tmpl
Normal 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" .}}
|
Reference in New Issue
Block a user