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