@@ -10,8 +10,8 @@ package blame
1010
1111import (
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
146209func (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 .
173235func (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