Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to blame files #5721

Merged
merged 20 commits into from
Apr 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions models/git_blame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2019 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 (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"regexp"

"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/process"
)

// BlamePart represents block of blame - continuous lines with one sha
type BlamePart struct {
Sha string
Lines []string
}

// BlameReader returns part of file blame one by one
type BlameReader struct {
cmd *exec.Cmd
pid int64
output io.ReadCloser
scanner *bufio.Scanner
lastSha *string
}

var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")

// NextPart returns next part of blame (sequencial code lines with the same commit)
func (r *BlameReader) NextPart() (*BlamePart, error) {
var blamePart *BlamePart

scanner := r.scanner

if r.lastSha != nil {
blamePart = &BlamePart{*r.lastSha, make([]string, 0, 0)}
}

for scanner.Scan() {
line := scanner.Text()

// Skip empty lines
if len(line) == 0 {
continue
}

lines := shaLineRegex.FindStringSubmatch(line)
if lines != nil {
sha1 := lines[1]

if blamePart == nil {
blamePart = &BlamePart{sha1, make([]string, 0, 0)}
}

if blamePart.Sha != sha1 {
r.lastSha = &sha1
return blamePart, nil
}
} else if line[0] == '\t' {
code := line[1:]

blamePart.Lines = append(blamePart.Lines, code)
}
}

r.lastSha = nil

return blamePart, nil
}

// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
process.GetManager().Remove(r.pid)

if err := r.cmd.Wait(); err != nil {
return fmt.Errorf("Wait: %v", err)
}

return nil
}

// CreateBlameReader creates reader for given repository, commit and file
func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
_, err := git.OpenRepository(repoPath)
if err != nil {
return nil, err
}

return createBlameReader(repoPath, "git", "blame", commitID, "--porcelain", "--", file)
}

func createBlameReader(dir string, command ...string) (*BlameReader, error) {
cmd := exec.Command(command[0], command[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr

stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("StdoutPipe: %v", err)
}

if err = cmd.Start(); err != nil {
return nil, fmt.Errorf("Start: %v", err)
}

pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cmd)

scanner := bufio.NewScanner(stdout)

return &BlameReader{
cmd,
pid,
stdout,
scanner,
nil,
}, nil
}
141 changes: 141 additions & 0 deletions models/git_blame_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2019 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 (
"io/ioutil"
"testing"

"github.com/stretchr/testify/assert"
)

const exampleBlame = `
4b92a6c2df28054ad766bc262f308db9f6066596 1 1 1
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// Copyright 2014 The Gogs Authors. All rights reserved.
ce21ed6c3490cdfad797319cbb1145e2330a8fef 2 2 1
author Joubert RedRat
author-mail <eu+github@redrat.com.br>
author-time 1482322397
author-tz -0200
committer Lunny Xiao
committer-mail <xiaolunwen@gmail.com>
committer-time 1482322397
committer-tz +0800
summary Remove remaining Gogs reference on locales and cmd (#430)
previous 618407c018cdf668ceedde7454c42fb22ba422d8 main.go
filename main.go
// Copyright 2016 The Gitea Authors. All rights reserved.
4b92a6c2df28054ad766bc262f308db9f6066596 2 3 2
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// Use of this source code is governed by a MIT-style
4b92a6c2df28054ad766bc262f308db9f6066596 3 4
author Unknown
author-mail <joe2010xtmf@163.com>
author-time 1392833071
author-tz -0500
committer Unknown
committer-mail <joe2010xtmf@163.com>
committer-time 1392833071
committer-tz -0500
summary Add code of delete user
previous be0ba9ea88aff8a658d0495d36accf944b74888d gogs.go
filename gogs.go
// license that can be found in the LICENSE file.

e2aa991e10ffd924a828ec149951f2f20eecead2 6 6 2
author Lunny Xiao
author-mail <xiaolunwen@gmail.com>
author-time 1478872595
author-tz +0800
committer Sandro Santilli
committer-mail <strk@kbt.io>
committer-time 1478872595
committer-tz +0100
summary ask for go get from code.gitea.io/gitea and change gogs to gitea on main file (#146)
previous 5fc370e332171b8658caed771b48585576f11737 main.go
filename main.go
// Gitea (git with a cup of tea) is a painless self-hosted Git Service.
e2aa991e10ffd924a828ec149951f2f20eecead2 7 7
package main // import "code.gitea.io/gitea"
`

func TestReadingBlameOutput(t *testing.T) {
tempFile, err := ioutil.TempFile("", ".txt")
if err != nil {
panic(err)
}

defer tempFile.Close()

if _, err = tempFile.WriteString(exampleBlame); err != nil {
panic(err)
}

blameReader, err := createBlameReader("", "cat", tempFile.Name())
if err != nil {
panic(err)
}
defer blameReader.Close()

parts := []*BlamePart{
{
"4b92a6c2df28054ad766bc262f308db9f6066596",
[]string{
"// Copyright 2014 The Gogs Authors. All rights reserved.",
},
},
{
"ce21ed6c3490cdfad797319cbb1145e2330a8fef",
[]string{
"// Copyright 2016 The Gitea Authors. All rights reserved.",
},
},
{
"4b92a6c2df28054ad766bc262f308db9f6066596",
[]string{
"// Use of this source code is governed by a MIT-style",
"// license that can be found in the LICENSE file.",
"",
},
},
{
"e2aa991e10ffd924a828ec149951f2f20eecead2",
[]string{
"// Gitea (git with a cup of tea) is a painless self-hosted Git Service.",
"package main // import \"code.gitea.io/gitea\"",
},
},
nil,
}

for _, part := range parts {
actualPart, err := blameReader.NextPart()
if err != nil {
panic(err)
}
assert.Equal(t, part, actualPart)
}
}
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,8 @@ video_not_supported_in_browser = Your browser does not support the HTML5 'video'
audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag.
stored_lfs = Stored with Git LFS
commit_graph = Commit Graph
blame = Blame
normal_view = Normal View

editor.new_file = New File
editor.upload_file = Upload File
Expand Down
2 changes: 1 addition & 1 deletion public/css/index.css

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions public/less/_repository.less
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,53 @@
}
}
}
.lines-commit {
vertical-align: top;
color: #999;
padding: 0;
background: #f5f5f5;
width: 1%;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
user-select: none;

.blame-info {
width: 350px;
max-width: 350px;
display: block;
user-select: none;
padding: 0 0 0 10px;

.blame-data {
display: flex;
font-family: @default-fonts;

.blame-message {
flex-grow: 2;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 20px;
}
.blame-time, .blame-avatar {
flex-shrink: 0;
}
}
}

.ui.avatar.image {
height: 18px;
width: 18px;
}
}
.lines-num,
.lines-code,
.lines-commit {
.bottom-line {
border-bottom: 1px solid #eaecef;
}
}
.active {
background: #ffffdd;
}
Expand Down
Loading