Code

Add initial version of gitk to the CVS repository
[git.git] / gitk
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
5 # Copyright (C) 2005 Paul Mackerras.  All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 set datemode 0
11 set boldnames 0
12 set revtreeargs {}
14 foreach arg $argv {
15     switch -regexp -- $arg {
16         "^$" { }
17         "^-d" { set datemode 1 }
18         "^-b" { set boldnames 1 }
19         "^-.*" {
20             puts stderr "unrecognized option $arg"
21             exit 1
22         }
23         default {
24             lappend revtreeargs $arg
25         }
26     }
27 }
29 proc getcommits {rargs} {
30     global commits parents cdate nparents children nchildren
31     if {$rargs == {}} {
32         set rargs HEAD
33     }
34     set commits {}
35     foreach c [split [eval exec git-rev-tree $rargs] "\n"] {
36         set i 0
37         set cid {}
38         foreach f $c {
39             if {$i == 0} {
40                 set d $f
41             } else {
42                 set id [lindex [split $f :] 0]
43                 if {![info exists nchildren($id)]} {
44                     set children($id) {}
45                     set nchildren($id) 0
46                 }
47                 if {$i == 1} {
48                     set cid $id
49                     lappend commits $id
50                     set parents($id) {}
51                     set cdate($id) $d
52                     set nparents($id) 0
53                 } else {
54                     lappend parents($cid) $id
55                     incr nparents($cid)
56                     incr nchildren($id)
57                     lappend children($id) $cid
58                 }
59             }
60             incr i
61         }
62     }
63 }
65 proc readcommit {id} {
66     global commitinfo
67     set inhdr 1
68     set comment {}
69     set headline {}
70     set auname {}
71     set audate {}
72     set comname {}
73     set comdate {}
74     foreach line [split [exec git-cat-file commit $id] "\n"] {
75         if {$inhdr} {
76             if {$line == {}} {
77                 set inhdr 0
78             } else {
79                 set tag [lindex $line 0]
80                 if {$tag == "author"} {
81                     set x [expr {[llength $line] - 2}]
82                     set audate [lindex $line $x]
83                     set auname [lrange $line 1 [expr {$x - 1}]]
84                 } elseif {$tag == "committer"} {
85                     set x [expr {[llength $line] - 2}]
86                     set comdate [lindex $line $x]
87                     set comname [lrange $line 1 [expr {$x - 1}]]
88                 }
89             }
90         } else {
91             if {$comment == {}} {
92                 set headline $line
93             } else {
94                 append comment "\n"
95             }
96             append comment $line
97         }
98     }
99     if {$audate != {}} {
100         set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
101     }
102     if {$comdate != {}} {
103         set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
104     }
105     set commitinfo($id) [list $comment $auname $audate $comname $comdate]
106     return [list $headline $auname $audate]
109 proc makewindow {} {
110     global canv linespc charspc ctext
111     frame .clist
112     set canv .clist.canv
113     canvas $canv -height [expr 30 * $linespc + 4] -width [expr 90 * $charspc] \
114         -bg white -relief sunk -bd 1 \
115         -yscrollincr $linespc -yscrollcommand ".clist.csb set"
116     scrollbar .clist.csb -command "$canv yview" -highlightthickness 0
117     pack .clist.csb -side right -fill y
118     pack $canv -side bottom -fill both -expand 1
119     pack .clist -side top -fill both -expand 1
120     set ctext .ctext
121     text $ctext -bg white
122     pack $ctext -side top -fill x -expand 1
124     bind $canv <1> {selcanvline %x %y}
125     bind $canv <B1-Motion> {selcanvline %x %y}
126     bind $canv <ButtonRelease-4> "$canv yview scroll -5 u"
127     bind $canv <ButtonRelease-5> "$canv yview scroll 5 u"
128     bind $canv <2> "$canv scan mark 0 %y"
129     bind $canv <B2-Motion> "$canv scan dragto 0 %y"
130     bind . <Key-Prior> "$canv yview scroll -1 p"
131     bind . <Key-Next> "$canv yview scroll 1 p"
132     bind . <Key-Delete> "$canv yview scroll -1 p"
133     bind . <Key-BackSpace> "$canv yview scroll -1 p"
134     bind . <Key-space> "$canv yview scroll 1 p"
135     bind . <Key-Up> "$canv yview scroll -1 u"
136     bind . <Key-Down> "$canv yview scroll 1 u"
137     bind . Q "set stopped 1; destroy ."
140 proc truncatetofit {str width font} {
141     if {[font measure $font $str] <= $width} {
142         return $str
143     }
144     set best 0
145     set bad [string length $str]
146     set tmp $str
147     while {$best < $bad - 1} {
148         set try [expr {int(($best + $bad) / 2)}]
149         set tmp "[string range $str 0 [expr $try-1]]..."
150         if {[font measure $font $tmp] <= $width} {
151             set best $try
152         } else {
153             set bad $try
154         }
155     }
156     return $tmp
159 proc drawgraph {start} {
160     global parents children nparents nchildren commits
161     global canv mainfont namefont canvx0 canvy0 linespc namex datex
162     global datemode cdate
163     global lineid linehtag linentag linedtag
165     set colors {green red blue magenta darkgrey brown orange}
166     set ncolors [llength $colors]
167     set nextcolor 0
168     set colormap($start) [lindex $colors 0]
169     foreach id $commits {
170         set ncleft($id) $nchildren($id)
171     }
172     set todo [list $start]
173     set level 0
174     set canvy $canvy0
175     set linestarty(0) $canvy
176     set nullentry -1
177     set lineno -1
178     while 1 {
179         incr lineno
180         set nlines [llength $todo]
181         set id [lindex $todo $level]
182         set lineid($lineno) $id
183         foreach p $parents($id) {
184             incr ncleft($p) -1
185         }
186         set cinfo [readcommit $id]
187         set x [expr $canvx0 + $level * $linespc]
188         set y2 [expr $canvy + $linespc]
189         if {$linestarty($level) < $canvy} {
190             set t [$canv create line $x $linestarty($level) $x $canvy \
191                        -width 2 -fill $colormap($id)]
192             $canv lower $t
193             set linestarty($level) $canvy
194         }
195         set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
196                    [expr $x + 3] [expr $canvy + 3] \
197                    -fill blue -outline black -width 1]
198         $canv raise $t
199         set xt [expr $canvx0 + $nlines * $linespc]
200         set headline [lindex $cinfo 0]
201         set name [lindex $cinfo 1]
202         set date [lindex $cinfo 2]
203         set headline [truncatetofit $headline [expr $namex-$xt-$linespc] \
204                          $mainfont]
205         set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
206                                    -text $headline -font $mainfont ]
207         set name [truncatetofit $name [expr $datex-$namex-$linespc] $namefont]
208         set linentag($lineno) [$canv create text $namex $canvy -anchor w \
209                                    -text $name -font $namefont]
210         set linedtag($lineno) [$canv create text $datex $canvy -anchor w \
211                                  -text $date -font $mainfont]
212         if {!$datemode && $nparents($id) == 1} {
213             set p [lindex $parents($id) 0]
214             if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
215                 set todo [lreplace $todo $level $level $p]
216                 set colormap($p) $colormap($id)
217                 set canvy $y2
218                 $canv conf -scrollregion [list 0 0 0 $canvy]
219                 update
220                 continue
221             }
222         }
224         set oldtodo $todo
225         set oldlevel $level
226         set lines {}
227         for {set i 0} {$i < $nlines} {incr i} {
228             if {[lindex $todo $i] == {}} continue
229             set oldstarty($i) $linestarty($i)
230             if {$i != $level} {
231                 lappend lines [list $i [lindex $todo $i]]
232             }
233         }
234         unset linestarty
235         if {$nullentry >= 0} {
236             set todo [lreplace $todo $nullentry $nullentry]
237             if {$nullentry < $level} {
238                 incr level -1
239             }
240         }
242         set badcolors [list $colormap($id)]
243         foreach p $parents($id) {
244             if {[info exists colormap($p)]} {
245                 lappend badcolors $colormap($p)
246             }
247         }
248         set todo [lreplace $todo $level $level]
249         if {$nullentry > $level} {
250             incr nullentry -1
251         }
252         set i $level
253         foreach p $parents($id) {
254             set k [lsearch -exact $todo $p]
255             if {$k < 0} {
256                 set todo [linsert $todo $i $p]
257                 if {$nullentry >= $i} {
258                     incr nullentry
259                 }
260                 if {$nparents($id) == 1 && $nparents($p) == 1
261                     && $nchildren($p) == 1} {
262                     set colormap($p) $colormap($id)
263                 } else {
264                     for {set j 0} {$j <= $ncolors} {incr j} {
265                         if {[incr nextcolor] >= $ncolors} {
266                             set nextcolor 0
267                         }
268                         set c [lindex $colors $nextcolor]
269                         # make sure the incoming and outgoing colors differ
270                         if {[lsearch -exact $badcolors $c] < 0} break
271                     }
272                     set colormap($p) $c
273                     lappend badcolors $c
274                 }
275             }
276             lappend lines [list $oldlevel $p]
277         }
279         # choose which one to do next time around
280         set todol [llength $todo]
281         set level -1
282         set latest {}
283         for {set k $todol} {[incr k -1] >= 0} {} {
284             set p [lindex $todo $k]
285             if {$p == {}} continue
286             if {$ncleft($p) == 0} {
287                 if {$datemode} {
288                     if {$latest == {} || $cdate($p) > $latest} {
289                         set level $k
290                         set latest $cdate($p)
291                     }
292                 } else {
293                     set level $k
294                     break
295                 }
296             }
297         }
298         if {$level < 0} {
299             if {$todo != {}} {
300                 puts "ERROR: none of the pending commits can be done yet:"
301                 foreach p $todo {
302                     puts "  $p"
303                 }
304             }
305             break
306         }
308         # If we are reducing, put in a null entry
309         if {$todol < $nlines} {
310             if {$nullentry >= 0} {
311                 set i $nullentry
312                 while {$i < $todol
313                        && [lindex $oldtodo $i] == [lindex $todo $i]} {
314                     incr i
315                 }
316             } else {
317                 set i $oldlevel
318                 if {$level >= $i} {
319                     incr i
320                 }
321             }
322             if {$i >= $todol} {
323                 set nullentry -1
324             } else {
325                 set nullentry $i
326                 set todo [linsert $todo $nullentry {}]
327                 if {$level >= $i} {
328                     incr level
329                 }
330             }
331         } else {
332             set nullentry -1
333         }
335         foreach l $lines {
336             set i [lindex $l 0]
337             set dst [lindex $l 1]
338             set j [lsearch -exact $todo $dst]
339             if {$i == $j} {
340                 set linestarty($i) $oldstarty($i)
341                 continue
342             }
343             set xi [expr {$canvx0 + $i * $linespc}]
344             set xj [expr {$canvx0 + $j * $linespc}]
345             set coords {}
346             if {$oldstarty($i) < $canvy} {
347                 lappend coords $xi $oldstarty($i)
348             }
349             lappend coords $xi $canvy
350             if {$j < $i - 1} {
351                 lappend coords [expr $xj + $linespc] $canvy
352             } elseif {$j > $i + 1} {
353                 lappend coords [expr $xj - $linespc] $canvy
354             }
355             lappend coords $xj $y2
356             set t [$canv create line $coords -width 2 -fill $colormap($dst)]
357             $canv lower $t
358             if {![info exists linestarty($j)]} {
359                 set linestarty($j) $y2
360             }
361         }
362         set canvy $y2
363         $canv conf -scrollregion [list 0 0 0 $canvy]
364         update
365     }
368 proc selcanvline {x y} {
369     global canv canvy0 ctext linespc selectedline
370     global lineid linehtag linentag linedtag commitinfo
371     set ymax [lindex [$canv cget -scrollregion] 3]
372     set yfrac [lindex [$canv yview] 0]
373     set y [expr {$y + $yfrac * $ymax}]
374     set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
375     if {$l < 0} {
376         set l 0
377     }
378     if {[info exists selectedline] && $selectedline == $l} return
379     if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
380     $canv select clear
381     $canv select from $linehtag($l) 0
382     $canv select to $linehtag($l) end
383     set id $lineid($l)
384     $ctext delete 0.0 end
385     set info $commitinfo($id)
386     $ctext insert end "Author: [lindex $info 1]  \t[lindex $info 2]\n"
387     $ctext insert end "Committer: [lindex $info 3]  \t[lindex $info 4]\n"
388     $ctext insert end "\n"
389     $ctext insert end [lindex $info 0]
392 getcommits $revtreeargs
394 set mainfont {Helvetica 9}
395 set namefont $mainfont
396 if {$boldnames} {
397     lappend namefont bold
399 set linespc [font metrics $mainfont -linespace]
400 set charspc [font measure $mainfont "m"]
402 set canvy0 [expr 3 + 0.5 * $linespc]
403 set canvx0 [expr 3 + 0.5 * $linespc]
404 set namex [expr 45 * $charspc]
405 set datex [expr 75 * $charspc]
407 makewindow
409 set start {}
410 foreach id $commits {
411     if {$nchildren($id) == 0} {
412         set start $id
413         break
414     }
416 if {$start != {}} {
417     drawgraph $start