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