gitk: Implement date mode in the new framework
[git.git] / gitk
blob53106018197da8a71e93138c2cd23f9717987d1b
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright (C) 2005-2006 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 [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25 global isonrunq runq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {}} {
30 after idle dorunq
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
41 global runq
43 fileevent $fd readable {}
44 if {$runq eq {}} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc nukefile {fd} {
51 global runq
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
56 } else {
57 incr i
62 proc dorunq {} {
63 global isonrunq runq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set repeat [eval $script]
71 set t1 [clock clicks -milliseconds]
72 set t [expr {$t1 - $t0}]
73 set runq [lrange $runq 1 end]
74 if {$repeat ne {} && $repeat} {
75 if {$fd eq {} || $repeat == 2} {
76 # script returns 1 if it wants to be readded
77 # file readers return 2 if they could do more straight away
78 lappend runq [list $fd $script]
79 } else {
80 fileevent $fd readable [list filereadable $fd $script]
82 } elseif {$fd eq {}} {
83 unset isonrunq($script)
85 set t0 $t1
86 if {$t1 - $tstart >= 80} break
88 if {$runq ne {}} {
89 after idle dorunq
93 # Start off a git rev-list process and arrange to read its output
94 proc start_rev_list {view} {
95 global startmsecs
96 global commfd leftover tclencoding datemode
97 global viewargs viewfiles commitidx viewcomplete vnextroot
98 global showlocalchanges commitinterest mainheadid
99 global progressdirn progresscoords proglastnc curview
100 global viewincl viewactive loginstance viewinstances
102 set startmsecs [clock clicks -milliseconds]
103 set commitidx($view) 0
104 set viewcomplete($view) 0
105 set viewactive($view) 1
106 set vnextroot($view) 0
107 varcinit $view
109 set commits [eval exec git rev-parse --default HEAD --revs-only \
110 $viewargs($view)]
111 set viewincl($view) {}
112 foreach c $commits {
113 if {![string match "^*" $c]} {
114 lappend viewincl($view) $c
117 if {[catch {
118 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
119 --boundary $commits "--" $viewfiles($view)] r]
120 } err]} {
121 error_popup "Error executing git log: $err"
122 exit 1
124 set i [incr loginstance]
125 set viewinstances($view) [list $i]
126 set commfd($i) $fd
127 set leftover($i) {}
128 if {$showlocalchanges} {
129 lappend commitinterest($mainheadid) {dodiffindex}
131 fconfigure $fd -blocking 0 -translation lf -eofchar {}
132 if {$tclencoding != {}} {
133 fconfigure $fd -encoding $tclencoding
135 filerun $fd [list getcommitlines $fd $i $view]
136 nowbusy $view "Reading"
137 if {$view == $curview} {
138 set progressdirn 1
139 set progresscoords {0 0}
140 set proglastnc 0
144 proc stop_rev_list {view} {
145 global commfd viewinstances leftover
147 foreach inst $viewinstances($view) {
148 set fd $commfd($inst)
149 catch {
150 set pid [pid $fd]
151 exec kill $pid
153 catch {close $fd}
154 nukefile $fd
155 unset commfd($inst)
156 unset leftover($inst)
158 set viewinstances($view) {}
161 proc getcommits {} {
162 global canv curview
164 initlayout
165 start_rev_list $curview
166 show_status "Reading commits..."
169 proc updatecommits {} {
170 global curview viewargs viewfiles viewincl viewinstances
171 global viewactive viewcomplete loginstance tclencoding mainheadid
172 global varcid startmsecs commfd showneartags showlocalchanges leftover
174 if {$showlocalchanges && [commitinview $mainheadid $curview]} {
175 dodiffindex
177 set view $curview
178 set commits [exec git rev-parse --default HEAD --revs-only \
179 $viewargs($view)]
180 set pos {}
181 set neg {}
182 foreach c $commits {
183 if {[string match "^*" $c]} {
184 lappend neg $c
185 } else {
186 if {!([info exists varcid($view,$c)] ||
187 [lsearch -exact $viewincl($view) $c] >= 0)} {
188 lappend pos $c
192 if {$pos eq {}} {
193 return
195 foreach id $viewincl($view) {
196 lappend neg "^$id"
198 set viewincl($view) [concat $viewincl($view) $pos]
199 if {[catch {
200 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
201 --boundary $pos $neg "--" $viewfiles($view)] r]
202 } err]} {
203 error_popup "Error executing git log: $err"
204 exit 1
206 if {$viewactive($view) == 0} {
207 set startmsecs [clock clicks -milliseconds]
209 set i [incr loginstance]
210 lappend viewinstances($view) $i
211 set commfd($i) $fd
212 set leftover($i) {}
213 fconfigure $fd -blocking 0 -translation lf -eofchar {}
214 if {$tclencoding != {}} {
215 fconfigure $fd -encoding $tclencoding
217 filerun $fd [list getcommitlines $fd $i $view]
218 incr viewactive($view)
219 set viewcomplete($view) 0
220 nowbusy $view "Reading"
221 readrefs
222 changedrefs
223 if {$showneartags} {
224 getallcommits
228 proc reloadcommits {} {
229 global curview viewcomplete selectedline currentid thickerline
230 global showneartags treediffs commitinterest cached_commitrow
231 global progresscoords
233 if {!$viewcomplete($curview)} {
234 stop_rev_list $curview
235 set progresscoords {0 0}
236 adjustprogress
238 resetvarcs $curview
239 catch {unset selectedline}
240 catch {unset currentid}
241 catch {unset thickerline}
242 catch {unset treediffs}
243 readrefs
244 changedrefs
245 if {$showneartags} {
246 getallcommits
248 clear_display
249 catch {unset commitinterest}
250 catch {unset cached_commitrow}
251 setcanvscroll
252 getcommits
255 # This makes a string representation of a positive integer which
256 # sorts as a string in numerical order
257 proc strrep {n} {
258 if {$n < 16} {
259 return [format "%x" $n]
260 } elseif {$n < 256} {
261 return [format "x%.2x" $n]
262 } elseif {$n < 65536} {
263 return [format "y%.4x" $n]
265 return [format "z%.8x" $n]
268 # Procedures used in reordering commits from git log (without
269 # --topo-order) into the order for display.
271 proc varcinit {view} {
272 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
273 global vtokmod varcmod vrowmod varcix vlastins
275 set varcstart($view) {{}}
276 set vupptr($view) {0}
277 set vdownptr($view) {0}
278 set vleftptr($view) {0}
279 set vbackptr($view) {0}
280 set varctok($view) {{}}
281 set varcrow($view) {{}}
282 set vtokmod($view) {}
283 set varcmod($view) 0
284 set vrowmod($view) 0
285 set varcix($view) {{}}
286 set vlastins($view) {0}
289 proc resetvarcs {view} {
290 global varcid varccommits parents children vseedcount ordertok
292 foreach vid [array names varcid $view,*] {
293 unset varcid($vid)
294 unset children($vid)
295 unset parents($vid)
297 # some commits might have children but haven't been seen yet
298 foreach vid [array names children $view,*] {
299 unset children($vid)
301 foreach va [array names varccommits $view,*] {
302 unset varccommits($va)
304 foreach vd [array names vseedcount $view,*] {
305 unset vseedcount($vd)
307 catch {unset ordertok}
310 proc newvarc {view id} {
311 global varcid varctok parents children datemode
312 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
313 global commitdata commitinfo vseedcount varccommits vlastins
315 set a [llength $varctok($view)]
316 set vid $view,$id
317 if {[llength $children($vid)] == 0 || $datemode} {
318 if {![info exists commitinfo($id)]} {
319 parsecommit $id $commitdata($id) 1
321 set cdate [lindex $commitinfo($id) 4]
322 if {![string is integer -strict $cdate]} {
323 set cdate 0
325 if {![info exists vseedcount($view,$cdate)]} {
326 set vseedcount($view,$cdate) -1
328 set c [incr vseedcount($view,$cdate)]
329 set cdate [expr {$cdate ^ 0xffffffff}]
330 set tok "s[strrep $cdate][strrep $c]"
331 } else {
332 set tok {}
334 set ka 0
335 if {[llength $children($vid)] > 0} {
336 set kid [lindex $children($vid) end]
337 set k $varcid($view,$kid)
338 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
339 set ki $kid
340 set ka $k
341 set tok [lindex $varctok($view) $k]
344 if {$ka != 0} {
345 set i [lsearch -exact $parents($view,$ki) $id]
346 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
347 append tok [strrep $j]
349 set c [lindex $vlastins($view) $ka]
350 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
351 set c $ka
352 set b [lindex $vdownptr($view) $ka]
353 } else {
354 set b [lindex $vleftptr($view) $c]
356 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
357 set c $b
358 set b [lindex $vleftptr($view) $c]
360 if {$c == $ka} {
361 lset vdownptr($view) $ka $a
362 lappend vbackptr($view) 0
363 } else {
364 lset vleftptr($view) $c $a
365 lappend vbackptr($view) $c
367 lset vlastins($view) $ka $a
368 lappend vupptr($view) $ka
369 lappend vleftptr($view) $b
370 if {$b != 0} {
371 lset vbackptr($view) $b $a
373 lappend varctok($view) $tok
374 lappend varcstart($view) $id
375 lappend vdownptr($view) 0
376 lappend varcrow($view) {}
377 lappend varcix($view) {}
378 set varccommits($view,$a) {}
379 lappend vlastins($view) 0
380 return $a
383 proc splitvarc {p v} {
384 global varcid varcstart varccommits varctok
385 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
387 set oa $varcid($v,$p)
388 set ac $varccommits($v,$oa)
389 set i [lsearch -exact $varccommits($v,$oa) $p]
390 if {$i <= 0} return
391 set na [llength $varctok($v)]
392 # "%" sorts before "0"...
393 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
394 lappend varctok($v) $tok
395 lappend varcrow($v) {}
396 lappend varcix($v) {}
397 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
398 set varccommits($v,$na) [lrange $ac $i end]
399 lappend varcstart($v) $p
400 foreach id $varccommits($v,$na) {
401 set varcid($v,$id) $na
403 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
404 lset vdownptr($v) $oa $na
405 lappend vupptr($v) $oa
406 lappend vleftptr($v) 0
407 lappend vbackptr($v) 0
408 lappend vlastins($v) 0
409 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
410 lset vupptr($v) $b $na
414 proc renumbervarc {a v} {
415 global parents children varctok varcstart varccommits
416 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod datemode
418 set t1 [clock clicks -milliseconds]
419 set todo {}
420 set isrelated($a) 1
421 set kidchanged($a) 1
422 set ntot 0
423 while {$a != 0} {
424 if {[info exists isrelated($a)]} {
425 lappend todo $a
426 set id [lindex $varccommits($v,$a) end]
427 foreach p $parents($v,$id) {
428 if {[info exists varcid($v,$p)]} {
429 set isrelated($varcid($v,$p)) 1
433 incr ntot
434 set b [lindex $vdownptr($v) $a]
435 if {$b == 0} {
436 while {$a != 0} {
437 set b [lindex $vleftptr($v) $a]
438 if {$b != 0} break
439 set a [lindex $vupptr($v) $a]
442 set a $b
444 foreach a $todo {
445 if {![info exists kidchanged($a)]} continue
446 set id [lindex $varcstart($v) $a]
447 if {[llength $children($v,$id)] > 1} {
448 set children($v,$id) [lsort -command [list vtokcmp $v] \
449 $children($v,$id)]
451 set oldtok [lindex $varctok($v) $a]
452 if {!$datemode} {
453 set tok {}
454 } else {
455 set tok $oldtok
457 set ka 0
458 if {[llength $children($v,$id)] > 0} {
459 set kid [lindex $children($v,$id) end]
460 set k $varcid($v,$kid)
461 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
462 set ki $kid
463 set ka $k
464 set tok [lindex $varctok($v) $k]
467 if {$ka != 0} {
468 set i [lsearch -exact $parents($v,$ki) $id]
469 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
470 append tok [strrep $j]
472 if {$tok eq $oldtok} {
473 continue
475 set id [lindex $varccommits($v,$a) end]
476 foreach p $parents($v,$id) {
477 if {[info exists varcid($v,$p)]} {
478 set kidchanged($varcid($v,$p)) 1
479 } else {
480 set sortkids($p) 1
483 lset varctok($v) $a $tok
484 set b [lindex $vupptr($v) $a]
485 if {$b != $ka} {
486 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
487 modify_arc $v $ka
489 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
490 modify_arc $v $b
492 set c [lindex $vbackptr($v) $a]
493 set d [lindex $vleftptr($v) $a]
494 if {$c == 0} {
495 lset vdownptr($v) $b $d
496 } else {
497 lset vleftptr($v) $c $d
499 if {$d != 0} {
500 lset vbackptr($v) $d $c
502 lset vupptr($v) $a $ka
503 set c [lindex $vlastins($v) $ka]
504 if {$c == 0 || \
505 [string compare $tok [lindex $varctok($v) $c]] < 0} {
506 set c $ka
507 set b [lindex $vdownptr($v) $ka]
508 } else {
509 set b [lindex $vleftptr($v) $c]
511 while {$b != 0 && \
512 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
513 set c $b
514 set b [lindex $vleftptr($v) $c]
516 if {$c == $ka} {
517 lset vdownptr($v) $ka $a
518 lset vbackptr($v) $a 0
519 } else {
520 lset vleftptr($v) $c $a
521 lset vbackptr($v) $a $c
523 lset vleftptr($v) $a $b
524 if {$b != 0} {
525 lset vbackptr($v) $b $a
527 lset vlastins($v) $ka $a
530 foreach id [array names sortkids] {
531 if {[llength $children($v,$id)] > 1} {
532 set children($v,$id) [lsort -command [list vtokcmp $v] \
533 $children($v,$id)]
536 set t2 [clock clicks -milliseconds]
537 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
540 proc fix_reversal {p a v} {
541 global varcid varcstart varctok vupptr
543 set pa $varcid($v,$p)
544 if {$p ne [lindex $varcstart($v) $pa]} {
545 splitvarc $p $v
546 set pa $varcid($v,$p)
548 # seeds always need to be renumbered
549 if {[lindex $vupptr($v) $pa] == 0 ||
550 [string compare [lindex $varctok($v) $a] \
551 [lindex $varctok($v) $pa]] > 0} {
552 renumbervarc $pa $v
556 proc insertrow {id p v} {
557 global varcid varccommits parents children cmitlisted
558 global commitidx varctok vtokmod
560 set a $varcid($v,$p)
561 set i [lsearch -exact $varccommits($v,$a) $p]
562 if {$i < 0} {
563 puts "oops: insertrow can't find [shortids $p] on arc $a"
564 return
566 set children($v,$id) {}
567 set parents($v,$id) [list $p]
568 set varcid($v,$id) $a
569 lappend children($v,$p) $id
570 set cmitlisted($v,$id) 1
571 incr commitidx($v)
572 # note we deliberately don't update varcstart($v) even if $i == 0
573 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
574 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
575 modify_arc $v $a $i
577 drawvisible
580 proc removerow {id v} {
581 global varcid varccommits parents children commitidx
582 global varctok vtokmod cmitlisted
584 if {[llength $parents($v,$id)] != 1} {
585 puts "oops: removerow [shortids $id] has [llength $parents($v,$id)] parents"
586 return
588 set p [lindex $parents($v,$id) 0]
589 set a $varcid($v,$id)
590 set i [lsearch -exact $varccommits($v,$a) $id]
591 if {$i < 0} {
592 puts "oops: removerow can't find [shortids $id] on arc $a"
593 return
595 unset varcid($v,$id)
596 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
597 unset parents($v,$id)
598 unset children($v,$id)
599 unset cmitlisted($v,$id)
600 incr commitidx($v) -1
601 set j [lsearch -exact $children($v,$p) $id]
602 if {$j >= 0} {
603 set children($v,$p) [lreplace $children($v,$p) $j $j]
605 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
606 modify_arc $v $a $i
608 drawvisible
611 proc vtokcmp {v a b} {
612 global varctok varcid
614 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
615 [lindex $varctok($v) $varcid($v,$b)]]
618 proc modify_arc {v a {lim {}}} {
619 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
620 global vhighlights nhighlights fhighlights rhighlights
622 set vtokmod($v) [lindex $varctok($v) $a]
623 set varcmod($v) $a
624 if {$v == $curview} {
625 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
626 set a [lindex $vupptr($v) $a]
627 set lim {}
629 set r 0
630 if {$a != 0} {
631 if {$lim eq {}} {
632 set lim [llength $varccommits($v,$a)]
634 set r [expr {[lindex $varcrow($v) $a] + $lim}]
636 set vrowmod($v) $r
637 undolayout $r
639 catch {unset nhighlights}
640 catch {unset fhighlights}
641 catch {unset vhighlights}
642 catch {unset rhighlights}
645 proc update_arcrows {v} {
646 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
647 global varcid vrownum varcorder varcix varccommits
648 global vupptr vdownptr vleftptr varctok
649 global displayorder parentlist curview cached_commitrow
651 set narctot [expr {[llength $varctok($v)] - 1}]
652 set a $varcmod($v)
653 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
654 # go up the tree until we find something that has a row number,
655 # or we get to a seed
656 set a [lindex $vupptr($v) $a]
658 if {$a == 0} {
659 set a [lindex $vdownptr($v) 0]
660 if {$a == 0} return
661 set vrownum($v) {0}
662 set varcorder($v) [list $a]
663 lset varcix($v) $a 0
664 lset varcrow($v) $a 0
665 set arcn 0
666 set row 0
667 } else {
668 set arcn [lindex $varcix($v) $a]
669 # see if a is the last arc; if so, nothing to do
670 if {$arcn == $narctot - 1} {
671 return
673 if {[llength $vrownum($v)] > $arcn + 1} {
674 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
675 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
677 set row [lindex $varcrow($v) $a]
679 if {$v == $curview} {
680 if {[llength $displayorder] > $vrowmod($v)} {
681 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
682 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
684 catch {unset cached_commitrow}
686 while {1} {
687 set p $a
688 incr row [llength $varccommits($v,$a)]
689 # go down if possible
690 set b [lindex $vdownptr($v) $a]
691 if {$b == 0} {
692 # if not, go left, or go up until we can go left
693 while {$a != 0} {
694 set b [lindex $vleftptr($v) $a]
695 if {$b != 0} break
696 set a [lindex $vupptr($v) $a]
698 if {$a == 0} break
700 set a $b
701 incr arcn
702 lappend vrownum($v) $row
703 lappend varcorder($v) $a
704 lset varcix($v) $a $arcn
705 lset varcrow($v) $a $row
707 set vtokmod($v) [lindex $varctok($v) $p]
708 set varcmod($v) $p
709 set vrowmod($v) $row
710 if {[info exists currentid]} {
711 set selectedline [rowofcommit $currentid]
715 # Test whether view $v contains commit $id
716 proc commitinview {id v} {
717 global varcid
719 return [info exists varcid($v,$id)]
722 # Return the row number for commit $id in the current view
723 proc rowofcommit {id} {
724 global varcid varccommits varcrow curview cached_commitrow
725 global varctok vtokmod
727 if {[info exists cached_commitrow($id)]} {
728 return $cached_commitrow($id)
730 set v $curview
731 if {![info exists varcid($v,$id)]} {
732 puts "oops rowofcommit no arc for [shortids $id]"
733 return {}
735 set a $varcid($v,$id)
736 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] > 0} {
737 update_arcrows $v
739 set i [lsearch -exact $varccommits($v,$a) $id]
740 if {$i < 0} {
741 puts "oops didn't find commit [shortids $id] in arc $a"
742 return {}
744 incr i [lindex $varcrow($v) $a]
745 set cached_commitrow($id) $i
746 return $i
749 proc bsearch {l elt} {
750 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
751 return 0
753 set lo 0
754 set hi [llength $l]
755 while {$hi - $lo > 1} {
756 set mid [expr {int(($lo + $hi) / 2)}]
757 set t [lindex $l $mid]
758 if {$elt < $t} {
759 set hi $mid
760 } elseif {$elt > $t} {
761 set lo $mid
762 } else {
763 return $mid
766 return $lo
769 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
770 proc make_disporder {start end} {
771 global vrownum curview commitidx displayorder parentlist
772 global varccommits varcorder parents vrowmod varcrow
773 global d_valid_start d_valid_end
775 if {$end > $vrowmod($curview)} {
776 update_arcrows $curview
778 set ai [bsearch $vrownum($curview) $start]
779 set start [lindex $vrownum($curview) $ai]
780 set narc [llength $vrownum($curview)]
781 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
782 set a [lindex $varcorder($curview) $ai]
783 set l [llength $displayorder]
784 set al [llength $varccommits($curview,$a)]
785 if {$l < $r + $al} {
786 if {$l < $r} {
787 set pad [ntimes [expr {$r - $l}] {}]
788 set displayorder [concat $displayorder $pad]
789 set parentlist [concat $parentlist $pad]
790 } elseif {$l > $r} {
791 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
792 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
794 foreach id $varccommits($curview,$a) {
795 lappend displayorder $id
796 lappend parentlist $parents($curview,$id)
798 } elseif {[lindex $displayorder $r] eq {}} {
799 set i $r
800 foreach id $varccommits($curview,$a) {
801 lset displayorder $i $id
802 lset parentlist $i $parents($curview,$id)
803 incr i
806 incr r $al
810 proc commitonrow {row} {
811 global displayorder
813 set id [lindex $displayorder $row]
814 if {$id eq {}} {
815 make_disporder $row [expr {$row + 1}]
816 set id [lindex $displayorder $row]
818 return $id
821 proc closevarcs {v} {
822 global varctok varccommits varcid parents children
823 global cmitlisted commitidx commitinterest vtokmod
825 set missing_parents 0
826 set scripts {}
827 set narcs [llength $varctok($v)]
828 for {set a 1} {$a < $narcs} {incr a} {
829 set id [lindex $varccommits($v,$a) end]
830 foreach p $parents($v,$id) {
831 if {[info exists varcid($v,$p)]} continue
832 # add p as a new commit
833 incr missing_parents
834 set cmitlisted($v,$p) 0
835 set parents($v,$p) {}
836 if {[llength $children($v,$p)] == 1 &&
837 [llength $parents($v,$id)] == 1} {
838 set b $a
839 } else {
840 set b [newvarc $v $p]
842 set varcid($v,$p) $b
843 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
844 modify_arc $v $b
846 lappend varccommits($v,$b) $p
847 incr commitidx($v)
848 if {[info exists commitinterest($p)]} {
849 foreach script $commitinterest($p) {
850 lappend scripts [string map [list "%I" $p] $script]
852 unset commitinterest($id)
856 if {$missing_parents > 0} {
857 foreach s $scripts {
858 eval $s
863 proc getcommitlines {fd inst view} {
864 global cmitlisted commitinterest leftover
865 global commitidx commitdata datemode
866 global parents children curview hlview
867 global vnextroot idpending ordertok
868 global varccommits varcid varctok vtokmod
870 set stuff [read $fd 500000]
871 # git log doesn't terminate the last commit with a null...
872 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
873 set stuff "\0"
875 if {$stuff == {}} {
876 if {![eof $fd]} {
877 return 1
879 global commfd viewcomplete viewactive viewname progresscoords
880 global viewinstances
881 unset commfd($inst)
882 set i [lsearch -exact $viewinstances($view) $inst]
883 if {$i >= 0} {
884 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
886 # set it blocking so we wait for the process to terminate
887 fconfigure $fd -blocking 1
888 if {[catch {close $fd} err]} {
889 set fv {}
890 if {$view != $curview} {
891 set fv " for the \"$viewname($view)\" view"
893 if {[string range $err 0 4] == "usage"} {
894 set err "Gitk: error reading commits$fv:\
895 bad arguments to git rev-list."
896 if {$viewname($view) eq "Command line"} {
897 append err \
898 " (Note: arguments to gitk are passed to git rev-list\
899 to allow selection of commits to be displayed.)"
901 } else {
902 set err "Error reading commits$fv: $err"
904 error_popup $err
906 if {[incr viewactive($view) -1] <= 0} {
907 set viewcomplete($view) 1
908 # Check if we have seen any ids listed as parents that haven't
909 # appeared in the list
910 closevarcs $view
911 notbusy $view
912 set progresscoords {0 0}
913 adjustprogress
915 if {$view == $curview} {
916 run chewcommits $view
918 return 0
920 set start 0
921 set gotsome 0
922 set scripts {}
923 while 1 {
924 set i [string first "\0" $stuff $start]
925 if {$i < 0} {
926 append leftover($inst) [string range $stuff $start end]
927 break
929 if {$start == 0} {
930 set cmit $leftover($inst)
931 append cmit [string range $stuff 0 [expr {$i - 1}]]
932 set leftover($inst) {}
933 } else {
934 set cmit [string range $stuff $start [expr {$i - 1}]]
936 set start [expr {$i + 1}]
937 set j [string first "\n" $cmit]
938 set ok 0
939 set listed 1
940 if {$j >= 0 && [string match "commit *" $cmit]} {
941 set ids [string range $cmit 7 [expr {$j - 1}]]
942 if {[string match {[-<>]*} $ids]} {
943 switch -- [string index $ids 0] {
944 "-" {set listed 0}
945 "<" {set listed 2}
946 ">" {set listed 3}
948 set ids [string range $ids 1 end]
950 set ok 1
951 foreach id $ids {
952 if {[string length $id] != 40} {
953 set ok 0
954 break
958 if {!$ok} {
959 set shortcmit $cmit
960 if {[string length $shortcmit] > 80} {
961 set shortcmit "[string range $shortcmit 0 80]..."
963 error_popup "Can't parse git log output: {$shortcmit}"
964 exit 1
966 set id [lindex $ids 0]
967 set vid $view,$id
968 if {!$listed && [info exists parents($vid)]} continue
969 if {$listed} {
970 set olds [lrange $ids 1 end]
971 } else {
972 set olds {}
974 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
975 set cmitlisted($vid) $listed
976 set parents($vid) $olds
977 set a 0
978 if {![info exists children($vid)]} {
979 set children($vid) {}
980 } elseif {[llength $children($vid)] == 1} {
981 set k [lindex $children($vid) 0]
982 if {[llength $parents($view,$k)] == 1 &&
983 (!$datemode ||
984 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
985 set a $varcid($view,$k)
988 if {$a == 0} {
989 # new arc
990 set a [newvarc $view $id]
992 set varcid($vid) $a
993 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
994 modify_arc $view $a
996 lappend varccommits($view,$a) $id
998 set i 0
999 foreach p $olds {
1000 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1001 set vp $view,$p
1002 if {[llength [lappend children($vp) $id]] > 1 &&
1003 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1004 set children($vp) [lsort -command [list vtokcmp $view] \
1005 $children($vp)]
1006 catch {unset ordertok}
1008 if {[info exists varcid($view,$p)]} {
1009 fix_reversal $p $a $view
1012 incr i
1015 incr commitidx($view)
1016 if {[info exists commitinterest($id)]} {
1017 foreach script $commitinterest($id) {
1018 lappend scripts [string map [list "%I" $id] $script]
1020 unset commitinterest($id)
1022 set gotsome 1
1024 if {$gotsome} {
1025 run chewcommits $view
1026 foreach s $scripts {
1027 eval $s
1029 if {$view == $curview} {
1030 # update progress bar
1031 global progressdirn progresscoords proglastnc
1032 set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
1033 set proglastnc $commitidx($view)
1034 set l [lindex $progresscoords 0]
1035 set r [lindex $progresscoords 1]
1036 if {$progressdirn} {
1037 set r [expr {$r + $inc}]
1038 if {$r >= 1.0} {
1039 set r 1.0
1040 set progressdirn 0
1042 if {$r > 0.2} {
1043 set l [expr {$r - 0.2}]
1045 } else {
1046 set l [expr {$l - $inc}]
1047 if {$l <= 0.0} {
1048 set l 0.0
1049 set progressdirn 1
1051 set r [expr {$l + 0.2}]
1053 set progresscoords [list $l $r]
1054 adjustprogress
1057 return 2
1060 proc chewcommits {view} {
1061 global curview hlview viewcomplete
1062 global pending_select
1064 if {$view == $curview} {
1065 layoutmore
1066 if {$viewcomplete($view)} {
1067 global commitidx varctok
1068 global numcommits startmsecs
1069 global mainheadid commitinfo nullid
1071 if {[info exists pending_select]} {
1072 set row [first_real_row]
1073 selectline $row 1
1075 if {$commitidx($curview) > 0} {
1076 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1077 #puts "overall $ms ms for $numcommits commits"
1078 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1079 } else {
1080 show_status "No commits selected"
1082 notbusy layout
1085 if {[info exists hlview] && $view == $hlview} {
1086 vhighlightmore
1088 return 0
1091 proc readcommit {id} {
1092 if {[catch {set contents [exec git cat-file commit $id]}]} return
1093 parsecommit $id $contents 0
1096 proc parsecommit {id contents listed} {
1097 global commitinfo cdate
1099 set inhdr 1
1100 set comment {}
1101 set headline {}
1102 set auname {}
1103 set audate {}
1104 set comname {}
1105 set comdate {}
1106 set hdrend [string first "\n\n" $contents]
1107 if {$hdrend < 0} {
1108 # should never happen...
1109 set hdrend [string length $contents]
1111 set header [string range $contents 0 [expr {$hdrend - 1}]]
1112 set comment [string range $contents [expr {$hdrend + 2}] end]
1113 foreach line [split $header "\n"] {
1114 set tag [lindex $line 0]
1115 if {$tag == "author"} {
1116 set audate [lindex $line end-1]
1117 set auname [lrange $line 1 end-2]
1118 } elseif {$tag == "committer"} {
1119 set comdate [lindex $line end-1]
1120 set comname [lrange $line 1 end-2]
1123 set headline {}
1124 # take the first non-blank line of the comment as the headline
1125 set headline [string trimleft $comment]
1126 set i [string first "\n" $headline]
1127 if {$i >= 0} {
1128 set headline [string range $headline 0 $i]
1130 set headline [string trimright $headline]
1131 set i [string first "\r" $headline]
1132 if {$i >= 0} {
1133 set headline [string trimright [string range $headline 0 $i]]
1135 if {!$listed} {
1136 # git rev-list indents the comment by 4 spaces;
1137 # if we got this via git cat-file, add the indentation
1138 set newcomment {}
1139 foreach line [split $comment "\n"] {
1140 append newcomment " "
1141 append newcomment $line
1142 append newcomment "\n"
1144 set comment $newcomment
1146 if {$comdate != {}} {
1147 set cdate($id) $comdate
1149 set commitinfo($id) [list $headline $auname $audate \
1150 $comname $comdate $comment]
1153 proc getcommit {id} {
1154 global commitdata commitinfo
1156 if {[info exists commitdata($id)]} {
1157 parsecommit $id $commitdata($id) 1
1158 } else {
1159 readcommit $id
1160 if {![info exists commitinfo($id)]} {
1161 set commitinfo($id) {"No commit information available"}
1164 return 1
1167 proc readrefs {} {
1168 global tagids idtags headids idheads tagobjid
1169 global otherrefids idotherrefs mainhead mainheadid
1171 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1172 catch {unset $v}
1174 set refd [open [list | git show-ref -d] r]
1175 while {[gets $refd line] >= 0} {
1176 if {[string index $line 40] ne " "} continue
1177 set id [string range $line 0 39]
1178 set ref [string range $line 41 end]
1179 if {![string match "refs/*" $ref]} continue
1180 set name [string range $ref 5 end]
1181 if {[string match "remotes/*" $name]} {
1182 if {![string match "*/HEAD" $name]} {
1183 set headids($name) $id
1184 lappend idheads($id) $name
1186 } elseif {[string match "heads/*" $name]} {
1187 set name [string range $name 6 end]
1188 set headids($name) $id
1189 lappend idheads($id) $name
1190 } elseif {[string match "tags/*" $name]} {
1191 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1192 # which is what we want since the former is the commit ID
1193 set name [string range $name 5 end]
1194 if {[string match "*^{}" $name]} {
1195 set name [string range $name 0 end-3]
1196 } else {
1197 set tagobjid($name) $id
1199 set tagids($name) $id
1200 lappend idtags($id) $name
1201 } else {
1202 set otherrefids($name) $id
1203 lappend idotherrefs($id) $name
1206 catch {close $refd}
1207 set mainhead {}
1208 set mainheadid {}
1209 catch {
1210 set thehead [exec git symbolic-ref HEAD]
1211 if {[string match "refs/heads/*" $thehead]} {
1212 set mainhead [string range $thehead 11 end]
1213 if {[info exists headids($mainhead)]} {
1214 set mainheadid $headids($mainhead)
1220 # skip over fake commits
1221 proc first_real_row {} {
1222 global nullid nullid2 numcommits
1224 for {set row 0} {$row < $numcommits} {incr row} {
1225 set id [commitonrow $row]
1226 if {$id ne $nullid && $id ne $nullid2} {
1227 break
1230 return $row
1233 # update things for a head moved to a child of its previous location
1234 proc movehead {id name} {
1235 global headids idheads
1237 removehead $headids($name) $name
1238 set headids($name) $id
1239 lappend idheads($id) $name
1242 # update things when a head has been removed
1243 proc removehead {id name} {
1244 global headids idheads
1246 if {$idheads($id) eq $name} {
1247 unset idheads($id)
1248 } else {
1249 set i [lsearch -exact $idheads($id) $name]
1250 if {$i >= 0} {
1251 set idheads($id) [lreplace $idheads($id) $i $i]
1254 unset headids($name)
1257 proc show_error {w top msg} {
1258 message $w.m -text $msg -justify center -aspect 400
1259 pack $w.m -side top -fill x -padx 20 -pady 20
1260 button $w.ok -text OK -command "destroy $top"
1261 pack $w.ok -side bottom -fill x
1262 bind $top <Visibility> "grab $top; focus $top"
1263 bind $top <Key-Return> "destroy $top"
1264 tkwait window $top
1267 proc error_popup msg {
1268 set w .error
1269 toplevel $w
1270 wm transient $w .
1271 show_error $w $w $msg
1274 proc confirm_popup msg {
1275 global confirm_ok
1276 set confirm_ok 0
1277 set w .confirm
1278 toplevel $w
1279 wm transient $w .
1280 message $w.m -text $msg -justify center -aspect 400
1281 pack $w.m -side top -fill x -padx 20 -pady 20
1282 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
1283 pack $w.ok -side left -fill x
1284 button $w.cancel -text Cancel -command "destroy $w"
1285 pack $w.cancel -side right -fill x
1286 bind $w <Visibility> "grab $w; focus $w"
1287 tkwait window $w
1288 return $confirm_ok
1291 proc makewindow {} {
1292 global canv canv2 canv3 linespc charspc ctext cflist
1293 global tabstop
1294 global findtype findtypemenu findloc findstring fstring geometry
1295 global entries sha1entry sha1string sha1but
1296 global diffcontextstring diffcontext
1297 global maincursor textcursor curtextcursor
1298 global rowctxmenu fakerowmenu mergemax wrapcomment
1299 global highlight_files gdttype
1300 global searchstring sstring
1301 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1302 global headctxmenu progresscanv progressitem progresscoords statusw
1303 global fprogitem fprogcoord lastprogupdate progupdatepending
1304 global rprogitem rprogcoord
1305 global have_tk85
1307 menu .bar
1308 .bar add cascade -label "File" -menu .bar.file
1309 .bar configure -font uifont
1310 menu .bar.file
1311 .bar.file add command -label "Update" -command updatecommits
1312 .bar.file add command -label "Reload" -command reloadcommits
1313 .bar.file add command -label "Reread references" -command rereadrefs
1314 .bar.file add command -label "List references" -command showrefs
1315 .bar.file add command -label "Quit" -command doquit
1316 .bar.file configure -font uifont
1317 menu .bar.edit
1318 .bar add cascade -label "Edit" -menu .bar.edit
1319 .bar.edit add command -label "Preferences" -command doprefs
1320 .bar.edit configure -font uifont
1322 menu .bar.view -font uifont
1323 .bar add cascade -label "View" -menu .bar.view
1324 .bar.view add command -label "New view..." -command {newview 0}
1325 .bar.view add command -label "Edit view..." -command editview \
1326 -state disabled
1327 .bar.view add command -label "Delete view" -command delview -state disabled
1328 .bar.view add separator
1329 .bar.view add radiobutton -label "All files" -command {showview 0} \
1330 -variable selectedview -value 0
1332 menu .bar.help
1333 .bar add cascade -label "Help" -menu .bar.help
1334 .bar.help add command -label "About gitk" -command about
1335 .bar.help add command -label "Key bindings" -command keys
1336 .bar.help configure -font uifont
1337 . configure -menu .bar
1339 # the gui has upper and lower half, parts of a paned window.
1340 panedwindow .ctop -orient vertical
1342 # possibly use assumed geometry
1343 if {![info exists geometry(pwsash0)]} {
1344 set geometry(topheight) [expr {15 * $linespc}]
1345 set geometry(topwidth) [expr {80 * $charspc}]
1346 set geometry(botheight) [expr {15 * $linespc}]
1347 set geometry(botwidth) [expr {50 * $charspc}]
1348 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1349 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1352 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1353 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1354 frame .tf.histframe
1355 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1357 # create three canvases
1358 set cscroll .tf.histframe.csb
1359 set canv .tf.histframe.pwclist.canv
1360 canvas $canv \
1361 -selectbackground $selectbgcolor \
1362 -background $bgcolor -bd 0 \
1363 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1364 .tf.histframe.pwclist add $canv
1365 set canv2 .tf.histframe.pwclist.canv2
1366 canvas $canv2 \
1367 -selectbackground $selectbgcolor \
1368 -background $bgcolor -bd 0 -yscrollincr $linespc
1369 .tf.histframe.pwclist add $canv2
1370 set canv3 .tf.histframe.pwclist.canv3
1371 canvas $canv3 \
1372 -selectbackground $selectbgcolor \
1373 -background $bgcolor -bd 0 -yscrollincr $linespc
1374 .tf.histframe.pwclist add $canv3
1375 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1376 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1378 # a scroll bar to rule them
1379 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1380 pack $cscroll -side right -fill y
1381 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1382 lappend bglist $canv $canv2 $canv3
1383 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1385 # we have two button bars at bottom of top frame. Bar 1
1386 frame .tf.bar
1387 frame .tf.lbar -height 15
1389 set sha1entry .tf.bar.sha1
1390 set entries $sha1entry
1391 set sha1but .tf.bar.sha1label
1392 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
1393 -command gotocommit -width 8 -font uifont
1394 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1395 pack .tf.bar.sha1label -side left
1396 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1397 trace add variable sha1string write sha1change
1398 pack $sha1entry -side left -pady 2
1400 image create bitmap bm-left -data {
1401 #define left_width 16
1402 #define left_height 16
1403 static unsigned char left_bits[] = {
1404 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1405 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1406 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1408 image create bitmap bm-right -data {
1409 #define right_width 16
1410 #define right_height 16
1411 static unsigned char right_bits[] = {
1412 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1413 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1414 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1416 button .tf.bar.leftbut -image bm-left -command goback \
1417 -state disabled -width 26
1418 pack .tf.bar.leftbut -side left -fill y
1419 button .tf.bar.rightbut -image bm-right -command goforw \
1420 -state disabled -width 26
1421 pack .tf.bar.rightbut -side left -fill y
1423 # Status label and progress bar
1424 set statusw .tf.bar.status
1425 label $statusw -width 15 -relief sunken -font uifont
1426 pack $statusw -side left -padx 5
1427 set h [expr {[font metrics uifont -linespace] + 2}]
1428 set progresscanv .tf.bar.progress
1429 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1430 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1431 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1432 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1433 pack $progresscanv -side right -expand 1 -fill x
1434 set progresscoords {0 0}
1435 set fprogcoord 0
1436 set rprogcoord 0
1437 bind $progresscanv <Configure> adjustprogress
1438 set lastprogupdate [clock clicks -milliseconds]
1439 set progupdatepending 0
1441 # build up the bottom bar of upper window
1442 label .tf.lbar.flabel -text "Find " -font uifont
1443 button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont
1444 button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont
1445 label .tf.lbar.flab2 -text " commit " -font uifont
1446 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1447 -side left -fill y
1448 set gdttype "containing:"
1449 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1450 "containing:" \
1451 "touching paths:" \
1452 "adding/removing string:"]
1453 trace add variable gdttype write gdttype_change
1454 $gm conf -font uifont
1455 .tf.lbar.gdttype conf -font uifont
1456 pack .tf.lbar.gdttype -side left -fill y
1458 set findstring {}
1459 set fstring .tf.lbar.findstring
1460 lappend entries $fstring
1461 entry $fstring -width 30 -font textfont -textvariable findstring
1462 trace add variable findstring write find_change
1463 set findtype Exact
1464 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1465 findtype Exact IgnCase Regexp]
1466 trace add variable findtype write findcom_change
1467 .tf.lbar.findtype configure -font uifont
1468 .tf.lbar.findtype.menu configure -font uifont
1469 set findloc "All fields"
1470 tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \
1471 Comments Author Committer
1472 trace add variable findloc write find_change
1473 .tf.lbar.findloc configure -font uifont
1474 .tf.lbar.findloc.menu configure -font uifont
1475 pack .tf.lbar.findloc -side right
1476 pack .tf.lbar.findtype -side right
1477 pack $fstring -side left -expand 1 -fill x
1479 # Finish putting the upper half of the viewer together
1480 pack .tf.lbar -in .tf -side bottom -fill x
1481 pack .tf.bar -in .tf -side bottom -fill x
1482 pack .tf.histframe -fill both -side top -expand 1
1483 .ctop add .tf
1484 .ctop paneconfigure .tf -height $geometry(topheight)
1485 .ctop paneconfigure .tf -width $geometry(topwidth)
1487 # now build up the bottom
1488 panedwindow .pwbottom -orient horizontal
1490 # lower left, a text box over search bar, scroll bar to the right
1491 # if we know window height, then that will set the lower text height, otherwise
1492 # we set lower text height which will drive window height
1493 if {[info exists geometry(main)]} {
1494 frame .bleft -width $geometry(botwidth)
1495 } else {
1496 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1498 frame .bleft.top
1499 frame .bleft.mid
1501 button .bleft.top.search -text "Search" -command dosearch \
1502 -font uifont
1503 pack .bleft.top.search -side left -padx 5
1504 set sstring .bleft.top.sstring
1505 entry $sstring -width 20 -font textfont -textvariable searchstring
1506 lappend entries $sstring
1507 trace add variable searchstring write incrsearch
1508 pack $sstring -side left -expand 1 -fill x
1509 radiobutton .bleft.mid.diff -text "Diff" -font uifont \
1510 -command changediffdisp -variable diffelide -value {0 0}
1511 radiobutton .bleft.mid.old -text "Old version" -font uifont \
1512 -command changediffdisp -variable diffelide -value {0 1}
1513 radiobutton .bleft.mid.new -text "New version" -font uifont \
1514 -command changediffdisp -variable diffelide -value {1 0}
1515 label .bleft.mid.labeldiffcontext -text " Lines of context: " \
1516 -font uifont
1517 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1518 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1519 -from 1 -increment 1 -to 10000000 \
1520 -validate all -validatecommand "diffcontextvalidate %P" \
1521 -textvariable diffcontextstring
1522 .bleft.mid.diffcontext set $diffcontext
1523 trace add variable diffcontextstring write diffcontextchange
1524 lappend entries .bleft.mid.diffcontext
1525 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1526 set ctext .bleft.ctext
1527 text $ctext -background $bgcolor -foreground $fgcolor \
1528 -state disabled -font textfont \
1529 -yscrollcommand scrolltext -wrap none
1530 if {$have_tk85} {
1531 $ctext conf -tabstyle wordprocessor
1533 scrollbar .bleft.sb -command "$ctext yview"
1534 pack .bleft.top -side top -fill x
1535 pack .bleft.mid -side top -fill x
1536 pack .bleft.sb -side right -fill y
1537 pack $ctext -side left -fill both -expand 1
1538 lappend bglist $ctext
1539 lappend fglist $ctext
1541 $ctext tag conf comment -wrap $wrapcomment
1542 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
1543 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
1544 $ctext tag conf d0 -fore [lindex $diffcolors 0]
1545 $ctext tag conf d1 -fore [lindex $diffcolors 1]
1546 $ctext tag conf m0 -fore red
1547 $ctext tag conf m1 -fore blue
1548 $ctext tag conf m2 -fore green
1549 $ctext tag conf m3 -fore purple
1550 $ctext tag conf m4 -fore brown
1551 $ctext tag conf m5 -fore "#009090"
1552 $ctext tag conf m6 -fore magenta
1553 $ctext tag conf m7 -fore "#808000"
1554 $ctext tag conf m8 -fore "#009000"
1555 $ctext tag conf m9 -fore "#ff0080"
1556 $ctext tag conf m10 -fore cyan
1557 $ctext tag conf m11 -fore "#b07070"
1558 $ctext tag conf m12 -fore "#70b0f0"
1559 $ctext tag conf m13 -fore "#70f0b0"
1560 $ctext tag conf m14 -fore "#f0b070"
1561 $ctext tag conf m15 -fore "#ff70b0"
1562 $ctext tag conf mmax -fore darkgrey
1563 set mergemax 16
1564 $ctext tag conf mresult -font textfontbold
1565 $ctext tag conf msep -font textfontbold
1566 $ctext tag conf found -back yellow
1568 .pwbottom add .bleft
1569 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
1571 # lower right
1572 frame .bright
1573 frame .bright.mode
1574 radiobutton .bright.mode.patch -text "Patch" \
1575 -command reselectline -variable cmitmode -value "patch"
1576 .bright.mode.patch configure -font uifont
1577 radiobutton .bright.mode.tree -text "Tree" \
1578 -command reselectline -variable cmitmode -value "tree"
1579 .bright.mode.tree configure -font uifont
1580 grid .bright.mode.patch .bright.mode.tree -sticky ew
1581 pack .bright.mode -side top -fill x
1582 set cflist .bright.cfiles
1583 set indent [font measure mainfont "nn"]
1584 text $cflist \
1585 -selectbackground $selectbgcolor \
1586 -background $bgcolor -foreground $fgcolor \
1587 -font mainfont \
1588 -tabs [list $indent [expr {2 * $indent}]] \
1589 -yscrollcommand ".bright.sb set" \
1590 -cursor [. cget -cursor] \
1591 -spacing1 1 -spacing3 1
1592 lappend bglist $cflist
1593 lappend fglist $cflist
1594 scrollbar .bright.sb -command "$cflist yview"
1595 pack .bright.sb -side right -fill y
1596 pack $cflist -side left -fill both -expand 1
1597 $cflist tag configure highlight \
1598 -background [$cflist cget -selectbackground]
1599 $cflist tag configure bold -font mainfontbold
1601 .pwbottom add .bright
1602 .ctop add .pwbottom
1604 # restore window position if known
1605 if {[info exists geometry(main)]} {
1606 wm geometry . "$geometry(main)"
1609 if {[tk windowingsystem] eq {aqua}} {
1610 set M1B M1
1611 } else {
1612 set M1B Control
1615 bind .pwbottom <Configure> {resizecdetpanes %W %w}
1616 pack .ctop -fill both -expand 1
1617 bindall <1> {selcanvline %W %x %y}
1618 #bindall <B1-Motion> {selcanvline %W %x %y}
1619 if {[tk windowingsystem] == "win32"} {
1620 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
1621 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
1622 } else {
1623 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
1624 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
1625 if {[tk windowingsystem] eq "aqua"} {
1626 bindall <MouseWheel> {
1627 set delta [expr {- (%D)}]
1628 allcanvs yview scroll $delta units
1632 bindall <2> "canvscan mark %W %x %y"
1633 bindall <B2-Motion> "canvscan dragto %W %x %y"
1634 bindkey <Home> selfirstline
1635 bindkey <End> sellastline
1636 bind . <Key-Up> "selnextline -1"
1637 bind . <Key-Down> "selnextline 1"
1638 bind . <Shift-Key-Up> "dofind -1 0"
1639 bind . <Shift-Key-Down> "dofind 1 0"
1640 bindkey <Key-Right> "goforw"
1641 bindkey <Key-Left> "goback"
1642 bind . <Key-Prior> "selnextpage -1"
1643 bind . <Key-Next> "selnextpage 1"
1644 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
1645 bind . <$M1B-End> "allcanvs yview moveto 1.0"
1646 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
1647 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
1648 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
1649 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
1650 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
1651 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
1652 bindkey <Key-space> "$ctext yview scroll 1 pages"
1653 bindkey p "selnextline -1"
1654 bindkey n "selnextline 1"
1655 bindkey z "goback"
1656 bindkey x "goforw"
1657 bindkey i "selnextline -1"
1658 bindkey k "selnextline 1"
1659 bindkey j "goback"
1660 bindkey l "goforw"
1661 bindkey b "$ctext yview scroll -1 pages"
1662 bindkey d "$ctext yview scroll 18 units"
1663 bindkey u "$ctext yview scroll -18 units"
1664 bindkey / {dofind 1 1}
1665 bindkey <Key-Return> {dofind 1 1}
1666 bindkey ? {dofind -1 1}
1667 bindkey f nextfile
1668 bindkey <F5> updatecommits
1669 bind . <$M1B-q> doquit
1670 bind . <$M1B-f> {dofind 1 1}
1671 bind . <$M1B-g> {dofind 1 0}
1672 bind . <$M1B-r> dosearchback
1673 bind . <$M1B-s> dosearch
1674 bind . <$M1B-equal> {incrfont 1}
1675 bind . <$M1B-KP_Add> {incrfont 1}
1676 bind . <$M1B-minus> {incrfont -1}
1677 bind . <$M1B-KP_Subtract> {incrfont -1}
1678 wm protocol . WM_DELETE_WINDOW doquit
1679 bind . <Button-1> "click %W"
1680 bind $fstring <Key-Return> {dofind 1 1}
1681 bind $sha1entry <Key-Return> gotocommit
1682 bind $sha1entry <<PasteSelection>> clearsha1
1683 bind $cflist <1> {sel_flist %W %x %y; break}
1684 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
1685 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
1686 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
1688 set maincursor [. cget -cursor]
1689 set textcursor [$ctext cget -cursor]
1690 set curtextcursor $textcursor
1692 set rowctxmenu .rowctxmenu
1693 menu $rowctxmenu -tearoff 0
1694 $rowctxmenu add command -label "Diff this -> selected" \
1695 -command {diffvssel 0}
1696 $rowctxmenu add command -label "Diff selected -> this" \
1697 -command {diffvssel 1}
1698 $rowctxmenu add command -label "Make patch" -command mkpatch
1699 $rowctxmenu add command -label "Create tag" -command mktag
1700 $rowctxmenu add command -label "Write commit to file" -command writecommit
1701 $rowctxmenu add command -label "Create new branch" -command mkbranch
1702 $rowctxmenu add command -label "Cherry-pick this commit" \
1703 -command cherrypick
1704 $rowctxmenu add command -label "Reset HEAD branch to here" \
1705 -command resethead
1707 set fakerowmenu .fakerowmenu
1708 menu $fakerowmenu -tearoff 0
1709 $fakerowmenu add command -label "Diff this -> selected" \
1710 -command {diffvssel 0}
1711 $fakerowmenu add command -label "Diff selected -> this" \
1712 -command {diffvssel 1}
1713 $fakerowmenu add command -label "Make patch" -command mkpatch
1714 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
1715 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
1716 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
1718 set headctxmenu .headctxmenu
1719 menu $headctxmenu -tearoff 0
1720 $headctxmenu add command -label "Check out this branch" \
1721 -command cobranch
1722 $headctxmenu add command -label "Remove this branch" \
1723 -command rmbranch
1725 global flist_menu
1726 set flist_menu .flistctxmenu
1727 menu $flist_menu -tearoff 0
1728 $flist_menu add command -label "Highlight this too" \
1729 -command {flist_hl 0}
1730 $flist_menu add command -label "Highlight this only" \
1731 -command {flist_hl 1}
1734 # Windows sends all mouse wheel events to the current focused window, not
1735 # the one where the mouse hovers, so bind those events here and redirect
1736 # to the correct window
1737 proc windows_mousewheel_redirector {W X Y D} {
1738 global canv canv2 canv3
1739 set w [winfo containing -displayof $W $X $Y]
1740 if {$w ne ""} {
1741 set u [expr {$D < 0 ? 5 : -5}]
1742 if {$w == $canv || $w == $canv2 || $w == $canv3} {
1743 allcanvs yview scroll $u units
1744 } else {
1745 catch {
1746 $w yview scroll $u units
1752 # mouse-2 makes all windows scan vertically, but only the one
1753 # the cursor is in scans horizontally
1754 proc canvscan {op w x y} {
1755 global canv canv2 canv3
1756 foreach c [list $canv $canv2 $canv3] {
1757 if {$c == $w} {
1758 $c scan $op $x $y
1759 } else {
1760 $c scan $op 0 $y
1765 proc scrollcanv {cscroll f0 f1} {
1766 $cscroll set $f0 $f1
1767 drawfrac $f0 $f1
1768 flushhighlights
1771 # when we make a key binding for the toplevel, make sure
1772 # it doesn't get triggered when that key is pressed in the
1773 # find string entry widget.
1774 proc bindkey {ev script} {
1775 global entries
1776 bind . $ev $script
1777 set escript [bind Entry $ev]
1778 if {$escript == {}} {
1779 set escript [bind Entry <Key>]
1781 foreach e $entries {
1782 bind $e $ev "$escript; break"
1786 # set the focus back to the toplevel for any click outside
1787 # the entry widgets
1788 proc click {w} {
1789 global ctext entries
1790 foreach e [concat $entries $ctext] {
1791 if {$w == $e} return
1793 focus .
1796 # Adjust the progress bar for a change in requested extent or canvas size
1797 proc adjustprogress {} {
1798 global progresscanv progressitem progresscoords
1799 global fprogitem fprogcoord lastprogupdate progupdatepending
1800 global rprogitem rprogcoord
1802 set w [expr {[winfo width $progresscanv] - 4}]
1803 set x0 [expr {$w * [lindex $progresscoords 0]}]
1804 set x1 [expr {$w * [lindex $progresscoords 1]}]
1805 set h [winfo height $progresscanv]
1806 $progresscanv coords $progressitem $x0 0 $x1 $h
1807 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
1808 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
1809 set now [clock clicks -milliseconds]
1810 if {$now >= $lastprogupdate + 100} {
1811 set progupdatepending 0
1812 update
1813 } elseif {!$progupdatepending} {
1814 set progupdatepending 1
1815 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
1819 proc doprogupdate {} {
1820 global lastprogupdate progupdatepending
1822 if {$progupdatepending} {
1823 set progupdatepending 0
1824 set lastprogupdate [clock clicks -milliseconds]
1825 update
1829 proc savestuff {w} {
1830 global canv canv2 canv3 mainfont textfont uifont tabstop
1831 global stuffsaved findmergefiles maxgraphpct
1832 global maxwidth showneartags showlocalchanges
1833 global viewname viewfiles viewargs viewperm nextviewnum
1834 global cmitmode wrapcomment datetimeformat limitdiffs
1835 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
1837 if {$stuffsaved} return
1838 if {![winfo viewable .]} return
1839 catch {
1840 set f [open "~/.gitk-new" w]
1841 puts $f [list set mainfont $mainfont]
1842 puts $f [list set textfont $textfont]
1843 puts $f [list set uifont $uifont]
1844 puts $f [list set tabstop $tabstop]
1845 puts $f [list set findmergefiles $findmergefiles]
1846 puts $f [list set maxgraphpct $maxgraphpct]
1847 puts $f [list set maxwidth $maxwidth]
1848 puts $f [list set cmitmode $cmitmode]
1849 puts $f [list set wrapcomment $wrapcomment]
1850 puts $f [list set showneartags $showneartags]
1851 puts $f [list set showlocalchanges $showlocalchanges]
1852 puts $f [list set datetimeformat $datetimeformat]
1853 puts $f [list set limitdiffs $limitdiffs]
1854 puts $f [list set bgcolor $bgcolor]
1855 puts $f [list set fgcolor $fgcolor]
1856 puts $f [list set colors $colors]
1857 puts $f [list set diffcolors $diffcolors]
1858 puts $f [list set diffcontext $diffcontext]
1859 puts $f [list set selectbgcolor $selectbgcolor]
1861 puts $f "set geometry(main) [wm geometry .]"
1862 puts $f "set geometry(topwidth) [winfo width .tf]"
1863 puts $f "set geometry(topheight) [winfo height .tf]"
1864 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1865 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1866 puts $f "set geometry(botwidth) [winfo width .bleft]"
1867 puts $f "set geometry(botheight) [winfo height .bleft]"
1869 puts -nonewline $f "set permviews {"
1870 for {set v 0} {$v < $nextviewnum} {incr v} {
1871 if {$viewperm($v)} {
1872 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1875 puts $f "}"
1876 close $f
1877 file rename -force "~/.gitk-new" "~/.gitk"
1879 set stuffsaved 1
1882 proc resizeclistpanes {win w} {
1883 global oldwidth
1884 if {[info exists oldwidth($win)]} {
1885 set s0 [$win sash coord 0]
1886 set s1 [$win sash coord 1]
1887 if {$w < 60} {
1888 set sash0 [expr {int($w/2 - 2)}]
1889 set sash1 [expr {int($w*5/6 - 2)}]
1890 } else {
1891 set factor [expr {1.0 * $w / $oldwidth($win)}]
1892 set sash0 [expr {int($factor * [lindex $s0 0])}]
1893 set sash1 [expr {int($factor * [lindex $s1 0])}]
1894 if {$sash0 < 30} {
1895 set sash0 30
1897 if {$sash1 < $sash0 + 20} {
1898 set sash1 [expr {$sash0 + 20}]
1900 if {$sash1 > $w - 10} {
1901 set sash1 [expr {$w - 10}]
1902 if {$sash0 > $sash1 - 20} {
1903 set sash0 [expr {$sash1 - 20}]
1907 $win sash place 0 $sash0 [lindex $s0 1]
1908 $win sash place 1 $sash1 [lindex $s1 1]
1910 set oldwidth($win) $w
1913 proc resizecdetpanes {win w} {
1914 global oldwidth
1915 if {[info exists oldwidth($win)]} {
1916 set s0 [$win sash coord 0]
1917 if {$w < 60} {
1918 set sash0 [expr {int($w*3/4 - 2)}]
1919 } else {
1920 set factor [expr {1.0 * $w / $oldwidth($win)}]
1921 set sash0 [expr {int($factor * [lindex $s0 0])}]
1922 if {$sash0 < 45} {
1923 set sash0 45
1925 if {$sash0 > $w - 15} {
1926 set sash0 [expr {$w - 15}]
1929 $win sash place 0 $sash0 [lindex $s0 1]
1931 set oldwidth($win) $w
1934 proc allcanvs args {
1935 global canv canv2 canv3
1936 eval $canv $args
1937 eval $canv2 $args
1938 eval $canv3 $args
1941 proc bindall {event action} {
1942 global canv canv2 canv3
1943 bind $canv $event $action
1944 bind $canv2 $event $action
1945 bind $canv3 $event $action
1948 proc about {} {
1949 global uifont
1950 set w .about
1951 if {[winfo exists $w]} {
1952 raise $w
1953 return
1955 toplevel $w
1956 wm title $w "About gitk"
1957 message $w.m -text {
1958 Gitk - a commit viewer for git
1960 Copyright © 2005-2007 Paul Mackerras
1962 Use and redistribute under the terms of the GNU General Public License} \
1963 -justify center -aspect 400 -border 2 -bg white -relief groove
1964 pack $w.m -side top -fill x -padx 2 -pady 2
1965 $w.m configure -font uifont
1966 button $w.ok -text Close -command "destroy $w" -default active
1967 pack $w.ok -side bottom
1968 $w.ok configure -font uifont
1969 bind $w <Visibility> "focus $w.ok"
1970 bind $w <Key-Escape> "destroy $w"
1971 bind $w <Key-Return> "destroy $w"
1974 proc keys {} {
1975 global uifont
1976 set w .keys
1977 if {[winfo exists $w]} {
1978 raise $w
1979 return
1981 if {[tk windowingsystem] eq {aqua}} {
1982 set M1T Cmd
1983 } else {
1984 set M1T Ctrl
1986 toplevel $w
1987 wm title $w "Gitk key bindings"
1988 message $w.m -text "
1989 Gitk key bindings:
1991 <$M1T-Q> Quit
1992 <Home> Move to first commit
1993 <End> Move to last commit
1994 <Up>, p, i Move up one commit
1995 <Down>, n, k Move down one commit
1996 <Left>, z, j Go back in history list
1997 <Right>, x, l Go forward in history list
1998 <PageUp> Move up one page in commit list
1999 <PageDown> Move down one page in commit list
2000 <$M1T-Home> Scroll to top of commit list
2001 <$M1T-End> Scroll to bottom of commit list
2002 <$M1T-Up> Scroll commit list up one line
2003 <$M1T-Down> Scroll commit list down one line
2004 <$M1T-PageUp> Scroll commit list up one page
2005 <$M1T-PageDown> Scroll commit list down one page
2006 <Shift-Up> Find backwards (upwards, later commits)
2007 <Shift-Down> Find forwards (downwards, earlier commits)
2008 <Delete>, b Scroll diff view up one page
2009 <Backspace> Scroll diff view up one page
2010 <Space> Scroll diff view down one page
2011 u Scroll diff view up 18 lines
2012 d Scroll diff view down 18 lines
2013 <$M1T-F> Find
2014 <$M1T-G> Move to next find hit
2015 <Return> Move to next find hit
2016 / Move to next find hit, or redo find
2017 ? Move to previous find hit
2018 f Scroll diff view to next file
2019 <$M1T-S> Search for next hit in diff view
2020 <$M1T-R> Search for previous hit in diff view
2021 <$M1T-KP+> Increase font size
2022 <$M1T-plus> Increase font size
2023 <$M1T-KP-> Decrease font size
2024 <$M1T-minus> Decrease font size
2025 <F5> Update
2027 -justify left -bg white -border 2 -relief groove
2028 pack $w.m -side top -fill both -padx 2 -pady 2
2029 $w.m configure -font uifont
2030 button $w.ok -text Close -command "destroy $w" -default active
2031 pack $w.ok -side bottom
2032 $w.ok configure -font uifont
2033 bind $w <Visibility> "focus $w.ok"
2034 bind $w <Key-Escape> "destroy $w"
2035 bind $w <Key-Return> "destroy $w"
2038 # Procedures for manipulating the file list window at the
2039 # bottom right of the overall window.
2041 proc treeview {w l openlevs} {
2042 global treecontents treediropen treeheight treeparent treeindex
2044 set ix 0
2045 set treeindex() 0
2046 set lev 0
2047 set prefix {}
2048 set prefixend -1
2049 set prefendstack {}
2050 set htstack {}
2051 set ht 0
2052 set treecontents() {}
2053 $w conf -state normal
2054 foreach f $l {
2055 while {[string range $f 0 $prefixend] ne $prefix} {
2056 if {$lev <= $openlevs} {
2057 $w mark set e:$treeindex($prefix) "end -1c"
2058 $w mark gravity e:$treeindex($prefix) left
2060 set treeheight($prefix) $ht
2061 incr ht [lindex $htstack end]
2062 set htstack [lreplace $htstack end end]
2063 set prefixend [lindex $prefendstack end]
2064 set prefendstack [lreplace $prefendstack end end]
2065 set prefix [string range $prefix 0 $prefixend]
2066 incr lev -1
2068 set tail [string range $f [expr {$prefixend+1}] end]
2069 while {[set slash [string first "/" $tail]] >= 0} {
2070 lappend htstack $ht
2071 set ht 0
2072 lappend prefendstack $prefixend
2073 incr prefixend [expr {$slash + 1}]
2074 set d [string range $tail 0 $slash]
2075 lappend treecontents($prefix) $d
2076 set oldprefix $prefix
2077 append prefix $d
2078 set treecontents($prefix) {}
2079 set treeindex($prefix) [incr ix]
2080 set treeparent($prefix) $oldprefix
2081 set tail [string range $tail [expr {$slash+1}] end]
2082 if {$lev <= $openlevs} {
2083 set ht 1
2084 set treediropen($prefix) [expr {$lev < $openlevs}]
2085 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2086 $w mark set d:$ix "end -1c"
2087 $w mark gravity d:$ix left
2088 set str "\n"
2089 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2090 $w insert end $str
2091 $w image create end -align center -image $bm -padx 1 \
2092 -name a:$ix
2093 $w insert end $d [highlight_tag $prefix]
2094 $w mark set s:$ix "end -1c"
2095 $w mark gravity s:$ix left
2097 incr lev
2099 if {$tail ne {}} {
2100 if {$lev <= $openlevs} {
2101 incr ht
2102 set str "\n"
2103 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2104 $w insert end $str
2105 $w insert end $tail [highlight_tag $f]
2107 lappend treecontents($prefix) $tail
2110 while {$htstack ne {}} {
2111 set treeheight($prefix) $ht
2112 incr ht [lindex $htstack end]
2113 set htstack [lreplace $htstack end end]
2114 set prefixend [lindex $prefendstack end]
2115 set prefendstack [lreplace $prefendstack end end]
2116 set prefix [string range $prefix 0 $prefixend]
2118 $w conf -state disabled
2121 proc linetoelt {l} {
2122 global treeheight treecontents
2124 set y 2
2125 set prefix {}
2126 while {1} {
2127 foreach e $treecontents($prefix) {
2128 if {$y == $l} {
2129 return "$prefix$e"
2131 set n 1
2132 if {[string index $e end] eq "/"} {
2133 set n $treeheight($prefix$e)
2134 if {$y + $n > $l} {
2135 append prefix $e
2136 incr y
2137 break
2140 incr y $n
2145 proc highlight_tree {y prefix} {
2146 global treeheight treecontents cflist
2148 foreach e $treecontents($prefix) {
2149 set path $prefix$e
2150 if {[highlight_tag $path] ne {}} {
2151 $cflist tag add bold $y.0 "$y.0 lineend"
2153 incr y
2154 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2155 set y [highlight_tree $y $path]
2158 return $y
2161 proc treeclosedir {w dir} {
2162 global treediropen treeheight treeparent treeindex
2164 set ix $treeindex($dir)
2165 $w conf -state normal
2166 $w delete s:$ix e:$ix
2167 set treediropen($dir) 0
2168 $w image configure a:$ix -image tri-rt
2169 $w conf -state disabled
2170 set n [expr {1 - $treeheight($dir)}]
2171 while {$dir ne {}} {
2172 incr treeheight($dir) $n
2173 set dir $treeparent($dir)
2177 proc treeopendir {w dir} {
2178 global treediropen treeheight treeparent treecontents treeindex
2180 set ix $treeindex($dir)
2181 $w conf -state normal
2182 $w image configure a:$ix -image tri-dn
2183 $w mark set e:$ix s:$ix
2184 $w mark gravity e:$ix right
2185 set lev 0
2186 set str "\n"
2187 set n [llength $treecontents($dir)]
2188 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2189 incr lev
2190 append str "\t"
2191 incr treeheight($x) $n
2193 foreach e $treecontents($dir) {
2194 set de $dir$e
2195 if {[string index $e end] eq "/"} {
2196 set iy $treeindex($de)
2197 $w mark set d:$iy e:$ix
2198 $w mark gravity d:$iy left
2199 $w insert e:$ix $str
2200 set treediropen($de) 0
2201 $w image create e:$ix -align center -image tri-rt -padx 1 \
2202 -name a:$iy
2203 $w insert e:$ix $e [highlight_tag $de]
2204 $w mark set s:$iy e:$ix
2205 $w mark gravity s:$iy left
2206 set treeheight($de) 1
2207 } else {
2208 $w insert e:$ix $str
2209 $w insert e:$ix $e [highlight_tag $de]
2212 $w mark gravity e:$ix left
2213 $w conf -state disabled
2214 set treediropen($dir) 1
2215 set top [lindex [split [$w index @0,0] .] 0]
2216 set ht [$w cget -height]
2217 set l [lindex [split [$w index s:$ix] .] 0]
2218 if {$l < $top} {
2219 $w yview $l.0
2220 } elseif {$l + $n + 1 > $top + $ht} {
2221 set top [expr {$l + $n + 2 - $ht}]
2222 if {$l < $top} {
2223 set top $l
2225 $w yview $top.0
2229 proc treeclick {w x y} {
2230 global treediropen cmitmode ctext cflist cflist_top
2232 if {$cmitmode ne "tree"} return
2233 if {![info exists cflist_top]} return
2234 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2235 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2236 $cflist tag add highlight $l.0 "$l.0 lineend"
2237 set cflist_top $l
2238 if {$l == 1} {
2239 $ctext yview 1.0
2240 return
2242 set e [linetoelt $l]
2243 if {[string index $e end] ne "/"} {
2244 showfile $e
2245 } elseif {$treediropen($e)} {
2246 treeclosedir $w $e
2247 } else {
2248 treeopendir $w $e
2252 proc setfilelist {id} {
2253 global treefilelist cflist
2255 treeview $cflist $treefilelist($id) 0
2258 image create bitmap tri-rt -background black -foreground blue -data {
2259 #define tri-rt_width 13
2260 #define tri-rt_height 13
2261 static unsigned char tri-rt_bits[] = {
2262 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2263 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2264 0x00, 0x00};
2265 } -maskdata {
2266 #define tri-rt-mask_width 13
2267 #define tri-rt-mask_height 13
2268 static unsigned char tri-rt-mask_bits[] = {
2269 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2270 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2271 0x08, 0x00};
2273 image create bitmap tri-dn -background black -foreground blue -data {
2274 #define tri-dn_width 13
2275 #define tri-dn_height 13
2276 static unsigned char tri-dn_bits[] = {
2277 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2278 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2279 0x00, 0x00};
2280 } -maskdata {
2281 #define tri-dn-mask_width 13
2282 #define tri-dn-mask_height 13
2283 static unsigned char tri-dn-mask_bits[] = {
2284 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2285 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2286 0x00, 0x00};
2289 image create bitmap reficon-T -background black -foreground yellow -data {
2290 #define tagicon_width 13
2291 #define tagicon_height 9
2292 static unsigned char tagicon_bits[] = {
2293 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2294 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2295 } -maskdata {
2296 #define tagicon-mask_width 13
2297 #define tagicon-mask_height 9
2298 static unsigned char tagicon-mask_bits[] = {
2299 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2300 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2302 set rectdata {
2303 #define headicon_width 13
2304 #define headicon_height 9
2305 static unsigned char headicon_bits[] = {
2306 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2307 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2309 set rectmask {
2310 #define headicon-mask_width 13
2311 #define headicon-mask_height 9
2312 static unsigned char headicon-mask_bits[] = {
2313 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2314 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2316 image create bitmap reficon-H -background black -foreground green \
2317 -data $rectdata -maskdata $rectmask
2318 image create bitmap reficon-o -background black -foreground "#ddddff" \
2319 -data $rectdata -maskdata $rectmask
2321 proc init_flist {first} {
2322 global cflist cflist_top difffilestart
2324 $cflist conf -state normal
2325 $cflist delete 0.0 end
2326 if {$first ne {}} {
2327 $cflist insert end $first
2328 set cflist_top 1
2329 $cflist tag add highlight 1.0 "1.0 lineend"
2330 } else {
2331 catch {unset cflist_top}
2333 $cflist conf -state disabled
2334 set difffilestart {}
2337 proc highlight_tag {f} {
2338 global highlight_paths
2340 foreach p $highlight_paths {
2341 if {[string match $p $f]} {
2342 return "bold"
2345 return {}
2348 proc highlight_filelist {} {
2349 global cmitmode cflist
2351 $cflist conf -state normal
2352 if {$cmitmode ne "tree"} {
2353 set end [lindex [split [$cflist index end] .] 0]
2354 for {set l 2} {$l < $end} {incr l} {
2355 set line [$cflist get $l.0 "$l.0 lineend"]
2356 if {[highlight_tag $line] ne {}} {
2357 $cflist tag add bold $l.0 "$l.0 lineend"
2360 } else {
2361 highlight_tree 2 {}
2363 $cflist conf -state disabled
2366 proc unhighlight_filelist {} {
2367 global cflist
2369 $cflist conf -state normal
2370 $cflist tag remove bold 1.0 end
2371 $cflist conf -state disabled
2374 proc add_flist {fl} {
2375 global cflist
2377 $cflist conf -state normal
2378 foreach f $fl {
2379 $cflist insert end "\n"
2380 $cflist insert end $f [highlight_tag $f]
2382 $cflist conf -state disabled
2385 proc sel_flist {w x y} {
2386 global ctext difffilestart cflist cflist_top cmitmode
2388 if {$cmitmode eq "tree"} return
2389 if {![info exists cflist_top]} return
2390 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2391 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2392 $cflist tag add highlight $l.0 "$l.0 lineend"
2393 set cflist_top $l
2394 if {$l == 1} {
2395 $ctext yview 1.0
2396 } else {
2397 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2401 proc pop_flist_menu {w X Y x y} {
2402 global ctext cflist cmitmode flist_menu flist_menu_file
2403 global treediffs diffids
2405 stopfinding
2406 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2407 if {$l <= 1} return
2408 if {$cmitmode eq "tree"} {
2409 set e [linetoelt $l]
2410 if {[string index $e end] eq "/"} return
2411 } else {
2412 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2414 set flist_menu_file $e
2415 tk_popup $flist_menu $X $Y
2418 proc flist_hl {only} {
2419 global flist_menu_file findstring gdttype
2421 set x [shellquote $flist_menu_file]
2422 if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
2423 set findstring $x
2424 } else {
2425 append findstring " " $x
2427 set gdttype "touching paths:"
2430 # Functions for adding and removing shell-type quoting
2432 proc shellquote {str} {
2433 if {![string match "*\['\"\\ \t]*" $str]} {
2434 return $str
2436 if {![string match "*\['\"\\]*" $str]} {
2437 return "\"$str\""
2439 if {![string match "*'*" $str]} {
2440 return "'$str'"
2442 return "\"[string map {\" \\\" \\ \\\\} $str]\""
2445 proc shellarglist {l} {
2446 set str {}
2447 foreach a $l {
2448 if {$str ne {}} {
2449 append str " "
2451 append str [shellquote $a]
2453 return $str
2456 proc shelldequote {str} {
2457 set ret {}
2458 set used -1
2459 while {1} {
2460 incr used
2461 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
2462 append ret [string range $str $used end]
2463 set used [string length $str]
2464 break
2466 set first [lindex $first 0]
2467 set ch [string index $str $first]
2468 if {$first > $used} {
2469 append ret [string range $str $used [expr {$first - 1}]]
2470 set used $first
2472 if {$ch eq " " || $ch eq "\t"} break
2473 incr used
2474 if {$ch eq "'"} {
2475 set first [string first "'" $str $used]
2476 if {$first < 0} {
2477 error "unmatched single-quote"
2479 append ret [string range $str $used [expr {$first - 1}]]
2480 set used $first
2481 continue
2483 if {$ch eq "\\"} {
2484 if {$used >= [string length $str]} {
2485 error "trailing backslash"
2487 append ret [string index $str $used]
2488 continue
2490 # here ch == "\""
2491 while {1} {
2492 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
2493 error "unmatched double-quote"
2495 set first [lindex $first 0]
2496 set ch [string index $str $first]
2497 if {$first > $used} {
2498 append ret [string range $str $used [expr {$first - 1}]]
2499 set used $first
2501 if {$ch eq "\""} break
2502 incr used
2503 append ret [string index $str $used]
2504 incr used
2507 return [list $used $ret]
2510 proc shellsplit {str} {
2511 set l {}
2512 while {1} {
2513 set str [string trimleft $str]
2514 if {$str eq {}} break
2515 set dq [shelldequote $str]
2516 set n [lindex $dq 0]
2517 set word [lindex $dq 1]
2518 set str [string range $str $n end]
2519 lappend l $word
2521 return $l
2524 # Code to implement multiple views
2526 proc newview {ishighlight} {
2527 global nextviewnum newviewname newviewperm uifont newishighlight
2528 global newviewargs revtreeargs
2530 set newishighlight $ishighlight
2531 set top .gitkview
2532 if {[winfo exists $top]} {
2533 raise $top
2534 return
2536 set newviewname($nextviewnum) "View $nextviewnum"
2537 set newviewperm($nextviewnum) 0
2538 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
2539 vieweditor $top $nextviewnum "Gitk view definition"
2542 proc editview {} {
2543 global curview
2544 global viewname viewperm newviewname newviewperm
2545 global viewargs newviewargs
2547 set top .gitkvedit-$curview
2548 if {[winfo exists $top]} {
2549 raise $top
2550 return
2552 set newviewname($curview) $viewname($curview)
2553 set newviewperm($curview) $viewperm($curview)
2554 set newviewargs($curview) [shellarglist $viewargs($curview)]
2555 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
2558 proc vieweditor {top n title} {
2559 global newviewname newviewperm viewfiles
2560 global uifont
2562 toplevel $top
2563 wm title $top $title
2564 label $top.nl -text "Name" -font uifont
2565 entry $top.name -width 20 -textvariable newviewname($n) -font uifont
2566 grid $top.nl $top.name -sticky w -pady 5
2567 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
2568 -font uifont
2569 grid $top.perm - -pady 5 -sticky w
2570 message $top.al -aspect 1000 -font uifont \
2571 -text "Commits to include (arguments to git rev-list):"
2572 grid $top.al - -sticky w -pady 5
2573 entry $top.args -width 50 -textvariable newviewargs($n) \
2574 -background white -font uifont
2575 grid $top.args - -sticky ew -padx 5
2576 message $top.l -aspect 1000 -font uifont \
2577 -text "Enter files and directories to include, one per line:"
2578 grid $top.l - -sticky w
2579 text $top.t -width 40 -height 10 -background white -font uifont
2580 if {[info exists viewfiles($n)]} {
2581 foreach f $viewfiles($n) {
2582 $top.t insert end $f
2583 $top.t insert end "\n"
2585 $top.t delete {end - 1c} end
2586 $top.t mark set insert 0.0
2588 grid $top.t - -sticky ew -padx 5
2589 frame $top.buts
2590 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
2591 -font uifont
2592 button $top.buts.can -text "Cancel" -command [list destroy $top] \
2593 -font uifont
2594 grid $top.buts.ok $top.buts.can
2595 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2596 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2597 grid $top.buts - -pady 10 -sticky ew
2598 focus $top.t
2601 proc doviewmenu {m first cmd op argv} {
2602 set nmenu [$m index end]
2603 for {set i $first} {$i <= $nmenu} {incr i} {
2604 if {[$m entrycget $i -command] eq $cmd} {
2605 eval $m $op $i $argv
2606 break
2611 proc allviewmenus {n op args} {
2612 # global viewhlmenu
2614 doviewmenu .bar.view 5 [list showview $n] $op $args
2615 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
2618 proc newviewok {top n} {
2619 global nextviewnum newviewperm newviewname newishighlight
2620 global viewname viewfiles viewperm selectedview curview
2621 global viewargs newviewargs viewhlmenu
2623 if {[catch {
2624 set newargs [shellsplit $newviewargs($n)]
2625 } err]} {
2626 error_popup "Error in commit selection arguments: $err"
2627 wm raise $top
2628 focus $top
2629 return
2631 set files {}
2632 foreach f [split [$top.t get 0.0 end] "\n"] {
2633 set ft [string trim $f]
2634 if {$ft ne {}} {
2635 lappend files $ft
2638 if {![info exists viewfiles($n)]} {
2639 # creating a new view
2640 incr nextviewnum
2641 set viewname($n) $newviewname($n)
2642 set viewperm($n) $newviewperm($n)
2643 set viewfiles($n) $files
2644 set viewargs($n) $newargs
2645 addviewmenu $n
2646 if {!$newishighlight} {
2647 run showview $n
2648 } else {
2649 run addvhighlight $n
2651 } else {
2652 # editing an existing view
2653 set viewperm($n) $newviewperm($n)
2654 if {$newviewname($n) ne $viewname($n)} {
2655 set viewname($n) $newviewname($n)
2656 doviewmenu .bar.view 5 [list showview $n] \
2657 entryconf [list -label $viewname($n)]
2658 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
2659 # entryconf [list -label $viewname($n) -value $viewname($n)]
2661 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
2662 set viewfiles($n) $files
2663 set viewargs($n) $newargs
2664 if {$curview == $n} {
2665 run reloadcommits
2669 catch {destroy $top}
2672 proc delview {} {
2673 global curview viewperm hlview selectedhlview
2675 if {$curview == 0} return
2676 if {[info exists hlview] && $hlview == $curview} {
2677 set selectedhlview None
2678 unset hlview
2680 allviewmenus $curview delete
2681 set viewperm($curview) 0
2682 showview 0
2685 proc addviewmenu {n} {
2686 global viewname viewhlmenu
2688 .bar.view add radiobutton -label $viewname($n) \
2689 -command [list showview $n] -variable selectedview -value $n
2690 #$viewhlmenu add radiobutton -label $viewname($n) \
2691 # -command [list addvhighlight $n] -variable selectedhlview
2694 proc showview {n} {
2695 global curview viewfiles cached_commitrow ordertok
2696 global displayorder parentlist rowidlist rowisopt rowfinal
2697 global colormap rowtextx nextcolor canvxmax
2698 global numcommits viewcomplete
2699 global selectedline currentid canv canvy0
2700 global treediffs
2701 global pending_select
2702 global commitidx
2703 global selectedview selectfirst
2704 global hlview selectedhlview commitinterest
2706 if {$n == $curview} return
2707 set selid {}
2708 set ymax [lindex [$canv cget -scrollregion] 3]
2709 set span [$canv yview]
2710 set ytop [expr {[lindex $span 0] * $ymax}]
2711 set ybot [expr {[lindex $span 1] * $ymax}]
2712 set yscreen [expr {($ybot - $ytop) / 2}]
2713 if {[info exists selectedline]} {
2714 set selid $currentid
2715 set y [yc $selectedline]
2716 if {$ytop < $y && $y < $ybot} {
2717 set yscreen [expr {$y - $ytop}]
2719 } elseif {[info exists pending_select]} {
2720 set selid $pending_select
2721 unset pending_select
2723 unselectline
2724 normalline
2725 catch {unset treediffs}
2726 clear_display
2727 if {[info exists hlview] && $hlview == $n} {
2728 unset hlview
2729 set selectedhlview None
2731 catch {unset commitinterest}
2732 catch {unset cached_commitrow}
2733 catch {unset ordertok}
2735 set curview $n
2736 set selectedview $n
2737 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
2738 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
2740 run refill_reflist
2741 if {![info exists viewcomplete($n)]} {
2742 if {$selid ne {}} {
2743 set pending_select $selid
2745 getcommits
2746 return
2749 set displayorder {}
2750 set parentlist {}
2751 set rowidlist {}
2752 set rowisopt {}
2753 set rowfinal {}
2754 set numcommits $commitidx($n)
2756 catch {unset colormap}
2757 catch {unset rowtextx}
2758 set nextcolor 0
2759 set canvxmax [$canv cget -width]
2760 set curview $n
2761 set row 0
2762 setcanvscroll
2763 set yf 0
2764 set row {}
2765 set selectfirst 0
2766 if {$selid ne {} && [commitinview $selid $n]} {
2767 set row [rowofcommit $selid]
2768 # try to get the selected row in the same position on the screen
2769 set ymax [lindex [$canv cget -scrollregion] 3]
2770 set ytop [expr {[yc $row] - $yscreen}]
2771 if {$ytop < 0} {
2772 set ytop 0
2774 set yf [expr {$ytop * 1.0 / $ymax}]
2776 allcanvs yview moveto $yf
2777 drawvisible
2778 if {$row ne {}} {
2779 selectline $row 0
2780 } elseif {$selid ne {}} {
2781 set pending_select $selid
2782 } else {
2783 set row [first_real_row]
2784 if {$row < $numcommits} {
2785 selectline $row 0
2786 } else {
2787 set selectfirst 1
2790 if {!$viewcomplete($n)} {
2791 if {$numcommits == 0} {
2792 show_status "Reading commits..."
2793 } else {
2794 run chewcommits $n
2796 } elseif {$numcommits == 0} {
2797 show_status "No commits selected"
2801 # Stuff relating to the highlighting facility
2803 proc ishighlighted {row} {
2804 global vhighlights fhighlights nhighlights rhighlights
2806 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
2807 return $nhighlights($row)
2809 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
2810 return $vhighlights($row)
2812 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
2813 return $fhighlights($row)
2815 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
2816 return $rhighlights($row)
2818 return 0
2821 proc bolden {row font} {
2822 global canv linehtag selectedline boldrows
2824 lappend boldrows $row
2825 $canv itemconf $linehtag($row) -font $font
2826 if {[info exists selectedline] && $row == $selectedline} {
2827 $canv delete secsel
2828 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2829 -outline {{}} -tags secsel \
2830 -fill [$canv cget -selectbackground]]
2831 $canv lower $t
2835 proc bolden_name {row font} {
2836 global canv2 linentag selectedline boldnamerows
2838 lappend boldnamerows $row
2839 $canv2 itemconf $linentag($row) -font $font
2840 if {[info exists selectedline] && $row == $selectedline} {
2841 $canv2 delete secsel
2842 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2843 -outline {{}} -tags secsel \
2844 -fill [$canv2 cget -selectbackground]]
2845 $canv2 lower $t
2849 proc unbolden {} {
2850 global boldrows
2852 set stillbold {}
2853 foreach row $boldrows {
2854 if {![ishighlighted $row]} {
2855 bolden $row mainfont
2856 } else {
2857 lappend stillbold $row
2860 set boldrows $stillbold
2863 proc addvhighlight {n} {
2864 global hlview viewcomplete curview vhl_done vhighlights commitidx
2866 if {[info exists hlview]} {
2867 delvhighlight
2869 set hlview $n
2870 if {$n != $curview && ![info exists viewcomplete($n)]} {
2871 start_rev_list $n
2873 set vhl_done $commitidx($hlview)
2874 if {$vhl_done > 0} {
2875 drawvisible
2879 proc delvhighlight {} {
2880 global hlview vhighlights
2882 if {![info exists hlview]} return
2883 unset hlview
2884 catch {unset vhighlights}
2885 unbolden
2888 proc vhighlightmore {} {
2889 global hlview vhl_done commitidx vhighlights curview
2891 set max $commitidx($hlview)
2892 set vr [visiblerows]
2893 set r0 [lindex $vr 0]
2894 set r1 [lindex $vr 1]
2895 for {set i $vhl_done} {$i < $max} {incr i} {
2896 set id [commitonrow $i $hlview]
2897 if {[commitinview $id $curview]} {
2898 set row [rowofcommit $id]
2899 if {$r0 <= $row && $row <= $r1} {
2900 if {![highlighted $row]} {
2901 bolden $row mainfontbold
2903 set vhighlights($row) 1
2907 set vhl_done $max
2910 proc askvhighlight {row id} {
2911 global hlview vhighlights iddrawn
2913 if {[commitinview $id $hlview]} {
2914 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2915 bolden $row mainfontbold
2917 set vhighlights($row) 1
2918 } else {
2919 set vhighlights($row) 0
2923 proc hfiles_change {} {
2924 global highlight_files filehighlight fhighlights fh_serial
2925 global highlight_paths gdttype
2927 if {[info exists filehighlight]} {
2928 # delete previous highlights
2929 catch {close $filehighlight}
2930 unset filehighlight
2931 catch {unset fhighlights}
2932 unbolden
2933 unhighlight_filelist
2935 set highlight_paths {}
2936 after cancel do_file_hl $fh_serial
2937 incr fh_serial
2938 if {$highlight_files ne {}} {
2939 after 300 do_file_hl $fh_serial
2943 proc gdttype_change {name ix op} {
2944 global gdttype highlight_files findstring findpattern
2946 stopfinding
2947 if {$findstring ne {}} {
2948 if {$gdttype eq "containing:"} {
2949 if {$highlight_files ne {}} {
2950 set highlight_files {}
2951 hfiles_change
2953 findcom_change
2954 } else {
2955 if {$findpattern ne {}} {
2956 set findpattern {}
2957 findcom_change
2959 set highlight_files $findstring
2960 hfiles_change
2962 drawvisible
2964 # enable/disable findtype/findloc menus too
2967 proc find_change {name ix op} {
2968 global gdttype findstring highlight_files
2970 stopfinding
2971 if {$gdttype eq "containing:"} {
2972 findcom_change
2973 } else {
2974 if {$highlight_files ne $findstring} {
2975 set highlight_files $findstring
2976 hfiles_change
2979 drawvisible
2982 proc findcom_change args {
2983 global nhighlights boldnamerows
2984 global findpattern findtype findstring gdttype
2986 stopfinding
2987 # delete previous highlights, if any
2988 foreach row $boldnamerows {
2989 bolden_name $row mainfont
2991 set boldnamerows {}
2992 catch {unset nhighlights}
2993 unbolden
2994 unmarkmatches
2995 if {$gdttype ne "containing:" || $findstring eq {}} {
2996 set findpattern {}
2997 } elseif {$findtype eq "Regexp"} {
2998 set findpattern $findstring
2999 } else {
3000 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3001 $findstring]
3002 set findpattern "*$e*"
3006 proc makepatterns {l} {
3007 set ret {}
3008 foreach e $l {
3009 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3010 if {[string index $ee end] eq "/"} {
3011 lappend ret "$ee*"
3012 } else {
3013 lappend ret $ee
3014 lappend ret "$ee/*"
3017 return $ret
3020 proc do_file_hl {serial} {
3021 global highlight_files filehighlight highlight_paths gdttype fhl_list
3023 if {$gdttype eq "touching paths:"} {
3024 if {[catch {set paths [shellsplit $highlight_files]}]} return
3025 set highlight_paths [makepatterns $paths]
3026 highlight_filelist
3027 set gdtargs [concat -- $paths]
3028 } elseif {$gdttype eq "adding/removing string:"} {
3029 set gdtargs [list "-S$highlight_files"]
3030 } else {
3031 # must be "containing:", i.e. we're searching commit info
3032 return
3034 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3035 set filehighlight [open $cmd r+]
3036 fconfigure $filehighlight -blocking 0
3037 filerun $filehighlight readfhighlight
3038 set fhl_list {}
3039 drawvisible
3040 flushhighlights
3043 proc flushhighlights {} {
3044 global filehighlight fhl_list
3046 if {[info exists filehighlight]} {
3047 lappend fhl_list {}
3048 puts $filehighlight ""
3049 flush $filehighlight
3053 proc askfilehighlight {row id} {
3054 global filehighlight fhighlights fhl_list
3056 lappend fhl_list $id
3057 set fhighlights($row) -1
3058 puts $filehighlight $id
3061 proc readfhighlight {} {
3062 global filehighlight fhighlights curview iddrawn
3063 global fhl_list find_dirn
3065 if {![info exists filehighlight]} {
3066 return 0
3068 set nr 0
3069 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3070 set line [string trim $line]
3071 set i [lsearch -exact $fhl_list $line]
3072 if {$i < 0} continue
3073 for {set j 0} {$j < $i} {incr j} {
3074 set id [lindex $fhl_list $j]
3075 if {[commitinview $id $curview]} {
3076 set fhighlights([rowofcommit $id]) 0
3079 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3080 if {$line eq {}} continue
3081 if {![commitinview $line $curview]} continue
3082 set row [rowofcommit $line]
3083 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
3084 bolden $row mainfontbold
3086 set fhighlights($row) 1
3088 if {[eof $filehighlight]} {
3089 # strange...
3090 puts "oops, git diff-tree died"
3091 catch {close $filehighlight}
3092 unset filehighlight
3093 return 0
3095 if {[info exists find_dirn]} {
3096 run findmore
3098 return 1
3101 proc doesmatch {f} {
3102 global findtype findpattern
3104 if {$findtype eq "Regexp"} {
3105 return [regexp $findpattern $f]
3106 } elseif {$findtype eq "IgnCase"} {
3107 return [string match -nocase $findpattern $f]
3108 } else {
3109 return [string match $findpattern $f]
3113 proc askfindhighlight {row id} {
3114 global nhighlights commitinfo iddrawn
3115 global findloc
3116 global markingmatches
3118 if {![info exists commitinfo($id)]} {
3119 getcommit $id
3121 set info $commitinfo($id)
3122 set isbold 0
3123 set fldtypes {Headline Author Date Committer CDate Comments}
3124 foreach f $info ty $fldtypes {
3125 if {($findloc eq "All fields" || $findloc eq $ty) &&
3126 [doesmatch $f]} {
3127 if {$ty eq "Author"} {
3128 set isbold 2
3129 break
3131 set isbold 1
3134 if {$isbold && [info exists iddrawn($id)]} {
3135 if {![ishighlighted $row]} {
3136 bolden $row mainfontbold
3137 if {$isbold > 1} {
3138 bolden_name $row mainfontbold
3141 if {$markingmatches} {
3142 markrowmatches $row $id
3145 set nhighlights($row) $isbold
3148 proc markrowmatches {row id} {
3149 global canv canv2 linehtag linentag commitinfo findloc
3151 set headline [lindex $commitinfo($id) 0]
3152 set author [lindex $commitinfo($id) 1]
3153 $canv delete match$row
3154 $canv2 delete match$row
3155 if {$findloc eq "All fields" || $findloc eq "Headline"} {
3156 set m [findmatches $headline]
3157 if {$m ne {}} {
3158 markmatches $canv $row $headline $linehtag($row) $m \
3159 [$canv itemcget $linehtag($row) -font] $row
3162 if {$findloc eq "All fields" || $findloc eq "Author"} {
3163 set m [findmatches $author]
3164 if {$m ne {}} {
3165 markmatches $canv2 $row $author $linentag($row) $m \
3166 [$canv2 itemcget $linentag($row) -font] $row
3171 proc vrel_change {name ix op} {
3172 global highlight_related
3174 rhighlight_none
3175 if {$highlight_related ne "None"} {
3176 run drawvisible
3180 # prepare for testing whether commits are descendents or ancestors of a
3181 proc rhighlight_sel {a} {
3182 global descendent desc_todo ancestor anc_todo
3183 global highlight_related rhighlights
3185 catch {unset descendent}
3186 set desc_todo [list $a]
3187 catch {unset ancestor}
3188 set anc_todo [list $a]
3189 if {$highlight_related ne "None"} {
3190 rhighlight_none
3191 run drawvisible
3195 proc rhighlight_none {} {
3196 global rhighlights
3198 catch {unset rhighlights}
3199 unbolden
3202 proc is_descendent {a} {
3203 global curview children descendent desc_todo
3205 set v $curview
3206 set la [rowofcommit $a]
3207 set todo $desc_todo
3208 set leftover {}
3209 set done 0
3210 for {set i 0} {$i < [llength $todo]} {incr i} {
3211 set do [lindex $todo $i]
3212 if {[rowofcommit $do] < $la} {
3213 lappend leftover $do
3214 continue
3216 foreach nk $children($v,$do) {
3217 if {![info exists descendent($nk)]} {
3218 set descendent($nk) 1
3219 lappend todo $nk
3220 if {$nk eq $a} {
3221 set done 1
3225 if {$done} {
3226 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3227 return
3230 set descendent($a) 0
3231 set desc_todo $leftover
3234 proc is_ancestor {a} {
3235 global curview parents ancestor anc_todo
3237 set v $curview
3238 set la [rowofcommit $a]
3239 set todo $anc_todo
3240 set leftover {}
3241 set done 0
3242 for {set i 0} {$i < [llength $todo]} {incr i} {
3243 set do [lindex $todo $i]
3244 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3245 lappend leftover $do
3246 continue
3248 foreach np $parents($v,$do) {
3249 if {![info exists ancestor($np)]} {
3250 set ancestor($np) 1
3251 lappend todo $np
3252 if {$np eq $a} {
3253 set done 1
3257 if {$done} {
3258 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3259 return
3262 set ancestor($a) 0
3263 set anc_todo $leftover
3266 proc askrelhighlight {row id} {
3267 global descendent highlight_related iddrawn rhighlights
3268 global selectedline ancestor
3270 if {![info exists selectedline]} return
3271 set isbold 0
3272 if {$highlight_related eq "Descendent" ||
3273 $highlight_related eq "Not descendent"} {
3274 if {![info exists descendent($id)]} {
3275 is_descendent $id
3277 if {$descendent($id) == ($highlight_related eq "Descendent")} {
3278 set isbold 1
3280 } elseif {$highlight_related eq "Ancestor" ||
3281 $highlight_related eq "Not ancestor"} {
3282 if {![info exists ancestor($id)]} {
3283 is_ancestor $id
3285 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
3286 set isbold 1
3289 if {[info exists iddrawn($id)]} {
3290 if {$isbold && ![ishighlighted $row]} {
3291 bolden $row mainfontbold
3294 set rhighlights($row) $isbold
3297 # Graph layout functions
3299 proc shortids {ids} {
3300 set res {}
3301 foreach id $ids {
3302 if {[llength $id] > 1} {
3303 lappend res [shortids $id]
3304 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3305 lappend res [string range $id 0 7]
3306 } else {
3307 lappend res $id
3310 return $res
3313 proc ntimes {n o} {
3314 set ret {}
3315 set o [list $o]
3316 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3317 if {($n & $mask) != 0} {
3318 set ret [concat $ret $o]
3320 set o [concat $o $o]
3322 return $ret
3325 proc ordertoken {id} {
3326 global ordertok curview varcid varcstart varctok curview parents children
3327 global nullid nullid2
3329 if {[info exists ordertok($id)]} {
3330 return $ordertok($id)
3332 set origid $id
3333 set todo {}
3334 while {1} {
3335 if {[info exists varcid($curview,$id)]} {
3336 set a $varcid($curview,$id)
3337 set p [lindex $varcstart($curview) $a]
3338 } else {
3339 set p [lindex $children($curview,$id) 0]
3341 if {[info exists ordertok($p)]} {
3342 set tok $ordertok($p)
3343 break
3345 if {[llength $children($curview,$p)] == 0} {
3346 # it's a root
3347 set tok [lindex $varctok($curview) $a]
3348 break
3350 set id [lindex $children($curview,$p) 0]
3351 if {$id eq $nullid || $id eq $nullid2} {
3352 # XXX treat it as a root
3353 set tok [lindex $varctok($curview) $a]
3354 break
3356 if {[llength $parents($curview,$id)] == 1} {
3357 lappend todo [list $p {}]
3358 } else {
3359 set j [lsearch -exact $parents($curview,$id) $p]
3360 if {$j < 0} {
3361 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3363 lappend todo [list $p [strrep $j]]
3366 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3367 set p [lindex $todo $i 0]
3368 append tok [lindex $todo $i 1]
3369 set ordertok($p) $tok
3371 set ordertok($origid) $tok
3372 return $tok
3375 # Work out where id should go in idlist so that order-token
3376 # values increase from left to right
3377 proc idcol {idlist id {i 0}} {
3378 set t [ordertoken $id]
3379 if {$i < 0} {
3380 set i 0
3382 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3383 if {$i > [llength $idlist]} {
3384 set i [llength $idlist]
3386 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3387 incr i
3388 } else {
3389 if {$t > [ordertoken [lindex $idlist $i]]} {
3390 while {[incr i] < [llength $idlist] &&
3391 $t >= [ordertoken [lindex $idlist $i]]} {}
3394 return $i
3397 proc initlayout {} {
3398 global rowidlist rowisopt rowfinal displayorder parentlist
3399 global numcommits canvxmax canv
3400 global nextcolor
3401 global colormap rowtextx
3402 global selectfirst
3404 set numcommits 0
3405 set displayorder {}
3406 set parentlist {}
3407 set nextcolor 0
3408 set rowidlist {}
3409 set rowisopt {}
3410 set rowfinal {}
3411 set canvxmax [$canv cget -width]
3412 catch {unset colormap}
3413 catch {unset rowtextx}
3414 set selectfirst 1
3417 proc setcanvscroll {} {
3418 global canv canv2 canv3 numcommits linespc canvxmax canvy0
3420 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3421 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3422 $canv2 conf -scrollregion [list 0 0 0 $ymax]
3423 $canv3 conf -scrollregion [list 0 0 0 $ymax]
3426 proc visiblerows {} {
3427 global canv numcommits linespc
3429 set ymax [lindex [$canv cget -scrollregion] 3]
3430 if {$ymax eq {} || $ymax == 0} return
3431 set f [$canv yview]
3432 set y0 [expr {int([lindex $f 0] * $ymax)}]
3433 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
3434 if {$r0 < 0} {
3435 set r0 0
3437 set y1 [expr {int([lindex $f 1] * $ymax)}]
3438 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
3439 if {$r1 >= $numcommits} {
3440 set r1 [expr {$numcommits - 1}]
3442 return [list $r0 $r1]
3445 proc layoutmore {} {
3446 global commitidx viewcomplete curview
3447 global numcommits pending_select selectedline curview
3448 global selectfirst lastscrollset commitinterest
3450 set canshow $commitidx($curview)
3451 if {$canshow <= $numcommits && !$viewcomplete($curview)} return
3452 if {$numcommits == 0} {
3453 allcanvs delete all
3455 set r0 $numcommits
3456 set prev $numcommits
3457 set numcommits $canshow
3458 set t [clock clicks -milliseconds]
3459 if {$prev < 100 || $viewcomplete($curview) || $t - $lastscrollset > 500} {
3460 set lastscrollset $t
3461 setcanvscroll
3463 set rows [visiblerows]
3464 set r1 [lindex $rows 1]
3465 if {$r1 >= $canshow} {
3466 set r1 [expr {$canshow - 1}]
3468 if {$r0 <= $r1} {
3469 drawcommits $r0 $r1
3471 if {[info exists pending_select] &&
3472 [commitinview $pending_select $curview]} {
3473 selectline [rowofcommit $pending_select] 1
3475 if {$selectfirst} {
3476 if {[info exists selectedline] || [info exists pending_select]} {
3477 set selectfirst 0
3478 } else {
3479 set l [first_real_row]
3480 selectline $l 1
3481 set selectfirst 0
3486 proc doshowlocalchanges {} {
3487 global curview mainheadid
3489 if {[commitinview $mainheadid $curview]} {
3490 dodiffindex
3491 } else {
3492 lappend commitinterest($mainheadid) {dodiffindex}
3496 proc dohidelocalchanges {} {
3497 global nullid nullid2 lserial curview
3499 if {[commitinview $nullid $curview]} {
3500 removerow $nullid $curview
3502 if {[commitinview $nullid2 $curview]} {
3503 removerow $nullid2 $curview
3505 incr lserial
3508 # spawn off a process to do git diff-index --cached HEAD
3509 proc dodiffindex {} {
3510 global lserial showlocalchanges
3512 if {!$showlocalchanges} return
3513 incr lserial
3514 set fd [open "|git diff-index --cached HEAD" r]
3515 fconfigure $fd -blocking 0
3516 filerun $fd [list readdiffindex $fd $lserial]
3519 proc readdiffindex {fd serial} {
3520 global mainheadid nullid2 curview commitinfo commitdata lserial
3522 set isdiff 1
3523 if {[gets $fd line] < 0} {
3524 if {![eof $fd]} {
3525 return 1
3527 set isdiff 0
3529 # we only need to see one line and we don't really care what it says...
3530 close $fd
3532 if {$serial != $lserial} {
3533 return 0
3536 # now see if there are any local changes not checked in to the index
3537 set fd [open "|git diff-files" r]
3538 fconfigure $fd -blocking 0
3539 filerun $fd [list readdifffiles $fd $serial]
3541 if {$isdiff && ![commitinview $nullid2 $curview]} {
3542 # add the line for the changes in the index to the graph
3543 set hl "Local changes checked in to index but not committed"
3544 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
3545 set commitdata($nullid2) "\n $hl\n"
3546 insertrow $nullid2 $mainheadid $curview
3547 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
3548 removerow $nullid2 $curview
3550 return 0
3553 proc readdifffiles {fd serial} {
3554 global mainheadid nullid nullid2 curview
3555 global commitinfo commitdata lserial
3557 set isdiff 1
3558 if {[gets $fd line] < 0} {
3559 if {![eof $fd]} {
3560 return 1
3562 set isdiff 0
3564 # we only need to see one line and we don't really care what it says...
3565 close $fd
3567 if {$serial != $lserial} {
3568 return 0
3571 if {$isdiff && ![commitinview $nullid $curview]} {
3572 # add the line for the local diff to the graph
3573 set hl "Local uncommitted changes, not checked in to index"
3574 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
3575 set commitdata($nullid) "\n $hl\n"
3576 if {[commitinview $nullid2 $curview]} {
3577 set p $nullid2
3578 } else {
3579 set p $mainheadid
3581 insertrow $nullid $p $curview
3582 } elseif {!$isdiff && [commitinview $nullid $curview]} {
3583 removerow $nullid $curview
3585 return 0
3588 proc nextuse {id row} {
3589 global curview children
3591 if {[info exists children($curview,$id)]} {
3592 foreach kid $children($curview,$id) {
3593 if {![commitinview $kid $curview]} {
3594 return -1
3596 if {[rowofcommit $kid] > $row} {
3597 return [rowofcommit $kid]
3601 if {[commitinview $id $curview]} {
3602 return [rowofcommit $id]
3604 return -1
3607 proc prevuse {id row} {
3608 global curview children
3610 set ret -1
3611 if {[info exists children($curview,$id)]} {
3612 foreach kid $children($curview,$id) {
3613 if {![commitinview $kid $curview]} break
3614 if {[rowofcommit $kid] < $row} {
3615 set ret [rowofcommit $kid]
3619 return $ret
3622 proc make_idlist {row} {
3623 global displayorder parentlist uparrowlen downarrowlen mingaplen
3624 global commitidx curview children
3626 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
3627 if {$r < 0} {
3628 set r 0
3630 set ra [expr {$row - $downarrowlen}]
3631 if {$ra < 0} {
3632 set ra 0
3634 set rb [expr {$row + $uparrowlen}]
3635 if {$rb > $commitidx($curview)} {
3636 set rb $commitidx($curview)
3638 make_disporder $r [expr {$rb + 1}]
3639 set ids {}
3640 for {} {$r < $ra} {incr r} {
3641 set nextid [lindex $displayorder [expr {$r + 1}]]
3642 foreach p [lindex $parentlist $r] {
3643 if {$p eq $nextid} continue
3644 set rn [nextuse $p $r]
3645 if {$rn >= $row &&
3646 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
3647 lappend ids [list [ordertoken $p] $p]
3651 for {} {$r < $row} {incr r} {
3652 set nextid [lindex $displayorder [expr {$r + 1}]]
3653 foreach p [lindex $parentlist $r] {
3654 if {$p eq $nextid} continue
3655 set rn [nextuse $p $r]
3656 if {$rn < 0 || $rn >= $row} {
3657 lappend ids [list [ordertoken $p] $p]
3661 set id [lindex $displayorder $row]
3662 lappend ids [list [ordertoken $id] $id]
3663 while {$r < $rb} {
3664 foreach p [lindex $parentlist $r] {
3665 set firstkid [lindex $children($curview,$p) 0]
3666 if {[rowofcommit $firstkid] < $row} {
3667 lappend ids [list [ordertoken $p] $p]
3670 incr r
3671 set id [lindex $displayorder $r]
3672 if {$id ne {}} {
3673 set firstkid [lindex $children($curview,$id) 0]
3674 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
3675 lappend ids [list [ordertoken $id] $id]
3679 set idlist {}
3680 foreach idx [lsort -unique $ids] {
3681 lappend idlist [lindex $idx 1]
3683 return $idlist
3686 proc rowsequal {a b} {
3687 while {[set i [lsearch -exact $a {}]] >= 0} {
3688 set a [lreplace $a $i $i]
3690 while {[set i [lsearch -exact $b {}]] >= 0} {
3691 set b [lreplace $b $i $i]
3693 return [expr {$a eq $b}]
3696 proc makeupline {id row rend col} {
3697 global rowidlist uparrowlen downarrowlen mingaplen
3699 for {set r $rend} {1} {set r $rstart} {
3700 set rstart [prevuse $id $r]
3701 if {$rstart < 0} return
3702 if {$rstart < $row} break
3704 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
3705 set rstart [expr {$rend - $uparrowlen - 1}]
3707 for {set r $rstart} {[incr r] <= $row} {} {
3708 set idlist [lindex $rowidlist $r]
3709 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
3710 set col [idcol $idlist $id $col]
3711 lset rowidlist $r [linsert $idlist $col $id]
3712 changedrow $r
3717 proc layoutrows {row endrow} {
3718 global rowidlist rowisopt rowfinal displayorder
3719 global uparrowlen downarrowlen maxwidth mingaplen
3720 global children parentlist
3721 global commitidx viewcomplete curview
3723 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
3724 set idlist {}
3725 if {$row > 0} {
3726 set rm1 [expr {$row - 1}]
3727 foreach id [lindex $rowidlist $rm1] {
3728 if {$id ne {}} {
3729 lappend idlist $id
3732 set final [lindex $rowfinal $rm1]
3734 for {} {$row < $endrow} {incr row} {
3735 set rm1 [expr {$row - 1}]
3736 if {$rm1 < 0 || $idlist eq {}} {
3737 set idlist [make_idlist $row]
3738 set final 1
3739 } else {
3740 set id [lindex $displayorder $rm1]
3741 set col [lsearch -exact $idlist $id]
3742 set idlist [lreplace $idlist $col $col]
3743 foreach p [lindex $parentlist $rm1] {
3744 if {[lsearch -exact $idlist $p] < 0} {
3745 set col [idcol $idlist $p $col]
3746 set idlist [linsert $idlist $col $p]
3747 # if not the first child, we have to insert a line going up
3748 if {$id ne [lindex $children($curview,$p) 0]} {
3749 makeupline $p $rm1 $row $col
3753 set id [lindex $displayorder $row]
3754 if {$row > $downarrowlen} {
3755 set termrow [expr {$row - $downarrowlen - 1}]
3756 foreach p [lindex $parentlist $termrow] {
3757 set i [lsearch -exact $idlist $p]
3758 if {$i < 0} continue
3759 set nr [nextuse $p $termrow]
3760 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
3761 set idlist [lreplace $idlist $i $i]
3765 set col [lsearch -exact $idlist $id]
3766 if {$col < 0} {
3767 set col [idcol $idlist $id]
3768 set idlist [linsert $idlist $col $id]
3769 if {$children($curview,$id) ne {}} {
3770 makeupline $id $rm1 $row $col
3773 set r [expr {$row + $uparrowlen - 1}]
3774 if {$r < $commitidx($curview)} {
3775 set x $col
3776 foreach p [lindex $parentlist $r] {
3777 if {[lsearch -exact $idlist $p] >= 0} continue
3778 set fk [lindex $children($curview,$p) 0]
3779 if {[rowofcommit $fk] < $row} {
3780 set x [idcol $idlist $p $x]
3781 set idlist [linsert $idlist $x $p]
3784 if {[incr r] < $commitidx($curview)} {
3785 set p [lindex $displayorder $r]
3786 if {[lsearch -exact $idlist $p] < 0} {
3787 set fk [lindex $children($curview,$p) 0]
3788 if {$fk ne {} && [rowofcommit $fk] < $row} {
3789 set x [idcol $idlist $p $x]
3790 set idlist [linsert $idlist $x $p]
3796 if {$final && !$viewcomplete($curview) &&
3797 $row + $uparrowlen + $mingaplen + $downarrowlen
3798 >= $commitidx($curview)} {
3799 set final 0
3801 set l [llength $rowidlist]
3802 if {$row == $l} {
3803 lappend rowidlist $idlist
3804 lappend rowisopt 0
3805 lappend rowfinal $final
3806 } elseif {$row < $l} {
3807 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
3808 lset rowidlist $row $idlist
3809 changedrow $row
3811 lset rowfinal $row $final
3812 } else {
3813 set pad [ntimes [expr {$row - $l}] {}]
3814 set rowidlist [concat $rowidlist $pad]
3815 lappend rowidlist $idlist
3816 set rowfinal [concat $rowfinal $pad]
3817 lappend rowfinal $final
3818 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
3821 return $row
3824 proc changedrow {row} {
3825 global displayorder iddrawn rowisopt need_redisplay
3827 set l [llength $rowisopt]
3828 if {$row < $l} {
3829 lset rowisopt $row 0
3830 if {$row + 1 < $l} {
3831 lset rowisopt [expr {$row + 1}] 0
3832 if {$row + 2 < $l} {
3833 lset rowisopt [expr {$row + 2}] 0
3837 set id [lindex $displayorder $row]
3838 if {[info exists iddrawn($id)]} {
3839 set need_redisplay 1
3843 proc insert_pad {row col npad} {
3844 global rowidlist
3846 set pad [ntimes $npad {}]
3847 set idlist [lindex $rowidlist $row]
3848 set bef [lrange $idlist 0 [expr {$col - 1}]]
3849 set aft [lrange $idlist $col end]
3850 set i [lsearch -exact $aft {}]
3851 if {$i > 0} {
3852 set aft [lreplace $aft $i $i]
3854 lset rowidlist $row [concat $bef $pad $aft]
3855 changedrow $row
3858 proc optimize_rows {row col endrow} {
3859 global rowidlist rowisopt displayorder curview children
3861 if {$row < 1} {
3862 set row 1
3864 for {} {$row < $endrow} {incr row; set col 0} {
3865 if {[lindex $rowisopt $row]} continue
3866 set haspad 0
3867 set y0 [expr {$row - 1}]
3868 set ym [expr {$row - 2}]
3869 set idlist [lindex $rowidlist $row]
3870 set previdlist [lindex $rowidlist $y0]
3871 if {$idlist eq {} || $previdlist eq {}} continue
3872 if {$ym >= 0} {
3873 set pprevidlist [lindex $rowidlist $ym]
3874 if {$pprevidlist eq {}} continue
3875 } else {
3876 set pprevidlist {}
3878 set x0 -1
3879 set xm -1
3880 for {} {$col < [llength $idlist]} {incr col} {
3881 set id [lindex $idlist $col]
3882 if {[lindex $previdlist $col] eq $id} continue
3883 if {$id eq {}} {
3884 set haspad 1
3885 continue
3887 set x0 [lsearch -exact $previdlist $id]
3888 if {$x0 < 0} continue
3889 set z [expr {$x0 - $col}]
3890 set isarrow 0
3891 set z0 {}
3892 if {$ym >= 0} {
3893 set xm [lsearch -exact $pprevidlist $id]
3894 if {$xm >= 0} {
3895 set z0 [expr {$xm - $x0}]
3898 if {$z0 eq {}} {
3899 # if row y0 is the first child of $id then it's not an arrow
3900 if {[lindex $children($curview,$id) 0] ne
3901 [lindex $displayorder $y0]} {
3902 set isarrow 1
3905 if {!$isarrow && $id ne [lindex $displayorder $row] &&
3906 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
3907 set isarrow 1
3909 # Looking at lines from this row to the previous row,
3910 # make them go straight up if they end in an arrow on
3911 # the previous row; otherwise make them go straight up
3912 # or at 45 degrees.
3913 if {$z < -1 || ($z < 0 && $isarrow)} {
3914 # Line currently goes left too much;
3915 # insert pads in the previous row, then optimize it
3916 set npad [expr {-1 - $z + $isarrow}]
3917 insert_pad $y0 $x0 $npad
3918 if {$y0 > 0} {
3919 optimize_rows $y0 $x0 $row
3921 set previdlist [lindex $rowidlist $y0]
3922 set x0 [lsearch -exact $previdlist $id]
3923 set z [expr {$x0 - $col}]
3924 if {$z0 ne {}} {
3925 set pprevidlist [lindex $rowidlist $ym]
3926 set xm [lsearch -exact $pprevidlist $id]
3927 set z0 [expr {$xm - $x0}]
3929 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3930 # Line currently goes right too much;
3931 # insert pads in this line
3932 set npad [expr {$z - 1 + $isarrow}]
3933 insert_pad $row $col $npad
3934 set idlist [lindex $rowidlist $row]
3935 incr col $npad
3936 set z [expr {$x0 - $col}]
3937 set haspad 1
3939 if {$z0 eq {} && !$isarrow && $ym >= 0} {
3940 # this line links to its first child on row $row-2
3941 set id [lindex $displayorder $ym]
3942 set xc [lsearch -exact $pprevidlist $id]
3943 if {$xc >= 0} {
3944 set z0 [expr {$xc - $x0}]
3947 # avoid lines jigging left then immediately right
3948 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3949 insert_pad $y0 $x0 1
3950 incr x0
3951 optimize_rows $y0 $x0 $row
3952 set previdlist [lindex $rowidlist $y0]
3955 if {!$haspad} {
3956 # Find the first column that doesn't have a line going right
3957 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3958 set id [lindex $idlist $col]
3959 if {$id eq {}} break
3960 set x0 [lsearch -exact $previdlist $id]
3961 if {$x0 < 0} {
3962 # check if this is the link to the first child
3963 set kid [lindex $displayorder $y0]
3964 if {[lindex $children($curview,$id) 0] eq $kid} {
3965 # it is, work out offset to child
3966 set x0 [lsearch -exact $previdlist $kid]
3969 if {$x0 <= $col} break
3971 # Insert a pad at that column as long as it has a line and
3972 # isn't the last column
3973 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
3974 set idlist [linsert $idlist $col {}]
3975 lset rowidlist $row $idlist
3976 changedrow $row
3982 proc xc {row col} {
3983 global canvx0 linespc
3984 return [expr {$canvx0 + $col * $linespc}]
3987 proc yc {row} {
3988 global canvy0 linespc
3989 return [expr {$canvy0 + $row * $linespc}]
3992 proc linewidth {id} {
3993 global thickerline lthickness
3995 set wid $lthickness
3996 if {[info exists thickerline] && $id eq $thickerline} {
3997 set wid [expr {2 * $lthickness}]
3999 return $wid
4002 proc rowranges {id} {
4003 global curview children uparrowlen downarrowlen
4004 global rowidlist
4006 set kids $children($curview,$id)
4007 if {$kids eq {}} {
4008 return {}
4010 set ret {}
4011 lappend kids $id
4012 foreach child $kids {
4013 if {![commitinview $child $curview]} break
4014 set row [rowofcommit $child]
4015 if {![info exists prev]} {
4016 lappend ret [expr {$row + 1}]
4017 } else {
4018 if {$row <= $prevrow} {
4019 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4021 # see if the line extends the whole way from prevrow to row
4022 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4023 [lsearch -exact [lindex $rowidlist \
4024 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4025 # it doesn't, see where it ends
4026 set r [expr {$prevrow + $downarrowlen}]
4027 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4028 while {[incr r -1] > $prevrow &&
4029 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4030 } else {
4031 while {[incr r] <= $row &&
4032 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4033 incr r -1
4035 lappend ret $r
4036 # see where it starts up again
4037 set r [expr {$row - $uparrowlen}]
4038 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4039 while {[incr r] < $row &&
4040 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4041 } else {
4042 while {[incr r -1] >= $prevrow &&
4043 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4044 incr r
4046 lappend ret $r
4049 if {$child eq $id} {
4050 lappend ret $row
4052 set prev $child
4053 set prevrow $row
4055 return $ret
4058 proc drawlineseg {id row endrow arrowlow} {
4059 global rowidlist displayorder iddrawn linesegs
4060 global canv colormap linespc curview maxlinelen parentlist
4062 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4063 set le [expr {$row + 1}]
4064 set arrowhigh 1
4065 while {1} {
4066 set c [lsearch -exact [lindex $rowidlist $le] $id]
4067 if {$c < 0} {
4068 incr le -1
4069 break
4071 lappend cols $c
4072 set x [lindex $displayorder $le]
4073 if {$x eq $id} {
4074 set arrowhigh 0
4075 break
4077 if {[info exists iddrawn($x)] || $le == $endrow} {
4078 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4079 if {$c >= 0} {
4080 lappend cols $c
4081 set arrowhigh 0
4083 break
4085 incr le
4087 if {$le <= $row} {
4088 return $row
4091 set lines {}
4092 set i 0
4093 set joinhigh 0
4094 if {[info exists linesegs($id)]} {
4095 set lines $linesegs($id)
4096 foreach li $lines {
4097 set r0 [lindex $li 0]
4098 if {$r0 > $row} {
4099 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4100 set joinhigh 1
4102 break
4104 incr i
4107 set joinlow 0
4108 if {$i > 0} {
4109 set li [lindex $lines [expr {$i-1}]]
4110 set r1 [lindex $li 1]
4111 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4112 set joinlow 1
4116 set x [lindex $cols [expr {$le - $row}]]
4117 set xp [lindex $cols [expr {$le - 1 - $row}]]
4118 set dir [expr {$xp - $x}]
4119 if {$joinhigh} {
4120 set ith [lindex $lines $i 2]
4121 set coords [$canv coords $ith]
4122 set ah [$canv itemcget $ith -arrow]
4123 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4124 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4125 if {$x2 ne {} && $x - $x2 == $dir} {
4126 set coords [lrange $coords 0 end-2]
4128 } else {
4129 set coords [list [xc $le $x] [yc $le]]
4131 if {$joinlow} {
4132 set itl [lindex $lines [expr {$i-1}] 2]
4133 set al [$canv itemcget $itl -arrow]
4134 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4135 } elseif {$arrowlow} {
4136 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4137 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4138 set arrowlow 0
4141 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4142 for {set y $le} {[incr y -1] > $row} {} {
4143 set x $xp
4144 set xp [lindex $cols [expr {$y - 1 - $row}]]
4145 set ndir [expr {$xp - $x}]
4146 if {$dir != $ndir || $xp < 0} {
4147 lappend coords [xc $y $x] [yc $y]
4149 set dir $ndir
4151 if {!$joinlow} {
4152 if {$xp < 0} {
4153 # join parent line to first child
4154 set ch [lindex $displayorder $row]
4155 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4156 if {$xc < 0} {
4157 puts "oops: drawlineseg: child $ch not on row $row"
4158 } elseif {$xc != $x} {
4159 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4160 set d [expr {int(0.5 * $linespc)}]
4161 set x1 [xc $row $x]
4162 if {$xc < $x} {
4163 set x2 [expr {$x1 - $d}]
4164 } else {
4165 set x2 [expr {$x1 + $d}]
4167 set y2 [yc $row]
4168 set y1 [expr {$y2 + $d}]
4169 lappend coords $x1 $y1 $x2 $y2
4170 } elseif {$xc < $x - 1} {
4171 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4172 } elseif {$xc > $x + 1} {
4173 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4175 set x $xc
4177 lappend coords [xc $row $x] [yc $row]
4178 } else {
4179 set xn [xc $row $xp]
4180 set yn [yc $row]
4181 lappend coords $xn $yn
4183 if {!$joinhigh} {
4184 assigncolor $id
4185 set t [$canv create line $coords -width [linewidth $id] \
4186 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4187 $canv lower $t
4188 bindline $t $id
4189 set lines [linsert $lines $i [list $row $le $t]]
4190 } else {
4191 $canv coords $ith $coords
4192 if {$arrow ne $ah} {
4193 $canv itemconf $ith -arrow $arrow
4195 lset lines $i 0 $row
4197 } else {
4198 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4199 set ndir [expr {$xo - $xp}]
4200 set clow [$canv coords $itl]
4201 if {$dir == $ndir} {
4202 set clow [lrange $clow 2 end]
4204 set coords [concat $coords $clow]
4205 if {!$joinhigh} {
4206 lset lines [expr {$i-1}] 1 $le
4207 } else {
4208 # coalesce two pieces
4209 $canv delete $ith
4210 set b [lindex $lines [expr {$i-1}] 0]
4211 set e [lindex $lines $i 1]
4212 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4214 $canv coords $itl $coords
4215 if {$arrow ne $al} {
4216 $canv itemconf $itl -arrow $arrow
4220 set linesegs($id) $lines
4221 return $le
4224 proc drawparentlinks {id row} {
4225 global rowidlist canv colormap curview parentlist
4226 global idpos linespc
4228 set rowids [lindex $rowidlist $row]
4229 set col [lsearch -exact $rowids $id]
4230 if {$col < 0} return
4231 set olds [lindex $parentlist $row]
4232 set row2 [expr {$row + 1}]
4233 set x [xc $row $col]
4234 set y [yc $row]
4235 set y2 [yc $row2]
4236 set d [expr {int(0.5 * $linespc)}]
4237 set ymid [expr {$y + $d}]
4238 set ids [lindex $rowidlist $row2]
4239 # rmx = right-most X coord used
4240 set rmx 0
4241 foreach p $olds {
4242 set i [lsearch -exact $ids $p]
4243 if {$i < 0} {
4244 puts "oops, parent $p of $id not in list"
4245 continue
4247 set x2 [xc $row2 $i]
4248 if {$x2 > $rmx} {
4249 set rmx $x2
4251 set j [lsearch -exact $rowids $p]
4252 if {$j < 0} {
4253 # drawlineseg will do this one for us
4254 continue
4256 assigncolor $p
4257 # should handle duplicated parents here...
4258 set coords [list $x $y]
4259 if {$i != $col} {
4260 # if attaching to a vertical segment, draw a smaller
4261 # slant for visual distinctness
4262 if {$i == $j} {
4263 if {$i < $col} {
4264 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4265 } else {
4266 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4268 } elseif {$i < $col && $i < $j} {
4269 # segment slants towards us already
4270 lappend coords [xc $row $j] $y
4271 } else {
4272 if {$i < $col - 1} {
4273 lappend coords [expr {$x2 + $linespc}] $y
4274 } elseif {$i > $col + 1} {
4275 lappend coords [expr {$x2 - $linespc}] $y
4277 lappend coords $x2 $y2
4279 } else {
4280 lappend coords $x2 $y2
4282 set t [$canv create line $coords -width [linewidth $p] \
4283 -fill $colormap($p) -tags lines.$p]
4284 $canv lower $t
4285 bindline $t $p
4287 if {$rmx > [lindex $idpos($id) 1]} {
4288 lset idpos($id) 1 $rmx
4289 redrawtags $id
4293 proc drawlines {id} {
4294 global canv
4296 $canv itemconf lines.$id -width [linewidth $id]
4299 proc drawcmittext {id row col} {
4300 global linespc canv canv2 canv3 fgcolor curview
4301 global cmitlisted commitinfo rowidlist parentlist
4302 global rowtextx idpos idtags idheads idotherrefs
4303 global linehtag linentag linedtag selectedline
4304 global canvxmax boldrows boldnamerows fgcolor nullid nullid2
4306 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
4307 set listed $cmitlisted($curview,$id)
4308 if {$id eq $nullid} {
4309 set ofill red
4310 } elseif {$id eq $nullid2} {
4311 set ofill green
4312 } else {
4313 set ofill [expr {$listed != 0? "blue": "white"}]
4315 set x [xc $row $col]
4316 set y [yc $row]
4317 set orad [expr {$linespc / 3}]
4318 if {$listed <= 1} {
4319 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4320 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4321 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4322 } elseif {$listed == 2} {
4323 # triangle pointing left for left-side commits
4324 set t [$canv create polygon \
4325 [expr {$x - $orad}] $y \
4326 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4327 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4328 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4329 } else {
4330 # triangle pointing right for right-side commits
4331 set t [$canv create polygon \
4332 [expr {$x + $orad - 1}] $y \
4333 [expr {$x - $orad}] [expr {$y - $orad}] \
4334 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4335 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4337 $canv raise $t
4338 $canv bind $t <1> {selcanvline {} %x %y}
4339 set rmx [llength [lindex $rowidlist $row]]
4340 set olds [lindex $parentlist $row]
4341 if {$olds ne {}} {
4342 set nextids [lindex $rowidlist [expr {$row + 1}]]
4343 foreach p $olds {
4344 set i [lsearch -exact $nextids $p]
4345 if {$i > $rmx} {
4346 set rmx $i
4350 set xt [xc $row $rmx]
4351 set rowtextx($row) $xt
4352 set idpos($id) [list $x $xt $y]
4353 if {[info exists idtags($id)] || [info exists idheads($id)]
4354 || [info exists idotherrefs($id)]} {
4355 set xt [drawtags $id $x $xt $y]
4357 set headline [lindex $commitinfo($id) 0]
4358 set name [lindex $commitinfo($id) 1]
4359 set date [lindex $commitinfo($id) 2]
4360 set date [formatdate $date]
4361 set font mainfont
4362 set nfont mainfont
4363 set isbold [ishighlighted $row]
4364 if {$isbold > 0} {
4365 lappend boldrows $row
4366 set font mainfontbold
4367 if {$isbold > 1} {
4368 lappend boldnamerows $row
4369 set nfont mainfontbold
4372 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4373 -text $headline -font $font -tags text]
4374 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4375 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4376 -text $name -font $nfont -tags text]
4377 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4378 -text $date -font mainfont -tags text]
4379 if {[info exists selectedline] && $selectedline == $row} {
4380 make_secsel $row
4382 set xr [expr {$xt + [font measure $font $headline]}]
4383 if {$xr > $canvxmax} {
4384 set canvxmax $xr
4385 setcanvscroll
4389 proc drawcmitrow {row} {
4390 global displayorder rowidlist nrows_drawn
4391 global iddrawn markingmatches
4392 global commitinfo numcommits
4393 global filehighlight fhighlights findpattern nhighlights
4394 global hlview vhighlights
4395 global highlight_related rhighlights
4397 if {$row >= $numcommits} return
4399 set id [lindex $displayorder $row]
4400 if {[info exists hlview] && ![info exists vhighlights($row)]} {
4401 askvhighlight $row $id
4403 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
4404 askfilehighlight $row $id
4406 if {$findpattern ne {} && ![info exists nhighlights($row)]} {
4407 askfindhighlight $row $id
4409 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
4410 askrelhighlight $row $id
4412 if {![info exists iddrawn($id)]} {
4413 set col [lsearch -exact [lindex $rowidlist $row] $id]
4414 if {$col < 0} {
4415 puts "oops, row $row id $id not in list"
4416 return
4418 if {![info exists commitinfo($id)]} {
4419 getcommit $id
4421 assigncolor $id
4422 drawcmittext $id $row $col
4423 set iddrawn($id) 1
4424 incr nrows_drawn
4426 if {$markingmatches} {
4427 markrowmatches $row $id
4431 proc drawcommits {row {endrow {}}} {
4432 global numcommits iddrawn displayorder curview need_redisplay
4433 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
4435 if {$row < 0} {
4436 set row 0
4438 if {$endrow eq {}} {
4439 set endrow $row
4441 if {$endrow >= $numcommits} {
4442 set endrow [expr {$numcommits - 1}]
4445 set rl1 [expr {$row - $downarrowlen - 3}]
4446 if {$rl1 < 0} {
4447 set rl1 0
4449 set ro1 [expr {$row - 3}]
4450 if {$ro1 < 0} {
4451 set ro1 0
4453 set r2 [expr {$endrow + $uparrowlen + 3}]
4454 if {$r2 > $numcommits} {
4455 set r2 $numcommits
4457 for {set r $rl1} {$r < $r2} {incr r} {
4458 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
4459 if {$rl1 < $r} {
4460 layoutrows $rl1 $r
4462 set rl1 [expr {$r + 1}]
4465 if {$rl1 < $r} {
4466 layoutrows $rl1 $r
4468 optimize_rows $ro1 0 $r2
4469 if {$need_redisplay || $nrows_drawn > 2000} {
4470 clear_display
4471 drawvisible
4474 # make the lines join to already-drawn rows either side
4475 set r [expr {$row - 1}]
4476 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
4477 set r $row
4479 set er [expr {$endrow + 1}]
4480 if {$er >= $numcommits ||
4481 ![info exists iddrawn([lindex $displayorder $er])]} {
4482 set er $endrow
4484 for {} {$r <= $er} {incr r} {
4485 set id [lindex $displayorder $r]
4486 set wasdrawn [info exists iddrawn($id)]
4487 drawcmitrow $r
4488 if {$r == $er} break
4489 set nextid [lindex $displayorder [expr {$r + 1}]]
4490 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
4491 drawparentlinks $id $r
4493 set rowids [lindex $rowidlist $r]
4494 foreach lid $rowids {
4495 if {$lid eq {}} continue
4496 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
4497 if {$lid eq $id} {
4498 # see if this is the first child of any of its parents
4499 foreach p [lindex $parentlist $r] {
4500 if {[lsearch -exact $rowids $p] < 0} {
4501 # make this line extend up to the child
4502 set lineend($p) [drawlineseg $p $r $er 0]
4505 } else {
4506 set lineend($lid) [drawlineseg $lid $r $er 1]
4512 proc undolayout {row} {
4513 global uparrowlen mingaplen downarrowlen
4514 global rowidlist rowisopt rowfinal need_redisplay
4516 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
4517 if {$r < 0} {
4518 set r 0
4520 if {[llength $rowidlist] > $r} {
4521 incr r -1
4522 set rowidlist [lrange $rowidlist 0 $r]
4523 set rowfinal [lrange $rowfinal 0 $r]
4524 set rowisopt [lrange $rowisopt 0 $r]
4525 set need_redisplay 1
4526 run drawvisible
4530 proc drawfrac {f0 f1} {
4531 global canv linespc
4533 set ymax [lindex [$canv cget -scrollregion] 3]
4534 if {$ymax eq {} || $ymax == 0} return
4535 set y0 [expr {int($f0 * $ymax)}]
4536 set row [expr {int(($y0 - 3) / $linespc) - 1}]
4537 set y1 [expr {int($f1 * $ymax)}]
4538 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
4539 drawcommits $row $endrow
4542 proc drawvisible {} {
4543 global canv
4544 eval drawfrac [$canv yview]
4547 proc clear_display {} {
4548 global iddrawn linesegs need_redisplay nrows_drawn
4549 global vhighlights fhighlights nhighlights rhighlights
4551 allcanvs delete all
4552 catch {unset iddrawn}
4553 catch {unset linesegs}
4554 catch {unset vhighlights}
4555 catch {unset fhighlights}
4556 catch {unset nhighlights}
4557 catch {unset rhighlights}
4558 set need_redisplay 0
4559 set nrows_drawn 0
4562 proc findcrossings {id} {
4563 global rowidlist parentlist numcommits displayorder
4565 set cross {}
4566 set ccross {}
4567 foreach {s e} [rowranges $id] {
4568 if {$e >= $numcommits} {
4569 set e [expr {$numcommits - 1}]
4571 if {$e <= $s} continue
4572 for {set row $e} {[incr row -1] >= $s} {} {
4573 set x [lsearch -exact [lindex $rowidlist $row] $id]
4574 if {$x < 0} break
4575 set olds [lindex $parentlist $row]
4576 set kid [lindex $displayorder $row]
4577 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
4578 if {$kidx < 0} continue
4579 set nextrow [lindex $rowidlist [expr {$row + 1}]]
4580 foreach p $olds {
4581 set px [lsearch -exact $nextrow $p]
4582 if {$px < 0} continue
4583 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
4584 if {[lsearch -exact $ccross $p] >= 0} continue
4585 if {$x == $px + ($kidx < $px? -1: 1)} {
4586 lappend ccross $p
4587 } elseif {[lsearch -exact $cross $p] < 0} {
4588 lappend cross $p
4594 return [concat $ccross {{}} $cross]
4597 proc assigncolor {id} {
4598 global colormap colors nextcolor
4599 global parents children children curview
4601 if {[info exists colormap($id)]} return
4602 set ncolors [llength $colors]
4603 if {[info exists children($curview,$id)]} {
4604 set kids $children($curview,$id)
4605 } else {
4606 set kids {}
4608 if {[llength $kids] == 1} {
4609 set child [lindex $kids 0]
4610 if {[info exists colormap($child)]
4611 && [llength $parents($curview,$child)] == 1} {
4612 set colormap($id) $colormap($child)
4613 return
4616 set badcolors {}
4617 set origbad {}
4618 foreach x [findcrossings $id] {
4619 if {$x eq {}} {
4620 # delimiter between corner crossings and other crossings
4621 if {[llength $badcolors] >= $ncolors - 1} break
4622 set origbad $badcolors
4624 if {[info exists colormap($x)]
4625 && [lsearch -exact $badcolors $colormap($x)] < 0} {
4626 lappend badcolors $colormap($x)
4629 if {[llength $badcolors] >= $ncolors} {
4630 set badcolors $origbad
4632 set origbad $badcolors
4633 if {[llength $badcolors] < $ncolors - 1} {
4634 foreach child $kids {
4635 if {[info exists colormap($child)]
4636 && [lsearch -exact $badcolors $colormap($child)] < 0} {
4637 lappend badcolors $colormap($child)
4639 foreach p $parents($curview,$child) {
4640 if {[info exists colormap($p)]
4641 && [lsearch -exact $badcolors $colormap($p)] < 0} {
4642 lappend badcolors $colormap($p)
4646 if {[llength $badcolors] >= $ncolors} {
4647 set badcolors $origbad
4650 for {set i 0} {$i <= $ncolors} {incr i} {
4651 set c [lindex $colors $nextcolor]
4652 if {[incr nextcolor] >= $ncolors} {
4653 set nextcolor 0
4655 if {[lsearch -exact $badcolors $c]} break
4657 set colormap($id) $c
4660 proc bindline {t id} {
4661 global canv
4663 $canv bind $t <Enter> "lineenter %x %y $id"
4664 $canv bind $t <Motion> "linemotion %x %y $id"
4665 $canv bind $t <Leave> "lineleave $id"
4666 $canv bind $t <Button-1> "lineclick %x %y $id 1"
4669 proc drawtags {id x xt y1} {
4670 global idtags idheads idotherrefs mainhead
4671 global linespc lthickness
4672 global canv rowtextx curview fgcolor bgcolor
4674 set marks {}
4675 set ntags 0
4676 set nheads 0
4677 if {[info exists idtags($id)]} {
4678 set marks $idtags($id)
4679 set ntags [llength $marks]
4681 if {[info exists idheads($id)]} {
4682 set marks [concat $marks $idheads($id)]
4683 set nheads [llength $idheads($id)]
4685 if {[info exists idotherrefs($id)]} {
4686 set marks [concat $marks $idotherrefs($id)]
4688 if {$marks eq {}} {
4689 return $xt
4692 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
4693 set yt [expr {$y1 - 0.5 * $linespc}]
4694 set yb [expr {$yt + $linespc - 1}]
4695 set xvals {}
4696 set wvals {}
4697 set i -1
4698 foreach tag $marks {
4699 incr i
4700 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
4701 set wid [font measure mainfontbold $tag]
4702 } else {
4703 set wid [font measure mainfont $tag]
4705 lappend xvals $xt
4706 lappend wvals $wid
4707 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
4709 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
4710 -width $lthickness -fill black -tags tag.$id]
4711 $canv lower $t
4712 foreach tag $marks x $xvals wid $wvals {
4713 set xl [expr {$x + $delta}]
4714 set xr [expr {$x + $delta + $wid + $lthickness}]
4715 set font mainfont
4716 if {[incr ntags -1] >= 0} {
4717 # draw a tag
4718 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
4719 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
4720 -width 1 -outline black -fill yellow -tags tag.$id]
4721 $canv bind $t <1> [list showtag $tag 1]
4722 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
4723 } else {
4724 # draw a head or other ref
4725 if {[incr nheads -1] >= 0} {
4726 set col green
4727 if {$tag eq $mainhead} {
4728 set font mainfontbold
4730 } else {
4731 set col "#ddddff"
4733 set xl [expr {$xl - $delta/2}]
4734 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
4735 -width 1 -outline black -fill $col -tags tag.$id
4736 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
4737 set rwid [font measure mainfont $remoteprefix]
4738 set xi [expr {$x + 1}]
4739 set yti [expr {$yt + 1}]
4740 set xri [expr {$x + $rwid}]
4741 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
4742 -width 0 -fill "#ffddaa" -tags tag.$id
4745 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
4746 -font $font -tags [list tag.$id text]]
4747 if {$ntags >= 0} {
4748 $canv bind $t <1> [list showtag $tag 1]
4749 } elseif {$nheads >= 0} {
4750 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
4753 return $xt
4756 proc xcoord {i level ln} {
4757 global canvx0 xspc1 xspc2
4759 set x [expr {$canvx0 + $i * $xspc1($ln)}]
4760 if {$i > 0 && $i == $level} {
4761 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
4762 } elseif {$i > $level} {
4763 set x [expr {$x + $xspc2 - $xspc1($ln)}]
4765 return $x
4768 proc show_status {msg} {
4769 global canv fgcolor
4771 clear_display
4772 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
4773 -tags text -fill $fgcolor
4776 # Don't change the text pane cursor if it is currently the hand cursor,
4777 # showing that we are over a sha1 ID link.
4778 proc settextcursor {c} {
4779 global ctext curtextcursor
4781 if {[$ctext cget -cursor] == $curtextcursor} {
4782 $ctext config -cursor $c
4784 set curtextcursor $c
4787 proc nowbusy {what {name {}}} {
4788 global isbusy busyname statusw
4790 if {[array names isbusy] eq {}} {
4791 . config -cursor watch
4792 settextcursor watch
4794 set isbusy($what) 1
4795 set busyname($what) $name
4796 if {$name ne {}} {
4797 $statusw conf -text $name
4801 proc notbusy {what} {
4802 global isbusy maincursor textcursor busyname statusw
4804 catch {
4805 unset isbusy($what)
4806 if {$busyname($what) ne {} &&
4807 [$statusw cget -text] eq $busyname($what)} {
4808 $statusw conf -text {}
4811 if {[array names isbusy] eq {}} {
4812 . config -cursor $maincursor
4813 settextcursor $textcursor
4817 proc findmatches {f} {
4818 global findtype findstring
4819 if {$findtype == "Regexp"} {
4820 set matches [regexp -indices -all -inline $findstring $f]
4821 } else {
4822 set fs $findstring
4823 if {$findtype == "IgnCase"} {
4824 set f [string tolower $f]
4825 set fs [string tolower $fs]
4827 set matches {}
4828 set i 0
4829 set l [string length $fs]
4830 while {[set j [string first $fs $f $i]] >= 0} {
4831 lappend matches [list $j [expr {$j+$l-1}]]
4832 set i [expr {$j + $l}]
4835 return $matches
4838 proc dofind {{dirn 1} {wrap 1}} {
4839 global findstring findstartline findcurline selectedline numcommits
4840 global gdttype filehighlight fh_serial find_dirn findallowwrap
4842 if {[info exists find_dirn]} {
4843 if {$find_dirn == $dirn} return
4844 stopfinding
4846 focus .
4847 if {$findstring eq {} || $numcommits == 0} return
4848 if {![info exists selectedline]} {
4849 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
4850 } else {
4851 set findstartline $selectedline
4853 set findcurline $findstartline
4854 nowbusy finding "Searching"
4855 if {$gdttype ne "containing:" && ![info exists filehighlight]} {
4856 after cancel do_file_hl $fh_serial
4857 do_file_hl $fh_serial
4859 set find_dirn $dirn
4860 set findallowwrap $wrap
4861 run findmore
4864 proc stopfinding {} {
4865 global find_dirn findcurline fprogcoord
4867 if {[info exists find_dirn]} {
4868 unset find_dirn
4869 unset findcurline
4870 notbusy finding
4871 set fprogcoord 0
4872 adjustprogress
4876 proc findmore {} {
4877 global commitdata commitinfo numcommits findpattern findloc
4878 global findstartline findcurline findallowwrap
4879 global find_dirn gdttype fhighlights fprogcoord
4880 global curview varcorder vrownum varccommits
4882 if {![info exists find_dirn]} {
4883 return 0
4885 set fldtypes {Headline Author Date Committer CDate Comments}
4886 set l $findcurline
4887 set moretodo 0
4888 if {$find_dirn > 0} {
4889 incr l
4890 if {$l >= $numcommits} {
4891 set l 0
4893 if {$l <= $findstartline} {
4894 set lim [expr {$findstartline + 1}]
4895 } else {
4896 set lim $numcommits
4897 set moretodo $findallowwrap
4899 } else {
4900 if {$l == 0} {
4901 set l $numcommits
4903 incr l -1
4904 if {$l >= $findstartline} {
4905 set lim [expr {$findstartline - 1}]
4906 } else {
4907 set lim -1
4908 set moretodo $findallowwrap
4911 set n [expr {($lim - $l) * $find_dirn}]
4912 if {$n > 500} {
4913 set n 500
4914 set moretodo 1
4916 set found 0
4917 set domore 1
4918 set ai [bsearch $vrownum($curview) $l]
4919 set a [lindex $varcorder($curview) $ai]
4920 set arow [lindex $vrownum($curview) $ai]
4921 set ids [lindex $varccommits($curview,$a)]
4922 set arowend [expr {$arow + [llength $ids]}]
4923 if {$gdttype eq "containing:"} {
4924 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4925 if {$l < $arow || $l >= $arowend} {
4926 incr ai $find_dirn
4927 set a [lindex $varcorder($curview) $ai]
4928 set arow [lindex $vrownum($curview) $ai]
4929 set ids [lindex $varccommits($curview,$a)]
4930 set arowend [expr {$arow + [llength $ids]}]
4932 set id [lindex $ids [expr {$l - $arow}]]
4933 # shouldn't happen unless git log doesn't give all the commits...
4934 if {![info exists commitdata($id)] ||
4935 ![doesmatch $commitdata($id)]} {
4936 continue
4938 if {![info exists commitinfo($id)]} {
4939 getcommit $id
4941 set info $commitinfo($id)
4942 foreach f $info ty $fldtypes {
4943 if {($findloc eq "All fields" || $findloc eq $ty) &&
4944 [doesmatch $f]} {
4945 set found 1
4946 break
4949 if {$found} break
4951 } else {
4952 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4953 if {$l < $arow || $l >= $arowend} {
4954 incr ai $find_dirn
4955 set a [lindex $varcorder($curview) $ai]
4956 set arow [lindex $vrownum($curview) $ai]
4957 set ids [lindex $varccommits($curview,$a)]
4958 set arowend [expr {$arow + [llength $ids]}]
4960 set id [lindex $ids [expr {$l - $arow}]]
4961 if {![info exists fhighlights($l)]} {
4962 askfilehighlight $l $id
4963 if {$domore} {
4964 set domore 0
4965 set findcurline [expr {$l - $find_dirn}]
4967 } elseif {$fhighlights($l)} {
4968 set found $domore
4969 break
4973 if {$found || ($domore && !$moretodo)} {
4974 unset findcurline
4975 unset find_dirn
4976 notbusy finding
4977 set fprogcoord 0
4978 adjustprogress
4979 if {$found} {
4980 findselectline $l
4981 } else {
4982 bell
4984 return 0
4986 if {!$domore} {
4987 flushhighlights
4988 } else {
4989 set findcurline [expr {$l - $find_dirn}]
4991 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
4992 if {$n < 0} {
4993 incr n $numcommits
4995 set fprogcoord [expr {$n * 1.0 / $numcommits}]
4996 adjustprogress
4997 return $domore
5000 proc findselectline {l} {
5001 global findloc commentend ctext findcurline markingmatches gdttype
5003 set markingmatches 1
5004 set findcurline $l
5005 selectline $l 1
5006 if {$findloc == "All fields" || $findloc == "Comments"} {
5007 # highlight the matches in the comments
5008 set f [$ctext get 1.0 $commentend]
5009 set matches [findmatches $f]
5010 foreach match $matches {
5011 set start [lindex $match 0]
5012 set end [expr {[lindex $match 1] + 1}]
5013 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5016 drawvisible
5019 # mark the bits of a headline or author that match a find string
5020 proc markmatches {canv l str tag matches font row} {
5021 global selectedline
5023 set bbox [$canv bbox $tag]
5024 set x0 [lindex $bbox 0]
5025 set y0 [lindex $bbox 1]
5026 set y1 [lindex $bbox 3]
5027 foreach match $matches {
5028 set start [lindex $match 0]
5029 set end [lindex $match 1]
5030 if {$start > $end} continue
5031 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5032 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5033 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5034 [expr {$x0+$xlen+2}] $y1 \
5035 -outline {} -tags [list match$l matches] -fill yellow]
5036 $canv lower $t
5037 if {[info exists selectedline] && $row == $selectedline} {
5038 $canv raise $t secsel
5043 proc unmarkmatches {} {
5044 global markingmatches
5046 allcanvs delete matches
5047 set markingmatches 0
5048 stopfinding
5051 proc selcanvline {w x y} {
5052 global canv canvy0 ctext linespc
5053 global rowtextx
5054 set ymax [lindex [$canv cget -scrollregion] 3]
5055 if {$ymax == {}} return
5056 set yfrac [lindex [$canv yview] 0]
5057 set y [expr {$y + $yfrac * $ymax}]
5058 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5059 if {$l < 0} {
5060 set l 0
5062 if {$w eq $canv} {
5063 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
5065 unmarkmatches
5066 selectline $l 1
5069 proc commit_descriptor {p} {
5070 global commitinfo
5071 if {![info exists commitinfo($p)]} {
5072 getcommit $p
5074 set l "..."
5075 if {[llength $commitinfo($p)] > 1} {
5076 set l [lindex $commitinfo($p) 0]
5078 return "$p ($l)\n"
5081 # append some text to the ctext widget, and make any SHA1 ID
5082 # that we know about be a clickable link.
5083 proc appendwithlinks {text tags} {
5084 global ctext linknum curview pendinglinks
5086 set start [$ctext index "end - 1c"]
5087 $ctext insert end $text $tags
5088 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5089 foreach l $links {
5090 set s [lindex $l 0]
5091 set e [lindex $l 1]
5092 set linkid [string range $text $s $e]
5093 incr e
5094 $ctext tag delete link$linknum
5095 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5096 setlink $linkid link$linknum
5097 incr linknum
5101 proc setlink {id lk} {
5102 global curview ctext pendinglinks commitinterest
5104 if {[commitinview $id $curview]} {
5105 $ctext tag conf $lk -foreground blue -underline 1
5106 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5107 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5108 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5109 } else {
5110 lappend pendinglinks($id) $lk
5111 lappend commitinterest($id) {makelink %I}
5115 proc makelink {id} {
5116 global pendinglinks
5118 if {![info exists pendinglinks($id)]} return
5119 foreach lk $pendinglinks($id) {
5120 setlink $id $lk
5122 unset pendinglinks($id)
5125 proc linkcursor {w inc} {
5126 global linkentercount curtextcursor
5128 if {[incr linkentercount $inc] > 0} {
5129 $w configure -cursor hand2
5130 } else {
5131 $w configure -cursor $curtextcursor
5132 if {$linkentercount < 0} {
5133 set linkentercount 0
5138 proc viewnextline {dir} {
5139 global canv linespc
5141 $canv delete hover
5142 set ymax [lindex [$canv cget -scrollregion] 3]
5143 set wnow [$canv yview]
5144 set wtop [expr {[lindex $wnow 0] * $ymax}]
5145 set newtop [expr {$wtop + $dir * $linespc}]
5146 if {$newtop < 0} {
5147 set newtop 0
5148 } elseif {$newtop > $ymax} {
5149 set newtop $ymax
5151 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5154 # add a list of tag or branch names at position pos
5155 # returns the number of names inserted
5156 proc appendrefs {pos ids var} {
5157 global ctext linknum curview $var maxrefs
5159 if {[catch {$ctext index $pos}]} {
5160 return 0
5162 $ctext conf -state normal
5163 $ctext delete $pos "$pos lineend"
5164 set tags {}
5165 foreach id $ids {
5166 foreach tag [set $var\($id\)] {
5167 lappend tags [list $tag $id]
5170 if {[llength $tags] > $maxrefs} {
5171 $ctext insert $pos "many ([llength $tags])"
5172 } else {
5173 set tags [lsort -index 0 -decreasing $tags]
5174 set sep {}
5175 foreach ti $tags {
5176 set id [lindex $ti 1]
5177 set lk link$linknum
5178 incr linknum
5179 $ctext tag delete $lk
5180 $ctext insert $pos $sep
5181 $ctext insert $pos [lindex $ti 0] $lk
5182 setlink $id $lk
5183 set sep ", "
5186 $ctext conf -state disabled
5187 return [llength $tags]
5190 # called when we have finished computing the nearby tags
5191 proc dispneartags {delay} {
5192 global selectedline currentid showneartags tagphase
5194 if {![info exists selectedline] || !$showneartags} return
5195 after cancel dispnexttag
5196 if {$delay} {
5197 after 200 dispnexttag
5198 set tagphase -1
5199 } else {
5200 after idle dispnexttag
5201 set tagphase 0
5205 proc dispnexttag {} {
5206 global selectedline currentid showneartags tagphase ctext
5208 if {![info exists selectedline] || !$showneartags} return
5209 switch -- $tagphase {
5211 set dtags [desctags $currentid]
5212 if {$dtags ne {}} {
5213 appendrefs precedes $dtags idtags
5217 set atags [anctags $currentid]
5218 if {$atags ne {}} {
5219 appendrefs follows $atags idtags
5223 set dheads [descheads $currentid]
5224 if {$dheads ne {}} {
5225 if {[appendrefs branch $dheads idheads] > 1
5226 && [$ctext get "branch -3c"] eq "h"} {
5227 # turn "Branch" into "Branches"
5228 $ctext conf -state normal
5229 $ctext insert "branch -2c" "es"
5230 $ctext conf -state disabled
5235 if {[incr tagphase] <= 2} {
5236 after idle dispnexttag
5240 proc make_secsel {l} {
5241 global linehtag linentag linedtag canv canv2 canv3
5243 if {![info exists linehtag($l)]} return
5244 $canv delete secsel
5245 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5246 -tags secsel -fill [$canv cget -selectbackground]]
5247 $canv lower $t
5248 $canv2 delete secsel
5249 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5250 -tags secsel -fill [$canv2 cget -selectbackground]]
5251 $canv2 lower $t
5252 $canv3 delete secsel
5253 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5254 -tags secsel -fill [$canv3 cget -selectbackground]]
5255 $canv3 lower $t
5258 proc selectline {l isnew} {
5259 global canv ctext commitinfo selectedline
5260 global canvy0 linespc parents children curview
5261 global currentid sha1entry
5262 global commentend idtags linknum
5263 global mergemax numcommits pending_select
5264 global cmitmode showneartags allcommits
5266 catch {unset pending_select}
5267 $canv delete hover
5268 normalline
5269 unsel_reflist
5270 stopfinding
5271 if {$l < 0 || $l >= $numcommits} return
5272 set y [expr {$canvy0 + $l * $linespc}]
5273 set ymax [lindex [$canv cget -scrollregion] 3]
5274 set ytop [expr {$y - $linespc - 1}]
5275 set ybot [expr {$y + $linespc + 1}]
5276 set wnow [$canv yview]
5277 set wtop [expr {[lindex $wnow 0] * $ymax}]
5278 set wbot [expr {[lindex $wnow 1] * $ymax}]
5279 set wh [expr {$wbot - $wtop}]
5280 set newtop $wtop
5281 if {$ytop < $wtop} {
5282 if {$ybot < $wtop} {
5283 set newtop [expr {$y - $wh / 2.0}]
5284 } else {
5285 set newtop $ytop
5286 if {$newtop > $wtop - $linespc} {
5287 set newtop [expr {$wtop - $linespc}]
5290 } elseif {$ybot > $wbot} {
5291 if {$ytop > $wbot} {
5292 set newtop [expr {$y - $wh / 2.0}]
5293 } else {
5294 set newtop [expr {$ybot - $wh}]
5295 if {$newtop < $wtop + $linespc} {
5296 set newtop [expr {$wtop + $linespc}]
5300 if {$newtop != $wtop} {
5301 if {$newtop < 0} {
5302 set newtop 0
5304 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5305 drawvisible
5308 make_secsel $l
5310 if {$isnew} {
5311 addtohistory [list selectline $l 0]
5314 set selectedline $l
5316 set id [commitonrow $l]
5317 set currentid $id
5318 $sha1entry delete 0 end
5319 $sha1entry insert 0 $id
5320 $sha1entry selection from 0
5321 $sha1entry selection to end
5322 rhighlight_sel $id
5324 $ctext conf -state normal
5325 clear_ctext
5326 set linknum 0
5327 set info $commitinfo($id)
5328 set date [formatdate [lindex $info 2]]
5329 $ctext insert end "Author: [lindex $info 1] $date\n"
5330 set date [formatdate [lindex $info 4]]
5331 $ctext insert end "Committer: [lindex $info 3] $date\n"
5332 if {[info exists idtags($id)]} {
5333 $ctext insert end "Tags:"
5334 foreach tag $idtags($id) {
5335 $ctext insert end " $tag"
5337 $ctext insert end "\n"
5340 set headers {}
5341 set olds $parents($curview,$id)
5342 if {[llength $olds] > 1} {
5343 set np 0
5344 foreach p $olds {
5345 if {$np >= $mergemax} {
5346 set tag mmax
5347 } else {
5348 set tag m$np
5350 $ctext insert end "Parent: " $tag
5351 appendwithlinks [commit_descriptor $p] {}
5352 incr np
5354 } else {
5355 foreach p $olds {
5356 append headers "Parent: [commit_descriptor $p]"
5360 foreach c $children($curview,$id) {
5361 append headers "Child: [commit_descriptor $c]"
5364 # make anything that looks like a SHA1 ID be a clickable link
5365 appendwithlinks $headers {}
5366 if {$showneartags} {
5367 if {![info exists allcommits]} {
5368 getallcommits
5370 $ctext insert end "Branch: "
5371 $ctext mark set branch "end -1c"
5372 $ctext mark gravity branch left
5373 $ctext insert end "\nFollows: "
5374 $ctext mark set follows "end -1c"
5375 $ctext mark gravity follows left
5376 $ctext insert end "\nPrecedes: "
5377 $ctext mark set precedes "end -1c"
5378 $ctext mark gravity precedes left
5379 $ctext insert end "\n"
5380 dispneartags 1
5382 $ctext insert end "\n"
5383 set comment [lindex $info 5]
5384 if {[string first "\r" $comment] >= 0} {
5385 set comment [string map {"\r" "\n "} $comment]
5387 appendwithlinks $comment {comment}
5389 $ctext tag remove found 1.0 end
5390 $ctext conf -state disabled
5391 set commentend [$ctext index "end - 1c"]
5393 init_flist "Comments"
5394 if {$cmitmode eq "tree"} {
5395 gettree $id
5396 } elseif {[llength $olds] <= 1} {
5397 startdiff $id
5398 } else {
5399 mergediff $id
5403 proc selfirstline {} {
5404 unmarkmatches
5405 selectline 0 1
5408 proc sellastline {} {
5409 global numcommits
5410 unmarkmatches
5411 set l [expr {$numcommits - 1}]
5412 selectline $l 1
5415 proc selnextline {dir} {
5416 global selectedline
5417 focus .
5418 if {![info exists selectedline]} return
5419 set l [expr {$selectedline + $dir}]
5420 unmarkmatches
5421 selectline $l 1
5424 proc selnextpage {dir} {
5425 global canv linespc selectedline numcommits
5427 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
5428 if {$lpp < 1} {
5429 set lpp 1
5431 allcanvs yview scroll [expr {$dir * $lpp}] units
5432 drawvisible
5433 if {![info exists selectedline]} return
5434 set l [expr {$selectedline + $dir * $lpp}]
5435 if {$l < 0} {
5436 set l 0
5437 } elseif {$l >= $numcommits} {
5438 set l [expr $numcommits - 1]
5440 unmarkmatches
5441 selectline $l 1
5444 proc unselectline {} {
5445 global selectedline currentid
5447 catch {unset selectedline}
5448 catch {unset currentid}
5449 allcanvs delete secsel
5450 rhighlight_none
5453 proc reselectline {} {
5454 global selectedline
5456 if {[info exists selectedline]} {
5457 selectline $selectedline 0
5461 proc addtohistory {cmd} {
5462 global history historyindex curview
5464 set elt [list $curview $cmd]
5465 if {$historyindex > 0
5466 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
5467 return
5470 if {$historyindex < [llength $history]} {
5471 set history [lreplace $history $historyindex end $elt]
5472 } else {
5473 lappend history $elt
5475 incr historyindex
5476 if {$historyindex > 1} {
5477 .tf.bar.leftbut conf -state normal
5478 } else {
5479 .tf.bar.leftbut conf -state disabled
5481 .tf.bar.rightbut conf -state disabled
5484 proc godo {elt} {
5485 global curview
5487 set view [lindex $elt 0]
5488 set cmd [lindex $elt 1]
5489 if {$curview != $view} {
5490 showview $view
5492 eval $cmd
5495 proc goback {} {
5496 global history historyindex
5497 focus .
5499 if {$historyindex > 1} {
5500 incr historyindex -1
5501 godo [lindex $history [expr {$historyindex - 1}]]
5502 .tf.bar.rightbut conf -state normal
5504 if {$historyindex <= 1} {
5505 .tf.bar.leftbut conf -state disabled
5509 proc goforw {} {
5510 global history historyindex
5511 focus .
5513 if {$historyindex < [llength $history]} {
5514 set cmd [lindex $history $historyindex]
5515 incr historyindex
5516 godo $cmd
5517 .tf.bar.leftbut conf -state normal
5519 if {$historyindex >= [llength $history]} {
5520 .tf.bar.rightbut conf -state disabled
5524 proc gettree {id} {
5525 global treefilelist treeidlist diffids diffmergeid treepending
5526 global nullid nullid2
5528 set diffids $id
5529 catch {unset diffmergeid}
5530 if {![info exists treefilelist($id)]} {
5531 if {![info exists treepending]} {
5532 if {$id eq $nullid} {
5533 set cmd [list | git ls-files]
5534 } elseif {$id eq $nullid2} {
5535 set cmd [list | git ls-files --stage -t]
5536 } else {
5537 set cmd [list | git ls-tree -r $id]
5539 if {[catch {set gtf [open $cmd r]}]} {
5540 return
5542 set treepending $id
5543 set treefilelist($id) {}
5544 set treeidlist($id) {}
5545 fconfigure $gtf -blocking 0
5546 filerun $gtf [list gettreeline $gtf $id]
5548 } else {
5549 setfilelist $id
5553 proc gettreeline {gtf id} {
5554 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
5556 set nl 0
5557 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
5558 if {$diffids eq $nullid} {
5559 set fname $line
5560 } else {
5561 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
5562 set i [string first "\t" $line]
5563 if {$i < 0} continue
5564 set sha1 [lindex $line 2]
5565 set fname [string range $line [expr {$i+1}] end]
5566 if {[string index $fname 0] eq "\""} {
5567 set fname [lindex $fname 0]
5569 lappend treeidlist($id) $sha1
5571 lappend treefilelist($id) $fname
5573 if {![eof $gtf]} {
5574 return [expr {$nl >= 1000? 2: 1}]
5576 close $gtf
5577 unset treepending
5578 if {$cmitmode ne "tree"} {
5579 if {![info exists diffmergeid]} {
5580 gettreediffs $diffids
5582 } elseif {$id ne $diffids} {
5583 gettree $diffids
5584 } else {
5585 setfilelist $id
5587 return 0
5590 proc showfile {f} {
5591 global treefilelist treeidlist diffids nullid nullid2
5592 global ctext commentend
5594 set i [lsearch -exact $treefilelist($diffids) $f]
5595 if {$i < 0} {
5596 puts "oops, $f not in list for id $diffids"
5597 return
5599 if {$diffids eq $nullid} {
5600 if {[catch {set bf [open $f r]} err]} {
5601 puts "oops, can't read $f: $err"
5602 return
5604 } else {
5605 set blob [lindex $treeidlist($diffids) $i]
5606 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
5607 puts "oops, error reading blob $blob: $err"
5608 return
5611 fconfigure $bf -blocking 0
5612 filerun $bf [list getblobline $bf $diffids]
5613 $ctext config -state normal
5614 clear_ctext $commentend
5615 $ctext insert end "\n"
5616 $ctext insert end "$f\n" filesep
5617 $ctext config -state disabled
5618 $ctext yview $commentend
5619 settabs 0
5622 proc getblobline {bf id} {
5623 global diffids cmitmode ctext
5625 if {$id ne $diffids || $cmitmode ne "tree"} {
5626 catch {close $bf}
5627 return 0
5629 $ctext config -state normal
5630 set nl 0
5631 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
5632 $ctext insert end "$line\n"
5634 if {[eof $bf]} {
5635 # delete last newline
5636 $ctext delete "end - 2c" "end - 1c"
5637 close $bf
5638 return 0
5640 $ctext config -state disabled
5641 return [expr {$nl >= 1000? 2: 1}]
5644 proc mergediff {id} {
5645 global diffmergeid mdifffd
5646 global diffids
5647 global parents
5648 global limitdiffs viewfiles curview
5650 set diffmergeid $id
5651 set diffids $id
5652 # this doesn't seem to actually affect anything...
5653 set cmd [concat | git diff-tree --no-commit-id --cc $id]
5654 if {$limitdiffs && $viewfiles($curview) ne {}} {
5655 set cmd [concat $cmd -- $viewfiles($curview)]
5657 if {[catch {set mdf [open $cmd r]} err]} {
5658 error_popup "Error getting merge diffs: $err"
5659 return
5661 fconfigure $mdf -blocking 0
5662 set mdifffd($id) $mdf
5663 set np [llength $parents($curview,$id)]
5664 settabs $np
5665 filerun $mdf [list getmergediffline $mdf $id $np]
5668 proc getmergediffline {mdf id np} {
5669 global diffmergeid ctext cflist mergemax
5670 global difffilestart mdifffd
5672 $ctext conf -state normal
5673 set nr 0
5674 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
5675 if {![info exists diffmergeid] || $id != $diffmergeid
5676 || $mdf != $mdifffd($id)} {
5677 close $mdf
5678 return 0
5680 if {[regexp {^diff --cc (.*)} $line match fname]} {
5681 # start of a new file
5682 $ctext insert end "\n"
5683 set here [$ctext index "end - 1c"]
5684 lappend difffilestart $here
5685 add_flist [list $fname]
5686 set l [expr {(78 - [string length $fname]) / 2}]
5687 set pad [string range "----------------------------------------" 1 $l]
5688 $ctext insert end "$pad $fname $pad\n" filesep
5689 } elseif {[regexp {^@@} $line]} {
5690 $ctext insert end "$line\n" hunksep
5691 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
5692 # do nothing
5693 } else {
5694 # parse the prefix - one ' ', '-' or '+' for each parent
5695 set spaces {}
5696 set minuses {}
5697 set pluses {}
5698 set isbad 0
5699 for {set j 0} {$j < $np} {incr j} {
5700 set c [string range $line $j $j]
5701 if {$c == " "} {
5702 lappend spaces $j
5703 } elseif {$c == "-"} {
5704 lappend minuses $j
5705 } elseif {$c == "+"} {
5706 lappend pluses $j
5707 } else {
5708 set isbad 1
5709 break
5712 set tags {}
5713 set num {}
5714 if {!$isbad && $minuses ne {} && $pluses eq {}} {
5715 # line doesn't appear in result, parents in $minuses have the line
5716 set num [lindex $minuses 0]
5717 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
5718 # line appears in result, parents in $pluses don't have the line
5719 lappend tags mresult
5720 set num [lindex $spaces 0]
5722 if {$num ne {}} {
5723 if {$num >= $mergemax} {
5724 set num "max"
5726 lappend tags m$num
5728 $ctext insert end "$line\n" $tags
5731 $ctext conf -state disabled
5732 if {[eof $mdf]} {
5733 close $mdf
5734 return 0
5736 return [expr {$nr >= 1000? 2: 1}]
5739 proc startdiff {ids} {
5740 global treediffs diffids treepending diffmergeid nullid nullid2
5742 settabs 1
5743 set diffids $ids
5744 catch {unset diffmergeid}
5745 if {![info exists treediffs($ids)] ||
5746 [lsearch -exact $ids $nullid] >= 0 ||
5747 [lsearch -exact $ids $nullid2] >= 0} {
5748 if {![info exists treepending]} {
5749 gettreediffs $ids
5751 } else {
5752 addtocflist $ids
5756 proc path_filter {filter name} {
5757 foreach p $filter {
5758 set l [string length $p]
5759 if {[string index $p end] eq "/"} {
5760 if {[string compare -length $l $p $name] == 0} {
5761 return 1
5763 } else {
5764 if {[string compare -length $l $p $name] == 0 &&
5765 ([string length $name] == $l ||
5766 [string index $name $l] eq "/")} {
5767 return 1
5771 return 0
5774 proc addtocflist {ids} {
5775 global treediffs
5777 add_flist $treediffs($ids)
5778 getblobdiffs $ids
5781 proc diffcmd {ids flags} {
5782 global nullid nullid2
5784 set i [lsearch -exact $ids $nullid]
5785 set j [lsearch -exact $ids $nullid2]
5786 if {$i >= 0} {
5787 if {[llength $ids] > 1 && $j < 0} {
5788 # comparing working directory with some specific revision
5789 set cmd [concat | git diff-index $flags]
5790 if {$i == 0} {
5791 lappend cmd -R [lindex $ids 1]
5792 } else {
5793 lappend cmd [lindex $ids 0]
5795 } else {
5796 # comparing working directory with index
5797 set cmd [concat | git diff-files $flags]
5798 if {$j == 1} {
5799 lappend cmd -R
5802 } elseif {$j >= 0} {
5803 set cmd [concat | git diff-index --cached $flags]
5804 if {[llength $ids] > 1} {
5805 # comparing index with specific revision
5806 if {$i == 0} {
5807 lappend cmd -R [lindex $ids 1]
5808 } else {
5809 lappend cmd [lindex $ids 0]
5811 } else {
5812 # comparing index with HEAD
5813 lappend cmd HEAD
5815 } else {
5816 set cmd [concat | git diff-tree -r $flags $ids]
5818 return $cmd
5821 proc gettreediffs {ids} {
5822 global treediff treepending
5824 set treepending $ids
5825 set treediff {}
5826 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5827 fconfigure $gdtf -blocking 0
5828 filerun $gdtf [list gettreediffline $gdtf $ids]
5831 proc gettreediffline {gdtf ids} {
5832 global treediff treediffs treepending diffids diffmergeid
5833 global cmitmode viewfiles curview limitdiffs
5835 set nr 0
5836 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5837 set i [string first "\t" $line]
5838 if {$i >= 0} {
5839 set file [string range $line [expr {$i+1}] end]
5840 if {[string index $file 0] eq "\""} {
5841 set file [lindex $file 0]
5843 lappend treediff $file
5846 if {![eof $gdtf]} {
5847 return [expr {$nr >= 1000? 2: 1}]
5849 close $gdtf
5850 if {$limitdiffs && $viewfiles($curview) ne {}} {
5851 set flist {}
5852 foreach f $treediff {
5853 if {[path_filter $viewfiles($curview) $f]} {
5854 lappend flist $f
5857 set treediffs($ids) $flist
5858 } else {
5859 set treediffs($ids) $treediff
5861 unset treepending
5862 if {$cmitmode eq "tree"} {
5863 gettree $diffids
5864 } elseif {$ids != $diffids} {
5865 if {![info exists diffmergeid]} {
5866 gettreediffs $diffids
5868 } else {
5869 addtocflist $ids
5871 return 0
5874 # empty string or positive integer
5875 proc diffcontextvalidate {v} {
5876 return [regexp {^(|[1-9][0-9]*)$} $v]
5879 proc diffcontextchange {n1 n2 op} {
5880 global diffcontextstring diffcontext
5882 if {[string is integer -strict $diffcontextstring]} {
5883 if {$diffcontextstring > 0} {
5884 set diffcontext $diffcontextstring
5885 reselectline
5890 proc getblobdiffs {ids} {
5891 global blobdifffd diffids env
5892 global diffinhdr treediffs
5893 global diffcontext
5894 global limitdiffs viewfiles curview
5896 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
5897 if {$limitdiffs && $viewfiles($curview) ne {}} {
5898 set cmd [concat $cmd -- $viewfiles($curview)]
5900 if {[catch {set bdf [open $cmd r]} err]} {
5901 puts "error getting diffs: $err"
5902 return
5904 set diffinhdr 0
5905 fconfigure $bdf -blocking 0
5906 set blobdifffd($ids) $bdf
5907 filerun $bdf [list getblobdiffline $bdf $diffids]
5910 proc setinlist {var i val} {
5911 global $var
5913 while {[llength [set $var]] < $i} {
5914 lappend $var {}
5916 if {[llength [set $var]] == $i} {
5917 lappend $var $val
5918 } else {
5919 lset $var $i $val
5923 proc makediffhdr {fname ids} {
5924 global ctext curdiffstart treediffs
5926 set i [lsearch -exact $treediffs($ids) $fname]
5927 if {$i >= 0} {
5928 setinlist difffilestart $i $curdiffstart
5930 set l [expr {(78 - [string length $fname]) / 2}]
5931 set pad [string range "----------------------------------------" 1 $l]
5932 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5935 proc getblobdiffline {bdf ids} {
5936 global diffids blobdifffd ctext curdiffstart
5937 global diffnexthead diffnextnote difffilestart
5938 global diffinhdr treediffs
5940 set nr 0
5941 $ctext conf -state normal
5942 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5943 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5944 close $bdf
5945 return 0
5947 if {![string compare -length 11 "diff --git " $line]} {
5948 # trim off "diff --git "
5949 set line [string range $line 11 end]
5950 set diffinhdr 1
5951 # start of a new file
5952 $ctext insert end "\n"
5953 set curdiffstart [$ctext index "end - 1c"]
5954 $ctext insert end "\n" filesep
5955 # If the name hasn't changed the length will be odd,
5956 # the middle char will be a space, and the two bits either
5957 # side will be a/name and b/name, or "a/name" and "b/name".
5958 # If the name has changed we'll get "rename from" and
5959 # "rename to" or "copy from" and "copy to" lines following this,
5960 # and we'll use them to get the filenames.
5961 # This complexity is necessary because spaces in the filename(s)
5962 # don't get escaped.
5963 set l [string length $line]
5964 set i [expr {$l / 2}]
5965 if {!(($l & 1) && [string index $line $i] eq " " &&
5966 [string range $line 2 [expr {$i - 1}]] eq \
5967 [string range $line [expr {$i + 3}] end])} {
5968 continue
5970 # unescape if quoted and chop off the a/ from the front
5971 if {[string index $line 0] eq "\""} {
5972 set fname [string range [lindex $line 0] 2 end]
5973 } else {
5974 set fname [string range $line 2 [expr {$i - 1}]]
5976 makediffhdr $fname $ids
5978 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5979 $line match f1l f1c f2l f2c rest]} {
5980 $ctext insert end "$line\n" hunksep
5981 set diffinhdr 0
5983 } elseif {$diffinhdr} {
5984 if {![string compare -length 12 "rename from " $line]} {
5985 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
5986 if {[string index $fname 0] eq "\""} {
5987 set fname [lindex $fname 0]
5989 set i [lsearch -exact $treediffs($ids) $fname]
5990 if {$i >= 0} {
5991 setinlist difffilestart $i $curdiffstart
5993 } elseif {![string compare -length 10 $line "rename to "] ||
5994 ![string compare -length 8 $line "copy to "]} {
5995 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
5996 if {[string index $fname 0] eq "\""} {
5997 set fname [lindex $fname 0]
5999 makediffhdr $fname $ids
6000 } elseif {[string compare -length 3 $line "---"] == 0} {
6001 # do nothing
6002 continue
6003 } elseif {[string compare -length 3 $line "+++"] == 0} {
6004 set diffinhdr 0
6005 continue
6007 $ctext insert end "$line\n" filesep
6009 } else {
6010 set x [string range $line 0 0]
6011 if {$x == "-" || $x == "+"} {
6012 set tag [expr {$x == "+"}]
6013 $ctext insert end "$line\n" d$tag
6014 } elseif {$x == " "} {
6015 $ctext insert end "$line\n"
6016 } else {
6017 # "\ No newline at end of file",
6018 # or something else we don't recognize
6019 $ctext insert end "$line\n" hunksep
6023 $ctext conf -state disabled
6024 if {[eof $bdf]} {
6025 close $bdf
6026 return 0
6028 return [expr {$nr >= 1000? 2: 1}]
6031 proc changediffdisp {} {
6032 global ctext diffelide
6034 $ctext tag conf d0 -elide [lindex $diffelide 0]
6035 $ctext tag conf d1 -elide [lindex $diffelide 1]
6038 proc prevfile {} {
6039 global difffilestart ctext
6040 set prev [lindex $difffilestart 0]
6041 set here [$ctext index @0,0]
6042 foreach loc $difffilestart {
6043 if {[$ctext compare $loc >= $here]} {
6044 $ctext yview $prev
6045 return
6047 set prev $loc
6049 $ctext yview $prev
6052 proc nextfile {} {
6053 global difffilestart ctext
6054 set here [$ctext index @0,0]
6055 foreach loc $difffilestart {
6056 if {[$ctext compare $loc > $here]} {
6057 $ctext yview $loc
6058 return
6063 proc clear_ctext {{first 1.0}} {
6064 global ctext smarktop smarkbot
6065 global pendinglinks
6067 set l [lindex [split $first .] 0]
6068 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6069 set smarktop $l
6071 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6072 set smarkbot $l
6074 $ctext delete $first end
6075 if {$first eq "1.0"} {
6076 catch {unset pendinglinks}
6080 proc settabs {{firstab {}}} {
6081 global firsttabstop tabstop ctext have_tk85
6083 if {$firstab ne {} && $have_tk85} {
6084 set firsttabstop $firstab
6086 set w [font measure textfont "0"]
6087 if {$firsttabstop != 0} {
6088 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6089 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6090 } elseif {$have_tk85 || $tabstop != 8} {
6091 $ctext conf -tabs [expr {$tabstop * $w}]
6092 } else {
6093 $ctext conf -tabs {}
6097 proc incrsearch {name ix op} {
6098 global ctext searchstring searchdirn
6100 $ctext tag remove found 1.0 end
6101 if {[catch {$ctext index anchor}]} {
6102 # no anchor set, use start of selection, or of visible area
6103 set sel [$ctext tag ranges sel]
6104 if {$sel ne {}} {
6105 $ctext mark set anchor [lindex $sel 0]
6106 } elseif {$searchdirn eq "-forwards"} {
6107 $ctext mark set anchor @0,0
6108 } else {
6109 $ctext mark set anchor @0,[winfo height $ctext]
6112 if {$searchstring ne {}} {
6113 set here [$ctext search $searchdirn -- $searchstring anchor]
6114 if {$here ne {}} {
6115 $ctext see $here
6117 searchmarkvisible 1
6121 proc dosearch {} {
6122 global sstring ctext searchstring searchdirn
6124 focus $sstring
6125 $sstring icursor end
6126 set searchdirn -forwards
6127 if {$searchstring ne {}} {
6128 set sel [$ctext tag ranges sel]
6129 if {$sel ne {}} {
6130 set start "[lindex $sel 0] + 1c"
6131 } elseif {[catch {set start [$ctext index anchor]}]} {
6132 set start "@0,0"
6134 set match [$ctext search -count mlen -- $searchstring $start]
6135 $ctext tag remove sel 1.0 end
6136 if {$match eq {}} {
6137 bell
6138 return
6140 $ctext see $match
6141 set mend "$match + $mlen c"
6142 $ctext tag add sel $match $mend
6143 $ctext mark unset anchor
6147 proc dosearchback {} {
6148 global sstring ctext searchstring searchdirn
6150 focus $sstring
6151 $sstring icursor end
6152 set searchdirn -backwards
6153 if {$searchstring ne {}} {
6154 set sel [$ctext tag ranges sel]
6155 if {$sel ne {}} {
6156 set start [lindex $sel 0]
6157 } elseif {[catch {set start [$ctext index anchor]}]} {
6158 set start @0,[winfo height $ctext]
6160 set match [$ctext search -backwards -count ml -- $searchstring $start]
6161 $ctext tag remove sel 1.0 end
6162 if {$match eq {}} {
6163 bell
6164 return
6166 $ctext see $match
6167 set mend "$match + $ml c"
6168 $ctext tag add sel $match $mend
6169 $ctext mark unset anchor
6173 proc searchmark {first last} {
6174 global ctext searchstring
6176 set mend $first.0
6177 while {1} {
6178 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6179 if {$match eq {}} break
6180 set mend "$match + $mlen c"
6181 $ctext tag add found $match $mend
6185 proc searchmarkvisible {doall} {
6186 global ctext smarktop smarkbot
6188 set topline [lindex [split [$ctext index @0,0] .] 0]
6189 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6190 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6191 # no overlap with previous
6192 searchmark $topline $botline
6193 set smarktop $topline
6194 set smarkbot $botline
6195 } else {
6196 if {$topline < $smarktop} {
6197 searchmark $topline [expr {$smarktop-1}]
6198 set smarktop $topline
6200 if {$botline > $smarkbot} {
6201 searchmark [expr {$smarkbot+1}] $botline
6202 set smarkbot $botline
6207 proc scrolltext {f0 f1} {
6208 global searchstring
6210 .bleft.sb set $f0 $f1
6211 if {$searchstring ne {}} {
6212 searchmarkvisible 0
6216 proc setcoords {} {
6217 global linespc charspc canvx0 canvy0
6218 global xspc1 xspc2 lthickness
6220 set linespc [font metrics mainfont -linespace]
6221 set charspc [font measure mainfont "m"]
6222 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6223 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6224 set lthickness [expr {int($linespc / 9) + 1}]
6225 set xspc1(0) $linespc
6226 set xspc2 $linespc
6229 proc redisplay {} {
6230 global canv
6231 global selectedline
6233 set ymax [lindex [$canv cget -scrollregion] 3]
6234 if {$ymax eq {} || $ymax == 0} return
6235 set span [$canv yview]
6236 clear_display
6237 setcanvscroll
6238 allcanvs yview moveto [lindex $span 0]
6239 drawvisible
6240 if {[info exists selectedline]} {
6241 selectline $selectedline 0
6242 allcanvs yview moveto [lindex $span 0]
6246 proc parsefont {f n} {
6247 global fontattr
6249 set fontattr($f,family) [lindex $n 0]
6250 set s [lindex $n 1]
6251 if {$s eq {} || $s == 0} {
6252 set s 10
6253 } elseif {$s < 0} {
6254 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6256 set fontattr($f,size) $s
6257 set fontattr($f,weight) normal
6258 set fontattr($f,slant) roman
6259 foreach style [lrange $n 2 end] {
6260 switch -- $style {
6261 "normal" -
6262 "bold" {set fontattr($f,weight) $style}
6263 "roman" -
6264 "italic" {set fontattr($f,slant) $style}
6269 proc fontflags {f {isbold 0}} {
6270 global fontattr
6272 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6273 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6274 -slant $fontattr($f,slant)]
6277 proc fontname {f} {
6278 global fontattr
6280 set n [list $fontattr($f,family) $fontattr($f,size)]
6281 if {$fontattr($f,weight) eq "bold"} {
6282 lappend n "bold"
6284 if {$fontattr($f,slant) eq "italic"} {
6285 lappend n "italic"
6287 return $n
6290 proc incrfont {inc} {
6291 global mainfont textfont ctext canv cflist showrefstop
6292 global stopped entries fontattr
6294 unmarkmatches
6295 set s $fontattr(mainfont,size)
6296 incr s $inc
6297 if {$s < 1} {
6298 set s 1
6300 set fontattr(mainfont,size) $s
6301 font config mainfont -size $s
6302 font config mainfontbold -size $s
6303 set mainfont [fontname mainfont]
6304 set s $fontattr(textfont,size)
6305 incr s $inc
6306 if {$s < 1} {
6307 set s 1
6309 set fontattr(textfont,size) $s
6310 font config textfont -size $s
6311 font config textfontbold -size $s
6312 set textfont [fontname textfont]
6313 setcoords
6314 settabs
6315 redisplay
6318 proc clearsha1 {} {
6319 global sha1entry sha1string
6320 if {[string length $sha1string] == 40} {
6321 $sha1entry delete 0 end
6325 proc sha1change {n1 n2 op} {
6326 global sha1string currentid sha1but
6327 if {$sha1string == {}
6328 || ([info exists currentid] && $sha1string == $currentid)} {
6329 set state disabled
6330 } else {
6331 set state normal
6333 if {[$sha1but cget -state] == $state} return
6334 if {$state == "normal"} {
6335 $sha1but conf -state normal -relief raised -text "Goto: "
6336 } else {
6337 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
6341 proc gotocommit {} {
6342 global sha1string tagids headids curview varcid
6344 if {$sha1string == {}
6345 || ([info exists currentid] && $sha1string == $currentid)} return
6346 if {[info exists tagids($sha1string)]} {
6347 set id $tagids($sha1string)
6348 } elseif {[info exists headids($sha1string)]} {
6349 set id $headids($sha1string)
6350 } else {
6351 set id [string tolower $sha1string]
6352 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
6353 set matches [array names varcid "$curview,$id*"]
6354 if {$matches ne {}} {
6355 if {[llength $matches] > 1} {
6356 error_popup "Short SHA1 id $id is ambiguous"
6357 return
6359 set id [lindex [split [lindex $matches 0] ","] 1]
6363 if {[commitinview $id $curview]} {
6364 selectline [rowofcommit $id] 1
6365 return
6367 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
6368 set type "SHA1 id"
6369 } else {
6370 set type "Tag/Head"
6372 error_popup "$type $sha1string is not known"
6375 proc lineenter {x y id} {
6376 global hoverx hovery hoverid hovertimer
6377 global commitinfo canv
6379 if {![info exists commitinfo($id)] && ![getcommit $id]} return
6380 set hoverx $x
6381 set hovery $y
6382 set hoverid $id
6383 if {[info exists hovertimer]} {
6384 after cancel $hovertimer
6386 set hovertimer [after 500 linehover]
6387 $canv delete hover
6390 proc linemotion {x y id} {
6391 global hoverx hovery hoverid hovertimer
6393 if {[info exists hoverid] && $id == $hoverid} {
6394 set hoverx $x
6395 set hovery $y
6396 if {[info exists hovertimer]} {
6397 after cancel $hovertimer
6399 set hovertimer [after 500 linehover]
6403 proc lineleave {id} {
6404 global hoverid hovertimer canv
6406 if {[info exists hoverid] && $id == $hoverid} {
6407 $canv delete hover
6408 if {[info exists hovertimer]} {
6409 after cancel $hovertimer
6410 unset hovertimer
6412 unset hoverid
6416 proc linehover {} {
6417 global hoverx hovery hoverid hovertimer
6418 global canv linespc lthickness
6419 global commitinfo
6421 set text [lindex $commitinfo($hoverid) 0]
6422 set ymax [lindex [$canv cget -scrollregion] 3]
6423 if {$ymax == {}} return
6424 set yfrac [lindex [$canv yview] 0]
6425 set x [expr {$hoverx + 2 * $linespc}]
6426 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
6427 set x0 [expr {$x - 2 * $lthickness}]
6428 set y0 [expr {$y - 2 * $lthickness}]
6429 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
6430 set y1 [expr {$y + $linespc + 2 * $lthickness}]
6431 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
6432 -fill \#ffff80 -outline black -width 1 -tags hover]
6433 $canv raise $t
6434 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
6435 -font mainfont]
6436 $canv raise $t
6439 proc clickisonarrow {id y} {
6440 global lthickness
6442 set ranges [rowranges $id]
6443 set thresh [expr {2 * $lthickness + 6}]
6444 set n [expr {[llength $ranges] - 1}]
6445 for {set i 1} {$i < $n} {incr i} {
6446 set row [lindex $ranges $i]
6447 if {abs([yc $row] - $y) < $thresh} {
6448 return $i
6451 return {}
6454 proc arrowjump {id n y} {
6455 global canv
6457 # 1 <-> 2, 3 <-> 4, etc...
6458 set n [expr {(($n - 1) ^ 1) + 1}]
6459 set row [lindex [rowranges $id] $n]
6460 set yt [yc $row]
6461 set ymax [lindex [$canv cget -scrollregion] 3]
6462 if {$ymax eq {} || $ymax <= 0} return
6463 set view [$canv yview]
6464 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
6465 set yfrac [expr {$yt / $ymax - $yspan / 2}]
6466 if {$yfrac < 0} {
6467 set yfrac 0
6469 allcanvs yview moveto $yfrac
6472 proc lineclick {x y id isnew} {
6473 global ctext commitinfo children canv thickerline curview
6475 if {![info exists commitinfo($id)] && ![getcommit $id]} return
6476 unmarkmatches
6477 unselectline
6478 normalline
6479 $canv delete hover
6480 # draw this line thicker than normal
6481 set thickerline $id
6482 drawlines $id
6483 if {$isnew} {
6484 set ymax [lindex [$canv cget -scrollregion] 3]
6485 if {$ymax eq {}} return
6486 set yfrac [lindex [$canv yview] 0]
6487 set y [expr {$y + $yfrac * $ymax}]
6489 set dirn [clickisonarrow $id $y]
6490 if {$dirn ne {}} {
6491 arrowjump $id $dirn $y
6492 return
6495 if {$isnew} {
6496 addtohistory [list lineclick $x $y $id 0]
6498 # fill the details pane with info about this line
6499 $ctext conf -state normal
6500 clear_ctext
6501 settabs 0
6502 $ctext insert end "Parent:\t"
6503 $ctext insert end $id link0
6504 setlink $id link0
6505 set info $commitinfo($id)
6506 $ctext insert end "\n\t[lindex $info 0]\n"
6507 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
6508 set date [formatdate [lindex $info 2]]
6509 $ctext insert end "\tDate:\t$date\n"
6510 set kids $children($curview,$id)
6511 if {$kids ne {}} {
6512 $ctext insert end "\nChildren:"
6513 set i 0
6514 foreach child $kids {
6515 incr i
6516 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
6517 set info $commitinfo($child)
6518 $ctext insert end "\n\t"
6519 $ctext insert end $child link$i
6520 setlink $child link$i
6521 $ctext insert end "\n\t[lindex $info 0]"
6522 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
6523 set date [formatdate [lindex $info 2]]
6524 $ctext insert end "\n\tDate:\t$date\n"
6527 $ctext conf -state disabled
6528 init_flist {}
6531 proc normalline {} {
6532 global thickerline
6533 if {[info exists thickerline]} {
6534 set id $thickerline
6535 unset thickerline
6536 drawlines $id
6540 proc selbyid {id} {
6541 global curview
6542 if {[commitinview $id $curview]} {
6543 selectline [rowofcommit $id] 1
6547 proc mstime {} {
6548 global startmstime
6549 if {![info exists startmstime]} {
6550 set startmstime [clock clicks -milliseconds]
6552 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
6555 proc rowmenu {x y id} {
6556 global rowctxmenu selectedline rowmenuid curview
6557 global nullid nullid2 fakerowmenu mainhead
6559 stopfinding
6560 set rowmenuid $id
6561 if {![info exists selectedline]
6562 || [rowofcommit $id] eq $selectedline} {
6563 set state disabled
6564 } else {
6565 set state normal
6567 if {$id ne $nullid && $id ne $nullid2} {
6568 set menu $rowctxmenu
6569 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
6570 } else {
6571 set menu $fakerowmenu
6573 $menu entryconfigure "Diff this*" -state $state
6574 $menu entryconfigure "Diff selected*" -state $state
6575 $menu entryconfigure "Make patch" -state $state
6576 tk_popup $menu $x $y
6579 proc diffvssel {dirn} {
6580 global rowmenuid selectedline
6582 if {![info exists selectedline]} return
6583 if {$dirn} {
6584 set oldid [commitonrow $selectedline]
6585 set newid $rowmenuid
6586 } else {
6587 set oldid $rowmenuid
6588 set newid [commitonrow $selectedline]
6590 addtohistory [list doseldiff $oldid $newid]
6591 doseldiff $oldid $newid
6594 proc doseldiff {oldid newid} {
6595 global ctext
6596 global commitinfo
6598 $ctext conf -state normal
6599 clear_ctext
6600 init_flist "Top"
6601 $ctext insert end "From "
6602 $ctext insert end $oldid link0
6603 setlink $oldid link0
6604 $ctext insert end "\n "
6605 $ctext insert end [lindex $commitinfo($oldid) 0]
6606 $ctext insert end "\n\nTo "
6607 $ctext insert end $newid link1
6608 setlink $newid link1
6609 $ctext insert end "\n "
6610 $ctext insert end [lindex $commitinfo($newid) 0]
6611 $ctext insert end "\n"
6612 $ctext conf -state disabled
6613 $ctext tag remove found 1.0 end
6614 startdiff [list $oldid $newid]
6617 proc mkpatch {} {
6618 global rowmenuid currentid commitinfo patchtop patchnum
6620 if {![info exists currentid]} return
6621 set oldid $currentid
6622 set oldhead [lindex $commitinfo($oldid) 0]
6623 set newid $rowmenuid
6624 set newhead [lindex $commitinfo($newid) 0]
6625 set top .patch
6626 set patchtop $top
6627 catch {destroy $top}
6628 toplevel $top
6629 label $top.title -text "Generate patch"
6630 grid $top.title - -pady 10
6631 label $top.from -text "From:"
6632 entry $top.fromsha1 -width 40 -relief flat
6633 $top.fromsha1 insert 0 $oldid
6634 $top.fromsha1 conf -state readonly
6635 grid $top.from $top.fromsha1 -sticky w
6636 entry $top.fromhead -width 60 -relief flat
6637 $top.fromhead insert 0 $oldhead
6638 $top.fromhead conf -state readonly
6639 grid x $top.fromhead -sticky w
6640 label $top.to -text "To:"
6641 entry $top.tosha1 -width 40 -relief flat
6642 $top.tosha1 insert 0 $newid
6643 $top.tosha1 conf -state readonly
6644 grid $top.to $top.tosha1 -sticky w
6645 entry $top.tohead -width 60 -relief flat
6646 $top.tohead insert 0 $newhead
6647 $top.tohead conf -state readonly
6648 grid x $top.tohead -sticky w
6649 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
6650 grid $top.rev x -pady 10
6651 label $top.flab -text "Output file:"
6652 entry $top.fname -width 60
6653 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
6654 incr patchnum
6655 grid $top.flab $top.fname -sticky w
6656 frame $top.buts
6657 button $top.buts.gen -text "Generate" -command mkpatchgo
6658 button $top.buts.can -text "Cancel" -command mkpatchcan
6659 grid $top.buts.gen $top.buts.can
6660 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6661 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6662 grid $top.buts - -pady 10 -sticky ew
6663 focus $top.fname
6666 proc mkpatchrev {} {
6667 global patchtop
6669 set oldid [$patchtop.fromsha1 get]
6670 set oldhead [$patchtop.fromhead get]
6671 set newid [$patchtop.tosha1 get]
6672 set newhead [$patchtop.tohead get]
6673 foreach e [list fromsha1 fromhead tosha1 tohead] \
6674 v [list $newid $newhead $oldid $oldhead] {
6675 $patchtop.$e conf -state normal
6676 $patchtop.$e delete 0 end
6677 $patchtop.$e insert 0 $v
6678 $patchtop.$e conf -state readonly
6682 proc mkpatchgo {} {
6683 global patchtop nullid nullid2
6685 set oldid [$patchtop.fromsha1 get]
6686 set newid [$patchtop.tosha1 get]
6687 set fname [$patchtop.fname get]
6688 set cmd [diffcmd [list $oldid $newid] -p]
6689 # trim off the initial "|"
6690 set cmd [lrange $cmd 1 end]
6691 lappend cmd >$fname &
6692 if {[catch {eval exec $cmd} err]} {
6693 error_popup "Error creating patch: $err"
6695 catch {destroy $patchtop}
6696 unset patchtop
6699 proc mkpatchcan {} {
6700 global patchtop
6702 catch {destroy $patchtop}
6703 unset patchtop
6706 proc mktag {} {
6707 global rowmenuid mktagtop commitinfo
6709 set top .maketag
6710 set mktagtop $top
6711 catch {destroy $top}
6712 toplevel $top
6713 label $top.title -text "Create tag"
6714 grid $top.title - -pady 10
6715 label $top.id -text "ID:"
6716 entry $top.sha1 -width 40 -relief flat
6717 $top.sha1 insert 0 $rowmenuid
6718 $top.sha1 conf -state readonly
6719 grid $top.id $top.sha1 -sticky w
6720 entry $top.head -width 60 -relief flat
6721 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6722 $top.head conf -state readonly
6723 grid x $top.head -sticky w
6724 label $top.tlab -text "Tag name:"
6725 entry $top.tag -width 60
6726 grid $top.tlab $top.tag -sticky w
6727 frame $top.buts
6728 button $top.buts.gen -text "Create" -command mktaggo
6729 button $top.buts.can -text "Cancel" -command mktagcan
6730 grid $top.buts.gen $top.buts.can
6731 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6732 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6733 grid $top.buts - -pady 10 -sticky ew
6734 focus $top.tag
6737 proc domktag {} {
6738 global mktagtop env tagids idtags
6740 set id [$mktagtop.sha1 get]
6741 set tag [$mktagtop.tag get]
6742 if {$tag == {}} {
6743 error_popup "No tag name specified"
6744 return
6746 if {[info exists tagids($tag)]} {
6747 error_popup "Tag \"$tag\" already exists"
6748 return
6750 if {[catch {
6751 set dir [gitdir]
6752 set fname [file join $dir "refs/tags" $tag]
6753 set f [open $fname w]
6754 puts $f $id
6755 close $f
6756 } err]} {
6757 error_popup "Error creating tag: $err"
6758 return
6761 set tagids($tag) $id
6762 lappend idtags($id) $tag
6763 redrawtags $id
6764 addedtag $id
6765 dispneartags 0
6766 run refill_reflist
6769 proc redrawtags {id} {
6770 global canv linehtag idpos selectedline curview
6771 global canvxmax iddrawn
6773 if {![commitinview $id $curview]} return
6774 if {![info exists iddrawn($id)]} return
6775 drawcommits [rowofcommit $id]
6776 $canv delete tag.$id
6777 set xt [eval drawtags $id $idpos($id)]
6778 $canv coords $linehtag([rowofcommit $id]) $xt [lindex $idpos($id) 2]
6779 set text [$canv itemcget $linehtag([rowofcommit $id]) -text]
6780 set xr [expr {$xt + [font measure mainfont $text]}]
6781 if {$xr > $canvxmax} {
6782 set canvxmax $xr
6783 setcanvscroll
6785 if {[info exists selectedline]
6786 && $selectedline == [rowofcommit $id]} {
6787 selectline $selectedline 0
6791 proc mktagcan {} {
6792 global mktagtop
6794 catch {destroy $mktagtop}
6795 unset mktagtop
6798 proc mktaggo {} {
6799 domktag
6800 mktagcan
6803 proc writecommit {} {
6804 global rowmenuid wrcomtop commitinfo wrcomcmd
6806 set top .writecommit
6807 set wrcomtop $top
6808 catch {destroy $top}
6809 toplevel $top
6810 label $top.title -text "Write commit to file"
6811 grid $top.title - -pady 10
6812 label $top.id -text "ID:"
6813 entry $top.sha1 -width 40 -relief flat
6814 $top.sha1 insert 0 $rowmenuid
6815 $top.sha1 conf -state readonly
6816 grid $top.id $top.sha1 -sticky w
6817 entry $top.head -width 60 -relief flat
6818 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6819 $top.head conf -state readonly
6820 grid x $top.head -sticky w
6821 label $top.clab -text "Command:"
6822 entry $top.cmd -width 60 -textvariable wrcomcmd
6823 grid $top.clab $top.cmd -sticky w -pady 10
6824 label $top.flab -text "Output file:"
6825 entry $top.fname -width 60
6826 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
6827 grid $top.flab $top.fname -sticky w
6828 frame $top.buts
6829 button $top.buts.gen -text "Write" -command wrcomgo
6830 button $top.buts.can -text "Cancel" -command wrcomcan
6831 grid $top.buts.gen $top.buts.can
6832 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6833 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6834 grid $top.buts - -pady 10 -sticky ew
6835 focus $top.fname
6838 proc wrcomgo {} {
6839 global wrcomtop
6841 set id [$wrcomtop.sha1 get]
6842 set cmd "echo $id | [$wrcomtop.cmd get]"
6843 set fname [$wrcomtop.fname get]
6844 if {[catch {exec sh -c $cmd >$fname &} err]} {
6845 error_popup "Error writing commit: $err"
6847 catch {destroy $wrcomtop}
6848 unset wrcomtop
6851 proc wrcomcan {} {
6852 global wrcomtop
6854 catch {destroy $wrcomtop}
6855 unset wrcomtop
6858 proc mkbranch {} {
6859 global rowmenuid mkbrtop
6861 set top .makebranch
6862 catch {destroy $top}
6863 toplevel $top
6864 label $top.title -text "Create new branch"
6865 grid $top.title - -pady 10
6866 label $top.id -text "ID:"
6867 entry $top.sha1 -width 40 -relief flat
6868 $top.sha1 insert 0 $rowmenuid
6869 $top.sha1 conf -state readonly
6870 grid $top.id $top.sha1 -sticky w
6871 label $top.nlab -text "Name:"
6872 entry $top.name -width 40
6873 grid $top.nlab $top.name -sticky w
6874 frame $top.buts
6875 button $top.buts.go -text "Create" -command [list mkbrgo $top]
6876 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
6877 grid $top.buts.go $top.buts.can
6878 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6879 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6880 grid $top.buts - -pady 10 -sticky ew
6881 focus $top.name
6884 proc mkbrgo {top} {
6885 global headids idheads
6887 set name [$top.name get]
6888 set id [$top.sha1 get]
6889 if {$name eq {}} {
6890 error_popup "Please specify a name for the new branch"
6891 return
6893 catch {destroy $top}
6894 nowbusy newbranch
6895 update
6896 if {[catch {
6897 exec git branch $name $id
6898 } err]} {
6899 notbusy newbranch
6900 error_popup $err
6901 } else {
6902 set headids($name) $id
6903 lappend idheads($id) $name
6904 addedhead $id $name
6905 notbusy newbranch
6906 redrawtags $id
6907 dispneartags 0
6908 run refill_reflist
6912 proc cherrypick {} {
6913 global rowmenuid curview
6914 global mainhead
6916 set oldhead [exec git rev-parse HEAD]
6917 set dheads [descheads $rowmenuid]
6918 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6919 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
6920 included in branch $mainhead -- really re-apply it?"]
6921 if {!$ok} return
6923 nowbusy cherrypick "Cherry-picking"
6924 update
6925 # Unfortunately git-cherry-pick writes stuff to stderr even when
6926 # no error occurs, and exec takes that as an indication of error...
6927 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6928 notbusy cherrypick
6929 error_popup $err
6930 return
6932 set newhead [exec git rev-parse HEAD]
6933 if {$newhead eq $oldhead} {
6934 notbusy cherrypick
6935 error_popup "No changes committed"
6936 return
6938 addnewchild $newhead $oldhead
6939 if {[commitinview $oldhead $curview]} {
6940 insertrow $newhead $oldhead $curview
6941 if {$mainhead ne {}} {
6942 movehead $newhead $mainhead
6943 movedhead $newhead $mainhead
6945 redrawtags $oldhead
6946 redrawtags $newhead
6948 notbusy cherrypick
6951 proc resethead {} {
6952 global mainheadid mainhead rowmenuid confirm_ok resettype
6954 set confirm_ok 0
6955 set w ".confirmreset"
6956 toplevel $w
6957 wm transient $w .
6958 wm title $w "Confirm reset"
6959 message $w.m -text \
6960 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6961 -justify center -aspect 1000
6962 pack $w.m -side top -fill x -padx 20 -pady 20
6963 frame $w.f -relief sunken -border 2
6964 message $w.f.rt -text "Reset type:" -aspect 1000
6965 grid $w.f.rt -sticky w
6966 set resettype mixed
6967 radiobutton $w.f.soft -value soft -variable resettype -justify left \
6968 -text "Soft: Leave working tree and index untouched"
6969 grid $w.f.soft -sticky w
6970 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6971 -text "Mixed: Leave working tree untouched, reset index"
6972 grid $w.f.mixed -sticky w
6973 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6974 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6975 grid $w.f.hard -sticky w
6976 pack $w.f -side top -fill x
6977 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6978 pack $w.ok -side left -fill x -padx 20 -pady 20
6979 button $w.cancel -text Cancel -command "destroy $w"
6980 pack $w.cancel -side right -fill x -padx 20 -pady 20
6981 bind $w <Visibility> "grab $w; focus $w"
6982 tkwait window $w
6983 if {!$confirm_ok} return
6984 if {[catch {set fd [open \
6985 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6986 error_popup $err
6987 } else {
6988 dohidelocalchanges
6989 filerun $fd [list readresetstat $fd]
6990 nowbusy reset "Resetting"
6994 proc readresetstat {fd} {
6995 global mainhead mainheadid showlocalchanges rprogcoord
6997 if {[gets $fd line] >= 0} {
6998 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6999 set rprogcoord [expr {1.0 * $m / $n}]
7000 adjustprogress
7002 return 1
7004 set rprogcoord 0
7005 adjustprogress
7006 notbusy reset
7007 if {[catch {close $fd} err]} {
7008 error_popup $err
7010 set oldhead $mainheadid
7011 set newhead [exec git rev-parse HEAD]
7012 if {$newhead ne $oldhead} {
7013 movehead $newhead $mainhead
7014 movedhead $newhead $mainhead
7015 set mainheadid $newhead
7016 redrawtags $oldhead
7017 redrawtags $newhead
7019 if {$showlocalchanges} {
7020 doshowlocalchanges
7022 return 0
7025 # context menu for a head
7026 proc headmenu {x y id head} {
7027 global headmenuid headmenuhead headctxmenu mainhead
7029 stopfinding
7030 set headmenuid $id
7031 set headmenuhead $head
7032 set state normal
7033 if {$head eq $mainhead} {
7034 set state disabled
7036 $headctxmenu entryconfigure 0 -state $state
7037 $headctxmenu entryconfigure 1 -state $state
7038 tk_popup $headctxmenu $x $y
7041 proc cobranch {} {
7042 global headmenuid headmenuhead mainhead headids
7043 global showlocalchanges mainheadid
7045 # check the tree is clean first??
7046 set oldmainhead $mainhead
7047 nowbusy checkout "Checking out"
7048 update
7049 dohidelocalchanges
7050 if {[catch {
7051 exec git checkout -q $headmenuhead
7052 } err]} {
7053 notbusy checkout
7054 error_popup $err
7055 } else {
7056 notbusy checkout
7057 set mainhead $headmenuhead
7058 set mainheadid $headmenuid
7059 if {[info exists headids($oldmainhead)]} {
7060 redrawtags $headids($oldmainhead)
7062 redrawtags $headmenuid
7064 if {$showlocalchanges} {
7065 dodiffindex
7069 proc rmbranch {} {
7070 global headmenuid headmenuhead mainhead
7071 global idheads
7073 set head $headmenuhead
7074 set id $headmenuid
7075 # this check shouldn't be needed any more...
7076 if {$head eq $mainhead} {
7077 error_popup "Cannot delete the currently checked-out branch"
7078 return
7080 set dheads [descheads $id]
7081 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7082 # the stuff on this branch isn't on any other branch
7083 if {![confirm_popup "The commits on branch $head aren't on any other\
7084 branch.\nReally delete branch $head?"]} return
7086 nowbusy rmbranch
7087 update
7088 if {[catch {exec git branch -D $head} err]} {
7089 notbusy rmbranch
7090 error_popup $err
7091 return
7093 removehead $id $head
7094 removedhead $id $head
7095 redrawtags $id
7096 notbusy rmbranch
7097 dispneartags 0
7098 run refill_reflist
7101 # Display a list of tags and heads
7102 proc showrefs {} {
7103 global showrefstop bgcolor fgcolor selectbgcolor
7104 global bglist fglist reflistfilter reflist maincursor
7106 set top .showrefs
7107 set showrefstop $top
7108 if {[winfo exists $top]} {
7109 raise $top
7110 refill_reflist
7111 return
7113 toplevel $top
7114 wm title $top "Tags and heads: [file tail [pwd]]"
7115 text $top.list -background $bgcolor -foreground $fgcolor \
7116 -selectbackground $selectbgcolor -font mainfont \
7117 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7118 -width 30 -height 20 -cursor $maincursor \
7119 -spacing1 1 -spacing3 1 -state disabled
7120 $top.list tag configure highlight -background $selectbgcolor
7121 lappend bglist $top.list
7122 lappend fglist $top.list
7123 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7124 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7125 grid $top.list $top.ysb -sticky nsew
7126 grid $top.xsb x -sticky ew
7127 frame $top.f
7128 label $top.f.l -text "Filter: " -font uifont
7129 entry $top.f.e -width 20 -textvariable reflistfilter -font uifont
7130 set reflistfilter "*"
7131 trace add variable reflistfilter write reflistfilter_change
7132 pack $top.f.e -side right -fill x -expand 1
7133 pack $top.f.l -side left
7134 grid $top.f - -sticky ew -pady 2
7135 button $top.close -command [list destroy $top] -text "Close" \
7136 -font uifont
7137 grid $top.close -
7138 grid columnconfigure $top 0 -weight 1
7139 grid rowconfigure $top 0 -weight 1
7140 bind $top.list <1> {break}
7141 bind $top.list <B1-Motion> {break}
7142 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7143 set reflist {}
7144 refill_reflist
7147 proc sel_reflist {w x y} {
7148 global showrefstop reflist headids tagids otherrefids
7150 if {![winfo exists $showrefstop]} return
7151 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7152 set ref [lindex $reflist [expr {$l-1}]]
7153 set n [lindex $ref 0]
7154 switch -- [lindex $ref 1] {
7155 "H" {selbyid $headids($n)}
7156 "T" {selbyid $tagids($n)}
7157 "o" {selbyid $otherrefids($n)}
7159 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7162 proc unsel_reflist {} {
7163 global showrefstop
7165 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7166 $showrefstop.list tag remove highlight 0.0 end
7169 proc reflistfilter_change {n1 n2 op} {
7170 global reflistfilter
7172 after cancel refill_reflist
7173 after 200 refill_reflist
7176 proc refill_reflist {} {
7177 global reflist reflistfilter showrefstop headids tagids otherrefids
7178 global curview commitinterest
7180 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7181 set refs {}
7182 foreach n [array names headids] {
7183 if {[string match $reflistfilter $n]} {
7184 if {[commitinview $headids($n) $curview]} {
7185 lappend refs [list $n H]
7186 } else {
7187 set commitinterest($headids($n)) {run refill_reflist}
7191 foreach n [array names tagids] {
7192 if {[string match $reflistfilter $n]} {
7193 if {[commitinview $tagids($n) $curview]} {
7194 lappend refs [list $n T]
7195 } else {
7196 set commitinterest($tagids($n)) {run refill_reflist}
7200 foreach n [array names otherrefids] {
7201 if {[string match $reflistfilter $n]} {
7202 if {[commitinview $otherrefids($n) $curview]} {
7203 lappend refs [list $n o]
7204 } else {
7205 set commitinterest($otherrefids($n)) {run refill_reflist}
7209 set refs [lsort -index 0 $refs]
7210 if {$refs eq $reflist} return
7212 # Update the contents of $showrefstop.list according to the
7213 # differences between $reflist (old) and $refs (new)
7214 $showrefstop.list conf -state normal
7215 $showrefstop.list insert end "\n"
7216 set i 0
7217 set j 0
7218 while {$i < [llength $reflist] || $j < [llength $refs]} {
7219 if {$i < [llength $reflist]} {
7220 if {$j < [llength $refs]} {
7221 set cmp [string compare [lindex $reflist $i 0] \
7222 [lindex $refs $j 0]]
7223 if {$cmp == 0} {
7224 set cmp [string compare [lindex $reflist $i 1] \
7225 [lindex $refs $j 1]]
7227 } else {
7228 set cmp -1
7230 } else {
7231 set cmp 1
7233 switch -- $cmp {
7234 -1 {
7235 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7236 incr i
7239 incr i
7240 incr j
7243 set l [expr {$j + 1}]
7244 $showrefstop.list image create $l.0 -align baseline \
7245 -image reficon-[lindex $refs $j 1] -padx 2
7246 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7247 incr j
7251 set reflist $refs
7252 # delete last newline
7253 $showrefstop.list delete end-2c end-1c
7254 $showrefstop.list conf -state disabled
7257 # Stuff for finding nearby tags
7258 proc getallcommits {} {
7259 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7260 global idheads idtags idotherrefs allparents tagobjid
7262 if {![info exists allcommits]} {
7263 set nextarc 0
7264 set allcommits 0
7265 set seeds {}
7266 set allcwait 0
7267 set cachedarcs 0
7268 set allccache [file join [gitdir] "gitk.cache"]
7269 if {![catch {
7270 set f [open $allccache r]
7271 set allcwait 1
7272 getcache $f
7273 }]} return
7276 if {$allcwait} {
7277 return
7279 set cmd [list | git rev-list --parents]
7280 set allcupdate [expr {$seeds ne {}}]
7281 if {!$allcupdate} {
7282 set ids "--all"
7283 } else {
7284 set refs [concat [array names idheads] [array names idtags] \
7285 [array names idotherrefs]]
7286 set ids {}
7287 set tagobjs {}
7288 foreach name [array names tagobjid] {
7289 lappend tagobjs $tagobjid($name)
7291 foreach id [lsort -unique $refs] {
7292 if {![info exists allparents($id)] &&
7293 [lsearch -exact $tagobjs $id] < 0} {
7294 lappend ids $id
7297 if {$ids ne {}} {
7298 foreach id $seeds {
7299 lappend ids "^$id"
7303 if {$ids ne {}} {
7304 set fd [open [concat $cmd $ids] r]
7305 fconfigure $fd -blocking 0
7306 incr allcommits
7307 nowbusy allcommits
7308 filerun $fd [list getallclines $fd]
7309 } else {
7310 dispneartags 0
7314 # Since most commits have 1 parent and 1 child, we group strings of
7315 # such commits into "arcs" joining branch/merge points (BMPs), which
7316 # are commits that either don't have 1 parent or don't have 1 child.
7318 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
7319 # arcout(id) - outgoing arcs for BMP
7320 # arcids(a) - list of IDs on arc including end but not start
7321 # arcstart(a) - BMP ID at start of arc
7322 # arcend(a) - BMP ID at end of arc
7323 # growing(a) - arc a is still growing
7324 # arctags(a) - IDs out of arcids (excluding end) that have tags
7325 # archeads(a) - IDs out of arcids (excluding end) that have heads
7326 # The start of an arc is at the descendent end, so "incoming" means
7327 # coming from descendents, and "outgoing" means going towards ancestors.
7329 proc getallclines {fd} {
7330 global allparents allchildren idtags idheads nextarc
7331 global arcnos arcids arctags arcout arcend arcstart archeads growing
7332 global seeds allcommits cachedarcs allcupdate
7334 set nid 0
7335 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
7336 set id [lindex $line 0]
7337 if {[info exists allparents($id)]} {
7338 # seen it already
7339 continue
7341 set cachedarcs 0
7342 set olds [lrange $line 1 end]
7343 set allparents($id) $olds
7344 if {![info exists allchildren($id)]} {
7345 set allchildren($id) {}
7346 set arcnos($id) {}
7347 lappend seeds $id
7348 } else {
7349 set a $arcnos($id)
7350 if {[llength $olds] == 1 && [llength $a] == 1} {
7351 lappend arcids($a) $id
7352 if {[info exists idtags($id)]} {
7353 lappend arctags($a) $id
7355 if {[info exists idheads($id)]} {
7356 lappend archeads($a) $id
7358 if {[info exists allparents($olds)]} {
7359 # seen parent already
7360 if {![info exists arcout($olds)]} {
7361 splitarc $olds
7363 lappend arcids($a) $olds
7364 set arcend($a) $olds
7365 unset growing($a)
7367 lappend allchildren($olds) $id
7368 lappend arcnos($olds) $a
7369 continue
7372 foreach a $arcnos($id) {
7373 lappend arcids($a) $id
7374 set arcend($a) $id
7375 unset growing($a)
7378 set ao {}
7379 foreach p $olds {
7380 lappend allchildren($p) $id
7381 set a [incr nextarc]
7382 set arcstart($a) $id
7383 set archeads($a) {}
7384 set arctags($a) {}
7385 set archeads($a) {}
7386 set arcids($a) {}
7387 lappend ao $a
7388 set growing($a) 1
7389 if {[info exists allparents($p)]} {
7390 # seen it already, may need to make a new branch
7391 if {![info exists arcout($p)]} {
7392 splitarc $p
7394 lappend arcids($a) $p
7395 set arcend($a) $p
7396 unset growing($a)
7398 lappend arcnos($p) $a
7400 set arcout($id) $ao
7402 if {$nid > 0} {
7403 global cached_dheads cached_dtags cached_atags
7404 catch {unset cached_dheads}
7405 catch {unset cached_dtags}
7406 catch {unset cached_atags}
7408 if {![eof $fd]} {
7409 return [expr {$nid >= 1000? 2: 1}]
7411 set cacheok 1
7412 if {[catch {
7413 fconfigure $fd -blocking 1
7414 close $fd
7415 } err]} {
7416 # got an error reading the list of commits
7417 # if we were updating, try rereading the whole thing again
7418 if {$allcupdate} {
7419 incr allcommits -1
7420 dropcache $err
7421 return
7423 error_popup "Error reading commit topology information;\
7424 branch and preceding/following tag information\
7425 will be incomplete.\n($err)"
7426 set cacheok 0
7428 if {[incr allcommits -1] == 0} {
7429 notbusy allcommits
7430 if {$cacheok} {
7431 run savecache
7434 dispneartags 0
7435 return 0
7438 proc recalcarc {a} {
7439 global arctags archeads arcids idtags idheads
7441 set at {}
7442 set ah {}
7443 foreach id [lrange $arcids($a) 0 end-1] {
7444 if {[info exists idtags($id)]} {
7445 lappend at $id
7447 if {[info exists idheads($id)]} {
7448 lappend ah $id
7451 set arctags($a) $at
7452 set archeads($a) $ah
7455 proc splitarc {p} {
7456 global arcnos arcids nextarc arctags archeads idtags idheads
7457 global arcstart arcend arcout allparents growing
7459 set a $arcnos($p)
7460 if {[llength $a] != 1} {
7461 puts "oops splitarc called but [llength $a] arcs already"
7462 return
7464 set a [lindex $a 0]
7465 set i [lsearch -exact $arcids($a) $p]
7466 if {$i < 0} {
7467 puts "oops splitarc $p not in arc $a"
7468 return
7470 set na [incr nextarc]
7471 if {[info exists arcend($a)]} {
7472 set arcend($na) $arcend($a)
7473 } else {
7474 set l [lindex $allparents([lindex $arcids($a) end]) 0]
7475 set j [lsearch -exact $arcnos($l) $a]
7476 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
7478 set tail [lrange $arcids($a) [expr {$i+1}] end]
7479 set arcids($a) [lrange $arcids($a) 0 $i]
7480 set arcend($a) $p
7481 set arcstart($na) $p
7482 set arcout($p) $na
7483 set arcids($na) $tail
7484 if {[info exists growing($a)]} {
7485 set growing($na) 1
7486 unset growing($a)
7489 foreach id $tail {
7490 if {[llength $arcnos($id)] == 1} {
7491 set arcnos($id) $na
7492 } else {
7493 set j [lsearch -exact $arcnos($id) $a]
7494 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
7498 # reconstruct tags and heads lists
7499 if {$arctags($a) ne {} || $archeads($a) ne {}} {
7500 recalcarc $a
7501 recalcarc $na
7502 } else {
7503 set arctags($na) {}
7504 set archeads($na) {}
7508 # Update things for a new commit added that is a child of one
7509 # existing commit. Used when cherry-picking.
7510 proc addnewchild {id p} {
7511 global allparents allchildren idtags nextarc
7512 global arcnos arcids arctags arcout arcend arcstart archeads growing
7513 global seeds allcommits
7515 if {![info exists allcommits] || ![info exists arcnos($p)]} return
7516 set allparents($id) [list $p]
7517 set allchildren($id) {}
7518 set arcnos($id) {}
7519 lappend seeds $id
7520 lappend allchildren($p) $id
7521 set a [incr nextarc]
7522 set arcstart($a) $id
7523 set archeads($a) {}
7524 set arctags($a) {}
7525 set arcids($a) [list $p]
7526 set arcend($a) $p
7527 if {![info exists arcout($p)]} {
7528 splitarc $p
7530 lappend arcnos($p) $a
7531 set arcout($id) [list $a]
7534 # This implements a cache for the topology information.
7535 # The cache saves, for each arc, the start and end of the arc,
7536 # the ids on the arc, and the outgoing arcs from the end.
7537 proc readcache {f} {
7538 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
7539 global idtags idheads allparents cachedarcs possible_seeds seeds growing
7540 global allcwait
7542 set a $nextarc
7543 set lim $cachedarcs
7544 if {$lim - $a > 500} {
7545 set lim [expr {$a + 500}]
7547 if {[catch {
7548 if {$a == $lim} {
7549 # finish reading the cache and setting up arctags, etc.
7550 set line [gets $f]
7551 if {$line ne "1"} {error "bad final version"}
7552 close $f
7553 foreach id [array names idtags] {
7554 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7555 [llength $allparents($id)] == 1} {
7556 set a [lindex $arcnos($id) 0]
7557 if {$arctags($a) eq {}} {
7558 recalcarc $a
7562 foreach id [array names idheads] {
7563 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7564 [llength $allparents($id)] == 1} {
7565 set a [lindex $arcnos($id) 0]
7566 if {$archeads($a) eq {}} {
7567 recalcarc $a
7571 foreach id [lsort -unique $possible_seeds] {
7572 if {$arcnos($id) eq {}} {
7573 lappend seeds $id
7576 set allcwait 0
7577 } else {
7578 while {[incr a] <= $lim} {
7579 set line [gets $f]
7580 if {[llength $line] != 3} {error "bad line"}
7581 set s [lindex $line 0]
7582 set arcstart($a) $s
7583 lappend arcout($s) $a
7584 if {![info exists arcnos($s)]} {
7585 lappend possible_seeds $s
7586 set arcnos($s) {}
7588 set e [lindex $line 1]
7589 if {$e eq {}} {
7590 set growing($a) 1
7591 } else {
7592 set arcend($a) $e
7593 if {![info exists arcout($e)]} {
7594 set arcout($e) {}
7597 set arcids($a) [lindex $line 2]
7598 foreach id $arcids($a) {
7599 lappend allparents($s) $id
7600 set s $id
7601 lappend arcnos($id) $a
7603 if {![info exists allparents($s)]} {
7604 set allparents($s) {}
7606 set arctags($a) {}
7607 set archeads($a) {}
7609 set nextarc [expr {$a - 1}]
7611 } err]} {
7612 dropcache $err
7613 return 0
7615 if {!$allcwait} {
7616 getallcommits
7618 return $allcwait
7621 proc getcache {f} {
7622 global nextarc cachedarcs possible_seeds
7624 if {[catch {
7625 set line [gets $f]
7626 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
7627 # make sure it's an integer
7628 set cachedarcs [expr {int([lindex $line 1])}]
7629 if {$cachedarcs < 0} {error "bad number of arcs"}
7630 set nextarc 0
7631 set possible_seeds {}
7632 run readcache $f
7633 } err]} {
7634 dropcache $err
7636 return 0
7639 proc dropcache {err} {
7640 global allcwait nextarc cachedarcs seeds
7642 #puts "dropping cache ($err)"
7643 foreach v {arcnos arcout arcids arcstart arcend growing \
7644 arctags archeads allparents allchildren} {
7645 global $v
7646 catch {unset $v}
7648 set allcwait 0
7649 set nextarc 0
7650 set cachedarcs 0
7651 set seeds {}
7652 getallcommits
7655 proc writecache {f} {
7656 global cachearc cachedarcs allccache
7657 global arcstart arcend arcnos arcids arcout
7659 set a $cachearc
7660 set lim $cachedarcs
7661 if {$lim - $a > 1000} {
7662 set lim [expr {$a + 1000}]
7664 if {[catch {
7665 while {[incr a] <= $lim} {
7666 if {[info exists arcend($a)]} {
7667 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
7668 } else {
7669 puts $f [list $arcstart($a) {} $arcids($a)]
7672 } err]} {
7673 catch {close $f}
7674 catch {file delete $allccache}
7675 #puts "writing cache failed ($err)"
7676 return 0
7678 set cachearc [expr {$a - 1}]
7679 if {$a > $cachedarcs} {
7680 puts $f "1"
7681 close $f
7682 return 0
7684 return 1
7687 proc savecache {} {
7688 global nextarc cachedarcs cachearc allccache
7690 if {$nextarc == $cachedarcs} return
7691 set cachearc 0
7692 set cachedarcs $nextarc
7693 catch {
7694 set f [open $allccache w]
7695 puts $f [list 1 $cachedarcs]
7696 run writecache $f
7700 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
7701 # or 0 if neither is true.
7702 proc anc_or_desc {a b} {
7703 global arcout arcstart arcend arcnos cached_isanc
7705 if {$arcnos($a) eq $arcnos($b)} {
7706 # Both are on the same arc(s); either both are the same BMP,
7707 # or if one is not a BMP, the other is also not a BMP or is
7708 # the BMP at end of the arc (and it only has 1 incoming arc).
7709 # Or both can be BMPs with no incoming arcs.
7710 if {$a eq $b || $arcnos($a) eq {}} {
7711 return 0
7713 # assert {[llength $arcnos($a)] == 1}
7714 set arc [lindex $arcnos($a) 0]
7715 set i [lsearch -exact $arcids($arc) $a]
7716 set j [lsearch -exact $arcids($arc) $b]
7717 if {$i < 0 || $i > $j} {
7718 return 1
7719 } else {
7720 return -1
7724 if {![info exists arcout($a)]} {
7725 set arc [lindex $arcnos($a) 0]
7726 if {[info exists arcend($arc)]} {
7727 set aend $arcend($arc)
7728 } else {
7729 set aend {}
7731 set a $arcstart($arc)
7732 } else {
7733 set aend $a
7735 if {![info exists arcout($b)]} {
7736 set arc [lindex $arcnos($b) 0]
7737 if {[info exists arcend($arc)]} {
7738 set bend $arcend($arc)
7739 } else {
7740 set bend {}
7742 set b $arcstart($arc)
7743 } else {
7744 set bend $b
7746 if {$a eq $bend} {
7747 return 1
7749 if {$b eq $aend} {
7750 return -1
7752 if {[info exists cached_isanc($a,$bend)]} {
7753 if {$cached_isanc($a,$bend)} {
7754 return 1
7757 if {[info exists cached_isanc($b,$aend)]} {
7758 if {$cached_isanc($b,$aend)} {
7759 return -1
7761 if {[info exists cached_isanc($a,$bend)]} {
7762 return 0
7766 set todo [list $a $b]
7767 set anc($a) a
7768 set anc($b) b
7769 for {set i 0} {$i < [llength $todo]} {incr i} {
7770 set x [lindex $todo $i]
7771 if {$anc($x) eq {}} {
7772 continue
7774 foreach arc $arcnos($x) {
7775 set xd $arcstart($arc)
7776 if {$xd eq $bend} {
7777 set cached_isanc($a,$bend) 1
7778 set cached_isanc($b,$aend) 0
7779 return 1
7780 } elseif {$xd eq $aend} {
7781 set cached_isanc($b,$aend) 1
7782 set cached_isanc($a,$bend) 0
7783 return -1
7785 if {![info exists anc($xd)]} {
7786 set anc($xd) $anc($x)
7787 lappend todo $xd
7788 } elseif {$anc($xd) ne $anc($x)} {
7789 set anc($xd) {}
7793 set cached_isanc($a,$bend) 0
7794 set cached_isanc($b,$aend) 0
7795 return 0
7798 # This identifies whether $desc has an ancestor that is
7799 # a growing tip of the graph and which is not an ancestor of $anc
7800 # and returns 0 if so and 1 if not.
7801 # If we subsequently discover a tag on such a growing tip, and that
7802 # turns out to be a descendent of $anc (which it could, since we
7803 # don't necessarily see children before parents), then $desc
7804 # isn't a good choice to display as a descendent tag of
7805 # $anc (since it is the descendent of another tag which is
7806 # a descendent of $anc). Similarly, $anc isn't a good choice to
7807 # display as a ancestor tag of $desc.
7809 proc is_certain {desc anc} {
7810 global arcnos arcout arcstart arcend growing problems
7812 set certain {}
7813 if {[llength $arcnos($anc)] == 1} {
7814 # tags on the same arc are certain
7815 if {$arcnos($desc) eq $arcnos($anc)} {
7816 return 1
7818 if {![info exists arcout($anc)]} {
7819 # if $anc is partway along an arc, use the start of the arc instead
7820 set a [lindex $arcnos($anc) 0]
7821 set anc $arcstart($a)
7824 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
7825 set x $desc
7826 } else {
7827 set a [lindex $arcnos($desc) 0]
7828 set x $arcend($a)
7830 if {$x == $anc} {
7831 return 1
7833 set anclist [list $x]
7834 set dl($x) 1
7835 set nnh 1
7836 set ngrowanc 0
7837 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
7838 set x [lindex $anclist $i]
7839 if {$dl($x)} {
7840 incr nnh -1
7842 set done($x) 1
7843 foreach a $arcout($x) {
7844 if {[info exists growing($a)]} {
7845 if {![info exists growanc($x)] && $dl($x)} {
7846 set growanc($x) 1
7847 incr ngrowanc
7849 } else {
7850 set y $arcend($a)
7851 if {[info exists dl($y)]} {
7852 if {$dl($y)} {
7853 if {!$dl($x)} {
7854 set dl($y) 0
7855 if {![info exists done($y)]} {
7856 incr nnh -1
7858 if {[info exists growanc($x)]} {
7859 incr ngrowanc -1
7861 set xl [list $y]
7862 for {set k 0} {$k < [llength $xl]} {incr k} {
7863 set z [lindex $xl $k]
7864 foreach c $arcout($z) {
7865 if {[info exists arcend($c)]} {
7866 set v $arcend($c)
7867 if {[info exists dl($v)] && $dl($v)} {
7868 set dl($v) 0
7869 if {![info exists done($v)]} {
7870 incr nnh -1
7872 if {[info exists growanc($v)]} {
7873 incr ngrowanc -1
7875 lappend xl $v
7882 } elseif {$y eq $anc || !$dl($x)} {
7883 set dl($y) 0
7884 lappend anclist $y
7885 } else {
7886 set dl($y) 1
7887 lappend anclist $y
7888 incr nnh
7893 foreach x [array names growanc] {
7894 if {$dl($x)} {
7895 return 0
7897 return 0
7899 return 1
7902 proc validate_arctags {a} {
7903 global arctags idtags
7905 set i -1
7906 set na $arctags($a)
7907 foreach id $arctags($a) {
7908 incr i
7909 if {![info exists idtags($id)]} {
7910 set na [lreplace $na $i $i]
7911 incr i -1
7914 set arctags($a) $na
7917 proc validate_archeads {a} {
7918 global archeads idheads
7920 set i -1
7921 set na $archeads($a)
7922 foreach id $archeads($a) {
7923 incr i
7924 if {![info exists idheads($id)]} {
7925 set na [lreplace $na $i $i]
7926 incr i -1
7929 set archeads($a) $na
7932 # Return the list of IDs that have tags that are descendents of id,
7933 # ignoring IDs that are descendents of IDs already reported.
7934 proc desctags {id} {
7935 global arcnos arcstart arcids arctags idtags allparents
7936 global growing cached_dtags
7938 if {![info exists allparents($id)]} {
7939 return {}
7941 set t1 [clock clicks -milliseconds]
7942 set argid $id
7943 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7944 # part-way along an arc; check that arc first
7945 set a [lindex $arcnos($id) 0]
7946 if {$arctags($a) ne {}} {
7947 validate_arctags $a
7948 set i [lsearch -exact $arcids($a) $id]
7949 set tid {}
7950 foreach t $arctags($a) {
7951 set j [lsearch -exact $arcids($a) $t]
7952 if {$j >= $i} break
7953 set tid $t
7955 if {$tid ne {}} {
7956 return $tid
7959 set id $arcstart($a)
7960 if {[info exists idtags($id)]} {
7961 return $id
7964 if {[info exists cached_dtags($id)]} {
7965 return $cached_dtags($id)
7968 set origid $id
7969 set todo [list $id]
7970 set queued($id) 1
7971 set nc 1
7972 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7973 set id [lindex $todo $i]
7974 set done($id) 1
7975 set ta [info exists hastaggedancestor($id)]
7976 if {!$ta} {
7977 incr nc -1
7979 # ignore tags on starting node
7980 if {!$ta && $i > 0} {
7981 if {[info exists idtags($id)]} {
7982 set tagloc($id) $id
7983 set ta 1
7984 } elseif {[info exists cached_dtags($id)]} {
7985 set tagloc($id) $cached_dtags($id)
7986 set ta 1
7989 foreach a $arcnos($id) {
7990 set d $arcstart($a)
7991 if {!$ta && $arctags($a) ne {}} {
7992 validate_arctags $a
7993 if {$arctags($a) ne {}} {
7994 lappend tagloc($id) [lindex $arctags($a) end]
7997 if {$ta || $arctags($a) ne {}} {
7998 set tomark [list $d]
7999 for {set j 0} {$j < [llength $tomark]} {incr j} {
8000 set dd [lindex $tomark $j]
8001 if {![info exists hastaggedancestor($dd)]} {
8002 if {[info exists done($dd)]} {
8003 foreach b $arcnos($dd) {
8004 lappend tomark $arcstart($b)
8006 if {[info exists tagloc($dd)]} {
8007 unset tagloc($dd)
8009 } elseif {[info exists queued($dd)]} {
8010 incr nc -1
8012 set hastaggedancestor($dd) 1
8016 if {![info exists queued($d)]} {
8017 lappend todo $d
8018 set queued($d) 1
8019 if {![info exists hastaggedancestor($d)]} {
8020 incr nc
8025 set tags {}
8026 foreach id [array names tagloc] {
8027 if {![info exists hastaggedancestor($id)]} {
8028 foreach t $tagloc($id) {
8029 if {[lsearch -exact $tags $t] < 0} {
8030 lappend tags $t
8035 set t2 [clock clicks -milliseconds]
8036 set loopix $i
8038 # remove tags that are descendents of other tags
8039 for {set i 0} {$i < [llength $tags]} {incr i} {
8040 set a [lindex $tags $i]
8041 for {set j 0} {$j < $i} {incr j} {
8042 set b [lindex $tags $j]
8043 set r [anc_or_desc $a $b]
8044 if {$r == 1} {
8045 set tags [lreplace $tags $j $j]
8046 incr j -1
8047 incr i -1
8048 } elseif {$r == -1} {
8049 set tags [lreplace $tags $i $i]
8050 incr i -1
8051 break
8056 if {[array names growing] ne {}} {
8057 # graph isn't finished, need to check if any tag could get
8058 # eclipsed by another tag coming later. Simply ignore any
8059 # tags that could later get eclipsed.
8060 set ctags {}
8061 foreach t $tags {
8062 if {[is_certain $t $origid]} {
8063 lappend ctags $t
8066 if {$tags eq $ctags} {
8067 set cached_dtags($origid) $tags
8068 } else {
8069 set tags $ctags
8071 } else {
8072 set cached_dtags($origid) $tags
8074 set t3 [clock clicks -milliseconds]
8075 if {0 && $t3 - $t1 >= 100} {
8076 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8077 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8079 return $tags
8082 proc anctags {id} {
8083 global arcnos arcids arcout arcend arctags idtags allparents
8084 global growing cached_atags
8086 if {![info exists allparents($id)]} {
8087 return {}
8089 set t1 [clock clicks -milliseconds]
8090 set argid $id
8091 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8092 # part-way along an arc; check that arc first
8093 set a [lindex $arcnos($id) 0]
8094 if {$arctags($a) ne {}} {
8095 validate_arctags $a
8096 set i [lsearch -exact $arcids($a) $id]
8097 foreach t $arctags($a) {
8098 set j [lsearch -exact $arcids($a) $t]
8099 if {$j > $i} {
8100 return $t
8104 if {![info exists arcend($a)]} {
8105 return {}
8107 set id $arcend($a)
8108 if {[info exists idtags($id)]} {
8109 return $id
8112 if {[info exists cached_atags($id)]} {
8113 return $cached_atags($id)
8116 set origid $id
8117 set todo [list $id]
8118 set queued($id) 1
8119 set taglist {}
8120 set nc 1
8121 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8122 set id [lindex $todo $i]
8123 set done($id) 1
8124 set td [info exists hastaggeddescendent($id)]
8125 if {!$td} {
8126 incr nc -1
8128 # ignore tags on starting node
8129 if {!$td && $i > 0} {
8130 if {[info exists idtags($id)]} {
8131 set tagloc($id) $id
8132 set td 1
8133 } elseif {[info exists cached_atags($id)]} {
8134 set tagloc($id) $cached_atags($id)
8135 set td 1
8138 foreach a $arcout($id) {
8139 if {!$td && $arctags($a) ne {}} {
8140 validate_arctags $a
8141 if {$arctags($a) ne {}} {
8142 lappend tagloc($id) [lindex $arctags($a) 0]
8145 if {![info exists arcend($a)]} continue
8146 set d $arcend($a)
8147 if {$td || $arctags($a) ne {}} {
8148 set tomark [list $d]
8149 for {set j 0} {$j < [llength $tomark]} {incr j} {
8150 set dd [lindex $tomark $j]
8151 if {![info exists hastaggeddescendent($dd)]} {
8152 if {[info exists done($dd)]} {
8153 foreach b $arcout($dd) {
8154 if {[info exists arcend($b)]} {
8155 lappend tomark $arcend($b)
8158 if {[info exists tagloc($dd)]} {
8159 unset tagloc($dd)
8161 } elseif {[info exists queued($dd)]} {
8162 incr nc -1
8164 set hastaggeddescendent($dd) 1
8168 if {![info exists queued($d)]} {
8169 lappend todo $d
8170 set queued($d) 1
8171 if {![info exists hastaggeddescendent($d)]} {
8172 incr nc
8177 set t2 [clock clicks -milliseconds]
8178 set loopix $i
8179 set tags {}
8180 foreach id [array names tagloc] {
8181 if {![info exists hastaggeddescendent($id)]} {
8182 foreach t $tagloc($id) {
8183 if {[lsearch -exact $tags $t] < 0} {
8184 lappend tags $t
8190 # remove tags that are ancestors of other tags
8191 for {set i 0} {$i < [llength $tags]} {incr i} {
8192 set a [lindex $tags $i]
8193 for {set j 0} {$j < $i} {incr j} {
8194 set b [lindex $tags $j]
8195 set r [anc_or_desc $a $b]
8196 if {$r == -1} {
8197 set tags [lreplace $tags $j $j]
8198 incr j -1
8199 incr i -1
8200 } elseif {$r == 1} {
8201 set tags [lreplace $tags $i $i]
8202 incr i -1
8203 break
8208 if {[array names growing] ne {}} {
8209 # graph isn't finished, need to check if any tag could get
8210 # eclipsed by another tag coming later. Simply ignore any
8211 # tags that could later get eclipsed.
8212 set ctags {}
8213 foreach t $tags {
8214 if {[is_certain $origid $t]} {
8215 lappend ctags $t
8218 if {$tags eq $ctags} {
8219 set cached_atags($origid) $tags
8220 } else {
8221 set tags $ctags
8223 } else {
8224 set cached_atags($origid) $tags
8226 set t3 [clock clicks -milliseconds]
8227 if {0 && $t3 - $t1 >= 100} {
8228 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8229 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8231 return $tags
8234 # Return the list of IDs that have heads that are descendents of id,
8235 # including id itself if it has a head.
8236 proc descheads {id} {
8237 global arcnos arcstart arcids archeads idheads cached_dheads
8238 global allparents
8240 if {![info exists allparents($id)]} {
8241 return {}
8243 set aret {}
8244 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8245 # part-way along an arc; check it first
8246 set a [lindex $arcnos($id) 0]
8247 if {$archeads($a) ne {}} {
8248 validate_archeads $a
8249 set i [lsearch -exact $arcids($a) $id]
8250 foreach t $archeads($a) {
8251 set j [lsearch -exact $arcids($a) $t]
8252 if {$j > $i} break
8253 lappend aret $t
8256 set id $arcstart($a)
8258 set origid $id
8259 set todo [list $id]
8260 set seen($id) 1
8261 set ret {}
8262 for {set i 0} {$i < [llength $todo]} {incr i} {
8263 set id [lindex $todo $i]
8264 if {[info exists cached_dheads($id)]} {
8265 set ret [concat $ret $cached_dheads($id)]
8266 } else {
8267 if {[info exists idheads($id)]} {
8268 lappend ret $id
8270 foreach a $arcnos($id) {
8271 if {$archeads($a) ne {}} {
8272 validate_archeads $a
8273 if {$archeads($a) ne {}} {
8274 set ret [concat $ret $archeads($a)]
8277 set d $arcstart($a)
8278 if {![info exists seen($d)]} {
8279 lappend todo $d
8280 set seen($d) 1
8285 set ret [lsort -unique $ret]
8286 set cached_dheads($origid) $ret
8287 return [concat $ret $aret]
8290 proc addedtag {id} {
8291 global arcnos arcout cached_dtags cached_atags
8293 if {![info exists arcnos($id)]} return
8294 if {![info exists arcout($id)]} {
8295 recalcarc [lindex $arcnos($id) 0]
8297 catch {unset cached_dtags}
8298 catch {unset cached_atags}
8301 proc addedhead {hid head} {
8302 global arcnos arcout cached_dheads
8304 if {![info exists arcnos($hid)]} return
8305 if {![info exists arcout($hid)]} {
8306 recalcarc [lindex $arcnos($hid) 0]
8308 catch {unset cached_dheads}
8311 proc removedhead {hid head} {
8312 global cached_dheads
8314 catch {unset cached_dheads}
8317 proc movedhead {hid head} {
8318 global arcnos arcout cached_dheads
8320 if {![info exists arcnos($hid)]} return
8321 if {![info exists arcout($hid)]} {
8322 recalcarc [lindex $arcnos($hid) 0]
8324 catch {unset cached_dheads}
8327 proc changedrefs {} {
8328 global cached_dheads cached_dtags cached_atags
8329 global arctags archeads arcnos arcout idheads idtags
8331 foreach id [concat [array names idheads] [array names idtags]] {
8332 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
8333 set a [lindex $arcnos($id) 0]
8334 if {![info exists donearc($a)]} {
8335 recalcarc $a
8336 set donearc($a) 1
8340 catch {unset cached_dtags}
8341 catch {unset cached_atags}
8342 catch {unset cached_dheads}
8345 proc rereadrefs {} {
8346 global idtags idheads idotherrefs mainhead
8348 set refids [concat [array names idtags] \
8349 [array names idheads] [array names idotherrefs]]
8350 foreach id $refids {
8351 if {![info exists ref($id)]} {
8352 set ref($id) [listrefs $id]
8355 set oldmainhead $mainhead
8356 readrefs
8357 changedrefs
8358 set refids [lsort -unique [concat $refids [array names idtags] \
8359 [array names idheads] [array names idotherrefs]]]
8360 foreach id $refids {
8361 set v [listrefs $id]
8362 if {![info exists ref($id)] || $ref($id) != $v ||
8363 ($id eq $oldmainhead && $id ne $mainhead) ||
8364 ($id eq $mainhead && $id ne $oldmainhead)} {
8365 redrawtags $id
8368 run refill_reflist
8371 proc listrefs {id} {
8372 global idtags idheads idotherrefs
8374 set x {}
8375 if {[info exists idtags($id)]} {
8376 set x $idtags($id)
8378 set y {}
8379 if {[info exists idheads($id)]} {
8380 set y $idheads($id)
8382 set z {}
8383 if {[info exists idotherrefs($id)]} {
8384 set z $idotherrefs($id)
8386 return [list $x $y $z]
8389 proc showtag {tag isnew} {
8390 global ctext tagcontents tagids linknum tagobjid
8392 if {$isnew} {
8393 addtohistory [list showtag $tag 0]
8395 $ctext conf -state normal
8396 clear_ctext
8397 settabs 0
8398 set linknum 0
8399 if {![info exists tagcontents($tag)]} {
8400 catch {
8401 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
8404 if {[info exists tagcontents($tag)]} {
8405 set text $tagcontents($tag)
8406 } else {
8407 set text "Tag: $tag\nId: $tagids($tag)"
8409 appendwithlinks $text {}
8410 $ctext conf -state disabled
8411 init_flist {}
8414 proc doquit {} {
8415 global stopped
8416 set stopped 100
8417 savestuff .
8418 destroy .
8421 proc mkfontdisp {font top which} {
8422 global fontattr fontpref $font
8424 set fontpref($font) [set $font]
8425 button $top.${font}but -text $which -font optionfont \
8426 -command [list choosefont $font $which]
8427 label $top.$font -relief flat -font $font \
8428 -text $fontattr($font,family) -justify left
8429 grid x $top.${font}but $top.$font -sticky w
8432 proc choosefont {font which} {
8433 global fontparam fontlist fonttop fontattr
8435 set fontparam(which) $which
8436 set fontparam(font) $font
8437 set fontparam(family) [font actual $font -family]
8438 set fontparam(size) $fontattr($font,size)
8439 set fontparam(weight) $fontattr($font,weight)
8440 set fontparam(slant) $fontattr($font,slant)
8441 set top .gitkfont
8442 set fonttop $top
8443 if {![winfo exists $top]} {
8444 font create sample
8445 eval font config sample [font actual $font]
8446 toplevel $top
8447 wm title $top "Gitk font chooser"
8448 label $top.l -textvariable fontparam(which) -font uifont
8449 pack $top.l -side top
8450 set fontlist [lsort [font families]]
8451 frame $top.f
8452 listbox $top.f.fam -listvariable fontlist \
8453 -yscrollcommand [list $top.f.sb set]
8454 bind $top.f.fam <<ListboxSelect>> selfontfam
8455 scrollbar $top.f.sb -command [list $top.f.fam yview]
8456 pack $top.f.sb -side right -fill y
8457 pack $top.f.fam -side left -fill both -expand 1
8458 pack $top.f -side top -fill both -expand 1
8459 frame $top.g
8460 spinbox $top.g.size -from 4 -to 40 -width 4 \
8461 -textvariable fontparam(size) \
8462 -validatecommand {string is integer -strict %s}
8463 checkbutton $top.g.bold -padx 5 \
8464 -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \
8465 -variable fontparam(weight) -onvalue bold -offvalue normal
8466 checkbutton $top.g.ital -padx 5 \
8467 -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0 \
8468 -variable fontparam(slant) -onvalue italic -offvalue roman
8469 pack $top.g.size $top.g.bold $top.g.ital -side left
8470 pack $top.g -side top
8471 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
8472 -background white
8473 $top.c create text 100 25 -anchor center -text $which -font sample \
8474 -fill black -tags text
8475 bind $top.c <Configure> [list centertext $top.c]
8476 pack $top.c -side top -fill x
8477 frame $top.buts
8478 button $top.buts.ok -text "OK" -command fontok -default active \
8479 -font uifont
8480 button $top.buts.can -text "Cancel" -command fontcan -default normal \
8481 -font uifont
8482 grid $top.buts.ok $top.buts.can
8483 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8484 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8485 pack $top.buts -side bottom -fill x
8486 trace add variable fontparam write chg_fontparam
8487 } else {
8488 raise $top
8489 $top.c itemconf text -text $which
8491 set i [lsearch -exact $fontlist $fontparam(family)]
8492 if {$i >= 0} {
8493 $top.f.fam selection set $i
8494 $top.f.fam see $i
8498 proc centertext {w} {
8499 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
8502 proc fontok {} {
8503 global fontparam fontpref prefstop
8505 set f $fontparam(font)
8506 set fontpref($f) [list $fontparam(family) $fontparam(size)]
8507 if {$fontparam(weight) eq "bold"} {
8508 lappend fontpref($f) "bold"
8510 if {$fontparam(slant) eq "italic"} {
8511 lappend fontpref($f) "italic"
8513 set w $prefstop.$f
8514 $w conf -text $fontparam(family) -font $fontpref($f)
8516 fontcan
8519 proc fontcan {} {
8520 global fonttop fontparam
8522 if {[info exists fonttop]} {
8523 catch {destroy $fonttop}
8524 catch {font delete sample}
8525 unset fonttop
8526 unset fontparam
8530 proc selfontfam {} {
8531 global fonttop fontparam
8533 set i [$fonttop.f.fam curselection]
8534 if {$i ne {}} {
8535 set fontparam(family) [$fonttop.f.fam get $i]
8539 proc chg_fontparam {v sub op} {
8540 global fontparam
8542 font config sample -$sub $fontparam($sub)
8545 proc doprefs {} {
8546 global maxwidth maxgraphpct
8547 global oldprefs prefstop showneartags showlocalchanges
8548 global bgcolor fgcolor ctext diffcolors selectbgcolor
8549 global uifont tabstop limitdiffs
8551 set top .gitkprefs
8552 set prefstop $top
8553 if {[winfo exists $top]} {
8554 raise $top
8555 return
8557 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8558 limitdiffs tabstop} {
8559 set oldprefs($v) [set $v]
8561 toplevel $top
8562 wm title $top "Gitk preferences"
8563 label $top.ldisp -text "Commit list display options"
8564 $top.ldisp configure -font uifont
8565 grid $top.ldisp - -sticky w -pady 10
8566 label $top.spacer -text " "
8567 label $top.maxwidthl -text "Maximum graph width (lines)" \
8568 -font optionfont
8569 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
8570 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
8571 label $top.maxpctl -text "Maximum graph width (% of pane)" \
8572 -font optionfont
8573 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
8574 grid x $top.maxpctl $top.maxpct -sticky w
8575 frame $top.showlocal
8576 label $top.showlocal.l -text "Show local changes" -font optionfont
8577 checkbutton $top.showlocal.b -variable showlocalchanges
8578 pack $top.showlocal.b $top.showlocal.l -side left
8579 grid x $top.showlocal -sticky w
8581 label $top.ddisp -text "Diff display options"
8582 $top.ddisp configure -font uifont
8583 grid $top.ddisp - -sticky w -pady 10
8584 label $top.tabstopl -text "Tab spacing" -font optionfont
8585 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
8586 grid x $top.tabstopl $top.tabstop -sticky w
8587 frame $top.ntag
8588 label $top.ntag.l -text "Display nearby tags" -font optionfont
8589 checkbutton $top.ntag.b -variable showneartags
8590 pack $top.ntag.b $top.ntag.l -side left
8591 grid x $top.ntag -sticky w
8592 frame $top.ldiff
8593 label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont
8594 checkbutton $top.ldiff.b -variable limitdiffs
8595 pack $top.ldiff.b $top.ldiff.l -side left
8596 grid x $top.ldiff -sticky w
8598 label $top.cdisp -text "Colors: press to choose"
8599 $top.cdisp configure -font uifont
8600 grid $top.cdisp - -sticky w -pady 10
8601 label $top.bg -padx 40 -relief sunk -background $bgcolor
8602 button $top.bgbut -text "Background" -font optionfont \
8603 -command [list choosecolor bgcolor 0 $top.bg background setbg]
8604 grid x $top.bgbut $top.bg -sticky w
8605 label $top.fg -padx 40 -relief sunk -background $fgcolor
8606 button $top.fgbut -text "Foreground" -font optionfont \
8607 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
8608 grid x $top.fgbut $top.fg -sticky w
8609 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
8610 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
8611 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
8612 [list $ctext tag conf d0 -foreground]]
8613 grid x $top.diffoldbut $top.diffold -sticky w
8614 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
8615 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
8616 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
8617 [list $ctext tag conf d1 -foreground]]
8618 grid x $top.diffnewbut $top.diffnew -sticky w
8619 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
8620 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
8621 -command [list choosecolor diffcolors 2 $top.hunksep \
8622 "diff hunk header" \
8623 [list $ctext tag conf hunksep -foreground]]
8624 grid x $top.hunksepbut $top.hunksep -sticky w
8625 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
8626 button $top.selbgbut -text "Select bg" -font optionfont \
8627 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
8628 grid x $top.selbgbut $top.selbgsep -sticky w
8630 label $top.cfont -text "Fonts: press to choose"
8631 $top.cfont configure -font uifont
8632 grid $top.cfont - -sticky w -pady 10
8633 mkfontdisp mainfont $top "Main font"
8634 mkfontdisp textfont $top "Diff display font"
8635 mkfontdisp uifont $top "User interface font"
8637 frame $top.buts
8638 button $top.buts.ok -text "OK" -command prefsok -default active
8639 $top.buts.ok configure -font uifont
8640 button $top.buts.can -text "Cancel" -command prefscan -default normal
8641 $top.buts.can configure -font uifont
8642 grid $top.buts.ok $top.buts.can
8643 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8644 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8645 grid $top.buts - - -pady 10 -sticky ew
8646 bind $top <Visibility> "focus $top.buts.ok"
8649 proc choosecolor {v vi w x cmd} {
8650 global $v
8652 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
8653 -title "Gitk: choose color for $x"]
8654 if {$c eq {}} return
8655 $w conf -background $c
8656 lset $v $vi $c
8657 eval $cmd $c
8660 proc setselbg {c} {
8661 global bglist cflist
8662 foreach w $bglist {
8663 $w configure -selectbackground $c
8665 $cflist tag configure highlight \
8666 -background [$cflist cget -selectbackground]
8667 allcanvs itemconf secsel -fill $c
8670 proc setbg {c} {
8671 global bglist
8673 foreach w $bglist {
8674 $w conf -background $c
8678 proc setfg {c} {
8679 global fglist canv
8681 foreach w $fglist {
8682 $w conf -foreground $c
8684 allcanvs itemconf text -fill $c
8685 $canv itemconf circle -outline $c
8688 proc prefscan {} {
8689 global oldprefs prefstop
8691 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8692 limitdiffs tabstop} {
8693 global $v
8694 set $v $oldprefs($v)
8696 catch {destroy $prefstop}
8697 unset prefstop
8698 fontcan
8701 proc prefsok {} {
8702 global maxwidth maxgraphpct
8703 global oldprefs prefstop showneartags showlocalchanges
8704 global fontpref mainfont textfont uifont
8705 global limitdiffs treediffs
8707 catch {destroy $prefstop}
8708 unset prefstop
8709 fontcan
8710 set fontchanged 0
8711 if {$mainfont ne $fontpref(mainfont)} {
8712 set mainfont $fontpref(mainfont)
8713 parsefont mainfont $mainfont
8714 eval font configure mainfont [fontflags mainfont]
8715 eval font configure mainfontbold [fontflags mainfont 1]
8716 setcoords
8717 set fontchanged 1
8719 if {$textfont ne $fontpref(textfont)} {
8720 set textfont $fontpref(textfont)
8721 parsefont textfont $textfont
8722 eval font configure textfont [fontflags textfont]
8723 eval font configure textfontbold [fontflags textfont 1]
8725 if {$uifont ne $fontpref(uifont)} {
8726 set uifont $fontpref(uifont)
8727 parsefont uifont $uifont
8728 eval font configure uifont [fontflags uifont]
8730 settabs
8731 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
8732 if {$showlocalchanges} {
8733 doshowlocalchanges
8734 } else {
8735 dohidelocalchanges
8738 if {$limitdiffs != $oldprefs(limitdiffs)} {
8739 # treediffs elements are limited by path
8740 catch {unset treediffs}
8742 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
8743 || $maxgraphpct != $oldprefs(maxgraphpct)} {
8744 redisplay
8745 } elseif {$showneartags != $oldprefs(showneartags) ||
8746 $limitdiffs != $oldprefs(limitdiffs)} {
8747 reselectline
8751 proc formatdate {d} {
8752 global datetimeformat
8753 if {$d ne {}} {
8754 set d [clock format $d -format $datetimeformat]
8756 return $d
8759 # This list of encoding names and aliases is distilled from
8760 # http://www.iana.org/assignments/character-sets.
8761 # Not all of them are supported by Tcl.
8762 set encoding_aliases {
8763 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
8764 ISO646-US US-ASCII us IBM367 cp367 csASCII }
8765 { ISO-10646-UTF-1 csISO10646UTF1 }
8766 { ISO_646.basic:1983 ref csISO646basic1983 }
8767 { INVARIANT csINVARIANT }
8768 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
8769 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
8770 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
8771 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
8772 { NATS-DANO iso-ir-9-1 csNATSDANO }
8773 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
8774 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
8775 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
8776 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
8777 { ISO-2022-KR csISO2022KR }
8778 { EUC-KR csEUCKR }
8779 { ISO-2022-JP csISO2022JP }
8780 { ISO-2022-JP-2 csISO2022JP2 }
8781 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
8782 csISO13JISC6220jp }
8783 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
8784 { IT iso-ir-15 ISO646-IT csISO15Italian }
8785 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
8786 { ES iso-ir-17 ISO646-ES csISO17Spanish }
8787 { greek7-old iso-ir-18 csISO18Greek7Old }
8788 { latin-greek iso-ir-19 csISO19LatinGreek }
8789 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
8790 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
8791 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
8792 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
8793 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
8794 { BS_viewdata iso-ir-47 csISO47BSViewdata }
8795 { INIS iso-ir-49 csISO49INIS }
8796 { INIS-8 iso-ir-50 csISO50INIS8 }
8797 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
8798 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
8799 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
8800 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
8801 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
8802 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
8803 csISO60Norwegian1 }
8804 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
8805 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
8806 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
8807 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
8808 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
8809 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
8810 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
8811 { greek7 iso-ir-88 csISO88Greek7 }
8812 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
8813 { iso-ir-90 csISO90 }
8814 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
8815 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
8816 csISO92JISC62991984b }
8817 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
8818 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
8819 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
8820 csISO95JIS62291984handadd }
8821 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
8822 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
8823 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
8824 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
8825 CP819 csISOLatin1 }
8826 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
8827 { T.61-7bit iso-ir-102 csISO102T617bit }
8828 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
8829 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
8830 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
8831 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
8832 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
8833 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
8834 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
8835 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
8836 arabic csISOLatinArabic }
8837 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
8838 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
8839 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
8840 greek greek8 csISOLatinGreek }
8841 { T.101-G2 iso-ir-128 csISO128T101G2 }
8842 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
8843 csISOLatinHebrew }
8844 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
8845 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
8846 { CSN_369103 iso-ir-139 csISO139CSN369103 }
8847 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
8848 { ISO_6937-2-add iso-ir-142 csISOTextComm }
8849 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
8850 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
8851 csISOLatinCyrillic }
8852 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
8853 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
8854 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
8855 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
8856 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
8857 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
8858 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
8859 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
8860 { ISO_10367-box iso-ir-155 csISO10367Box }
8861 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
8862 { latin-lap lap iso-ir-158 csISO158Lap }
8863 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
8864 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
8865 { us-dk csUSDK }
8866 { dk-us csDKUS }
8867 { JIS_X0201 X0201 csHalfWidthKatakana }
8868 { KSC5636 ISO646-KR csKSC5636 }
8869 { ISO-10646-UCS-2 csUnicode }
8870 { ISO-10646-UCS-4 csUCS4 }
8871 { DEC-MCS dec csDECMCS }
8872 { hp-roman8 roman8 r8 csHPRoman8 }
8873 { macintosh mac csMacintosh }
8874 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
8875 csIBM037 }
8876 { IBM038 EBCDIC-INT cp038 csIBM038 }
8877 { IBM273 CP273 csIBM273 }
8878 { IBM274 EBCDIC-BE CP274 csIBM274 }
8879 { IBM275 EBCDIC-BR cp275 csIBM275 }
8880 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
8881 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
8882 { IBM280 CP280 ebcdic-cp-it csIBM280 }
8883 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
8884 { IBM284 CP284 ebcdic-cp-es csIBM284 }
8885 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
8886 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
8887 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
8888 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
8889 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
8890 { IBM424 cp424 ebcdic-cp-he csIBM424 }
8891 { IBM437 cp437 437 csPC8CodePage437 }
8892 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
8893 { IBM775 cp775 csPC775Baltic }
8894 { IBM850 cp850 850 csPC850Multilingual }
8895 { IBM851 cp851 851 csIBM851 }
8896 { IBM852 cp852 852 csPCp852 }
8897 { IBM855 cp855 855 csIBM855 }
8898 { IBM857 cp857 857 csIBM857 }
8899 { IBM860 cp860 860 csIBM860 }
8900 { IBM861 cp861 861 cp-is csIBM861 }
8901 { IBM862 cp862 862 csPC862LatinHebrew }
8902 { IBM863 cp863 863 csIBM863 }
8903 { IBM864 cp864 csIBM864 }
8904 { IBM865 cp865 865 csIBM865 }
8905 { IBM866 cp866 866 csIBM866 }
8906 { IBM868 CP868 cp-ar csIBM868 }
8907 { IBM869 cp869 869 cp-gr csIBM869 }
8908 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
8909 { IBM871 CP871 ebcdic-cp-is csIBM871 }
8910 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
8911 { IBM891 cp891 csIBM891 }
8912 { IBM903 cp903 csIBM903 }
8913 { IBM904 cp904 904 csIBBM904 }
8914 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
8915 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
8916 { IBM1026 CP1026 csIBM1026 }
8917 { EBCDIC-AT-DE csIBMEBCDICATDE }
8918 { EBCDIC-AT-DE-A csEBCDICATDEA }
8919 { EBCDIC-CA-FR csEBCDICCAFR }
8920 { EBCDIC-DK-NO csEBCDICDKNO }
8921 { EBCDIC-DK-NO-A csEBCDICDKNOA }
8922 { EBCDIC-FI-SE csEBCDICFISE }
8923 { EBCDIC-FI-SE-A csEBCDICFISEA }
8924 { EBCDIC-FR csEBCDICFR }
8925 { EBCDIC-IT csEBCDICIT }
8926 { EBCDIC-PT csEBCDICPT }
8927 { EBCDIC-ES csEBCDICES }
8928 { EBCDIC-ES-A csEBCDICESA }
8929 { EBCDIC-ES-S csEBCDICESS }
8930 { EBCDIC-UK csEBCDICUK }
8931 { EBCDIC-US csEBCDICUS }
8932 { UNKNOWN-8BIT csUnknown8BiT }
8933 { MNEMONIC csMnemonic }
8934 { MNEM csMnem }
8935 { VISCII csVISCII }
8936 { VIQR csVIQR }
8937 { KOI8-R csKOI8R }
8938 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
8939 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
8940 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
8941 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
8942 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
8943 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
8944 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
8945 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
8946 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
8947 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
8948 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
8949 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
8950 { IBM1047 IBM-1047 }
8951 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
8952 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
8953 { UNICODE-1-1 csUnicode11 }
8954 { CESU-8 csCESU-8 }
8955 { BOCU-1 csBOCU-1 }
8956 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
8957 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
8958 l8 }
8959 { ISO-8859-15 ISO_8859-15 Latin-9 }
8960 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
8961 { GBK CP936 MS936 windows-936 }
8962 { JIS_Encoding csJISEncoding }
8963 { Shift_JIS MS_Kanji csShiftJIS }
8964 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
8965 EUC-JP }
8966 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
8967 { ISO-10646-UCS-Basic csUnicodeASCII }
8968 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
8969 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
8970 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
8971 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
8972 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
8973 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
8974 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
8975 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
8976 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
8977 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
8978 { Adobe-Standard-Encoding csAdobeStandardEncoding }
8979 { Ventura-US csVenturaUS }
8980 { Ventura-International csVenturaInternational }
8981 { PC8-Danish-Norwegian csPC8DanishNorwegian }
8982 { PC8-Turkish csPC8Turkish }
8983 { IBM-Symbols csIBMSymbols }
8984 { IBM-Thai csIBMThai }
8985 { HP-Legal csHPLegal }
8986 { HP-Pi-font csHPPiFont }
8987 { HP-Math8 csHPMath8 }
8988 { Adobe-Symbol-Encoding csHPPSMath }
8989 { HP-DeskTop csHPDesktop }
8990 { Ventura-Math csVenturaMath }
8991 { Microsoft-Publishing csMicrosoftPublishing }
8992 { Windows-31J csWindows31J }
8993 { GB2312 csGB2312 }
8994 { Big5 csBig5 }
8997 proc tcl_encoding {enc} {
8998 global encoding_aliases
8999 set names [encoding names]
9000 set lcnames [string tolower $names]
9001 set enc [string tolower $enc]
9002 set i [lsearch -exact $lcnames $enc]
9003 if {$i < 0} {
9004 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9005 if {[regsub {^iso[-_]} $enc iso encx]} {
9006 set i [lsearch -exact $lcnames $encx]
9009 if {$i < 0} {
9010 foreach l $encoding_aliases {
9011 set ll [string tolower $l]
9012 if {[lsearch -exact $ll $enc] < 0} continue
9013 # look through the aliases for one that tcl knows about
9014 foreach e $ll {
9015 set i [lsearch -exact $lcnames $e]
9016 if {$i < 0} {
9017 if {[regsub {^iso[-_]} $e iso ex]} {
9018 set i [lsearch -exact $lcnames $ex]
9021 if {$i >= 0} break
9023 break
9026 if {$i >= 0} {
9027 return [lindex $names $i]
9029 return {}
9032 # First check that Tcl/Tk is recent enough
9033 if {[catch {package require Tk 8.4} err]} {
9034 show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9035 Gitk requires at least Tcl/Tk 8.4."
9036 exit 1
9039 # defaults...
9040 set datemode 0
9041 set wrcomcmd "git diff-tree --stdin -p --pretty"
9043 set gitencoding {}
9044 catch {
9045 set gitencoding [exec git config --get i18n.commitencoding]
9047 if {$gitencoding == ""} {
9048 set gitencoding "utf-8"
9050 set tclencoding [tcl_encoding $gitencoding]
9051 if {$tclencoding == {}} {
9052 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9055 set mainfont {Helvetica 9}
9056 set textfont {Courier 9}
9057 set uifont {Helvetica 9 bold}
9058 set tabstop 8
9059 set findmergefiles 0
9060 set maxgraphpct 50
9061 set maxwidth 16
9062 set revlistorder 0
9063 set fastdate 0
9064 set uparrowlen 5
9065 set downarrowlen 5
9066 set mingaplen 100
9067 set cmitmode "patch"
9068 set wrapcomment "none"
9069 set showneartags 1
9070 set maxrefs 20
9071 set maxlinelen 200
9072 set showlocalchanges 1
9073 set limitdiffs 1
9074 set datetimeformat "%Y-%m-%d %H:%M:%S"
9076 set colors {green red blue magenta darkgrey brown orange}
9077 set bgcolor white
9078 set fgcolor black
9079 set diffcolors {red "#00a000" blue}
9080 set diffcontext 3
9081 set selectbgcolor gray85
9083 catch {source ~/.gitk}
9085 font create optionfont -family sans-serif -size -12
9087 parsefont mainfont $mainfont
9088 eval font create mainfont [fontflags mainfont]
9089 eval font create mainfontbold [fontflags mainfont 1]
9091 parsefont textfont $textfont
9092 eval font create textfont [fontflags textfont]
9093 eval font create textfontbold [fontflags textfont 1]
9095 parsefont uifont $uifont
9096 eval font create uifont [fontflags uifont]
9098 # check that we can find a .git directory somewhere...
9099 if {[catch {set gitdir [gitdir]}]} {
9100 show_error {} . "Cannot find a git repository here."
9101 exit 1
9103 if {![file isdirectory $gitdir]} {
9104 show_error {} . "Cannot find the git directory \"$gitdir\"."
9105 exit 1
9108 set mergeonly 0
9109 set revtreeargs {}
9110 set cmdline_files {}
9111 set i 0
9112 foreach arg $argv {
9113 switch -- $arg {
9114 "" { }
9115 "-d" { set datemode 1 }
9116 "--merge" {
9117 set mergeonly 1
9118 lappend revtreeargs $arg
9120 "--" {
9121 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9122 break
9124 default {
9125 lappend revtreeargs $arg
9128 incr i
9131 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9132 # no -- on command line, but some arguments (other than -d)
9133 if {[catch {
9134 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9135 set cmdline_files [split $f "\n"]
9136 set n [llength $cmdline_files]
9137 set revtreeargs [lrange $revtreeargs 0 end-$n]
9138 # Unfortunately git rev-parse doesn't produce an error when
9139 # something is both a revision and a filename. To be consistent
9140 # with git log and git rev-list, check revtreeargs for filenames.
9141 foreach arg $revtreeargs {
9142 if {[file exists $arg]} {
9143 show_error {} . "Ambiguous argument '$arg': both revision\
9144 and filename"
9145 exit 1
9148 } err]} {
9149 # unfortunately we get both stdout and stderr in $err,
9150 # so look for "fatal:".
9151 set i [string first "fatal:" $err]
9152 if {$i > 0} {
9153 set err [string range $err [expr {$i + 6}] end]
9155 show_error {} . "Bad arguments to gitk:\n$err"
9156 exit 1
9160 if {$mergeonly} {
9161 # find the list of unmerged files
9162 set mlist {}
9163 set nr_unmerged 0
9164 if {[catch {
9165 set fd [open "| git ls-files -u" r]
9166 } err]} {
9167 show_error {} . "Couldn't get list of unmerged files: $err"
9168 exit 1
9170 while {[gets $fd line] >= 0} {
9171 set i [string first "\t" $line]
9172 if {$i < 0} continue
9173 set fname [string range $line [expr {$i+1}] end]
9174 if {[lsearch -exact $mlist $fname] >= 0} continue
9175 incr nr_unmerged
9176 if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
9177 lappend mlist $fname
9180 catch {close $fd}
9181 if {$mlist eq {}} {
9182 if {$nr_unmerged == 0} {
9183 show_error {} . "No files selected: --merge specified but\
9184 no files are unmerged."
9185 } else {
9186 show_error {} . "No files selected: --merge specified but\
9187 no unmerged files are within file limit."
9189 exit 1
9191 set cmdline_files $mlist
9194 set nullid "0000000000000000000000000000000000000000"
9195 set nullid2 "0000000000000000000000000000000000000001"
9197 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9199 set runq {}
9200 set history {}
9201 set historyindex 0
9202 set fh_serial 0
9203 set nhl_names {}
9204 set highlight_paths {}
9205 set findpattern {}
9206 set searchdirn -forwards
9207 set boldrows {}
9208 set boldnamerows {}
9209 set diffelide {0 0}
9210 set markingmatches 0
9211 set linkentercount 0
9212 set need_redisplay 0
9213 set nrows_drawn 0
9214 set firsttabstop 0
9216 set nextviewnum 1
9217 set curview 0
9218 set selectedview 0
9219 set selectedhlview None
9220 set highlight_related None
9221 set highlight_files {}
9222 set viewfiles(0) {}
9223 set viewperm(0) 0
9224 set viewargs(0) {}
9226 set loginstance 0
9227 set cmdlineok 0
9228 set stopped 0
9229 set stuffsaved 0
9230 set patchnum 0
9231 set lserial 0
9232 setcoords
9233 makewindow
9234 # wait for the window to become visible
9235 tkwait visibility .
9236 wm title . "[file tail $argv0]: [file tail [pwd]]"
9237 readrefs
9239 if {$cmdline_files ne {} || $revtreeargs ne {}} {
9240 # create a view for the files/dirs specified on the command line
9241 set curview 1
9242 set selectedview 1
9243 set nextviewnum 2
9244 set viewname(1) "Command line"
9245 set viewfiles(1) $cmdline_files
9246 set viewargs(1) $revtreeargs
9247 set viewperm(1) 0
9248 addviewmenu 1
9249 .bar.view entryconf Edit* -state normal
9250 .bar.view entryconf Delete* -state normal
9253 if {[info exists permviews]} {
9254 foreach v $permviews {
9255 set n $nextviewnum
9256 incr nextviewnum
9257 set viewname($n) [lindex $v 0]
9258 set viewfiles($n) [lindex $v 1]
9259 set viewargs($n) [lindex $v 2]
9260 set viewperm($n) 1
9261 addviewmenu $n
9264 getcommits