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