Skip to content

Commit 469d9b7

Browse files
andrzejresseltechknowlogick
authored andcommitted
Add option to blame files (#5721)
1 parent b9d1fb6 commit 469d9b7

File tree

10 files changed

+631
-2
lines changed

10 files changed

+631
-2
lines changed

models/git_blame.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"bufio"
9+
"fmt"
10+
"io"
11+
"os"
12+
"os/exec"
13+
"regexp"
14+
15+
"code.gitea.io/gitea/modules/git"
16+
"code.gitea.io/gitea/modules/process"
17+
)
18+
19+
// BlamePart represents block of blame - continuous lines with one sha
20+
type BlamePart struct {
21+
Sha string
22+
Lines []string
23+
}
24+
25+
// BlameReader returns part of file blame one by one
26+
type BlameReader struct {
27+
cmd *exec.Cmd
28+
pid int64
29+
output io.ReadCloser
30+
scanner *bufio.Scanner
31+
lastSha *string
32+
}
33+
34+
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
35+
36+
// NextPart returns next part of blame (sequencial code lines with the same commit)
37+
func (r *BlameReader) NextPart() (*BlamePart, error) {
38+
var blamePart *BlamePart
39+
40+
scanner := r.scanner
41+
42+
if r.lastSha != nil {
43+
blamePart = &BlamePart{*r.lastSha, make([]string, 0, 0)}
44+
}
45+
46+
for scanner.Scan() {
47+
line := scanner.Text()
48+
49+
// Skip empty lines
50+
if len(line) == 0 {
51+
continue
52+
}
53+
54+
lines := shaLineRegex.FindStringSubmatch(line)
55+
if lines != nil {
56+
sha1 := lines[1]
57+
58+
if blamePart == nil {
59+
blamePart = &BlamePart{sha1, make([]string, 0, 0)}
60+
}
61+
62+
if blamePart.Sha != sha1 {
63+
r.lastSha = &sha1
64+
return blamePart, nil
65+
}
66+
} else if line[0] == '\t' {
67+
code := line[1:]
68+
69+
blamePart.Lines = append(blamePart.Lines, code)
70+
}
71+
}
72+
73+
r.lastSha = nil
74+
75+
return blamePart, nil
76+
}
77+
78+
// Close BlameReader - don't run NextPart after invoking that
79+
func (r *BlameReader) Close() error {
80+
process.GetManager().Remove(r.pid)
81+
82+
if err := r.cmd.Wait(); err != nil {
83+
return fmt.Errorf("Wait: %v", err)
84+
}
85+
86+
return nil
87+
}
88+
89+
// CreateBlameReader creates reader for given repository, commit and file
90+
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
91+
_, err := git.OpenRepository(repoPath)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
return createBlameReader(repoPath, "git", "blame", commitID, "--porcelain", "--", file)
97+
}
98+
99+
func createBlameReader(dir string, command ...string) (*BlameReader, error) {
100+
cmd := exec.Command(command[0], command[1:]...)
101+
cmd.Dir = dir
102+
cmd.Stderr = os.Stderr
103+
104+
stdout, err := cmd.StdoutPipe()
105+
if err != nil {
106+
return nil, fmt.Errorf("StdoutPipe: %v", err)
107+
}
108+
109+
if err = cmd.Start(); err != nil {
110+
return nil, fmt.Errorf("Start: %v", err)
111+
}
112+
113+
pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cmd)
114+
115+
scanner := bufio.NewScanner(stdout)
116+
117+
return &BlameReader{
118+
cmd,
119+
pid,
120+
stdout,
121+
scanner,
122+
nil,
123+
}, nil
124+
}

models/git_blame_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import (
8+
"io/ioutil"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
const exampleBlame = `
15+
4b92a6c2df28054ad766bc262f308db9f6066596 1 1 1
16+
author Unknown
17+
author-mail <[email protected]>
18+
author-time 1392833071
19+
author-tz -0500
20+
committer Unknown
21+
committer-mail <[email protected]>
22+
committer-time 1392833071
23+
committer-tz -0500
24+
summary Add code of delete user
25+
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
26+
filename gogs.go
27+
// Copyright 2014 The Gogs Authors. All rights reserved.
28+
ce21ed6c3490cdfad797319cbb1145e2330a8fef 2 2 1
29+
author Joubert RedRat
30+
author-mail <[email protected]>
31+
author-time 1482322397
32+
author-tz -0200
33+
committer Lunny Xiao
34+
committer-mail <[email protected]>
35+
committer-time 1482322397
36+
committer-tz +0800
37+
summary Remove remaining Gogs reference on locales and cmd (#430)
38+
previous 618407c018cdf668ceedde7454c42fb22ba422d8 main.go
39+
filename main.go
40+
// Copyright 2016 The Gitea Authors. All rights reserved.
41+
4b92a6c2df28054ad766bc262f308db9f6066596 2 3 2
42+
author Unknown
43+
author-mail <[email protected]>
44+
author-time 1392833071
45+
author-tz -0500
46+
committer Unknown
47+
committer-mail <[email protected]>
48+
committer-time 1392833071
49+
committer-tz -0500
50+
summary Add code of delete user
51+
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
52+
filename gogs.go
53+
// Use of this source code is governed by a MIT-style
54+
4b92a6c2df28054ad766bc262f308db9f6066596 3 4
55+
author Unknown
56+
author-mail <[email protected]>
57+
author-time 1392833071
58+
author-tz -0500
59+
committer Unknown
60+
committer-mail <[email protected]>
61+
committer-time 1392833071
62+
committer-tz -0500
63+
summary Add code of delete user
64+
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
65+
filename gogs.go
66+
// license that can be found in the LICENSE file.
67+
68+
e2aa991e10ffd924a828ec149951f2f20eecead2 6 6 2
69+
author Lunny Xiao
70+
author-mail <[email protected]>
71+
author-time 1478872595
72+
author-tz +0800
73+
committer Sandro Santilli
74+
committer-mail <[email protected]>
75+
committer-time 1478872595
76+
committer-tz +0100
77+
summary ask for go get from code.gitea.io/gitea and change gogs to gitea on main file (#146)
78+
previous 5fc370e332171b8658caed771b48585576f11737 main.go
79+
filename main.go
80+
// Gitea (git with a cup of tea) is a painless self-hosted Git Service.
81+
e2aa991e10ffd924a828ec149951f2f20eecead2 7 7
82+
package main // import "code.gitea.io/gitea"
83+
`
84+
85+
func TestReadingBlameOutput(t *testing.T) {
86+
tempFile, err := ioutil.TempFile("", ".txt")
87+
if err != nil {
88+
panic(err)
89+
}
90+
91+
defer tempFile.Close()
92+
93+
if _, err = tempFile.WriteString(exampleBlame); err != nil {
94+
panic(err)
95+
}
96+
97+
blameReader, err := createBlameReader("", "cat", tempFile.Name())
98+
if err != nil {
99+
panic(err)
100+
}
101+
defer blameReader.Close()
102+
103+
parts := []*BlamePart{
104+
{
105+
"4b92a6c2df28054ad766bc262f308db9f6066596",
106+
[]string{
107+
"// Copyright 2014 The Gogs Authors. All rights reserved.",
108+
},
109+
},
110+
{
111+
"ce21ed6c3490cdfad797319cbb1145e2330a8fef",
112+
[]string{
113+
"// Copyright 2016 The Gitea Authors. All rights reserved.",
114+
},
115+
},
116+
{
117+
"4b92a6c2df28054ad766bc262f308db9f6066596",
118+
[]string{
119+
"// Use of this source code is governed by a MIT-style",
120+
"// license that can be found in the LICENSE file.",
121+
"",
122+
},
123+
},
124+
{
125+
"e2aa991e10ffd924a828ec149951f2f20eecead2",
126+
[]string{
127+
"// Gitea (git with a cup of tea) is a painless self-hosted Git Service.",
128+
"package main // import \"code.gitea.io/gitea\"",
129+
},
130+
},
131+
nil,
132+
}
133+
134+
for _, part := range parts {
135+
actualPart, err := blameReader.NextPart()
136+
if err != nil {
137+
panic(err)
138+
}
139+
assert.Equal(t, part, actualPart)
140+
}
141+
}

options/locale/locale_en-US.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,8 @@ video_not_supported_in_browser = Your browser does not support the HTML5 'video'
651651
audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag.
652652
stored_lfs = Stored with Git LFS
653653
commit_graph = Commit Graph
654+
blame = Blame
655+
normal_view = Normal View
654656
655657
editor.new_file = New File
656658
editor.upload_file = Upload File

public/css/index.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/less/_repository.less

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,53 @@
373373
}
374374
}
375375
}
376+
.lines-commit {
377+
vertical-align: top;
378+
color: #999;
379+
padding: 0;
380+
background: #f5f5f5;
381+
width: 1%;
382+
-moz-user-select: none;
383+
-ms-user-select: none;
384+
-webkit-user-select: none;
385+
user-select: none;
386+
387+
.blame-info {
388+
width: 350px;
389+
max-width: 350px;
390+
display: block;
391+
user-select: none;
392+
padding: 0 0 0 10px;
393+
394+
.blame-data {
395+
display: flex;
396+
font-family: @default-fonts;
397+
398+
.blame-message {
399+
flex-grow: 2;
400+
overflow: hidden;
401+
white-space: nowrap;
402+
text-overflow: ellipsis;
403+
line-height: 20px;
404+
}
405+
.blame-time, .blame-avatar {
406+
flex-shrink: 0;
407+
}
408+
}
409+
}
410+
411+
.ui.avatar.image {
412+
height: 18px;
413+
width: 18px;
414+
}
415+
}
416+
.lines-num,
417+
.lines-code,
418+
.lines-commit {
419+
.bottom-line {
420+
border-bottom: 1px solid #eaecef;
421+
}
422+
}
376423
.active {
377424
background: #ffffdd;
378425
}

0 commit comments

Comments
 (0)