]> asedeno.scripts.mit.edu Git - git.git/blob - lib/blame.tcl
git-gui: Remove empty blank line at end of blame
[git.git] / lib / blame.tcl
1 # git-gui blame viewer
2 # Copyright (C) 2006, 2007 Shawn Pearce
3
4 class blame {
5
6 field commit  ; # input commit to blame
7 field path    ; # input filename to view in $commit
8
9 field w
10 field w_line
11 field w_cgrp
12 field w_load
13 field w_file
14 field w_cmit
15 field status
16
17 field highlight_line   -1 ; # current line selected
18 field highlight_commit {} ; # sha1 of commit selected
19
20 field total_lines       0  ; # total length of file
21 field blame_lines       0  ; # number of lines computed
22 field commit_count      0  ; # number of commits in $commit_list
23 field commit_list      {}  ; # list of commit sha1 in receipt order
24 field order                ; # array commit -> receipt order
25 field header               ; # array commit,key -> header field
26 field line_commit          ; # array line -> sha1 commit
27 field line_file            ; # array line -> file name
28
29 field r_commit      ; # commit currently being parsed
30 field r_orig_line   ; # original line number
31 field r_final_line  ; # final line number
32 field r_line_count  ; # lines in this region
33
34 constructor new {i_commit i_path} {
35         set commit $i_commit
36         set path   $i_path
37
38         make_toplevel top w
39         wm title $top "[appname] ([reponame]): File Viewer"
40         set status "Loading $commit:$path..."
41
42         label $w.path -text "$commit:$path" \
43                 -anchor w \
44                 -justify left \
45                 -borderwidth 1 \
46                 -relief sunken \
47                 -font font_uibold
48         pack $w.path -side top -fill x
49
50         frame $w.out
51         set w_load $w.out.loaded_t
52         text $w_load \
53                 -background white -borderwidth 0 \
54                 -state disabled \
55                 -wrap none \
56                 -height 40 \
57                 -width 1 \
58                 -font font_diff
59         $w_load tag conf annotated -background grey
60
61         set w_line $w.out.linenumber_t
62         text $w_line \
63                 -background white -borderwidth 0 \
64                 -state disabled \
65                 -wrap none \
66                 -height 40 \
67                 -width 5 \
68                 -font font_diff
69         $w_line tag conf linenumber -justify right
70
71         set w_cgrp $w.out.commit_t
72         text $w_cgrp \
73                 -background white -borderwidth 0 \
74                 -state disabled \
75                 -wrap none \
76                 -height 40 \
77                 -width 4 \
78                 -font font_diff
79
80         set w_file $w.out.file_t
81         text $w_file \
82                 -background white -borderwidth 0 \
83                 -state disabled \
84                 -wrap none \
85                 -height 40 \
86                 -width 80 \
87                 -xscrollcommand [list $w.out.sbx set] \
88                 -font font_diff
89
90         scrollbar $w.out.sbx -orient h -command [list $w_file xview]
91         scrollbar $w.out.sby -orient v \
92                 -command [list scrollbar2many [list \
93                 $w_load \
94                 $w_line \
95                 $w_cgrp \
96                 $w_file \
97                 ] yview]
98         grid \
99                 $w_cgrp \
100                 $w_line \
101                 $w_load \
102                 $w_file \
103                 $w.out.sby \
104                 -sticky nsew
105         grid conf $w.out.sbx -column 3 -sticky we
106         grid columnconfigure $w.out 3 -weight 1
107         grid rowconfigure $w.out 0 -weight 1
108         pack $w.out -fill both -expand 1
109
110         label $w.status \
111                 -textvariable @status \
112                 -anchor w \
113                 -justify left \
114                 -borderwidth 1 \
115                 -relief sunken
116         pack $w.status -side bottom -fill x
117
118         frame $w.cm
119         set w_cmit $w.cm.t
120         text $w_cmit \
121                 -background white -borderwidth 0 \
122                 -state disabled \
123                 -wrap none \
124                 -height 10 \
125                 -width 80 \
126                 -xscrollcommand [list $w.cm.sbx set] \
127                 -yscrollcommand [list $w.cm.sby set] \
128                 -font font_diff
129         scrollbar $w.cm.sbx -orient h -command [list $w_cmit xview]
130         scrollbar $w.cm.sby -orient v -command [list $w_cmit yview]
131         pack $w.cm.sby -side right -fill y
132         pack $w.cm.sbx -side bottom -fill x
133         pack $w_cmit -expand 1 -fill both
134         pack $w.cm -side bottom -fill x
135
136         menu $w.ctxm -tearoff 0
137         $w.ctxm add command \
138                 -label "Copy Commit" \
139                 -command [cb _copycommit]
140
141         foreach i [list \
142                 $w_cgrp \
143                 $w_load \
144                 $w_line \
145                 $w_file] {
146                 $i tag conf in_sel \
147                         -background [$i cget -foreground] \
148                         -foreground [$i cget -background]
149                 $i conf -yscrollcommand \
150                         [list many2scrollbar [list \
151                         $w_cgrp \
152                         $w_load \
153                         $w_line \
154                         $w_file \
155                         ] yview $w.out.sby]
156                 bind $i <Button-1> "[cb _click $i @%x,%y]; focus $i"
157                 bind_button3 $i "
158                         set cursorX %x
159                         set cursorY %y
160                         set cursorW %W
161                         tk_popup $w.ctxm %X %Y
162                 "
163         }
164
165         foreach i [list \
166                 $w_cgrp \
167                 $w_load \
168                 $w_line \
169                 $w_file \
170                 $w_cmit] {
171                 bind $i <Key-Up>        {catch {%W yview scroll -1 units};break}
172                 bind $i <Key-Down>      {catch {%W yview scroll  1 units};break}
173                 bind $i <Key-Left>      {catch {%W xview scroll -1 units};break}
174                 bind $i <Key-Right>     {catch {%W xview scroll  1 units};break}
175                 bind $i <Key-k>         {catch {%W yview scroll -1 units};break}
176                 bind $i <Key-j>         {catch {%W yview scroll  1 units};break}
177                 bind $i <Key-h>         {catch {%W xview scroll -1 units};break}
178                 bind $i <Key-l>         {catch {%W xview scroll  1 units};break}
179                 bind $i <Control-Key-b> {catch {%W yview scroll -1 pages};break}
180                 bind $i <Control-Key-f> {catch {%W yview scroll  1 pages};break}
181         }
182
183         bind $w_cmit <Button-1> [list focus $w_cmit]
184         bind $top <Visibility> [list focus $top]
185         bind $top <Destroy> [list delete_this $this]
186
187         if {$commit eq {}} {
188                 set fd [open $path r]
189         } else {
190                 set cmd [list git cat-file blob "$commit:$path"]
191                 set fd [open "| $cmd" r]
192         }
193         fconfigure $fd -blocking 0 -translation lf -encoding binary
194         fileevent $fd readable [cb _read_file $fd]
195 }
196
197 method _read_file {fd} {
198         $w_load conf -state normal
199         $w_cgrp conf -state normal
200         $w_line conf -state normal
201         $w_file conf -state normal
202         while {[gets $fd line] >= 0} {
203                 regsub "\r\$" $line {} line
204                 incr total_lines
205
206                 if {$total_lines > 1} {
207                         $w_load insert end "\n"
208                         $w_cgrp insert end "\n"
209                         $w_line insert end "\n"
210                         $w_file insert end "\n"
211                 }
212
213                 $w_line insert end "$total_lines" linenumber
214                 $w_file insert end "$line"
215         }
216         $w_load conf -state disabled
217         $w_cgrp conf -state disabled
218         $w_line conf -state disabled
219         $w_file conf -state disabled
220
221         if {[eof $fd]} {
222                 close $fd
223                 _status $this
224                 set cmd [list git blame -M -C --incremental]
225                 if {$commit eq {}} {
226                         lappend cmd --contents $path
227                 } else {
228                         lappend cmd $commit
229                 }
230                 lappend cmd -- $path
231                 set fd [open "| $cmd" r]
232                 fconfigure $fd -blocking 0 -translation lf -encoding binary
233                 fileevent $fd readable [cb _read_blame $fd]
234         }
235 } ifdeleted { catch {close $fd} }
236
237 method _read_blame {fd} {
238         $w_cgrp conf -state normal
239         while {[gets $fd line] >= 0} {
240                 if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
241                         cmit original_line final_line line_count]} {
242                         set r_commit     $cmit
243                         set r_orig_line  $original_line
244                         set r_final_line $final_line
245                         set r_line_count $line_count
246
247                         if {[catch {set g $order($cmit)}]} {
248                                 $w_cgrp tag conf g$cmit
249                                 $w_line tag conf g$cmit
250                                 $w_file tag conf g$cmit
251
252                                 $w_cgrp tag raise in_sel
253                                 $w_line tag raise in_sel
254                                 $w_file tag raise in_sel
255
256                                 $w_file tag raise sel
257                                 set order($cmit) $commit_count
258                                 incr commit_count
259                                 lappend commit_list $cmit
260                         }
261                 } elseif {[string match {filename *} $line]} {
262                         set file [string range $line 9 end]
263                         set n    $r_line_count
264                         set lno  $r_final_line
265                         set cmit $r_commit
266                         set abbr [string range $cmit 0 4]
267
268                         while {$n > 0} {
269                                 set lno_e "$lno.0 lineend + 1c"
270                                 if {[catch {set g g$line_commit($lno)}]} {
271                                         $w_load tag add annotated $lno.0 $lno_e
272                                 } else {
273                                         $w_cgrp tag remove g$g $lno.0 $lno_e
274                                         $w_line tag remove g$g $lno.0 $lno_e
275                                         $w_file tag remove g$g $lno.0 $lno_e
276                                 }
277
278                                 set line_commit($lno) $cmit
279                                 set line_file($lno)   $file
280
281                                 $w_cgrp delete $lno.0 $lno_e
282                                 $w_cgrp insert $lno.0 "$abbr\n"
283
284                                 $w_cgrp tag add g$cmit $lno.0 $lno_e
285                                 $w_line tag add g$cmit $lno.0 $lno_e
286                                 $w_file tag add g$cmit $lno.0 $lno_e
287
288                                 if {$highlight_line == -1} {
289                                         if {[lindex [$w_file yview] 0] == 0} {
290                                                 $w_file see $lno.0
291                                                 _showcommit $this $lno
292                                         }
293                                 } elseif {$highlight_line == $lno} {
294                                         _showcommit $this $lno
295                                 }
296
297                                 incr n -1
298                                 incr lno
299                                 incr blame_lines
300                         }
301
302                         set hc $highlight_commit
303                         if {$hc ne {}
304                                 && [expr {$order($hc) + 1}] == $order($cmit)} {
305                                 _showcommit $this $highlight_line
306                         }
307                 } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
308                         set header($r_commit,$key) $data
309                 }
310         }
311         $w_cgrp conf -state disabled
312
313         if {[eof $fd]} {
314                 close $fd
315                 set status {Annotation complete.}
316         } else {
317                 _status $this
318         }
319 } ifdeleted { catch {close $fd} }
320
321 method _status {} {
322         set have  $blame_lines
323         set total $total_lines
324         set pdone 0
325         if {$total} {set pdone [expr {100 * $have / $total}]}
326
327         set status [format \
328                 "Loading annotations... %i of %i lines annotated (%2i%%)" \
329                 $have $total $pdone]
330 }
331
332 method _click {cur_w pos} {
333         set lno [lindex [split [$cur_w index $pos] .] 0]
334         if {$lno eq {}} return
335
336         set lno_e "$lno.0 + 1 line"
337
338         $w_cgrp tag remove in_sel 0.0 end
339         $w_line tag remove in_sel 0.0 end
340         $w_file tag remove in_sel 0.0 end
341
342         $w_cgrp tag add in_sel $lno.0 $lno_e
343         $w_line tag add in_sel $lno.0 $lno_e
344         $w_file tag add in_sel $lno.0 $lno_e
345
346         _showcommit $this $lno
347 }
348
349 variable blame_colors {
350         #ff4040
351         #ff40ff
352         #4040ff
353 }
354
355 method _showcommit {lno} {
356         global repo_config
357         variable blame_colors
358
359         if {$highlight_commit ne {}} {
360                 set idx $order($highlight_commit)
361                 set i 0
362                 foreach c $blame_colors {
363                         set h [lindex $commit_list [expr {$idx - 1 + $i}]]
364                         $w_cgrp tag conf g$h -background white
365                         $w_line tag conf g$h -background white
366                         $w_file tag conf g$h -background white
367                         incr i
368                 }
369         }
370
371         $w_cmit conf -state normal
372         $w_cmit delete 0.0 end
373         if {[catch {set cmit $line_commit($lno)}]} {
374                 set cmit {}
375                 $w_cmit insert end "Loading annotation..."
376         } else {
377                 set idx $order($cmit)
378                 set i 0
379                 foreach c $blame_colors {
380                         set h [lindex $commit_list [expr {$idx - 1 + $i}]]
381                         $w_cgrp tag conf g$h -background $c
382                         $w_line tag conf g$h -background $c
383                         $w_file tag conf g$h -background $c
384                         incr i
385                 }
386
387                 set author_name {}
388                 set author_email {}
389                 set author_time {}
390                 catch {set author_name $header($cmit,author)}
391                 catch {set author_email $header($cmit,author-mail)}
392                 catch {set author_time [clock format \
393                         $header($cmit,author-time) \
394                         -format {%Y-%m-%d %H:%M:%S}
395                 ]}
396
397                 set committer_name {}
398                 set committer_email {}
399                 set committer_time {}
400                 catch {set committer_name $header($cmit,committer)}
401                 catch {set committer_email $header($cmit,committer-mail)}
402                 catch {set committer_time [clock format \
403                         $header($cmit,committer-time) \
404                         -format {%Y-%m-%d %H:%M:%S}
405                 ]}
406
407                 if {[catch {set msg $header($cmit,message)}]} {
408                         set msg {}
409                         catch {
410                                 set fd [open "| git cat-file commit $cmit" r]
411                                 fconfigure $fd -encoding binary -translation lf
412                                 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
413                                         set enc utf-8
414                                 }
415                                 while {[gets $fd line] > 0} {
416                                         if {[string match {encoding *} $line]} {
417                                                 set enc [string tolower [string range $line 9 end]]
418                                         }
419                                 }
420                                 set msg [encoding convertfrom $enc [read $fd]]
421                                 set msg [string trim $msg]
422                                 close $fd
423
424                                 set author_name [encoding convertfrom $enc $author_name]
425                                 set committer_name [encoding convertfrom $enc $committer_name]
426
427                                 set header($cmit,author) $author_name
428                                 set header($cmit,committer) $committer_name
429                         }
430                         set header($cmit,message) $msg
431                 }
432
433                 $w_cmit insert end "commit $cmit
434 Author: $author_name $author_email  $author_time
435 Committer: $committer_name $committer_email  $committer_time
436 Original File: [escape_path $line_file($lno)]
437
438 $msg"
439         }
440         $w_cmit conf -state disabled
441
442         set highlight_line $lno
443         set highlight_commit $cmit
444 }
445
446 method _copycommit {} {
447         set pos @$::cursorX,$::cursorY
448         set lno [lindex [split [$::cursorW index $pos] .] 0]
449         if {![catch {set commit $line_commit($lno)}]} {
450                 clipboard clear
451                 clipboard append \
452                         -format STRING \
453                         -type STRING \
454                         -- $commit
455         }
456 }
457
458 }