gitk: Make updates go faster
[git/jnareb-git.git] / gitk
blob5f27c6ac4f8e830f10113e6f99342042a016f2c2
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright © 2005-2008 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25 global isonrunq runq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {}} {
30 after idle dorunq
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
41 global runq
43 fileevent $fd readable {}
44 if {$runq eq {}} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc nukefile {fd} {
51 global runq
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
56 } else {
57 incr i
62 proc dorunq {} {
63 global isonrunq runq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set repeat [eval $script]
71 set t1 [clock clicks -milliseconds]
72 set t [expr {$t1 - $t0}]
73 set runq [lrange $runq 1 end]
74 if {$repeat ne {} && $repeat} {
75 if {$fd eq {} || $repeat == 2} {
76 # script returns 1 if it wants to be readded
77 # file readers return 2 if they could do more straight away
78 lappend runq [list $fd $script]
79 } else {
80 fileevent $fd readable [list filereadable $fd $script]
82 } elseif {$fd eq {}} {
83 unset isonrunq($script)
85 set t0 $t1
86 if {$t1 - $tstart >= 80} break
88 if {$runq ne {}} {
89 after idle dorunq
93 proc unmerged_files {files} {
94 global nr_unmerged
96 # find the list of unmerged files
97 set mlist {}
98 set nr_unmerged 0
99 if {[catch {
100 set fd [open "| git ls-files -u" r]
101 } err]} {
102 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
103 exit 1
105 while {[gets $fd line] >= 0} {
106 set i [string first "\t" $line]
107 if {$i < 0} continue
108 set fname [string range $line [expr {$i+1}] end]
109 if {[lsearch -exact $mlist $fname] >= 0} continue
110 incr nr_unmerged
111 if {$files eq {} || [path_filter $files $fname]} {
112 lappend mlist $fname
115 catch {close $fd}
116 return $mlist
119 proc parseviewargs {n arglist} {
120 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
122 set vdatemode($n) 0
123 set vmergeonly($n) 0
124 set glflags {}
125 set diffargs {}
126 set nextisval 0
127 set revargs {}
128 set origargs $arglist
129 set allknown 1
130 set filtered 0
131 set i -1
132 foreach arg $arglist {
133 incr i
134 if {$nextisval} {
135 lappend glflags $arg
136 set nextisval 0
137 continue
139 switch -glob -- $arg {
140 "-d" -
141 "--date-order" {
142 set vdatemode($n) 1
143 # remove from origargs in case we hit an unknown option
144 set origargs [lreplace $origargs $i $i]
145 incr i -1
147 # These request or affect diff output, which we don't want.
148 # Some could be used to set our defaults for diff display.
149 "-[puabwcrRBMC]" -
150 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
151 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
152 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
153 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
154 "--ignore-space-change" - "-U*" - "--unified=*" {
155 lappend diffargs $arg
157 # These cause our parsing of git log's output to fail, or else
158 # they're options we want to set ourselves, so ignore them.
159 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
160 "--name-only" - "--name-status" - "--color" - "--color-words" -
161 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
162 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
163 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
164 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
165 "--objects" - "--objects-edge" - "--reverse" {
167 # These are harmless, and some are even useful
168 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
169 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
170 "--full-history" - "--dense" - "--sparse" -
171 "--follow" - "--left-right" - "--encoding=*" {
172 lappend glflags $arg
174 # These mean that we get a subset of the commits
175 "--diff-filter=*" - "--no-merges" - "--unpacked" -
176 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
177 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
178 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
179 "--remove-empty" - "--first-parent" - "--cherry-pick" -
180 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
181 set filtered 1
182 lappend glflags $arg
184 # This appears to be the only one that has a value as a
185 # separate word following it
186 "-n" {
187 set filtered 1
188 set nextisval 1
189 lappend glflags $arg
191 "--not" {
192 set notflag [expr {!$notflag}]
193 lappend revargs $arg
195 "--all" {
196 lappend revargs $arg
198 "--merge" {
199 set vmergeonly($n) 1
200 # git rev-parse doesn't understand --merge
201 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
203 # Other flag arguments including -<n>
204 "-*" {
205 if {[string is digit -strict [string range $arg 1 end]]} {
206 set filtered 1
207 } else {
208 # a flag argument that we don't recognize;
209 # that means we can't optimize
210 set allknown 0
212 lappend glflags $arg
214 # Non-flag arguments specify commits or ranges of commits
215 default {
216 if {[string match "*...*" $arg]} {
217 lappend revargs --gitk-symmetric-diff-marker
219 lappend revargs $arg
223 set vdflags($n) $diffargs
224 set vflags($n) $glflags
225 set vrevs($n) $revargs
226 set vfiltered($n) $filtered
227 set vorigargs($n) $origargs
228 return $allknown
231 proc parseviewrevs {view revs} {
232 global vposids vnegids
234 if {$revs eq {}} {
235 set revs HEAD
237 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
238 # we get stdout followed by stderr in $err
239 # for an unknown rev, git rev-parse echoes it and then errors out
240 set errlines [split $err "\n"]
241 set badrev {}
242 for {set l 0} {$l < [llength $errlines]} {incr l} {
243 set line [lindex $errlines $l]
244 if {!([string length $line] == 40 && [string is xdigit $line])} {
245 if {[string match "fatal:*" $line]} {
246 if {[string match "fatal: ambiguous argument*" $line]
247 && $badrev ne {}} {
248 if {[llength $badrev] == 1} {
249 set err "unknown revision $badrev"
250 } else {
251 set err "unknown revisions: [join $badrev ", "]"
253 } else {
254 set err [join [lrange $errlines $l end] "\n"]
256 break
258 lappend badrev $line
261 error_popup "Error parsing revisions: $err"
262 return {}
264 set ret {}
265 set pos {}
266 set neg {}
267 set sdm 0
268 foreach id [split $ids "\n"] {
269 if {$id eq "--gitk-symmetric-diff-marker"} {
270 set sdm 4
271 } elseif {[string match "^*" $id]} {
272 if {$sdm != 1} {
273 lappend ret $id
274 if {$sdm == 3} {
275 set sdm 0
278 lappend neg [string range $id 1 end]
279 } else {
280 if {$sdm != 2} {
281 lappend ret $id
282 } else {
283 lset ret end [lindex $ret end]...$id
285 lappend pos $id
287 incr sdm -1
289 set vposids($view) $pos
290 set vnegids($view) $neg
291 return $ret
294 # Start off a git log process and arrange to read its output
295 proc start_rev_list {view} {
296 global startmsecs commitidx viewcomplete
297 global commfd leftover tclencoding
298 global viewargs viewargscmd viewfiles vfilelimit
299 global showlocalchanges commitinterest mainheadid
300 global progressdirn progresscoords proglastnc curview
301 global viewactive loginstance viewinstances vmergeonly
302 global pending_select mainheadid
303 global vcanopt vflags vrevs vorigargs
305 set startmsecs [clock clicks -milliseconds]
306 set commitidx($view) 0
307 # these are set this way for the error exits
308 set viewcomplete($view) 1
309 set viewactive($view) 0
310 varcinit $view
312 set args $viewargs($view)
313 if {$viewargscmd($view) ne {}} {
314 if {[catch {
315 set str [exec sh -c $viewargscmd($view)]
316 } err]} {
317 error_popup "Error executing --argscmd command: $err"
318 return 0
320 set args [concat $args [split $str "\n"]]
322 set vcanopt($view) [parseviewargs $view $args]
324 set files $viewfiles($view)
325 if {$vmergeonly($view)} {
326 set files [unmerged_files $files]
327 if {$files eq {}} {
328 global nr_unmerged
329 if {$nr_unmerged == 0} {
330 error_popup [mc "No files selected: --merge specified but\
331 no files are unmerged."]
332 } else {
333 error_popup [mc "No files selected: --merge specified but\
334 no unmerged files are within file limit."]
336 return 0
339 set vfilelimit($view) $files
341 if {$vcanopt($view)} {
342 set revs [parseviewrevs $view $vrevs($view)]
343 if {$revs eq {}} {
344 return 0
346 set args [concat $vflags($view) $revs]
347 } else {
348 set args $vorigargs($view)
351 if {[catch {
352 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
353 --boundary $args "--" $files] r]
354 } err]} {
355 error_popup "[mc "Error executing git log:"] $err"
356 return 0
358 set i [incr loginstance]
359 set viewinstances($view) [list $i]
360 set commfd($i) $fd
361 set leftover($i) {}
362 if {$showlocalchanges} {
363 lappend commitinterest($mainheadid) {dodiffindex}
365 fconfigure $fd -blocking 0 -translation lf -eofchar {}
366 if {$tclencoding != {}} {
367 fconfigure $fd -encoding $tclencoding
369 filerun $fd [list getcommitlines $fd $i $view 0]
370 nowbusy $view [mc "Reading"]
371 if {$view == $curview} {
372 set progressdirn 1
373 set progresscoords {0 0}
374 set proglastnc 0
375 set pending_select $mainheadid
377 set viewcomplete($view) 0
378 set viewactive($view) 1
379 return 1
382 proc stop_rev_list {view} {
383 global commfd viewinstances leftover
385 foreach inst $viewinstances($view) {
386 set fd $commfd($inst)
387 catch {
388 set pid [pid $fd]
389 exec kill $pid
391 catch {close $fd}
392 nukefile $fd
393 unset commfd($inst)
394 unset leftover($inst)
396 set viewinstances($view) {}
399 proc getcommits {} {
400 global canv curview need_redisplay viewactive
402 initlayout
403 if {[start_rev_list $curview]} {
404 show_status [mc "Reading commits..."]
405 set need_redisplay 1
406 } else {
407 show_status [mc "No commits selected"]
411 proc updatecommits {} {
412 global curview vcanopt vorigargs vfilelimit viewinstances
413 global viewactive viewcomplete loginstance tclencoding mainheadid
414 global startmsecs commfd showneartags showlocalchanges leftover
415 global mainheadid pending_select
416 global isworktree
417 global varcid vposids vnegids vflags vrevs
419 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
420 set oldmainid $mainheadid
421 rereadrefs
422 if {$showlocalchanges} {
423 if {$mainheadid ne $oldmainid} {
424 dohidelocalchanges
426 if {[commitinview $mainheadid $curview]} {
427 dodiffindex
430 set view $curview
431 if {$vcanopt($view)} {
432 set oldpos $vposids($view)
433 set oldneg $vnegids($view)
434 set revs [parseviewrevs $view $vrevs($view)]
435 if {$revs eq {}} {
436 return
438 # note: getting the delta when negative refs change is hard,
439 # and could require multiple git log invocations, so in that
440 # case we ask git log for all the commits (not just the delta)
441 if {$oldneg eq $vnegids($view)} {
442 set newrevs {}
443 set npos 0
444 # take out positive refs that we asked for before or
445 # that we have already seen
446 foreach rev $revs {
447 if {[string length $rev] == 40} {
448 if {[lsearch -exact $oldpos $rev] < 0
449 && ![info exists varcid($view,$rev)]} {
450 lappend newrevs $rev
451 incr npos
453 } else {
454 lappend $newrevs $rev
457 if {$npos == 0} return
458 set revs $newrevs
459 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
461 set args [concat $vflags($view) $revs --not $oldpos]
462 } else {
463 set args $vorigargs($view)
465 if {[catch {
466 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
467 --boundary $args "--" $vfilelimit($view)] r]
468 } err]} {
469 error_popup "Error executing git log: $err"
470 return
472 if {$viewactive($view) == 0} {
473 set startmsecs [clock clicks -milliseconds]
475 set i [incr loginstance]
476 lappend viewinstances($view) $i
477 set commfd($i) $fd
478 set leftover($i) {}
479 fconfigure $fd -blocking 0 -translation lf -eofchar {}
480 if {$tclencoding != {}} {
481 fconfigure $fd -encoding $tclencoding
483 filerun $fd [list getcommitlines $fd $i $view 1]
484 incr viewactive($view)
485 set viewcomplete($view) 0
486 set pending_select $mainheadid
487 nowbusy $view "Reading"
488 if {$showneartags} {
489 getallcommits
493 proc reloadcommits {} {
494 global curview viewcomplete selectedline currentid thickerline
495 global showneartags treediffs commitinterest cached_commitrow
496 global progresscoords targetid
498 if {!$viewcomplete($curview)} {
499 stop_rev_list $curview
500 set progresscoords {0 0}
501 adjustprogress
503 resetvarcs $curview
504 catch {unset selectedline}
505 catch {unset currentid}
506 catch {unset thickerline}
507 catch {unset treediffs}
508 readrefs
509 changedrefs
510 if {$showneartags} {
511 getallcommits
513 clear_display
514 catch {unset commitinterest}
515 catch {unset cached_commitrow}
516 catch {unset targetid}
517 setcanvscroll
518 getcommits
519 return 0
522 # This makes a string representation of a positive integer which
523 # sorts as a string in numerical order
524 proc strrep {n} {
525 if {$n < 16} {
526 return [format "%x" $n]
527 } elseif {$n < 256} {
528 return [format "x%.2x" $n]
529 } elseif {$n < 65536} {
530 return [format "y%.4x" $n]
532 return [format "z%.8x" $n]
535 # Procedures used in reordering commits from git log (without
536 # --topo-order) into the order for display.
538 proc varcinit {view} {
539 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
540 global vtokmod varcmod vrowmod varcix vlastins
542 set varcstart($view) {{}}
543 set vupptr($view) {0}
544 set vdownptr($view) {0}
545 set vleftptr($view) {0}
546 set vbackptr($view) {0}
547 set varctok($view) {{}}
548 set varcrow($view) {{}}
549 set vtokmod($view) {}
550 set varcmod($view) 0
551 set vrowmod($view) 0
552 set varcix($view) {{}}
553 set vlastins($view) {0}
556 proc resetvarcs {view} {
557 global varcid varccommits parents children vseedcount ordertok
559 foreach vid [array names varcid $view,*] {
560 unset varcid($vid)
561 unset children($vid)
562 unset parents($vid)
564 # some commits might have children but haven't been seen yet
565 foreach vid [array names children $view,*] {
566 unset children($vid)
568 foreach va [array names varccommits $view,*] {
569 unset varccommits($va)
571 foreach vd [array names vseedcount $view,*] {
572 unset vseedcount($vd)
574 catch {unset ordertok}
577 # returns a list of the commits with no children
578 proc seeds {v} {
579 global vdownptr vleftptr varcstart
581 set ret {}
582 set a [lindex $vdownptr($v) 0]
583 while {$a != 0} {
584 lappend ret [lindex $varcstart($v) $a]
585 set a [lindex $vleftptr($v) $a]
587 return $ret
590 proc newvarc {view id} {
591 global varcid varctok parents children vdatemode
592 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
593 global commitdata commitinfo vseedcount varccommits vlastins
595 set a [llength $varctok($view)]
596 set vid $view,$id
597 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
598 if {![info exists commitinfo($id)]} {
599 parsecommit $id $commitdata($id) 1
601 set cdate [lindex $commitinfo($id) 4]
602 if {![string is integer -strict $cdate]} {
603 set cdate 0
605 if {![info exists vseedcount($view,$cdate)]} {
606 set vseedcount($view,$cdate) -1
608 set c [incr vseedcount($view,$cdate)]
609 set cdate [expr {$cdate ^ 0xffffffff}]
610 set tok "s[strrep $cdate][strrep $c]"
611 } else {
612 set tok {}
614 set ka 0
615 if {[llength $children($vid)] > 0} {
616 set kid [lindex $children($vid) end]
617 set k $varcid($view,$kid)
618 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
619 set ki $kid
620 set ka $k
621 set tok [lindex $varctok($view) $k]
624 if {$ka != 0} {
625 set i [lsearch -exact $parents($view,$ki) $id]
626 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
627 append tok [strrep $j]
629 set c [lindex $vlastins($view) $ka]
630 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
631 set c $ka
632 set b [lindex $vdownptr($view) $ka]
633 } else {
634 set b [lindex $vleftptr($view) $c]
636 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
637 set c $b
638 set b [lindex $vleftptr($view) $c]
640 if {$c == $ka} {
641 lset vdownptr($view) $ka $a
642 lappend vbackptr($view) 0
643 } else {
644 lset vleftptr($view) $c $a
645 lappend vbackptr($view) $c
647 lset vlastins($view) $ka $a
648 lappend vupptr($view) $ka
649 lappend vleftptr($view) $b
650 if {$b != 0} {
651 lset vbackptr($view) $b $a
653 lappend varctok($view) $tok
654 lappend varcstart($view) $id
655 lappend vdownptr($view) 0
656 lappend varcrow($view) {}
657 lappend varcix($view) {}
658 set varccommits($view,$a) {}
659 lappend vlastins($view) 0
660 return $a
663 proc splitvarc {p v} {
664 global varcid varcstart varccommits varctok
665 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
667 set oa $varcid($v,$p)
668 set ac $varccommits($v,$oa)
669 set i [lsearch -exact $varccommits($v,$oa) $p]
670 if {$i <= 0} return
671 set na [llength $varctok($v)]
672 # "%" sorts before "0"...
673 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
674 lappend varctok($v) $tok
675 lappend varcrow($v) {}
676 lappend varcix($v) {}
677 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
678 set varccommits($v,$na) [lrange $ac $i end]
679 lappend varcstart($v) $p
680 foreach id $varccommits($v,$na) {
681 set varcid($v,$id) $na
683 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
684 lappend vlastins($v) [lindex $vlastins($v) $oa]
685 lset vdownptr($v) $oa $na
686 lset vlastins($v) $oa 0
687 lappend vupptr($v) $oa
688 lappend vleftptr($v) 0
689 lappend vbackptr($v) 0
690 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
691 lset vupptr($v) $b $na
695 proc renumbervarc {a v} {
696 global parents children varctok varcstart varccommits
697 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
699 set t1 [clock clicks -milliseconds]
700 set todo {}
701 set isrelated($a) 1
702 set kidchanged($a) 1
703 set ntot 0
704 while {$a != 0} {
705 if {[info exists isrelated($a)]} {
706 lappend todo $a
707 set id [lindex $varccommits($v,$a) end]
708 foreach p $parents($v,$id) {
709 if {[info exists varcid($v,$p)]} {
710 set isrelated($varcid($v,$p)) 1
714 incr ntot
715 set b [lindex $vdownptr($v) $a]
716 if {$b == 0} {
717 while {$a != 0} {
718 set b [lindex $vleftptr($v) $a]
719 if {$b != 0} break
720 set a [lindex $vupptr($v) $a]
723 set a $b
725 foreach a $todo {
726 if {![info exists kidchanged($a)]} continue
727 set id [lindex $varcstart($v) $a]
728 if {[llength $children($v,$id)] > 1} {
729 set children($v,$id) [lsort -command [list vtokcmp $v] \
730 $children($v,$id)]
732 set oldtok [lindex $varctok($v) $a]
733 if {!$vdatemode($v)} {
734 set tok {}
735 } else {
736 set tok $oldtok
738 set ka 0
739 set kid [last_real_child $v,$id]
740 if {$kid ne {}} {
741 set k $varcid($v,$kid)
742 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
743 set ki $kid
744 set ka $k
745 set tok [lindex $varctok($v) $k]
748 if {$ka != 0} {
749 set i [lsearch -exact $parents($v,$ki) $id]
750 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
751 append tok [strrep $j]
753 if {$tok eq $oldtok} {
754 continue
756 set id [lindex $varccommits($v,$a) end]
757 foreach p $parents($v,$id) {
758 if {[info exists varcid($v,$p)]} {
759 set kidchanged($varcid($v,$p)) 1
760 } else {
761 set sortkids($p) 1
764 lset varctok($v) $a $tok
765 set b [lindex $vupptr($v) $a]
766 if {$b != $ka} {
767 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
768 modify_arc $v $ka
770 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
771 modify_arc $v $b
773 set c [lindex $vbackptr($v) $a]
774 set d [lindex $vleftptr($v) $a]
775 if {$c == 0} {
776 lset vdownptr($v) $b $d
777 } else {
778 lset vleftptr($v) $c $d
780 if {$d != 0} {
781 lset vbackptr($v) $d $c
783 if {[lindex $vlastins($v) $b] == $a} {
784 lset vlastins($v) $b $c
786 lset vupptr($v) $a $ka
787 set c [lindex $vlastins($v) $ka]
788 if {$c == 0 || \
789 [string compare $tok [lindex $varctok($v) $c]] < 0} {
790 set c $ka
791 set b [lindex $vdownptr($v) $ka]
792 } else {
793 set b [lindex $vleftptr($v) $c]
795 while {$b != 0 && \
796 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
797 set c $b
798 set b [lindex $vleftptr($v) $c]
800 if {$c == $ka} {
801 lset vdownptr($v) $ka $a
802 lset vbackptr($v) $a 0
803 } else {
804 lset vleftptr($v) $c $a
805 lset vbackptr($v) $a $c
807 lset vleftptr($v) $a $b
808 if {$b != 0} {
809 lset vbackptr($v) $b $a
811 lset vlastins($v) $ka $a
814 foreach id [array names sortkids] {
815 if {[llength $children($v,$id)] > 1} {
816 set children($v,$id) [lsort -command [list vtokcmp $v] \
817 $children($v,$id)]
820 set t2 [clock clicks -milliseconds]
821 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
824 # Fix up the graph after we have found out that in view $v,
825 # $p (a commit that we have already seen) is actually the parent
826 # of the last commit in arc $a.
827 proc fix_reversal {p a v} {
828 global varcid varcstart varctok vupptr
830 set pa $varcid($v,$p)
831 if {$p ne [lindex $varcstart($v) $pa]} {
832 splitvarc $p $v
833 set pa $varcid($v,$p)
835 # seeds always need to be renumbered
836 if {[lindex $vupptr($v) $pa] == 0 ||
837 [string compare [lindex $varctok($v) $a] \
838 [lindex $varctok($v) $pa]] > 0} {
839 renumbervarc $pa $v
843 proc insertrow {id p v} {
844 global cmitlisted children parents varcid varctok vtokmod
845 global varccommits ordertok commitidx numcommits curview
846 global targetid targetrow
848 readcommit $id
849 set vid $v,$id
850 set cmitlisted($vid) 1
851 set children($vid) {}
852 set parents($vid) [list $p]
853 set a [newvarc $v $id]
854 set varcid($vid) $a
855 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
856 modify_arc $v $a
858 lappend varccommits($v,$a) $id
859 set vp $v,$p
860 if {[llength [lappend children($vp) $id]] > 1} {
861 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
862 catch {unset ordertok}
864 fix_reversal $p $a $v
865 incr commitidx($v)
866 if {$v == $curview} {
867 set numcommits $commitidx($v)
868 setcanvscroll
869 if {[info exists targetid]} {
870 if {![comes_before $targetid $p]} {
871 incr targetrow
877 proc insertfakerow {id p} {
878 global varcid varccommits parents children cmitlisted
879 global commitidx varctok vtokmod targetid targetrow curview numcommits
881 set v $curview
882 set a $varcid($v,$p)
883 set i [lsearch -exact $varccommits($v,$a) $p]
884 if {$i < 0} {
885 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
886 return
888 set children($v,$id) {}
889 set parents($v,$id) [list $p]
890 set varcid($v,$id) $a
891 lappend children($v,$p) $id
892 set cmitlisted($v,$id) 1
893 set numcommits [incr commitidx($v)]
894 # note we deliberately don't update varcstart($v) even if $i == 0
895 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
896 modify_arc $v $a $i
897 if {[info exists targetid]} {
898 if {![comes_before $targetid $p]} {
899 incr targetrow
902 setcanvscroll
903 drawvisible
906 proc removefakerow {id} {
907 global varcid varccommits parents children commitidx
908 global varctok vtokmod cmitlisted currentid selectedline
909 global targetid curview numcommits
911 set v $curview
912 if {[llength $parents($v,$id)] != 1} {
913 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
914 return
916 set p [lindex $parents($v,$id) 0]
917 set a $varcid($v,$id)
918 set i [lsearch -exact $varccommits($v,$a) $id]
919 if {$i < 0} {
920 puts "oops: removefakerow can't find [shortids $id] on arc $a"
921 return
923 unset varcid($v,$id)
924 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
925 unset parents($v,$id)
926 unset children($v,$id)
927 unset cmitlisted($v,$id)
928 set numcommits [incr commitidx($v) -1]
929 set j [lsearch -exact $children($v,$p) $id]
930 if {$j >= 0} {
931 set children($v,$p) [lreplace $children($v,$p) $j $j]
933 modify_arc $v $a $i
934 if {[info exist currentid] && $id eq $currentid} {
935 unset currentid
936 unset selectedline
938 if {[info exists targetid] && $targetid eq $id} {
939 set targetid $p
941 setcanvscroll
942 drawvisible
945 proc first_real_child {vp} {
946 global children nullid nullid2
948 foreach id $children($vp) {
949 if {$id ne $nullid && $id ne $nullid2} {
950 return $id
953 return {}
956 proc last_real_child {vp} {
957 global children nullid nullid2
959 set kids $children($vp)
960 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
961 set id [lindex $kids $i]
962 if {$id ne $nullid && $id ne $nullid2} {
963 return $id
966 return {}
969 proc vtokcmp {v a b} {
970 global varctok varcid
972 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
973 [lindex $varctok($v) $varcid($v,$b)]]
976 # This assumes that if lim is not given, the caller has checked that
977 # arc a's token is less than $vtokmod($v)
978 proc modify_arc {v a {lim {}}} {
979 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
981 if {$lim ne {}} {
982 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
983 if {$c > 0} return
984 if {$c == 0} {
985 set r [lindex $varcrow($v) $a]
986 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
989 set vtokmod($v) [lindex $varctok($v) $a]
990 set varcmod($v) $a
991 if {$v == $curview} {
992 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
993 set a [lindex $vupptr($v) $a]
994 set lim {}
996 set r 0
997 if {$a != 0} {
998 if {$lim eq {}} {
999 set lim [llength $varccommits($v,$a)]
1001 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1003 set vrowmod($v) $r
1004 undolayout $r
1008 proc update_arcrows {v} {
1009 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1010 global varcid vrownum varcorder varcix varccommits
1011 global vupptr vdownptr vleftptr varctok
1012 global displayorder parentlist curview cached_commitrow
1014 if {$vrowmod($v) == $commitidx($v)} return
1015 if {$v == $curview} {
1016 if {[llength $displayorder] > $vrowmod($v)} {
1017 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1018 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1020 catch {unset cached_commitrow}
1022 set narctot [expr {[llength $varctok($v)] - 1}]
1023 set a $varcmod($v)
1024 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1025 # go up the tree until we find something that has a row number,
1026 # or we get to a seed
1027 set a [lindex $vupptr($v) $a]
1029 if {$a == 0} {
1030 set a [lindex $vdownptr($v) 0]
1031 if {$a == 0} return
1032 set vrownum($v) {0}
1033 set varcorder($v) [list $a]
1034 lset varcix($v) $a 0
1035 lset varcrow($v) $a 0
1036 set arcn 0
1037 set row 0
1038 } else {
1039 set arcn [lindex $varcix($v) $a]
1040 if {[llength $vrownum($v)] > $arcn + 1} {
1041 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1042 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1044 set row [lindex $varcrow($v) $a]
1046 while {1} {
1047 set p $a
1048 incr row [llength $varccommits($v,$a)]
1049 # go down if possible
1050 set b [lindex $vdownptr($v) $a]
1051 if {$b == 0} {
1052 # if not, go left, or go up until we can go left
1053 while {$a != 0} {
1054 set b [lindex $vleftptr($v) $a]
1055 if {$b != 0} break
1056 set a [lindex $vupptr($v) $a]
1058 if {$a == 0} break
1060 set a $b
1061 incr arcn
1062 lappend vrownum($v) $row
1063 lappend varcorder($v) $a
1064 lset varcix($v) $a $arcn
1065 lset varcrow($v) $a $row
1067 set vtokmod($v) [lindex $varctok($v) $p]
1068 set varcmod($v) $p
1069 set vrowmod($v) $row
1070 if {[info exists currentid]} {
1071 set selectedline [rowofcommit $currentid]
1075 # Test whether view $v contains commit $id
1076 proc commitinview {id v} {
1077 global varcid
1079 return [info exists varcid($v,$id)]
1082 # Return the row number for commit $id in the current view
1083 proc rowofcommit {id} {
1084 global varcid varccommits varcrow curview cached_commitrow
1085 global varctok vtokmod
1087 set v $curview
1088 if {![info exists varcid($v,$id)]} {
1089 puts "oops rowofcommit no arc for [shortids $id]"
1090 return {}
1092 set a $varcid($v,$id)
1093 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1094 update_arcrows $v
1096 if {[info exists cached_commitrow($id)]} {
1097 return $cached_commitrow($id)
1099 set i [lsearch -exact $varccommits($v,$a) $id]
1100 if {$i < 0} {
1101 puts "oops didn't find commit [shortids $id] in arc $a"
1102 return {}
1104 incr i [lindex $varcrow($v) $a]
1105 set cached_commitrow($id) $i
1106 return $i
1109 # Returns 1 if a is on an earlier row than b, otherwise 0
1110 proc comes_before {a b} {
1111 global varcid varctok curview
1113 set v $curview
1114 if {$a eq $b || ![info exists varcid($v,$a)] || \
1115 ![info exists varcid($v,$b)]} {
1116 return 0
1118 if {$varcid($v,$a) != $varcid($v,$b)} {
1119 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1120 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1122 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1125 proc bsearch {l elt} {
1126 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1127 return 0
1129 set lo 0
1130 set hi [llength $l]
1131 while {$hi - $lo > 1} {
1132 set mid [expr {int(($lo + $hi) / 2)}]
1133 set t [lindex $l $mid]
1134 if {$elt < $t} {
1135 set hi $mid
1136 } elseif {$elt > $t} {
1137 set lo $mid
1138 } else {
1139 return $mid
1142 return $lo
1145 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1146 proc make_disporder {start end} {
1147 global vrownum curview commitidx displayorder parentlist
1148 global varccommits varcorder parents vrowmod varcrow
1149 global d_valid_start d_valid_end
1151 if {$end > $vrowmod($curview)} {
1152 update_arcrows $curview
1154 set ai [bsearch $vrownum($curview) $start]
1155 set start [lindex $vrownum($curview) $ai]
1156 set narc [llength $vrownum($curview)]
1157 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1158 set a [lindex $varcorder($curview) $ai]
1159 set l [llength $displayorder]
1160 set al [llength $varccommits($curview,$a)]
1161 if {$l < $r + $al} {
1162 if {$l < $r} {
1163 set pad [ntimes [expr {$r - $l}] {}]
1164 set displayorder [concat $displayorder $pad]
1165 set parentlist [concat $parentlist $pad]
1166 } elseif {$l > $r} {
1167 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1168 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1170 foreach id $varccommits($curview,$a) {
1171 lappend displayorder $id
1172 lappend parentlist $parents($curview,$id)
1174 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1175 set i $r
1176 foreach id $varccommits($curview,$a) {
1177 lset displayorder $i $id
1178 lset parentlist $i $parents($curview,$id)
1179 incr i
1182 incr r $al
1186 proc commitonrow {row} {
1187 global displayorder
1189 set id [lindex $displayorder $row]
1190 if {$id eq {}} {
1191 make_disporder $row [expr {$row + 1}]
1192 set id [lindex $displayorder $row]
1194 return $id
1197 proc closevarcs {v} {
1198 global varctok varccommits varcid parents children
1199 global cmitlisted commitidx commitinterest vtokmod
1201 set missing_parents 0
1202 set scripts {}
1203 set narcs [llength $varctok($v)]
1204 for {set a 1} {$a < $narcs} {incr a} {
1205 set id [lindex $varccommits($v,$a) end]
1206 foreach p $parents($v,$id) {
1207 if {[info exists varcid($v,$p)]} continue
1208 # add p as a new commit
1209 incr missing_parents
1210 set cmitlisted($v,$p) 0
1211 set parents($v,$p) {}
1212 if {[llength $children($v,$p)] == 1 &&
1213 [llength $parents($v,$id)] == 1} {
1214 set b $a
1215 } else {
1216 set b [newvarc $v $p]
1218 set varcid($v,$p) $b
1219 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1220 modify_arc $v $b
1222 lappend varccommits($v,$b) $p
1223 incr commitidx($v)
1224 if {[info exists commitinterest($p)]} {
1225 foreach script $commitinterest($p) {
1226 lappend scripts [string map [list "%I" $p] $script]
1228 unset commitinterest($id)
1232 if {$missing_parents > 0} {
1233 foreach s $scripts {
1234 eval $s
1239 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1240 # Assumes we already have an arc for $rwid.
1241 proc rewrite_commit {v id rwid} {
1242 global children parents varcid varctok vtokmod varccommits
1244 foreach ch $children($v,$id) {
1245 # make $rwid be $ch's parent in place of $id
1246 set i [lsearch -exact $parents($v,$ch) $id]
1247 if {$i < 0} {
1248 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1250 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1251 # add $ch to $rwid's children and sort the list if necessary
1252 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1253 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1254 $children($v,$rwid)]
1256 # fix the graph after joining $id to $rwid
1257 set a $varcid($v,$ch)
1258 fix_reversal $rwid $a $v
1259 # parentlist is wrong for the last element of arc $a
1260 # even if displayorder is right, hence the 3rd arg here
1261 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1265 proc getcommitlines {fd inst view updating} {
1266 global cmitlisted commitinterest leftover
1267 global commitidx commitdata vdatemode
1268 global parents children curview hlview
1269 global idpending ordertok
1270 global varccommits varcid varctok vtokmod vfilelimit
1272 set stuff [read $fd 500000]
1273 # git log doesn't terminate the last commit with a null...
1274 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1275 set stuff "\0"
1277 if {$stuff == {}} {
1278 if {![eof $fd]} {
1279 return 1
1281 global commfd viewcomplete viewactive viewname progresscoords
1282 global viewinstances
1283 unset commfd($inst)
1284 set i [lsearch -exact $viewinstances($view) $inst]
1285 if {$i >= 0} {
1286 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1288 # set it blocking so we wait for the process to terminate
1289 fconfigure $fd -blocking 1
1290 if {[catch {close $fd} err]} {
1291 set fv {}
1292 if {$view != $curview} {
1293 set fv " for the \"$viewname($view)\" view"
1295 if {[string range $err 0 4] == "usage"} {
1296 set err "Gitk: error reading commits$fv:\
1297 bad arguments to git log."
1298 if {$viewname($view) eq "Command line"} {
1299 append err \
1300 " (Note: arguments to gitk are passed to git log\
1301 to allow selection of commits to be displayed.)"
1303 } else {
1304 set err "Error reading commits$fv: $err"
1306 error_popup $err
1308 if {[incr viewactive($view) -1] <= 0} {
1309 set viewcomplete($view) 1
1310 # Check if we have seen any ids listed as parents that haven't
1311 # appeared in the list
1312 closevarcs $view
1313 notbusy $view
1314 set progresscoords {0 0}
1315 adjustprogress
1317 if {$view == $curview} {
1318 run chewcommits
1320 return 0
1322 set start 0
1323 set gotsome 0
1324 set scripts {}
1325 while 1 {
1326 set i [string first "\0" $stuff $start]
1327 if {$i < 0} {
1328 append leftover($inst) [string range $stuff $start end]
1329 break
1331 if {$start == 0} {
1332 set cmit $leftover($inst)
1333 append cmit [string range $stuff 0 [expr {$i - 1}]]
1334 set leftover($inst) {}
1335 } else {
1336 set cmit [string range $stuff $start [expr {$i - 1}]]
1338 set start [expr {$i + 1}]
1339 set j [string first "\n" $cmit]
1340 set ok 0
1341 set listed 1
1342 if {$j >= 0 && [string match "commit *" $cmit]} {
1343 set ids [string range $cmit 7 [expr {$j - 1}]]
1344 if {[string match {[-^<>]*} $ids]} {
1345 switch -- [string index $ids 0] {
1346 "-" {set listed 0}
1347 "^" {set listed 2}
1348 "<" {set listed 3}
1349 ">" {set listed 4}
1351 set ids [string range $ids 1 end]
1353 set ok 1
1354 foreach id $ids {
1355 if {[string length $id] != 40} {
1356 set ok 0
1357 break
1361 if {!$ok} {
1362 set shortcmit $cmit
1363 if {[string length $shortcmit] > 80} {
1364 set shortcmit "[string range $shortcmit 0 80]..."
1366 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1367 exit 1
1369 set id [lindex $ids 0]
1370 set vid $view,$id
1372 if {!$listed && $updating && ![info exists varcid($vid)] &&
1373 $vfilelimit($view) ne {}} {
1374 # git log doesn't rewrite parents for unlisted commits
1375 # when doing path limiting, so work around that here
1376 # by working out the rewritten parent with git rev-list
1377 # and if we already know about it, using the rewritten
1378 # parent as a substitute parent for $id's children.
1379 if {![catch {
1380 set rwid [exec git rev-list --first-parent --max-count=1 \
1381 $id -- $vfilelimit($view)]
1382 }]} {
1383 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1384 # use $rwid in place of $id
1385 rewrite_commit $view $id $rwid
1386 continue
1391 set a 0
1392 if {[info exists varcid($vid)]} {
1393 if {$cmitlisted($vid) || !$listed} continue
1394 set a $varcid($vid)
1396 if {$listed} {
1397 set olds [lrange $ids 1 end]
1398 } else {
1399 set olds {}
1401 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1402 set cmitlisted($vid) $listed
1403 set parents($vid) $olds
1404 if {![info exists children($vid)]} {
1405 set children($vid) {}
1406 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1407 set k [lindex $children($vid) 0]
1408 if {[llength $parents($view,$k)] == 1 &&
1409 (!$vdatemode($view) ||
1410 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1411 set a $varcid($view,$k)
1414 if {$a == 0} {
1415 # new arc
1416 set a [newvarc $view $id]
1418 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1419 modify_arc $view $a
1421 if {![info exists varcid($vid)]} {
1422 set varcid($vid) $a
1423 lappend varccommits($view,$a) $id
1424 incr commitidx($view)
1427 set i 0
1428 foreach p $olds {
1429 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1430 set vp $view,$p
1431 if {[llength [lappend children($vp) $id]] > 1 &&
1432 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1433 set children($vp) [lsort -command [list vtokcmp $view] \
1434 $children($vp)]
1435 catch {unset ordertok}
1437 if {[info exists varcid($view,$p)]} {
1438 fix_reversal $p $a $view
1441 incr i
1444 if {[info exists commitinterest($id)]} {
1445 foreach script $commitinterest($id) {
1446 lappend scripts [string map [list "%I" $id] $script]
1448 unset commitinterest($id)
1450 set gotsome 1
1452 if {$gotsome} {
1453 global numcommits hlview
1455 if {$view == $curview} {
1456 set numcommits $commitidx($view)
1457 run chewcommits
1459 if {[info exists hlview] && $view == $hlview} {
1460 # we never actually get here...
1461 run vhighlightmore
1463 foreach s $scripts {
1464 eval $s
1466 if {$view == $curview} {
1467 # update progress bar
1468 global progressdirn progresscoords proglastnc
1469 set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
1470 set proglastnc $commitidx($view)
1471 set l [lindex $progresscoords 0]
1472 set r [lindex $progresscoords 1]
1473 if {$progressdirn} {
1474 set r [expr {$r + $inc}]
1475 if {$r >= 1.0} {
1476 set r 1.0
1477 set progressdirn 0
1479 if {$r > 0.2} {
1480 set l [expr {$r - 0.2}]
1482 } else {
1483 set l [expr {$l - $inc}]
1484 if {$l <= 0.0} {
1485 set l 0.0
1486 set progressdirn 1
1488 set r [expr {$l + 0.2}]
1490 set progresscoords [list $l $r]
1491 adjustprogress
1494 return 2
1497 proc chewcommits {} {
1498 global curview hlview viewcomplete
1499 global pending_select
1501 layoutmore
1502 if {$viewcomplete($curview)} {
1503 global commitidx varctok
1504 global numcommits startmsecs
1505 global mainheadid nullid
1507 if {[info exists pending_select]} {
1508 set row [first_real_row]
1509 selectline $row 1
1511 if {$commitidx($curview) > 0} {
1512 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1513 #puts "overall $ms ms for $numcommits commits"
1514 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1515 } else {
1516 show_status [mc "No commits selected"]
1518 notbusy layout
1520 return 0
1523 proc readcommit {id} {
1524 if {[catch {set contents [exec git cat-file commit $id]}]} return
1525 parsecommit $id $contents 0
1528 proc parsecommit {id contents listed} {
1529 global commitinfo cdate
1531 set inhdr 1
1532 set comment {}
1533 set headline {}
1534 set auname {}
1535 set audate {}
1536 set comname {}
1537 set comdate {}
1538 set hdrend [string first "\n\n" $contents]
1539 if {$hdrend < 0} {
1540 # should never happen...
1541 set hdrend [string length $contents]
1543 set header [string range $contents 0 [expr {$hdrend - 1}]]
1544 set comment [string range $contents [expr {$hdrend + 2}] end]
1545 foreach line [split $header "\n"] {
1546 set tag [lindex $line 0]
1547 if {$tag == "author"} {
1548 set audate [lindex $line end-1]
1549 set auname [lrange $line 1 end-2]
1550 } elseif {$tag == "committer"} {
1551 set comdate [lindex $line end-1]
1552 set comname [lrange $line 1 end-2]
1555 set headline {}
1556 # take the first non-blank line of the comment as the headline
1557 set headline [string trimleft $comment]
1558 set i [string first "\n" $headline]
1559 if {$i >= 0} {
1560 set headline [string range $headline 0 $i]
1562 set headline [string trimright $headline]
1563 set i [string first "\r" $headline]
1564 if {$i >= 0} {
1565 set headline [string trimright [string range $headline 0 $i]]
1567 if {!$listed} {
1568 # git log indents the comment by 4 spaces;
1569 # if we got this via git cat-file, add the indentation
1570 set newcomment {}
1571 foreach line [split $comment "\n"] {
1572 append newcomment " "
1573 append newcomment $line
1574 append newcomment "\n"
1576 set comment $newcomment
1578 if {$comdate != {}} {
1579 set cdate($id) $comdate
1581 set commitinfo($id) [list $headline $auname $audate \
1582 $comname $comdate $comment]
1585 proc getcommit {id} {
1586 global commitdata commitinfo
1588 if {[info exists commitdata($id)]} {
1589 parsecommit $id $commitdata($id) 1
1590 } else {
1591 readcommit $id
1592 if {![info exists commitinfo($id)]} {
1593 set commitinfo($id) [list [mc "No commit information available"]]
1596 return 1
1599 proc readrefs {} {
1600 global tagids idtags headids idheads tagobjid
1601 global otherrefids idotherrefs mainhead mainheadid
1603 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1604 catch {unset $v}
1606 set refd [open [list | git show-ref -d] r]
1607 while {[gets $refd line] >= 0} {
1608 if {[string index $line 40] ne " "} continue
1609 set id [string range $line 0 39]
1610 set ref [string range $line 41 end]
1611 if {![string match "refs/*" $ref]} continue
1612 set name [string range $ref 5 end]
1613 if {[string match "remotes/*" $name]} {
1614 if {![string match "*/HEAD" $name]} {
1615 set headids($name) $id
1616 lappend idheads($id) $name
1618 } elseif {[string match "heads/*" $name]} {
1619 set name [string range $name 6 end]
1620 set headids($name) $id
1621 lappend idheads($id) $name
1622 } elseif {[string match "tags/*" $name]} {
1623 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1624 # which is what we want since the former is the commit ID
1625 set name [string range $name 5 end]
1626 if {[string match "*^{}" $name]} {
1627 set name [string range $name 0 end-3]
1628 } else {
1629 set tagobjid($name) $id
1631 set tagids($name) $id
1632 lappend idtags($id) $name
1633 } else {
1634 set otherrefids($name) $id
1635 lappend idotherrefs($id) $name
1638 catch {close $refd}
1639 set mainhead {}
1640 set mainheadid {}
1641 catch {
1642 set thehead [exec git symbolic-ref HEAD]
1643 if {[string match "refs/heads/*" $thehead]} {
1644 set mainhead [string range $thehead 11 end]
1645 if {[info exists headids($mainhead)]} {
1646 set mainheadid $headids($mainhead)
1652 # skip over fake commits
1653 proc first_real_row {} {
1654 global nullid nullid2 numcommits
1656 for {set row 0} {$row < $numcommits} {incr row} {
1657 set id [commitonrow $row]
1658 if {$id ne $nullid && $id ne $nullid2} {
1659 break
1662 return $row
1665 # update things for a head moved to a child of its previous location
1666 proc movehead {id name} {
1667 global headids idheads
1669 removehead $headids($name) $name
1670 set headids($name) $id
1671 lappend idheads($id) $name
1674 # update things when a head has been removed
1675 proc removehead {id name} {
1676 global headids idheads
1678 if {$idheads($id) eq $name} {
1679 unset idheads($id)
1680 } else {
1681 set i [lsearch -exact $idheads($id) $name]
1682 if {$i >= 0} {
1683 set idheads($id) [lreplace $idheads($id) $i $i]
1686 unset headids($name)
1689 proc show_error {w top msg} {
1690 message $w.m -text $msg -justify center -aspect 400
1691 pack $w.m -side top -fill x -padx 20 -pady 20
1692 button $w.ok -text [mc OK] -command "destroy $top"
1693 pack $w.ok -side bottom -fill x
1694 bind $top <Visibility> "grab $top; focus $top"
1695 bind $top <Key-Return> "destroy $top"
1696 tkwait window $top
1699 proc error_popup msg {
1700 set w .error
1701 toplevel $w
1702 wm transient $w .
1703 show_error $w $w $msg
1706 proc confirm_popup msg {
1707 global confirm_ok
1708 set confirm_ok 0
1709 set w .confirm
1710 toplevel $w
1711 wm transient $w .
1712 message $w.m -text $msg -justify center -aspect 400
1713 pack $w.m -side top -fill x -padx 20 -pady 20
1714 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1715 pack $w.ok -side left -fill x
1716 button $w.cancel -text [mc Cancel] -command "destroy $w"
1717 pack $w.cancel -side right -fill x
1718 bind $w <Visibility> "grab $w; focus $w"
1719 tkwait window $w
1720 return $confirm_ok
1723 proc setoptions {} {
1724 option add *Panedwindow.showHandle 1 startupFile
1725 option add *Panedwindow.sashRelief raised startupFile
1726 option add *Button.font uifont startupFile
1727 option add *Checkbutton.font uifont startupFile
1728 option add *Radiobutton.font uifont startupFile
1729 option add *Menu.font uifont startupFile
1730 option add *Menubutton.font uifont startupFile
1731 option add *Label.font uifont startupFile
1732 option add *Message.font uifont startupFile
1733 option add *Entry.font uifont startupFile
1736 proc makewindow {} {
1737 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1738 global tabstop
1739 global findtype findtypemenu findloc findstring fstring geometry
1740 global entries sha1entry sha1string sha1but
1741 global diffcontextstring diffcontext
1742 global ignorespace
1743 global maincursor textcursor curtextcursor
1744 global rowctxmenu fakerowmenu mergemax wrapcomment
1745 global highlight_files gdttype
1746 global searchstring sstring
1747 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1748 global headctxmenu progresscanv progressitem progresscoords statusw
1749 global fprogitem fprogcoord lastprogupdate progupdatepending
1750 global rprogitem rprogcoord
1751 global have_tk85
1753 menu .bar
1754 .bar add cascade -label [mc "File"] -menu .bar.file
1755 menu .bar.file
1756 .bar.file add command -label [mc "Update"] -command updatecommits
1757 .bar.file add command -label [mc "Reload"] -command reloadcommits
1758 .bar.file add command -label [mc "Reread references"] -command rereadrefs
1759 .bar.file add command -label [mc "List references"] -command showrefs
1760 .bar.file add command -label [mc "Quit"] -command doquit
1761 menu .bar.edit
1762 .bar add cascade -label [mc "Edit"] -menu .bar.edit
1763 .bar.edit add command -label [mc "Preferences"] -command doprefs
1765 menu .bar.view
1766 .bar add cascade -label [mc "View"] -menu .bar.view
1767 .bar.view add command -label [mc "New view..."] -command {newview 0}
1768 .bar.view add command -label [mc "Edit view..."] -command editview \
1769 -state disabled
1770 .bar.view add command -label [mc "Delete view"] -command delview -state disabled
1771 .bar.view add separator
1772 .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
1773 -variable selectedview -value 0
1775 menu .bar.help
1776 .bar add cascade -label [mc "Help"] -menu .bar.help
1777 .bar.help add command -label [mc "About gitk"] -command about
1778 .bar.help add command -label [mc "Key bindings"] -command keys
1779 .bar.help configure
1780 . configure -menu .bar
1782 # the gui has upper and lower half, parts of a paned window.
1783 panedwindow .ctop -orient vertical
1785 # possibly use assumed geometry
1786 if {![info exists geometry(pwsash0)]} {
1787 set geometry(topheight) [expr {15 * $linespc}]
1788 set geometry(topwidth) [expr {80 * $charspc}]
1789 set geometry(botheight) [expr {15 * $linespc}]
1790 set geometry(botwidth) [expr {50 * $charspc}]
1791 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1792 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1795 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1796 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1797 frame .tf.histframe
1798 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1800 # create three canvases
1801 set cscroll .tf.histframe.csb
1802 set canv .tf.histframe.pwclist.canv
1803 canvas $canv \
1804 -selectbackground $selectbgcolor \
1805 -background $bgcolor -bd 0 \
1806 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1807 .tf.histframe.pwclist add $canv
1808 set canv2 .tf.histframe.pwclist.canv2
1809 canvas $canv2 \
1810 -selectbackground $selectbgcolor \
1811 -background $bgcolor -bd 0 -yscrollincr $linespc
1812 .tf.histframe.pwclist add $canv2
1813 set canv3 .tf.histframe.pwclist.canv3
1814 canvas $canv3 \
1815 -selectbackground $selectbgcolor \
1816 -background $bgcolor -bd 0 -yscrollincr $linespc
1817 .tf.histframe.pwclist add $canv3
1818 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1819 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1821 # a scroll bar to rule them
1822 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1823 pack $cscroll -side right -fill y
1824 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1825 lappend bglist $canv $canv2 $canv3
1826 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1828 # we have two button bars at bottom of top frame. Bar 1
1829 frame .tf.bar
1830 frame .tf.lbar -height 15
1832 set sha1entry .tf.bar.sha1
1833 set entries $sha1entry
1834 set sha1but .tf.bar.sha1label
1835 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1836 -command gotocommit -width 8
1837 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1838 pack .tf.bar.sha1label -side left
1839 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1840 trace add variable sha1string write sha1change
1841 pack $sha1entry -side left -pady 2
1843 image create bitmap bm-left -data {
1844 #define left_width 16
1845 #define left_height 16
1846 static unsigned char left_bits[] = {
1847 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1848 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1849 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1851 image create bitmap bm-right -data {
1852 #define right_width 16
1853 #define right_height 16
1854 static unsigned char right_bits[] = {
1855 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1856 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1857 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1859 button .tf.bar.leftbut -image bm-left -command goback \
1860 -state disabled -width 26
1861 pack .tf.bar.leftbut -side left -fill y
1862 button .tf.bar.rightbut -image bm-right -command goforw \
1863 -state disabled -width 26
1864 pack .tf.bar.rightbut -side left -fill y
1866 # Status label and progress bar
1867 set statusw .tf.bar.status
1868 label $statusw -width 15 -relief sunken
1869 pack $statusw -side left -padx 5
1870 set h [expr {[font metrics uifont -linespace] + 2}]
1871 set progresscanv .tf.bar.progress
1872 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1873 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1874 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1875 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1876 pack $progresscanv -side right -expand 1 -fill x
1877 set progresscoords {0 0}
1878 set fprogcoord 0
1879 set rprogcoord 0
1880 bind $progresscanv <Configure> adjustprogress
1881 set lastprogupdate [clock clicks -milliseconds]
1882 set progupdatepending 0
1884 # build up the bottom bar of upper window
1885 label .tf.lbar.flabel -text "[mc "Find"] "
1886 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1887 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1888 label .tf.lbar.flab2 -text " [mc "commit"] "
1889 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1890 -side left -fill y
1891 set gdttype [mc "containing:"]
1892 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1893 [mc "containing:"] \
1894 [mc "touching paths:"] \
1895 [mc "adding/removing string:"]]
1896 trace add variable gdttype write gdttype_change
1897 pack .tf.lbar.gdttype -side left -fill y
1899 set findstring {}
1900 set fstring .tf.lbar.findstring
1901 lappend entries $fstring
1902 entry $fstring -width 30 -font textfont -textvariable findstring
1903 trace add variable findstring write find_change
1904 set findtype [mc "Exact"]
1905 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1906 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
1907 trace add variable findtype write findcom_change
1908 set findloc [mc "All fields"]
1909 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
1910 [mc "Comments"] [mc "Author"] [mc "Committer"]
1911 trace add variable findloc write find_change
1912 pack .tf.lbar.findloc -side right
1913 pack .tf.lbar.findtype -side right
1914 pack $fstring -side left -expand 1 -fill x
1916 # Finish putting the upper half of the viewer together
1917 pack .tf.lbar -in .tf -side bottom -fill x
1918 pack .tf.bar -in .tf -side bottom -fill x
1919 pack .tf.histframe -fill both -side top -expand 1
1920 .ctop add .tf
1921 .ctop paneconfigure .tf -height $geometry(topheight)
1922 .ctop paneconfigure .tf -width $geometry(topwidth)
1924 # now build up the bottom
1925 panedwindow .pwbottom -orient horizontal
1927 # lower left, a text box over search bar, scroll bar to the right
1928 # if we know window height, then that will set the lower text height, otherwise
1929 # we set lower text height which will drive window height
1930 if {[info exists geometry(main)]} {
1931 frame .bleft -width $geometry(botwidth)
1932 } else {
1933 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1935 frame .bleft.top
1936 frame .bleft.mid
1937 frame .bleft.bottom
1939 button .bleft.top.search -text [mc "Search"] -command dosearch
1940 pack .bleft.top.search -side left -padx 5
1941 set sstring .bleft.top.sstring
1942 entry $sstring -width 20 -font textfont -textvariable searchstring
1943 lappend entries $sstring
1944 trace add variable searchstring write incrsearch
1945 pack $sstring -side left -expand 1 -fill x
1946 radiobutton .bleft.mid.diff -text [mc "Diff"] \
1947 -command changediffdisp -variable diffelide -value {0 0}
1948 radiobutton .bleft.mid.old -text [mc "Old version"] \
1949 -command changediffdisp -variable diffelide -value {0 1}
1950 radiobutton .bleft.mid.new -text [mc "New version"] \
1951 -command changediffdisp -variable diffelide -value {1 0}
1952 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
1953 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1954 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1955 -from 1 -increment 1 -to 10000000 \
1956 -validate all -validatecommand "diffcontextvalidate %P" \
1957 -textvariable diffcontextstring
1958 .bleft.mid.diffcontext set $diffcontext
1959 trace add variable diffcontextstring write diffcontextchange
1960 lappend entries .bleft.mid.diffcontext
1961 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1962 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
1963 -command changeignorespace -variable ignorespace
1964 pack .bleft.mid.ignspace -side left -padx 5
1965 set ctext .bleft.bottom.ctext
1966 text $ctext -background $bgcolor -foreground $fgcolor \
1967 -state disabled -font textfont \
1968 -yscrollcommand scrolltext -wrap none \
1969 -xscrollcommand ".bleft.bottom.sbhorizontal set"
1970 if {$have_tk85} {
1971 $ctext conf -tabstyle wordprocessor
1973 scrollbar .bleft.bottom.sb -command "$ctext yview"
1974 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
1975 -width 10
1976 pack .bleft.top -side top -fill x
1977 pack .bleft.mid -side top -fill x
1978 grid $ctext .bleft.bottom.sb -sticky nsew
1979 grid .bleft.bottom.sbhorizontal -sticky ew
1980 grid columnconfigure .bleft.bottom 0 -weight 1
1981 grid rowconfigure .bleft.bottom 0 -weight 1
1982 grid rowconfigure .bleft.bottom 1 -weight 0
1983 pack .bleft.bottom -side top -fill both -expand 1
1984 lappend bglist $ctext
1985 lappend fglist $ctext
1987 $ctext tag conf comment -wrap $wrapcomment
1988 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
1989 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
1990 $ctext tag conf d0 -fore [lindex $diffcolors 0]
1991 $ctext tag conf d1 -fore [lindex $diffcolors 1]
1992 $ctext tag conf m0 -fore red
1993 $ctext tag conf m1 -fore blue
1994 $ctext tag conf m2 -fore green
1995 $ctext tag conf m3 -fore purple
1996 $ctext tag conf m4 -fore brown
1997 $ctext tag conf m5 -fore "#009090"
1998 $ctext tag conf m6 -fore magenta
1999 $ctext tag conf m7 -fore "#808000"
2000 $ctext tag conf m8 -fore "#009000"
2001 $ctext tag conf m9 -fore "#ff0080"
2002 $ctext tag conf m10 -fore cyan
2003 $ctext tag conf m11 -fore "#b07070"
2004 $ctext tag conf m12 -fore "#70b0f0"
2005 $ctext tag conf m13 -fore "#70f0b0"
2006 $ctext tag conf m14 -fore "#f0b070"
2007 $ctext tag conf m15 -fore "#ff70b0"
2008 $ctext tag conf mmax -fore darkgrey
2009 set mergemax 16
2010 $ctext tag conf mresult -font textfontbold
2011 $ctext tag conf msep -font textfontbold
2012 $ctext tag conf found -back yellow
2014 .pwbottom add .bleft
2015 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2017 # lower right
2018 frame .bright
2019 frame .bright.mode
2020 radiobutton .bright.mode.patch -text [mc "Patch"] \
2021 -command reselectline -variable cmitmode -value "patch"
2022 radiobutton .bright.mode.tree -text [mc "Tree"] \
2023 -command reselectline -variable cmitmode -value "tree"
2024 grid .bright.mode.patch .bright.mode.tree -sticky ew
2025 pack .bright.mode -side top -fill x
2026 set cflist .bright.cfiles
2027 set indent [font measure mainfont "nn"]
2028 text $cflist \
2029 -selectbackground $selectbgcolor \
2030 -background $bgcolor -foreground $fgcolor \
2031 -font mainfont \
2032 -tabs [list $indent [expr {2 * $indent}]] \
2033 -yscrollcommand ".bright.sb set" \
2034 -cursor [. cget -cursor] \
2035 -spacing1 1 -spacing3 1
2036 lappend bglist $cflist
2037 lappend fglist $cflist
2038 scrollbar .bright.sb -command "$cflist yview"
2039 pack .bright.sb -side right -fill y
2040 pack $cflist -side left -fill both -expand 1
2041 $cflist tag configure highlight \
2042 -background [$cflist cget -selectbackground]
2043 $cflist tag configure bold -font mainfontbold
2045 .pwbottom add .bright
2046 .ctop add .pwbottom
2048 # restore window width & height if known
2049 if {[info exists geometry(main)]} {
2050 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2051 if {$w > [winfo screenwidth .]} {
2052 set w [winfo screenwidth .]
2054 if {$h > [winfo screenheight .]} {
2055 set h [winfo screenheight .]
2057 wm geometry . "${w}x$h"
2061 if {[tk windowingsystem] eq {aqua}} {
2062 set M1B M1
2063 } else {
2064 set M1B Control
2067 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2068 pack .ctop -fill both -expand 1
2069 bindall <1> {selcanvline %W %x %y}
2070 #bindall <B1-Motion> {selcanvline %W %x %y}
2071 if {[tk windowingsystem] == "win32"} {
2072 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2073 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2074 } else {
2075 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2076 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2077 if {[tk windowingsystem] eq "aqua"} {
2078 bindall <MouseWheel> {
2079 set delta [expr {- (%D)}]
2080 allcanvs yview scroll $delta units
2084 bindall <2> "canvscan mark %W %x %y"
2085 bindall <B2-Motion> "canvscan dragto %W %x %y"
2086 bindkey <Home> selfirstline
2087 bindkey <End> sellastline
2088 bind . <Key-Up> "selnextline -1"
2089 bind . <Key-Down> "selnextline 1"
2090 bind . <Shift-Key-Up> "dofind -1 0"
2091 bind . <Shift-Key-Down> "dofind 1 0"
2092 bindkey <Key-Right> "goforw"
2093 bindkey <Key-Left> "goback"
2094 bind . <Key-Prior> "selnextpage -1"
2095 bind . <Key-Next> "selnextpage 1"
2096 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2097 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2098 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2099 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2100 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2101 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2102 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2103 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2104 bindkey <Key-space> "$ctext yview scroll 1 pages"
2105 bindkey p "selnextline -1"
2106 bindkey n "selnextline 1"
2107 bindkey z "goback"
2108 bindkey x "goforw"
2109 bindkey i "selnextline -1"
2110 bindkey k "selnextline 1"
2111 bindkey j "goback"
2112 bindkey l "goforw"
2113 bindkey b "$ctext yview scroll -1 pages"
2114 bindkey d "$ctext yview scroll 18 units"
2115 bindkey u "$ctext yview scroll -18 units"
2116 bindkey / {dofind 1 1}
2117 bindkey <Key-Return> {dofind 1 1}
2118 bindkey ? {dofind -1 1}
2119 bindkey f nextfile
2120 bindkey <F5> updatecommits
2121 bind . <$M1B-q> doquit
2122 bind . <$M1B-f> {dofind 1 1}
2123 bind . <$M1B-g> {dofind 1 0}
2124 bind . <$M1B-r> dosearchback
2125 bind . <$M1B-s> dosearch
2126 bind . <$M1B-equal> {incrfont 1}
2127 bind . <$M1B-plus> {incrfont 1}
2128 bind . <$M1B-KP_Add> {incrfont 1}
2129 bind . <$M1B-minus> {incrfont -1}
2130 bind . <$M1B-KP_Subtract> {incrfont -1}
2131 wm protocol . WM_DELETE_WINDOW doquit
2132 bind . <Button-1> "click %W"
2133 bind $fstring <Key-Return> {dofind 1 1}
2134 bind $sha1entry <Key-Return> gotocommit
2135 bind $sha1entry <<PasteSelection>> clearsha1
2136 bind $cflist <1> {sel_flist %W %x %y; break}
2137 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2138 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2139 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
2141 set maincursor [. cget -cursor]
2142 set textcursor [$ctext cget -cursor]
2143 set curtextcursor $textcursor
2145 set rowctxmenu .rowctxmenu
2146 menu $rowctxmenu -tearoff 0
2147 $rowctxmenu add command -label [mc "Diff this -> selected"] \
2148 -command {diffvssel 0}
2149 $rowctxmenu add command -label [mc "Diff selected -> this"] \
2150 -command {diffvssel 1}
2151 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
2152 $rowctxmenu add command -label [mc "Create tag"] -command mktag
2153 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
2154 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
2155 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
2156 -command cherrypick
2157 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
2158 -command resethead
2160 set fakerowmenu .fakerowmenu
2161 menu $fakerowmenu -tearoff 0
2162 $fakerowmenu add command -label [mc "Diff this -> selected"] \
2163 -command {diffvssel 0}
2164 $fakerowmenu add command -label [mc "Diff selected -> this"] \
2165 -command {diffvssel 1}
2166 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
2167 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
2168 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
2169 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
2171 set headctxmenu .headctxmenu
2172 menu $headctxmenu -tearoff 0
2173 $headctxmenu add command -label [mc "Check out this branch"] \
2174 -command cobranch
2175 $headctxmenu add command -label [mc "Remove this branch"] \
2176 -command rmbranch
2178 global flist_menu
2179 set flist_menu .flistctxmenu
2180 menu $flist_menu -tearoff 0
2181 $flist_menu add command -label [mc "Highlight this too"] \
2182 -command {flist_hl 0}
2183 $flist_menu add command -label [mc "Highlight this only"] \
2184 -command {flist_hl 1}
2187 # Windows sends all mouse wheel events to the current focused window, not
2188 # the one where the mouse hovers, so bind those events here and redirect
2189 # to the correct window
2190 proc windows_mousewheel_redirector {W X Y D} {
2191 global canv canv2 canv3
2192 set w [winfo containing -displayof $W $X $Y]
2193 if {$w ne ""} {
2194 set u [expr {$D < 0 ? 5 : -5}]
2195 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2196 allcanvs yview scroll $u units
2197 } else {
2198 catch {
2199 $w yview scroll $u units
2205 # mouse-2 makes all windows scan vertically, but only the one
2206 # the cursor is in scans horizontally
2207 proc canvscan {op w x y} {
2208 global canv canv2 canv3
2209 foreach c [list $canv $canv2 $canv3] {
2210 if {$c == $w} {
2211 $c scan $op $x $y
2212 } else {
2213 $c scan $op 0 $y
2218 proc scrollcanv {cscroll f0 f1} {
2219 $cscroll set $f0 $f1
2220 drawvisible
2221 flushhighlights
2224 # when we make a key binding for the toplevel, make sure
2225 # it doesn't get triggered when that key is pressed in the
2226 # find string entry widget.
2227 proc bindkey {ev script} {
2228 global entries
2229 bind . $ev $script
2230 set escript [bind Entry $ev]
2231 if {$escript == {}} {
2232 set escript [bind Entry <Key>]
2234 foreach e $entries {
2235 bind $e $ev "$escript; break"
2239 # set the focus back to the toplevel for any click outside
2240 # the entry widgets
2241 proc click {w} {
2242 global ctext entries
2243 foreach e [concat $entries $ctext] {
2244 if {$w == $e} return
2246 focus .
2249 # Adjust the progress bar for a change in requested extent or canvas size
2250 proc adjustprogress {} {
2251 global progresscanv progressitem progresscoords
2252 global fprogitem fprogcoord lastprogupdate progupdatepending
2253 global rprogitem rprogcoord
2255 set w [expr {[winfo width $progresscanv] - 4}]
2256 set x0 [expr {$w * [lindex $progresscoords 0]}]
2257 set x1 [expr {$w * [lindex $progresscoords 1]}]
2258 set h [winfo height $progresscanv]
2259 $progresscanv coords $progressitem $x0 0 $x1 $h
2260 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2261 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2262 set now [clock clicks -milliseconds]
2263 if {$now >= $lastprogupdate + 100} {
2264 set progupdatepending 0
2265 update
2266 } elseif {!$progupdatepending} {
2267 set progupdatepending 1
2268 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2272 proc doprogupdate {} {
2273 global lastprogupdate progupdatepending
2275 if {$progupdatepending} {
2276 set progupdatepending 0
2277 set lastprogupdate [clock clicks -milliseconds]
2278 update
2282 proc savestuff {w} {
2283 global canv canv2 canv3 mainfont textfont uifont tabstop
2284 global stuffsaved findmergefiles maxgraphpct
2285 global maxwidth showneartags showlocalchanges
2286 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2287 global cmitmode wrapcomment datetimeformat limitdiffs
2288 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2289 global autoselect
2291 if {$stuffsaved} return
2292 if {![winfo viewable .]} return
2293 catch {
2294 set f [open "~/.gitk-new" w]
2295 puts $f [list set mainfont $mainfont]
2296 puts $f [list set textfont $textfont]
2297 puts $f [list set uifont $uifont]
2298 puts $f [list set tabstop $tabstop]
2299 puts $f [list set findmergefiles $findmergefiles]
2300 puts $f [list set maxgraphpct $maxgraphpct]
2301 puts $f [list set maxwidth $maxwidth]
2302 puts $f [list set cmitmode $cmitmode]
2303 puts $f [list set wrapcomment $wrapcomment]
2304 puts $f [list set autoselect $autoselect]
2305 puts $f [list set showneartags $showneartags]
2306 puts $f [list set showlocalchanges $showlocalchanges]
2307 puts $f [list set datetimeformat $datetimeformat]
2308 puts $f [list set limitdiffs $limitdiffs]
2309 puts $f [list set bgcolor $bgcolor]
2310 puts $f [list set fgcolor $fgcolor]
2311 puts $f [list set colors $colors]
2312 puts $f [list set diffcolors $diffcolors]
2313 puts $f [list set diffcontext $diffcontext]
2314 puts $f [list set selectbgcolor $selectbgcolor]
2316 puts $f "set geometry(main) [wm geometry .]"
2317 puts $f "set geometry(topwidth) [winfo width .tf]"
2318 puts $f "set geometry(topheight) [winfo height .tf]"
2319 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2320 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2321 puts $f "set geometry(botwidth) [winfo width .bleft]"
2322 puts $f "set geometry(botheight) [winfo height .bleft]"
2324 puts -nonewline $f "set permviews {"
2325 for {set v 0} {$v < $nextviewnum} {incr v} {
2326 if {$viewperm($v)} {
2327 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2330 puts $f "}"
2331 close $f
2332 file rename -force "~/.gitk-new" "~/.gitk"
2334 set stuffsaved 1
2337 proc resizeclistpanes {win w} {
2338 global oldwidth
2339 if {[info exists oldwidth($win)]} {
2340 set s0 [$win sash coord 0]
2341 set s1 [$win sash coord 1]
2342 if {$w < 60} {
2343 set sash0 [expr {int($w/2 - 2)}]
2344 set sash1 [expr {int($w*5/6 - 2)}]
2345 } else {
2346 set factor [expr {1.0 * $w / $oldwidth($win)}]
2347 set sash0 [expr {int($factor * [lindex $s0 0])}]
2348 set sash1 [expr {int($factor * [lindex $s1 0])}]
2349 if {$sash0 < 30} {
2350 set sash0 30
2352 if {$sash1 < $sash0 + 20} {
2353 set sash1 [expr {$sash0 + 20}]
2355 if {$sash1 > $w - 10} {
2356 set sash1 [expr {$w - 10}]
2357 if {$sash0 > $sash1 - 20} {
2358 set sash0 [expr {$sash1 - 20}]
2362 $win sash place 0 $sash0 [lindex $s0 1]
2363 $win sash place 1 $sash1 [lindex $s1 1]
2365 set oldwidth($win) $w
2368 proc resizecdetpanes {win w} {
2369 global oldwidth
2370 if {[info exists oldwidth($win)]} {
2371 set s0 [$win sash coord 0]
2372 if {$w < 60} {
2373 set sash0 [expr {int($w*3/4 - 2)}]
2374 } else {
2375 set factor [expr {1.0 * $w / $oldwidth($win)}]
2376 set sash0 [expr {int($factor * [lindex $s0 0])}]
2377 if {$sash0 < 45} {
2378 set sash0 45
2380 if {$sash0 > $w - 15} {
2381 set sash0 [expr {$w - 15}]
2384 $win sash place 0 $sash0 [lindex $s0 1]
2386 set oldwidth($win) $w
2389 proc allcanvs args {
2390 global canv canv2 canv3
2391 eval $canv $args
2392 eval $canv2 $args
2393 eval $canv3 $args
2396 proc bindall {event action} {
2397 global canv canv2 canv3
2398 bind $canv $event $action
2399 bind $canv2 $event $action
2400 bind $canv3 $event $action
2403 proc about {} {
2404 global uifont
2405 set w .about
2406 if {[winfo exists $w]} {
2407 raise $w
2408 return
2410 toplevel $w
2411 wm title $w [mc "About gitk"]
2412 message $w.m -text [mc "
2413 Gitk - a commit viewer for git
2415 Copyright © 2005-2008 Paul Mackerras
2417 Use and redistribute under the terms of the GNU General Public License"] \
2418 -justify center -aspect 400 -border 2 -bg white -relief groove
2419 pack $w.m -side top -fill x -padx 2 -pady 2
2420 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2421 pack $w.ok -side bottom
2422 bind $w <Visibility> "focus $w.ok"
2423 bind $w <Key-Escape> "destroy $w"
2424 bind $w <Key-Return> "destroy $w"
2427 proc keys {} {
2428 set w .keys
2429 if {[winfo exists $w]} {
2430 raise $w
2431 return
2433 if {[tk windowingsystem] eq {aqua}} {
2434 set M1T Cmd
2435 } else {
2436 set M1T Ctrl
2438 toplevel $w
2439 wm title $w [mc "Gitk key bindings"]
2440 message $w.m -text "
2441 [mc "Gitk key bindings:"]
2443 [mc "<%s-Q> Quit" $M1T]
2444 [mc "<Home> Move to first commit"]
2445 [mc "<End> Move to last commit"]
2446 [mc "<Up>, p, i Move up one commit"]
2447 [mc "<Down>, n, k Move down one commit"]
2448 [mc "<Left>, z, j Go back in history list"]
2449 [mc "<Right>, x, l Go forward in history list"]
2450 [mc "<PageUp> Move up one page in commit list"]
2451 [mc "<PageDown> Move down one page in commit list"]
2452 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2453 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2454 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2455 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2456 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2457 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2458 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2459 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2460 [mc "<Delete>, b Scroll diff view up one page"]
2461 [mc "<Backspace> Scroll diff view up one page"]
2462 [mc "<Space> Scroll diff view down one page"]
2463 [mc "u Scroll diff view up 18 lines"]
2464 [mc "d Scroll diff view down 18 lines"]
2465 [mc "<%s-F> Find" $M1T]
2466 [mc "<%s-G> Move to next find hit" $M1T]
2467 [mc "<Return> Move to next find hit"]
2468 [mc "/ Move to next find hit, or redo find"]
2469 [mc "? Move to previous find hit"]
2470 [mc "f Scroll diff view to next file"]
2471 [mc "<%s-S> Search for next hit in diff view" $M1T]
2472 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2473 [mc "<%s-KP+> Increase font size" $M1T]
2474 [mc "<%s-plus> Increase font size" $M1T]
2475 [mc "<%s-KP-> Decrease font size" $M1T]
2476 [mc "<%s-minus> Decrease font size" $M1T]
2477 [mc "<F5> Update"]
2479 -justify left -bg white -border 2 -relief groove
2480 pack $w.m -side top -fill both -padx 2 -pady 2
2481 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2482 pack $w.ok -side bottom
2483 bind $w <Visibility> "focus $w.ok"
2484 bind $w <Key-Escape> "destroy $w"
2485 bind $w <Key-Return> "destroy $w"
2488 # Procedures for manipulating the file list window at the
2489 # bottom right of the overall window.
2491 proc treeview {w l openlevs} {
2492 global treecontents treediropen treeheight treeparent treeindex
2494 set ix 0
2495 set treeindex() 0
2496 set lev 0
2497 set prefix {}
2498 set prefixend -1
2499 set prefendstack {}
2500 set htstack {}
2501 set ht 0
2502 set treecontents() {}
2503 $w conf -state normal
2504 foreach f $l {
2505 while {[string range $f 0 $prefixend] ne $prefix} {
2506 if {$lev <= $openlevs} {
2507 $w mark set e:$treeindex($prefix) "end -1c"
2508 $w mark gravity e:$treeindex($prefix) left
2510 set treeheight($prefix) $ht
2511 incr ht [lindex $htstack end]
2512 set htstack [lreplace $htstack end end]
2513 set prefixend [lindex $prefendstack end]
2514 set prefendstack [lreplace $prefendstack end end]
2515 set prefix [string range $prefix 0 $prefixend]
2516 incr lev -1
2518 set tail [string range $f [expr {$prefixend+1}] end]
2519 while {[set slash [string first "/" $tail]] >= 0} {
2520 lappend htstack $ht
2521 set ht 0
2522 lappend prefendstack $prefixend
2523 incr prefixend [expr {$slash + 1}]
2524 set d [string range $tail 0 $slash]
2525 lappend treecontents($prefix) $d
2526 set oldprefix $prefix
2527 append prefix $d
2528 set treecontents($prefix) {}
2529 set treeindex($prefix) [incr ix]
2530 set treeparent($prefix) $oldprefix
2531 set tail [string range $tail [expr {$slash+1}] end]
2532 if {$lev <= $openlevs} {
2533 set ht 1
2534 set treediropen($prefix) [expr {$lev < $openlevs}]
2535 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2536 $w mark set d:$ix "end -1c"
2537 $w mark gravity d:$ix left
2538 set str "\n"
2539 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2540 $w insert end $str
2541 $w image create end -align center -image $bm -padx 1 \
2542 -name a:$ix
2543 $w insert end $d [highlight_tag $prefix]
2544 $w mark set s:$ix "end -1c"
2545 $w mark gravity s:$ix left
2547 incr lev
2549 if {$tail ne {}} {
2550 if {$lev <= $openlevs} {
2551 incr ht
2552 set str "\n"
2553 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2554 $w insert end $str
2555 $w insert end $tail [highlight_tag $f]
2557 lappend treecontents($prefix) $tail
2560 while {$htstack ne {}} {
2561 set treeheight($prefix) $ht
2562 incr ht [lindex $htstack end]
2563 set htstack [lreplace $htstack end end]
2564 set prefixend [lindex $prefendstack end]
2565 set prefendstack [lreplace $prefendstack end end]
2566 set prefix [string range $prefix 0 $prefixend]
2568 $w conf -state disabled
2571 proc linetoelt {l} {
2572 global treeheight treecontents
2574 set y 2
2575 set prefix {}
2576 while {1} {
2577 foreach e $treecontents($prefix) {
2578 if {$y == $l} {
2579 return "$prefix$e"
2581 set n 1
2582 if {[string index $e end] eq "/"} {
2583 set n $treeheight($prefix$e)
2584 if {$y + $n > $l} {
2585 append prefix $e
2586 incr y
2587 break
2590 incr y $n
2595 proc highlight_tree {y prefix} {
2596 global treeheight treecontents cflist
2598 foreach e $treecontents($prefix) {
2599 set path $prefix$e
2600 if {[highlight_tag $path] ne {}} {
2601 $cflist tag add bold $y.0 "$y.0 lineend"
2603 incr y
2604 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2605 set y [highlight_tree $y $path]
2608 return $y
2611 proc treeclosedir {w dir} {
2612 global treediropen treeheight treeparent treeindex
2614 set ix $treeindex($dir)
2615 $w conf -state normal
2616 $w delete s:$ix e:$ix
2617 set treediropen($dir) 0
2618 $w image configure a:$ix -image tri-rt
2619 $w conf -state disabled
2620 set n [expr {1 - $treeheight($dir)}]
2621 while {$dir ne {}} {
2622 incr treeheight($dir) $n
2623 set dir $treeparent($dir)
2627 proc treeopendir {w dir} {
2628 global treediropen treeheight treeparent treecontents treeindex
2630 set ix $treeindex($dir)
2631 $w conf -state normal
2632 $w image configure a:$ix -image tri-dn
2633 $w mark set e:$ix s:$ix
2634 $w mark gravity e:$ix right
2635 set lev 0
2636 set str "\n"
2637 set n [llength $treecontents($dir)]
2638 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2639 incr lev
2640 append str "\t"
2641 incr treeheight($x) $n
2643 foreach e $treecontents($dir) {
2644 set de $dir$e
2645 if {[string index $e end] eq "/"} {
2646 set iy $treeindex($de)
2647 $w mark set d:$iy e:$ix
2648 $w mark gravity d:$iy left
2649 $w insert e:$ix $str
2650 set treediropen($de) 0
2651 $w image create e:$ix -align center -image tri-rt -padx 1 \
2652 -name a:$iy
2653 $w insert e:$ix $e [highlight_tag $de]
2654 $w mark set s:$iy e:$ix
2655 $w mark gravity s:$iy left
2656 set treeheight($de) 1
2657 } else {
2658 $w insert e:$ix $str
2659 $w insert e:$ix $e [highlight_tag $de]
2662 $w mark gravity e:$ix left
2663 $w conf -state disabled
2664 set treediropen($dir) 1
2665 set top [lindex [split [$w index @0,0] .] 0]
2666 set ht [$w cget -height]
2667 set l [lindex [split [$w index s:$ix] .] 0]
2668 if {$l < $top} {
2669 $w yview $l.0
2670 } elseif {$l + $n + 1 > $top + $ht} {
2671 set top [expr {$l + $n + 2 - $ht}]
2672 if {$l < $top} {
2673 set top $l
2675 $w yview $top.0
2679 proc treeclick {w x y} {
2680 global treediropen cmitmode ctext cflist cflist_top
2682 if {$cmitmode ne "tree"} return
2683 if {![info exists cflist_top]} return
2684 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2685 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2686 $cflist tag add highlight $l.0 "$l.0 lineend"
2687 set cflist_top $l
2688 if {$l == 1} {
2689 $ctext yview 1.0
2690 return
2692 set e [linetoelt $l]
2693 if {[string index $e end] ne "/"} {
2694 showfile $e
2695 } elseif {$treediropen($e)} {
2696 treeclosedir $w $e
2697 } else {
2698 treeopendir $w $e
2702 proc setfilelist {id} {
2703 global treefilelist cflist
2705 treeview $cflist $treefilelist($id) 0
2708 image create bitmap tri-rt -background black -foreground blue -data {
2709 #define tri-rt_width 13
2710 #define tri-rt_height 13
2711 static unsigned char tri-rt_bits[] = {
2712 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2713 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2714 0x00, 0x00};
2715 } -maskdata {
2716 #define tri-rt-mask_width 13
2717 #define tri-rt-mask_height 13
2718 static unsigned char tri-rt-mask_bits[] = {
2719 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2720 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2721 0x08, 0x00};
2723 image create bitmap tri-dn -background black -foreground blue -data {
2724 #define tri-dn_width 13
2725 #define tri-dn_height 13
2726 static unsigned char tri-dn_bits[] = {
2727 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2728 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2729 0x00, 0x00};
2730 } -maskdata {
2731 #define tri-dn-mask_width 13
2732 #define tri-dn-mask_height 13
2733 static unsigned char tri-dn-mask_bits[] = {
2734 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2735 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2736 0x00, 0x00};
2739 image create bitmap reficon-T -background black -foreground yellow -data {
2740 #define tagicon_width 13
2741 #define tagicon_height 9
2742 static unsigned char tagicon_bits[] = {
2743 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2744 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2745 } -maskdata {
2746 #define tagicon-mask_width 13
2747 #define tagicon-mask_height 9
2748 static unsigned char tagicon-mask_bits[] = {
2749 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2750 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2752 set rectdata {
2753 #define headicon_width 13
2754 #define headicon_height 9
2755 static unsigned char headicon_bits[] = {
2756 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2757 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2759 set rectmask {
2760 #define headicon-mask_width 13
2761 #define headicon-mask_height 9
2762 static unsigned char headicon-mask_bits[] = {
2763 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2764 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2766 image create bitmap reficon-H -background black -foreground green \
2767 -data $rectdata -maskdata $rectmask
2768 image create bitmap reficon-o -background black -foreground "#ddddff" \
2769 -data $rectdata -maskdata $rectmask
2771 proc init_flist {first} {
2772 global cflist cflist_top difffilestart
2774 $cflist conf -state normal
2775 $cflist delete 0.0 end
2776 if {$first ne {}} {
2777 $cflist insert end $first
2778 set cflist_top 1
2779 $cflist tag add highlight 1.0 "1.0 lineend"
2780 } else {
2781 catch {unset cflist_top}
2783 $cflist conf -state disabled
2784 set difffilestart {}
2787 proc highlight_tag {f} {
2788 global highlight_paths
2790 foreach p $highlight_paths {
2791 if {[string match $p $f]} {
2792 return "bold"
2795 return {}
2798 proc highlight_filelist {} {
2799 global cmitmode cflist
2801 $cflist conf -state normal
2802 if {$cmitmode ne "tree"} {
2803 set end [lindex [split [$cflist index end] .] 0]
2804 for {set l 2} {$l < $end} {incr l} {
2805 set line [$cflist get $l.0 "$l.0 lineend"]
2806 if {[highlight_tag $line] ne {}} {
2807 $cflist tag add bold $l.0 "$l.0 lineend"
2810 } else {
2811 highlight_tree 2 {}
2813 $cflist conf -state disabled
2816 proc unhighlight_filelist {} {
2817 global cflist
2819 $cflist conf -state normal
2820 $cflist tag remove bold 1.0 end
2821 $cflist conf -state disabled
2824 proc add_flist {fl} {
2825 global cflist
2827 $cflist conf -state normal
2828 foreach f $fl {
2829 $cflist insert end "\n"
2830 $cflist insert end $f [highlight_tag $f]
2832 $cflist conf -state disabled
2835 proc sel_flist {w x y} {
2836 global ctext difffilestart cflist cflist_top cmitmode
2838 if {$cmitmode eq "tree"} return
2839 if {![info exists cflist_top]} return
2840 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2841 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2842 $cflist tag add highlight $l.0 "$l.0 lineend"
2843 set cflist_top $l
2844 if {$l == 1} {
2845 $ctext yview 1.0
2846 } else {
2847 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2851 proc pop_flist_menu {w X Y x y} {
2852 global ctext cflist cmitmode flist_menu flist_menu_file
2853 global treediffs diffids
2855 stopfinding
2856 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2857 if {$l <= 1} return
2858 if {$cmitmode eq "tree"} {
2859 set e [linetoelt $l]
2860 if {[string index $e end] eq "/"} return
2861 } else {
2862 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2864 set flist_menu_file $e
2865 tk_popup $flist_menu $X $Y
2868 proc flist_hl {only} {
2869 global flist_menu_file findstring gdttype
2871 set x [shellquote $flist_menu_file]
2872 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
2873 set findstring $x
2874 } else {
2875 append findstring " " $x
2877 set gdttype [mc "touching paths:"]
2880 # Functions for adding and removing shell-type quoting
2882 proc shellquote {str} {
2883 if {![string match "*\['\"\\ \t]*" $str]} {
2884 return $str
2886 if {![string match "*\['\"\\]*" $str]} {
2887 return "\"$str\""
2889 if {![string match "*'*" $str]} {
2890 return "'$str'"
2892 return "\"[string map {\" \\\" \\ \\\\} $str]\""
2895 proc shellarglist {l} {
2896 set str {}
2897 foreach a $l {
2898 if {$str ne {}} {
2899 append str " "
2901 append str [shellquote $a]
2903 return $str
2906 proc shelldequote {str} {
2907 set ret {}
2908 set used -1
2909 while {1} {
2910 incr used
2911 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
2912 append ret [string range $str $used end]
2913 set used [string length $str]
2914 break
2916 set first [lindex $first 0]
2917 set ch [string index $str $first]
2918 if {$first > $used} {
2919 append ret [string range $str $used [expr {$first - 1}]]
2920 set used $first
2922 if {$ch eq " " || $ch eq "\t"} break
2923 incr used
2924 if {$ch eq "'"} {
2925 set first [string first "'" $str $used]
2926 if {$first < 0} {
2927 error "unmatched single-quote"
2929 append ret [string range $str $used [expr {$first - 1}]]
2930 set used $first
2931 continue
2933 if {$ch eq "\\"} {
2934 if {$used >= [string length $str]} {
2935 error "trailing backslash"
2937 append ret [string index $str $used]
2938 continue
2940 # here ch == "\""
2941 while {1} {
2942 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
2943 error "unmatched double-quote"
2945 set first [lindex $first 0]
2946 set ch [string index $str $first]
2947 if {$first > $used} {
2948 append ret [string range $str $used [expr {$first - 1}]]
2949 set used $first
2951 if {$ch eq "\""} break
2952 incr used
2953 append ret [string index $str $used]
2954 incr used
2957 return [list $used $ret]
2960 proc shellsplit {str} {
2961 set l {}
2962 while {1} {
2963 set str [string trimleft $str]
2964 if {$str eq {}} break
2965 set dq [shelldequote $str]
2966 set n [lindex $dq 0]
2967 set word [lindex $dq 1]
2968 set str [string range $str $n end]
2969 lappend l $word
2971 return $l
2974 # Code to implement multiple views
2976 proc newview {ishighlight} {
2977 global nextviewnum newviewname newviewperm newishighlight
2978 global newviewargs revtreeargs viewargscmd newviewargscmd curview
2980 set newishighlight $ishighlight
2981 set top .gitkview
2982 if {[winfo exists $top]} {
2983 raise $top
2984 return
2986 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
2987 set newviewperm($nextviewnum) 0
2988 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
2989 set newviewargscmd($nextviewnum) $viewargscmd($curview)
2990 vieweditor $top $nextviewnum [mc "Gitk view definition"]
2993 proc editview {} {
2994 global curview
2995 global viewname viewperm newviewname newviewperm
2996 global viewargs newviewargs viewargscmd newviewargscmd
2998 set top .gitkvedit-$curview
2999 if {[winfo exists $top]} {
3000 raise $top
3001 return
3003 set newviewname($curview) $viewname($curview)
3004 set newviewperm($curview) $viewperm($curview)
3005 set newviewargs($curview) [shellarglist $viewargs($curview)]
3006 set newviewargscmd($curview) $viewargscmd($curview)
3007 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3010 proc vieweditor {top n title} {
3011 global newviewname newviewperm viewfiles bgcolor
3013 toplevel $top
3014 wm title $top $title
3015 label $top.nl -text [mc "Name"]
3016 entry $top.name -width 20 -textvariable newviewname($n)
3017 grid $top.nl $top.name -sticky w -pady 5
3018 checkbutton $top.perm -text [mc "Remember this view"] \
3019 -variable newviewperm($n)
3020 grid $top.perm - -pady 5 -sticky w
3021 message $top.al -aspect 1000 \
3022 -text [mc "Commits to include (arguments to git log):"]
3023 grid $top.al - -sticky w -pady 5
3024 entry $top.args -width 50 -textvariable newviewargs($n) \
3025 -background $bgcolor
3026 grid $top.args - -sticky ew -padx 5
3028 message $top.ac -aspect 1000 \
3029 -text [mc "Command to generate more commits to include:"]
3030 grid $top.ac - -sticky w -pady 5
3031 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3032 -background white
3033 grid $top.argscmd - -sticky ew -padx 5
3035 message $top.l -aspect 1000 \
3036 -text [mc "Enter files and directories to include, one per line:"]
3037 grid $top.l - -sticky w
3038 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3039 if {[info exists viewfiles($n)]} {
3040 foreach f $viewfiles($n) {
3041 $top.t insert end $f
3042 $top.t insert end "\n"
3044 $top.t delete {end - 1c} end
3045 $top.t mark set insert 0.0
3047 grid $top.t - -sticky ew -padx 5
3048 frame $top.buts
3049 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3050 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3051 grid $top.buts.ok $top.buts.can
3052 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3053 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3054 grid $top.buts - -pady 10 -sticky ew
3055 focus $top.t
3058 proc doviewmenu {m first cmd op argv} {
3059 set nmenu [$m index end]
3060 for {set i $first} {$i <= $nmenu} {incr i} {
3061 if {[$m entrycget $i -command] eq $cmd} {
3062 eval $m $op $i $argv
3063 break
3068 proc allviewmenus {n op args} {
3069 # global viewhlmenu
3071 doviewmenu .bar.view 5 [list showview $n] $op $args
3072 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3075 proc newviewok {top n} {
3076 global nextviewnum newviewperm newviewname newishighlight
3077 global viewname viewfiles viewperm selectedview curview
3078 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3080 if {[catch {
3081 set newargs [shellsplit $newviewargs($n)]
3082 } err]} {
3083 error_popup "[mc "Error in commit selection arguments:"] $err"
3084 wm raise $top
3085 focus $top
3086 return
3088 set files {}
3089 foreach f [split [$top.t get 0.0 end] "\n"] {
3090 set ft [string trim $f]
3091 if {$ft ne {}} {
3092 lappend files $ft
3095 if {![info exists viewfiles($n)]} {
3096 # creating a new view
3097 incr nextviewnum
3098 set viewname($n) $newviewname($n)
3099 set viewperm($n) $newviewperm($n)
3100 set viewfiles($n) $files
3101 set viewargs($n) $newargs
3102 set viewargscmd($n) $newviewargscmd($n)
3103 addviewmenu $n
3104 if {!$newishighlight} {
3105 run showview $n
3106 } else {
3107 run addvhighlight $n
3109 } else {
3110 # editing an existing view
3111 set viewperm($n) $newviewperm($n)
3112 if {$newviewname($n) ne $viewname($n)} {
3113 set viewname($n) $newviewname($n)
3114 doviewmenu .bar.view 5 [list showview $n] \
3115 entryconf [list -label $viewname($n)]
3116 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3117 # entryconf [list -label $viewname($n) -value $viewname($n)]
3119 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3120 $newviewargscmd($n) ne $viewargscmd($n)} {
3121 set viewfiles($n) $files
3122 set viewargs($n) $newargs
3123 set viewargscmd($n) $newviewargscmd($n)
3124 if {$curview == $n} {
3125 run reloadcommits
3129 catch {destroy $top}
3132 proc delview {} {
3133 global curview viewperm hlview selectedhlview
3135 if {$curview == 0} return
3136 if {[info exists hlview] && $hlview == $curview} {
3137 set selectedhlview [mc "None"]
3138 unset hlview
3140 allviewmenus $curview delete
3141 set viewperm($curview) 0
3142 showview 0
3145 proc addviewmenu {n} {
3146 global viewname viewhlmenu
3148 .bar.view add radiobutton -label $viewname($n) \
3149 -command [list showview $n] -variable selectedview -value $n
3150 #$viewhlmenu add radiobutton -label $viewname($n) \
3151 # -command [list addvhighlight $n] -variable selectedhlview
3154 proc showview {n} {
3155 global curview cached_commitrow ordertok
3156 global displayorder parentlist rowidlist rowisopt rowfinal
3157 global colormap rowtextx nextcolor canvxmax
3158 global numcommits viewcomplete
3159 global selectedline currentid canv canvy0
3160 global treediffs
3161 global pending_select mainheadid
3162 global commitidx
3163 global selectedview
3164 global hlview selectedhlview commitinterest
3166 if {$n == $curview} return
3167 set selid {}
3168 set ymax [lindex [$canv cget -scrollregion] 3]
3169 set span [$canv yview]
3170 set ytop [expr {[lindex $span 0] * $ymax}]
3171 set ybot [expr {[lindex $span 1] * $ymax}]
3172 set yscreen [expr {($ybot - $ytop) / 2}]
3173 if {[info exists selectedline]} {
3174 set selid $currentid
3175 set y [yc $selectedline]
3176 if {$ytop < $y && $y < $ybot} {
3177 set yscreen [expr {$y - $ytop}]
3179 } elseif {[info exists pending_select]} {
3180 set selid $pending_select
3181 unset pending_select
3183 unselectline
3184 normalline
3185 catch {unset treediffs}
3186 clear_display
3187 if {[info exists hlview] && $hlview == $n} {
3188 unset hlview
3189 set selectedhlview [mc "None"]
3191 catch {unset commitinterest}
3192 catch {unset cached_commitrow}
3193 catch {unset ordertok}
3195 set curview $n
3196 set selectedview $n
3197 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3198 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3200 run refill_reflist
3201 if {![info exists viewcomplete($n)]} {
3202 if {$selid ne {}} {
3203 set pending_select $selid
3205 getcommits
3206 return
3209 set displayorder {}
3210 set parentlist {}
3211 set rowidlist {}
3212 set rowisopt {}
3213 set rowfinal {}
3214 set numcommits $commitidx($n)
3216 catch {unset colormap}
3217 catch {unset rowtextx}
3218 set nextcolor 0
3219 set canvxmax [$canv cget -width]
3220 set curview $n
3221 set row 0
3222 setcanvscroll
3223 set yf 0
3224 set row {}
3225 if {$selid ne {} && [commitinview $selid $n]} {
3226 set row [rowofcommit $selid]
3227 # try to get the selected row in the same position on the screen
3228 set ymax [lindex [$canv cget -scrollregion] 3]
3229 set ytop [expr {[yc $row] - $yscreen}]
3230 if {$ytop < 0} {
3231 set ytop 0
3233 set yf [expr {$ytop * 1.0 / $ymax}]
3235 allcanvs yview moveto $yf
3236 drawvisible
3237 if {$row ne {}} {
3238 selectline $row 0
3239 } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
3240 selectline [rowofcommit $mainheadid] 1
3241 } elseif {!$viewcomplete($n)} {
3242 if {$selid ne {}} {
3243 set pending_select $selid
3244 } else {
3245 set pending_select $mainheadid
3247 } else {
3248 set row [first_real_row]
3249 if {$row < $numcommits} {
3250 selectline $row 0
3253 if {!$viewcomplete($n)} {
3254 if {$numcommits == 0} {
3255 show_status [mc "Reading commits..."]
3257 } elseif {$numcommits == 0} {
3258 show_status [mc "No commits selected"]
3262 # Stuff relating to the highlighting facility
3264 proc ishighlighted {id} {
3265 global vhighlights fhighlights nhighlights rhighlights
3267 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3268 return $nhighlights($id)
3270 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3271 return $vhighlights($id)
3273 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3274 return $fhighlights($id)
3276 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3277 return $rhighlights($id)
3279 return 0
3282 proc bolden {row font} {
3283 global canv linehtag selectedline boldrows
3285 lappend boldrows $row
3286 $canv itemconf $linehtag($row) -font $font
3287 if {[info exists selectedline] && $row == $selectedline} {
3288 $canv delete secsel
3289 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3290 -outline {{}} -tags secsel \
3291 -fill [$canv cget -selectbackground]]
3292 $canv lower $t
3296 proc bolden_name {row font} {
3297 global canv2 linentag selectedline boldnamerows
3299 lappend boldnamerows $row
3300 $canv2 itemconf $linentag($row) -font $font
3301 if {[info exists selectedline] && $row == $selectedline} {
3302 $canv2 delete secsel
3303 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3304 -outline {{}} -tags secsel \
3305 -fill [$canv2 cget -selectbackground]]
3306 $canv2 lower $t
3310 proc unbolden {} {
3311 global boldrows
3313 set stillbold {}
3314 foreach row $boldrows {
3315 if {![ishighlighted [commitonrow $row]]} {
3316 bolden $row mainfont
3317 } else {
3318 lappend stillbold $row
3321 set boldrows $stillbold
3324 proc addvhighlight {n} {
3325 global hlview viewcomplete curview vhl_done commitidx
3327 if {[info exists hlview]} {
3328 delvhighlight
3330 set hlview $n
3331 if {$n != $curview && ![info exists viewcomplete($n)]} {
3332 start_rev_list $n
3334 set vhl_done $commitidx($hlview)
3335 if {$vhl_done > 0} {
3336 drawvisible
3340 proc delvhighlight {} {
3341 global hlview vhighlights
3343 if {![info exists hlview]} return
3344 unset hlview
3345 catch {unset vhighlights}
3346 unbolden
3349 proc vhighlightmore {} {
3350 global hlview vhl_done commitidx vhighlights curview
3352 set max $commitidx($hlview)
3353 set vr [visiblerows]
3354 set r0 [lindex $vr 0]
3355 set r1 [lindex $vr 1]
3356 for {set i $vhl_done} {$i < $max} {incr i} {
3357 set id [commitonrow $i $hlview]
3358 if {[commitinview $id $curview]} {
3359 set row [rowofcommit $id]
3360 if {$r0 <= $row && $row <= $r1} {
3361 if {![highlighted $row]} {
3362 bolden $row mainfontbold
3364 set vhighlights($id) 1
3368 set vhl_done $max
3369 return 0
3372 proc askvhighlight {row id} {
3373 global hlview vhighlights iddrawn
3375 if {[commitinview $id $hlview]} {
3376 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3377 bolden $row mainfontbold
3379 set vhighlights($id) 1
3380 } else {
3381 set vhighlights($id) 0
3385 proc hfiles_change {} {
3386 global highlight_files filehighlight fhighlights fh_serial
3387 global highlight_paths gdttype
3389 if {[info exists filehighlight]} {
3390 # delete previous highlights
3391 catch {close $filehighlight}
3392 unset filehighlight
3393 catch {unset fhighlights}
3394 unbolden
3395 unhighlight_filelist
3397 set highlight_paths {}
3398 after cancel do_file_hl $fh_serial
3399 incr fh_serial
3400 if {$highlight_files ne {}} {
3401 after 300 do_file_hl $fh_serial
3405 proc gdttype_change {name ix op} {
3406 global gdttype highlight_files findstring findpattern
3408 stopfinding
3409 if {$findstring ne {}} {
3410 if {$gdttype eq [mc "containing:"]} {
3411 if {$highlight_files ne {}} {
3412 set highlight_files {}
3413 hfiles_change
3415 findcom_change
3416 } else {
3417 if {$findpattern ne {}} {
3418 set findpattern {}
3419 findcom_change
3421 set highlight_files $findstring
3422 hfiles_change
3424 drawvisible
3426 # enable/disable findtype/findloc menus too
3429 proc find_change {name ix op} {
3430 global gdttype findstring highlight_files
3432 stopfinding
3433 if {$gdttype eq [mc "containing:"]} {
3434 findcom_change
3435 } else {
3436 if {$highlight_files ne $findstring} {
3437 set highlight_files $findstring
3438 hfiles_change
3441 drawvisible
3444 proc findcom_change args {
3445 global nhighlights boldnamerows
3446 global findpattern findtype findstring gdttype
3448 stopfinding
3449 # delete previous highlights, if any
3450 foreach row $boldnamerows {
3451 bolden_name $row mainfont
3453 set boldnamerows {}
3454 catch {unset nhighlights}
3455 unbolden
3456 unmarkmatches
3457 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3458 set findpattern {}
3459 } elseif {$findtype eq [mc "Regexp"]} {
3460 set findpattern $findstring
3461 } else {
3462 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3463 $findstring]
3464 set findpattern "*$e*"
3468 proc makepatterns {l} {
3469 set ret {}
3470 foreach e $l {
3471 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3472 if {[string index $ee end] eq "/"} {
3473 lappend ret "$ee*"
3474 } else {
3475 lappend ret $ee
3476 lappend ret "$ee/*"
3479 return $ret
3482 proc do_file_hl {serial} {
3483 global highlight_files filehighlight highlight_paths gdttype fhl_list
3485 if {$gdttype eq [mc "touching paths:"]} {
3486 if {[catch {set paths [shellsplit $highlight_files]}]} return
3487 set highlight_paths [makepatterns $paths]
3488 highlight_filelist
3489 set gdtargs [concat -- $paths]
3490 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3491 set gdtargs [list "-S$highlight_files"]
3492 } else {
3493 # must be "containing:", i.e. we're searching commit info
3494 return
3496 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3497 set filehighlight [open $cmd r+]
3498 fconfigure $filehighlight -blocking 0
3499 filerun $filehighlight readfhighlight
3500 set fhl_list {}
3501 drawvisible
3502 flushhighlights
3505 proc flushhighlights {} {
3506 global filehighlight fhl_list
3508 if {[info exists filehighlight]} {
3509 lappend fhl_list {}
3510 puts $filehighlight ""
3511 flush $filehighlight
3515 proc askfilehighlight {row id} {
3516 global filehighlight fhighlights fhl_list
3518 lappend fhl_list $id
3519 set fhighlights($id) -1
3520 puts $filehighlight $id
3523 proc readfhighlight {} {
3524 global filehighlight fhighlights curview iddrawn
3525 global fhl_list find_dirn
3527 if {![info exists filehighlight]} {
3528 return 0
3530 set nr 0
3531 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3532 set line [string trim $line]
3533 set i [lsearch -exact $fhl_list $line]
3534 if {$i < 0} continue
3535 for {set j 0} {$j < $i} {incr j} {
3536 set id [lindex $fhl_list $j]
3537 set fhighlights($id) 0
3539 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3540 if {$line eq {}} continue
3541 if {![commitinview $line $curview]} continue
3542 set row [rowofcommit $line]
3543 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3544 bolden $row mainfontbold
3546 set fhighlights($line) 1
3548 if {[eof $filehighlight]} {
3549 # strange...
3550 puts "oops, git diff-tree died"
3551 catch {close $filehighlight}
3552 unset filehighlight
3553 return 0
3555 if {[info exists find_dirn]} {
3556 run findmore
3558 return 1
3561 proc doesmatch {f} {
3562 global findtype findpattern
3564 if {$findtype eq [mc "Regexp"]} {
3565 return [regexp $findpattern $f]
3566 } elseif {$findtype eq [mc "IgnCase"]} {
3567 return [string match -nocase $findpattern $f]
3568 } else {
3569 return [string match $findpattern $f]
3573 proc askfindhighlight {row id} {
3574 global nhighlights commitinfo iddrawn
3575 global findloc
3576 global markingmatches
3578 if {![info exists commitinfo($id)]} {
3579 getcommit $id
3581 set info $commitinfo($id)
3582 set isbold 0
3583 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3584 foreach f $info ty $fldtypes {
3585 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3586 [doesmatch $f]} {
3587 if {$ty eq [mc "Author"]} {
3588 set isbold 2
3589 break
3591 set isbold 1
3594 if {$isbold && [info exists iddrawn($id)]} {
3595 if {![ishighlighted $id]} {
3596 bolden $row mainfontbold
3597 if {$isbold > 1} {
3598 bolden_name $row mainfontbold
3601 if {$markingmatches} {
3602 markrowmatches $row $id
3605 set nhighlights($id) $isbold
3608 proc markrowmatches {row id} {
3609 global canv canv2 linehtag linentag commitinfo findloc
3611 set headline [lindex $commitinfo($id) 0]
3612 set author [lindex $commitinfo($id) 1]
3613 $canv delete match$row
3614 $canv2 delete match$row
3615 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3616 set m [findmatches $headline]
3617 if {$m ne {}} {
3618 markmatches $canv $row $headline $linehtag($row) $m \
3619 [$canv itemcget $linehtag($row) -font] $row
3622 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3623 set m [findmatches $author]
3624 if {$m ne {}} {
3625 markmatches $canv2 $row $author $linentag($row) $m \
3626 [$canv2 itemcget $linentag($row) -font] $row
3631 proc vrel_change {name ix op} {
3632 global highlight_related
3634 rhighlight_none
3635 if {$highlight_related ne [mc "None"]} {
3636 run drawvisible
3640 # prepare for testing whether commits are descendents or ancestors of a
3641 proc rhighlight_sel {a} {
3642 global descendent desc_todo ancestor anc_todo
3643 global highlight_related
3645 catch {unset descendent}
3646 set desc_todo [list $a]
3647 catch {unset ancestor}
3648 set anc_todo [list $a]
3649 if {$highlight_related ne [mc "None"]} {
3650 rhighlight_none
3651 run drawvisible
3655 proc rhighlight_none {} {
3656 global rhighlights
3658 catch {unset rhighlights}
3659 unbolden
3662 proc is_descendent {a} {
3663 global curview children descendent desc_todo
3665 set v $curview
3666 set la [rowofcommit $a]
3667 set todo $desc_todo
3668 set leftover {}
3669 set done 0
3670 for {set i 0} {$i < [llength $todo]} {incr i} {
3671 set do [lindex $todo $i]
3672 if {[rowofcommit $do] < $la} {
3673 lappend leftover $do
3674 continue
3676 foreach nk $children($v,$do) {
3677 if {![info exists descendent($nk)]} {
3678 set descendent($nk) 1
3679 lappend todo $nk
3680 if {$nk eq $a} {
3681 set done 1
3685 if {$done} {
3686 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3687 return
3690 set descendent($a) 0
3691 set desc_todo $leftover
3694 proc is_ancestor {a} {
3695 global curview parents ancestor anc_todo
3697 set v $curview
3698 set la [rowofcommit $a]
3699 set todo $anc_todo
3700 set leftover {}
3701 set done 0
3702 for {set i 0} {$i < [llength $todo]} {incr i} {
3703 set do [lindex $todo $i]
3704 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3705 lappend leftover $do
3706 continue
3708 foreach np $parents($v,$do) {
3709 if {![info exists ancestor($np)]} {
3710 set ancestor($np) 1
3711 lappend todo $np
3712 if {$np eq $a} {
3713 set done 1
3717 if {$done} {
3718 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3719 return
3722 set ancestor($a) 0
3723 set anc_todo $leftover
3726 proc askrelhighlight {row id} {
3727 global descendent highlight_related iddrawn rhighlights
3728 global selectedline ancestor
3730 if {![info exists selectedline]} return
3731 set isbold 0
3732 if {$highlight_related eq [mc "Descendant"] ||
3733 $highlight_related eq [mc "Not descendant"]} {
3734 if {![info exists descendent($id)]} {
3735 is_descendent $id
3737 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3738 set isbold 1
3740 } elseif {$highlight_related eq [mc "Ancestor"] ||
3741 $highlight_related eq [mc "Not ancestor"]} {
3742 if {![info exists ancestor($id)]} {
3743 is_ancestor $id
3745 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
3746 set isbold 1
3749 if {[info exists iddrawn($id)]} {
3750 if {$isbold && ![ishighlighted $id]} {
3751 bolden $row mainfontbold
3754 set rhighlights($id) $isbold
3757 # Graph layout functions
3759 proc shortids {ids} {
3760 set res {}
3761 foreach id $ids {
3762 if {[llength $id] > 1} {
3763 lappend res [shortids $id]
3764 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3765 lappend res [string range $id 0 7]
3766 } else {
3767 lappend res $id
3770 return $res
3773 proc ntimes {n o} {
3774 set ret {}
3775 set o [list $o]
3776 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3777 if {($n & $mask) != 0} {
3778 set ret [concat $ret $o]
3780 set o [concat $o $o]
3782 return $ret
3785 proc ordertoken {id} {
3786 global ordertok curview varcid varcstart varctok curview parents children
3787 global nullid nullid2
3789 if {[info exists ordertok($id)]} {
3790 return $ordertok($id)
3792 set origid $id
3793 set todo {}
3794 while {1} {
3795 if {[info exists varcid($curview,$id)]} {
3796 set a $varcid($curview,$id)
3797 set p [lindex $varcstart($curview) $a]
3798 } else {
3799 set p [lindex $children($curview,$id) 0]
3801 if {[info exists ordertok($p)]} {
3802 set tok $ordertok($p)
3803 break
3805 set id [first_real_child $curview,$p]
3806 if {$id eq {}} {
3807 # it's a root
3808 set tok [lindex $varctok($curview) $varcid($curview,$p)]
3809 break
3811 if {[llength $parents($curview,$id)] == 1} {
3812 lappend todo [list $p {}]
3813 } else {
3814 set j [lsearch -exact $parents($curview,$id) $p]
3815 if {$j < 0} {
3816 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3818 lappend todo [list $p [strrep $j]]
3821 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3822 set p [lindex $todo $i 0]
3823 append tok [lindex $todo $i 1]
3824 set ordertok($p) $tok
3826 set ordertok($origid) $tok
3827 return $tok
3830 # Work out where id should go in idlist so that order-token
3831 # values increase from left to right
3832 proc idcol {idlist id {i 0}} {
3833 set t [ordertoken $id]
3834 if {$i < 0} {
3835 set i 0
3837 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3838 if {$i > [llength $idlist]} {
3839 set i [llength $idlist]
3841 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3842 incr i
3843 } else {
3844 if {$t > [ordertoken [lindex $idlist $i]]} {
3845 while {[incr i] < [llength $idlist] &&
3846 $t >= [ordertoken [lindex $idlist $i]]} {}
3849 return $i
3852 proc initlayout {} {
3853 global rowidlist rowisopt rowfinal displayorder parentlist
3854 global numcommits canvxmax canv
3855 global nextcolor
3856 global colormap rowtextx
3858 set numcommits 0
3859 set displayorder {}
3860 set parentlist {}
3861 set nextcolor 0
3862 set rowidlist {}
3863 set rowisopt {}
3864 set rowfinal {}
3865 set canvxmax [$canv cget -width]
3866 catch {unset colormap}
3867 catch {unset rowtextx}
3868 setcanvscroll
3871 proc setcanvscroll {} {
3872 global canv canv2 canv3 numcommits linespc canvxmax canvy0
3873 global lastscrollset lastscrollrows
3875 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3876 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3877 $canv2 conf -scrollregion [list 0 0 0 $ymax]
3878 $canv3 conf -scrollregion [list 0 0 0 $ymax]
3879 set lastscrollset [clock clicks -milliseconds]
3880 set lastscrollrows $numcommits
3883 proc visiblerows {} {
3884 global canv numcommits linespc
3886 set ymax [lindex [$canv cget -scrollregion] 3]
3887 if {$ymax eq {} || $ymax == 0} return
3888 set f [$canv yview]
3889 set y0 [expr {int([lindex $f 0] * $ymax)}]
3890 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
3891 if {$r0 < 0} {
3892 set r0 0
3894 set y1 [expr {int([lindex $f 1] * $ymax)}]
3895 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
3896 if {$r1 >= $numcommits} {
3897 set r1 [expr {$numcommits - 1}]
3899 return [list $r0 $r1]
3902 proc layoutmore {} {
3903 global commitidx viewcomplete curview
3904 global numcommits pending_select selectedline curview
3905 global lastscrollset lastscrollrows commitinterest
3907 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
3908 [clock clicks -milliseconds] - $lastscrollset > 500} {
3909 setcanvscroll
3911 if {[info exists pending_select] &&
3912 [commitinview $pending_select $curview]} {
3913 selectline [rowofcommit $pending_select] 1
3915 drawvisible
3918 proc doshowlocalchanges {} {
3919 global curview mainheadid
3921 if {[commitinview $mainheadid $curview]} {
3922 dodiffindex
3923 } else {
3924 lappend commitinterest($mainheadid) {dodiffindex}
3928 proc dohidelocalchanges {} {
3929 global nullid nullid2 lserial curview
3931 if {[commitinview $nullid $curview]} {
3932 removefakerow $nullid
3934 if {[commitinview $nullid2 $curview]} {
3935 removefakerow $nullid2
3937 incr lserial
3940 # spawn off a process to do git diff-index --cached HEAD
3941 proc dodiffindex {} {
3942 global lserial showlocalchanges
3943 global isworktree
3945 if {!$showlocalchanges || !$isworktree} return
3946 incr lserial
3947 set fd [open "|git diff-index --cached HEAD" r]
3948 fconfigure $fd -blocking 0
3949 filerun $fd [list readdiffindex $fd $lserial]
3952 proc readdiffindex {fd serial} {
3953 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
3955 set isdiff 1
3956 if {[gets $fd line] < 0} {
3957 if {![eof $fd]} {
3958 return 1
3960 set isdiff 0
3962 # we only need to see one line and we don't really care what it says...
3963 close $fd
3965 if {$serial != $lserial} {
3966 return 0
3969 # now see if there are any local changes not checked in to the index
3970 set fd [open "|git diff-files" r]
3971 fconfigure $fd -blocking 0
3972 filerun $fd [list readdifffiles $fd $serial]
3974 if {$isdiff && ![commitinview $nullid2 $curview]} {
3975 # add the line for the changes in the index to the graph
3976 set hl [mc "Local changes checked in to index but not committed"]
3977 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
3978 set commitdata($nullid2) "\n $hl\n"
3979 if {[commitinview $nullid $curview]} {
3980 removefakerow $nullid
3982 insertfakerow $nullid2 $mainheadid
3983 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
3984 removefakerow $nullid2
3986 return 0
3989 proc readdifffiles {fd serial} {
3990 global mainheadid nullid nullid2 curview
3991 global commitinfo commitdata lserial
3993 set isdiff 1
3994 if {[gets $fd line] < 0} {
3995 if {![eof $fd]} {
3996 return 1
3998 set isdiff 0
4000 # we only need to see one line and we don't really care what it says...
4001 close $fd
4003 if {$serial != $lserial} {
4004 return 0
4007 if {$isdiff && ![commitinview $nullid $curview]} {
4008 # add the line for the local diff to the graph
4009 set hl [mc "Local uncommitted changes, not checked in to index"]
4010 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4011 set commitdata($nullid) "\n $hl\n"
4012 if {[commitinview $nullid2 $curview]} {
4013 set p $nullid2
4014 } else {
4015 set p $mainheadid
4017 insertfakerow $nullid $p
4018 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4019 removefakerow $nullid
4021 return 0
4024 proc nextuse {id row} {
4025 global curview children
4027 if {[info exists children($curview,$id)]} {
4028 foreach kid $children($curview,$id) {
4029 if {![commitinview $kid $curview]} {
4030 return -1
4032 if {[rowofcommit $kid] > $row} {
4033 return [rowofcommit $kid]
4037 if {[commitinview $id $curview]} {
4038 return [rowofcommit $id]
4040 return -1
4043 proc prevuse {id row} {
4044 global curview children
4046 set ret -1
4047 if {[info exists children($curview,$id)]} {
4048 foreach kid $children($curview,$id) {
4049 if {![commitinview $kid $curview]} break
4050 if {[rowofcommit $kid] < $row} {
4051 set ret [rowofcommit $kid]
4055 return $ret
4058 proc make_idlist {row} {
4059 global displayorder parentlist uparrowlen downarrowlen mingaplen
4060 global commitidx curview children
4062 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4063 if {$r < 0} {
4064 set r 0
4066 set ra [expr {$row - $downarrowlen}]
4067 if {$ra < 0} {
4068 set ra 0
4070 set rb [expr {$row + $uparrowlen}]
4071 if {$rb > $commitidx($curview)} {
4072 set rb $commitidx($curview)
4074 make_disporder $r [expr {$rb + 1}]
4075 set ids {}
4076 for {} {$r < $ra} {incr r} {
4077 set nextid [lindex $displayorder [expr {$r + 1}]]
4078 foreach p [lindex $parentlist $r] {
4079 if {$p eq $nextid} continue
4080 set rn [nextuse $p $r]
4081 if {$rn >= $row &&
4082 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4083 lappend ids [list [ordertoken $p] $p]
4087 for {} {$r < $row} {incr r} {
4088 set nextid [lindex $displayorder [expr {$r + 1}]]
4089 foreach p [lindex $parentlist $r] {
4090 if {$p eq $nextid} continue
4091 set rn [nextuse $p $r]
4092 if {$rn < 0 || $rn >= $row} {
4093 lappend ids [list [ordertoken $p] $p]
4097 set id [lindex $displayorder $row]
4098 lappend ids [list [ordertoken $id] $id]
4099 while {$r < $rb} {
4100 foreach p [lindex $parentlist $r] {
4101 set firstkid [lindex $children($curview,$p) 0]
4102 if {[rowofcommit $firstkid] < $row} {
4103 lappend ids [list [ordertoken $p] $p]
4106 incr r
4107 set id [lindex $displayorder $r]
4108 if {$id ne {}} {
4109 set firstkid [lindex $children($curview,$id) 0]
4110 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4111 lappend ids [list [ordertoken $id] $id]
4115 set idlist {}
4116 foreach idx [lsort -unique $ids] {
4117 lappend idlist [lindex $idx 1]
4119 return $idlist
4122 proc rowsequal {a b} {
4123 while {[set i [lsearch -exact $a {}]] >= 0} {
4124 set a [lreplace $a $i $i]
4126 while {[set i [lsearch -exact $b {}]] >= 0} {
4127 set b [lreplace $b $i $i]
4129 return [expr {$a eq $b}]
4132 proc makeupline {id row rend col} {
4133 global rowidlist uparrowlen downarrowlen mingaplen
4135 for {set r $rend} {1} {set r $rstart} {
4136 set rstart [prevuse $id $r]
4137 if {$rstart < 0} return
4138 if {$rstart < $row} break
4140 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4141 set rstart [expr {$rend - $uparrowlen - 1}]
4143 for {set r $rstart} {[incr r] <= $row} {} {
4144 set idlist [lindex $rowidlist $r]
4145 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4146 set col [idcol $idlist $id $col]
4147 lset rowidlist $r [linsert $idlist $col $id]
4148 changedrow $r
4153 proc layoutrows {row endrow} {
4154 global rowidlist rowisopt rowfinal displayorder
4155 global uparrowlen downarrowlen maxwidth mingaplen
4156 global children parentlist
4157 global commitidx viewcomplete curview
4159 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4160 set idlist {}
4161 if {$row > 0} {
4162 set rm1 [expr {$row - 1}]
4163 foreach id [lindex $rowidlist $rm1] {
4164 if {$id ne {}} {
4165 lappend idlist $id
4168 set final [lindex $rowfinal $rm1]
4170 for {} {$row < $endrow} {incr row} {
4171 set rm1 [expr {$row - 1}]
4172 if {$rm1 < 0 || $idlist eq {}} {
4173 set idlist [make_idlist $row]
4174 set final 1
4175 } else {
4176 set id [lindex $displayorder $rm1]
4177 set col [lsearch -exact $idlist $id]
4178 set idlist [lreplace $idlist $col $col]
4179 foreach p [lindex $parentlist $rm1] {
4180 if {[lsearch -exact $idlist $p] < 0} {
4181 set col [idcol $idlist $p $col]
4182 set idlist [linsert $idlist $col $p]
4183 # if not the first child, we have to insert a line going up
4184 if {$id ne [lindex $children($curview,$p) 0]} {
4185 makeupline $p $rm1 $row $col
4189 set id [lindex $displayorder $row]
4190 if {$row > $downarrowlen} {
4191 set termrow [expr {$row - $downarrowlen - 1}]
4192 foreach p [lindex $parentlist $termrow] {
4193 set i [lsearch -exact $idlist $p]
4194 if {$i < 0} continue
4195 set nr [nextuse $p $termrow]
4196 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4197 set idlist [lreplace $idlist $i $i]
4201 set col [lsearch -exact $idlist $id]
4202 if {$col < 0} {
4203 set col [idcol $idlist $id]
4204 set idlist [linsert $idlist $col $id]
4205 if {$children($curview,$id) ne {}} {
4206 makeupline $id $rm1 $row $col
4209 set r [expr {$row + $uparrowlen - 1}]
4210 if {$r < $commitidx($curview)} {
4211 set x $col
4212 foreach p [lindex $parentlist $r] {
4213 if {[lsearch -exact $idlist $p] >= 0} continue
4214 set fk [lindex $children($curview,$p) 0]
4215 if {[rowofcommit $fk] < $row} {
4216 set x [idcol $idlist $p $x]
4217 set idlist [linsert $idlist $x $p]
4220 if {[incr r] < $commitidx($curview)} {
4221 set p [lindex $displayorder $r]
4222 if {[lsearch -exact $idlist $p] < 0} {
4223 set fk [lindex $children($curview,$p) 0]
4224 if {$fk ne {} && [rowofcommit $fk] < $row} {
4225 set x [idcol $idlist $p $x]
4226 set idlist [linsert $idlist $x $p]
4232 if {$final && !$viewcomplete($curview) &&
4233 $row + $uparrowlen + $mingaplen + $downarrowlen
4234 >= $commitidx($curview)} {
4235 set final 0
4237 set l [llength $rowidlist]
4238 if {$row == $l} {
4239 lappend rowidlist $idlist
4240 lappend rowisopt 0
4241 lappend rowfinal $final
4242 } elseif {$row < $l} {
4243 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4244 lset rowidlist $row $idlist
4245 changedrow $row
4247 lset rowfinal $row $final
4248 } else {
4249 set pad [ntimes [expr {$row - $l}] {}]
4250 set rowidlist [concat $rowidlist $pad]
4251 lappend rowidlist $idlist
4252 set rowfinal [concat $rowfinal $pad]
4253 lappend rowfinal $final
4254 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4257 return $row
4260 proc changedrow {row} {
4261 global displayorder iddrawn rowisopt need_redisplay
4263 set l [llength $rowisopt]
4264 if {$row < $l} {
4265 lset rowisopt $row 0
4266 if {$row + 1 < $l} {
4267 lset rowisopt [expr {$row + 1}] 0
4268 if {$row + 2 < $l} {
4269 lset rowisopt [expr {$row + 2}] 0
4273 set id [lindex $displayorder $row]
4274 if {[info exists iddrawn($id)]} {
4275 set need_redisplay 1
4279 proc insert_pad {row col npad} {
4280 global rowidlist
4282 set pad [ntimes $npad {}]
4283 set idlist [lindex $rowidlist $row]
4284 set bef [lrange $idlist 0 [expr {$col - 1}]]
4285 set aft [lrange $idlist $col end]
4286 set i [lsearch -exact $aft {}]
4287 if {$i > 0} {
4288 set aft [lreplace $aft $i $i]
4290 lset rowidlist $row [concat $bef $pad $aft]
4291 changedrow $row
4294 proc optimize_rows {row col endrow} {
4295 global rowidlist rowisopt displayorder curview children
4297 if {$row < 1} {
4298 set row 1
4300 for {} {$row < $endrow} {incr row; set col 0} {
4301 if {[lindex $rowisopt $row]} continue
4302 set haspad 0
4303 set y0 [expr {$row - 1}]
4304 set ym [expr {$row - 2}]
4305 set idlist [lindex $rowidlist $row]
4306 set previdlist [lindex $rowidlist $y0]
4307 if {$idlist eq {} || $previdlist eq {}} continue
4308 if {$ym >= 0} {
4309 set pprevidlist [lindex $rowidlist $ym]
4310 if {$pprevidlist eq {}} continue
4311 } else {
4312 set pprevidlist {}
4314 set x0 -1
4315 set xm -1
4316 for {} {$col < [llength $idlist]} {incr col} {
4317 set id [lindex $idlist $col]
4318 if {[lindex $previdlist $col] eq $id} continue
4319 if {$id eq {}} {
4320 set haspad 1
4321 continue
4323 set x0 [lsearch -exact $previdlist $id]
4324 if {$x0 < 0} continue
4325 set z [expr {$x0 - $col}]
4326 set isarrow 0
4327 set z0 {}
4328 if {$ym >= 0} {
4329 set xm [lsearch -exact $pprevidlist $id]
4330 if {$xm >= 0} {
4331 set z0 [expr {$xm - $x0}]
4334 if {$z0 eq {}} {
4335 # if row y0 is the first child of $id then it's not an arrow
4336 if {[lindex $children($curview,$id) 0] ne
4337 [lindex $displayorder $y0]} {
4338 set isarrow 1
4341 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4342 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4343 set isarrow 1
4345 # Looking at lines from this row to the previous row,
4346 # make them go straight up if they end in an arrow on
4347 # the previous row; otherwise make them go straight up
4348 # or at 45 degrees.
4349 if {$z < -1 || ($z < 0 && $isarrow)} {
4350 # Line currently goes left too much;
4351 # insert pads in the previous row, then optimize it
4352 set npad [expr {-1 - $z + $isarrow}]
4353 insert_pad $y0 $x0 $npad
4354 if {$y0 > 0} {
4355 optimize_rows $y0 $x0 $row
4357 set previdlist [lindex $rowidlist $y0]
4358 set x0 [lsearch -exact $previdlist $id]
4359 set z [expr {$x0 - $col}]
4360 if {$z0 ne {}} {
4361 set pprevidlist [lindex $rowidlist $ym]
4362 set xm [lsearch -exact $pprevidlist $id]
4363 set z0 [expr {$xm - $x0}]
4365 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4366 # Line currently goes right too much;
4367 # insert pads in this line
4368 set npad [expr {$z - 1 + $isarrow}]
4369 insert_pad $row $col $npad
4370 set idlist [lindex $rowidlist $row]
4371 incr col $npad
4372 set z [expr {$x0 - $col}]
4373 set haspad 1
4375 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4376 # this line links to its first child on row $row-2
4377 set id [lindex $displayorder $ym]
4378 set xc [lsearch -exact $pprevidlist $id]
4379 if {$xc >= 0} {
4380 set z0 [expr {$xc - $x0}]
4383 # avoid lines jigging left then immediately right
4384 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4385 insert_pad $y0 $x0 1
4386 incr x0
4387 optimize_rows $y0 $x0 $row
4388 set previdlist [lindex $rowidlist $y0]
4391 if {!$haspad} {
4392 # Find the first column that doesn't have a line going right
4393 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4394 set id [lindex $idlist $col]
4395 if {$id eq {}} break
4396 set x0 [lsearch -exact $previdlist $id]
4397 if {$x0 < 0} {
4398 # check if this is the link to the first child
4399 set kid [lindex $displayorder $y0]
4400 if {[lindex $children($curview,$id) 0] eq $kid} {
4401 # it is, work out offset to child
4402 set x0 [lsearch -exact $previdlist $kid]
4405 if {$x0 <= $col} break
4407 # Insert a pad at that column as long as it has a line and
4408 # isn't the last column
4409 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4410 set idlist [linsert $idlist $col {}]
4411 lset rowidlist $row $idlist
4412 changedrow $row
4418 proc xc {row col} {
4419 global canvx0 linespc
4420 return [expr {$canvx0 + $col * $linespc}]
4423 proc yc {row} {
4424 global canvy0 linespc
4425 return [expr {$canvy0 + $row * $linespc}]
4428 proc linewidth {id} {
4429 global thickerline lthickness
4431 set wid $lthickness
4432 if {[info exists thickerline] && $id eq $thickerline} {
4433 set wid [expr {2 * $lthickness}]
4435 return $wid
4438 proc rowranges {id} {
4439 global curview children uparrowlen downarrowlen
4440 global rowidlist
4442 set kids $children($curview,$id)
4443 if {$kids eq {}} {
4444 return {}
4446 set ret {}
4447 lappend kids $id
4448 foreach child $kids {
4449 if {![commitinview $child $curview]} break
4450 set row [rowofcommit $child]
4451 if {![info exists prev]} {
4452 lappend ret [expr {$row + 1}]
4453 } else {
4454 if {$row <= $prevrow} {
4455 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4457 # see if the line extends the whole way from prevrow to row
4458 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4459 [lsearch -exact [lindex $rowidlist \
4460 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4461 # it doesn't, see where it ends
4462 set r [expr {$prevrow + $downarrowlen}]
4463 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4464 while {[incr r -1] > $prevrow &&
4465 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4466 } else {
4467 while {[incr r] <= $row &&
4468 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4469 incr r -1
4471 lappend ret $r
4472 # see where it starts up again
4473 set r [expr {$row - $uparrowlen}]
4474 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4475 while {[incr r] < $row &&
4476 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4477 } else {
4478 while {[incr r -1] >= $prevrow &&
4479 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4480 incr r
4482 lappend ret $r
4485 if {$child eq $id} {
4486 lappend ret $row
4488 set prev $child
4489 set prevrow $row
4491 return $ret
4494 proc drawlineseg {id row endrow arrowlow} {
4495 global rowidlist displayorder iddrawn linesegs
4496 global canv colormap linespc curview maxlinelen parentlist
4498 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4499 set le [expr {$row + 1}]
4500 set arrowhigh 1
4501 while {1} {
4502 set c [lsearch -exact [lindex $rowidlist $le] $id]
4503 if {$c < 0} {
4504 incr le -1
4505 break
4507 lappend cols $c
4508 set x [lindex $displayorder $le]
4509 if {$x eq $id} {
4510 set arrowhigh 0
4511 break
4513 if {[info exists iddrawn($x)] || $le == $endrow} {
4514 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4515 if {$c >= 0} {
4516 lappend cols $c
4517 set arrowhigh 0
4519 break
4521 incr le
4523 if {$le <= $row} {
4524 return $row
4527 set lines {}
4528 set i 0
4529 set joinhigh 0
4530 if {[info exists linesegs($id)]} {
4531 set lines $linesegs($id)
4532 foreach li $lines {
4533 set r0 [lindex $li 0]
4534 if {$r0 > $row} {
4535 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4536 set joinhigh 1
4538 break
4540 incr i
4543 set joinlow 0
4544 if {$i > 0} {
4545 set li [lindex $lines [expr {$i-1}]]
4546 set r1 [lindex $li 1]
4547 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4548 set joinlow 1
4552 set x [lindex $cols [expr {$le - $row}]]
4553 set xp [lindex $cols [expr {$le - 1 - $row}]]
4554 set dir [expr {$xp - $x}]
4555 if {$joinhigh} {
4556 set ith [lindex $lines $i 2]
4557 set coords [$canv coords $ith]
4558 set ah [$canv itemcget $ith -arrow]
4559 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4560 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4561 if {$x2 ne {} && $x - $x2 == $dir} {
4562 set coords [lrange $coords 0 end-2]
4564 } else {
4565 set coords [list [xc $le $x] [yc $le]]
4567 if {$joinlow} {
4568 set itl [lindex $lines [expr {$i-1}] 2]
4569 set al [$canv itemcget $itl -arrow]
4570 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4571 } elseif {$arrowlow} {
4572 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4573 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4574 set arrowlow 0
4577 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4578 for {set y $le} {[incr y -1] > $row} {} {
4579 set x $xp
4580 set xp [lindex $cols [expr {$y - 1 - $row}]]
4581 set ndir [expr {$xp - $x}]
4582 if {$dir != $ndir || $xp < 0} {
4583 lappend coords [xc $y $x] [yc $y]
4585 set dir $ndir
4587 if {!$joinlow} {
4588 if {$xp < 0} {
4589 # join parent line to first child
4590 set ch [lindex $displayorder $row]
4591 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4592 if {$xc < 0} {
4593 puts "oops: drawlineseg: child $ch not on row $row"
4594 } elseif {$xc != $x} {
4595 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4596 set d [expr {int(0.5 * $linespc)}]
4597 set x1 [xc $row $x]
4598 if {$xc < $x} {
4599 set x2 [expr {$x1 - $d}]
4600 } else {
4601 set x2 [expr {$x1 + $d}]
4603 set y2 [yc $row]
4604 set y1 [expr {$y2 + $d}]
4605 lappend coords $x1 $y1 $x2 $y2
4606 } elseif {$xc < $x - 1} {
4607 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4608 } elseif {$xc > $x + 1} {
4609 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4611 set x $xc
4613 lappend coords [xc $row $x] [yc $row]
4614 } else {
4615 set xn [xc $row $xp]
4616 set yn [yc $row]
4617 lappend coords $xn $yn
4619 if {!$joinhigh} {
4620 assigncolor $id
4621 set t [$canv create line $coords -width [linewidth $id] \
4622 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4623 $canv lower $t
4624 bindline $t $id
4625 set lines [linsert $lines $i [list $row $le $t]]
4626 } else {
4627 $canv coords $ith $coords
4628 if {$arrow ne $ah} {
4629 $canv itemconf $ith -arrow $arrow
4631 lset lines $i 0 $row
4633 } else {
4634 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4635 set ndir [expr {$xo - $xp}]
4636 set clow [$canv coords $itl]
4637 if {$dir == $ndir} {
4638 set clow [lrange $clow 2 end]
4640 set coords [concat $coords $clow]
4641 if {!$joinhigh} {
4642 lset lines [expr {$i-1}] 1 $le
4643 } else {
4644 # coalesce two pieces
4645 $canv delete $ith
4646 set b [lindex $lines [expr {$i-1}] 0]
4647 set e [lindex $lines $i 1]
4648 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4650 $canv coords $itl $coords
4651 if {$arrow ne $al} {
4652 $canv itemconf $itl -arrow $arrow
4656 set linesegs($id) $lines
4657 return $le
4660 proc drawparentlinks {id row} {
4661 global rowidlist canv colormap curview parentlist
4662 global idpos linespc
4664 set rowids [lindex $rowidlist $row]
4665 set col [lsearch -exact $rowids $id]
4666 if {$col < 0} return
4667 set olds [lindex $parentlist $row]
4668 set row2 [expr {$row + 1}]
4669 set x [xc $row $col]
4670 set y [yc $row]
4671 set y2 [yc $row2]
4672 set d [expr {int(0.5 * $linespc)}]
4673 set ymid [expr {$y + $d}]
4674 set ids [lindex $rowidlist $row2]
4675 # rmx = right-most X coord used
4676 set rmx 0
4677 foreach p $olds {
4678 set i [lsearch -exact $ids $p]
4679 if {$i < 0} {
4680 puts "oops, parent $p of $id not in list"
4681 continue
4683 set x2 [xc $row2 $i]
4684 if {$x2 > $rmx} {
4685 set rmx $x2
4687 set j [lsearch -exact $rowids $p]
4688 if {$j < 0} {
4689 # drawlineseg will do this one for us
4690 continue
4692 assigncolor $p
4693 # should handle duplicated parents here...
4694 set coords [list $x $y]
4695 if {$i != $col} {
4696 # if attaching to a vertical segment, draw a smaller
4697 # slant for visual distinctness
4698 if {$i == $j} {
4699 if {$i < $col} {
4700 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4701 } else {
4702 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4704 } elseif {$i < $col && $i < $j} {
4705 # segment slants towards us already
4706 lappend coords [xc $row $j] $y
4707 } else {
4708 if {$i < $col - 1} {
4709 lappend coords [expr {$x2 + $linespc}] $y
4710 } elseif {$i > $col + 1} {
4711 lappend coords [expr {$x2 - $linespc}] $y
4713 lappend coords $x2 $y2
4715 } else {
4716 lappend coords $x2 $y2
4718 set t [$canv create line $coords -width [linewidth $p] \
4719 -fill $colormap($p) -tags lines.$p]
4720 $canv lower $t
4721 bindline $t $p
4723 if {$rmx > [lindex $idpos($id) 1]} {
4724 lset idpos($id) 1 $rmx
4725 redrawtags $id
4729 proc drawlines {id} {
4730 global canv
4732 $canv itemconf lines.$id -width [linewidth $id]
4735 proc drawcmittext {id row col} {
4736 global linespc canv canv2 canv3 fgcolor curview
4737 global cmitlisted commitinfo rowidlist parentlist
4738 global rowtextx idpos idtags idheads idotherrefs
4739 global linehtag linentag linedtag selectedline
4740 global canvxmax boldrows boldnamerows fgcolor nullid nullid2
4742 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
4743 set listed $cmitlisted($curview,$id)
4744 if {$id eq $nullid} {
4745 set ofill red
4746 } elseif {$id eq $nullid2} {
4747 set ofill green
4748 } else {
4749 set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}]
4751 set x [xc $row $col]
4752 set y [yc $row]
4753 set orad [expr {$linespc / 3}]
4754 if {$listed <= 2} {
4755 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4756 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4757 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4758 } elseif {$listed == 3} {
4759 # triangle pointing left for left-side commits
4760 set t [$canv create polygon \
4761 [expr {$x - $orad}] $y \
4762 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4763 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4764 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4765 } else {
4766 # triangle pointing right for right-side commits
4767 set t [$canv create polygon \
4768 [expr {$x + $orad - 1}] $y \
4769 [expr {$x - $orad}] [expr {$y - $orad}] \
4770 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4771 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4773 $canv raise $t
4774 $canv bind $t <1> {selcanvline {} %x %y}
4775 set rmx [llength [lindex $rowidlist $row]]
4776 set olds [lindex $parentlist $row]
4777 if {$olds ne {}} {
4778 set nextids [lindex $rowidlist [expr {$row + 1}]]
4779 foreach p $olds {
4780 set i [lsearch -exact $nextids $p]
4781 if {$i > $rmx} {
4782 set rmx $i
4786 set xt [xc $row $rmx]
4787 set rowtextx($row) $xt
4788 set idpos($id) [list $x $xt $y]
4789 if {[info exists idtags($id)] || [info exists idheads($id)]
4790 || [info exists idotherrefs($id)]} {
4791 set xt [drawtags $id $x $xt $y]
4793 set headline [lindex $commitinfo($id) 0]
4794 set name [lindex $commitinfo($id) 1]
4795 set date [lindex $commitinfo($id) 2]
4796 set date [formatdate $date]
4797 set font mainfont
4798 set nfont mainfont
4799 set isbold [ishighlighted $id]
4800 if {$isbold > 0} {
4801 lappend boldrows $row
4802 set font mainfontbold
4803 if {$isbold > 1} {
4804 lappend boldnamerows $row
4805 set nfont mainfontbold
4808 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4809 -text $headline -font $font -tags text]
4810 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4811 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4812 -text $name -font $nfont -tags text]
4813 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4814 -text $date -font mainfont -tags text]
4815 if {[info exists selectedline] && $selectedline == $row} {
4816 make_secsel $row
4818 set xr [expr {$xt + [font measure $font $headline]}]
4819 if {$xr > $canvxmax} {
4820 set canvxmax $xr
4821 setcanvscroll
4825 proc drawcmitrow {row} {
4826 global displayorder rowidlist nrows_drawn
4827 global iddrawn markingmatches
4828 global commitinfo numcommits
4829 global filehighlight fhighlights findpattern nhighlights
4830 global hlview vhighlights
4831 global highlight_related rhighlights
4833 if {$row >= $numcommits} return
4835 set id [lindex $displayorder $row]
4836 if {[info exists hlview] && ![info exists vhighlights($id)]} {
4837 askvhighlight $row $id
4839 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
4840 askfilehighlight $row $id
4842 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
4843 askfindhighlight $row $id
4845 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
4846 askrelhighlight $row $id
4848 if {![info exists iddrawn($id)]} {
4849 set col [lsearch -exact [lindex $rowidlist $row] $id]
4850 if {$col < 0} {
4851 puts "oops, row $row id $id not in list"
4852 return
4854 if {![info exists commitinfo($id)]} {
4855 getcommit $id
4857 assigncolor $id
4858 drawcmittext $id $row $col
4859 set iddrawn($id) 1
4860 incr nrows_drawn
4862 if {$markingmatches} {
4863 markrowmatches $row $id
4867 proc drawcommits {row {endrow {}}} {
4868 global numcommits iddrawn displayorder curview need_redisplay
4869 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
4871 if {$row < 0} {
4872 set row 0
4874 if {$endrow eq {}} {
4875 set endrow $row
4877 if {$endrow >= $numcommits} {
4878 set endrow [expr {$numcommits - 1}]
4881 set rl1 [expr {$row - $downarrowlen - 3}]
4882 if {$rl1 < 0} {
4883 set rl1 0
4885 set ro1 [expr {$row - 3}]
4886 if {$ro1 < 0} {
4887 set ro1 0
4889 set r2 [expr {$endrow + $uparrowlen + 3}]
4890 if {$r2 > $numcommits} {
4891 set r2 $numcommits
4893 for {set r $rl1} {$r < $r2} {incr r} {
4894 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
4895 if {$rl1 < $r} {
4896 layoutrows $rl1 $r
4898 set rl1 [expr {$r + 1}]
4901 if {$rl1 < $r} {
4902 layoutrows $rl1 $r
4904 optimize_rows $ro1 0 $r2
4905 if {$need_redisplay || $nrows_drawn > 2000} {
4906 clear_display
4907 drawvisible
4910 # make the lines join to already-drawn rows either side
4911 set r [expr {$row - 1}]
4912 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
4913 set r $row
4915 set er [expr {$endrow + 1}]
4916 if {$er >= $numcommits ||
4917 ![info exists iddrawn([lindex $displayorder $er])]} {
4918 set er $endrow
4920 for {} {$r <= $er} {incr r} {
4921 set id [lindex $displayorder $r]
4922 set wasdrawn [info exists iddrawn($id)]
4923 drawcmitrow $r
4924 if {$r == $er} break
4925 set nextid [lindex $displayorder [expr {$r + 1}]]
4926 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
4927 drawparentlinks $id $r
4929 set rowids [lindex $rowidlist $r]
4930 foreach lid $rowids {
4931 if {$lid eq {}} continue
4932 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
4933 if {$lid eq $id} {
4934 # see if this is the first child of any of its parents
4935 foreach p [lindex $parentlist $r] {
4936 if {[lsearch -exact $rowids $p] < 0} {
4937 # make this line extend up to the child
4938 set lineend($p) [drawlineseg $p $r $er 0]
4941 } else {
4942 set lineend($lid) [drawlineseg $lid $r $er 1]
4948 proc undolayout {row} {
4949 global uparrowlen mingaplen downarrowlen
4950 global rowidlist rowisopt rowfinal need_redisplay
4952 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
4953 if {$r < 0} {
4954 set r 0
4956 if {[llength $rowidlist] > $r} {
4957 incr r -1
4958 set rowidlist [lrange $rowidlist 0 $r]
4959 set rowfinal [lrange $rowfinal 0 $r]
4960 set rowisopt [lrange $rowisopt 0 $r]
4961 set need_redisplay 1
4962 run drawvisible
4966 proc drawvisible {} {
4967 global canv linespc curview vrowmod selectedline targetrow targetid
4968 global need_redisplay cscroll numcommits
4970 set fs [$canv yview]
4971 set ymax [lindex [$canv cget -scrollregion] 3]
4972 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
4973 set f0 [lindex $fs 0]
4974 set f1 [lindex $fs 1]
4975 set y0 [expr {int($f0 * $ymax)}]
4976 set y1 [expr {int($f1 * $ymax)}]
4978 if {[info exists targetid]} {
4979 if {[commitinview $targetid $curview]} {
4980 set r [rowofcommit $targetid]
4981 if {$r != $targetrow} {
4982 # Fix up the scrollregion and change the scrolling position
4983 # now that our target row has moved.
4984 set diff [expr {($r - $targetrow) * $linespc}]
4985 set targetrow $r
4986 setcanvscroll
4987 set ymax [lindex [$canv cget -scrollregion] 3]
4988 incr y0 $diff
4989 incr y1 $diff
4990 set f0 [expr {$y0 / $ymax}]
4991 set f1 [expr {$y1 / $ymax}]
4992 allcanvs yview moveto $f0
4993 $cscroll set $f0 $f1
4994 set need_redisplay 1
4996 } else {
4997 unset targetid
5001 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5002 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5003 if {$endrow >= $vrowmod($curview)} {
5004 update_arcrows $curview
5006 if {[info exists selectedline] &&
5007 $row <= $selectedline && $selectedline <= $endrow} {
5008 set targetrow $selectedline
5009 } elseif {[info exists targetid]} {
5010 set targetrow [expr {int(($row + $endrow) / 2)}]
5012 if {[info exists targetrow]} {
5013 if {$targetrow >= $numcommits} {
5014 set targetrow [expr {$numcommits - 1}]
5016 set targetid [commitonrow $targetrow]
5018 drawcommits $row $endrow
5021 proc clear_display {} {
5022 global iddrawn linesegs need_redisplay nrows_drawn
5023 global vhighlights fhighlights nhighlights rhighlights
5025 allcanvs delete all
5026 catch {unset iddrawn}
5027 catch {unset linesegs}
5028 catch {unset vhighlights}
5029 catch {unset fhighlights}
5030 catch {unset nhighlights}
5031 catch {unset rhighlights}
5032 set need_redisplay 0
5033 set nrows_drawn 0
5036 proc findcrossings {id} {
5037 global rowidlist parentlist numcommits displayorder
5039 set cross {}
5040 set ccross {}
5041 foreach {s e} [rowranges $id] {
5042 if {$e >= $numcommits} {
5043 set e [expr {$numcommits - 1}]
5045 if {$e <= $s} continue
5046 for {set row $e} {[incr row -1] >= $s} {} {
5047 set x [lsearch -exact [lindex $rowidlist $row] $id]
5048 if {$x < 0} break
5049 set olds [lindex $parentlist $row]
5050 set kid [lindex $displayorder $row]
5051 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5052 if {$kidx < 0} continue
5053 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5054 foreach p $olds {
5055 set px [lsearch -exact $nextrow $p]
5056 if {$px < 0} continue
5057 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5058 if {[lsearch -exact $ccross $p] >= 0} continue
5059 if {$x == $px + ($kidx < $px? -1: 1)} {
5060 lappend ccross $p
5061 } elseif {[lsearch -exact $cross $p] < 0} {
5062 lappend cross $p
5068 return [concat $ccross {{}} $cross]
5071 proc assigncolor {id} {
5072 global colormap colors nextcolor
5073 global parents children children curview
5075 if {[info exists colormap($id)]} return
5076 set ncolors [llength $colors]
5077 if {[info exists children($curview,$id)]} {
5078 set kids $children($curview,$id)
5079 } else {
5080 set kids {}
5082 if {[llength $kids] == 1} {
5083 set child [lindex $kids 0]
5084 if {[info exists colormap($child)]
5085 && [llength $parents($curview,$child)] == 1} {
5086 set colormap($id) $colormap($child)
5087 return
5090 set badcolors {}
5091 set origbad {}
5092 foreach x [findcrossings $id] {
5093 if {$x eq {}} {
5094 # delimiter between corner crossings and other crossings
5095 if {[llength $badcolors] >= $ncolors - 1} break
5096 set origbad $badcolors
5098 if {[info exists colormap($x)]
5099 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5100 lappend badcolors $colormap($x)
5103 if {[llength $badcolors] >= $ncolors} {
5104 set badcolors $origbad
5106 set origbad $badcolors
5107 if {[llength $badcolors] < $ncolors - 1} {
5108 foreach child $kids {
5109 if {[info exists colormap($child)]
5110 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5111 lappend badcolors $colormap($child)
5113 foreach p $parents($curview,$child) {
5114 if {[info exists colormap($p)]
5115 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5116 lappend badcolors $colormap($p)
5120 if {[llength $badcolors] >= $ncolors} {
5121 set badcolors $origbad
5124 for {set i 0} {$i <= $ncolors} {incr i} {
5125 set c [lindex $colors $nextcolor]
5126 if {[incr nextcolor] >= $ncolors} {
5127 set nextcolor 0
5129 if {[lsearch -exact $badcolors $c]} break
5131 set colormap($id) $c
5134 proc bindline {t id} {
5135 global canv
5137 $canv bind $t <Enter> "lineenter %x %y $id"
5138 $canv bind $t <Motion> "linemotion %x %y $id"
5139 $canv bind $t <Leave> "lineleave $id"
5140 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5143 proc drawtags {id x xt y1} {
5144 global idtags idheads idotherrefs mainhead
5145 global linespc lthickness
5146 global canv rowtextx curview fgcolor bgcolor
5148 set marks {}
5149 set ntags 0
5150 set nheads 0
5151 if {[info exists idtags($id)]} {
5152 set marks $idtags($id)
5153 set ntags [llength $marks]
5155 if {[info exists idheads($id)]} {
5156 set marks [concat $marks $idheads($id)]
5157 set nheads [llength $idheads($id)]
5159 if {[info exists idotherrefs($id)]} {
5160 set marks [concat $marks $idotherrefs($id)]
5162 if {$marks eq {}} {
5163 return $xt
5166 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5167 set yt [expr {$y1 - 0.5 * $linespc}]
5168 set yb [expr {$yt + $linespc - 1}]
5169 set xvals {}
5170 set wvals {}
5171 set i -1
5172 foreach tag $marks {
5173 incr i
5174 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5175 set wid [font measure mainfontbold $tag]
5176 } else {
5177 set wid [font measure mainfont $tag]
5179 lappend xvals $xt
5180 lappend wvals $wid
5181 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5183 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5184 -width $lthickness -fill black -tags tag.$id]
5185 $canv lower $t
5186 foreach tag $marks x $xvals wid $wvals {
5187 set xl [expr {$x + $delta}]
5188 set xr [expr {$x + $delta + $wid + $lthickness}]
5189 set font mainfont
5190 if {[incr ntags -1] >= 0} {
5191 # draw a tag
5192 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5193 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5194 -width 1 -outline black -fill yellow -tags tag.$id]
5195 $canv bind $t <1> [list showtag $tag 1]
5196 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5197 } else {
5198 # draw a head or other ref
5199 if {[incr nheads -1] >= 0} {
5200 set col green
5201 if {$tag eq $mainhead} {
5202 set font mainfontbold
5204 } else {
5205 set col "#ddddff"
5207 set xl [expr {$xl - $delta/2}]
5208 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5209 -width 1 -outline black -fill $col -tags tag.$id
5210 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5211 set rwid [font measure mainfont $remoteprefix]
5212 set xi [expr {$x + 1}]
5213 set yti [expr {$yt + 1}]
5214 set xri [expr {$x + $rwid}]
5215 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5216 -width 0 -fill "#ffddaa" -tags tag.$id
5219 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5220 -font $font -tags [list tag.$id text]]
5221 if {$ntags >= 0} {
5222 $canv bind $t <1> [list showtag $tag 1]
5223 } elseif {$nheads >= 0} {
5224 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
5227 return $xt
5230 proc xcoord {i level ln} {
5231 global canvx0 xspc1 xspc2
5233 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5234 if {$i > 0 && $i == $level} {
5235 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5236 } elseif {$i > $level} {
5237 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5239 return $x
5242 proc show_status {msg} {
5243 global canv fgcolor
5245 clear_display
5246 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5247 -tags text -fill $fgcolor
5250 # Don't change the text pane cursor if it is currently the hand cursor,
5251 # showing that we are over a sha1 ID link.
5252 proc settextcursor {c} {
5253 global ctext curtextcursor
5255 if {[$ctext cget -cursor] == $curtextcursor} {
5256 $ctext config -cursor $c
5258 set curtextcursor $c
5261 proc nowbusy {what {name {}}} {
5262 global isbusy busyname statusw
5264 if {[array names isbusy] eq {}} {
5265 . config -cursor watch
5266 settextcursor watch
5268 set isbusy($what) 1
5269 set busyname($what) $name
5270 if {$name ne {}} {
5271 $statusw conf -text $name
5275 proc notbusy {what} {
5276 global isbusy maincursor textcursor busyname statusw
5278 catch {
5279 unset isbusy($what)
5280 if {$busyname($what) ne {} &&
5281 [$statusw cget -text] eq $busyname($what)} {
5282 $statusw conf -text {}
5285 if {[array names isbusy] eq {}} {
5286 . config -cursor $maincursor
5287 settextcursor $textcursor
5291 proc findmatches {f} {
5292 global findtype findstring
5293 if {$findtype == [mc "Regexp"]} {
5294 set matches [regexp -indices -all -inline $findstring $f]
5295 } else {
5296 set fs $findstring
5297 if {$findtype == [mc "IgnCase"]} {
5298 set f [string tolower $f]
5299 set fs [string tolower $fs]
5301 set matches {}
5302 set i 0
5303 set l [string length $fs]
5304 while {[set j [string first $fs $f $i]] >= 0} {
5305 lappend matches [list $j [expr {$j+$l-1}]]
5306 set i [expr {$j + $l}]
5309 return $matches
5312 proc dofind {{dirn 1} {wrap 1}} {
5313 global findstring findstartline findcurline selectedline numcommits
5314 global gdttype filehighlight fh_serial find_dirn findallowwrap
5316 if {[info exists find_dirn]} {
5317 if {$find_dirn == $dirn} return
5318 stopfinding
5320 focus .
5321 if {$findstring eq {} || $numcommits == 0} return
5322 if {![info exists selectedline]} {
5323 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5324 } else {
5325 set findstartline $selectedline
5327 set findcurline $findstartline
5328 nowbusy finding [mc "Searching"]
5329 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5330 after cancel do_file_hl $fh_serial
5331 do_file_hl $fh_serial
5333 set find_dirn $dirn
5334 set findallowwrap $wrap
5335 run findmore
5338 proc stopfinding {} {
5339 global find_dirn findcurline fprogcoord
5341 if {[info exists find_dirn]} {
5342 unset find_dirn
5343 unset findcurline
5344 notbusy finding
5345 set fprogcoord 0
5346 adjustprogress
5350 proc findmore {} {
5351 global commitdata commitinfo numcommits findpattern findloc
5352 global findstartline findcurline findallowwrap
5353 global find_dirn gdttype fhighlights fprogcoord
5354 global curview varcorder vrownum varccommits vrowmod
5356 if {![info exists find_dirn]} {
5357 return 0
5359 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5360 set l $findcurline
5361 set moretodo 0
5362 if {$find_dirn > 0} {
5363 incr l
5364 if {$l >= $numcommits} {
5365 set l 0
5367 if {$l <= $findstartline} {
5368 set lim [expr {$findstartline + 1}]
5369 } else {
5370 set lim $numcommits
5371 set moretodo $findallowwrap
5373 } else {
5374 if {$l == 0} {
5375 set l $numcommits
5377 incr l -1
5378 if {$l >= $findstartline} {
5379 set lim [expr {$findstartline - 1}]
5380 } else {
5381 set lim -1
5382 set moretodo $findallowwrap
5385 set n [expr {($lim - $l) * $find_dirn}]
5386 if {$n > 500} {
5387 set n 500
5388 set moretodo 1
5390 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5391 update_arcrows $curview
5393 set found 0
5394 set domore 1
5395 set ai [bsearch $vrownum($curview) $l]
5396 set a [lindex $varcorder($curview) $ai]
5397 set arow [lindex $vrownum($curview) $ai]
5398 set ids [lindex $varccommits($curview,$a)]
5399 set arowend [expr {$arow + [llength $ids]}]
5400 if {$gdttype eq [mc "containing:"]} {
5401 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5402 if {$l < $arow || $l >= $arowend} {
5403 incr ai $find_dirn
5404 set a [lindex $varcorder($curview) $ai]
5405 set arow [lindex $vrownum($curview) $ai]
5406 set ids [lindex $varccommits($curview,$a)]
5407 set arowend [expr {$arow + [llength $ids]}]
5409 set id [lindex $ids [expr {$l - $arow}]]
5410 # shouldn't happen unless git log doesn't give all the commits...
5411 if {![info exists commitdata($id)] ||
5412 ![doesmatch $commitdata($id)]} {
5413 continue
5415 if {![info exists commitinfo($id)]} {
5416 getcommit $id
5418 set info $commitinfo($id)
5419 foreach f $info ty $fldtypes {
5420 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5421 [doesmatch $f]} {
5422 set found 1
5423 break
5426 if {$found} break
5428 } else {
5429 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5430 if {$l < $arow || $l >= $arowend} {
5431 incr ai $find_dirn
5432 set a [lindex $varcorder($curview) $ai]
5433 set arow [lindex $vrownum($curview) $ai]
5434 set ids [lindex $varccommits($curview,$a)]
5435 set arowend [expr {$arow + [llength $ids]}]
5437 set id [lindex $ids [expr {$l - $arow}]]
5438 if {![info exists fhighlights($id)]} {
5439 # this sets fhighlights($id) to -1
5440 askfilehighlight $l $id
5442 if {$fhighlights($id) > 0} {
5443 set found $domore
5444 break
5446 if {$fhighlights($id) < 0} {
5447 if {$domore} {
5448 set domore 0
5449 set findcurline [expr {$l - $find_dirn}]
5454 if {$found || ($domore && !$moretodo)} {
5455 unset findcurline
5456 unset find_dirn
5457 notbusy finding
5458 set fprogcoord 0
5459 adjustprogress
5460 if {$found} {
5461 findselectline $l
5462 } else {
5463 bell
5465 return 0
5467 if {!$domore} {
5468 flushhighlights
5469 } else {
5470 set findcurline [expr {$l - $find_dirn}]
5472 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5473 if {$n < 0} {
5474 incr n $numcommits
5476 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5477 adjustprogress
5478 return $domore
5481 proc findselectline {l} {
5482 global findloc commentend ctext findcurline markingmatches gdttype
5484 set markingmatches 1
5485 set findcurline $l
5486 selectline $l 1
5487 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5488 # highlight the matches in the comments
5489 set f [$ctext get 1.0 $commentend]
5490 set matches [findmatches $f]
5491 foreach match $matches {
5492 set start [lindex $match 0]
5493 set end [expr {[lindex $match 1] + 1}]
5494 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5497 drawvisible
5500 # mark the bits of a headline or author that match a find string
5501 proc markmatches {canv l str tag matches font row} {
5502 global selectedline
5504 set bbox [$canv bbox $tag]
5505 set x0 [lindex $bbox 0]
5506 set y0 [lindex $bbox 1]
5507 set y1 [lindex $bbox 3]
5508 foreach match $matches {
5509 set start [lindex $match 0]
5510 set end [lindex $match 1]
5511 if {$start > $end} continue
5512 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5513 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5514 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5515 [expr {$x0+$xlen+2}] $y1 \
5516 -outline {} -tags [list match$l matches] -fill yellow]
5517 $canv lower $t
5518 if {[info exists selectedline] && $row == $selectedline} {
5519 $canv raise $t secsel
5524 proc unmarkmatches {} {
5525 global markingmatches
5527 allcanvs delete matches
5528 set markingmatches 0
5529 stopfinding
5532 proc selcanvline {w x y} {
5533 global canv canvy0 ctext linespc
5534 global rowtextx
5535 set ymax [lindex [$canv cget -scrollregion] 3]
5536 if {$ymax == {}} return
5537 set yfrac [lindex [$canv yview] 0]
5538 set y [expr {$y + $yfrac * $ymax}]
5539 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5540 if {$l < 0} {
5541 set l 0
5543 if {$w eq $canv} {
5544 set xmax [lindex [$canv cget -scrollregion] 2]
5545 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5546 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5548 unmarkmatches
5549 selectline $l 1
5552 proc commit_descriptor {p} {
5553 global commitinfo
5554 if {![info exists commitinfo($p)]} {
5555 getcommit $p
5557 set l "..."
5558 if {[llength $commitinfo($p)] > 1} {
5559 set l [lindex $commitinfo($p) 0]
5561 return "$p ($l)\n"
5564 # append some text to the ctext widget, and make any SHA1 ID
5565 # that we know about be a clickable link.
5566 proc appendwithlinks {text tags} {
5567 global ctext linknum curview pendinglinks
5569 set start [$ctext index "end - 1c"]
5570 $ctext insert end $text $tags
5571 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5572 foreach l $links {
5573 set s [lindex $l 0]
5574 set e [lindex $l 1]
5575 set linkid [string range $text $s $e]
5576 incr e
5577 $ctext tag delete link$linknum
5578 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5579 setlink $linkid link$linknum
5580 incr linknum
5584 proc setlink {id lk} {
5585 global curview ctext pendinglinks commitinterest
5587 if {[commitinview $id $curview]} {
5588 $ctext tag conf $lk -foreground blue -underline 1
5589 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5590 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5591 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5592 } else {
5593 lappend pendinglinks($id) $lk
5594 lappend commitinterest($id) {makelink %I}
5598 proc makelink {id} {
5599 global pendinglinks
5601 if {![info exists pendinglinks($id)]} return
5602 foreach lk $pendinglinks($id) {
5603 setlink $id $lk
5605 unset pendinglinks($id)
5608 proc linkcursor {w inc} {
5609 global linkentercount curtextcursor
5611 if {[incr linkentercount $inc] > 0} {
5612 $w configure -cursor hand2
5613 } else {
5614 $w configure -cursor $curtextcursor
5615 if {$linkentercount < 0} {
5616 set linkentercount 0
5621 proc viewnextline {dir} {
5622 global canv linespc
5624 $canv delete hover
5625 set ymax [lindex [$canv cget -scrollregion] 3]
5626 set wnow [$canv yview]
5627 set wtop [expr {[lindex $wnow 0] * $ymax}]
5628 set newtop [expr {$wtop + $dir * $linespc}]
5629 if {$newtop < 0} {
5630 set newtop 0
5631 } elseif {$newtop > $ymax} {
5632 set newtop $ymax
5634 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5637 # add a list of tag or branch names at position pos
5638 # returns the number of names inserted
5639 proc appendrefs {pos ids var} {
5640 global ctext linknum curview $var maxrefs
5642 if {[catch {$ctext index $pos}]} {
5643 return 0
5645 $ctext conf -state normal
5646 $ctext delete $pos "$pos lineend"
5647 set tags {}
5648 foreach id $ids {
5649 foreach tag [set $var\($id\)] {
5650 lappend tags [list $tag $id]
5653 if {[llength $tags] > $maxrefs} {
5654 $ctext insert $pos "many ([llength $tags])"
5655 } else {
5656 set tags [lsort -index 0 -decreasing $tags]
5657 set sep {}
5658 foreach ti $tags {
5659 set id [lindex $ti 1]
5660 set lk link$linknum
5661 incr linknum
5662 $ctext tag delete $lk
5663 $ctext insert $pos $sep
5664 $ctext insert $pos [lindex $ti 0] $lk
5665 setlink $id $lk
5666 set sep ", "
5669 $ctext conf -state disabled
5670 return [llength $tags]
5673 # called when we have finished computing the nearby tags
5674 proc dispneartags {delay} {
5675 global selectedline currentid showneartags tagphase
5677 if {![info exists selectedline] || !$showneartags} return
5678 after cancel dispnexttag
5679 if {$delay} {
5680 after 200 dispnexttag
5681 set tagphase -1
5682 } else {
5683 after idle dispnexttag
5684 set tagphase 0
5688 proc dispnexttag {} {
5689 global selectedline currentid showneartags tagphase ctext
5691 if {![info exists selectedline] || !$showneartags} return
5692 switch -- $tagphase {
5694 set dtags [desctags $currentid]
5695 if {$dtags ne {}} {
5696 appendrefs precedes $dtags idtags
5700 set atags [anctags $currentid]
5701 if {$atags ne {}} {
5702 appendrefs follows $atags idtags
5706 set dheads [descheads $currentid]
5707 if {$dheads ne {}} {
5708 if {[appendrefs branch $dheads idheads] > 1
5709 && [$ctext get "branch -3c"] eq "h"} {
5710 # turn "Branch" into "Branches"
5711 $ctext conf -state normal
5712 $ctext insert "branch -2c" "es"
5713 $ctext conf -state disabled
5718 if {[incr tagphase] <= 2} {
5719 after idle dispnexttag
5723 proc make_secsel {l} {
5724 global linehtag linentag linedtag canv canv2 canv3
5726 if {![info exists linehtag($l)]} return
5727 $canv delete secsel
5728 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5729 -tags secsel -fill [$canv cget -selectbackground]]
5730 $canv lower $t
5731 $canv2 delete secsel
5732 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5733 -tags secsel -fill [$canv2 cget -selectbackground]]
5734 $canv2 lower $t
5735 $canv3 delete secsel
5736 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5737 -tags secsel -fill [$canv3 cget -selectbackground]]
5738 $canv3 lower $t
5741 proc selectline {l isnew} {
5742 global canv ctext commitinfo selectedline
5743 global canvy0 linespc parents children curview
5744 global currentid sha1entry
5745 global commentend idtags linknum
5746 global mergemax numcommits pending_select
5747 global cmitmode showneartags allcommits
5748 global targetrow targetid lastscrollrows
5749 global autoselect
5751 catch {unset pending_select}
5752 $canv delete hover
5753 normalline
5754 unsel_reflist
5755 stopfinding
5756 if {$l < 0 || $l >= $numcommits} return
5757 set id [commitonrow $l]
5758 set targetid $id
5759 set targetrow $l
5760 set selectedline $l
5761 set currentid $id
5762 if {$lastscrollrows < $numcommits} {
5763 setcanvscroll
5766 set y [expr {$canvy0 + $l * $linespc}]
5767 set ymax [lindex [$canv cget -scrollregion] 3]
5768 set ytop [expr {$y - $linespc - 1}]
5769 set ybot [expr {$y + $linespc + 1}]
5770 set wnow [$canv yview]
5771 set wtop [expr {[lindex $wnow 0] * $ymax}]
5772 set wbot [expr {[lindex $wnow 1] * $ymax}]
5773 set wh [expr {$wbot - $wtop}]
5774 set newtop $wtop
5775 if {$ytop < $wtop} {
5776 if {$ybot < $wtop} {
5777 set newtop [expr {$y - $wh / 2.0}]
5778 } else {
5779 set newtop $ytop
5780 if {$newtop > $wtop - $linespc} {
5781 set newtop [expr {$wtop - $linespc}]
5784 } elseif {$ybot > $wbot} {
5785 if {$ytop > $wbot} {
5786 set newtop [expr {$y - $wh / 2.0}]
5787 } else {
5788 set newtop [expr {$ybot - $wh}]
5789 if {$newtop < $wtop + $linespc} {
5790 set newtop [expr {$wtop + $linespc}]
5794 if {$newtop != $wtop} {
5795 if {$newtop < 0} {
5796 set newtop 0
5798 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5799 drawvisible
5802 make_secsel $l
5804 if {$isnew} {
5805 addtohistory [list selbyid $id]
5808 $sha1entry delete 0 end
5809 $sha1entry insert 0 $id
5810 if {$autoselect} {
5811 $sha1entry selection from 0
5812 $sha1entry selection to end
5814 rhighlight_sel $id
5816 $ctext conf -state normal
5817 clear_ctext
5818 set linknum 0
5819 if {![info exists commitinfo($id)]} {
5820 getcommit $id
5822 set info $commitinfo($id)
5823 set date [formatdate [lindex $info 2]]
5824 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
5825 set date [formatdate [lindex $info 4]]
5826 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
5827 if {[info exists idtags($id)]} {
5828 $ctext insert end [mc "Tags:"]
5829 foreach tag $idtags($id) {
5830 $ctext insert end " $tag"
5832 $ctext insert end "\n"
5835 set headers {}
5836 set olds $parents($curview,$id)
5837 if {[llength $olds] > 1} {
5838 set np 0
5839 foreach p $olds {
5840 if {$np >= $mergemax} {
5841 set tag mmax
5842 } else {
5843 set tag m$np
5845 $ctext insert end "[mc "Parent"]: " $tag
5846 appendwithlinks [commit_descriptor $p] {}
5847 incr np
5849 } else {
5850 foreach p $olds {
5851 append headers "[mc "Parent"]: [commit_descriptor $p]"
5855 foreach c $children($curview,$id) {
5856 append headers "[mc "Child"]: [commit_descriptor $c]"
5859 # make anything that looks like a SHA1 ID be a clickable link
5860 appendwithlinks $headers {}
5861 if {$showneartags} {
5862 if {![info exists allcommits]} {
5863 getallcommits
5865 $ctext insert end "[mc "Branch"]: "
5866 $ctext mark set branch "end -1c"
5867 $ctext mark gravity branch left
5868 $ctext insert end "\n[mc "Follows"]: "
5869 $ctext mark set follows "end -1c"
5870 $ctext mark gravity follows left
5871 $ctext insert end "\n[mc "Precedes"]: "
5872 $ctext mark set precedes "end -1c"
5873 $ctext mark gravity precedes left
5874 $ctext insert end "\n"
5875 dispneartags 1
5877 $ctext insert end "\n"
5878 set comment [lindex $info 5]
5879 if {[string first "\r" $comment] >= 0} {
5880 set comment [string map {"\r" "\n "} $comment]
5882 appendwithlinks $comment {comment}
5884 $ctext tag remove found 1.0 end
5885 $ctext conf -state disabled
5886 set commentend [$ctext index "end - 1c"]
5888 init_flist [mc "Comments"]
5889 if {$cmitmode eq "tree"} {
5890 gettree $id
5891 } elseif {[llength $olds] <= 1} {
5892 startdiff $id
5893 } else {
5894 mergediff $id
5898 proc selfirstline {} {
5899 unmarkmatches
5900 selectline 0 1
5903 proc sellastline {} {
5904 global numcommits
5905 unmarkmatches
5906 set l [expr {$numcommits - 1}]
5907 selectline $l 1
5910 proc selnextline {dir} {
5911 global selectedline
5912 focus .
5913 if {![info exists selectedline]} return
5914 set l [expr {$selectedline + $dir}]
5915 unmarkmatches
5916 selectline $l 1
5919 proc selnextpage {dir} {
5920 global canv linespc selectedline numcommits
5922 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
5923 if {$lpp < 1} {
5924 set lpp 1
5926 allcanvs yview scroll [expr {$dir * $lpp}] units
5927 drawvisible
5928 if {![info exists selectedline]} return
5929 set l [expr {$selectedline + $dir * $lpp}]
5930 if {$l < 0} {
5931 set l 0
5932 } elseif {$l >= $numcommits} {
5933 set l [expr $numcommits - 1]
5935 unmarkmatches
5936 selectline $l 1
5939 proc unselectline {} {
5940 global selectedline currentid
5942 catch {unset selectedline}
5943 catch {unset currentid}
5944 allcanvs delete secsel
5945 rhighlight_none
5948 proc reselectline {} {
5949 global selectedline
5951 if {[info exists selectedline]} {
5952 selectline $selectedline 0
5956 proc addtohistory {cmd} {
5957 global history historyindex curview
5959 set elt [list $curview $cmd]
5960 if {$historyindex > 0
5961 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
5962 return
5965 if {$historyindex < [llength $history]} {
5966 set history [lreplace $history $historyindex end $elt]
5967 } else {
5968 lappend history $elt
5970 incr historyindex
5971 if {$historyindex > 1} {
5972 .tf.bar.leftbut conf -state normal
5973 } else {
5974 .tf.bar.leftbut conf -state disabled
5976 .tf.bar.rightbut conf -state disabled
5979 proc godo {elt} {
5980 global curview
5982 set view [lindex $elt 0]
5983 set cmd [lindex $elt 1]
5984 if {$curview != $view} {
5985 showview $view
5987 eval $cmd
5990 proc goback {} {
5991 global history historyindex
5992 focus .
5994 if {$historyindex > 1} {
5995 incr historyindex -1
5996 godo [lindex $history [expr {$historyindex - 1}]]
5997 .tf.bar.rightbut conf -state normal
5999 if {$historyindex <= 1} {
6000 .tf.bar.leftbut conf -state disabled
6004 proc goforw {} {
6005 global history historyindex
6006 focus .
6008 if {$historyindex < [llength $history]} {
6009 set cmd [lindex $history $historyindex]
6010 incr historyindex
6011 godo $cmd
6012 .tf.bar.leftbut conf -state normal
6014 if {$historyindex >= [llength $history]} {
6015 .tf.bar.rightbut conf -state disabled
6019 proc gettree {id} {
6020 global treefilelist treeidlist diffids diffmergeid treepending
6021 global nullid nullid2
6023 set diffids $id
6024 catch {unset diffmergeid}
6025 if {![info exists treefilelist($id)]} {
6026 if {![info exists treepending]} {
6027 if {$id eq $nullid} {
6028 set cmd [list | git ls-files]
6029 } elseif {$id eq $nullid2} {
6030 set cmd [list | git ls-files --stage -t]
6031 } else {
6032 set cmd [list | git ls-tree -r $id]
6034 if {[catch {set gtf [open $cmd r]}]} {
6035 return
6037 set treepending $id
6038 set treefilelist($id) {}
6039 set treeidlist($id) {}
6040 fconfigure $gtf -blocking 0
6041 filerun $gtf [list gettreeline $gtf $id]
6043 } else {
6044 setfilelist $id
6048 proc gettreeline {gtf id} {
6049 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6051 set nl 0
6052 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6053 if {$diffids eq $nullid} {
6054 set fname $line
6055 } else {
6056 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6057 set i [string first "\t" $line]
6058 if {$i < 0} continue
6059 set sha1 [lindex $line 2]
6060 set fname [string range $line [expr {$i+1}] end]
6061 if {[string index $fname 0] eq "\""} {
6062 set fname [lindex $fname 0]
6064 lappend treeidlist($id) $sha1
6066 lappend treefilelist($id) $fname
6068 if {![eof $gtf]} {
6069 return [expr {$nl >= 1000? 2: 1}]
6071 close $gtf
6072 unset treepending
6073 if {$cmitmode ne "tree"} {
6074 if {![info exists diffmergeid]} {
6075 gettreediffs $diffids
6077 } elseif {$id ne $diffids} {
6078 gettree $diffids
6079 } else {
6080 setfilelist $id
6082 return 0
6085 proc showfile {f} {
6086 global treefilelist treeidlist diffids nullid nullid2
6087 global ctext commentend
6089 set i [lsearch -exact $treefilelist($diffids) $f]
6090 if {$i < 0} {
6091 puts "oops, $f not in list for id $diffids"
6092 return
6094 if {$diffids eq $nullid} {
6095 if {[catch {set bf [open $f r]} err]} {
6096 puts "oops, can't read $f: $err"
6097 return
6099 } else {
6100 set blob [lindex $treeidlist($diffids) $i]
6101 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6102 puts "oops, error reading blob $blob: $err"
6103 return
6106 fconfigure $bf -blocking 0
6107 filerun $bf [list getblobline $bf $diffids]
6108 $ctext config -state normal
6109 clear_ctext $commentend
6110 $ctext insert end "\n"
6111 $ctext insert end "$f\n" filesep
6112 $ctext config -state disabled
6113 $ctext yview $commentend
6114 settabs 0
6117 proc getblobline {bf id} {
6118 global diffids cmitmode ctext
6120 if {$id ne $diffids || $cmitmode ne "tree"} {
6121 catch {close $bf}
6122 return 0
6124 $ctext config -state normal
6125 set nl 0
6126 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6127 $ctext insert end "$line\n"
6129 if {[eof $bf]} {
6130 # delete last newline
6131 $ctext delete "end - 2c" "end - 1c"
6132 close $bf
6133 return 0
6135 $ctext config -state disabled
6136 return [expr {$nl >= 1000? 2: 1}]
6139 proc mergediff {id} {
6140 global diffmergeid mdifffd
6141 global diffids
6142 global parents
6143 global diffcontext
6144 global limitdiffs vfilelimit curview
6146 set diffmergeid $id
6147 set diffids $id
6148 # this doesn't seem to actually affect anything...
6149 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6150 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6151 set cmd [concat $cmd -- $vfilelimit($curview)]
6153 if {[catch {set mdf [open $cmd r]} err]} {
6154 error_popup "[mc "Error getting merge diffs:"] $err"
6155 return
6157 fconfigure $mdf -blocking 0
6158 set mdifffd($id) $mdf
6159 set np [llength $parents($curview,$id)]
6160 settabs $np
6161 filerun $mdf [list getmergediffline $mdf $id $np]
6164 proc getmergediffline {mdf id np} {
6165 global diffmergeid ctext cflist mergemax
6166 global difffilestart mdifffd
6168 $ctext conf -state normal
6169 set nr 0
6170 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6171 if {![info exists diffmergeid] || $id != $diffmergeid
6172 || $mdf != $mdifffd($id)} {
6173 close $mdf
6174 return 0
6176 if {[regexp {^diff --cc (.*)} $line match fname]} {
6177 # start of a new file
6178 $ctext insert end "\n"
6179 set here [$ctext index "end - 1c"]
6180 lappend difffilestart $here
6181 add_flist [list $fname]
6182 set l [expr {(78 - [string length $fname]) / 2}]
6183 set pad [string range "----------------------------------------" 1 $l]
6184 $ctext insert end "$pad $fname $pad\n" filesep
6185 } elseif {[regexp {^@@} $line]} {
6186 $ctext insert end "$line\n" hunksep
6187 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6188 # do nothing
6189 } else {
6190 # parse the prefix - one ' ', '-' or '+' for each parent
6191 set spaces {}
6192 set minuses {}
6193 set pluses {}
6194 set isbad 0
6195 for {set j 0} {$j < $np} {incr j} {
6196 set c [string range $line $j $j]
6197 if {$c == " "} {
6198 lappend spaces $j
6199 } elseif {$c == "-"} {
6200 lappend minuses $j
6201 } elseif {$c == "+"} {
6202 lappend pluses $j
6203 } else {
6204 set isbad 1
6205 break
6208 set tags {}
6209 set num {}
6210 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6211 # line doesn't appear in result, parents in $minuses have the line
6212 set num [lindex $minuses 0]
6213 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6214 # line appears in result, parents in $pluses don't have the line
6215 lappend tags mresult
6216 set num [lindex $spaces 0]
6218 if {$num ne {}} {
6219 if {$num >= $mergemax} {
6220 set num "max"
6222 lappend tags m$num
6224 $ctext insert end "$line\n" $tags
6227 $ctext conf -state disabled
6228 if {[eof $mdf]} {
6229 close $mdf
6230 return 0
6232 return [expr {$nr >= 1000? 2: 1}]
6235 proc startdiff {ids} {
6236 global treediffs diffids treepending diffmergeid nullid nullid2
6238 settabs 1
6239 set diffids $ids
6240 catch {unset diffmergeid}
6241 if {![info exists treediffs($ids)] ||
6242 [lsearch -exact $ids $nullid] >= 0 ||
6243 [lsearch -exact $ids $nullid2] >= 0} {
6244 if {![info exists treepending]} {
6245 gettreediffs $ids
6247 } else {
6248 addtocflist $ids
6252 proc path_filter {filter name} {
6253 foreach p $filter {
6254 set l [string length $p]
6255 if {[string index $p end] eq "/"} {
6256 if {[string compare -length $l $p $name] == 0} {
6257 return 1
6259 } else {
6260 if {[string compare -length $l $p $name] == 0 &&
6261 ([string length $name] == $l ||
6262 [string index $name $l] eq "/")} {
6263 return 1
6267 return 0
6270 proc addtocflist {ids} {
6271 global treediffs
6273 add_flist $treediffs($ids)
6274 getblobdiffs $ids
6277 proc diffcmd {ids flags} {
6278 global nullid nullid2
6280 set i [lsearch -exact $ids $nullid]
6281 set j [lsearch -exact $ids $nullid2]
6282 if {$i >= 0} {
6283 if {[llength $ids] > 1 && $j < 0} {
6284 # comparing working directory with some specific revision
6285 set cmd [concat | git diff-index $flags]
6286 if {$i == 0} {
6287 lappend cmd -R [lindex $ids 1]
6288 } else {
6289 lappend cmd [lindex $ids 0]
6291 } else {
6292 # comparing working directory with index
6293 set cmd [concat | git diff-files $flags]
6294 if {$j == 1} {
6295 lappend cmd -R
6298 } elseif {$j >= 0} {
6299 set cmd [concat | git diff-index --cached $flags]
6300 if {[llength $ids] > 1} {
6301 # comparing index with specific revision
6302 if {$i == 0} {
6303 lappend cmd -R [lindex $ids 1]
6304 } else {
6305 lappend cmd [lindex $ids 0]
6307 } else {
6308 # comparing index with HEAD
6309 lappend cmd HEAD
6311 } else {
6312 set cmd [concat | git diff-tree -r $flags $ids]
6314 return $cmd
6317 proc gettreediffs {ids} {
6318 global treediff treepending
6320 set treepending $ids
6321 set treediff {}
6322 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6323 fconfigure $gdtf -blocking 0
6324 filerun $gdtf [list gettreediffline $gdtf $ids]
6327 proc gettreediffline {gdtf ids} {
6328 global treediff treediffs treepending diffids diffmergeid
6329 global cmitmode vfilelimit curview limitdiffs
6331 set nr 0
6332 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
6333 set i [string first "\t" $line]
6334 if {$i >= 0} {
6335 set file [string range $line [expr {$i+1}] end]
6336 if {[string index $file 0] eq "\""} {
6337 set file [lindex $file 0]
6339 lappend treediff $file
6342 if {![eof $gdtf]} {
6343 return [expr {$nr >= 1000? 2: 1}]
6345 close $gdtf
6346 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6347 set flist {}
6348 foreach f $treediff {
6349 if {[path_filter $vfilelimit($curview) $f]} {
6350 lappend flist $f
6353 set treediffs($ids) $flist
6354 } else {
6355 set treediffs($ids) $treediff
6357 unset treepending
6358 if {$cmitmode eq "tree"} {
6359 gettree $diffids
6360 } elseif {$ids != $diffids} {
6361 if {![info exists diffmergeid]} {
6362 gettreediffs $diffids
6364 } else {
6365 addtocflist $ids
6367 return 0
6370 # empty string or positive integer
6371 proc diffcontextvalidate {v} {
6372 return [regexp {^(|[1-9][0-9]*)$} $v]
6375 proc diffcontextchange {n1 n2 op} {
6376 global diffcontextstring diffcontext
6378 if {[string is integer -strict $diffcontextstring]} {
6379 if {$diffcontextstring > 0} {
6380 set diffcontext $diffcontextstring
6381 reselectline
6386 proc changeignorespace {} {
6387 reselectline
6390 proc getblobdiffs {ids} {
6391 global blobdifffd diffids env
6392 global diffinhdr treediffs
6393 global diffcontext
6394 global ignorespace
6395 global limitdiffs vfilelimit curview
6397 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6398 if {$ignorespace} {
6399 append cmd " -w"
6401 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6402 set cmd [concat $cmd -- $vfilelimit($curview)]
6404 if {[catch {set bdf [open $cmd r]} err]} {
6405 puts "error getting diffs: $err"
6406 return
6408 set diffinhdr 0
6409 fconfigure $bdf -blocking 0
6410 set blobdifffd($ids) $bdf
6411 filerun $bdf [list getblobdiffline $bdf $diffids]
6414 proc setinlist {var i val} {
6415 global $var
6417 while {[llength [set $var]] < $i} {
6418 lappend $var {}
6420 if {[llength [set $var]] == $i} {
6421 lappend $var $val
6422 } else {
6423 lset $var $i $val
6427 proc makediffhdr {fname ids} {
6428 global ctext curdiffstart treediffs
6430 set i [lsearch -exact $treediffs($ids) $fname]
6431 if {$i >= 0} {
6432 setinlist difffilestart $i $curdiffstart
6434 set l [expr {(78 - [string length $fname]) / 2}]
6435 set pad [string range "----------------------------------------" 1 $l]
6436 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6439 proc getblobdiffline {bdf ids} {
6440 global diffids blobdifffd ctext curdiffstart
6441 global diffnexthead diffnextnote difffilestart
6442 global diffinhdr treediffs
6444 set nr 0
6445 $ctext conf -state normal
6446 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6447 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6448 close $bdf
6449 return 0
6451 if {![string compare -length 11 "diff --git " $line]} {
6452 # trim off "diff --git "
6453 set line [string range $line 11 end]
6454 set diffinhdr 1
6455 # start of a new file
6456 $ctext insert end "\n"
6457 set curdiffstart [$ctext index "end - 1c"]
6458 $ctext insert end "\n" filesep
6459 # If the name hasn't changed the length will be odd,
6460 # the middle char will be a space, and the two bits either
6461 # side will be a/name and b/name, or "a/name" and "b/name".
6462 # If the name has changed we'll get "rename from" and
6463 # "rename to" or "copy from" and "copy to" lines following this,
6464 # and we'll use them to get the filenames.
6465 # This complexity is necessary because spaces in the filename(s)
6466 # don't get escaped.
6467 set l [string length $line]
6468 set i [expr {$l / 2}]
6469 if {!(($l & 1) && [string index $line $i] eq " " &&
6470 [string range $line 2 [expr {$i - 1}]] eq \
6471 [string range $line [expr {$i + 3}] end])} {
6472 continue
6474 # unescape if quoted and chop off the a/ from the front
6475 if {[string index $line 0] eq "\""} {
6476 set fname [string range [lindex $line 0] 2 end]
6477 } else {
6478 set fname [string range $line 2 [expr {$i - 1}]]
6480 makediffhdr $fname $ids
6482 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6483 $line match f1l f1c f2l f2c rest]} {
6484 $ctext insert end "$line\n" hunksep
6485 set diffinhdr 0
6487 } elseif {$diffinhdr} {
6488 if {![string compare -length 12 "rename from " $line]} {
6489 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6490 if {[string index $fname 0] eq "\""} {
6491 set fname [lindex $fname 0]
6493 set i [lsearch -exact $treediffs($ids) $fname]
6494 if {$i >= 0} {
6495 setinlist difffilestart $i $curdiffstart
6497 } elseif {![string compare -length 10 $line "rename to "] ||
6498 ![string compare -length 8 $line "copy to "]} {
6499 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6500 if {[string index $fname 0] eq "\""} {
6501 set fname [lindex $fname 0]
6503 makediffhdr $fname $ids
6504 } elseif {[string compare -length 3 $line "---"] == 0} {
6505 # do nothing
6506 continue
6507 } elseif {[string compare -length 3 $line "+++"] == 0} {
6508 set diffinhdr 0
6509 continue
6511 $ctext insert end "$line\n" filesep
6513 } else {
6514 set x [string range $line 0 0]
6515 if {$x == "-" || $x == "+"} {
6516 set tag [expr {$x == "+"}]
6517 $ctext insert end "$line\n" d$tag
6518 } elseif {$x == " "} {
6519 $ctext insert end "$line\n"
6520 } else {
6521 # "\ No newline at end of file",
6522 # or something else we don't recognize
6523 $ctext insert end "$line\n" hunksep
6527 $ctext conf -state disabled
6528 if {[eof $bdf]} {
6529 close $bdf
6530 return 0
6532 return [expr {$nr >= 1000? 2: 1}]
6535 proc changediffdisp {} {
6536 global ctext diffelide
6538 $ctext tag conf d0 -elide [lindex $diffelide 0]
6539 $ctext tag conf d1 -elide [lindex $diffelide 1]
6542 proc prevfile {} {
6543 global difffilestart ctext
6544 set prev [lindex $difffilestart 0]
6545 set here [$ctext index @0,0]
6546 foreach loc $difffilestart {
6547 if {[$ctext compare $loc >= $here]} {
6548 $ctext yview $prev
6549 return
6551 set prev $loc
6553 $ctext yview $prev
6556 proc nextfile {} {
6557 global difffilestart ctext
6558 set here [$ctext index @0,0]
6559 foreach loc $difffilestart {
6560 if {[$ctext compare $loc > $here]} {
6561 $ctext yview $loc
6562 return
6567 proc clear_ctext {{first 1.0}} {
6568 global ctext smarktop smarkbot
6569 global pendinglinks
6571 set l [lindex [split $first .] 0]
6572 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6573 set smarktop $l
6575 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6576 set smarkbot $l
6578 $ctext delete $first end
6579 if {$first eq "1.0"} {
6580 catch {unset pendinglinks}
6584 proc settabs {{firstab {}}} {
6585 global firsttabstop tabstop ctext have_tk85
6587 if {$firstab ne {} && $have_tk85} {
6588 set firsttabstop $firstab
6590 set w [font measure textfont "0"]
6591 if {$firsttabstop != 0} {
6592 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6593 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6594 } elseif {$have_tk85 || $tabstop != 8} {
6595 $ctext conf -tabs [expr {$tabstop * $w}]
6596 } else {
6597 $ctext conf -tabs {}
6601 proc incrsearch {name ix op} {
6602 global ctext searchstring searchdirn
6604 $ctext tag remove found 1.0 end
6605 if {[catch {$ctext index anchor}]} {
6606 # no anchor set, use start of selection, or of visible area
6607 set sel [$ctext tag ranges sel]
6608 if {$sel ne {}} {
6609 $ctext mark set anchor [lindex $sel 0]
6610 } elseif {$searchdirn eq "-forwards"} {
6611 $ctext mark set anchor @0,0
6612 } else {
6613 $ctext mark set anchor @0,[winfo height $ctext]
6616 if {$searchstring ne {}} {
6617 set here [$ctext search $searchdirn -- $searchstring anchor]
6618 if {$here ne {}} {
6619 $ctext see $here
6621 searchmarkvisible 1
6625 proc dosearch {} {
6626 global sstring ctext searchstring searchdirn
6628 focus $sstring
6629 $sstring icursor end
6630 set searchdirn -forwards
6631 if {$searchstring ne {}} {
6632 set sel [$ctext tag ranges sel]
6633 if {$sel ne {}} {
6634 set start "[lindex $sel 0] + 1c"
6635 } elseif {[catch {set start [$ctext index anchor]}]} {
6636 set start "@0,0"
6638 set match [$ctext search -count mlen -- $searchstring $start]
6639 $ctext tag remove sel 1.0 end
6640 if {$match eq {}} {
6641 bell
6642 return
6644 $ctext see $match
6645 set mend "$match + $mlen c"
6646 $ctext tag add sel $match $mend
6647 $ctext mark unset anchor
6651 proc dosearchback {} {
6652 global sstring ctext searchstring searchdirn
6654 focus $sstring
6655 $sstring icursor end
6656 set searchdirn -backwards
6657 if {$searchstring ne {}} {
6658 set sel [$ctext tag ranges sel]
6659 if {$sel ne {}} {
6660 set start [lindex $sel 0]
6661 } elseif {[catch {set start [$ctext index anchor]}]} {
6662 set start @0,[winfo height $ctext]
6664 set match [$ctext search -backwards -count ml -- $searchstring $start]
6665 $ctext tag remove sel 1.0 end
6666 if {$match eq {}} {
6667 bell
6668 return
6670 $ctext see $match
6671 set mend "$match + $ml c"
6672 $ctext tag add sel $match $mend
6673 $ctext mark unset anchor
6677 proc searchmark {first last} {
6678 global ctext searchstring
6680 set mend $first.0
6681 while {1} {
6682 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6683 if {$match eq {}} break
6684 set mend "$match + $mlen c"
6685 $ctext tag add found $match $mend
6689 proc searchmarkvisible {doall} {
6690 global ctext smarktop smarkbot
6692 set topline [lindex [split [$ctext index @0,0] .] 0]
6693 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6694 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6695 # no overlap with previous
6696 searchmark $topline $botline
6697 set smarktop $topline
6698 set smarkbot $botline
6699 } else {
6700 if {$topline < $smarktop} {
6701 searchmark $topline [expr {$smarktop-1}]
6702 set smarktop $topline
6704 if {$botline > $smarkbot} {
6705 searchmark [expr {$smarkbot+1}] $botline
6706 set smarkbot $botline
6711 proc scrolltext {f0 f1} {
6712 global searchstring
6714 .bleft.bottom.sb set $f0 $f1
6715 if {$searchstring ne {}} {
6716 searchmarkvisible 0
6720 proc setcoords {} {
6721 global linespc charspc canvx0 canvy0
6722 global xspc1 xspc2 lthickness
6724 set linespc [font metrics mainfont -linespace]
6725 set charspc [font measure mainfont "m"]
6726 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6727 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6728 set lthickness [expr {int($linespc / 9) + 1}]
6729 set xspc1(0) $linespc
6730 set xspc2 $linespc
6733 proc redisplay {} {
6734 global canv
6735 global selectedline
6737 set ymax [lindex [$canv cget -scrollregion] 3]
6738 if {$ymax eq {} || $ymax == 0} return
6739 set span [$canv yview]
6740 clear_display
6741 setcanvscroll
6742 allcanvs yview moveto [lindex $span 0]
6743 drawvisible
6744 if {[info exists selectedline]} {
6745 selectline $selectedline 0
6746 allcanvs yview moveto [lindex $span 0]
6750 proc parsefont {f n} {
6751 global fontattr
6753 set fontattr($f,family) [lindex $n 0]
6754 set s [lindex $n 1]
6755 if {$s eq {} || $s == 0} {
6756 set s 10
6757 } elseif {$s < 0} {
6758 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6760 set fontattr($f,size) $s
6761 set fontattr($f,weight) normal
6762 set fontattr($f,slant) roman
6763 foreach style [lrange $n 2 end] {
6764 switch -- $style {
6765 "normal" -
6766 "bold" {set fontattr($f,weight) $style}
6767 "roman" -
6768 "italic" {set fontattr($f,slant) $style}
6773 proc fontflags {f {isbold 0}} {
6774 global fontattr
6776 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6777 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6778 -slant $fontattr($f,slant)]
6781 proc fontname {f} {
6782 global fontattr
6784 set n [list $fontattr($f,family) $fontattr($f,size)]
6785 if {$fontattr($f,weight) eq "bold"} {
6786 lappend n "bold"
6788 if {$fontattr($f,slant) eq "italic"} {
6789 lappend n "italic"
6791 return $n
6794 proc incrfont {inc} {
6795 global mainfont textfont ctext canv cflist showrefstop
6796 global stopped entries fontattr
6798 unmarkmatches
6799 set s $fontattr(mainfont,size)
6800 incr s $inc
6801 if {$s < 1} {
6802 set s 1
6804 set fontattr(mainfont,size) $s
6805 font config mainfont -size $s
6806 font config mainfontbold -size $s
6807 set mainfont [fontname mainfont]
6808 set s $fontattr(textfont,size)
6809 incr s $inc
6810 if {$s < 1} {
6811 set s 1
6813 set fontattr(textfont,size) $s
6814 font config textfont -size $s
6815 font config textfontbold -size $s
6816 set textfont [fontname textfont]
6817 setcoords
6818 settabs
6819 redisplay
6822 proc clearsha1 {} {
6823 global sha1entry sha1string
6824 if {[string length $sha1string] == 40} {
6825 $sha1entry delete 0 end
6829 proc sha1change {n1 n2 op} {
6830 global sha1string currentid sha1but
6831 if {$sha1string == {}
6832 || ([info exists currentid] && $sha1string == $currentid)} {
6833 set state disabled
6834 } else {
6835 set state normal
6837 if {[$sha1but cget -state] == $state} return
6838 if {$state == "normal"} {
6839 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
6840 } else {
6841 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
6845 proc gotocommit {} {
6846 global sha1string tagids headids curview varcid
6848 if {$sha1string == {}
6849 || ([info exists currentid] && $sha1string == $currentid)} return
6850 if {[info exists tagids($sha1string)]} {
6851 set id $tagids($sha1string)
6852 } elseif {[info exists headids($sha1string)]} {
6853 set id $headids($sha1string)
6854 } else {
6855 set id [string tolower $sha1string]
6856 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
6857 set matches [array names varcid "$curview,$id*"]
6858 if {$matches ne {}} {
6859 if {[llength $matches] > 1} {
6860 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
6861 return
6863 set id [lindex [split [lindex $matches 0] ","] 1]
6867 if {[commitinview $id $curview]} {
6868 selectline [rowofcommit $id] 1
6869 return
6871 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
6872 set msg [mc "SHA1 id %s is not known" $sha1string]
6873 } else {
6874 set msg [mc "Tag/Head %s is not known" $sha1string]
6876 error_popup $msg
6879 proc lineenter {x y id} {
6880 global hoverx hovery hoverid hovertimer
6881 global commitinfo canv
6883 if {![info exists commitinfo($id)] && ![getcommit $id]} return
6884 set hoverx $x
6885 set hovery $y
6886 set hoverid $id
6887 if {[info exists hovertimer]} {
6888 after cancel $hovertimer
6890 set hovertimer [after 500 linehover]
6891 $canv delete hover
6894 proc linemotion {x y id} {
6895 global hoverx hovery hoverid hovertimer
6897 if {[info exists hoverid] && $id == $hoverid} {
6898 set hoverx $x
6899 set hovery $y
6900 if {[info exists hovertimer]} {
6901 after cancel $hovertimer
6903 set hovertimer [after 500 linehover]
6907 proc lineleave {id} {
6908 global hoverid hovertimer canv
6910 if {[info exists hoverid] && $id == $hoverid} {
6911 $canv delete hover
6912 if {[info exists hovertimer]} {
6913 after cancel $hovertimer
6914 unset hovertimer
6916 unset hoverid
6920 proc linehover {} {
6921 global hoverx hovery hoverid hovertimer
6922 global canv linespc lthickness
6923 global commitinfo
6925 set text [lindex $commitinfo($hoverid) 0]
6926 set ymax [lindex [$canv cget -scrollregion] 3]
6927 if {$ymax == {}} return
6928 set yfrac [lindex [$canv yview] 0]
6929 set x [expr {$hoverx + 2 * $linespc}]
6930 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
6931 set x0 [expr {$x - 2 * $lthickness}]
6932 set y0 [expr {$y - 2 * $lthickness}]
6933 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
6934 set y1 [expr {$y + $linespc + 2 * $lthickness}]
6935 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
6936 -fill \#ffff80 -outline black -width 1 -tags hover]
6937 $canv raise $t
6938 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
6939 -font mainfont]
6940 $canv raise $t
6943 proc clickisonarrow {id y} {
6944 global lthickness
6946 set ranges [rowranges $id]
6947 set thresh [expr {2 * $lthickness + 6}]
6948 set n [expr {[llength $ranges] - 1}]
6949 for {set i 1} {$i < $n} {incr i} {
6950 set row [lindex $ranges $i]
6951 if {abs([yc $row] - $y) < $thresh} {
6952 return $i
6955 return {}
6958 proc arrowjump {id n y} {
6959 global canv
6961 # 1 <-> 2, 3 <-> 4, etc...
6962 set n [expr {(($n - 1) ^ 1) + 1}]
6963 set row [lindex [rowranges $id] $n]
6964 set yt [yc $row]
6965 set ymax [lindex [$canv cget -scrollregion] 3]
6966 if {$ymax eq {} || $ymax <= 0} return
6967 set view [$canv yview]
6968 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
6969 set yfrac [expr {$yt / $ymax - $yspan / 2}]
6970 if {$yfrac < 0} {
6971 set yfrac 0
6973 allcanvs yview moveto $yfrac
6976 proc lineclick {x y id isnew} {
6977 global ctext commitinfo children canv thickerline curview
6979 if {![info exists commitinfo($id)] && ![getcommit $id]} return
6980 unmarkmatches
6981 unselectline
6982 normalline
6983 $canv delete hover
6984 # draw this line thicker than normal
6985 set thickerline $id
6986 drawlines $id
6987 if {$isnew} {
6988 set ymax [lindex [$canv cget -scrollregion] 3]
6989 if {$ymax eq {}} return
6990 set yfrac [lindex [$canv yview] 0]
6991 set y [expr {$y + $yfrac * $ymax}]
6993 set dirn [clickisonarrow $id $y]
6994 if {$dirn ne {}} {
6995 arrowjump $id $dirn $y
6996 return
6999 if {$isnew} {
7000 addtohistory [list lineclick $x $y $id 0]
7002 # fill the details pane with info about this line
7003 $ctext conf -state normal
7004 clear_ctext
7005 settabs 0
7006 $ctext insert end "[mc "Parent"]:\t"
7007 $ctext insert end $id link0
7008 setlink $id link0
7009 set info $commitinfo($id)
7010 $ctext insert end "\n\t[lindex $info 0]\n"
7011 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7012 set date [formatdate [lindex $info 2]]
7013 $ctext insert end "\t[mc "Date"]:\t$date\n"
7014 set kids $children($curview,$id)
7015 if {$kids ne {}} {
7016 $ctext insert end "\n[mc "Children"]:"
7017 set i 0
7018 foreach child $kids {
7019 incr i
7020 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7021 set info $commitinfo($child)
7022 $ctext insert end "\n\t"
7023 $ctext insert end $child link$i
7024 setlink $child link$i
7025 $ctext insert end "\n\t[lindex $info 0]"
7026 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7027 set date [formatdate [lindex $info 2]]
7028 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7031 $ctext conf -state disabled
7032 init_flist {}
7035 proc normalline {} {
7036 global thickerline
7037 if {[info exists thickerline]} {
7038 set id $thickerline
7039 unset thickerline
7040 drawlines $id
7044 proc selbyid {id} {
7045 global curview
7046 if {[commitinview $id $curview]} {
7047 selectline [rowofcommit $id] 1
7051 proc mstime {} {
7052 global startmstime
7053 if {![info exists startmstime]} {
7054 set startmstime [clock clicks -milliseconds]
7056 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7059 proc rowmenu {x y id} {
7060 global rowctxmenu selectedline rowmenuid curview
7061 global nullid nullid2 fakerowmenu mainhead
7063 stopfinding
7064 set rowmenuid $id
7065 if {![info exists selectedline]
7066 || [rowofcommit $id] eq $selectedline} {
7067 set state disabled
7068 } else {
7069 set state normal
7071 if {$id ne $nullid && $id ne $nullid2} {
7072 set menu $rowctxmenu
7073 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7074 } else {
7075 set menu $fakerowmenu
7077 $menu entryconfigure [mc "Diff this -> selected"] -state $state
7078 $menu entryconfigure [mc "Diff selected -> this"] -state $state
7079 $menu entryconfigure [mc "Make patch"] -state $state
7080 tk_popup $menu $x $y
7083 proc diffvssel {dirn} {
7084 global rowmenuid selectedline
7086 if {![info exists selectedline]} return
7087 if {$dirn} {
7088 set oldid [commitonrow $selectedline]
7089 set newid $rowmenuid
7090 } else {
7091 set oldid $rowmenuid
7092 set newid [commitonrow $selectedline]
7094 addtohistory [list doseldiff $oldid $newid]
7095 doseldiff $oldid $newid
7098 proc doseldiff {oldid newid} {
7099 global ctext
7100 global commitinfo
7102 $ctext conf -state normal
7103 clear_ctext
7104 init_flist [mc "Top"]
7105 $ctext insert end "[mc "From"] "
7106 $ctext insert end $oldid link0
7107 setlink $oldid link0
7108 $ctext insert end "\n "
7109 $ctext insert end [lindex $commitinfo($oldid) 0]
7110 $ctext insert end "\n\n[mc "To"] "
7111 $ctext insert end $newid link1
7112 setlink $newid link1
7113 $ctext insert end "\n "
7114 $ctext insert end [lindex $commitinfo($newid) 0]
7115 $ctext insert end "\n"
7116 $ctext conf -state disabled
7117 $ctext tag remove found 1.0 end
7118 startdiff [list $oldid $newid]
7121 proc mkpatch {} {
7122 global rowmenuid currentid commitinfo patchtop patchnum
7124 if {![info exists currentid]} return
7125 set oldid $currentid
7126 set oldhead [lindex $commitinfo($oldid) 0]
7127 set newid $rowmenuid
7128 set newhead [lindex $commitinfo($newid) 0]
7129 set top .patch
7130 set patchtop $top
7131 catch {destroy $top}
7132 toplevel $top
7133 label $top.title -text [mc "Generate patch"]
7134 grid $top.title - -pady 10
7135 label $top.from -text [mc "From:"]
7136 entry $top.fromsha1 -width 40 -relief flat
7137 $top.fromsha1 insert 0 $oldid
7138 $top.fromsha1 conf -state readonly
7139 grid $top.from $top.fromsha1 -sticky w
7140 entry $top.fromhead -width 60 -relief flat
7141 $top.fromhead insert 0 $oldhead
7142 $top.fromhead conf -state readonly
7143 grid x $top.fromhead -sticky w
7144 label $top.to -text [mc "To:"]
7145 entry $top.tosha1 -width 40 -relief flat
7146 $top.tosha1 insert 0 $newid
7147 $top.tosha1 conf -state readonly
7148 grid $top.to $top.tosha1 -sticky w
7149 entry $top.tohead -width 60 -relief flat
7150 $top.tohead insert 0 $newhead
7151 $top.tohead conf -state readonly
7152 grid x $top.tohead -sticky w
7153 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7154 grid $top.rev x -pady 10
7155 label $top.flab -text [mc "Output file:"]
7156 entry $top.fname -width 60
7157 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7158 incr patchnum
7159 grid $top.flab $top.fname -sticky w
7160 frame $top.buts
7161 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7162 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7163 grid $top.buts.gen $top.buts.can
7164 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7165 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7166 grid $top.buts - -pady 10 -sticky ew
7167 focus $top.fname
7170 proc mkpatchrev {} {
7171 global patchtop
7173 set oldid [$patchtop.fromsha1 get]
7174 set oldhead [$patchtop.fromhead get]
7175 set newid [$patchtop.tosha1 get]
7176 set newhead [$patchtop.tohead get]
7177 foreach e [list fromsha1 fromhead tosha1 tohead] \
7178 v [list $newid $newhead $oldid $oldhead] {
7179 $patchtop.$e conf -state normal
7180 $patchtop.$e delete 0 end
7181 $patchtop.$e insert 0 $v
7182 $patchtop.$e conf -state readonly
7186 proc mkpatchgo {} {
7187 global patchtop nullid nullid2
7189 set oldid [$patchtop.fromsha1 get]
7190 set newid [$patchtop.tosha1 get]
7191 set fname [$patchtop.fname get]
7192 set cmd [diffcmd [list $oldid $newid] -p]
7193 # trim off the initial "|"
7194 set cmd [lrange $cmd 1 end]
7195 lappend cmd >$fname &
7196 if {[catch {eval exec $cmd} err]} {
7197 error_popup "[mc "Error creating patch:"] $err"
7199 catch {destroy $patchtop}
7200 unset patchtop
7203 proc mkpatchcan {} {
7204 global patchtop
7206 catch {destroy $patchtop}
7207 unset patchtop
7210 proc mktag {} {
7211 global rowmenuid mktagtop commitinfo
7213 set top .maketag
7214 set mktagtop $top
7215 catch {destroy $top}
7216 toplevel $top
7217 label $top.title -text [mc "Create tag"]
7218 grid $top.title - -pady 10
7219 label $top.id -text [mc "ID:"]
7220 entry $top.sha1 -width 40 -relief flat
7221 $top.sha1 insert 0 $rowmenuid
7222 $top.sha1 conf -state readonly
7223 grid $top.id $top.sha1 -sticky w
7224 entry $top.head -width 60 -relief flat
7225 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7226 $top.head conf -state readonly
7227 grid x $top.head -sticky w
7228 label $top.tlab -text [mc "Tag name:"]
7229 entry $top.tag -width 60
7230 grid $top.tlab $top.tag -sticky w
7231 frame $top.buts
7232 button $top.buts.gen -text [mc "Create"] -command mktaggo
7233 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7234 grid $top.buts.gen $top.buts.can
7235 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7236 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7237 grid $top.buts - -pady 10 -sticky ew
7238 focus $top.tag
7241 proc domktag {} {
7242 global mktagtop env tagids idtags
7244 set id [$mktagtop.sha1 get]
7245 set tag [$mktagtop.tag get]
7246 if {$tag == {}} {
7247 error_popup [mc "No tag name specified"]
7248 return
7250 if {[info exists tagids($tag)]} {
7251 error_popup [mc "Tag \"%s\" already exists" $tag]
7252 return
7254 if {[catch {
7255 exec git tag $tag $id
7256 } err]} {
7257 error_popup "[mc "Error creating tag:"] $err"
7258 return
7261 set tagids($tag) $id
7262 lappend idtags($id) $tag
7263 redrawtags $id
7264 addedtag $id
7265 dispneartags 0
7266 run refill_reflist
7269 proc redrawtags {id} {
7270 global canv linehtag idpos currentid curview
7271 global canvxmax iddrawn
7273 if {![commitinview $id $curview]} return
7274 if {![info exists iddrawn($id)]} return
7275 set row [rowofcommit $id]
7276 $canv delete tag.$id
7277 set xt [eval drawtags $id $idpos($id)]
7278 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7279 set text [$canv itemcget $linehtag($row) -text]
7280 set font [$canv itemcget $linehtag($row) -font]
7281 set xr [expr {$xt + [font measure $font $text]}]
7282 if {$xr > $canvxmax} {
7283 set canvxmax $xr
7284 setcanvscroll
7286 if {[info exists currentid] && $currentid == $id} {
7287 make_secsel $row
7291 proc mktagcan {} {
7292 global mktagtop
7294 catch {destroy $mktagtop}
7295 unset mktagtop
7298 proc mktaggo {} {
7299 domktag
7300 mktagcan
7303 proc writecommit {} {
7304 global rowmenuid wrcomtop commitinfo wrcomcmd
7306 set top .writecommit
7307 set wrcomtop $top
7308 catch {destroy $top}
7309 toplevel $top
7310 label $top.title -text [mc "Write commit to file"]
7311 grid $top.title - -pady 10
7312 label $top.id -text [mc "ID:"]
7313 entry $top.sha1 -width 40 -relief flat
7314 $top.sha1 insert 0 $rowmenuid
7315 $top.sha1 conf -state readonly
7316 grid $top.id $top.sha1 -sticky w
7317 entry $top.head -width 60 -relief flat
7318 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7319 $top.head conf -state readonly
7320 grid x $top.head -sticky w
7321 label $top.clab -text [mc "Command:"]
7322 entry $top.cmd -width 60 -textvariable wrcomcmd
7323 grid $top.clab $top.cmd -sticky w -pady 10
7324 label $top.flab -text [mc "Output file:"]
7325 entry $top.fname -width 60
7326 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7327 grid $top.flab $top.fname -sticky w
7328 frame $top.buts
7329 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7330 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7331 grid $top.buts.gen $top.buts.can
7332 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7333 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7334 grid $top.buts - -pady 10 -sticky ew
7335 focus $top.fname
7338 proc wrcomgo {} {
7339 global wrcomtop
7341 set id [$wrcomtop.sha1 get]
7342 set cmd "echo $id | [$wrcomtop.cmd get]"
7343 set fname [$wrcomtop.fname get]
7344 if {[catch {exec sh -c $cmd >$fname &} err]} {
7345 error_popup "[mc "Error writing commit:"] $err"
7347 catch {destroy $wrcomtop}
7348 unset wrcomtop
7351 proc wrcomcan {} {
7352 global wrcomtop
7354 catch {destroy $wrcomtop}
7355 unset wrcomtop
7358 proc mkbranch {} {
7359 global rowmenuid mkbrtop
7361 set top .makebranch
7362 catch {destroy $top}
7363 toplevel $top
7364 label $top.title -text [mc "Create new branch"]
7365 grid $top.title - -pady 10
7366 label $top.id -text [mc "ID:"]
7367 entry $top.sha1 -width 40 -relief flat
7368 $top.sha1 insert 0 $rowmenuid
7369 $top.sha1 conf -state readonly
7370 grid $top.id $top.sha1 -sticky w
7371 label $top.nlab -text [mc "Name:"]
7372 entry $top.name -width 40
7373 grid $top.nlab $top.name -sticky w
7374 frame $top.buts
7375 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7376 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7377 grid $top.buts.go $top.buts.can
7378 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7379 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7380 grid $top.buts - -pady 10 -sticky ew
7381 focus $top.name
7384 proc mkbrgo {top} {
7385 global headids idheads
7387 set name [$top.name get]
7388 set id [$top.sha1 get]
7389 if {$name eq {}} {
7390 error_popup [mc "Please specify a name for the new branch"]
7391 return
7393 catch {destroy $top}
7394 nowbusy newbranch
7395 update
7396 if {[catch {
7397 exec git branch $name $id
7398 } err]} {
7399 notbusy newbranch
7400 error_popup $err
7401 } else {
7402 set headids($name) $id
7403 lappend idheads($id) $name
7404 addedhead $id $name
7405 notbusy newbranch
7406 redrawtags $id
7407 dispneartags 0
7408 run refill_reflist
7412 proc cherrypick {} {
7413 global rowmenuid curview
7414 global mainhead mainheadid
7416 set oldhead [exec git rev-parse HEAD]
7417 set dheads [descheads $rowmenuid]
7418 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7419 set ok [confirm_popup [mc "Commit %s is already\
7420 included in branch %s -- really re-apply it?" \
7421 [string range $rowmenuid 0 7] $mainhead]]
7422 if {!$ok} return
7424 nowbusy cherrypick [mc "Cherry-picking"]
7425 update
7426 # Unfortunately git-cherry-pick writes stuff to stderr even when
7427 # no error occurs, and exec takes that as an indication of error...
7428 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7429 notbusy cherrypick
7430 error_popup $err
7431 return
7433 set newhead [exec git rev-parse HEAD]
7434 if {$newhead eq $oldhead} {
7435 notbusy cherrypick
7436 error_popup [mc "No changes committed"]
7437 return
7439 addnewchild $newhead $oldhead
7440 if {[commitinview $oldhead $curview]} {
7441 insertrow $newhead $oldhead $curview
7442 if {$mainhead ne {}} {
7443 movehead $newhead $mainhead
7444 movedhead $newhead $mainhead
7445 set mainheadid $newhead
7447 redrawtags $oldhead
7448 redrawtags $newhead
7449 selbyid $newhead
7451 notbusy cherrypick
7454 proc resethead {} {
7455 global mainhead rowmenuid confirm_ok resettype
7457 set confirm_ok 0
7458 set w ".confirmreset"
7459 toplevel $w
7460 wm transient $w .
7461 wm title $w [mc "Confirm reset"]
7462 message $w.m -text \
7463 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7464 -justify center -aspect 1000
7465 pack $w.m -side top -fill x -padx 20 -pady 20
7466 frame $w.f -relief sunken -border 2
7467 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7468 grid $w.f.rt -sticky w
7469 set resettype mixed
7470 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7471 -text [mc "Soft: Leave working tree and index untouched"]
7472 grid $w.f.soft -sticky w
7473 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7474 -text [mc "Mixed: Leave working tree untouched, reset index"]
7475 grid $w.f.mixed -sticky w
7476 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7477 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7478 grid $w.f.hard -sticky w
7479 pack $w.f -side top -fill x
7480 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7481 pack $w.ok -side left -fill x -padx 20 -pady 20
7482 button $w.cancel -text [mc Cancel] -command "destroy $w"
7483 pack $w.cancel -side right -fill x -padx 20 -pady 20
7484 bind $w <Visibility> "grab $w; focus $w"
7485 tkwait window $w
7486 if {!$confirm_ok} return
7487 if {[catch {set fd [open \
7488 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
7489 error_popup $err
7490 } else {
7491 dohidelocalchanges
7492 filerun $fd [list readresetstat $fd]
7493 nowbusy reset [mc "Resetting"]
7494 selbyid $rowmenuid
7498 proc readresetstat {fd} {
7499 global mainhead mainheadid showlocalchanges rprogcoord
7501 if {[gets $fd line] >= 0} {
7502 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7503 set rprogcoord [expr {1.0 * $m / $n}]
7504 adjustprogress
7506 return 1
7508 set rprogcoord 0
7509 adjustprogress
7510 notbusy reset
7511 if {[catch {close $fd} err]} {
7512 error_popup $err
7514 set oldhead $mainheadid
7515 set newhead [exec git rev-parse HEAD]
7516 if {$newhead ne $oldhead} {
7517 movehead $newhead $mainhead
7518 movedhead $newhead $mainhead
7519 set mainheadid $newhead
7520 redrawtags $oldhead
7521 redrawtags $newhead
7523 if {$showlocalchanges} {
7524 doshowlocalchanges
7526 return 0
7529 # context menu for a head
7530 proc headmenu {x y id head} {
7531 global headmenuid headmenuhead headctxmenu mainhead
7533 stopfinding
7534 set headmenuid $id
7535 set headmenuhead $head
7536 set state normal
7537 if {$head eq $mainhead} {
7538 set state disabled
7540 $headctxmenu entryconfigure 0 -state $state
7541 $headctxmenu entryconfigure 1 -state $state
7542 tk_popup $headctxmenu $x $y
7545 proc cobranch {} {
7546 global headmenuid headmenuhead mainhead headids
7547 global showlocalchanges mainheadid
7549 # check the tree is clean first??
7550 set oldmainhead $mainhead
7551 nowbusy checkout [mc "Checking out"]
7552 update
7553 dohidelocalchanges
7554 if {[catch {
7555 exec git checkout -q $headmenuhead
7556 } err]} {
7557 notbusy checkout
7558 error_popup $err
7559 } else {
7560 notbusy checkout
7561 set mainhead $headmenuhead
7562 set mainheadid $headmenuid
7563 if {[info exists headids($oldmainhead)]} {
7564 redrawtags $headids($oldmainhead)
7566 redrawtags $headmenuid
7567 selbyid $headmenuid
7569 if {$showlocalchanges} {
7570 dodiffindex
7574 proc rmbranch {} {
7575 global headmenuid headmenuhead mainhead
7576 global idheads
7578 set head $headmenuhead
7579 set id $headmenuid
7580 # this check shouldn't be needed any more...
7581 if {$head eq $mainhead} {
7582 error_popup [mc "Cannot delete the currently checked-out branch"]
7583 return
7585 set dheads [descheads $id]
7586 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7587 # the stuff on this branch isn't on any other branch
7588 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7589 branch.\nReally delete branch %s?" $head $head]]} return
7591 nowbusy rmbranch
7592 update
7593 if {[catch {exec git branch -D $head} err]} {
7594 notbusy rmbranch
7595 error_popup $err
7596 return
7598 removehead $id $head
7599 removedhead $id $head
7600 redrawtags $id
7601 notbusy rmbranch
7602 dispneartags 0
7603 run refill_reflist
7606 # Display a list of tags and heads
7607 proc showrefs {} {
7608 global showrefstop bgcolor fgcolor selectbgcolor
7609 global bglist fglist reflistfilter reflist maincursor
7611 set top .showrefs
7612 set showrefstop $top
7613 if {[winfo exists $top]} {
7614 raise $top
7615 refill_reflist
7616 return
7618 toplevel $top
7619 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7620 text $top.list -background $bgcolor -foreground $fgcolor \
7621 -selectbackground $selectbgcolor -font mainfont \
7622 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7623 -width 30 -height 20 -cursor $maincursor \
7624 -spacing1 1 -spacing3 1 -state disabled
7625 $top.list tag configure highlight -background $selectbgcolor
7626 lappend bglist $top.list
7627 lappend fglist $top.list
7628 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7629 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7630 grid $top.list $top.ysb -sticky nsew
7631 grid $top.xsb x -sticky ew
7632 frame $top.f
7633 label $top.f.l -text "[mc "Filter"]: "
7634 entry $top.f.e -width 20 -textvariable reflistfilter
7635 set reflistfilter "*"
7636 trace add variable reflistfilter write reflistfilter_change
7637 pack $top.f.e -side right -fill x -expand 1
7638 pack $top.f.l -side left
7639 grid $top.f - -sticky ew -pady 2
7640 button $top.close -command [list destroy $top] -text [mc "Close"]
7641 grid $top.close -
7642 grid columnconfigure $top 0 -weight 1
7643 grid rowconfigure $top 0 -weight 1
7644 bind $top.list <1> {break}
7645 bind $top.list <B1-Motion> {break}
7646 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7647 set reflist {}
7648 refill_reflist
7651 proc sel_reflist {w x y} {
7652 global showrefstop reflist headids tagids otherrefids
7654 if {![winfo exists $showrefstop]} return
7655 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7656 set ref [lindex $reflist [expr {$l-1}]]
7657 set n [lindex $ref 0]
7658 switch -- [lindex $ref 1] {
7659 "H" {selbyid $headids($n)}
7660 "T" {selbyid $tagids($n)}
7661 "o" {selbyid $otherrefids($n)}
7663 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7666 proc unsel_reflist {} {
7667 global showrefstop
7669 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7670 $showrefstop.list tag remove highlight 0.0 end
7673 proc reflistfilter_change {n1 n2 op} {
7674 global reflistfilter
7676 after cancel refill_reflist
7677 after 200 refill_reflist
7680 proc refill_reflist {} {
7681 global reflist reflistfilter showrefstop headids tagids otherrefids
7682 global curview commitinterest
7684 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7685 set refs {}
7686 foreach n [array names headids] {
7687 if {[string match $reflistfilter $n]} {
7688 if {[commitinview $headids($n) $curview]} {
7689 lappend refs [list $n H]
7690 } else {
7691 set commitinterest($headids($n)) {run refill_reflist}
7695 foreach n [array names tagids] {
7696 if {[string match $reflistfilter $n]} {
7697 if {[commitinview $tagids($n) $curview]} {
7698 lappend refs [list $n T]
7699 } else {
7700 set commitinterest($tagids($n)) {run refill_reflist}
7704 foreach n [array names otherrefids] {
7705 if {[string match $reflistfilter $n]} {
7706 if {[commitinview $otherrefids($n) $curview]} {
7707 lappend refs [list $n o]
7708 } else {
7709 set commitinterest($otherrefids($n)) {run refill_reflist}
7713 set refs [lsort -index 0 $refs]
7714 if {$refs eq $reflist} return
7716 # Update the contents of $showrefstop.list according to the
7717 # differences between $reflist (old) and $refs (new)
7718 $showrefstop.list conf -state normal
7719 $showrefstop.list insert end "\n"
7720 set i 0
7721 set j 0
7722 while {$i < [llength $reflist] || $j < [llength $refs]} {
7723 if {$i < [llength $reflist]} {
7724 if {$j < [llength $refs]} {
7725 set cmp [string compare [lindex $reflist $i 0] \
7726 [lindex $refs $j 0]]
7727 if {$cmp == 0} {
7728 set cmp [string compare [lindex $reflist $i 1] \
7729 [lindex $refs $j 1]]
7731 } else {
7732 set cmp -1
7734 } else {
7735 set cmp 1
7737 switch -- $cmp {
7738 -1 {
7739 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7740 incr i
7743 incr i
7744 incr j
7747 set l [expr {$j + 1}]
7748 $showrefstop.list image create $l.0 -align baseline \
7749 -image reficon-[lindex $refs $j 1] -padx 2
7750 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7751 incr j
7755 set reflist $refs
7756 # delete last newline
7757 $showrefstop.list delete end-2c end-1c
7758 $showrefstop.list conf -state disabled
7761 # Stuff for finding nearby tags
7762 proc getallcommits {} {
7763 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7764 global idheads idtags idotherrefs allparents tagobjid
7766 if {![info exists allcommits]} {
7767 set nextarc 0
7768 set allcommits 0
7769 set seeds {}
7770 set allcwait 0
7771 set cachedarcs 0
7772 set allccache [file join [gitdir] "gitk.cache"]
7773 if {![catch {
7774 set f [open $allccache r]
7775 set allcwait 1
7776 getcache $f
7777 }]} return
7780 if {$allcwait} {
7781 return
7783 set cmd [list | git rev-list --parents]
7784 set allcupdate [expr {$seeds ne {}}]
7785 if {!$allcupdate} {
7786 set ids "--all"
7787 } else {
7788 set refs [concat [array names idheads] [array names idtags] \
7789 [array names idotherrefs]]
7790 set ids {}
7791 set tagobjs {}
7792 foreach name [array names tagobjid] {
7793 lappend tagobjs $tagobjid($name)
7795 foreach id [lsort -unique $refs] {
7796 if {![info exists allparents($id)] &&
7797 [lsearch -exact $tagobjs $id] < 0} {
7798 lappend ids $id
7801 if {$ids ne {}} {
7802 foreach id $seeds {
7803 lappend ids "^$id"
7807 if {$ids ne {}} {
7808 set fd [open [concat $cmd $ids] r]
7809 fconfigure $fd -blocking 0
7810 incr allcommits
7811 nowbusy allcommits
7812 filerun $fd [list getallclines $fd]
7813 } else {
7814 dispneartags 0
7818 # Since most commits have 1 parent and 1 child, we group strings of
7819 # such commits into "arcs" joining branch/merge points (BMPs), which
7820 # are commits that either don't have 1 parent or don't have 1 child.
7822 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
7823 # arcout(id) - outgoing arcs for BMP
7824 # arcids(a) - list of IDs on arc including end but not start
7825 # arcstart(a) - BMP ID at start of arc
7826 # arcend(a) - BMP ID at end of arc
7827 # growing(a) - arc a is still growing
7828 # arctags(a) - IDs out of arcids (excluding end) that have tags
7829 # archeads(a) - IDs out of arcids (excluding end) that have heads
7830 # The start of an arc is at the descendent end, so "incoming" means
7831 # coming from descendents, and "outgoing" means going towards ancestors.
7833 proc getallclines {fd} {
7834 global allparents allchildren idtags idheads nextarc
7835 global arcnos arcids arctags arcout arcend arcstart archeads growing
7836 global seeds allcommits cachedarcs allcupdate
7838 set nid 0
7839 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
7840 set id [lindex $line 0]
7841 if {[info exists allparents($id)]} {
7842 # seen it already
7843 continue
7845 set cachedarcs 0
7846 set olds [lrange $line 1 end]
7847 set allparents($id) $olds
7848 if {![info exists allchildren($id)]} {
7849 set allchildren($id) {}
7850 set arcnos($id) {}
7851 lappend seeds $id
7852 } else {
7853 set a $arcnos($id)
7854 if {[llength $olds] == 1 && [llength $a] == 1} {
7855 lappend arcids($a) $id
7856 if {[info exists idtags($id)]} {
7857 lappend arctags($a) $id
7859 if {[info exists idheads($id)]} {
7860 lappend archeads($a) $id
7862 if {[info exists allparents($olds)]} {
7863 # seen parent already
7864 if {![info exists arcout($olds)]} {
7865 splitarc $olds
7867 lappend arcids($a) $olds
7868 set arcend($a) $olds
7869 unset growing($a)
7871 lappend allchildren($olds) $id
7872 lappend arcnos($olds) $a
7873 continue
7876 foreach a $arcnos($id) {
7877 lappend arcids($a) $id
7878 set arcend($a) $id
7879 unset growing($a)
7882 set ao {}
7883 foreach p $olds {
7884 lappend allchildren($p) $id
7885 set a [incr nextarc]
7886 set arcstart($a) $id
7887 set archeads($a) {}
7888 set arctags($a) {}
7889 set archeads($a) {}
7890 set arcids($a) {}
7891 lappend ao $a
7892 set growing($a) 1
7893 if {[info exists allparents($p)]} {
7894 # seen it already, may need to make a new branch
7895 if {![info exists arcout($p)]} {
7896 splitarc $p
7898 lappend arcids($a) $p
7899 set arcend($a) $p
7900 unset growing($a)
7902 lappend arcnos($p) $a
7904 set arcout($id) $ao
7906 if {$nid > 0} {
7907 global cached_dheads cached_dtags cached_atags
7908 catch {unset cached_dheads}
7909 catch {unset cached_dtags}
7910 catch {unset cached_atags}
7912 if {![eof $fd]} {
7913 return [expr {$nid >= 1000? 2: 1}]
7915 set cacheok 1
7916 if {[catch {
7917 fconfigure $fd -blocking 1
7918 close $fd
7919 } err]} {
7920 # got an error reading the list of commits
7921 # if we were updating, try rereading the whole thing again
7922 if {$allcupdate} {
7923 incr allcommits -1
7924 dropcache $err
7925 return
7927 error_popup "[mc "Error reading commit topology information;\
7928 branch and preceding/following tag information\
7929 will be incomplete."]\n($err)"
7930 set cacheok 0
7932 if {[incr allcommits -1] == 0} {
7933 notbusy allcommits
7934 if {$cacheok} {
7935 run savecache
7938 dispneartags 0
7939 return 0
7942 proc recalcarc {a} {
7943 global arctags archeads arcids idtags idheads
7945 set at {}
7946 set ah {}
7947 foreach id [lrange $arcids($a) 0 end-1] {
7948 if {[info exists idtags($id)]} {
7949 lappend at $id
7951 if {[info exists idheads($id)]} {
7952 lappend ah $id
7955 set arctags($a) $at
7956 set archeads($a) $ah
7959 proc splitarc {p} {
7960 global arcnos arcids nextarc arctags archeads idtags idheads
7961 global arcstart arcend arcout allparents growing
7963 set a $arcnos($p)
7964 if {[llength $a] != 1} {
7965 puts "oops splitarc called but [llength $a] arcs already"
7966 return
7968 set a [lindex $a 0]
7969 set i [lsearch -exact $arcids($a) $p]
7970 if {$i < 0} {
7971 puts "oops splitarc $p not in arc $a"
7972 return
7974 set na [incr nextarc]
7975 if {[info exists arcend($a)]} {
7976 set arcend($na) $arcend($a)
7977 } else {
7978 set l [lindex $allparents([lindex $arcids($a) end]) 0]
7979 set j [lsearch -exact $arcnos($l) $a]
7980 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
7982 set tail [lrange $arcids($a) [expr {$i+1}] end]
7983 set arcids($a) [lrange $arcids($a) 0 $i]
7984 set arcend($a) $p
7985 set arcstart($na) $p
7986 set arcout($p) $na
7987 set arcids($na) $tail
7988 if {[info exists growing($a)]} {
7989 set growing($na) 1
7990 unset growing($a)
7993 foreach id $tail {
7994 if {[llength $arcnos($id)] == 1} {
7995 set arcnos($id) $na
7996 } else {
7997 set j [lsearch -exact $arcnos($id) $a]
7998 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8002 # reconstruct tags and heads lists
8003 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8004 recalcarc $a
8005 recalcarc $na
8006 } else {
8007 set arctags($na) {}
8008 set archeads($na) {}
8012 # Update things for a new commit added that is a child of one
8013 # existing commit. Used when cherry-picking.
8014 proc addnewchild {id p} {
8015 global allparents allchildren idtags nextarc
8016 global arcnos arcids arctags arcout arcend arcstart archeads growing
8017 global seeds allcommits
8019 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8020 set allparents($id) [list $p]
8021 set allchildren($id) {}
8022 set arcnos($id) {}
8023 lappend seeds $id
8024 lappend allchildren($p) $id
8025 set a [incr nextarc]
8026 set arcstart($a) $id
8027 set archeads($a) {}
8028 set arctags($a) {}
8029 set arcids($a) [list $p]
8030 set arcend($a) $p
8031 if {![info exists arcout($p)]} {
8032 splitarc $p
8034 lappend arcnos($p) $a
8035 set arcout($id) [list $a]
8038 # This implements a cache for the topology information.
8039 # The cache saves, for each arc, the start and end of the arc,
8040 # the ids on the arc, and the outgoing arcs from the end.
8041 proc readcache {f} {
8042 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8043 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8044 global allcwait
8046 set a $nextarc
8047 set lim $cachedarcs
8048 if {$lim - $a > 500} {
8049 set lim [expr {$a + 500}]
8051 if {[catch {
8052 if {$a == $lim} {
8053 # finish reading the cache and setting up arctags, etc.
8054 set line [gets $f]
8055 if {$line ne "1"} {error "bad final version"}
8056 close $f
8057 foreach id [array names idtags] {
8058 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8059 [llength $allparents($id)] == 1} {
8060 set a [lindex $arcnos($id) 0]
8061 if {$arctags($a) eq {}} {
8062 recalcarc $a
8066 foreach id [array names idheads] {
8067 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8068 [llength $allparents($id)] == 1} {
8069 set a [lindex $arcnos($id) 0]
8070 if {$archeads($a) eq {}} {
8071 recalcarc $a
8075 foreach id [lsort -unique $possible_seeds] {
8076 if {$arcnos($id) eq {}} {
8077 lappend seeds $id
8080 set allcwait 0
8081 } else {
8082 while {[incr a] <= $lim} {
8083 set line [gets $f]
8084 if {[llength $line] != 3} {error "bad line"}
8085 set s [lindex $line 0]
8086 set arcstart($a) $s
8087 lappend arcout($s) $a
8088 if {![info exists arcnos($s)]} {
8089 lappend possible_seeds $s
8090 set arcnos($s) {}
8092 set e [lindex $line 1]
8093 if {$e eq {}} {
8094 set growing($a) 1
8095 } else {
8096 set arcend($a) $e
8097 if {![info exists arcout($e)]} {
8098 set arcout($e) {}
8101 set arcids($a) [lindex $line 2]
8102 foreach id $arcids($a) {
8103 lappend allparents($s) $id
8104 set s $id
8105 lappend arcnos($id) $a
8107 if {![info exists allparents($s)]} {
8108 set allparents($s) {}
8110 set arctags($a) {}
8111 set archeads($a) {}
8113 set nextarc [expr {$a - 1}]
8115 } err]} {
8116 dropcache $err
8117 return 0
8119 if {!$allcwait} {
8120 getallcommits
8122 return $allcwait
8125 proc getcache {f} {
8126 global nextarc cachedarcs possible_seeds
8128 if {[catch {
8129 set line [gets $f]
8130 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8131 # make sure it's an integer
8132 set cachedarcs [expr {int([lindex $line 1])}]
8133 if {$cachedarcs < 0} {error "bad number of arcs"}
8134 set nextarc 0
8135 set possible_seeds {}
8136 run readcache $f
8137 } err]} {
8138 dropcache $err
8140 return 0
8143 proc dropcache {err} {
8144 global allcwait nextarc cachedarcs seeds
8146 #puts "dropping cache ($err)"
8147 foreach v {arcnos arcout arcids arcstart arcend growing \
8148 arctags archeads allparents allchildren} {
8149 global $v
8150 catch {unset $v}
8152 set allcwait 0
8153 set nextarc 0
8154 set cachedarcs 0
8155 set seeds {}
8156 getallcommits
8159 proc writecache {f} {
8160 global cachearc cachedarcs allccache
8161 global arcstart arcend arcnos arcids arcout
8163 set a $cachearc
8164 set lim $cachedarcs
8165 if {$lim - $a > 1000} {
8166 set lim [expr {$a + 1000}]
8168 if {[catch {
8169 while {[incr a] <= $lim} {
8170 if {[info exists arcend($a)]} {
8171 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8172 } else {
8173 puts $f [list $arcstart($a) {} $arcids($a)]
8176 } err]} {
8177 catch {close $f}
8178 catch {file delete $allccache}
8179 #puts "writing cache failed ($err)"
8180 return 0
8182 set cachearc [expr {$a - 1}]
8183 if {$a > $cachedarcs} {
8184 puts $f "1"
8185 close $f
8186 return 0
8188 return 1
8191 proc savecache {} {
8192 global nextarc cachedarcs cachearc allccache
8194 if {$nextarc == $cachedarcs} return
8195 set cachearc 0
8196 set cachedarcs $nextarc
8197 catch {
8198 set f [open $allccache w]
8199 puts $f [list 1 $cachedarcs]
8200 run writecache $f
8204 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8205 # or 0 if neither is true.
8206 proc anc_or_desc {a b} {
8207 global arcout arcstart arcend arcnos cached_isanc
8209 if {$arcnos($a) eq $arcnos($b)} {
8210 # Both are on the same arc(s); either both are the same BMP,
8211 # or if one is not a BMP, the other is also not a BMP or is
8212 # the BMP at end of the arc (and it only has 1 incoming arc).
8213 # Or both can be BMPs with no incoming arcs.
8214 if {$a eq $b || $arcnos($a) eq {}} {
8215 return 0
8217 # assert {[llength $arcnos($a)] == 1}
8218 set arc [lindex $arcnos($a) 0]
8219 set i [lsearch -exact $arcids($arc) $a]
8220 set j [lsearch -exact $arcids($arc) $b]
8221 if {$i < 0 || $i > $j} {
8222 return 1
8223 } else {
8224 return -1
8228 if {![info exists arcout($a)]} {
8229 set arc [lindex $arcnos($a) 0]
8230 if {[info exists arcend($arc)]} {
8231 set aend $arcend($arc)
8232 } else {
8233 set aend {}
8235 set a $arcstart($arc)
8236 } else {
8237 set aend $a
8239 if {![info exists arcout($b)]} {
8240 set arc [lindex $arcnos($b) 0]
8241 if {[info exists arcend($arc)]} {
8242 set bend $arcend($arc)
8243 } else {
8244 set bend {}
8246 set b $arcstart($arc)
8247 } else {
8248 set bend $b
8250 if {$a eq $bend} {
8251 return 1
8253 if {$b eq $aend} {
8254 return -1
8256 if {[info exists cached_isanc($a,$bend)]} {
8257 if {$cached_isanc($a,$bend)} {
8258 return 1
8261 if {[info exists cached_isanc($b,$aend)]} {
8262 if {$cached_isanc($b,$aend)} {
8263 return -1
8265 if {[info exists cached_isanc($a,$bend)]} {
8266 return 0
8270 set todo [list $a $b]
8271 set anc($a) a
8272 set anc($b) b
8273 for {set i 0} {$i < [llength $todo]} {incr i} {
8274 set x [lindex $todo $i]
8275 if {$anc($x) eq {}} {
8276 continue
8278 foreach arc $arcnos($x) {
8279 set xd $arcstart($arc)
8280 if {$xd eq $bend} {
8281 set cached_isanc($a,$bend) 1
8282 set cached_isanc($b,$aend) 0
8283 return 1
8284 } elseif {$xd eq $aend} {
8285 set cached_isanc($b,$aend) 1
8286 set cached_isanc($a,$bend) 0
8287 return -1
8289 if {![info exists anc($xd)]} {
8290 set anc($xd) $anc($x)
8291 lappend todo $xd
8292 } elseif {$anc($xd) ne $anc($x)} {
8293 set anc($xd) {}
8297 set cached_isanc($a,$bend) 0
8298 set cached_isanc($b,$aend) 0
8299 return 0
8302 # This identifies whether $desc has an ancestor that is
8303 # a growing tip of the graph and which is not an ancestor of $anc
8304 # and returns 0 if so and 1 if not.
8305 # If we subsequently discover a tag on such a growing tip, and that
8306 # turns out to be a descendent of $anc (which it could, since we
8307 # don't necessarily see children before parents), then $desc
8308 # isn't a good choice to display as a descendent tag of
8309 # $anc (since it is the descendent of another tag which is
8310 # a descendent of $anc). Similarly, $anc isn't a good choice to
8311 # display as a ancestor tag of $desc.
8313 proc is_certain {desc anc} {
8314 global arcnos arcout arcstart arcend growing problems
8316 set certain {}
8317 if {[llength $arcnos($anc)] == 1} {
8318 # tags on the same arc are certain
8319 if {$arcnos($desc) eq $arcnos($anc)} {
8320 return 1
8322 if {![info exists arcout($anc)]} {
8323 # if $anc is partway along an arc, use the start of the arc instead
8324 set a [lindex $arcnos($anc) 0]
8325 set anc $arcstart($a)
8328 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8329 set x $desc
8330 } else {
8331 set a [lindex $arcnos($desc) 0]
8332 set x $arcend($a)
8334 if {$x == $anc} {
8335 return 1
8337 set anclist [list $x]
8338 set dl($x) 1
8339 set nnh 1
8340 set ngrowanc 0
8341 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8342 set x [lindex $anclist $i]
8343 if {$dl($x)} {
8344 incr nnh -1
8346 set done($x) 1
8347 foreach a $arcout($x) {
8348 if {[info exists growing($a)]} {
8349 if {![info exists growanc($x)] && $dl($x)} {
8350 set growanc($x) 1
8351 incr ngrowanc
8353 } else {
8354 set y $arcend($a)
8355 if {[info exists dl($y)]} {
8356 if {$dl($y)} {
8357 if {!$dl($x)} {
8358 set dl($y) 0
8359 if {![info exists done($y)]} {
8360 incr nnh -1
8362 if {[info exists growanc($x)]} {
8363 incr ngrowanc -1
8365 set xl [list $y]
8366 for {set k 0} {$k < [llength $xl]} {incr k} {
8367 set z [lindex $xl $k]
8368 foreach c $arcout($z) {
8369 if {[info exists arcend($c)]} {
8370 set v $arcend($c)
8371 if {[info exists dl($v)] && $dl($v)} {
8372 set dl($v) 0
8373 if {![info exists done($v)]} {
8374 incr nnh -1
8376 if {[info exists growanc($v)]} {
8377 incr ngrowanc -1
8379 lappend xl $v
8386 } elseif {$y eq $anc || !$dl($x)} {
8387 set dl($y) 0
8388 lappend anclist $y
8389 } else {
8390 set dl($y) 1
8391 lappend anclist $y
8392 incr nnh
8397 foreach x [array names growanc] {
8398 if {$dl($x)} {
8399 return 0
8401 return 0
8403 return 1
8406 proc validate_arctags {a} {
8407 global arctags idtags
8409 set i -1
8410 set na $arctags($a)
8411 foreach id $arctags($a) {
8412 incr i
8413 if {![info exists idtags($id)]} {
8414 set na [lreplace $na $i $i]
8415 incr i -1
8418 set arctags($a) $na
8421 proc validate_archeads {a} {
8422 global archeads idheads
8424 set i -1
8425 set na $archeads($a)
8426 foreach id $archeads($a) {
8427 incr i
8428 if {![info exists idheads($id)]} {
8429 set na [lreplace $na $i $i]
8430 incr i -1
8433 set archeads($a) $na
8436 # Return the list of IDs that have tags that are descendents of id,
8437 # ignoring IDs that are descendents of IDs already reported.
8438 proc desctags {id} {
8439 global arcnos arcstart arcids arctags idtags allparents
8440 global growing cached_dtags
8442 if {![info exists allparents($id)]} {
8443 return {}
8445 set t1 [clock clicks -milliseconds]
8446 set argid $id
8447 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8448 # part-way along an arc; check that arc first
8449 set a [lindex $arcnos($id) 0]
8450 if {$arctags($a) ne {}} {
8451 validate_arctags $a
8452 set i [lsearch -exact $arcids($a) $id]
8453 set tid {}
8454 foreach t $arctags($a) {
8455 set j [lsearch -exact $arcids($a) $t]
8456 if {$j >= $i} break
8457 set tid $t
8459 if {$tid ne {}} {
8460 return $tid
8463 set id $arcstart($a)
8464 if {[info exists idtags($id)]} {
8465 return $id
8468 if {[info exists cached_dtags($id)]} {
8469 return $cached_dtags($id)
8472 set origid $id
8473 set todo [list $id]
8474 set queued($id) 1
8475 set nc 1
8476 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8477 set id [lindex $todo $i]
8478 set done($id) 1
8479 set ta [info exists hastaggedancestor($id)]
8480 if {!$ta} {
8481 incr nc -1
8483 # ignore tags on starting node
8484 if {!$ta && $i > 0} {
8485 if {[info exists idtags($id)]} {
8486 set tagloc($id) $id
8487 set ta 1
8488 } elseif {[info exists cached_dtags($id)]} {
8489 set tagloc($id) $cached_dtags($id)
8490 set ta 1
8493 foreach a $arcnos($id) {
8494 set d $arcstart($a)
8495 if {!$ta && $arctags($a) ne {}} {
8496 validate_arctags $a
8497 if {$arctags($a) ne {}} {
8498 lappend tagloc($id) [lindex $arctags($a) end]
8501 if {$ta || $arctags($a) ne {}} {
8502 set tomark [list $d]
8503 for {set j 0} {$j < [llength $tomark]} {incr j} {
8504 set dd [lindex $tomark $j]
8505 if {![info exists hastaggedancestor($dd)]} {
8506 if {[info exists done($dd)]} {
8507 foreach b $arcnos($dd) {
8508 lappend tomark $arcstart($b)
8510 if {[info exists tagloc($dd)]} {
8511 unset tagloc($dd)
8513 } elseif {[info exists queued($dd)]} {
8514 incr nc -1
8516 set hastaggedancestor($dd) 1
8520 if {![info exists queued($d)]} {
8521 lappend todo $d
8522 set queued($d) 1
8523 if {![info exists hastaggedancestor($d)]} {
8524 incr nc
8529 set tags {}
8530 foreach id [array names tagloc] {
8531 if {![info exists hastaggedancestor($id)]} {
8532 foreach t $tagloc($id) {
8533 if {[lsearch -exact $tags $t] < 0} {
8534 lappend tags $t
8539 set t2 [clock clicks -milliseconds]
8540 set loopix $i
8542 # remove tags that are descendents of other tags
8543 for {set i 0} {$i < [llength $tags]} {incr i} {
8544 set a [lindex $tags $i]
8545 for {set j 0} {$j < $i} {incr j} {
8546 set b [lindex $tags $j]
8547 set r [anc_or_desc $a $b]
8548 if {$r == 1} {
8549 set tags [lreplace $tags $j $j]
8550 incr j -1
8551 incr i -1
8552 } elseif {$r == -1} {
8553 set tags [lreplace $tags $i $i]
8554 incr i -1
8555 break
8560 if {[array names growing] ne {}} {
8561 # graph isn't finished, need to check if any tag could get
8562 # eclipsed by another tag coming later. Simply ignore any
8563 # tags that could later get eclipsed.
8564 set ctags {}
8565 foreach t $tags {
8566 if {[is_certain $t $origid]} {
8567 lappend ctags $t
8570 if {$tags eq $ctags} {
8571 set cached_dtags($origid) $tags
8572 } else {
8573 set tags $ctags
8575 } else {
8576 set cached_dtags($origid) $tags
8578 set t3 [clock clicks -milliseconds]
8579 if {0 && $t3 - $t1 >= 100} {
8580 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8581 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8583 return $tags
8586 proc anctags {id} {
8587 global arcnos arcids arcout arcend arctags idtags allparents
8588 global growing cached_atags
8590 if {![info exists allparents($id)]} {
8591 return {}
8593 set t1 [clock clicks -milliseconds]
8594 set argid $id
8595 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8596 # part-way along an arc; check that arc first
8597 set a [lindex $arcnos($id) 0]
8598 if {$arctags($a) ne {}} {
8599 validate_arctags $a
8600 set i [lsearch -exact $arcids($a) $id]
8601 foreach t $arctags($a) {
8602 set j [lsearch -exact $arcids($a) $t]
8603 if {$j > $i} {
8604 return $t
8608 if {![info exists arcend($a)]} {
8609 return {}
8611 set id $arcend($a)
8612 if {[info exists idtags($id)]} {
8613 return $id
8616 if {[info exists cached_atags($id)]} {
8617 return $cached_atags($id)
8620 set origid $id
8621 set todo [list $id]
8622 set queued($id) 1
8623 set taglist {}
8624 set nc 1
8625 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8626 set id [lindex $todo $i]
8627 set done($id) 1
8628 set td [info exists hastaggeddescendent($id)]
8629 if {!$td} {
8630 incr nc -1
8632 # ignore tags on starting node
8633 if {!$td && $i > 0} {
8634 if {[info exists idtags($id)]} {
8635 set tagloc($id) $id
8636 set td 1
8637 } elseif {[info exists cached_atags($id)]} {
8638 set tagloc($id) $cached_atags($id)
8639 set td 1
8642 foreach a $arcout($id) {
8643 if {!$td && $arctags($a) ne {}} {
8644 validate_arctags $a
8645 if {$arctags($a) ne {}} {
8646 lappend tagloc($id) [lindex $arctags($a) 0]
8649 if {![info exists arcend($a)]} continue
8650 set d $arcend($a)
8651 if {$td || $arctags($a) ne {}} {
8652 set tomark [list $d]
8653 for {set j 0} {$j < [llength $tomark]} {incr j} {
8654 set dd [lindex $tomark $j]
8655 if {![info exists hastaggeddescendent($dd)]} {
8656 if {[info exists done($dd)]} {
8657 foreach b $arcout($dd) {
8658 if {[info exists arcend($b)]} {
8659 lappend tomark $arcend($b)
8662 if {[info exists tagloc($dd)]} {
8663 unset tagloc($dd)
8665 } elseif {[info exists queued($dd)]} {
8666 incr nc -1
8668 set hastaggeddescendent($dd) 1
8672 if {![info exists queued($d)]} {
8673 lappend todo $d
8674 set queued($d) 1
8675 if {![info exists hastaggeddescendent($d)]} {
8676 incr nc
8681 set t2 [clock clicks -milliseconds]
8682 set loopix $i
8683 set tags {}
8684 foreach id [array names tagloc] {
8685 if {![info exists hastaggeddescendent($id)]} {
8686 foreach t $tagloc($id) {
8687 if {[lsearch -exact $tags $t] < 0} {
8688 lappend tags $t
8694 # remove tags that are ancestors of other tags
8695 for {set i 0} {$i < [llength $tags]} {incr i} {
8696 set a [lindex $tags $i]
8697 for {set j 0} {$j < $i} {incr j} {
8698 set b [lindex $tags $j]
8699 set r [anc_or_desc $a $b]
8700 if {$r == -1} {
8701 set tags [lreplace $tags $j $j]
8702 incr j -1
8703 incr i -1
8704 } elseif {$r == 1} {
8705 set tags [lreplace $tags $i $i]
8706 incr i -1
8707 break
8712 if {[array names growing] ne {}} {
8713 # graph isn't finished, need to check if any tag could get
8714 # eclipsed by another tag coming later. Simply ignore any
8715 # tags that could later get eclipsed.
8716 set ctags {}
8717 foreach t $tags {
8718 if {[is_certain $origid $t]} {
8719 lappend ctags $t
8722 if {$tags eq $ctags} {
8723 set cached_atags($origid) $tags
8724 } else {
8725 set tags $ctags
8727 } else {
8728 set cached_atags($origid) $tags
8730 set t3 [clock clicks -milliseconds]
8731 if {0 && $t3 - $t1 >= 100} {
8732 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8733 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8735 return $tags
8738 # Return the list of IDs that have heads that are descendents of id,
8739 # including id itself if it has a head.
8740 proc descheads {id} {
8741 global arcnos arcstart arcids archeads idheads cached_dheads
8742 global allparents
8744 if {![info exists allparents($id)]} {
8745 return {}
8747 set aret {}
8748 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8749 # part-way along an arc; check it first
8750 set a [lindex $arcnos($id) 0]
8751 if {$archeads($a) ne {}} {
8752 validate_archeads $a
8753 set i [lsearch -exact $arcids($a) $id]
8754 foreach t $archeads($a) {
8755 set j [lsearch -exact $arcids($a) $t]
8756 if {$j > $i} break
8757 lappend aret $t
8760 set id $arcstart($a)
8762 set origid $id
8763 set todo [list $id]
8764 set seen($id) 1
8765 set ret {}
8766 for {set i 0} {$i < [llength $todo]} {incr i} {
8767 set id [lindex $todo $i]
8768 if {[info exists cached_dheads($id)]} {
8769 set ret [concat $ret $cached_dheads($id)]
8770 } else {
8771 if {[info exists idheads($id)]} {
8772 lappend ret $id
8774 foreach a $arcnos($id) {
8775 if {$archeads($a) ne {}} {
8776 validate_archeads $a
8777 if {$archeads($a) ne {}} {
8778 set ret [concat $ret $archeads($a)]
8781 set d $arcstart($a)
8782 if {![info exists seen($d)]} {
8783 lappend todo $d
8784 set seen($d) 1
8789 set ret [lsort -unique $ret]
8790 set cached_dheads($origid) $ret
8791 return [concat $ret $aret]
8794 proc addedtag {id} {
8795 global arcnos arcout cached_dtags cached_atags
8797 if {![info exists arcnos($id)]} return
8798 if {![info exists arcout($id)]} {
8799 recalcarc [lindex $arcnos($id) 0]
8801 catch {unset cached_dtags}
8802 catch {unset cached_atags}
8805 proc addedhead {hid head} {
8806 global arcnos arcout cached_dheads
8808 if {![info exists arcnos($hid)]} return
8809 if {![info exists arcout($hid)]} {
8810 recalcarc [lindex $arcnos($hid) 0]
8812 catch {unset cached_dheads}
8815 proc removedhead {hid head} {
8816 global cached_dheads
8818 catch {unset cached_dheads}
8821 proc movedhead {hid head} {
8822 global arcnos arcout cached_dheads
8824 if {![info exists arcnos($hid)]} return
8825 if {![info exists arcout($hid)]} {
8826 recalcarc [lindex $arcnos($hid) 0]
8828 catch {unset cached_dheads}
8831 proc changedrefs {} {
8832 global cached_dheads cached_dtags cached_atags
8833 global arctags archeads arcnos arcout idheads idtags
8835 foreach id [concat [array names idheads] [array names idtags]] {
8836 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
8837 set a [lindex $arcnos($id) 0]
8838 if {![info exists donearc($a)]} {
8839 recalcarc $a
8840 set donearc($a) 1
8844 catch {unset cached_dtags}
8845 catch {unset cached_atags}
8846 catch {unset cached_dheads}
8849 proc rereadrefs {} {
8850 global idtags idheads idotherrefs mainheadid
8852 set refids [concat [array names idtags] \
8853 [array names idheads] [array names idotherrefs]]
8854 foreach id $refids {
8855 if {![info exists ref($id)]} {
8856 set ref($id) [listrefs $id]
8859 set oldmainhead $mainheadid
8860 readrefs
8861 changedrefs
8862 set refids [lsort -unique [concat $refids [array names idtags] \
8863 [array names idheads] [array names idotherrefs]]]
8864 foreach id $refids {
8865 set v [listrefs $id]
8866 if {![info exists ref($id)] || $ref($id) != $v ||
8867 ($id eq $oldmainhead && $id ne $mainheadid) ||
8868 ($id eq $mainheadid && $id ne $oldmainhead)} {
8869 redrawtags $id
8872 run refill_reflist
8875 proc listrefs {id} {
8876 global idtags idheads idotherrefs
8878 set x {}
8879 if {[info exists idtags($id)]} {
8880 set x $idtags($id)
8882 set y {}
8883 if {[info exists idheads($id)]} {
8884 set y $idheads($id)
8886 set z {}
8887 if {[info exists idotherrefs($id)]} {
8888 set z $idotherrefs($id)
8890 return [list $x $y $z]
8893 proc showtag {tag isnew} {
8894 global ctext tagcontents tagids linknum tagobjid
8896 if {$isnew} {
8897 addtohistory [list showtag $tag 0]
8899 $ctext conf -state normal
8900 clear_ctext
8901 settabs 0
8902 set linknum 0
8903 if {![info exists tagcontents($tag)]} {
8904 catch {
8905 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
8908 if {[info exists tagcontents($tag)]} {
8909 set text $tagcontents($tag)
8910 } else {
8911 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
8913 appendwithlinks $text {}
8914 $ctext conf -state disabled
8915 init_flist {}
8918 proc doquit {} {
8919 global stopped
8920 set stopped 100
8921 savestuff .
8922 destroy .
8925 proc mkfontdisp {font top which} {
8926 global fontattr fontpref $font
8928 set fontpref($font) [set $font]
8929 button $top.${font}but -text $which -font optionfont \
8930 -command [list choosefont $font $which]
8931 label $top.$font -relief flat -font $font \
8932 -text $fontattr($font,family) -justify left
8933 grid x $top.${font}but $top.$font -sticky w
8936 proc choosefont {font which} {
8937 global fontparam fontlist fonttop fontattr
8939 set fontparam(which) $which
8940 set fontparam(font) $font
8941 set fontparam(family) [font actual $font -family]
8942 set fontparam(size) $fontattr($font,size)
8943 set fontparam(weight) $fontattr($font,weight)
8944 set fontparam(slant) $fontattr($font,slant)
8945 set top .gitkfont
8946 set fonttop $top
8947 if {![winfo exists $top]} {
8948 font create sample
8949 eval font config sample [font actual $font]
8950 toplevel $top
8951 wm title $top [mc "Gitk font chooser"]
8952 label $top.l -textvariable fontparam(which)
8953 pack $top.l -side top
8954 set fontlist [lsort [font families]]
8955 frame $top.f
8956 listbox $top.f.fam -listvariable fontlist \
8957 -yscrollcommand [list $top.f.sb set]
8958 bind $top.f.fam <<ListboxSelect>> selfontfam
8959 scrollbar $top.f.sb -command [list $top.f.fam yview]
8960 pack $top.f.sb -side right -fill y
8961 pack $top.f.fam -side left -fill both -expand 1
8962 pack $top.f -side top -fill both -expand 1
8963 frame $top.g
8964 spinbox $top.g.size -from 4 -to 40 -width 4 \
8965 -textvariable fontparam(size) \
8966 -validatecommand {string is integer -strict %s}
8967 checkbutton $top.g.bold -padx 5 \
8968 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
8969 -variable fontparam(weight) -onvalue bold -offvalue normal
8970 checkbutton $top.g.ital -padx 5 \
8971 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
8972 -variable fontparam(slant) -onvalue italic -offvalue roman
8973 pack $top.g.size $top.g.bold $top.g.ital -side left
8974 pack $top.g -side top
8975 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
8976 -background white
8977 $top.c create text 100 25 -anchor center -text $which -font sample \
8978 -fill black -tags text
8979 bind $top.c <Configure> [list centertext $top.c]
8980 pack $top.c -side top -fill x
8981 frame $top.buts
8982 button $top.buts.ok -text [mc "OK"] -command fontok -default active
8983 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
8984 grid $top.buts.ok $top.buts.can
8985 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8986 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8987 pack $top.buts -side bottom -fill x
8988 trace add variable fontparam write chg_fontparam
8989 } else {
8990 raise $top
8991 $top.c itemconf text -text $which
8993 set i [lsearch -exact $fontlist $fontparam(family)]
8994 if {$i >= 0} {
8995 $top.f.fam selection set $i
8996 $top.f.fam see $i
9000 proc centertext {w} {
9001 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9004 proc fontok {} {
9005 global fontparam fontpref prefstop
9007 set f $fontparam(font)
9008 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9009 if {$fontparam(weight) eq "bold"} {
9010 lappend fontpref($f) "bold"
9012 if {$fontparam(slant) eq "italic"} {
9013 lappend fontpref($f) "italic"
9015 set w $prefstop.$f
9016 $w conf -text $fontparam(family) -font $fontpref($f)
9018 fontcan
9021 proc fontcan {} {
9022 global fonttop fontparam
9024 if {[info exists fonttop]} {
9025 catch {destroy $fonttop}
9026 catch {font delete sample}
9027 unset fonttop
9028 unset fontparam
9032 proc selfontfam {} {
9033 global fonttop fontparam
9035 set i [$fonttop.f.fam curselection]
9036 if {$i ne {}} {
9037 set fontparam(family) [$fonttop.f.fam get $i]
9041 proc chg_fontparam {v sub op} {
9042 global fontparam
9044 font config sample -$sub $fontparam($sub)
9047 proc doprefs {} {
9048 global maxwidth maxgraphpct
9049 global oldprefs prefstop showneartags showlocalchanges
9050 global bgcolor fgcolor ctext diffcolors selectbgcolor
9051 global tabstop limitdiffs autoselect
9053 set top .gitkprefs
9054 set prefstop $top
9055 if {[winfo exists $top]} {
9056 raise $top
9057 return
9059 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9060 limitdiffs tabstop} {
9061 set oldprefs($v) [set $v]
9063 toplevel $top
9064 wm title $top [mc "Gitk preferences"]
9065 label $top.ldisp -text [mc "Commit list display options"]
9066 grid $top.ldisp - -sticky w -pady 10
9067 label $top.spacer -text " "
9068 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9069 -font optionfont
9070 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9071 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9072 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9073 -font optionfont
9074 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9075 grid x $top.maxpctl $top.maxpct -sticky w
9076 frame $top.showlocal
9077 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9078 checkbutton $top.showlocal.b -variable showlocalchanges
9079 pack $top.showlocal.b $top.showlocal.l -side left
9080 grid x $top.showlocal -sticky w
9081 frame $top.autoselect
9082 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9083 checkbutton $top.autoselect.b -variable autoselect
9084 pack $top.autoselect.b $top.autoselect.l -side left
9085 grid x $top.autoselect -sticky w
9087 label $top.ddisp -text [mc "Diff display options"]
9088 grid $top.ddisp - -sticky w -pady 10
9089 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9090 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9091 grid x $top.tabstopl $top.tabstop -sticky w
9092 frame $top.ntag
9093 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9094 checkbutton $top.ntag.b -variable showneartags
9095 pack $top.ntag.b $top.ntag.l -side left
9096 grid x $top.ntag -sticky w
9097 frame $top.ldiff
9098 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9099 checkbutton $top.ldiff.b -variable limitdiffs
9100 pack $top.ldiff.b $top.ldiff.l -side left
9101 grid x $top.ldiff -sticky w
9103 label $top.cdisp -text [mc "Colors: press to choose"]
9104 grid $top.cdisp - -sticky w -pady 10
9105 label $top.bg -padx 40 -relief sunk -background $bgcolor
9106 button $top.bgbut -text [mc "Background"] -font optionfont \
9107 -command [list choosecolor bgcolor 0 $top.bg background setbg]
9108 grid x $top.bgbut $top.bg -sticky w
9109 label $top.fg -padx 40 -relief sunk -background $fgcolor
9110 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9111 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
9112 grid x $top.fgbut $top.fg -sticky w
9113 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9114 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9115 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9116 [list $ctext tag conf d0 -foreground]]
9117 grid x $top.diffoldbut $top.diffold -sticky w
9118 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9119 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9120 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9121 [list $ctext tag conf d1 -foreground]]
9122 grid x $top.diffnewbut $top.diffnew -sticky w
9123 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9124 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9125 -command [list choosecolor diffcolors 2 $top.hunksep \
9126 "diff hunk header" \
9127 [list $ctext tag conf hunksep -foreground]]
9128 grid x $top.hunksepbut $top.hunksep -sticky w
9129 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9130 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9131 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
9132 grid x $top.selbgbut $top.selbgsep -sticky w
9134 label $top.cfont -text [mc "Fonts: press to choose"]
9135 grid $top.cfont - -sticky w -pady 10
9136 mkfontdisp mainfont $top [mc "Main font"]
9137 mkfontdisp textfont $top [mc "Diff display font"]
9138 mkfontdisp uifont $top [mc "User interface font"]
9140 frame $top.buts
9141 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9142 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9143 grid $top.buts.ok $top.buts.can
9144 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9145 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9146 grid $top.buts - - -pady 10 -sticky ew
9147 bind $top <Visibility> "focus $top.buts.ok"
9150 proc choosecolor {v vi w x cmd} {
9151 global $v
9153 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9154 -title [mc "Gitk: choose color for %s" $x]]
9155 if {$c eq {}} return
9156 $w conf -background $c
9157 lset $v $vi $c
9158 eval $cmd $c
9161 proc setselbg {c} {
9162 global bglist cflist
9163 foreach w $bglist {
9164 $w configure -selectbackground $c
9166 $cflist tag configure highlight \
9167 -background [$cflist cget -selectbackground]
9168 allcanvs itemconf secsel -fill $c
9171 proc setbg {c} {
9172 global bglist
9174 foreach w $bglist {
9175 $w conf -background $c
9179 proc setfg {c} {
9180 global fglist canv
9182 foreach w $fglist {
9183 $w conf -foreground $c
9185 allcanvs itemconf text -fill $c
9186 $canv itemconf circle -outline $c
9189 proc prefscan {} {
9190 global oldprefs prefstop
9192 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9193 limitdiffs tabstop} {
9194 global $v
9195 set $v $oldprefs($v)
9197 catch {destroy $prefstop}
9198 unset prefstop
9199 fontcan
9202 proc prefsok {} {
9203 global maxwidth maxgraphpct
9204 global oldprefs prefstop showneartags showlocalchanges
9205 global fontpref mainfont textfont uifont
9206 global limitdiffs treediffs
9208 catch {destroy $prefstop}
9209 unset prefstop
9210 fontcan
9211 set fontchanged 0
9212 if {$mainfont ne $fontpref(mainfont)} {
9213 set mainfont $fontpref(mainfont)
9214 parsefont mainfont $mainfont
9215 eval font configure mainfont [fontflags mainfont]
9216 eval font configure mainfontbold [fontflags mainfont 1]
9217 setcoords
9218 set fontchanged 1
9220 if {$textfont ne $fontpref(textfont)} {
9221 set textfont $fontpref(textfont)
9222 parsefont textfont $textfont
9223 eval font configure textfont [fontflags textfont]
9224 eval font configure textfontbold [fontflags textfont 1]
9226 if {$uifont ne $fontpref(uifont)} {
9227 set uifont $fontpref(uifont)
9228 parsefont uifont $uifont
9229 eval font configure uifont [fontflags uifont]
9231 settabs
9232 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9233 if {$showlocalchanges} {
9234 doshowlocalchanges
9235 } else {
9236 dohidelocalchanges
9239 if {$limitdiffs != $oldprefs(limitdiffs)} {
9240 # treediffs elements are limited by path
9241 catch {unset treediffs}
9243 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9244 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9245 redisplay
9246 } elseif {$showneartags != $oldprefs(showneartags) ||
9247 $limitdiffs != $oldprefs(limitdiffs)} {
9248 reselectline
9252 proc formatdate {d} {
9253 global datetimeformat
9254 if {$d ne {}} {
9255 set d [clock format $d -format $datetimeformat]
9257 return $d
9260 # This list of encoding names and aliases is distilled from
9261 # http://www.iana.org/assignments/character-sets.
9262 # Not all of them are supported by Tcl.
9263 set encoding_aliases {
9264 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9265 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9266 { ISO-10646-UTF-1 csISO10646UTF1 }
9267 { ISO_646.basic:1983 ref csISO646basic1983 }
9268 { INVARIANT csINVARIANT }
9269 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9270 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9271 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9272 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9273 { NATS-DANO iso-ir-9-1 csNATSDANO }
9274 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9275 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9276 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9277 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9278 { ISO-2022-KR csISO2022KR }
9279 { EUC-KR csEUCKR }
9280 { ISO-2022-JP csISO2022JP }
9281 { ISO-2022-JP-2 csISO2022JP2 }
9282 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9283 csISO13JISC6220jp }
9284 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9285 { IT iso-ir-15 ISO646-IT csISO15Italian }
9286 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9287 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9288 { greek7-old iso-ir-18 csISO18Greek7Old }
9289 { latin-greek iso-ir-19 csISO19LatinGreek }
9290 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9291 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9292 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9293 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9294 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9295 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9296 { INIS iso-ir-49 csISO49INIS }
9297 { INIS-8 iso-ir-50 csISO50INIS8 }
9298 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9299 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9300 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9301 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9302 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9303 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9304 csISO60Norwegian1 }
9305 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9306 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9307 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9308 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9309 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9310 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9311 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9312 { greek7 iso-ir-88 csISO88Greek7 }
9313 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9314 { iso-ir-90 csISO90 }
9315 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9316 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9317 csISO92JISC62991984b }
9318 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9319 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9320 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9321 csISO95JIS62291984handadd }
9322 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9323 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9324 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9325 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9326 CP819 csISOLatin1 }
9327 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9328 { T.61-7bit iso-ir-102 csISO102T617bit }
9329 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9330 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9331 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9332 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9333 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9334 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9335 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9336 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9337 arabic csISOLatinArabic }
9338 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9339 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9340 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9341 greek greek8 csISOLatinGreek }
9342 { T.101-G2 iso-ir-128 csISO128T101G2 }
9343 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9344 csISOLatinHebrew }
9345 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9346 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9347 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9348 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9349 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9350 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9351 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9352 csISOLatinCyrillic }
9353 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9354 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9355 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9356 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9357 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9358 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9359 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9360 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9361 { ISO_10367-box iso-ir-155 csISO10367Box }
9362 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9363 { latin-lap lap iso-ir-158 csISO158Lap }
9364 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9365 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9366 { us-dk csUSDK }
9367 { dk-us csDKUS }
9368 { JIS_X0201 X0201 csHalfWidthKatakana }
9369 { KSC5636 ISO646-KR csKSC5636 }
9370 { ISO-10646-UCS-2 csUnicode }
9371 { ISO-10646-UCS-4 csUCS4 }
9372 { DEC-MCS dec csDECMCS }
9373 { hp-roman8 roman8 r8 csHPRoman8 }
9374 { macintosh mac csMacintosh }
9375 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9376 csIBM037 }
9377 { IBM038 EBCDIC-INT cp038 csIBM038 }
9378 { IBM273 CP273 csIBM273 }
9379 { IBM274 EBCDIC-BE CP274 csIBM274 }
9380 { IBM275 EBCDIC-BR cp275 csIBM275 }
9381 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9382 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9383 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9384 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9385 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9386 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9387 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9388 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9389 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9390 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9391 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9392 { IBM437 cp437 437 csPC8CodePage437 }
9393 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9394 { IBM775 cp775 csPC775Baltic }
9395 { IBM850 cp850 850 csPC850Multilingual }
9396 { IBM851 cp851 851 csIBM851 }
9397 { IBM852 cp852 852 csPCp852 }
9398 { IBM855 cp855 855 csIBM855 }
9399 { IBM857 cp857 857 csIBM857 }
9400 { IBM860 cp860 860 csIBM860 }
9401 { IBM861 cp861 861 cp-is csIBM861 }
9402 { IBM862 cp862 862 csPC862LatinHebrew }
9403 { IBM863 cp863 863 csIBM863 }
9404 { IBM864 cp864 csIBM864 }
9405 { IBM865 cp865 865 csIBM865 }
9406 { IBM866 cp866 866 csIBM866 }
9407 { IBM868 CP868 cp-ar csIBM868 }
9408 { IBM869 cp869 869 cp-gr csIBM869 }
9409 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9410 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9411 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9412 { IBM891 cp891 csIBM891 }
9413 { IBM903 cp903 csIBM903 }
9414 { IBM904 cp904 904 csIBBM904 }
9415 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9416 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9417 { IBM1026 CP1026 csIBM1026 }
9418 { EBCDIC-AT-DE csIBMEBCDICATDE }
9419 { EBCDIC-AT-DE-A csEBCDICATDEA }
9420 { EBCDIC-CA-FR csEBCDICCAFR }
9421 { EBCDIC-DK-NO csEBCDICDKNO }
9422 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9423 { EBCDIC-FI-SE csEBCDICFISE }
9424 { EBCDIC-FI-SE-A csEBCDICFISEA }
9425 { EBCDIC-FR csEBCDICFR }
9426 { EBCDIC-IT csEBCDICIT }
9427 { EBCDIC-PT csEBCDICPT }
9428 { EBCDIC-ES csEBCDICES }
9429 { EBCDIC-ES-A csEBCDICESA }
9430 { EBCDIC-ES-S csEBCDICESS }
9431 { EBCDIC-UK csEBCDICUK }
9432 { EBCDIC-US csEBCDICUS }
9433 { UNKNOWN-8BIT csUnknown8BiT }
9434 { MNEMONIC csMnemonic }
9435 { MNEM csMnem }
9436 { VISCII csVISCII }
9437 { VIQR csVIQR }
9438 { KOI8-R csKOI8R }
9439 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9440 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9441 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9442 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9443 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9444 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9445 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9446 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9447 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9448 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9449 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9450 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9451 { IBM1047 IBM-1047 }
9452 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9453 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9454 { UNICODE-1-1 csUnicode11 }
9455 { CESU-8 csCESU-8 }
9456 { BOCU-1 csBOCU-1 }
9457 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9458 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9459 l8 }
9460 { ISO-8859-15 ISO_8859-15 Latin-9 }
9461 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9462 { GBK CP936 MS936 windows-936 }
9463 { JIS_Encoding csJISEncoding }
9464 { Shift_JIS MS_Kanji csShiftJIS }
9465 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9466 EUC-JP }
9467 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9468 { ISO-10646-UCS-Basic csUnicodeASCII }
9469 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9470 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9471 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9472 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9473 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9474 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9475 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9476 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9477 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9478 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9479 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9480 { Ventura-US csVenturaUS }
9481 { Ventura-International csVenturaInternational }
9482 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9483 { PC8-Turkish csPC8Turkish }
9484 { IBM-Symbols csIBMSymbols }
9485 { IBM-Thai csIBMThai }
9486 { HP-Legal csHPLegal }
9487 { HP-Pi-font csHPPiFont }
9488 { HP-Math8 csHPMath8 }
9489 { Adobe-Symbol-Encoding csHPPSMath }
9490 { HP-DeskTop csHPDesktop }
9491 { Ventura-Math csVenturaMath }
9492 { Microsoft-Publishing csMicrosoftPublishing }
9493 { Windows-31J csWindows31J }
9494 { GB2312 csGB2312 }
9495 { Big5 csBig5 }
9498 proc tcl_encoding {enc} {
9499 global encoding_aliases
9500 set names [encoding names]
9501 set lcnames [string tolower $names]
9502 set enc [string tolower $enc]
9503 set i [lsearch -exact $lcnames $enc]
9504 if {$i < 0} {
9505 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9506 if {[regsub {^iso[-_]} $enc iso encx]} {
9507 set i [lsearch -exact $lcnames $encx]
9510 if {$i < 0} {
9511 foreach l $encoding_aliases {
9512 set ll [string tolower $l]
9513 if {[lsearch -exact $ll $enc] < 0} continue
9514 # look through the aliases for one that tcl knows about
9515 foreach e $ll {
9516 set i [lsearch -exact $lcnames $e]
9517 if {$i < 0} {
9518 if {[regsub {^iso[-_]} $e iso ex]} {
9519 set i [lsearch -exact $lcnames $ex]
9522 if {$i >= 0} break
9524 break
9527 if {$i >= 0} {
9528 return [lindex $names $i]
9530 return {}
9533 # First check that Tcl/Tk is recent enough
9534 if {[catch {package require Tk 8.4} err]} {
9535 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9536 Gitk requires at least Tcl/Tk 8.4."]
9537 exit 1
9540 # defaults...
9541 set wrcomcmd "git diff-tree --stdin -p --pretty"
9543 set gitencoding {}
9544 catch {
9545 set gitencoding [exec git config --get i18n.commitencoding]
9547 if {$gitencoding == ""} {
9548 set gitencoding "utf-8"
9550 set tclencoding [tcl_encoding $gitencoding]
9551 if {$tclencoding == {}} {
9552 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9555 set mainfont {Helvetica 9}
9556 set textfont {Courier 9}
9557 set uifont {Helvetica 9 bold}
9558 set tabstop 8
9559 set findmergefiles 0
9560 set maxgraphpct 50
9561 set maxwidth 16
9562 set revlistorder 0
9563 set fastdate 0
9564 set uparrowlen 5
9565 set downarrowlen 5
9566 set mingaplen 100
9567 set cmitmode "patch"
9568 set wrapcomment "none"
9569 set showneartags 1
9570 set maxrefs 20
9571 set maxlinelen 200
9572 set showlocalchanges 1
9573 set limitdiffs 1
9574 set datetimeformat "%Y-%m-%d %H:%M:%S"
9575 set autoselect 1
9577 set colors {green red blue magenta darkgrey brown orange}
9578 set bgcolor white
9579 set fgcolor black
9580 set diffcolors {red "#00a000" blue}
9581 set diffcontext 3
9582 set ignorespace 0
9583 set selectbgcolor gray85
9585 ## For msgcat loading, first locate the installation location.
9586 if { [info exists ::env(GITK_MSGSDIR)] } {
9587 ## Msgsdir was manually set in the environment.
9588 set gitk_msgsdir $::env(GITK_MSGSDIR)
9589 } else {
9590 ## Let's guess the prefix from argv0.
9591 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
9592 set gitk_libdir [file join $gitk_prefix share gitk lib]
9593 set gitk_msgsdir [file join $gitk_libdir msgs]
9594 unset gitk_prefix
9597 ## Internationalization (i18n) through msgcat and gettext. See
9598 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
9599 package require msgcat
9600 namespace import ::msgcat::mc
9601 ## And eventually load the actual message catalog
9602 ::msgcat::mcload $gitk_msgsdir
9604 catch {source ~/.gitk}
9606 font create optionfont -family sans-serif -size -12
9608 parsefont mainfont $mainfont
9609 eval font create mainfont [fontflags mainfont]
9610 eval font create mainfontbold [fontflags mainfont 1]
9612 parsefont textfont $textfont
9613 eval font create textfont [fontflags textfont]
9614 eval font create textfontbold [fontflags textfont 1]
9616 parsefont uifont $uifont
9617 eval font create uifont [fontflags uifont]
9619 setoptions
9621 # check that we can find a .git directory somewhere...
9622 if {[catch {set gitdir [gitdir]}]} {
9623 show_error {} . [mc "Cannot find a git repository here."]
9624 exit 1
9626 if {![file isdirectory $gitdir]} {
9627 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
9628 exit 1
9631 set revtreeargs {}
9632 set cmdline_files {}
9633 set i 0
9634 set revtreeargscmd {}
9635 foreach arg $argv {
9636 switch -glob -- $arg {
9637 "" { }
9638 "--" {
9639 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9640 break
9642 "--argscmd=*" {
9643 set revtreeargscmd [string range $arg 10 end]
9645 default {
9646 lappend revtreeargs $arg
9649 incr i
9652 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9653 # no -- on command line, but some arguments (other than --argscmd)
9654 if {[catch {
9655 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9656 set cmdline_files [split $f "\n"]
9657 set n [llength $cmdline_files]
9658 set revtreeargs [lrange $revtreeargs 0 end-$n]
9659 # Unfortunately git rev-parse doesn't produce an error when
9660 # something is both a revision and a filename. To be consistent
9661 # with git log and git rev-list, check revtreeargs for filenames.
9662 foreach arg $revtreeargs {
9663 if {[file exists $arg]} {
9664 show_error {} . [mc "Ambiguous argument '%s': both revision\
9665 and filename" $arg]
9666 exit 1
9669 } err]} {
9670 # unfortunately we get both stdout and stderr in $err,
9671 # so look for "fatal:".
9672 set i [string first "fatal:" $err]
9673 if {$i > 0} {
9674 set err [string range $err [expr {$i + 6}] end]
9676 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
9677 exit 1
9681 set nullid "0000000000000000000000000000000000000000"
9682 set nullid2 "0000000000000000000000000000000000000001"
9684 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9686 set runq {}
9687 set history {}
9688 set historyindex 0
9689 set fh_serial 0
9690 set nhl_names {}
9691 set highlight_paths {}
9692 set findpattern {}
9693 set searchdirn -forwards
9694 set boldrows {}
9695 set boldnamerows {}
9696 set diffelide {0 0}
9697 set markingmatches 0
9698 set linkentercount 0
9699 set need_redisplay 0
9700 set nrows_drawn 0
9701 set firsttabstop 0
9703 set nextviewnum 1
9704 set curview 0
9705 set selectedview 0
9706 set selectedhlview [mc "None"]
9707 set highlight_related [mc "None"]
9708 set highlight_files {}
9709 set viewfiles(0) {}
9710 set viewperm(0) 0
9711 set viewargs(0) {}
9712 set viewargscmd(0) {}
9714 set loginstance 0
9715 set cmdlineok 0
9716 set stopped 0
9717 set stuffsaved 0
9718 set patchnum 0
9719 set lserial 0
9720 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
9721 setcoords
9722 makewindow
9723 # wait for the window to become visible
9724 tkwait visibility .
9725 wm title . "[file tail $argv0]: [file tail [pwd]]"
9726 readrefs
9728 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
9729 # create a view for the files/dirs specified on the command line
9730 set curview 1
9731 set selectedview 1
9732 set nextviewnum 2
9733 set viewname(1) [mc "Command line"]
9734 set viewfiles(1) $cmdline_files
9735 set viewargs(1) $revtreeargs
9736 set viewargscmd(1) $revtreeargscmd
9737 set viewperm(1) 0
9738 set vdatemode(1) 0
9739 addviewmenu 1
9740 .bar.view entryconf [mc "Edit view..."] -state normal
9741 .bar.view entryconf [mc "Delete view"] -state normal
9744 if {[info exists permviews]} {
9745 foreach v $permviews {
9746 set n $nextviewnum
9747 incr nextviewnum
9748 set viewname($n) [lindex $v 0]
9749 set viewfiles($n) [lindex $v 1]
9750 set viewargs($n) [lindex $v 2]
9751 set viewargscmd($n) [lindex $v 3]
9752 set viewperm($n) 1
9753 addviewmenu $n
9756 getcommits