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