GIT 0.99.4 (release candidate)
[git/gitweb.git] / gitk
blob63369f0ac149073789830e19db25605dc3b7a519
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 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return ".git"
19 proc getcommits {rargs} {
20 global commits commfd phase canv mainfont env
21 global startmsecs nextupdate
22 global ctext maincursor textcursor leftover
24 # check that we can find a .git directory somewhere...
25 set gitdir [gitdir]
26 if {![file isdirectory $gitdir]} {
27 error_popup "Cannot find the git directory \"$gitdir\"."
28 exit 1
30 set commits {}
31 set phase getcommits
32 set startmsecs [clock clicks -milliseconds]
33 set nextupdate [expr $startmsecs + 100]
34 if [catch {
35 set parse_args [concat --default HEAD $rargs]
36 set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
37 }] {
38 # if git-rev-parse failed for some reason...
39 if {$rargs == {}} {
40 set rargs HEAD
42 set parsed_args $rargs
44 if [catch {
45 set commfd [open "|git-rev-list --header --topo-order $parsed_args" r]
46 } err] {
47 puts stderr "Error executing git-rev-list: $err"
48 exit 1
50 set leftover {}
51 fconfigure $commfd -blocking 0 -translation binary
52 fileevent $commfd readable "getcommitlines $commfd"
53 $canv delete all
54 $canv create text 3 3 -anchor nw -text "Reading commits..." \
55 -font $mainfont -tags textitems
56 . config -cursor watch
57 $ctext config -cursor watch
60 proc getcommitlines {commfd} {
61 global commits parents cdate children nchildren
62 global commitlisted phase commitinfo nextupdate
63 global stopped redisplaying leftover
65 set stuff [read $commfd]
66 if {$stuff == {}} {
67 if {![eof $commfd]} return
68 # set it blocking so we wait for the process to terminate
69 fconfigure $commfd -blocking 1
70 if {![catch {close $commfd} err]} {
71 after idle finishcommits
72 return
74 if {[string range $err 0 4] == "usage"} {
75 set err \
76 {Gitk: error reading commits: bad arguments to git-rev-list.
77 (Note: arguments to gitk are passed to git-rev-list
78 to allow selection of commits to be displayed.)}
79 } else {
80 set err "Error reading commits: $err"
82 error_popup $err
83 exit 1
85 set start 0
86 while 1 {
87 set i [string first "\0" $stuff $start]
88 if {$i < 0} {
89 append leftover [string range $stuff $start end]
90 return
92 set cmit [string range $stuff $start [expr {$i - 1}]]
93 if {$start == 0} {
94 set cmit "$leftover$cmit"
95 set leftover {}
97 set start [expr {$i + 1}]
98 if {![regexp {^([0-9a-f]{40})\n} $cmit match id]} {
99 set shortcmit $cmit
100 if {[string length $shortcmit] > 80} {
101 set shortcmit "[string range $shortcmit 0 80]..."
103 error_popup "Can't parse git-rev-list output: {$shortcmit}"
104 exit 1
106 set cmit [string range $cmit 41 end]
107 lappend commits $id
108 set commitlisted($id) 1
109 parsecommit $id $cmit 1
110 drawcommit $id
111 if {[clock clicks -milliseconds] >= $nextupdate} {
112 doupdate
114 while {$redisplaying} {
115 set redisplaying 0
116 if {$stopped == 1} {
117 set stopped 0
118 set phase "getcommits"
119 foreach id $commits {
120 drawcommit $id
121 if {$stopped} break
122 if {[clock clicks -milliseconds] >= $nextupdate} {
123 doupdate
131 proc doupdate {} {
132 global commfd nextupdate
134 incr nextupdate 100
135 fileevent $commfd readable {}
136 update
137 fileevent $commfd readable "getcommitlines $commfd"
140 proc readcommit {id} {
141 if [catch {set contents [exec git-cat-file commit $id]}] return
142 parsecommit $id $contents 0
145 proc parsecommit {id contents listed} {
146 global commitinfo children nchildren parents nparents cdate ncleft
148 set inhdr 1
149 set comment {}
150 set headline {}
151 set auname {}
152 set audate {}
153 set comname {}
154 set comdate {}
155 if {![info exists nchildren($id)]} {
156 set children($id) {}
157 set nchildren($id) 0
158 set ncleft($id) 0
160 set parents($id) {}
161 set nparents($id) 0
162 foreach line [split $contents "\n"] {
163 if {$inhdr} {
164 if {$line == {}} {
165 set inhdr 0
166 } else {
167 set tag [lindex $line 0]
168 if {$tag == "parent"} {
169 set p [lindex $line 1]
170 if {![info exists nchildren($p)]} {
171 set children($p) {}
172 set nchildren($p) 0
173 set ncleft($p) 0
175 lappend parents($id) $p
176 incr nparents($id)
177 # sometimes we get a commit that lists a parent twice...
178 if {$listed && [lsearch -exact $children($p) $id] < 0} {
179 lappend children($p) $id
180 incr nchildren($p)
181 incr ncleft($p)
183 } elseif {$tag == "author"} {
184 set x [expr {[llength $line] - 2}]
185 set audate [lindex $line $x]
186 set auname [lrange $line 1 [expr {$x - 1}]]
187 } elseif {$tag == "committer"} {
188 set x [expr {[llength $line] - 2}]
189 set comdate [lindex $line $x]
190 set comname [lrange $line 1 [expr {$x - 1}]]
193 } else {
194 if {$comment == {}} {
195 set headline [string trim $line]
196 } else {
197 append comment "\n"
199 if {!$listed} {
200 # git-rev-list indents the comment by 4 spaces;
201 # if we got this via git-cat-file, add the indentation
202 append comment " "
204 append comment $line
207 if {$audate != {}} {
208 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
210 if {$comdate != {}} {
211 set cdate($id) $comdate
212 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
214 set commitinfo($id) [list $headline $auname $audate \
215 $comname $comdate $comment]
218 proc readrefs {} {
219 global tagids idtags headids idheads
220 set tags [glob -nocomplain -types f [gitdir]/refs/tags/*]
221 foreach f $tags {
222 catch {
223 set fd [open $f r]
224 set line [read $fd]
225 if {[regexp {^[0-9a-f]{40}} $line id]} {
226 set direct [file tail $f]
227 set tagids($direct) $id
228 lappend idtags($id) $direct
229 set contents [split [exec git-cat-file tag $id] "\n"]
230 set obj {}
231 set type {}
232 set tag {}
233 foreach l $contents {
234 if {$l == {}} break
235 switch -- [lindex $l 0] {
236 "object" {set obj [lindex $l 1]}
237 "type" {set type [lindex $l 1]}
238 "tag" {set tag [string range $l 4 end]}
241 if {$obj != {} && $type == "commit" && $tag != {}} {
242 set tagids($tag) $obj
243 lappend idtags($obj) $tag
246 close $fd
249 set heads [glob -nocomplain -types f [gitdir]/refs/heads/*]
250 foreach f $heads {
251 catch {
252 set fd [open $f r]
253 set line [read $fd 40]
254 if {[regexp {^[0-9a-f]{40}} $line id]} {
255 set head [file tail $f]
256 set headids($head) $line
257 lappend idheads($line) $head
259 close $fd
264 proc error_popup msg {
265 set w .error
266 toplevel $w
267 wm transient $w .
268 message $w.m -text $msg -justify center -aspect 400
269 pack $w.m -side top -fill x -padx 20 -pady 20
270 button $w.ok -text OK -command "destroy $w"
271 pack $w.ok -side bottom -fill x
272 bind $w <Visibility> "grab $w; focus $w"
273 tkwait window $w
276 proc makewindow {} {
277 global canv canv2 canv3 linespc charspc ctext cflist textfont
278 global findtype findtypemenu findloc findstring fstring geometry
279 global entries sha1entry sha1string sha1but
280 global maincursor textcursor
281 global rowctxmenu gaudydiff mergemax
283 menu .bar
284 .bar add cascade -label "File" -menu .bar.file
285 menu .bar.file
286 .bar.file add command -label "Quit" -command doquit
287 menu .bar.help
288 .bar add cascade -label "Help" -menu .bar.help
289 .bar.help add command -label "About gitk" -command about
290 . configure -menu .bar
292 if {![info exists geometry(canv1)]} {
293 set geometry(canv1) [expr 45 * $charspc]
294 set geometry(canv2) [expr 30 * $charspc]
295 set geometry(canv3) [expr 15 * $charspc]
296 set geometry(canvh) [expr 25 * $linespc + 4]
297 set geometry(ctextw) 80
298 set geometry(ctexth) 30
299 set geometry(cflistw) 30
301 panedwindow .ctop -orient vertical
302 if {[info exists geometry(width)]} {
303 .ctop conf -width $geometry(width) -height $geometry(height)
304 set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
305 set geometry(ctexth) [expr {($texth - 8) /
306 [font metrics $textfont -linespace]}]
308 frame .ctop.top
309 frame .ctop.top.bar
310 pack .ctop.top.bar -side bottom -fill x
311 set cscroll .ctop.top.csb
312 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
313 pack $cscroll -side right -fill y
314 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
315 pack .ctop.top.clist -side top -fill both -expand 1
316 .ctop add .ctop.top
317 set canv .ctop.top.clist.canv
318 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
319 -bg white -bd 0 \
320 -yscrollincr $linespc -yscrollcommand "$cscroll set"
321 .ctop.top.clist add $canv
322 set canv2 .ctop.top.clist.canv2
323 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
324 -bg white -bd 0 -yscrollincr $linespc
325 .ctop.top.clist add $canv2
326 set canv3 .ctop.top.clist.canv3
327 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
328 -bg white -bd 0 -yscrollincr $linespc
329 .ctop.top.clist add $canv3
330 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
332 set sha1entry .ctop.top.bar.sha1
333 set entries $sha1entry
334 set sha1but .ctop.top.bar.sha1label
335 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
336 -command gotocommit -width 8
337 $sha1but conf -disabledforeground [$sha1but cget -foreground]
338 pack .ctop.top.bar.sha1label -side left
339 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
340 trace add variable sha1string write sha1change
341 pack $sha1entry -side left -pady 2
343 image create bitmap bm-left -data {
344 #define left_width 16
345 #define left_height 16
346 static unsigned char left_bits[] = {
347 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
348 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
349 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
351 image create bitmap bm-right -data {
352 #define right_width 16
353 #define right_height 16
354 static unsigned char right_bits[] = {
355 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
356 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
357 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
359 button .ctop.top.bar.leftbut -image bm-left -command goback \
360 -state disabled -width 26
361 pack .ctop.top.bar.leftbut -side left -fill y
362 button .ctop.top.bar.rightbut -image bm-right -command goforw \
363 -state disabled -width 26
364 pack .ctop.top.bar.rightbut -side left -fill y
366 button .ctop.top.bar.findbut -text "Find" -command dofind
367 pack .ctop.top.bar.findbut -side left
368 set findstring {}
369 set fstring .ctop.top.bar.findstring
370 lappend entries $fstring
371 entry $fstring -width 30 -font $textfont -textvariable findstring
372 pack $fstring -side left -expand 1 -fill x
373 set findtype Exact
374 set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
375 findtype Exact IgnCase Regexp]
376 set findloc "All fields"
377 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
378 Comments Author Committer Files Pickaxe
379 pack .ctop.top.bar.findloc -side right
380 pack .ctop.top.bar.findtype -side right
381 # for making sure type==Exact whenever loc==Pickaxe
382 trace add variable findloc write findlocchange
384 panedwindow .ctop.cdet -orient horizontal
385 .ctop add .ctop.cdet
386 frame .ctop.cdet.left
387 set ctext .ctop.cdet.left.ctext
388 text $ctext -bg white -state disabled -font $textfont \
389 -width $geometry(ctextw) -height $geometry(ctexth) \
390 -yscrollcommand ".ctop.cdet.left.sb set"
391 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
392 pack .ctop.cdet.left.sb -side right -fill y
393 pack $ctext -side left -fill both -expand 1
394 .ctop.cdet add .ctop.cdet.left
396 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
397 if {$gaudydiff} {
398 $ctext tag conf hunksep -back blue -fore white
399 $ctext tag conf d0 -back "#ff8080"
400 $ctext tag conf d1 -back green
401 } else {
402 $ctext tag conf hunksep -fore blue
403 $ctext tag conf d0 -fore red
404 $ctext tag conf d1 -fore "#00a000"
405 $ctext tag conf m0 -fore red
406 $ctext tag conf m1 -fore blue
407 $ctext tag conf m2 -fore green
408 $ctext tag conf m3 -fore purple
409 $ctext tag conf m4 -fore brown
410 $ctext tag conf mmax -fore darkgrey
411 set mergemax 5
412 $ctext tag conf mresult -font [concat $textfont bold]
413 $ctext tag conf msep -font [concat $textfont bold]
414 $ctext tag conf found -back yellow
417 frame .ctop.cdet.right
418 set cflist .ctop.cdet.right.cfiles
419 listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
420 -yscrollcommand ".ctop.cdet.right.sb set"
421 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
422 pack .ctop.cdet.right.sb -side right -fill y
423 pack $cflist -side left -fill both -expand 1
424 .ctop.cdet add .ctop.cdet.right
425 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
427 pack .ctop -side top -fill both -expand 1
429 bindall <1> {selcanvline %W %x %y}
430 #bindall <B1-Motion> {selcanvline %W %x %y}
431 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
432 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
433 bindall <2> "allcanvs scan mark 0 %y"
434 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
435 bind . <Key-Up> "selnextline -1"
436 bind . <Key-Down> "selnextline 1"
437 bind . <Key-Prior> "allcanvs yview scroll -1 pages"
438 bind . <Key-Next> "allcanvs yview scroll 1 pages"
439 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
440 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
441 bindkey <Key-space> "$ctext yview scroll 1 pages"
442 bindkey p "selnextline -1"
443 bindkey n "selnextline 1"
444 bindkey b "$ctext yview scroll -1 pages"
445 bindkey d "$ctext yview scroll 18 units"
446 bindkey u "$ctext yview scroll -18 units"
447 bindkey / {findnext 1}
448 bindkey <Key-Return> {findnext 0}
449 bindkey ? findprev
450 bindkey f nextfile
451 bind . <Control-q> doquit
452 bind . <Control-f> dofind
453 bind . <Control-g> {findnext 0}
454 bind . <Control-r> findprev
455 bind . <Control-equal> {incrfont 1}
456 bind . <Control-KP_Add> {incrfont 1}
457 bind . <Control-minus> {incrfont -1}
458 bind . <Control-KP_Subtract> {incrfont -1}
459 bind $cflist <<ListboxSelect>> listboxsel
460 bind . <Destroy> {savestuff %W}
461 bind . <Button-1> "click %W"
462 bind $fstring <Key-Return> dofind
463 bind $sha1entry <Key-Return> gotocommit
464 bind $sha1entry <<PasteSelection>> clearsha1
466 set maincursor [. cget -cursor]
467 set textcursor [$ctext cget -cursor]
469 set rowctxmenu .rowctxmenu
470 menu $rowctxmenu -tearoff 0
471 $rowctxmenu add command -label "Diff this -> selected" \
472 -command {diffvssel 0}
473 $rowctxmenu add command -label "Diff selected -> this" \
474 -command {diffvssel 1}
475 $rowctxmenu add command -label "Make patch" -command mkpatch
476 $rowctxmenu add command -label "Create tag" -command mktag
477 $rowctxmenu add command -label "Write commit to file" -command writecommit
480 # when we make a key binding for the toplevel, make sure
481 # it doesn't get triggered when that key is pressed in the
482 # find string entry widget.
483 proc bindkey {ev script} {
484 global entries
485 bind . $ev $script
486 set escript [bind Entry $ev]
487 if {$escript == {}} {
488 set escript [bind Entry <Key>]
490 foreach e $entries {
491 bind $e $ev "$escript; break"
495 # set the focus back to the toplevel for any click outside
496 # the entry widgets
497 proc click {w} {
498 global entries
499 foreach e $entries {
500 if {$w == $e} return
502 focus .
505 proc savestuff {w} {
506 global canv canv2 canv3 ctext cflist mainfont textfont
507 global stuffsaved findmergefiles gaudydiff maxgraphpct
509 if {$stuffsaved} return
510 if {![winfo viewable .]} return
511 catch {
512 set f [open "~/.gitk-new" w]
513 puts $f [list set mainfont $mainfont]
514 puts $f [list set textfont $textfont]
515 puts $f [list set findmergefiles $findmergefiles]
516 puts $f [list set gaudydiff $gaudydiff]
517 puts $f [list set maxgraphpct $maxgraphpct]
518 puts $f "set geometry(width) [winfo width .ctop]"
519 puts $f "set geometry(height) [winfo height .ctop]"
520 puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
521 puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
522 puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
523 puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
524 set wid [expr {([winfo width $ctext] - 8) \
525 / [font measure $textfont "0"]}]
526 puts $f "set geometry(ctextw) $wid"
527 set wid [expr {([winfo width $cflist] - 11) \
528 / [font measure [$cflist cget -font] "0"]}]
529 puts $f "set geometry(cflistw) $wid"
530 close $f
531 file rename -force "~/.gitk-new" "~/.gitk"
533 set stuffsaved 1
536 proc resizeclistpanes {win w} {
537 global oldwidth
538 if [info exists oldwidth($win)] {
539 set s0 [$win sash coord 0]
540 set s1 [$win sash coord 1]
541 if {$w < 60} {
542 set sash0 [expr {int($w/2 - 2)}]
543 set sash1 [expr {int($w*5/6 - 2)}]
544 } else {
545 set factor [expr {1.0 * $w / $oldwidth($win)}]
546 set sash0 [expr {int($factor * [lindex $s0 0])}]
547 set sash1 [expr {int($factor * [lindex $s1 0])}]
548 if {$sash0 < 30} {
549 set sash0 30
551 if {$sash1 < $sash0 + 20} {
552 set sash1 [expr $sash0 + 20]
554 if {$sash1 > $w - 10} {
555 set sash1 [expr $w - 10]
556 if {$sash0 > $sash1 - 20} {
557 set sash0 [expr $sash1 - 20]
561 $win sash place 0 $sash0 [lindex $s0 1]
562 $win sash place 1 $sash1 [lindex $s1 1]
564 set oldwidth($win) $w
567 proc resizecdetpanes {win w} {
568 global oldwidth
569 if [info exists oldwidth($win)] {
570 set s0 [$win sash coord 0]
571 if {$w < 60} {
572 set sash0 [expr {int($w*3/4 - 2)}]
573 } else {
574 set factor [expr {1.0 * $w / $oldwidth($win)}]
575 set sash0 [expr {int($factor * [lindex $s0 0])}]
576 if {$sash0 < 45} {
577 set sash0 45
579 if {$sash0 > $w - 15} {
580 set sash0 [expr $w - 15]
583 $win sash place 0 $sash0 [lindex $s0 1]
585 set oldwidth($win) $w
588 proc allcanvs args {
589 global canv canv2 canv3
590 eval $canv $args
591 eval $canv2 $args
592 eval $canv3 $args
595 proc bindall {event action} {
596 global canv canv2 canv3
597 bind $canv $event $action
598 bind $canv2 $event $action
599 bind $canv3 $event $action
602 proc about {} {
603 set w .about
604 if {[winfo exists $w]} {
605 raise $w
606 return
608 toplevel $w
609 wm title $w "About gitk"
610 message $w.m -text {
611 Gitk version 1.2
613 Copyright © 2005 Paul Mackerras
615 Use and redistribute under the terms of the GNU General Public License} \
616 -justify center -aspect 400
617 pack $w.m -side top -fill x -padx 20 -pady 20
618 button $w.ok -text Close -command "destroy $w"
619 pack $w.ok -side bottom
622 proc assigncolor {id} {
623 global commitinfo colormap commcolors colors nextcolor
624 global parents nparents children nchildren
625 global cornercrossings crossings
627 if [info exists colormap($id)] return
628 set ncolors [llength $colors]
629 if {$nparents($id) <= 1 && $nchildren($id) == 1} {
630 set child [lindex $children($id) 0]
631 if {[info exists colormap($child)]
632 && $nparents($child) == 1} {
633 set colormap($id) $colormap($child)
634 return
637 set badcolors {}
638 if {[info exists cornercrossings($id)]} {
639 foreach x $cornercrossings($id) {
640 if {[info exists colormap($x)]
641 && [lsearch -exact $badcolors $colormap($x)] < 0} {
642 lappend badcolors $colormap($x)
645 if {[llength $badcolors] >= $ncolors} {
646 set badcolors {}
649 set origbad $badcolors
650 if {[llength $badcolors] < $ncolors - 1} {
651 if {[info exists crossings($id)]} {
652 foreach x $crossings($id) {
653 if {[info exists colormap($x)]
654 && [lsearch -exact $badcolors $colormap($x)] < 0} {
655 lappend badcolors $colormap($x)
658 if {[llength $badcolors] >= $ncolors} {
659 set badcolors $origbad
662 set origbad $badcolors
664 if {[llength $badcolors] < $ncolors - 1} {
665 foreach child $children($id) {
666 if {[info exists colormap($child)]
667 && [lsearch -exact $badcolors $colormap($child)] < 0} {
668 lappend badcolors $colormap($child)
670 if {[info exists parents($child)]} {
671 foreach p $parents($child) {
672 if {[info exists colormap($p)]
673 && [lsearch -exact $badcolors $colormap($p)] < 0} {
674 lappend badcolors $colormap($p)
679 if {[llength $badcolors] >= $ncolors} {
680 set badcolors $origbad
683 for {set i 0} {$i <= $ncolors} {incr i} {
684 set c [lindex $colors $nextcolor]
685 if {[incr nextcolor] >= $ncolors} {
686 set nextcolor 0
688 if {[lsearch -exact $badcolors $c]} break
690 set colormap($id) $c
693 proc initgraph {} {
694 global canvy canvy0 lineno numcommits lthickness nextcolor linespc
695 global mainline sidelines
696 global nchildren ncleft
698 allcanvs delete all
699 set nextcolor 0
700 set canvy $canvy0
701 set lineno -1
702 set numcommits 0
703 set lthickness [expr {int($linespc / 9) + 1}]
704 catch {unset mainline}
705 catch {unset sidelines}
706 foreach id [array names nchildren] {
707 set ncleft($id) $nchildren($id)
711 proc bindline {t id} {
712 global canv
714 $canv bind $t <Enter> "lineenter %x %y $id"
715 $canv bind $t <Motion> "linemotion %x %y $id"
716 $canv bind $t <Leave> "lineleave $id"
717 $canv bind $t <Button-1> "lineclick %x %y $id"
720 proc drawcommitline {level} {
721 global parents children nparents nchildren todo
722 global canv canv2 canv3 mainfont namefont canvy linespc
723 global lineid linehtag linentag linedtag commitinfo
724 global colormap numcommits currentparents dupparents
725 global oldlevel oldnlines oldtodo
726 global idtags idline idheads
727 global lineno lthickness mainline sidelines
728 global commitlisted rowtextx idpos
730 incr numcommits
731 incr lineno
732 set id [lindex $todo $level]
733 set lineid($lineno) $id
734 set idline($id) $lineno
735 set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
736 if {![info exists commitinfo($id)]} {
737 readcommit $id
738 if {![info exists commitinfo($id)]} {
739 set commitinfo($id) {"No commit information available"}
740 set nparents($id) 0
743 assigncolor $id
744 set currentparents {}
745 set dupparents {}
746 if {[info exists commitlisted($id)] && [info exists parents($id)]} {
747 foreach p $parents($id) {
748 if {[lsearch -exact $currentparents $p] < 0} {
749 lappend currentparents $p
750 } else {
751 # remember that this parent was listed twice
752 lappend dupparents $p
756 set x [xcoord $level $level $lineno]
757 set y1 $canvy
758 set canvy [expr $canvy + $linespc]
759 allcanvs conf -scrollregion \
760 [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
761 if {[info exists mainline($id)]} {
762 lappend mainline($id) $x $y1
763 set t [$canv create line $mainline($id) \
764 -width $lthickness -fill $colormap($id)]
765 $canv lower $t
766 bindline $t $id
768 if {[info exists sidelines($id)]} {
769 foreach ls $sidelines($id) {
770 set coords [lindex $ls 0]
771 set thick [lindex $ls 1]
772 set t [$canv create line $coords -fill $colormap($id) \
773 -width [expr {$thick * $lthickness}]]
774 $canv lower $t
775 bindline $t $id
778 set orad [expr {$linespc / 3}]
779 set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
780 [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
781 -fill $ofill -outline black -width 1]
782 $canv raise $t
783 $canv bind $t <1> {selcanvline {} %x %y}
784 set xt [xcoord [llength $todo] $level $lineno]
785 if {[llength $currentparents] > 2} {
786 set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
788 set rowtextx($lineno) $xt
789 set idpos($id) [list $x $xt $y1]
790 if {[info exists idtags($id)] || [info exists idheads($id)]} {
791 set xt [drawtags $id $x $xt $y1]
793 set headline [lindex $commitinfo($id) 0]
794 set name [lindex $commitinfo($id) 1]
795 set date [lindex $commitinfo($id) 2]
796 set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
797 -text $headline -font $mainfont ]
798 $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id"
799 set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
800 -text $name -font $namefont]
801 set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
802 -text $date -font $mainfont]
805 proc drawtags {id x xt y1} {
806 global idtags idheads
807 global linespc lthickness
808 global canv mainfont
810 set marks {}
811 set ntags 0
812 if {[info exists idtags($id)]} {
813 set marks $idtags($id)
814 set ntags [llength $marks]
816 if {[info exists idheads($id)]} {
817 set marks [concat $marks $idheads($id)]
819 if {$marks eq {}} {
820 return $xt
823 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
824 set yt [expr $y1 - 0.5 * $linespc]
825 set yb [expr $yt + $linespc - 1]
826 set xvals {}
827 set wvals {}
828 foreach tag $marks {
829 set wid [font measure $mainfont $tag]
830 lappend xvals $xt
831 lappend wvals $wid
832 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
834 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
835 -width $lthickness -fill black -tags tag.$id]
836 $canv lower $t
837 foreach tag $marks x $xvals wid $wvals {
838 set xl [expr $x + $delta]
839 set xr [expr $x + $delta + $wid + $lthickness]
840 if {[incr ntags -1] >= 0} {
841 # draw a tag
842 $canv create polygon $x [expr $yt + $delta] $xl $yt\
843 $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
844 -width 1 -outline black -fill yellow -tags tag.$id
845 } else {
846 # draw a head
847 set xl [expr $xl - $delta/2]
848 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
849 -width 1 -outline black -fill green -tags tag.$id
851 $canv create text $xl $y1 -anchor w -text $tag \
852 -font $mainfont -tags tag.$id
854 return $xt
857 proc updatetodo {level noshortcut} {
858 global currentparents ncleft todo
859 global mainline oldlevel oldtodo oldnlines
860 global canvy linespc mainline
861 global commitinfo lineno xspc1
863 set oldlevel $level
864 set oldtodo $todo
865 set oldnlines [llength $todo]
866 if {!$noshortcut && [llength $currentparents] == 1} {
867 set p [lindex $currentparents 0]
868 if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
869 set ncleft($p) 0
870 set x [xcoord $level $level $lineno]
871 set y [expr $canvy - $linespc]
872 set mainline($p) [list $x $y]
873 set todo [lreplace $todo $level $level $p]
874 set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
875 return 0
879 set todo [lreplace $todo $level $level]
880 set i $level
881 foreach p $currentparents {
882 incr ncleft($p) -1
883 set k [lsearch -exact $todo $p]
884 if {$k < 0} {
885 set todo [linsert $todo $i $p]
886 incr i
889 return 1
892 proc notecrossings {id lo hi corner} {
893 global oldtodo crossings cornercrossings
895 for {set i $lo} {[incr i] < $hi} {} {
896 set p [lindex $oldtodo $i]
897 if {$p == {}} continue
898 if {$i == $corner} {
899 if {![info exists cornercrossings($id)]
900 || [lsearch -exact $cornercrossings($id) $p] < 0} {
901 lappend cornercrossings($id) $p
903 if {![info exists cornercrossings($p)]
904 || [lsearch -exact $cornercrossings($p) $id] < 0} {
905 lappend cornercrossings($p) $id
907 } else {
908 if {![info exists crossings($id)]
909 || [lsearch -exact $crossings($id) $p] < 0} {
910 lappend crossings($id) $p
912 if {![info exists crossings($p)]
913 || [lsearch -exact $crossings($p) $id] < 0} {
914 lappend crossings($p) $id
920 proc xcoord {i level ln} {
921 global canvx0 xspc1 xspc2
923 set x [expr {$canvx0 + $i * $xspc1($ln)}]
924 if {$i > 0 && $i == $level} {
925 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
926 } elseif {$i > $level} {
927 set x [expr {$x + $xspc2 - $xspc1($ln)}]
929 return $x
932 proc drawslants {level} {
933 global canv mainline sidelines canvx0 canvy xspc1 xspc2 lthickness
934 global oldlevel oldtodo todo currentparents dupparents
935 global lthickness linespc canvy colormap lineno geometry
936 global maxgraphpct
938 # decide on the line spacing for the next line
939 set lj [expr {$lineno + 1}]
940 set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
941 set n [llength $todo]
942 if {$n <= 1 || $canvx0 + $n * $xspc2 <= $maxw} {
943 set xspc1($lj) $xspc2
944 } else {
945 set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($n - 1)}]
946 if {$xspc1($lj) < $lthickness} {
947 set xspc1($lj) $lthickness
951 set y1 [expr $canvy - $linespc]
952 set y2 $canvy
953 set i -1
954 foreach id $oldtodo {
955 incr i
956 if {$id == {}} continue
957 set xi [xcoord $i $oldlevel $lineno]
958 if {$i == $oldlevel} {
959 foreach p $currentparents {
960 set j [lsearch -exact $todo $p]
961 set coords [list $xi $y1]
962 set xj [xcoord $j $level $lj]
963 if {$xj < $xi - $linespc} {
964 lappend coords [expr {$xj + $linespc}] $y1
965 notecrossings $p $j $i [expr {$j + 1}]
966 } elseif {$xj > $xi + $linespc} {
967 lappend coords [expr {$xj - $linespc}] $y1
968 notecrossings $p $i $j [expr {$j - 1}]
970 if {[lsearch -exact $dupparents $p] >= 0} {
971 # draw a double-width line to indicate the doubled parent
972 lappend coords $xj $y2
973 lappend sidelines($p) [list $coords 2]
974 if {![info exists mainline($p)]} {
975 set mainline($p) [list $xj $y2]
977 } else {
978 # normal case, no parent duplicated
979 set yb $y2
980 set dx [expr {abs($xi - $xj)}]
981 if {0 && $dx < $linespc} {
982 set yb [expr {$y1 + $dx}]
984 if {![info exists mainline($p)]} {
985 if {$xi != $xj} {
986 lappend coords $xj $yb
988 set mainline($p) $coords
989 } else {
990 lappend coords $xj $yb
991 if {$yb < $y2} {
992 lappend coords $xj $y2
994 lappend sidelines($p) [list $coords 1]
998 } else {
999 set j $i
1000 if {[lindex $todo $i] != $id} {
1001 set j [lsearch -exact $todo $id]
1003 if {$j != $i || $xspc1($lineno) != $xspc1($lj)
1004 || ($oldlevel <= $i && $i <= $level)
1005 || ($level <= $i && $i <= $oldlevel)} {
1006 set xj [xcoord $j $level $lj]
1007 set dx [expr {abs($xi - $xj)}]
1008 set yb $y2
1009 if {0 && $dx < $linespc} {
1010 set yb [expr {$y1 + $dx}]
1012 lappend mainline($id) $xi $y1 $xj $yb
1018 proc decidenext {{noread 0}} {
1019 global parents children nchildren ncleft todo
1020 global canv canv2 canv3 mainfont namefont canvy linespc
1021 global datemode cdate
1022 global commitinfo
1023 global currentparents oldlevel oldnlines oldtodo
1024 global lineno lthickness
1026 # remove the null entry if present
1027 set nullentry [lsearch -exact $todo {}]
1028 if {$nullentry >= 0} {
1029 set todo [lreplace $todo $nullentry $nullentry]
1032 # choose which one to do next time around
1033 set todol [llength $todo]
1034 set level -1
1035 set latest {}
1036 for {set k $todol} {[incr k -1] >= 0} {} {
1037 set p [lindex $todo $k]
1038 if {$ncleft($p) == 0} {
1039 if {$datemode} {
1040 if {![info exists commitinfo($p)]} {
1041 if {$noread} {
1042 return {}
1044 readcommit $p
1046 if {$latest == {} || $cdate($p) > $latest} {
1047 set level $k
1048 set latest $cdate($p)
1050 } else {
1051 set level $k
1052 break
1056 if {$level < 0} {
1057 if {$todo != {}} {
1058 puts "ERROR: none of the pending commits can be done yet:"
1059 foreach p $todo {
1060 puts " $p ($ncleft($p))"
1063 return -1
1066 # If we are reducing, put in a null entry
1067 if {$todol < $oldnlines} {
1068 if {$nullentry >= 0} {
1069 set i $nullentry
1070 while {$i < $todol
1071 && [lindex $oldtodo $i] == [lindex $todo $i]} {
1072 incr i
1074 } else {
1075 set i $oldlevel
1076 if {$level >= $i} {
1077 incr i
1080 if {$i < $todol} {
1081 set todo [linsert $todo $i {}]
1082 if {$level >= $i} {
1083 incr level
1087 return $level
1090 proc drawcommit {id} {
1091 global phase todo nchildren datemode nextupdate
1092 global startcommits
1094 if {$phase != "incrdraw"} {
1095 set phase incrdraw
1096 set todo $id
1097 set startcommits $id
1098 initgraph
1099 drawcommitline 0
1100 updatetodo 0 $datemode
1101 } else {
1102 if {$nchildren($id) == 0} {
1103 lappend todo $id
1104 lappend startcommits $id
1106 set level [decidenext 1]
1107 if {$level == {} || $id != [lindex $todo $level]} {
1108 return
1110 while 1 {
1111 drawslants $level
1112 drawcommitline $level
1113 if {[updatetodo $level $datemode]} {
1114 set level [decidenext 1]
1115 if {$level == {}} break
1117 set id [lindex $todo $level]
1118 if {![info exists commitlisted($id)]} {
1119 break
1121 if {[clock clicks -milliseconds] >= $nextupdate} {
1122 doupdate
1123 if {$stopped} break
1129 proc finishcommits {} {
1130 global phase
1131 global startcommits
1132 global canv mainfont ctext maincursor textcursor
1134 if {$phase != "incrdraw"} {
1135 $canv delete all
1136 $canv create text 3 3 -anchor nw -text "No commits selected" \
1137 -font $mainfont -tags textitems
1138 set phase {}
1139 } else {
1140 set level [decidenext]
1141 drawslants $level
1142 drawrest $level [llength $startcommits]
1144 . config -cursor $maincursor
1145 $ctext config -cursor $textcursor
1148 proc drawgraph {} {
1149 global nextupdate startmsecs startcommits todo
1151 if {$startcommits == {}} return
1152 set startmsecs [clock clicks -milliseconds]
1153 set nextupdate [expr $startmsecs + 100]
1154 initgraph
1155 set todo [lindex $startcommits 0]
1156 drawrest 0 1
1159 proc drawrest {level startix} {
1160 global phase stopped redisplaying selectedline
1161 global datemode currentparents todo
1162 global numcommits
1163 global nextupdate startmsecs startcommits idline
1165 if {$level >= 0} {
1166 set phase drawgraph
1167 set startid [lindex $startcommits $startix]
1168 set startline -1
1169 if {$startid != {}} {
1170 set startline $idline($startid)
1172 while 1 {
1173 if {$stopped} break
1174 drawcommitline $level
1175 set hard [updatetodo $level $datemode]
1176 if {$numcommits == $startline} {
1177 lappend todo $startid
1178 set hard 1
1179 incr startix
1180 set startid [lindex $startcommits $startix]
1181 set startline -1
1182 if {$startid != {}} {
1183 set startline $idline($startid)
1186 if {$hard} {
1187 set level [decidenext]
1188 if {$level < 0} break
1189 drawslants $level
1191 if {[clock clicks -milliseconds] >= $nextupdate} {
1192 update
1193 incr nextupdate 100
1197 set phase {}
1198 set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
1199 #puts "overall $drawmsecs ms for $numcommits commits"
1200 if {$redisplaying} {
1201 if {$stopped == 0 && [info exists selectedline]} {
1202 selectline $selectedline 0
1204 if {$stopped == 1} {
1205 set stopped 0
1206 after idle drawgraph
1207 } else {
1208 set redisplaying 0
1213 proc findmatches {f} {
1214 global findtype foundstring foundstrlen
1215 if {$findtype == "Regexp"} {
1216 set matches [regexp -indices -all -inline $foundstring $f]
1217 } else {
1218 if {$findtype == "IgnCase"} {
1219 set str [string tolower $f]
1220 } else {
1221 set str $f
1223 set matches {}
1224 set i 0
1225 while {[set j [string first $foundstring $str $i]] >= 0} {
1226 lappend matches [list $j [expr $j+$foundstrlen-1]]
1227 set i [expr $j + $foundstrlen]
1230 return $matches
1233 proc dofind {} {
1234 global findtype findloc findstring markedmatches commitinfo
1235 global numcommits lineid linehtag linentag linedtag
1236 global mainfont namefont canv canv2 canv3 selectedline
1237 global matchinglines foundstring foundstrlen
1239 stopfindproc
1240 unmarkmatches
1241 focus .
1242 set matchinglines {}
1243 if {$findloc == "Pickaxe"} {
1244 findpatches
1245 return
1247 if {$findtype == "IgnCase"} {
1248 set foundstring [string tolower $findstring]
1249 } else {
1250 set foundstring $findstring
1252 set foundstrlen [string length $findstring]
1253 if {$foundstrlen == 0} return
1254 if {$findloc == "Files"} {
1255 findfiles
1256 return
1258 if {![info exists selectedline]} {
1259 set oldsel -1
1260 } else {
1261 set oldsel $selectedline
1263 set didsel 0
1264 set fldtypes {Headline Author Date Committer CDate Comment}
1265 for {set l 0} {$l < $numcommits} {incr l} {
1266 set id $lineid($l)
1267 set info $commitinfo($id)
1268 set doesmatch 0
1269 foreach f $info ty $fldtypes {
1270 if {$findloc != "All fields" && $findloc != $ty} {
1271 continue
1273 set matches [findmatches $f]
1274 if {$matches == {}} continue
1275 set doesmatch 1
1276 if {$ty == "Headline"} {
1277 markmatches $canv $l $f $linehtag($l) $matches $mainfont
1278 } elseif {$ty == "Author"} {
1279 markmatches $canv2 $l $f $linentag($l) $matches $namefont
1280 } elseif {$ty == "Date"} {
1281 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
1284 if {$doesmatch} {
1285 lappend matchinglines $l
1286 if {!$didsel && $l > $oldsel} {
1287 findselectline $l
1288 set didsel 1
1292 if {$matchinglines == {}} {
1293 bell
1294 } elseif {!$didsel} {
1295 findselectline [lindex $matchinglines 0]
1299 proc findselectline {l} {
1300 global findloc commentend ctext
1301 selectline $l 1
1302 if {$findloc == "All fields" || $findloc == "Comments"} {
1303 # highlight the matches in the comments
1304 set f [$ctext get 1.0 $commentend]
1305 set matches [findmatches $f]
1306 foreach match $matches {
1307 set start [lindex $match 0]
1308 set end [expr [lindex $match 1] + 1]
1309 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
1314 proc findnext {restart} {
1315 global matchinglines selectedline
1316 if {![info exists matchinglines]} {
1317 if {$restart} {
1318 dofind
1320 return
1322 if {![info exists selectedline]} return
1323 foreach l $matchinglines {
1324 if {$l > $selectedline} {
1325 findselectline $l
1326 return
1329 bell
1332 proc findprev {} {
1333 global matchinglines selectedline
1334 if {![info exists matchinglines]} {
1335 dofind
1336 return
1338 if {![info exists selectedline]} return
1339 set prev {}
1340 foreach l $matchinglines {
1341 if {$l >= $selectedline} break
1342 set prev $l
1344 if {$prev != {}} {
1345 findselectline $prev
1346 } else {
1347 bell
1351 proc findlocchange {name ix op} {
1352 global findloc findtype findtypemenu
1353 if {$findloc == "Pickaxe"} {
1354 set findtype Exact
1355 set state disabled
1356 } else {
1357 set state normal
1359 $findtypemenu entryconf 1 -state $state
1360 $findtypemenu entryconf 2 -state $state
1363 proc stopfindproc {{done 0}} {
1364 global findprocpid findprocfile findids
1365 global ctext findoldcursor phase maincursor textcursor
1366 global findinprogress
1368 catch {unset findids}
1369 if {[info exists findprocpid]} {
1370 if {!$done} {
1371 catch {exec kill $findprocpid}
1373 catch {close $findprocfile}
1374 unset findprocpid
1376 if {[info exists findinprogress]} {
1377 unset findinprogress
1378 if {$phase != "incrdraw"} {
1379 . config -cursor $maincursor
1380 $ctext config -cursor $textcursor
1385 proc findpatches {} {
1386 global findstring selectedline numcommits
1387 global findprocpid findprocfile
1388 global finddidsel ctext lineid findinprogress
1389 global findinsertpos
1391 if {$numcommits == 0} return
1393 # make a list of all the ids to search, starting at the one
1394 # after the selected line (if any)
1395 if {[info exists selectedline]} {
1396 set l $selectedline
1397 } else {
1398 set l -1
1400 set inputids {}
1401 for {set i 0} {$i < $numcommits} {incr i} {
1402 if {[incr l] >= $numcommits} {
1403 set l 0
1405 append inputids $lineid($l) "\n"
1408 if {[catch {
1409 set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
1410 << $inputids] r]
1411 } err]} {
1412 error_popup "Error starting search process: $err"
1413 return
1416 set findinsertpos end
1417 set findprocfile $f
1418 set findprocpid [pid $f]
1419 fconfigure $f -blocking 0
1420 fileevent $f readable readfindproc
1421 set finddidsel 0
1422 . config -cursor watch
1423 $ctext config -cursor watch
1424 set findinprogress 1
1427 proc readfindproc {} {
1428 global findprocfile finddidsel
1429 global idline matchinglines findinsertpos
1431 set n [gets $findprocfile line]
1432 if {$n < 0} {
1433 if {[eof $findprocfile]} {
1434 stopfindproc 1
1435 if {!$finddidsel} {
1436 bell
1439 return
1441 if {![regexp {^[0-9a-f]{40}} $line id]} {
1442 error_popup "Can't parse git-diff-tree output: $line"
1443 stopfindproc
1444 return
1446 if {![info exists idline($id)]} {
1447 puts stderr "spurious id: $id"
1448 return
1450 set l $idline($id)
1451 insertmatch $l $id
1454 proc insertmatch {l id} {
1455 global matchinglines findinsertpos finddidsel
1457 if {$findinsertpos == "end"} {
1458 if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
1459 set matchinglines [linsert $matchinglines 0 $l]
1460 set findinsertpos 1
1461 } else {
1462 lappend matchinglines $l
1464 } else {
1465 set matchinglines [linsert $matchinglines $findinsertpos $l]
1466 incr findinsertpos
1468 markheadline $l $id
1469 if {!$finddidsel} {
1470 findselectline $l
1471 set finddidsel 1
1475 proc findfiles {} {
1476 global selectedline numcommits lineid ctext
1477 global ffileline finddidsel parents nparents
1478 global findinprogress findstartline findinsertpos
1479 global treediffs fdiffids fdiffsneeded fdiffpos
1480 global findmergefiles
1482 if {$numcommits == 0} return
1484 if {[info exists selectedline]} {
1485 set l [expr {$selectedline + 1}]
1486 } else {
1487 set l 0
1489 set ffileline $l
1490 set findstartline $l
1491 set diffsneeded {}
1492 set fdiffsneeded {}
1493 while 1 {
1494 set id $lineid($l)
1495 if {$findmergefiles || $nparents($id) == 1} {
1496 foreach p $parents($id) {
1497 if {![info exists treediffs([list $id $p])]} {
1498 append diffsneeded "$id $p\n"
1499 lappend fdiffsneeded [list $id $p]
1503 if {[incr l] >= $numcommits} {
1504 set l 0
1506 if {$l == $findstartline} break
1509 # start off a git-diff-tree process if needed
1510 if {$diffsneeded ne {}} {
1511 if {[catch {
1512 set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
1513 } err ]} {
1514 error_popup "Error starting search process: $err"
1515 return
1517 catch {unset fdiffids}
1518 set fdiffpos 0
1519 fconfigure $df -blocking 0
1520 fileevent $df readable [list readfilediffs $df]
1523 set finddidsel 0
1524 set findinsertpos end
1525 set id $lineid($l)
1526 set p [lindex $parents($id) 0]
1527 . config -cursor watch
1528 $ctext config -cursor watch
1529 set findinprogress 1
1530 findcont [list $id $p]
1531 update
1534 proc readfilediffs {df} {
1535 global findids fdiffids fdiffs
1537 set n [gets $df line]
1538 if {$n < 0} {
1539 if {[eof $df]} {
1540 donefilediff
1541 if {[catch {close $df} err]} {
1542 stopfindproc
1543 bell
1544 error_popup "Error in git-diff-tree: $err"
1545 } elseif {[info exists findids]} {
1546 set ids $findids
1547 stopfindproc
1548 bell
1549 error_popup "Couldn't find diffs for {$ids}"
1552 return
1554 if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
1555 # start of a new string of diffs
1556 donefilediff
1557 set fdiffids [list $id $p]
1558 set fdiffs {}
1559 } elseif {[string match ":*" $line]} {
1560 lappend fdiffs [lindex $line 5]
1564 proc donefilediff {} {
1565 global fdiffids fdiffs treediffs findids
1566 global fdiffsneeded fdiffpos
1568 if {[info exists fdiffids]} {
1569 while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
1570 && $fdiffpos < [llength $fdiffsneeded]} {
1571 # git-diff-tree doesn't output anything for a commit
1572 # which doesn't change anything
1573 set nullids [lindex $fdiffsneeded $fdiffpos]
1574 set treediffs($nullids) {}
1575 if {[info exists findids] && $nullids eq $findids} {
1576 unset findids
1577 findcont $nullids
1579 incr fdiffpos
1581 incr fdiffpos
1583 if {![info exists treediffs($fdiffids)]} {
1584 set treediffs($fdiffids) $fdiffs
1586 if {[info exists findids] && $fdiffids eq $findids} {
1587 unset findids
1588 findcont $fdiffids
1593 proc findcont {ids} {
1594 global findids treediffs parents nparents
1595 global ffileline findstartline finddidsel
1596 global lineid numcommits matchinglines findinprogress
1597 global findmergefiles
1599 set id [lindex $ids 0]
1600 set p [lindex $ids 1]
1601 set pi [lsearch -exact $parents($id) $p]
1602 set l $ffileline
1603 while 1 {
1604 if {$findmergefiles || $nparents($id) == 1} {
1605 if {![info exists treediffs($ids)]} {
1606 set findids $ids
1607 set ffileline $l
1608 return
1610 set doesmatch 0
1611 foreach f $treediffs($ids) {
1612 set x [findmatches $f]
1613 if {$x != {}} {
1614 set doesmatch 1
1615 break
1618 if {$doesmatch} {
1619 insertmatch $l $id
1620 set pi $nparents($id)
1622 } else {
1623 set pi $nparents($id)
1625 if {[incr pi] >= $nparents($id)} {
1626 set pi 0
1627 if {[incr l] >= $numcommits} {
1628 set l 0
1630 if {$l == $findstartline} break
1631 set id $lineid($l)
1633 set p [lindex $parents($id) $pi]
1634 set ids [list $id $p]
1636 stopfindproc
1637 if {!$finddidsel} {
1638 bell
1642 # mark a commit as matching by putting a yellow background
1643 # behind the headline
1644 proc markheadline {l id} {
1645 global canv mainfont linehtag commitinfo
1647 set bbox [$canv bbox $linehtag($l)]
1648 set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
1649 $canv lower $t
1652 # mark the bits of a headline, author or date that match a find string
1653 proc markmatches {canv l str tag matches font} {
1654 set bbox [$canv bbox $tag]
1655 set x0 [lindex $bbox 0]
1656 set y0 [lindex $bbox 1]
1657 set y1 [lindex $bbox 3]
1658 foreach match $matches {
1659 set start [lindex $match 0]
1660 set end [lindex $match 1]
1661 if {$start > $end} continue
1662 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
1663 set xlen [font measure $font [string range $str 0 [expr $end]]]
1664 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
1665 -outline {} -tags matches -fill yellow]
1666 $canv lower $t
1670 proc unmarkmatches {} {
1671 global matchinglines findids
1672 allcanvs delete matches
1673 catch {unset matchinglines}
1674 catch {unset findids}
1677 proc selcanvline {w x y} {
1678 global canv canvy0 ctext linespc selectedline
1679 global lineid linehtag linentag linedtag rowtextx
1680 set ymax [lindex [$canv cget -scrollregion] 3]
1681 if {$ymax == {}} return
1682 set yfrac [lindex [$canv yview] 0]
1683 set y [expr {$y + $yfrac * $ymax}]
1684 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
1685 if {$l < 0} {
1686 set l 0
1688 if {$w eq $canv} {
1689 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
1691 unmarkmatches
1692 selectline $l 1
1695 proc selectline {l isnew} {
1696 global canv canv2 canv3 ctext commitinfo selectedline
1697 global lineid linehtag linentag linedtag
1698 global canvy0 linespc parents nparents
1699 global cflist currentid sha1entry
1700 global commentend idtags idline
1701 global history historyindex
1703 $canv delete hover
1704 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
1705 $canv delete secsel
1706 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
1707 -tags secsel -fill [$canv cget -selectbackground]]
1708 $canv lower $t
1709 $canv2 delete secsel
1710 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
1711 -tags secsel -fill [$canv2 cget -selectbackground]]
1712 $canv2 lower $t
1713 $canv3 delete secsel
1714 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
1715 -tags secsel -fill [$canv3 cget -selectbackground]]
1716 $canv3 lower $t
1717 set y [expr {$canvy0 + $l * $linespc}]
1718 set ymax [lindex [$canv cget -scrollregion] 3]
1719 set ytop [expr {$y - $linespc - 1}]
1720 set ybot [expr {$y + $linespc + 1}]
1721 set wnow [$canv yview]
1722 set wtop [expr [lindex $wnow 0] * $ymax]
1723 set wbot [expr [lindex $wnow 1] * $ymax]
1724 set wh [expr {$wbot - $wtop}]
1725 set newtop $wtop
1726 if {$ytop < $wtop} {
1727 if {$ybot < $wtop} {
1728 set newtop [expr {$y - $wh / 2.0}]
1729 } else {
1730 set newtop $ytop
1731 if {$newtop > $wtop - $linespc} {
1732 set newtop [expr {$wtop - $linespc}]
1735 } elseif {$ybot > $wbot} {
1736 if {$ytop > $wbot} {
1737 set newtop [expr {$y - $wh / 2.0}]
1738 } else {
1739 set newtop [expr {$ybot - $wh}]
1740 if {$newtop < $wtop + $linespc} {
1741 set newtop [expr {$wtop + $linespc}]
1745 if {$newtop != $wtop} {
1746 if {$newtop < 0} {
1747 set newtop 0
1749 allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
1752 if {$isnew && (![info exists selectedline] || $selectedline != $l)} {
1753 if {$historyindex < [llength $history]} {
1754 set history [lreplace $history $historyindex end $l]
1755 } else {
1756 lappend history $l
1758 incr historyindex
1759 if {$historyindex > 1} {
1760 .ctop.top.bar.leftbut conf -state normal
1761 } else {
1762 .ctop.top.bar.leftbut conf -state disabled
1764 .ctop.top.bar.rightbut conf -state disabled
1767 set selectedline $l
1769 set id $lineid($l)
1770 set currentid $id
1771 $sha1entry delete 0 end
1772 $sha1entry insert 0 $id
1773 $sha1entry selection from 0
1774 $sha1entry selection to end
1776 $ctext conf -state normal
1777 $ctext delete 0.0 end
1778 $ctext mark set fmark.0 0.0
1779 $ctext mark gravity fmark.0 left
1780 set info $commitinfo($id)
1781 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
1782 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
1783 if {[info exists idtags($id)]} {
1784 $ctext insert end "Tags:"
1785 foreach tag $idtags($id) {
1786 $ctext insert end " $tag"
1788 $ctext insert end "\n"
1790 $ctext insert end "\n"
1791 set commentstart [$ctext index "end - 1c"]
1792 set comment [lindex $info 5]
1793 $ctext insert end $comment
1794 $ctext insert end "\n"
1796 # make anything that looks like a SHA1 ID be a clickable link
1797 set links [regexp -indices -all -inline {[0-9a-f]{40}} $comment]
1798 set i 0
1799 foreach l $links {
1800 set s [lindex $l 0]
1801 set e [lindex $l 1]
1802 set linkid [string range $comment $s $e]
1803 if {![info exists idline($linkid)]} continue
1804 incr e
1805 incr i
1806 $ctext tag conf link$i -foreground blue -underline 1
1807 $ctext tag add link$i "$commentstart + $s c" "$commentstart + $e c"
1808 $ctext tag bind link$i <1> [list selectline $idline($linkid) 1]
1811 $ctext tag delete Comments
1812 $ctext tag remove found 1.0 end
1813 $ctext conf -state disabled
1814 set commentend [$ctext index "end - 1c"]
1816 $cflist delete 0 end
1817 $cflist insert end "Comments"
1818 if {$nparents($id) == 1} {
1819 startdiff [concat $id $parents($id)]
1820 } elseif {$nparents($id) > 1} {
1821 mergediff $id
1825 proc selnextline {dir} {
1826 global selectedline
1827 if {![info exists selectedline]} return
1828 set l [expr $selectedline + $dir]
1829 unmarkmatches
1830 selectline $l 1
1833 proc goback {} {
1834 global history historyindex
1836 if {$historyindex > 1} {
1837 incr historyindex -1
1838 selectline [lindex $history [expr {$historyindex - 1}]] 0
1839 .ctop.top.bar.rightbut conf -state normal
1841 if {$historyindex <= 1} {
1842 .ctop.top.bar.leftbut conf -state disabled
1846 proc goforw {} {
1847 global history historyindex
1849 if {$historyindex < [llength $history]} {
1850 set l [lindex $history $historyindex]
1851 incr historyindex
1852 selectline $l 0
1853 .ctop.top.bar.leftbut conf -state normal
1855 if {$historyindex >= [llength $history]} {
1856 .ctop.top.bar.rightbut conf -state disabled
1860 proc mergediff {id} {
1861 global parents diffmergeid diffmergegca mergefilelist diffpindex
1863 set diffmergeid $id
1864 set diffpindex -1
1865 set diffmergegca [findgca $parents($id)]
1866 if {[info exists mergefilelist($id)]} {
1867 if {$mergefilelist($id) ne {}} {
1868 showmergediff
1870 } else {
1871 contmergediff {}
1875 proc findgca {ids} {
1876 set gca {}
1877 foreach id $ids {
1878 if {$gca eq {}} {
1879 set gca $id
1880 } else {
1881 if {[catch {
1882 set gca [exec git-merge-base $gca $id]
1883 } err]} {
1884 return {}
1888 return $gca
1891 proc contmergediff {ids} {
1892 global diffmergeid diffpindex parents nparents diffmergegca
1893 global treediffs mergefilelist diffids treepending
1895 # diff the child against each of the parents, and diff
1896 # each of the parents against the GCA.
1897 while 1 {
1898 if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
1899 set ids [list [lindex $ids 1] $diffmergegca]
1900 } else {
1901 if {[incr diffpindex] >= $nparents($diffmergeid)} break
1902 set p [lindex $parents($diffmergeid) $diffpindex]
1903 set ids [list $diffmergeid $p]
1905 if {![info exists treediffs($ids)]} {
1906 set diffids $ids
1907 if {![info exists treepending]} {
1908 gettreediffs $ids
1910 return
1914 # If a file in some parent is different from the child and also
1915 # different from the GCA, then it's interesting.
1916 # If we don't have a GCA, then a file is interesting if it is
1917 # different from the child in all the parents.
1918 if {$diffmergegca ne {}} {
1919 set files {}
1920 foreach p $parents($diffmergeid) {
1921 set gcadiffs $treediffs([list $p $diffmergegca])
1922 foreach f $treediffs([list $diffmergeid $p]) {
1923 if {[lsearch -exact $files $f] < 0
1924 && [lsearch -exact $gcadiffs $f] >= 0} {
1925 lappend files $f
1929 set files [lsort $files]
1930 } else {
1931 set p [lindex $parents($diffmergeid) 0]
1932 set files $treediffs([list $diffmergeid $p])
1933 for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
1934 set p [lindex $parents($diffmergeid) $i]
1935 set df $treediffs([list $diffmergeid $p])
1936 set nf {}
1937 foreach f $files {
1938 if {[lsearch -exact $df $f] >= 0} {
1939 lappend nf $f
1942 set files $nf
1946 set mergefilelist($diffmergeid) $files
1947 if {$files ne {}} {
1948 showmergediff
1952 proc showmergediff {} {
1953 global cflist diffmergeid mergefilelist parents
1954 global diffopts diffinhunk currentfile currenthunk filelines
1955 global diffblocked groupfilelast mergefds groupfilenum grouphunks
1957 set files $mergefilelist($diffmergeid)
1958 foreach f $files {
1959 $cflist insert end $f
1961 set env(GIT_DIFF_OPTS) $diffopts
1962 set flist {}
1963 catch {unset currentfile}
1964 catch {unset currenthunk}
1965 catch {unset filelines}
1966 catch {unset groupfilenum}
1967 catch {unset grouphunks}
1968 set groupfilelast -1
1969 foreach p $parents($diffmergeid) {
1970 set cmd [list | git-diff-tree -p $p $diffmergeid]
1971 set cmd [concat $cmd $mergefilelist($diffmergeid)]
1972 if {[catch {set f [open $cmd r]} err]} {
1973 error_popup "Error getting diffs: $err"
1974 foreach f $flist {
1975 catch {close $f}
1977 return
1979 lappend flist $f
1980 set ids [list $diffmergeid $p]
1981 set mergefds($ids) $f
1982 set diffinhunk($ids) 0
1983 set diffblocked($ids) 0
1984 fconfigure $f -blocking 0
1985 fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
1989 proc getmergediffline {f ids id} {
1990 global diffmergeid diffinhunk diffoldlines diffnewlines
1991 global currentfile currenthunk
1992 global diffoldstart diffnewstart diffoldlno diffnewlno
1993 global diffblocked mergefilelist
1994 global noldlines nnewlines difflcounts filelines
1996 set n [gets $f line]
1997 if {$n < 0} {
1998 if {![eof $f]} return
2001 if {!([info exists diffmergeid] && $diffmergeid == $id)} {
2002 if {$n < 0} {
2003 close $f
2005 return
2008 if {$diffinhunk($ids) != 0} {
2009 set fi $currentfile($ids)
2010 if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
2011 # continuing an existing hunk
2012 set line [string range $line 1 end]
2013 set p [lindex $ids 1]
2014 if {$match eq "-" || $match eq " "} {
2015 set filelines($p,$fi,$diffoldlno($ids)) $line
2016 incr diffoldlno($ids)
2018 if {$match eq "+" || $match eq " "} {
2019 set filelines($id,$fi,$diffnewlno($ids)) $line
2020 incr diffnewlno($ids)
2022 if {$match eq " "} {
2023 if {$diffinhunk($ids) == 2} {
2024 lappend difflcounts($ids) \
2025 [list $noldlines($ids) $nnewlines($ids)]
2026 set noldlines($ids) 0
2027 set diffinhunk($ids) 1
2029 incr noldlines($ids)
2030 } elseif {$match eq "-" || $match eq "+"} {
2031 if {$diffinhunk($ids) == 1} {
2032 lappend difflcounts($ids) [list $noldlines($ids)]
2033 set noldlines($ids) 0
2034 set nnewlines($ids) 0
2035 set diffinhunk($ids) 2
2037 if {$match eq "-"} {
2038 incr noldlines($ids)
2039 } else {
2040 incr nnewlines($ids)
2043 # and if it's \ No newline at end of line, then what?
2044 return
2046 # end of a hunk
2047 if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
2048 lappend difflcounts($ids) [list $noldlines($ids)]
2049 } elseif {$diffinhunk($ids) == 2
2050 && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
2051 lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
2053 set currenthunk($ids) [list $currentfile($ids) \
2054 $diffoldstart($ids) $diffnewstart($ids) \
2055 $diffoldlno($ids) $diffnewlno($ids) \
2056 $difflcounts($ids)]
2057 set diffinhunk($ids) 0
2058 # -1 = need to block, 0 = unblocked, 1 = is blocked
2059 set diffblocked($ids) -1
2060 processhunks
2061 if {$diffblocked($ids) == -1} {
2062 fileevent $f readable {}
2063 set diffblocked($ids) 1
2067 if {$n < 0} {
2068 # eof
2069 if {!$diffblocked($ids)} {
2070 close $f
2071 set currentfile($ids) [llength $mergefilelist($diffmergeid)]
2072 set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
2073 processhunks
2075 } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
2076 # start of a new file
2077 set currentfile($ids) \
2078 [lsearch -exact $mergefilelist($diffmergeid) $fname]
2079 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2080 $line match f1l f1c f2l f2c rest]} {
2081 if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
2082 # start of a new hunk
2083 if {$f1l == 0 && $f1c == 0} {
2084 set f1l 1
2086 if {$f2l == 0 && $f2c == 0} {
2087 set f2l 1
2089 set diffinhunk($ids) 1
2090 set diffoldstart($ids) $f1l
2091 set diffnewstart($ids) $f2l
2092 set diffoldlno($ids) $f1l
2093 set diffnewlno($ids) $f2l
2094 set difflcounts($ids) {}
2095 set noldlines($ids) 0
2096 set nnewlines($ids) 0
2101 proc processhunks {} {
2102 global diffmergeid parents nparents currenthunk
2103 global mergefilelist diffblocked mergefds
2104 global grouphunks grouplinestart grouplineend groupfilenum
2106 set nfiles [llength $mergefilelist($diffmergeid)]
2107 while 1 {
2108 set fi $nfiles
2109 set lno 0
2110 # look for the earliest hunk
2111 foreach p $parents($diffmergeid) {
2112 set ids [list $diffmergeid $p]
2113 if {![info exists currenthunk($ids)]} return
2114 set i [lindex $currenthunk($ids) 0]
2115 set l [lindex $currenthunk($ids) 2]
2116 if {$i < $fi || ($i == $fi && $l < $lno)} {
2117 set fi $i
2118 set lno $l
2119 set pi $p
2123 if {$fi < $nfiles} {
2124 set ids [list $diffmergeid $pi]
2125 set hunk $currenthunk($ids)
2126 unset currenthunk($ids)
2127 if {$diffblocked($ids) > 0} {
2128 fileevent $mergefds($ids) readable \
2129 [list getmergediffline $mergefds($ids) $ids $diffmergeid]
2131 set diffblocked($ids) 0
2133 if {[info exists groupfilenum] && $groupfilenum == $fi
2134 && $lno <= $grouplineend} {
2135 # add this hunk to the pending group
2136 lappend grouphunks($pi) $hunk
2137 set endln [lindex $hunk 4]
2138 if {$endln > $grouplineend} {
2139 set grouplineend $endln
2141 continue
2145 # succeeding stuff doesn't belong in this group, so
2146 # process the group now
2147 if {[info exists groupfilenum]} {
2148 processgroup
2149 unset groupfilenum
2150 unset grouphunks
2153 if {$fi >= $nfiles} break
2155 # start a new group
2156 set groupfilenum $fi
2157 set grouphunks($pi) [list $hunk]
2158 set grouplinestart $lno
2159 set grouplineend [lindex $hunk 4]
2163 proc processgroup {} {
2164 global groupfilelast groupfilenum difffilestart
2165 global mergefilelist diffmergeid ctext filelines
2166 global parents diffmergeid diffoffset
2167 global grouphunks grouplinestart grouplineend nparents
2168 global mergemax
2170 $ctext conf -state normal
2171 set id $diffmergeid
2172 set f $groupfilenum
2173 if {$groupfilelast != $f} {
2174 $ctext insert end "\n"
2175 set here [$ctext index "end - 1c"]
2176 set difffilestart($f) $here
2177 set mark fmark.[expr {$f + 1}]
2178 $ctext mark set $mark $here
2179 $ctext mark gravity $mark left
2180 set header [lindex $mergefilelist($id) $f]
2181 set l [expr {(78 - [string length $header]) / 2}]
2182 set pad [string range "----------------------------------------" 1 $l]
2183 $ctext insert end "$pad $header $pad\n" filesep
2184 set groupfilelast $f
2185 foreach p $parents($id) {
2186 set diffoffset($p) 0
2190 $ctext insert end "@@" msep
2191 set nlines [expr {$grouplineend - $grouplinestart}]
2192 set events {}
2193 set pnum 0
2194 foreach p $parents($id) {
2195 set startline [expr {$grouplinestart + $diffoffset($p)}]
2196 set ol $startline
2197 set nl $grouplinestart
2198 if {[info exists grouphunks($p)]} {
2199 foreach h $grouphunks($p) {
2200 set l [lindex $h 2]
2201 if {$nl < $l} {
2202 for {} {$nl < $l} {incr nl} {
2203 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2204 incr ol
2207 foreach chunk [lindex $h 5] {
2208 if {[llength $chunk] == 2} {
2209 set olc [lindex $chunk 0]
2210 set nlc [lindex $chunk 1]
2211 set nnl [expr {$nl + $nlc}]
2212 lappend events [list $nl $nnl $pnum $olc $nlc]
2213 incr ol $olc
2214 set nl $nnl
2215 } else {
2216 incr ol [lindex $chunk 0]
2217 incr nl [lindex $chunk 0]
2222 if {$nl < $grouplineend} {
2223 for {} {$nl < $grouplineend} {incr nl} {
2224 set filelines($p,$f,$ol) $filelines($id,$f,$nl)
2225 incr ol
2228 set nlines [expr {$ol - $startline}]
2229 $ctext insert end " -$startline,$nlines" msep
2230 incr pnum
2233 set nlines [expr {$grouplineend - $grouplinestart}]
2234 $ctext insert end " +$grouplinestart,$nlines @@\n" msep
2236 set events [lsort -integer -index 0 $events]
2237 set nevents [llength $events]
2238 set nmerge $nparents($diffmergeid)
2239 set l $grouplinestart
2240 for {set i 0} {$i < $nevents} {set i $j} {
2241 set nl [lindex $events $i 0]
2242 while {$l < $nl} {
2243 $ctext insert end " $filelines($id,$f,$l)\n"
2244 incr l
2246 set e [lindex $events $i]
2247 set enl [lindex $e 1]
2248 set j $i
2249 set active {}
2250 while 1 {
2251 set pnum [lindex $e 2]
2252 set olc [lindex $e 3]
2253 set nlc [lindex $e 4]
2254 if {![info exists delta($pnum)]} {
2255 set delta($pnum) [expr {$olc - $nlc}]
2256 lappend active $pnum
2257 } else {
2258 incr delta($pnum) [expr {$olc - $nlc}]
2260 if {[incr j] >= $nevents} break
2261 set e [lindex $events $j]
2262 if {[lindex $e 0] >= $enl} break
2263 if {[lindex $e 1] > $enl} {
2264 set enl [lindex $e 1]
2267 set nlc [expr {$enl - $l}]
2268 set ncol mresult
2269 set bestpn -1
2270 if {[llength $active] == $nmerge - 1} {
2271 # no diff for one of the parents, i.e. it's identical
2272 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2273 if {![info exists delta($pnum)]} {
2274 if {$pnum < $mergemax} {
2275 lappend ncol m$pnum
2276 } else {
2277 lappend ncol mmax
2279 break
2282 } elseif {[llength $active] == $nmerge} {
2283 # all parents are different, see if one is very similar
2284 set bestsim 30
2285 for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
2286 set sim [similarity $pnum $l $nlc $f \
2287 [lrange $events $i [expr {$j-1}]]]
2288 if {$sim > $bestsim} {
2289 set bestsim $sim
2290 set bestpn $pnum
2293 if {$bestpn >= 0} {
2294 lappend ncol m$bestpn
2297 set pnum -1
2298 foreach p $parents($id) {
2299 incr pnum
2300 if {![info exists delta($pnum)] || $pnum == $bestpn} continue
2301 set olc [expr {$nlc + $delta($pnum)}]
2302 set ol [expr {$l + $diffoffset($p)}]
2303 incr diffoffset($p) $delta($pnum)
2304 unset delta($pnum)
2305 for {} {$olc > 0} {incr olc -1} {
2306 $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
2307 incr ol
2310 set endl [expr {$l + $nlc}]
2311 if {$bestpn >= 0} {
2312 # show this pretty much as a normal diff
2313 set p [lindex $parents($id) $bestpn]
2314 set ol [expr {$l + $diffoffset($p)}]
2315 incr diffoffset($p) $delta($bestpn)
2316 unset delta($bestpn)
2317 for {set k $i} {$k < $j} {incr k} {
2318 set e [lindex $events $k]
2319 if {[lindex $e 2] != $bestpn} continue
2320 set nl [lindex $e 0]
2321 set ol [expr {$ol + $nl - $l}]
2322 for {} {$l < $nl} {incr l} {
2323 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2325 set c [lindex $e 3]
2326 for {} {$c > 0} {incr c -1} {
2327 $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
2328 incr ol
2330 set nl [lindex $e 1]
2331 for {} {$l < $nl} {incr l} {
2332 $ctext insert end "+$filelines($id,$f,$l)\n" mresult
2336 for {} {$l < $endl} {incr l} {
2337 $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
2340 while {$l < $grouplineend} {
2341 $ctext insert end " $filelines($id,$f,$l)\n"
2342 incr l
2344 $ctext conf -state disabled
2347 proc similarity {pnum l nlc f events} {
2348 global diffmergeid parents diffoffset filelines
2350 set id $diffmergeid
2351 set p [lindex $parents($id) $pnum]
2352 set ol [expr {$l + $diffoffset($p)}]
2353 set endl [expr {$l + $nlc}]
2354 set same 0
2355 set diff 0
2356 foreach e $events {
2357 if {[lindex $e 2] != $pnum} continue
2358 set nl [lindex $e 0]
2359 set ol [expr {$ol + $nl - $l}]
2360 for {} {$l < $nl} {incr l} {
2361 incr same [string length $filelines($id,$f,$l)]
2362 incr same
2364 set oc [lindex $e 3]
2365 for {} {$oc > 0} {incr oc -1} {
2366 incr diff [string length $filelines($p,$f,$ol)]
2367 incr diff
2368 incr ol
2370 set nl [lindex $e 1]
2371 for {} {$l < $nl} {incr l} {
2372 incr diff [string length $filelines($id,$f,$l)]
2373 incr diff
2376 for {} {$l < $endl} {incr l} {
2377 incr same [string length $filelines($id,$f,$l)]
2378 incr same
2380 if {$same == 0} {
2381 return 0
2383 return [expr {200 * $same / (2 * $same + $diff)}]
2386 proc startdiff {ids} {
2387 global treediffs diffids treepending diffmergeid
2389 set diffids $ids
2390 catch {unset diffmergeid}
2391 if {![info exists treediffs($ids)]} {
2392 if {![info exists treepending]} {
2393 gettreediffs $ids
2395 } else {
2396 addtocflist $ids
2400 proc addtocflist {ids} {
2401 global treediffs cflist
2402 foreach f $treediffs($ids) {
2403 $cflist insert end $f
2405 getblobdiffs $ids
2408 proc gettreediffs {ids} {
2409 global treediff parents treepending
2410 set treepending $ids
2411 set treediff {}
2412 set id [lindex $ids 0]
2413 set p [lindex $ids 1]
2414 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
2415 fconfigure $gdtf -blocking 0
2416 fileevent $gdtf readable [list gettreediffline $gdtf $ids]
2419 proc gettreediffline {gdtf ids} {
2420 global treediff treediffs treepending diffids diffmergeid
2422 set n [gets $gdtf line]
2423 if {$n < 0} {
2424 if {![eof $gdtf]} return
2425 close $gdtf
2426 set treediffs($ids) $treediff
2427 unset treepending
2428 if {$ids != $diffids} {
2429 gettreediffs $diffids
2430 } else {
2431 if {[info exists diffmergeid]} {
2432 contmergediff $ids
2433 } else {
2434 addtocflist $ids
2437 return
2439 set file [lindex $line 5]
2440 lappend treediff $file
2443 proc getblobdiffs {ids} {
2444 global diffopts blobdifffd diffids env curdifftag curtagstart
2445 global difffilestart nextupdate diffinhdr treediffs
2447 set id [lindex $ids 0]
2448 set p [lindex $ids 1]
2449 set env(GIT_DIFF_OPTS) $diffopts
2450 set cmd [list | git-diff-tree -r -p -C $p $id]
2451 if {[catch {set bdf [open $cmd r]} err]} {
2452 puts "error getting diffs: $err"
2453 return
2455 set diffinhdr 0
2456 fconfigure $bdf -blocking 0
2457 set blobdifffd($ids) $bdf
2458 set curdifftag Comments
2459 set curtagstart 0.0
2460 catch {unset difffilestart}
2461 fileevent $bdf readable [list getblobdiffline $bdf $diffids]
2462 set nextupdate [expr {[clock clicks -milliseconds] + 100}]
2465 proc getblobdiffline {bdf ids} {
2466 global diffids blobdifffd ctext curdifftag curtagstart
2467 global diffnexthead diffnextnote difffilestart
2468 global nextupdate diffinhdr treediffs
2469 global gaudydiff
2471 set n [gets $bdf line]
2472 if {$n < 0} {
2473 if {[eof $bdf]} {
2474 close $bdf
2475 if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
2476 $ctext tag add $curdifftag $curtagstart end
2479 return
2481 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
2482 return
2484 $ctext conf -state normal
2485 if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
2486 # start of a new file
2487 $ctext insert end "\n"
2488 $ctext tag add $curdifftag $curtagstart end
2489 set curtagstart [$ctext index "end - 1c"]
2490 set header $newname
2491 set here [$ctext index "end - 1c"]
2492 set i [lsearch -exact $treediffs($diffids) $fname]
2493 if {$i >= 0} {
2494 set difffilestart($i) $here
2495 incr i
2496 $ctext mark set fmark.$i $here
2497 $ctext mark gravity fmark.$i left
2499 if {$newname != $fname} {
2500 set i [lsearch -exact $treediffs($diffids) $newname]
2501 if {$i >= 0} {
2502 set difffilestart($i) $here
2503 incr i
2504 $ctext mark set fmark.$i $here
2505 $ctext mark gravity fmark.$i left
2508 set curdifftag "f:$fname"
2509 $ctext tag delete $curdifftag
2510 set l [expr {(78 - [string length $header]) / 2}]
2511 set pad [string range "----------------------------------------" 1 $l]
2512 $ctext insert end "$pad $header $pad\n" filesep
2513 set diffinhdr 1
2514 } elseif {[regexp {^(---|\+\+\+)} $line]} {
2515 set diffinhdr 0
2516 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
2517 $line match f1l f1c f2l f2c rest]} {
2518 if {$gaudydiff} {
2519 $ctext insert end "\t" hunksep
2520 $ctext insert end " $f1l " d0 " $f2l " d1
2521 $ctext insert end " $rest \n" hunksep
2522 } else {
2523 $ctext insert end "$line\n" hunksep
2525 set diffinhdr 0
2526 } else {
2527 set x [string range $line 0 0]
2528 if {$x == "-" || $x == "+"} {
2529 set tag [expr {$x == "+"}]
2530 if {$gaudydiff} {
2531 set line [string range $line 1 end]
2533 $ctext insert end "$line\n" d$tag
2534 } elseif {$x == " "} {
2535 if {$gaudydiff} {
2536 set line [string range $line 1 end]
2538 $ctext insert end "$line\n"
2539 } elseif {$diffinhdr || $x == "\\"} {
2540 # e.g. "\ No newline at end of file"
2541 $ctext insert end "$line\n" filesep
2542 } else {
2543 # Something else we don't recognize
2544 if {$curdifftag != "Comments"} {
2545 $ctext insert end "\n"
2546 $ctext tag add $curdifftag $curtagstart end
2547 set curtagstart [$ctext index "end - 1c"]
2548 set curdifftag Comments
2550 $ctext insert end "$line\n" filesep
2553 $ctext conf -state disabled
2554 if {[clock clicks -milliseconds] >= $nextupdate} {
2555 incr nextupdate 100
2556 fileevent $bdf readable {}
2557 update
2558 fileevent $bdf readable "getblobdiffline $bdf {$ids}"
2562 proc nextfile {} {
2563 global difffilestart ctext
2564 set here [$ctext index @0,0]
2565 for {set i 0} {[info exists difffilestart($i)]} {incr i} {
2566 if {[$ctext compare $difffilestart($i) > $here]} {
2567 if {![info exists pos]
2568 || [$ctext compare $difffilestart($i) < $pos]} {
2569 set pos $difffilestart($i)
2573 if {[info exists pos]} {
2574 $ctext yview $pos
2578 proc listboxsel {} {
2579 global ctext cflist currentid
2580 if {![info exists currentid]} return
2581 set sel [lsort [$cflist curselection]]
2582 if {$sel eq {}} return
2583 set first [lindex $sel 0]
2584 catch {$ctext yview fmark.$first}
2587 proc setcoords {} {
2588 global linespc charspc canvx0 canvy0 mainfont
2589 global xspc1 xspc2
2591 set linespc [font metrics $mainfont -linespace]
2592 set charspc [font measure $mainfont "m"]
2593 set canvy0 [expr 3 + 0.5 * $linespc]
2594 set canvx0 [expr 3 + 0.5 * $linespc]
2595 set xspc1(0) $linespc
2596 set xspc2 $linespc
2599 proc redisplay {} {
2600 global selectedline stopped redisplaying phase
2601 if {$stopped > 1} return
2602 if {$phase == "getcommits"} return
2603 set redisplaying 1
2604 if {$phase == "drawgraph" || $phase == "incrdraw"} {
2605 set stopped 1
2606 } else {
2607 drawgraph
2611 proc incrfont {inc} {
2612 global mainfont namefont textfont selectedline ctext canv phase
2613 global stopped entries
2614 unmarkmatches
2615 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
2616 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
2617 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
2618 setcoords
2619 $ctext conf -font $textfont
2620 $ctext tag conf filesep -font [concat $textfont bold]
2621 foreach e $entries {
2622 $e conf -font $mainfont
2624 if {$phase == "getcommits"} {
2625 $canv itemconf textitems -font $mainfont
2627 redisplay
2630 proc clearsha1 {} {
2631 global sha1entry sha1string
2632 if {[string length $sha1string] == 40} {
2633 $sha1entry delete 0 end
2637 proc sha1change {n1 n2 op} {
2638 global sha1string currentid sha1but
2639 if {$sha1string == {}
2640 || ([info exists currentid] && $sha1string == $currentid)} {
2641 set state disabled
2642 } else {
2643 set state normal
2645 if {[$sha1but cget -state] == $state} return
2646 if {$state == "normal"} {
2647 $sha1but conf -state normal -relief raised -text "Goto: "
2648 } else {
2649 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
2653 proc gotocommit {} {
2654 global sha1string currentid idline tagids
2655 global lineid numcommits
2657 if {$sha1string == {}
2658 || ([info exists currentid] && $sha1string == $currentid)} return
2659 if {[info exists tagids($sha1string)]} {
2660 set id $tagids($sha1string)
2661 } else {
2662 set id [string tolower $sha1string]
2663 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
2664 set matches {}
2665 for {set l 0} {$l < $numcommits} {incr l} {
2666 if {[string match $id* $lineid($l)]} {
2667 lappend matches $lineid($l)
2670 if {$matches ne {}} {
2671 if {[llength $matches] > 1} {
2672 error_popup "Short SHA1 id $id is ambiguous"
2673 return
2675 set id [lindex $matches 0]
2679 if {[info exists idline($id)]} {
2680 selectline $idline($id) 1
2681 return
2683 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
2684 set type "SHA1 id"
2685 } else {
2686 set type "Tag"
2688 error_popup "$type $sha1string is not known"
2691 proc lineenter {x y id} {
2692 global hoverx hovery hoverid hovertimer
2693 global commitinfo canv
2695 if {![info exists commitinfo($id)]} return
2696 set hoverx $x
2697 set hovery $y
2698 set hoverid $id
2699 if {[info exists hovertimer]} {
2700 after cancel $hovertimer
2702 set hovertimer [after 500 linehover]
2703 $canv delete hover
2706 proc linemotion {x y id} {
2707 global hoverx hovery hoverid hovertimer
2709 if {[info exists hoverid] && $id == $hoverid} {
2710 set hoverx $x
2711 set hovery $y
2712 if {[info exists hovertimer]} {
2713 after cancel $hovertimer
2715 set hovertimer [after 500 linehover]
2719 proc lineleave {id} {
2720 global hoverid hovertimer canv
2722 if {[info exists hoverid] && $id == $hoverid} {
2723 $canv delete hover
2724 if {[info exists hovertimer]} {
2725 after cancel $hovertimer
2726 unset hovertimer
2728 unset hoverid
2732 proc linehover {} {
2733 global hoverx hovery hoverid hovertimer
2734 global canv linespc lthickness
2735 global commitinfo mainfont
2737 set text [lindex $commitinfo($hoverid) 0]
2738 set ymax [lindex [$canv cget -scrollregion] 3]
2739 if {$ymax == {}} return
2740 set yfrac [lindex [$canv yview] 0]
2741 set x [expr {$hoverx + 2 * $linespc}]
2742 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
2743 set x0 [expr {$x - 2 * $lthickness}]
2744 set y0 [expr {$y - 2 * $lthickness}]
2745 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
2746 set y1 [expr {$y + $linespc + 2 * $lthickness}]
2747 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
2748 -fill \#ffff80 -outline black -width 1 -tags hover]
2749 $canv raise $t
2750 set t [$canv create text $x $y -anchor nw -text $text -tags hover]
2751 $canv raise $t
2754 proc lineclick {x y id} {
2755 global ctext commitinfo children cflist canv
2757 unmarkmatches
2758 $canv delete hover
2759 # fill the details pane with info about this line
2760 $ctext conf -state normal
2761 $ctext delete 0.0 end
2762 $ctext insert end "Parent:\n "
2763 catch {destroy $ctext.$id}
2764 button $ctext.$id -text "Go:" -command "selbyid $id" \
2765 -padx 4 -pady 0
2766 $ctext window create end -window $ctext.$id -align center
2767 set info $commitinfo($id)
2768 $ctext insert end "\t[lindex $info 0]\n"
2769 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
2770 $ctext insert end "\tDate:\t[lindex $info 2]\n"
2771 $ctext insert end "\tID:\t$id\n"
2772 if {[info exists children($id)]} {
2773 $ctext insert end "\nChildren:"
2774 foreach child $children($id) {
2775 $ctext insert end "\n "
2776 catch {destroy $ctext.$child}
2777 button $ctext.$child -text "Go:" -command "selbyid $child" \
2778 -padx 4 -pady 0
2779 $ctext window create end -window $ctext.$child -align center
2780 set info $commitinfo($child)
2781 $ctext insert end "\t[lindex $info 0]"
2784 $ctext conf -state disabled
2786 $cflist delete 0 end
2789 proc selbyid {id} {
2790 global idline
2791 if {[info exists idline($id)]} {
2792 selectline $idline($id) 1
2796 proc mstime {} {
2797 global startmstime
2798 if {![info exists startmstime]} {
2799 set startmstime [clock clicks -milliseconds]
2801 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
2804 proc rowmenu {x y id} {
2805 global rowctxmenu idline selectedline rowmenuid
2807 if {![info exists selectedline] || $idline($id) eq $selectedline} {
2808 set state disabled
2809 } else {
2810 set state normal
2812 $rowctxmenu entryconfigure 0 -state $state
2813 $rowctxmenu entryconfigure 1 -state $state
2814 $rowctxmenu entryconfigure 2 -state $state
2815 set rowmenuid $id
2816 tk_popup $rowctxmenu $x $y
2819 proc diffvssel {dirn} {
2820 global rowmenuid selectedline lineid
2821 global ctext cflist
2822 global commitinfo
2824 if {![info exists selectedline]} return
2825 if {$dirn} {
2826 set oldid $lineid($selectedline)
2827 set newid $rowmenuid
2828 } else {
2829 set oldid $rowmenuid
2830 set newid $lineid($selectedline)
2832 $ctext conf -state normal
2833 $ctext delete 0.0 end
2834 $ctext mark set fmark.0 0.0
2835 $ctext mark gravity fmark.0 left
2836 $cflist delete 0 end
2837 $cflist insert end "Top"
2838 $ctext insert end "From $oldid\n "
2839 $ctext insert end [lindex $commitinfo($oldid) 0]
2840 $ctext insert end "\n\nTo $newid\n "
2841 $ctext insert end [lindex $commitinfo($newid) 0]
2842 $ctext insert end "\n"
2843 $ctext conf -state disabled
2844 $ctext tag delete Comments
2845 $ctext tag remove found 1.0 end
2846 startdiff [list $newid $oldid]
2849 proc mkpatch {} {
2850 global rowmenuid currentid commitinfo patchtop patchnum
2852 if {![info exists currentid]} return
2853 set oldid $currentid
2854 set oldhead [lindex $commitinfo($oldid) 0]
2855 set newid $rowmenuid
2856 set newhead [lindex $commitinfo($newid) 0]
2857 set top .patch
2858 set patchtop $top
2859 catch {destroy $top}
2860 toplevel $top
2861 label $top.title -text "Generate patch"
2862 grid $top.title - -pady 10
2863 label $top.from -text "From:"
2864 entry $top.fromsha1 -width 40 -relief flat
2865 $top.fromsha1 insert 0 $oldid
2866 $top.fromsha1 conf -state readonly
2867 grid $top.from $top.fromsha1 -sticky w
2868 entry $top.fromhead -width 60 -relief flat
2869 $top.fromhead insert 0 $oldhead
2870 $top.fromhead conf -state readonly
2871 grid x $top.fromhead -sticky w
2872 label $top.to -text "To:"
2873 entry $top.tosha1 -width 40 -relief flat
2874 $top.tosha1 insert 0 $newid
2875 $top.tosha1 conf -state readonly
2876 grid $top.to $top.tosha1 -sticky w
2877 entry $top.tohead -width 60 -relief flat
2878 $top.tohead insert 0 $newhead
2879 $top.tohead conf -state readonly
2880 grid x $top.tohead -sticky w
2881 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
2882 grid $top.rev x -pady 10
2883 label $top.flab -text "Output file:"
2884 entry $top.fname -width 60
2885 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
2886 incr patchnum
2887 grid $top.flab $top.fname -sticky w
2888 frame $top.buts
2889 button $top.buts.gen -text "Generate" -command mkpatchgo
2890 button $top.buts.can -text "Cancel" -command mkpatchcan
2891 grid $top.buts.gen $top.buts.can
2892 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2893 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2894 grid $top.buts - -pady 10 -sticky ew
2895 focus $top.fname
2898 proc mkpatchrev {} {
2899 global patchtop
2901 set oldid [$patchtop.fromsha1 get]
2902 set oldhead [$patchtop.fromhead get]
2903 set newid [$patchtop.tosha1 get]
2904 set newhead [$patchtop.tohead get]
2905 foreach e [list fromsha1 fromhead tosha1 tohead] \
2906 v [list $newid $newhead $oldid $oldhead] {
2907 $patchtop.$e conf -state normal
2908 $patchtop.$e delete 0 end
2909 $patchtop.$e insert 0 $v
2910 $patchtop.$e conf -state readonly
2914 proc mkpatchgo {} {
2915 global patchtop
2917 set oldid [$patchtop.fromsha1 get]
2918 set newid [$patchtop.tosha1 get]
2919 set fname [$patchtop.fname get]
2920 if {[catch {exec git-diff-tree -p $oldid $newid >$fname &} err]} {
2921 error_popup "Error creating patch: $err"
2923 catch {destroy $patchtop}
2924 unset patchtop
2927 proc mkpatchcan {} {
2928 global patchtop
2930 catch {destroy $patchtop}
2931 unset patchtop
2934 proc mktag {} {
2935 global rowmenuid mktagtop commitinfo
2937 set top .maketag
2938 set mktagtop $top
2939 catch {destroy $top}
2940 toplevel $top
2941 label $top.title -text "Create tag"
2942 grid $top.title - -pady 10
2943 label $top.id -text "ID:"
2944 entry $top.sha1 -width 40 -relief flat
2945 $top.sha1 insert 0 $rowmenuid
2946 $top.sha1 conf -state readonly
2947 grid $top.id $top.sha1 -sticky w
2948 entry $top.head -width 60 -relief flat
2949 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
2950 $top.head conf -state readonly
2951 grid x $top.head -sticky w
2952 label $top.tlab -text "Tag name:"
2953 entry $top.tag -width 60
2954 grid $top.tlab $top.tag -sticky w
2955 frame $top.buts
2956 button $top.buts.gen -text "Create" -command mktaggo
2957 button $top.buts.can -text "Cancel" -command mktagcan
2958 grid $top.buts.gen $top.buts.can
2959 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2960 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2961 grid $top.buts - -pady 10 -sticky ew
2962 focus $top.tag
2965 proc domktag {} {
2966 global mktagtop env tagids idtags
2967 global idpos idline linehtag canv selectedline
2969 set id [$mktagtop.sha1 get]
2970 set tag [$mktagtop.tag get]
2971 if {$tag == {}} {
2972 error_popup "No tag name specified"
2973 return
2975 if {[info exists tagids($tag)]} {
2976 error_popup "Tag \"$tag\" already exists"
2977 return
2979 if {[catch {
2980 set dir [gitdir]
2981 set fname [file join $dir "refs/tags" $tag]
2982 set f [open $fname w]
2983 puts $f $id
2984 close $f
2985 } err]} {
2986 error_popup "Error creating tag: $err"
2987 return
2990 set tagids($tag) $id
2991 lappend idtags($id) $tag
2992 $canv delete tag.$id
2993 set xt [eval drawtags $id $idpos($id)]
2994 $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
2995 if {[info exists selectedline] && $selectedline == $idline($id)} {
2996 selectline $selectedline 0
3000 proc mktagcan {} {
3001 global mktagtop
3003 catch {destroy $mktagtop}
3004 unset mktagtop
3007 proc mktaggo {} {
3008 domktag
3009 mktagcan
3012 proc writecommit {} {
3013 global rowmenuid wrcomtop commitinfo wrcomcmd
3015 set top .writecommit
3016 set wrcomtop $top
3017 catch {destroy $top}
3018 toplevel $top
3019 label $top.title -text "Write commit to file"
3020 grid $top.title - -pady 10
3021 label $top.id -text "ID:"
3022 entry $top.sha1 -width 40 -relief flat
3023 $top.sha1 insert 0 $rowmenuid
3024 $top.sha1 conf -state readonly
3025 grid $top.id $top.sha1 -sticky w
3026 entry $top.head -width 60 -relief flat
3027 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
3028 $top.head conf -state readonly
3029 grid x $top.head -sticky w
3030 label $top.clab -text "Command:"
3031 entry $top.cmd -width 60 -textvariable wrcomcmd
3032 grid $top.clab $top.cmd -sticky w -pady 10
3033 label $top.flab -text "Output file:"
3034 entry $top.fname -width 60
3035 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
3036 grid $top.flab $top.fname -sticky w
3037 frame $top.buts
3038 button $top.buts.gen -text "Write" -command wrcomgo
3039 button $top.buts.can -text "Cancel" -command wrcomcan
3040 grid $top.buts.gen $top.buts.can
3041 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3042 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3043 grid $top.buts - -pady 10 -sticky ew
3044 focus $top.fname
3047 proc wrcomgo {} {
3048 global wrcomtop
3050 set id [$wrcomtop.sha1 get]
3051 set cmd "echo $id | [$wrcomtop.cmd get]"
3052 set fname [$wrcomtop.fname get]
3053 if {[catch {exec sh -c $cmd >$fname &} err]} {
3054 error_popup "Error writing commit: $err"
3056 catch {destroy $wrcomtop}
3057 unset wrcomtop
3060 proc wrcomcan {} {
3061 global wrcomtop
3063 catch {destroy $wrcomtop}
3064 unset wrcomtop
3067 proc doquit {} {
3068 global stopped
3069 set stopped 100
3070 destroy .
3073 # defaults...
3074 set datemode 0
3075 set boldnames 0
3076 set diffopts "-U 5 -p"
3077 set wrcomcmd "git-diff-tree --stdin -p --pretty"
3079 set mainfont {Helvetica 9}
3080 set textfont {Courier 9}
3081 set findmergefiles 0
3082 set gaudydiff 0
3083 set maxgraphpct 50
3085 set colors {green red blue magenta darkgrey brown orange}
3087 catch {source ~/.gitk}
3089 set namefont $mainfont
3090 if {$boldnames} {
3091 lappend namefont bold
3094 set revtreeargs {}
3095 foreach arg $argv {
3096 switch -regexp -- $arg {
3097 "^$" { }
3098 "^-b" { set boldnames 1 }
3099 "^-d" { set datemode 1 }
3100 default {
3101 lappend revtreeargs $arg
3106 set history {}
3107 set historyindex 0
3109 set stopped 0
3110 set redisplaying 0
3111 set stuffsaved 0
3112 set patchnum 0
3113 setcoords
3114 makewindow
3115 readrefs
3116 getcommits $revtreeargs