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