Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 48bf5bd

Browse files
committed
fix PR#7 comments
1 parent d643cea commit 48bf5bd

File tree

14 files changed

+686
-621
lines changed

14 files changed

+686
-621
lines changed

blame/blame.go

Lines changed: 105 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ package blame
1010

1111
import (
1212
"bytes"
13+
"errors"
1314
"fmt"
14-
"sort"
1515
"strconv"
1616
"strings"
1717
"unicode/utf8"
@@ -62,52 +62,125 @@ import (
6262
// 2. Improve how to traverse the history (example a backward
6363
// traversal will be much more efficient)
6464
//
65-
// TODO: ways to improve the functrion in general
65+
// TODO: ways to improve the function in general:
6666
//
6767
// 1. Add memoization betweenn revlist and assign.
6868
//
6969
// 2. It is using much more memmory than needed, see the TODOs below.
70-
func Blame(repo *git.Repository, commit *git.Commit, path string) ([]*git.Commit, error) {
70+
71+
type Blame struct {
72+
Repo string
73+
Path string
74+
Rev string
75+
Lines []*line
76+
}
77+
78+
func New(repo *git.Repository, path string, commit *git.Commit) (*Blame, error) {
7179
// init the internal blame struct
7280
b := new(blame)
7381
b.repo = repo
7482
b.fRev = commit
7583
b.path = path
7684

77-
// calculte the history of the file and store it in the
78-
// internal blame struct.
79-
var err error
80-
b.revs, err = revlist.New(b.repo, b.fRev, b.path)
85+
// get all the file revisions
86+
if err := b.fillRevs(); err != nil {
87+
return nil, err
88+
}
89+
90+
// calculate the line tracking graph and fill in
91+
// file contents in data.
92+
if err := b.fillGraphAndData(); err != nil {
93+
return nil, err
94+
}
95+
96+
file, err := b.fRev.File(b.path)
97+
if err != nil {
98+
return nil, err
99+
}
100+
finalLines := file.Lines()
101+
102+
lines, err := newLines(finalLines, b.sliceGraph(len(b.graph)-1))
81103
if err != nil {
82104
return nil, err
83105
}
84-
sort.Sort(b.revs) // for forward blame, we need the history sorted by commit date
85106

86-
// allocate space for the data in all the revisions of the file
87-
b.data = make([]string, len(b.revs))
107+
return &Blame{
108+
Repo: repo.URL,
109+
Path: path,
110+
Rev: commit.Hash.String(),
111+
Lines: lines,
112+
}, nil
113+
}
88114

89-
// init the graph
90-
b.graph = make([][]vertex, len(b.revs))
115+
type line struct {
116+
author string
117+
text string
118+
}
91119

92-
// for all every revision of the file, starting with the first
120+
func newLine(author, text string) *line {
121+
return &line{
122+
author: author,
123+
text: text,
124+
}
125+
}
126+
127+
func newLines(contents []string, commits []*git.Commit) ([]*line, error) {
128+
if len(contents) != len(commits) {
129+
fmt.Println(len(contents))
130+
fmt.Println(len(commits))
131+
return nil, errors.New("contents and commits have different length")
132+
}
133+
result := make([]*line, 0, len(contents))
134+
for i := range contents {
135+
l := newLine(commits[i].Author.Email, contents[i])
136+
result = append(result, l)
137+
}
138+
return result, nil
139+
}
140+
141+
// this struct is internally used by the blame function to hold its
142+
// inputs, outputs and state.
143+
type blame struct {
144+
repo *git.Repository // the repo holding the history of the file to blame
145+
path string // the path of the file to blame
146+
fRev *git.Commit // the commit of the final revision of the file to blame
147+
revs revlist.Revs // the chain of revisions affecting the the file to blame
148+
data []string // the contents of the file across all its revisions
149+
graph [][]*git.Commit // the graph of the lines in the file across all the revisions TODO: not all commits are needed, only the current rev and the prev
150+
}
151+
152+
// calculte the history of a file "path", from commit "from, sorted by commit date.
153+
func (b *blame) fillRevs() error {
154+
var err error
155+
b.revs, err = revlist.NewRevs(b.repo, b.fRev, b.path)
156+
if err != nil {
157+
return err
158+
}
159+
return nil
160+
}
161+
162+
// build graph of a file from its revision history
163+
func (b *blame) fillGraphAndData() error {
164+
b.graph = make([][]*git.Commit, len(b.revs))
165+
b.data = make([]string, len(b.revs)) // file contents in all the revisions
166+
// for every revision of the file, starting with the first
93167
// one...
94-
var found bool
95168
for i, rev := range b.revs {
96169
// get the contents of the file
97-
b.data[i], found = git.Data(b.path, rev)
98-
if !found {
99-
continue
170+
file, err := rev.File(b.path)
171+
if err != nil {
172+
return nil
100173
}
101-
// count its lines
174+
b.data[i] = file.Contents()
102175
nLines := git.CountLines(b.data[i])
103176
// create a node for each line
104-
b.graph[i] = make([]vertex, nLines)
177+
b.graph[i] = make([]*git.Commit, nLines)
105178
// assign a commit to each node
106179
// if this is the first revision, then the node is assigned to
107180
// this first commit.
108181
if i == 0 {
109182
for j := 0; j < nLines; j++ {
110-
b.graph[i][j] = vertex(b.revs[i])
183+
b.graph[i][j] = (*git.Commit)(b.revs[i])
111184
}
112185
} else {
113186
// if this is not the first commit, then assign to the old
@@ -116,31 +189,21 @@ func Blame(repo *git.Repository, commit *git.Commit, path string) ([]*git.Commit
116189
b.assignOrigin(i, i-1)
117190
}
118191
}
192+
return nil
193+
}
119194

120-
// fill in the output results: copy the nodes of the last revision
121-
// into the result.
122-
fVs := b.graph[len(b.graph)-1]
195+
// sliceGraph returns a slice of commits (one per line) for a particular
196+
// revision of a file (0=first revision).
197+
func (b *blame) sliceGraph(i int) []*git.Commit {
198+
fVs := b.graph[i]
123199
result := make([]*git.Commit, 0, len(fVs))
124200
for _, v := range fVs {
125201
c := git.Commit(*v)
126202
result = append(result, &c)
127203
}
128-
return result, nil
129-
}
130-
131-
// this struct is internally used by the blame function to hold its
132-
// intputs, outputs and state.
133-
type blame struct {
134-
repo *git.Repository // the repo holding the history of the file to blame
135-
path string // the path of the file to blame
136-
fRev *git.Commit // the commit of the final revision of the file to blame
137-
revs revlist.Revs // the chain of revisions affecting the the file to blame
138-
data []string // the contents on the file in all the revisions TODO: not all data is needed, only the current rev and the prev
139-
graph [][]vertex // the graph of the lines in the file across all the revisions TODO: not all vertexes are needed, only the current rev and the prev
204+
return result
140205
}
141206

142-
type vertex *git.Commit // a vertex only needs to store the original commit it came from
143-
144207
// Assigns origin to vertexes in current (c) rev from data in its previous (p)
145208
// revision
146209
func (b *blame) assignOrigin(c, p int) {
@@ -151,15 +214,14 @@ func (b *blame) assignOrigin(c, p int) {
151214
for h := range hunks {
152215
hLines := git.CountLines(hunks[h].Text)
153216
for hl := 0; hl < hLines; hl++ {
154-
// fmt.Printf("file=%q, rev=%d, r=%d, h=%d, hunk=%v, hunkLine=%d\n", file, rev, r, h, hunks[h], hl)
155217
switch {
156218
case hunks[h].Type == 0:
157219
sl++
158220
dl++
159221
b.graph[c][dl] = b.graph[p][sl]
160222
case hunks[h].Type == 1:
161223
dl++
162-
b.graph[c][dl] = vertex(b.revs[c])
224+
b.graph[c][dl] = (*git.Commit)(b.revs[c])
163225
case hunks[h].Type == -1:
164226
sl++
165227
default:
@@ -169,14 +231,15 @@ func (b *blame) assignOrigin(c, p int) {
169231
}
170232
}
171233

172-
// This will print the results of a Blame as in git-blame.
234+
// PrettyPrint prints the results of a Blame using git-blame's style.
173235
func (b *blame) PrettyPrint() string {
174236
var buf bytes.Buffer
175237

176-
contents, found := git.Data(b.path, b.fRev)
177-
if !found {
238+
file, err := b.fRev.File(b.path)
239+
if err != nil {
178240
panic("PrettyPrint: internal error in repo.Data")
179241
}
242+
contents := file.Contents()
180243

181244
lines := strings.Split(contents, "\n")
182245
// max line number length

0 commit comments

Comments
 (0)