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