gitk: Add option to specify the default commit on command line
[git/dscho.git] / gitk
blob7698b7081706026f5e036ebdcbfe9ca8c9fc4506
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 currunq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {} && ![info exists currunq]} {
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 currunq
43 fileevent $fd readable {}
44 if {$runq eq {} && ![info exists currunq]} {
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 currunq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set currunq [lindex $runq 0]
71 set runq [lrange $runq 1 end]
72 set repeat [eval $script]
73 unset currunq
74 set t1 [clock clicks -milliseconds]
75 set t [expr {$t1 - $t0}]
76 if {$repeat ne {} && $repeat} {
77 if {$fd eq {} || $repeat == 2} {
78 # script returns 1 if it wants to be readded
79 # file readers return 2 if they could do more straight away
80 lappend runq [list $fd $script]
81 } else {
82 fileevent $fd readable [list filereadable $fd $script]
84 } elseif {$fd eq {}} {
85 unset isonrunq($script)
87 set t0 $t1
88 if {$t1 - $tstart >= 80} break
90 if {$runq ne {}} {
91 after idle dorunq
95 proc reg_instance {fd} {
96 global commfd leftover loginstance
98 set i [incr loginstance]
99 set commfd($i) $fd
100 set leftover($i) {}
101 return $i
104 proc unmerged_files {files} {
105 global nr_unmerged
107 # find the list of unmerged files
108 set mlist {}
109 set nr_unmerged 0
110 if {[catch {
111 set fd [open "| git ls-files -u" r]
112 } err]} {
113 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
114 exit 1
116 while {[gets $fd line] >= 0} {
117 set i [string first "\t" $line]
118 if {$i < 0} continue
119 set fname [string range $line [expr {$i+1}] end]
120 if {[lsearch -exact $mlist $fname] >= 0} continue
121 incr nr_unmerged
122 if {$files eq {} || [path_filter $files $fname]} {
123 lappend mlist $fname
126 catch {close $fd}
127 return $mlist
130 proc parseviewargs {n arglist} {
131 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
133 set vdatemode($n) 0
134 set vmergeonly($n) 0
135 set glflags {}
136 set diffargs {}
137 set nextisval 0
138 set revargs {}
139 set origargs $arglist
140 set allknown 1
141 set filtered 0
142 set i -1
143 foreach arg $arglist {
144 incr i
145 if {$nextisval} {
146 lappend glflags $arg
147 set nextisval 0
148 continue
150 switch -glob -- $arg {
151 "-d" -
152 "--date-order" {
153 set vdatemode($n) 1
154 # remove from origargs in case we hit an unknown option
155 set origargs [lreplace $origargs $i $i]
156 incr i -1
158 # These request or affect diff output, which we don't want.
159 # Some could be used to set our defaults for diff display.
160 "-[puabwcrRBMC]" -
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 lappend diffargs $arg
168 # These cause our parsing of git log's output to fail, or else
169 # they're options we want to set ourselves, so ignore them.
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
178 # These are harmless, and some are even useful
179 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
180 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
181 "--full-history" - "--dense" - "--sparse" -
182 "--follow" - "--left-right" - "--encoding=*" {
183 lappend glflags $arg
185 # These mean that we get a subset of the commits
186 "--diff-filter=*" - "--no-merges" - "--unpacked" -
187 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
188 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
189 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
190 "--remove-empty" - "--first-parent" - "--cherry-pick" -
191 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
192 set filtered 1
193 lappend glflags $arg
195 # This appears to be the only one that has a value as a
196 # separate word following it
197 "-n" {
198 set filtered 1
199 set nextisval 1
200 lappend glflags $arg
202 "--not" {
203 set notflag [expr {!$notflag}]
204 lappend revargs $arg
206 "--all" {
207 lappend revargs $arg
209 "--merge" {
210 set vmergeonly($n) 1
211 # git rev-parse doesn't understand --merge
212 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
214 # Other flag arguments including -<n>
215 "-*" {
216 if {[string is digit -strict [string range $arg 1 end]]} {
217 set filtered 1
218 } else {
219 # a flag argument that we don't recognize;
220 # that means we can't optimize
221 set allknown 0
223 lappend glflags $arg
225 # Non-flag arguments specify commits or ranges of commits
226 default {
227 if {[string match "*...*" $arg]} {
228 lappend revargs --gitk-symmetric-diff-marker
230 lappend revargs $arg
234 set vdflags($n) $diffargs
235 set vflags($n) $glflags
236 set vrevs($n) $revargs
237 set vfiltered($n) $filtered
238 set vorigargs($n) $origargs
239 return $allknown
242 proc parseviewrevs {view revs} {
243 global vposids vnegids
245 if {$revs eq {}} {
246 set revs HEAD
248 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
249 # we get stdout followed by stderr in $err
250 # for an unknown rev, git rev-parse echoes it and then errors out
251 set errlines [split $err "\n"]
252 set badrev {}
253 for {set l 0} {$l < [llength $errlines]} {incr l} {
254 set line [lindex $errlines $l]
255 if {!([string length $line] == 40 && [string is xdigit $line])} {
256 if {[string match "fatal:*" $line]} {
257 if {[string match "fatal: ambiguous argument*" $line]
258 && $badrev ne {}} {
259 if {[llength $badrev] == 1} {
260 set err "unknown revision $badrev"
261 } else {
262 set err "unknown revisions: [join $badrev ", "]"
264 } else {
265 set err [join [lrange $errlines $l end] "\n"]
267 break
269 lappend badrev $line
272 error_popup "Error parsing revisions: $err"
273 return {}
275 set ret {}
276 set pos {}
277 set neg {}
278 set sdm 0
279 foreach id [split $ids "\n"] {
280 if {$id eq "--gitk-symmetric-diff-marker"} {
281 set sdm 4
282 } elseif {[string match "^*" $id]} {
283 if {$sdm != 1} {
284 lappend ret $id
285 if {$sdm == 3} {
286 set sdm 0
289 lappend neg [string range $id 1 end]
290 } else {
291 if {$sdm != 2} {
292 lappend ret $id
293 } else {
294 lset ret end [lindex $ret end]...$id
296 lappend pos $id
298 incr sdm -1
300 set vposids($view) $pos
301 set vnegids($view) $neg
302 return $ret
305 # Start off a git log process and arrange to read its output
306 proc start_rev_list {view} {
307 global startmsecs commitidx viewcomplete curview
308 global tclencoding
309 global viewargs viewargscmd viewfiles vfilelimit
310 global showlocalchanges commitinterest
311 global viewactive viewinstances vmergeonly
312 global mainheadid
313 global vcanopt vflags vrevs vorigargs
315 set startmsecs [clock clicks -milliseconds]
316 set commitidx($view) 0
317 # these are set this way for the error exits
318 set viewcomplete($view) 1
319 set viewactive($view) 0
320 varcinit $view
322 set args $viewargs($view)
323 if {$viewargscmd($view) ne {}} {
324 if {[catch {
325 set str [exec sh -c $viewargscmd($view)]
326 } err]} {
327 error_popup "Error executing --argscmd command: $err"
328 return 0
330 set args [concat $args [split $str "\n"]]
332 set vcanopt($view) [parseviewargs $view $args]
334 set files $viewfiles($view)
335 if {$vmergeonly($view)} {
336 set files [unmerged_files $files]
337 if {$files eq {}} {
338 global nr_unmerged
339 if {$nr_unmerged == 0} {
340 error_popup [mc "No files selected: --merge specified but\
341 no files are unmerged."]
342 } else {
343 error_popup [mc "No files selected: --merge specified but\
344 no unmerged files are within file limit."]
346 return 0
349 set vfilelimit($view) $files
351 if {$vcanopt($view)} {
352 set revs [parseviewrevs $view $vrevs($view)]
353 if {$revs eq {}} {
354 return 0
356 set args [concat $vflags($view) $revs]
357 } else {
358 set args $vorigargs($view)
361 if {[catch {
362 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
363 --boundary $args "--" $files] r]
364 } err]} {
365 error_popup "[mc "Error executing git log:"] $err"
366 return 0
368 set i [reg_instance $fd]
369 set viewinstances($view) [list $i]
370 if {$showlocalchanges && $mainheadid ne {}} {
371 lappend commitinterest($mainheadid) {dodiffindex}
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
381 return 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
388 catch {
389 set pid [pid $fd]
391 if {$::tcl_platform(platform) eq {windows}} {
392 exec kill -f $pid
393 } else {
394 exec kill $pid
397 catch {close $fd}
398 nukefile $fd
399 unset commfd($inst)
400 unset leftover($inst)
403 proc stop_backends {} {
404 global commfd
406 foreach inst [array names commfd] {
407 stop_instance $inst
411 proc stop_rev_list {view} {
412 global viewinstances
414 foreach inst $viewinstances($view) {
415 stop_instance $inst
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid selectheadid
423 if {$selid ne {}} {
424 set pending_select $selid
425 } elseif {$selectheadid ne {}} {
426 set pending_select $selectheadid
427 } else {
428 set pending_select $mainheadid
432 proc getcommits {selid} {
433 global canv curview need_redisplay viewactive
435 initlayout
436 if {[start_rev_list $curview]} {
437 reset_pending_select $selid
438 show_status [mc "Reading commits..."]
439 set need_redisplay 1
440 } else {
441 show_status [mc "No commits selected"]
445 proc updatecommits {} {
446 global curview vcanopt vorigargs vfilelimit viewinstances
447 global viewactive viewcomplete tclencoding
448 global startmsecs showneartags showlocalchanges
449 global mainheadid pending_select
450 global isworktree
451 global varcid vposids vnegids vflags vrevs
453 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
454 set oldmainid $mainheadid
455 rereadrefs
456 if {$showlocalchanges} {
457 if {$mainheadid ne $oldmainid} {
458 dohidelocalchanges
460 if {[commitinview $mainheadid $curview]} {
461 dodiffindex
464 set view $curview
465 if {$vcanopt($view)} {
466 set oldpos $vposids($view)
467 set oldneg $vnegids($view)
468 set revs [parseviewrevs $view $vrevs($view)]
469 if {$revs eq {}} {
470 return
472 # note: getting the delta when negative refs change is hard,
473 # and could require multiple git log invocations, so in that
474 # case we ask git log for all the commits (not just the delta)
475 if {$oldneg eq $vnegids($view)} {
476 set newrevs {}
477 set npos 0
478 # take out positive refs that we asked for before or
479 # that we have already seen
480 foreach rev $revs {
481 if {[string length $rev] == 40} {
482 if {[lsearch -exact $oldpos $rev] < 0
483 && ![info exists varcid($view,$rev)]} {
484 lappend newrevs $rev
485 incr npos
487 } else {
488 lappend $newrevs $rev
491 if {$npos == 0} return
492 set revs $newrevs
493 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
495 set args [concat $vflags($view) $revs --not $oldpos]
496 } else {
497 set args $vorigargs($view)
499 if {[catch {
500 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
501 --boundary $args "--" $vfilelimit($view)] r]
502 } err]} {
503 error_popup "Error executing git log: $err"
504 return
506 if {$viewactive($view) == 0} {
507 set startmsecs [clock clicks -milliseconds]
509 set i [reg_instance $fd]
510 lappend viewinstances($view) $i
511 fconfigure $fd -blocking 0 -translation lf -eofchar {}
512 if {$tclencoding != {}} {
513 fconfigure $fd -encoding $tclencoding
515 filerun $fd [list getcommitlines $fd $i $view 1]
516 incr viewactive($view)
517 set viewcomplete($view) 0
518 reset_pending_select {}
519 nowbusy $view "Reading"
520 if {$showneartags} {
521 getallcommits
525 proc reloadcommits {} {
526 global curview viewcomplete selectedline currentid thickerline
527 global showneartags treediffs commitinterest cached_commitrow
528 global targetid
530 set selid {}
531 if {$selectedline ne {}} {
532 set selid $currentid
535 if {!$viewcomplete($curview)} {
536 stop_rev_list $curview
538 resetvarcs $curview
539 set selectedline {}
540 catch {unset currentid}
541 catch {unset thickerline}
542 catch {unset treediffs}
543 readrefs
544 changedrefs
545 if {$showneartags} {
546 getallcommits
548 clear_display
549 catch {unset commitinterest}
550 catch {unset cached_commitrow}
551 catch {unset targetid}
552 setcanvscroll
553 getcommits $selid
554 return 0
557 # This makes a string representation of a positive integer which
558 # sorts as a string in numerical order
559 proc strrep {n} {
560 if {$n < 16} {
561 return [format "%x" $n]
562 } elseif {$n < 256} {
563 return [format "x%.2x" $n]
564 } elseif {$n < 65536} {
565 return [format "y%.4x" $n]
567 return [format "z%.8x" $n]
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
573 proc varcinit {view} {
574 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575 global vtokmod varcmod vrowmod varcix vlastins
577 set varcstart($view) {{}}
578 set vupptr($view) {0}
579 set vdownptr($view) {0}
580 set vleftptr($view) {0}
581 set vbackptr($view) {0}
582 set varctok($view) {{}}
583 set varcrow($view) {{}}
584 set vtokmod($view) {}
585 set varcmod($view) 0
586 set vrowmod($view) 0
587 set varcix($view) {{}}
588 set vlastins($view) {0}
591 proc resetvarcs {view} {
592 global varcid varccommits parents children vseedcount ordertok
594 foreach vid [array names varcid $view,*] {
595 unset varcid($vid)
596 unset children($vid)
597 unset parents($vid)
599 # some commits might have children but haven't been seen yet
600 foreach vid [array names children $view,*] {
601 unset children($vid)
603 foreach va [array names varccommits $view,*] {
604 unset varccommits($va)
606 foreach vd [array names vseedcount $view,*] {
607 unset vseedcount($vd)
609 catch {unset ordertok}
612 # returns a list of the commits with no children
613 proc seeds {v} {
614 global vdownptr vleftptr varcstart
616 set ret {}
617 set a [lindex $vdownptr($v) 0]
618 while {$a != 0} {
619 lappend ret [lindex $varcstart($v) $a]
620 set a [lindex $vleftptr($v) $a]
622 return $ret
625 proc newvarc {view id} {
626 global varcid varctok parents children vdatemode
627 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
628 global commitdata commitinfo vseedcount varccommits vlastins
630 set a [llength $varctok($view)]
631 set vid $view,$id
632 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
633 if {![info exists commitinfo($id)]} {
634 parsecommit $id $commitdata($id) 1
636 set cdate [lindex $commitinfo($id) 4]
637 if {![string is integer -strict $cdate]} {
638 set cdate 0
640 if {![info exists vseedcount($view,$cdate)]} {
641 set vseedcount($view,$cdate) -1
643 set c [incr vseedcount($view,$cdate)]
644 set cdate [expr {$cdate ^ 0xffffffff}]
645 set tok "s[strrep $cdate][strrep $c]"
646 } else {
647 set tok {}
649 set ka 0
650 if {[llength $children($vid)] > 0} {
651 set kid [lindex $children($vid) end]
652 set k $varcid($view,$kid)
653 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
654 set ki $kid
655 set ka $k
656 set tok [lindex $varctok($view) $k]
659 if {$ka != 0} {
660 set i [lsearch -exact $parents($view,$ki) $id]
661 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
662 append tok [strrep $j]
664 set c [lindex $vlastins($view) $ka]
665 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
666 set c $ka
667 set b [lindex $vdownptr($view) $ka]
668 } else {
669 set b [lindex $vleftptr($view) $c]
671 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
672 set c $b
673 set b [lindex $vleftptr($view) $c]
675 if {$c == $ka} {
676 lset vdownptr($view) $ka $a
677 lappend vbackptr($view) 0
678 } else {
679 lset vleftptr($view) $c $a
680 lappend vbackptr($view) $c
682 lset vlastins($view) $ka $a
683 lappend vupptr($view) $ka
684 lappend vleftptr($view) $b
685 if {$b != 0} {
686 lset vbackptr($view) $b $a
688 lappend varctok($view) $tok
689 lappend varcstart($view) $id
690 lappend vdownptr($view) 0
691 lappend varcrow($view) {}
692 lappend varcix($view) {}
693 set varccommits($view,$a) {}
694 lappend vlastins($view) 0
695 return $a
698 proc splitvarc {p v} {
699 global varcid varcstart varccommits varctok
700 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
702 set oa $varcid($v,$p)
703 set ac $varccommits($v,$oa)
704 set i [lsearch -exact $varccommits($v,$oa) $p]
705 if {$i <= 0} return
706 set na [llength $varctok($v)]
707 # "%" sorts before "0"...
708 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
709 lappend varctok($v) $tok
710 lappend varcrow($v) {}
711 lappend varcix($v) {}
712 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
713 set varccommits($v,$na) [lrange $ac $i end]
714 lappend varcstart($v) $p
715 foreach id $varccommits($v,$na) {
716 set varcid($v,$id) $na
718 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
719 lappend vlastins($v) [lindex $vlastins($v) $oa]
720 lset vdownptr($v) $oa $na
721 lset vlastins($v) $oa 0
722 lappend vupptr($v) $oa
723 lappend vleftptr($v) 0
724 lappend vbackptr($v) 0
725 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
726 lset vupptr($v) $b $na
730 proc renumbervarc {a v} {
731 global parents children varctok varcstart varccommits
732 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
734 set t1 [clock clicks -milliseconds]
735 set todo {}
736 set isrelated($a) 1
737 set kidchanged($a) 1
738 set ntot 0
739 while {$a != 0} {
740 if {[info exists isrelated($a)]} {
741 lappend todo $a
742 set id [lindex $varccommits($v,$a) end]
743 foreach p $parents($v,$id) {
744 if {[info exists varcid($v,$p)]} {
745 set isrelated($varcid($v,$p)) 1
749 incr ntot
750 set b [lindex $vdownptr($v) $a]
751 if {$b == 0} {
752 while {$a != 0} {
753 set b [lindex $vleftptr($v) $a]
754 if {$b != 0} break
755 set a [lindex $vupptr($v) $a]
758 set a $b
760 foreach a $todo {
761 if {![info exists kidchanged($a)]} continue
762 set id [lindex $varcstart($v) $a]
763 if {[llength $children($v,$id)] > 1} {
764 set children($v,$id) [lsort -command [list vtokcmp $v] \
765 $children($v,$id)]
767 set oldtok [lindex $varctok($v) $a]
768 if {!$vdatemode($v)} {
769 set tok {}
770 } else {
771 set tok $oldtok
773 set ka 0
774 set kid [last_real_child $v,$id]
775 if {$kid ne {}} {
776 set k $varcid($v,$kid)
777 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
778 set ki $kid
779 set ka $k
780 set tok [lindex $varctok($v) $k]
783 if {$ka != 0} {
784 set i [lsearch -exact $parents($v,$ki) $id]
785 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
786 append tok [strrep $j]
788 if {$tok eq $oldtok} {
789 continue
791 set id [lindex $varccommits($v,$a) end]
792 foreach p $parents($v,$id) {
793 if {[info exists varcid($v,$p)]} {
794 set kidchanged($varcid($v,$p)) 1
795 } else {
796 set sortkids($p) 1
799 lset varctok($v) $a $tok
800 set b [lindex $vupptr($v) $a]
801 if {$b != $ka} {
802 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
803 modify_arc $v $ka
805 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
806 modify_arc $v $b
808 set c [lindex $vbackptr($v) $a]
809 set d [lindex $vleftptr($v) $a]
810 if {$c == 0} {
811 lset vdownptr($v) $b $d
812 } else {
813 lset vleftptr($v) $c $d
815 if {$d != 0} {
816 lset vbackptr($v) $d $c
818 if {[lindex $vlastins($v) $b] == $a} {
819 lset vlastins($v) $b $c
821 lset vupptr($v) $a $ka
822 set c [lindex $vlastins($v) $ka]
823 if {$c == 0 || \
824 [string compare $tok [lindex $varctok($v) $c]] < 0} {
825 set c $ka
826 set b [lindex $vdownptr($v) $ka]
827 } else {
828 set b [lindex $vleftptr($v) $c]
830 while {$b != 0 && \
831 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
832 set c $b
833 set b [lindex $vleftptr($v) $c]
835 if {$c == $ka} {
836 lset vdownptr($v) $ka $a
837 lset vbackptr($v) $a 0
838 } else {
839 lset vleftptr($v) $c $a
840 lset vbackptr($v) $a $c
842 lset vleftptr($v) $a $b
843 if {$b != 0} {
844 lset vbackptr($v) $b $a
846 lset vlastins($v) $ka $a
849 foreach id [array names sortkids] {
850 if {[llength $children($v,$id)] > 1} {
851 set children($v,$id) [lsort -command [list vtokcmp $v] \
852 $children($v,$id)]
855 set t2 [clock clicks -milliseconds]
856 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
859 # Fix up the graph after we have found out that in view $v,
860 # $p (a commit that we have already seen) is actually the parent
861 # of the last commit in arc $a.
862 proc fix_reversal {p a v} {
863 global varcid varcstart varctok vupptr
865 set pa $varcid($v,$p)
866 if {$p ne [lindex $varcstart($v) $pa]} {
867 splitvarc $p $v
868 set pa $varcid($v,$p)
870 # seeds always need to be renumbered
871 if {[lindex $vupptr($v) $pa] == 0 ||
872 [string compare [lindex $varctok($v) $a] \
873 [lindex $varctok($v) $pa]] > 0} {
874 renumbervarc $pa $v
878 proc insertrow {id p v} {
879 global cmitlisted children parents varcid varctok vtokmod
880 global varccommits ordertok commitidx numcommits curview
881 global targetid targetrow
883 readcommit $id
884 set vid $v,$id
885 set cmitlisted($vid) 1
886 set children($vid) {}
887 set parents($vid) [list $p]
888 set a [newvarc $v $id]
889 set varcid($vid) $a
890 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
891 modify_arc $v $a
893 lappend varccommits($v,$a) $id
894 set vp $v,$p
895 if {[llength [lappend children($vp) $id]] > 1} {
896 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
897 catch {unset ordertok}
899 fix_reversal $p $a $v
900 incr commitidx($v)
901 if {$v == $curview} {
902 set numcommits $commitidx($v)
903 setcanvscroll
904 if {[info exists targetid]} {
905 if {![comes_before $targetid $p]} {
906 incr targetrow
912 proc insertfakerow {id p} {
913 global varcid varccommits parents children cmitlisted
914 global commitidx varctok vtokmod targetid targetrow curview numcommits
916 set v $curview
917 set a $varcid($v,$p)
918 set i [lsearch -exact $varccommits($v,$a) $p]
919 if {$i < 0} {
920 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
921 return
923 set children($v,$id) {}
924 set parents($v,$id) [list $p]
925 set varcid($v,$id) $a
926 lappend children($v,$p) $id
927 set cmitlisted($v,$id) 1
928 set numcommits [incr commitidx($v)]
929 # note we deliberately don't update varcstart($v) even if $i == 0
930 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
931 modify_arc $v $a $i
932 if {[info exists targetid]} {
933 if {![comes_before $targetid $p]} {
934 incr targetrow
937 setcanvscroll
938 drawvisible
941 proc removefakerow {id} {
942 global varcid varccommits parents children commitidx
943 global varctok vtokmod cmitlisted currentid selectedline
944 global targetid curview numcommits
946 set v $curview
947 if {[llength $parents($v,$id)] != 1} {
948 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
949 return
951 set p [lindex $parents($v,$id) 0]
952 set a $varcid($v,$id)
953 set i [lsearch -exact $varccommits($v,$a) $id]
954 if {$i < 0} {
955 puts "oops: removefakerow can't find [shortids $id] on arc $a"
956 return
958 unset varcid($v,$id)
959 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
960 unset parents($v,$id)
961 unset children($v,$id)
962 unset cmitlisted($v,$id)
963 set numcommits [incr commitidx($v) -1]
964 set j [lsearch -exact $children($v,$p) $id]
965 if {$j >= 0} {
966 set children($v,$p) [lreplace $children($v,$p) $j $j]
968 modify_arc $v $a $i
969 if {[info exist currentid] && $id eq $currentid} {
970 unset currentid
971 set selectedline {}
973 if {[info exists targetid] && $targetid eq $id} {
974 set targetid $p
976 setcanvscroll
977 drawvisible
980 proc first_real_child {vp} {
981 global children nullid nullid2
983 foreach id $children($vp) {
984 if {$id ne $nullid && $id ne $nullid2} {
985 return $id
988 return {}
991 proc last_real_child {vp} {
992 global children nullid nullid2
994 set kids $children($vp)
995 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
996 set id [lindex $kids $i]
997 if {$id ne $nullid && $id ne $nullid2} {
998 return $id
1001 return {}
1004 proc vtokcmp {v a b} {
1005 global varctok varcid
1007 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008 [lindex $varctok($v) $varcid($v,$b)]]
1011 # This assumes that if lim is not given, the caller has checked that
1012 # arc a's token is less than $vtokmod($v)
1013 proc modify_arc {v a {lim {}}} {
1014 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1016 if {$lim ne {}} {
1017 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1018 if {$c > 0} return
1019 if {$c == 0} {
1020 set r [lindex $varcrow($v) $a]
1021 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1024 set vtokmod($v) [lindex $varctok($v) $a]
1025 set varcmod($v) $a
1026 if {$v == $curview} {
1027 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1028 set a [lindex $vupptr($v) $a]
1029 set lim {}
1031 set r 0
1032 if {$a != 0} {
1033 if {$lim eq {}} {
1034 set lim [llength $varccommits($v,$a)]
1036 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1038 set vrowmod($v) $r
1039 undolayout $r
1043 proc update_arcrows {v} {
1044 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1045 global varcid vrownum varcorder varcix varccommits
1046 global vupptr vdownptr vleftptr varctok
1047 global displayorder parentlist curview cached_commitrow
1049 if {$vrowmod($v) == $commitidx($v)} return
1050 if {$v == $curview} {
1051 if {[llength $displayorder] > $vrowmod($v)} {
1052 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1053 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1055 catch {unset cached_commitrow}
1057 set narctot [expr {[llength $varctok($v)] - 1}]
1058 set a $varcmod($v)
1059 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1060 # go up the tree until we find something that has a row number,
1061 # or we get to a seed
1062 set a [lindex $vupptr($v) $a]
1064 if {$a == 0} {
1065 set a [lindex $vdownptr($v) 0]
1066 if {$a == 0} return
1067 set vrownum($v) {0}
1068 set varcorder($v) [list $a]
1069 lset varcix($v) $a 0
1070 lset varcrow($v) $a 0
1071 set arcn 0
1072 set row 0
1073 } else {
1074 set arcn [lindex $varcix($v) $a]
1075 if {[llength $vrownum($v)] > $arcn + 1} {
1076 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1077 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1079 set row [lindex $varcrow($v) $a]
1081 while {1} {
1082 set p $a
1083 incr row [llength $varccommits($v,$a)]
1084 # go down if possible
1085 set b [lindex $vdownptr($v) $a]
1086 if {$b == 0} {
1087 # if not, go left, or go up until we can go left
1088 while {$a != 0} {
1089 set b [lindex $vleftptr($v) $a]
1090 if {$b != 0} break
1091 set a [lindex $vupptr($v) $a]
1093 if {$a == 0} break
1095 set a $b
1096 incr arcn
1097 lappend vrownum($v) $row
1098 lappend varcorder($v) $a
1099 lset varcix($v) $a $arcn
1100 lset varcrow($v) $a $row
1102 set vtokmod($v) [lindex $varctok($v) $p]
1103 set varcmod($v) $p
1104 set vrowmod($v) $row
1105 if {[info exists currentid]} {
1106 set selectedline [rowofcommit $currentid]
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1112 global varcid
1114 return [info exists varcid($v,$id)]
1117 # Return the row number for commit $id in the current view
1118 proc rowofcommit {id} {
1119 global varcid varccommits varcrow curview cached_commitrow
1120 global varctok vtokmod
1122 set v $curview
1123 if {![info exists varcid($v,$id)]} {
1124 puts "oops rowofcommit no arc for [shortids $id]"
1125 return {}
1127 set a $varcid($v,$id)
1128 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1129 update_arcrows $v
1131 if {[info exists cached_commitrow($id)]} {
1132 return $cached_commitrow($id)
1134 set i [lsearch -exact $varccommits($v,$a) $id]
1135 if {$i < 0} {
1136 puts "oops didn't find commit [shortids $id] in arc $a"
1137 return {}
1139 incr i [lindex $varcrow($v) $a]
1140 set cached_commitrow($id) $i
1141 return $i
1144 # Returns 1 if a is on an earlier row than b, otherwise 0
1145 proc comes_before {a b} {
1146 global varcid varctok curview
1148 set v $curview
1149 if {$a eq $b || ![info exists varcid($v,$a)] || \
1150 ![info exists varcid($v,$b)]} {
1151 return 0
1153 if {$varcid($v,$a) != $varcid($v,$b)} {
1154 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1155 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1157 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1160 proc bsearch {l elt} {
1161 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1162 return 0
1164 set lo 0
1165 set hi [llength $l]
1166 while {$hi - $lo > 1} {
1167 set mid [expr {int(($lo + $hi) / 2)}]
1168 set t [lindex $l $mid]
1169 if {$elt < $t} {
1170 set hi $mid
1171 } elseif {$elt > $t} {
1172 set lo $mid
1173 } else {
1174 return $mid
1177 return $lo
1180 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1181 proc make_disporder {start end} {
1182 global vrownum curview commitidx displayorder parentlist
1183 global varccommits varcorder parents vrowmod varcrow
1184 global d_valid_start d_valid_end
1186 if {$end > $vrowmod($curview)} {
1187 update_arcrows $curview
1189 set ai [bsearch $vrownum($curview) $start]
1190 set start [lindex $vrownum($curview) $ai]
1191 set narc [llength $vrownum($curview)]
1192 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1193 set a [lindex $varcorder($curview) $ai]
1194 set l [llength $displayorder]
1195 set al [llength $varccommits($curview,$a)]
1196 if {$l < $r + $al} {
1197 if {$l < $r} {
1198 set pad [ntimes [expr {$r - $l}] {}]
1199 set displayorder [concat $displayorder $pad]
1200 set parentlist [concat $parentlist $pad]
1201 } elseif {$l > $r} {
1202 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1203 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1205 foreach id $varccommits($curview,$a) {
1206 lappend displayorder $id
1207 lappend parentlist $parents($curview,$id)
1209 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1210 set i $r
1211 foreach id $varccommits($curview,$a) {
1212 lset displayorder $i $id
1213 lset parentlist $i $parents($curview,$id)
1214 incr i
1217 incr r $al
1221 proc commitonrow {row} {
1222 global displayorder
1224 set id [lindex $displayorder $row]
1225 if {$id eq {}} {
1226 make_disporder $row [expr {$row + 1}]
1227 set id [lindex $displayorder $row]
1229 return $id
1232 proc closevarcs {v} {
1233 global varctok varccommits varcid parents children
1234 global cmitlisted commitidx commitinterest vtokmod
1236 set missing_parents 0
1237 set scripts {}
1238 set narcs [llength $varctok($v)]
1239 for {set a 1} {$a < $narcs} {incr a} {
1240 set id [lindex $varccommits($v,$a) end]
1241 foreach p $parents($v,$id) {
1242 if {[info exists varcid($v,$p)]} continue
1243 # add p as a new commit
1244 incr missing_parents
1245 set cmitlisted($v,$p) 0
1246 set parents($v,$p) {}
1247 if {[llength $children($v,$p)] == 1 &&
1248 [llength $parents($v,$id)] == 1} {
1249 set b $a
1250 } else {
1251 set b [newvarc $v $p]
1253 set varcid($v,$p) $b
1254 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1255 modify_arc $v $b
1257 lappend varccommits($v,$b) $p
1258 incr commitidx($v)
1259 if {[info exists commitinterest($p)]} {
1260 foreach script $commitinterest($p) {
1261 lappend scripts [string map [list "%I" $p] $script]
1263 unset commitinterest($id)
1267 if {$missing_parents > 0} {
1268 foreach s $scripts {
1269 eval $s
1274 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1275 # Assumes we already have an arc for $rwid.
1276 proc rewrite_commit {v id rwid} {
1277 global children parents varcid varctok vtokmod varccommits
1279 foreach ch $children($v,$id) {
1280 # make $rwid be $ch's parent in place of $id
1281 set i [lsearch -exact $parents($v,$ch) $id]
1282 if {$i < 0} {
1283 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1285 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1286 # add $ch to $rwid's children and sort the list if necessary
1287 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1288 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1289 $children($v,$rwid)]
1291 # fix the graph after joining $id to $rwid
1292 set a $varcid($v,$ch)
1293 fix_reversal $rwid $a $v
1294 # parentlist is wrong for the last element of arc $a
1295 # even if displayorder is right, hence the 3rd arg here
1296 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1300 proc getcommitlines {fd inst view updating} {
1301 global cmitlisted commitinterest leftover
1302 global commitidx commitdata vdatemode
1303 global parents children curview hlview
1304 global idpending ordertok
1305 global varccommits varcid varctok vtokmod vfilelimit
1307 set stuff [read $fd 500000]
1308 # git log doesn't terminate the last commit with a null...
1309 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1310 set stuff "\0"
1312 if {$stuff == {}} {
1313 if {![eof $fd]} {
1314 return 1
1316 global commfd viewcomplete viewactive viewname
1317 global viewinstances
1318 unset commfd($inst)
1319 set i [lsearch -exact $viewinstances($view) $inst]
1320 if {$i >= 0} {
1321 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1323 # set it blocking so we wait for the process to terminate
1324 fconfigure $fd -blocking 1
1325 if {[catch {close $fd} err]} {
1326 set fv {}
1327 if {$view != $curview} {
1328 set fv " for the \"$viewname($view)\" view"
1330 if {[string range $err 0 4] == "usage"} {
1331 set err "Gitk: error reading commits$fv:\
1332 bad arguments to git log."
1333 if {$viewname($view) eq "Command line"} {
1334 append err \
1335 " (Note: arguments to gitk are passed to git log\
1336 to allow selection of commits to be displayed.)"
1338 } else {
1339 set err "Error reading commits$fv: $err"
1341 error_popup $err
1343 if {[incr viewactive($view) -1] <= 0} {
1344 set viewcomplete($view) 1
1345 # Check if we have seen any ids listed as parents that haven't
1346 # appeared in the list
1347 closevarcs $view
1348 notbusy $view
1350 if {$view == $curview} {
1351 run chewcommits
1353 return 0
1355 set start 0
1356 set gotsome 0
1357 set scripts {}
1358 while 1 {
1359 set i [string first "\0" $stuff $start]
1360 if {$i < 0} {
1361 append leftover($inst) [string range $stuff $start end]
1362 break
1364 if {$start == 0} {
1365 set cmit $leftover($inst)
1366 append cmit [string range $stuff 0 [expr {$i - 1}]]
1367 set leftover($inst) {}
1368 } else {
1369 set cmit [string range $stuff $start [expr {$i - 1}]]
1371 set start [expr {$i + 1}]
1372 set j [string first "\n" $cmit]
1373 set ok 0
1374 set listed 1
1375 if {$j >= 0 && [string match "commit *" $cmit]} {
1376 set ids [string range $cmit 7 [expr {$j - 1}]]
1377 if {[string match {[-^<>]*} $ids]} {
1378 switch -- [string index $ids 0] {
1379 "-" {set listed 0}
1380 "^" {set listed 2}
1381 "<" {set listed 3}
1382 ">" {set listed 4}
1384 set ids [string range $ids 1 end]
1386 set ok 1
1387 foreach id $ids {
1388 if {[string length $id] != 40} {
1389 set ok 0
1390 break
1394 if {!$ok} {
1395 set shortcmit $cmit
1396 if {[string length $shortcmit] > 80} {
1397 set shortcmit "[string range $shortcmit 0 80]..."
1399 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1400 exit 1
1402 set id [lindex $ids 0]
1403 set vid $view,$id
1405 if {!$listed && $updating && ![info exists varcid($vid)] &&
1406 $vfilelimit($view) ne {}} {
1407 # git log doesn't rewrite parents for unlisted commits
1408 # when doing path limiting, so work around that here
1409 # by working out the rewritten parent with git rev-list
1410 # and if we already know about it, using the rewritten
1411 # parent as a substitute parent for $id's children.
1412 if {![catch {
1413 set rwid [exec git rev-list --first-parent --max-count=1 \
1414 $id -- $vfilelimit($view)]
1415 }]} {
1416 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1417 # use $rwid in place of $id
1418 rewrite_commit $view $id $rwid
1419 continue
1424 set a 0
1425 if {[info exists varcid($vid)]} {
1426 if {$cmitlisted($vid) || !$listed} continue
1427 set a $varcid($vid)
1429 if {$listed} {
1430 set olds [lrange $ids 1 end]
1431 } else {
1432 set olds {}
1434 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1435 set cmitlisted($vid) $listed
1436 set parents($vid) $olds
1437 if {![info exists children($vid)]} {
1438 set children($vid) {}
1439 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1440 set k [lindex $children($vid) 0]
1441 if {[llength $parents($view,$k)] == 1 &&
1442 (!$vdatemode($view) ||
1443 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1444 set a $varcid($view,$k)
1447 if {$a == 0} {
1448 # new arc
1449 set a [newvarc $view $id]
1451 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1452 modify_arc $view $a
1454 if {![info exists varcid($vid)]} {
1455 set varcid($vid) $a
1456 lappend varccommits($view,$a) $id
1457 incr commitidx($view)
1460 set i 0
1461 foreach p $olds {
1462 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1463 set vp $view,$p
1464 if {[llength [lappend children($vp) $id]] > 1 &&
1465 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1466 set children($vp) [lsort -command [list vtokcmp $view] \
1467 $children($vp)]
1468 catch {unset ordertok}
1470 if {[info exists varcid($view,$p)]} {
1471 fix_reversal $p $a $view
1474 incr i
1477 if {[info exists commitinterest($id)]} {
1478 foreach script $commitinterest($id) {
1479 lappend scripts [string map [list "%I" $id] $script]
1481 unset commitinterest($id)
1483 set gotsome 1
1485 if {$gotsome} {
1486 global numcommits hlview
1488 if {$view == $curview} {
1489 set numcommits $commitidx($view)
1490 run chewcommits
1492 if {[info exists hlview] && $view == $hlview} {
1493 # we never actually get here...
1494 run vhighlightmore
1496 foreach s $scripts {
1497 eval $s
1500 return 2
1503 proc chewcommits {} {
1504 global curview hlview viewcomplete
1505 global pending_select
1507 layoutmore
1508 if {$viewcomplete($curview)} {
1509 global commitidx varctok
1510 global numcommits startmsecs
1512 if {[info exists pending_select]} {
1513 update
1514 reset_pending_select {}
1516 if {[commitinview $pending_select $curview]} {
1517 selectline [rowofcommit $pending_select] 1
1518 } else {
1519 set row [first_real_row]
1520 selectline $row 1
1523 if {$commitidx($curview) > 0} {
1524 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1525 #puts "overall $ms ms for $numcommits commits"
1526 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1527 } else {
1528 show_status [mc "No commits selected"]
1530 notbusy layout
1532 return 0
1535 proc readcommit {id} {
1536 if {[catch {set contents [exec git cat-file commit $id]}]} return
1537 parsecommit $id $contents 0
1540 proc parsecommit {id contents listed} {
1541 global commitinfo cdate
1543 set inhdr 1
1544 set comment {}
1545 set headline {}
1546 set auname {}
1547 set audate {}
1548 set comname {}
1549 set comdate {}
1550 set hdrend [string first "\n\n" $contents]
1551 if {$hdrend < 0} {
1552 # should never happen...
1553 set hdrend [string length $contents]
1555 set header [string range $contents 0 [expr {$hdrend - 1}]]
1556 set comment [string range $contents [expr {$hdrend + 2}] end]
1557 foreach line [split $header "\n"] {
1558 set tag [lindex $line 0]
1559 if {$tag == "author"} {
1560 set audate [lindex $line end-1]
1561 set auname [lrange $line 1 end-2]
1562 } elseif {$tag == "committer"} {
1563 set comdate [lindex $line end-1]
1564 set comname [lrange $line 1 end-2]
1567 set headline {}
1568 # take the first non-blank line of the comment as the headline
1569 set headline [string trimleft $comment]
1570 set i [string first "\n" $headline]
1571 if {$i >= 0} {
1572 set headline [string range $headline 0 $i]
1574 set headline [string trimright $headline]
1575 set i [string first "\r" $headline]
1576 if {$i >= 0} {
1577 set headline [string trimright [string range $headline 0 $i]]
1579 if {!$listed} {
1580 # git log indents the comment by 4 spaces;
1581 # if we got this via git cat-file, add the indentation
1582 set newcomment {}
1583 foreach line [split $comment "\n"] {
1584 append newcomment " "
1585 append newcomment $line
1586 append newcomment "\n"
1588 set comment $newcomment
1590 if {$comdate != {}} {
1591 set cdate($id) $comdate
1593 set commitinfo($id) [list $headline $auname $audate \
1594 $comname $comdate $comment]
1597 proc getcommit {id} {
1598 global commitdata commitinfo
1600 if {[info exists commitdata($id)]} {
1601 parsecommit $id $commitdata($id) 1
1602 } else {
1603 readcommit $id
1604 if {![info exists commitinfo($id)]} {
1605 set commitinfo($id) [list [mc "No commit information available"]]
1608 return 1
1611 proc readrefs {} {
1612 global tagids idtags headids idheads tagobjid
1613 global otherrefids idotherrefs mainhead mainheadid
1614 global selecthead selectheadid
1616 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1617 catch {unset $v}
1619 set refd [open [list | git show-ref -d] r]
1620 while {[gets $refd line] >= 0} {
1621 if {[string index $line 40] ne " "} continue
1622 set id [string range $line 0 39]
1623 set ref [string range $line 41 end]
1624 if {![string match "refs/*" $ref]} continue
1625 set name [string range $ref 5 end]
1626 if {[string match "remotes/*" $name]} {
1627 if {![string match "*/HEAD" $name]} {
1628 set headids($name) $id
1629 lappend idheads($id) $name
1631 } elseif {[string match "heads/*" $name]} {
1632 set name [string range $name 6 end]
1633 set headids($name) $id
1634 lappend idheads($id) $name
1635 } elseif {[string match "tags/*" $name]} {
1636 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1637 # which is what we want since the former is the commit ID
1638 set name [string range $name 5 end]
1639 if {[string match "*^{}" $name]} {
1640 set name [string range $name 0 end-3]
1641 } else {
1642 set tagobjid($name) $id
1644 set tagids($name) $id
1645 lappend idtags($id) $name
1646 } else {
1647 set otherrefids($name) $id
1648 lappend idotherrefs($id) $name
1651 catch {close $refd}
1652 set mainhead {}
1653 set mainheadid {}
1654 catch {
1655 set mainheadid [exec git rev-parse HEAD]
1656 set thehead [exec git symbolic-ref HEAD]
1657 if {[string match "refs/heads/*" $thehead]} {
1658 set mainhead [string range $thehead 11 end]
1661 set selectheadid {}
1662 if {$selecthead ne {}} {
1663 catch {
1664 set selectheadid [exec git rev-parse --verify $selecthead]
1669 # skip over fake commits
1670 proc first_real_row {} {
1671 global nullid nullid2 numcommits
1673 for {set row 0} {$row < $numcommits} {incr row} {
1674 set id [commitonrow $row]
1675 if {$id ne $nullid && $id ne $nullid2} {
1676 break
1679 return $row
1682 # update things for a head moved to a child of its previous location
1683 proc movehead {id name} {
1684 global headids idheads
1686 removehead $headids($name) $name
1687 set headids($name) $id
1688 lappend idheads($id) $name
1691 # update things when a head has been removed
1692 proc removehead {id name} {
1693 global headids idheads
1695 if {$idheads($id) eq $name} {
1696 unset idheads($id)
1697 } else {
1698 set i [lsearch -exact $idheads($id) $name]
1699 if {$i >= 0} {
1700 set idheads($id) [lreplace $idheads($id) $i $i]
1703 unset headids($name)
1706 proc show_error {w top msg} {
1707 message $w.m -text $msg -justify center -aspect 400
1708 pack $w.m -side top -fill x -padx 20 -pady 20
1709 button $w.ok -text [mc OK] -command "destroy $top"
1710 pack $w.ok -side bottom -fill x
1711 bind $top <Visibility> "grab $top; focus $top"
1712 bind $top <Key-Return> "destroy $top"
1713 tkwait window $top
1716 proc error_popup msg {
1717 set w .error
1718 toplevel $w
1719 wm transient $w .
1720 show_error $w $w $msg
1723 proc confirm_popup msg {
1724 global confirm_ok
1725 set confirm_ok 0
1726 set w .confirm
1727 toplevel $w
1728 wm transient $w .
1729 message $w.m -text $msg -justify center -aspect 400
1730 pack $w.m -side top -fill x -padx 20 -pady 20
1731 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1732 pack $w.ok -side left -fill x
1733 button $w.cancel -text [mc Cancel] -command "destroy $w"
1734 pack $w.cancel -side right -fill x
1735 bind $w <Visibility> "grab $w; focus $w"
1736 tkwait window $w
1737 return $confirm_ok
1740 proc setoptions {} {
1741 option add *Panedwindow.showHandle 1 startupFile
1742 option add *Panedwindow.sashRelief raised startupFile
1743 option add *Button.font uifont startupFile
1744 option add *Checkbutton.font uifont startupFile
1745 option add *Radiobutton.font uifont startupFile
1746 option add *Menu.font uifont startupFile
1747 option add *Menubutton.font uifont startupFile
1748 option add *Label.font uifont startupFile
1749 option add *Message.font uifont startupFile
1750 option add *Entry.font uifont startupFile
1753 proc makewindow {} {
1754 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1755 global tabstop
1756 global findtype findtypemenu findloc findstring fstring geometry
1757 global entries sha1entry sha1string sha1but
1758 global diffcontextstring diffcontext
1759 global ignorespace
1760 global maincursor textcursor curtextcursor
1761 global rowctxmenu fakerowmenu mergemax wrapcomment
1762 global highlight_files gdttype
1763 global searchstring sstring
1764 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1765 global headctxmenu progresscanv progressitem progresscoords statusw
1766 global fprogitem fprogcoord lastprogupdate progupdatepending
1767 global rprogitem rprogcoord rownumsel numcommits
1768 global have_tk85
1770 menu .bar
1771 .bar add cascade -label [mc "File"] -menu .bar.file
1772 menu .bar.file
1773 .bar.file add command -label [mc "Update"] -command updatecommits
1774 .bar.file add command -label [mc "Reload"] -command reloadcommits
1775 .bar.file add command -label [mc "Reread references"] -command rereadrefs
1776 .bar.file add command -label [mc "List references"] -command showrefs
1777 .bar.file add command -label [mc "Quit"] -command doquit
1778 menu .bar.edit
1779 .bar add cascade -label [mc "Edit"] -menu .bar.edit
1780 .bar.edit add command -label [mc "Preferences"] -command doprefs
1782 menu .bar.view
1783 .bar add cascade -label [mc "View"] -menu .bar.view
1784 .bar.view add command -label [mc "New view..."] -command {newview 0}
1785 .bar.view add command -label [mc "Edit view..."] -command editview \
1786 -state disabled
1787 .bar.view add command -label [mc "Delete view"] -command delview -state disabled
1788 .bar.view add separator
1789 .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
1790 -variable selectedview -value 0
1792 menu .bar.help
1793 .bar add cascade -label [mc "Help"] -menu .bar.help
1794 .bar.help add command -label [mc "About gitk"] -command about
1795 .bar.help add command -label [mc "Key bindings"] -command keys
1796 .bar.help configure
1797 . configure -menu .bar
1799 # the gui has upper and lower half, parts of a paned window.
1800 panedwindow .ctop -orient vertical
1802 # possibly use assumed geometry
1803 if {![info exists geometry(pwsash0)]} {
1804 set geometry(topheight) [expr {15 * $linespc}]
1805 set geometry(topwidth) [expr {80 * $charspc}]
1806 set geometry(botheight) [expr {15 * $linespc}]
1807 set geometry(botwidth) [expr {50 * $charspc}]
1808 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1809 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1812 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1813 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1814 frame .tf.histframe
1815 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1817 # create three canvases
1818 set cscroll .tf.histframe.csb
1819 set canv .tf.histframe.pwclist.canv
1820 canvas $canv \
1821 -selectbackground $selectbgcolor \
1822 -background $bgcolor -bd 0 \
1823 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1824 .tf.histframe.pwclist add $canv
1825 set canv2 .tf.histframe.pwclist.canv2
1826 canvas $canv2 \
1827 -selectbackground $selectbgcolor \
1828 -background $bgcolor -bd 0 -yscrollincr $linespc
1829 .tf.histframe.pwclist add $canv2
1830 set canv3 .tf.histframe.pwclist.canv3
1831 canvas $canv3 \
1832 -selectbackground $selectbgcolor \
1833 -background $bgcolor -bd 0 -yscrollincr $linespc
1834 .tf.histframe.pwclist add $canv3
1835 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1836 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1838 # a scroll bar to rule them
1839 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1840 pack $cscroll -side right -fill y
1841 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1842 lappend bglist $canv $canv2 $canv3
1843 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1845 # we have two button bars at bottom of top frame. Bar 1
1846 frame .tf.bar
1847 frame .tf.lbar -height 15
1849 set sha1entry .tf.bar.sha1
1850 set entries $sha1entry
1851 set sha1but .tf.bar.sha1label
1852 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1853 -command gotocommit -width 8
1854 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1855 pack .tf.bar.sha1label -side left
1856 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1857 trace add variable sha1string write sha1change
1858 pack $sha1entry -side left -pady 2
1860 image create bitmap bm-left -data {
1861 #define left_width 16
1862 #define left_height 16
1863 static unsigned char left_bits[] = {
1864 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1865 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1866 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1868 image create bitmap bm-right -data {
1869 #define right_width 16
1870 #define right_height 16
1871 static unsigned char right_bits[] = {
1872 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1873 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1874 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1876 button .tf.bar.leftbut -image bm-left -command goback \
1877 -state disabled -width 26
1878 pack .tf.bar.leftbut -side left -fill y
1879 button .tf.bar.rightbut -image bm-right -command goforw \
1880 -state disabled -width 26
1881 pack .tf.bar.rightbut -side left -fill y
1883 label .tf.bar.rowlabel -text [mc "Row"]
1884 set rownumsel {}
1885 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1886 -relief sunken -anchor e
1887 label .tf.bar.rowlabel2 -text "/"
1888 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1889 -relief sunken -anchor e
1890 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1891 -side left
1892 global selectedline
1893 trace add variable selectedline write selectedline_change
1895 # Status label and progress bar
1896 set statusw .tf.bar.status
1897 label $statusw -width 15 -relief sunken
1898 pack $statusw -side left -padx 5
1899 set h [expr {[font metrics uifont -linespace] + 2}]
1900 set progresscanv .tf.bar.progress
1901 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1902 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1903 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1904 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1905 pack $progresscanv -side right -expand 1 -fill x
1906 set progresscoords {0 0}
1907 set fprogcoord 0
1908 set rprogcoord 0
1909 bind $progresscanv <Configure> adjustprogress
1910 set lastprogupdate [clock clicks -milliseconds]
1911 set progupdatepending 0
1913 # build up the bottom bar of upper window
1914 label .tf.lbar.flabel -text "[mc "Find"] "
1915 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1916 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1917 label .tf.lbar.flab2 -text " [mc "commit"] "
1918 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1919 -side left -fill y
1920 set gdttype [mc "containing:"]
1921 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1922 [mc "containing:"] \
1923 [mc "touching paths:"] \
1924 [mc "adding/removing string:"]]
1925 trace add variable gdttype write gdttype_change
1926 pack .tf.lbar.gdttype -side left -fill y
1928 set findstring {}
1929 set fstring .tf.lbar.findstring
1930 lappend entries $fstring
1931 entry $fstring -width 30 -font textfont -textvariable findstring
1932 trace add variable findstring write find_change
1933 set findtype [mc "Exact"]
1934 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1935 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
1936 trace add variable findtype write findcom_change
1937 set findloc [mc "All fields"]
1938 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
1939 [mc "Comments"] [mc "Author"] [mc "Committer"]
1940 trace add variable findloc write find_change
1941 pack .tf.lbar.findloc -side right
1942 pack .tf.lbar.findtype -side right
1943 pack $fstring -side left -expand 1 -fill x
1945 # Finish putting the upper half of the viewer together
1946 pack .tf.lbar -in .tf -side bottom -fill x
1947 pack .tf.bar -in .tf -side bottom -fill x
1948 pack .tf.histframe -fill both -side top -expand 1
1949 .ctop add .tf
1950 .ctop paneconfigure .tf -height $geometry(topheight)
1951 .ctop paneconfigure .tf -width $geometry(topwidth)
1953 # now build up the bottom
1954 panedwindow .pwbottom -orient horizontal
1956 # lower left, a text box over search bar, scroll bar to the right
1957 # if we know window height, then that will set the lower text height, otherwise
1958 # we set lower text height which will drive window height
1959 if {[info exists geometry(main)]} {
1960 frame .bleft -width $geometry(botwidth)
1961 } else {
1962 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1964 frame .bleft.top
1965 frame .bleft.mid
1966 frame .bleft.bottom
1968 button .bleft.top.search -text [mc "Search"] -command dosearch
1969 pack .bleft.top.search -side left -padx 5
1970 set sstring .bleft.top.sstring
1971 entry $sstring -width 20 -font textfont -textvariable searchstring
1972 lappend entries $sstring
1973 trace add variable searchstring write incrsearch
1974 pack $sstring -side left -expand 1 -fill x
1975 radiobutton .bleft.mid.diff -text [mc "Diff"] \
1976 -command changediffdisp -variable diffelide -value {0 0}
1977 radiobutton .bleft.mid.old -text [mc "Old version"] \
1978 -command changediffdisp -variable diffelide -value {0 1}
1979 radiobutton .bleft.mid.new -text [mc "New version"] \
1980 -command changediffdisp -variable diffelide -value {1 0}
1981 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
1982 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1983 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1984 -from 1 -increment 1 -to 10000000 \
1985 -validate all -validatecommand "diffcontextvalidate %P" \
1986 -textvariable diffcontextstring
1987 .bleft.mid.diffcontext set $diffcontext
1988 trace add variable diffcontextstring write diffcontextchange
1989 lappend entries .bleft.mid.diffcontext
1990 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1991 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
1992 -command changeignorespace -variable ignorespace
1993 pack .bleft.mid.ignspace -side left -padx 5
1994 set ctext .bleft.bottom.ctext
1995 text $ctext -background $bgcolor -foreground $fgcolor \
1996 -state disabled -font textfont \
1997 -yscrollcommand scrolltext -wrap none \
1998 -xscrollcommand ".bleft.bottom.sbhorizontal set"
1999 if {$have_tk85} {
2000 $ctext conf -tabstyle wordprocessor
2002 scrollbar .bleft.bottom.sb -command "$ctext yview"
2003 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2004 -width 10
2005 pack .bleft.top -side top -fill x
2006 pack .bleft.mid -side top -fill x
2007 grid $ctext .bleft.bottom.sb -sticky nsew
2008 grid .bleft.bottom.sbhorizontal -sticky ew
2009 grid columnconfigure .bleft.bottom 0 -weight 1
2010 grid rowconfigure .bleft.bottom 0 -weight 1
2011 grid rowconfigure .bleft.bottom 1 -weight 0
2012 pack .bleft.bottom -side top -fill both -expand 1
2013 lappend bglist $ctext
2014 lappend fglist $ctext
2016 $ctext tag conf comment -wrap $wrapcomment
2017 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2018 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2019 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2020 $ctext tag conf d1 -fore [lindex $diffcolors 1]
2021 $ctext tag conf m0 -fore red
2022 $ctext tag conf m1 -fore blue
2023 $ctext tag conf m2 -fore green
2024 $ctext tag conf m3 -fore purple
2025 $ctext tag conf m4 -fore brown
2026 $ctext tag conf m5 -fore "#009090"
2027 $ctext tag conf m6 -fore magenta
2028 $ctext tag conf m7 -fore "#808000"
2029 $ctext tag conf m8 -fore "#009000"
2030 $ctext tag conf m9 -fore "#ff0080"
2031 $ctext tag conf m10 -fore cyan
2032 $ctext tag conf m11 -fore "#b07070"
2033 $ctext tag conf m12 -fore "#70b0f0"
2034 $ctext tag conf m13 -fore "#70f0b0"
2035 $ctext tag conf m14 -fore "#f0b070"
2036 $ctext tag conf m15 -fore "#ff70b0"
2037 $ctext tag conf mmax -fore darkgrey
2038 set mergemax 16
2039 $ctext tag conf mresult -font textfontbold
2040 $ctext tag conf msep -font textfontbold
2041 $ctext tag conf found -back yellow
2043 .pwbottom add .bleft
2044 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2046 # lower right
2047 frame .bright
2048 frame .bright.mode
2049 radiobutton .bright.mode.patch -text [mc "Patch"] \
2050 -command reselectline -variable cmitmode -value "patch"
2051 radiobutton .bright.mode.tree -text [mc "Tree"] \
2052 -command reselectline -variable cmitmode -value "tree"
2053 grid .bright.mode.patch .bright.mode.tree -sticky ew
2054 pack .bright.mode -side top -fill x
2055 set cflist .bright.cfiles
2056 set indent [font measure mainfont "nn"]
2057 text $cflist \
2058 -selectbackground $selectbgcolor \
2059 -background $bgcolor -foreground $fgcolor \
2060 -font mainfont \
2061 -tabs [list $indent [expr {2 * $indent}]] \
2062 -yscrollcommand ".bright.sb set" \
2063 -cursor [. cget -cursor] \
2064 -spacing1 1 -spacing3 1
2065 lappend bglist $cflist
2066 lappend fglist $cflist
2067 scrollbar .bright.sb -command "$cflist yview"
2068 pack .bright.sb -side right -fill y
2069 pack $cflist -side left -fill both -expand 1
2070 $cflist tag configure highlight \
2071 -background [$cflist cget -selectbackground]
2072 $cflist tag configure bold -font mainfontbold
2074 .pwbottom add .bright
2075 .ctop add .pwbottom
2077 # restore window width & height if known
2078 if {[info exists geometry(main)]} {
2079 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2080 if {$w > [winfo screenwidth .]} {
2081 set w [winfo screenwidth .]
2083 if {$h > [winfo screenheight .]} {
2084 set h [winfo screenheight .]
2086 wm geometry . "${w}x$h"
2090 if {[tk windowingsystem] eq {aqua}} {
2091 set M1B M1
2092 } else {
2093 set M1B Control
2096 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2097 pack .ctop -fill both -expand 1
2098 bindall <1> {selcanvline %W %x %y}
2099 #bindall <B1-Motion> {selcanvline %W %x %y}
2100 if {[tk windowingsystem] == "win32"} {
2101 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2102 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2103 } else {
2104 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2105 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2106 if {[tk windowingsystem] eq "aqua"} {
2107 bindall <MouseWheel> {
2108 set delta [expr {- (%D)}]
2109 allcanvs yview scroll $delta units
2113 bindall <2> "canvscan mark %W %x %y"
2114 bindall <B2-Motion> "canvscan dragto %W %x %y"
2115 bindkey <Home> selfirstline
2116 bindkey <End> sellastline
2117 bind . <Key-Up> "selnextline -1"
2118 bind . <Key-Down> "selnextline 1"
2119 bind . <Shift-Key-Up> "dofind -1 0"
2120 bind . <Shift-Key-Down> "dofind 1 0"
2121 bindkey <Key-Right> "goforw"
2122 bindkey <Key-Left> "goback"
2123 bind . <Key-Prior> "selnextpage -1"
2124 bind . <Key-Next> "selnextpage 1"
2125 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2126 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2127 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2128 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2129 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2130 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2131 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2132 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2133 bindkey <Key-space> "$ctext yview scroll 1 pages"
2134 bindkey p "selnextline -1"
2135 bindkey n "selnextline 1"
2136 bindkey z "goback"
2137 bindkey x "goforw"
2138 bindkey i "selnextline -1"
2139 bindkey k "selnextline 1"
2140 bindkey j "goback"
2141 bindkey l "goforw"
2142 bindkey b prevfile
2143 bindkey d "$ctext yview scroll 18 units"
2144 bindkey u "$ctext yview scroll -18 units"
2145 bindkey / {dofind 1 1}
2146 bindkey <Key-Return> {dofind 1 1}
2147 bindkey ? {dofind -1 1}
2148 bindkey f nextfile
2149 bindkey <F5> updatecommits
2150 bind . <$M1B-q> doquit
2151 bind . <$M1B-f> {dofind 1 1}
2152 bind . <$M1B-g> {dofind 1 0}
2153 bind . <$M1B-r> dosearchback
2154 bind . <$M1B-s> dosearch
2155 bind . <$M1B-equal> {incrfont 1}
2156 bind . <$M1B-plus> {incrfont 1}
2157 bind . <$M1B-KP_Add> {incrfont 1}
2158 bind . <$M1B-minus> {incrfont -1}
2159 bind . <$M1B-KP_Subtract> {incrfont -1}
2160 wm protocol . WM_DELETE_WINDOW doquit
2161 bind . <Destroy> {stop_backends}
2162 bind . <Button-1> "click %W"
2163 bind $fstring <Key-Return> {dofind 1 1}
2164 bind $sha1entry <Key-Return> gotocommit
2165 bind $sha1entry <<PasteSelection>> clearsha1
2166 bind $cflist <1> {sel_flist %W %x %y; break}
2167 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2168 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2169 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
2171 set maincursor [. cget -cursor]
2172 set textcursor [$ctext cget -cursor]
2173 set curtextcursor $textcursor
2175 set rowctxmenu .rowctxmenu
2176 menu $rowctxmenu -tearoff 0
2177 $rowctxmenu add command -label [mc "Diff this -> selected"] \
2178 -command {diffvssel 0}
2179 $rowctxmenu add command -label [mc "Diff selected -> this"] \
2180 -command {diffvssel 1}
2181 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
2182 $rowctxmenu add command -label [mc "Create tag"] -command mktag
2183 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
2184 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
2185 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
2186 -command cherrypick
2187 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
2188 -command resethead
2190 set fakerowmenu .fakerowmenu
2191 menu $fakerowmenu -tearoff 0
2192 $fakerowmenu add command -label [mc "Diff this -> selected"] \
2193 -command {diffvssel 0}
2194 $fakerowmenu add command -label [mc "Diff selected -> this"] \
2195 -command {diffvssel 1}
2196 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
2197 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
2198 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
2199 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
2201 set headctxmenu .headctxmenu
2202 menu $headctxmenu -tearoff 0
2203 $headctxmenu add command -label [mc "Check out this branch"] \
2204 -command cobranch
2205 $headctxmenu add command -label [mc "Remove this branch"] \
2206 -command rmbranch
2208 global flist_menu
2209 set flist_menu .flistctxmenu
2210 menu $flist_menu -tearoff 0
2211 $flist_menu add command -label [mc "Highlight this too"] \
2212 -command {flist_hl 0}
2213 $flist_menu add command -label [mc "Highlight this only"] \
2214 -command {flist_hl 1}
2215 $flist_menu add command -label [mc "External diff"] \
2216 -command {external_diff}
2219 # Windows sends all mouse wheel events to the current focused window, not
2220 # the one where the mouse hovers, so bind those events here and redirect
2221 # to the correct window
2222 proc windows_mousewheel_redirector {W X Y D} {
2223 global canv canv2 canv3
2224 set w [winfo containing -displayof $W $X $Y]
2225 if {$w ne ""} {
2226 set u [expr {$D < 0 ? 5 : -5}]
2227 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2228 allcanvs yview scroll $u units
2229 } else {
2230 catch {
2231 $w yview scroll $u units
2237 # Update row number label when selectedline changes
2238 proc selectedline_change {n1 n2 op} {
2239 global selectedline rownumsel
2241 if {$selectedline eq {}} {
2242 set rownumsel {}
2243 } else {
2244 set rownumsel [expr {$selectedline + 1}]
2248 # mouse-2 makes all windows scan vertically, but only the one
2249 # the cursor is in scans horizontally
2250 proc canvscan {op w x y} {
2251 global canv canv2 canv3
2252 foreach c [list $canv $canv2 $canv3] {
2253 if {$c == $w} {
2254 $c scan $op $x $y
2255 } else {
2256 $c scan $op 0 $y
2261 proc scrollcanv {cscroll f0 f1} {
2262 $cscroll set $f0 $f1
2263 drawvisible
2264 flushhighlights
2267 # when we make a key binding for the toplevel, make sure
2268 # it doesn't get triggered when that key is pressed in the
2269 # find string entry widget.
2270 proc bindkey {ev script} {
2271 global entries
2272 bind . $ev $script
2273 set escript [bind Entry $ev]
2274 if {$escript == {}} {
2275 set escript [bind Entry <Key>]
2277 foreach e $entries {
2278 bind $e $ev "$escript; break"
2282 # set the focus back to the toplevel for any click outside
2283 # the entry widgets
2284 proc click {w} {
2285 global ctext entries
2286 foreach e [concat $entries $ctext] {
2287 if {$w == $e} return
2289 focus .
2292 # Adjust the progress bar for a change in requested extent or canvas size
2293 proc adjustprogress {} {
2294 global progresscanv progressitem progresscoords
2295 global fprogitem fprogcoord lastprogupdate progupdatepending
2296 global rprogitem rprogcoord
2298 set w [expr {[winfo width $progresscanv] - 4}]
2299 set x0 [expr {$w * [lindex $progresscoords 0]}]
2300 set x1 [expr {$w * [lindex $progresscoords 1]}]
2301 set h [winfo height $progresscanv]
2302 $progresscanv coords $progressitem $x0 0 $x1 $h
2303 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2304 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2305 set now [clock clicks -milliseconds]
2306 if {$now >= $lastprogupdate + 100} {
2307 set progupdatepending 0
2308 update
2309 } elseif {!$progupdatepending} {
2310 set progupdatepending 1
2311 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2315 proc doprogupdate {} {
2316 global lastprogupdate progupdatepending
2318 if {$progupdatepending} {
2319 set progupdatepending 0
2320 set lastprogupdate [clock clicks -milliseconds]
2321 update
2325 proc savestuff {w} {
2326 global canv canv2 canv3 mainfont textfont uifont tabstop
2327 global stuffsaved findmergefiles maxgraphpct
2328 global maxwidth showneartags showlocalchanges
2329 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2330 global cmitmode wrapcomment datetimeformat limitdiffs
2331 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2332 global autoselect extdifftool
2334 if {$stuffsaved} return
2335 if {![winfo viewable .]} return
2336 catch {
2337 set f [open "~/.gitk-new" w]
2338 puts $f [list set mainfont $mainfont]
2339 puts $f [list set textfont $textfont]
2340 puts $f [list set uifont $uifont]
2341 puts $f [list set tabstop $tabstop]
2342 puts $f [list set findmergefiles $findmergefiles]
2343 puts $f [list set maxgraphpct $maxgraphpct]
2344 puts $f [list set maxwidth $maxwidth]
2345 puts $f [list set cmitmode $cmitmode]
2346 puts $f [list set wrapcomment $wrapcomment]
2347 puts $f [list set autoselect $autoselect]
2348 puts $f [list set showneartags $showneartags]
2349 puts $f [list set showlocalchanges $showlocalchanges]
2350 puts $f [list set datetimeformat $datetimeformat]
2351 puts $f [list set limitdiffs $limitdiffs]
2352 puts $f [list set bgcolor $bgcolor]
2353 puts $f [list set fgcolor $fgcolor]
2354 puts $f [list set colors $colors]
2355 puts $f [list set diffcolors $diffcolors]
2356 puts $f [list set diffcontext $diffcontext]
2357 puts $f [list set selectbgcolor $selectbgcolor]
2358 puts $f [list set extdifftool $extdifftool]
2360 puts $f "set geometry(main) [wm geometry .]"
2361 puts $f "set geometry(topwidth) [winfo width .tf]"
2362 puts $f "set geometry(topheight) [winfo height .tf]"
2363 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2364 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2365 puts $f "set geometry(botwidth) [winfo width .bleft]"
2366 puts $f "set geometry(botheight) [winfo height .bleft]"
2368 puts -nonewline $f "set permviews {"
2369 for {set v 0} {$v < $nextviewnum} {incr v} {
2370 if {$viewperm($v)} {
2371 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2374 puts $f "}"
2375 close $f
2376 file rename -force "~/.gitk-new" "~/.gitk"
2378 set stuffsaved 1
2381 proc resizeclistpanes {win w} {
2382 global oldwidth
2383 if {[info exists oldwidth($win)]} {
2384 set s0 [$win sash coord 0]
2385 set s1 [$win sash coord 1]
2386 if {$w < 60} {
2387 set sash0 [expr {int($w/2 - 2)}]
2388 set sash1 [expr {int($w*5/6 - 2)}]
2389 } else {
2390 set factor [expr {1.0 * $w / $oldwidth($win)}]
2391 set sash0 [expr {int($factor * [lindex $s0 0])}]
2392 set sash1 [expr {int($factor * [lindex $s1 0])}]
2393 if {$sash0 < 30} {
2394 set sash0 30
2396 if {$sash1 < $sash0 + 20} {
2397 set sash1 [expr {$sash0 + 20}]
2399 if {$sash1 > $w - 10} {
2400 set sash1 [expr {$w - 10}]
2401 if {$sash0 > $sash1 - 20} {
2402 set sash0 [expr {$sash1 - 20}]
2406 $win sash place 0 $sash0 [lindex $s0 1]
2407 $win sash place 1 $sash1 [lindex $s1 1]
2409 set oldwidth($win) $w
2412 proc resizecdetpanes {win w} {
2413 global oldwidth
2414 if {[info exists oldwidth($win)]} {
2415 set s0 [$win sash coord 0]
2416 if {$w < 60} {
2417 set sash0 [expr {int($w*3/4 - 2)}]
2418 } else {
2419 set factor [expr {1.0 * $w / $oldwidth($win)}]
2420 set sash0 [expr {int($factor * [lindex $s0 0])}]
2421 if {$sash0 < 45} {
2422 set sash0 45
2424 if {$sash0 > $w - 15} {
2425 set sash0 [expr {$w - 15}]
2428 $win sash place 0 $sash0 [lindex $s0 1]
2430 set oldwidth($win) $w
2433 proc allcanvs args {
2434 global canv canv2 canv3
2435 eval $canv $args
2436 eval $canv2 $args
2437 eval $canv3 $args
2440 proc bindall {event action} {
2441 global canv canv2 canv3
2442 bind $canv $event $action
2443 bind $canv2 $event $action
2444 bind $canv3 $event $action
2447 proc about {} {
2448 global uifont
2449 set w .about
2450 if {[winfo exists $w]} {
2451 raise $w
2452 return
2454 toplevel $w
2455 wm title $w [mc "About gitk"]
2456 message $w.m -text [mc "
2457 Gitk - a commit viewer for git
2459 Copyright © 2005-2008 Paul Mackerras
2461 Use and redistribute under the terms of the GNU General Public License"] \
2462 -justify center -aspect 400 -border 2 -bg white -relief groove
2463 pack $w.m -side top -fill x -padx 2 -pady 2
2464 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2465 pack $w.ok -side bottom
2466 bind $w <Visibility> "focus $w.ok"
2467 bind $w <Key-Escape> "destroy $w"
2468 bind $w <Key-Return> "destroy $w"
2471 proc keys {} {
2472 set w .keys
2473 if {[winfo exists $w]} {
2474 raise $w
2475 return
2477 if {[tk windowingsystem] eq {aqua}} {
2478 set M1T Cmd
2479 } else {
2480 set M1T Ctrl
2482 toplevel $w
2483 wm title $w [mc "Gitk key bindings"]
2484 message $w.m -text "
2485 [mc "Gitk key bindings:"]
2487 [mc "<%s-Q> Quit" $M1T]
2488 [mc "<Home> Move to first commit"]
2489 [mc "<End> Move to last commit"]
2490 [mc "<Up>, p, i Move up one commit"]
2491 [mc "<Down>, n, k Move down one commit"]
2492 [mc "<Left>, z, j Go back in history list"]
2493 [mc "<Right>, x, l Go forward in history list"]
2494 [mc "<PageUp> Move up one page in commit list"]
2495 [mc "<PageDown> Move down one page in commit list"]
2496 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2497 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2498 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2499 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2500 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2501 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2502 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2503 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2504 [mc "<Delete>, b Scroll diff view up one page"]
2505 [mc "<Backspace> Scroll diff view up one page"]
2506 [mc "<Space> Scroll diff view down one page"]
2507 [mc "u Scroll diff view up 18 lines"]
2508 [mc "d Scroll diff view down 18 lines"]
2509 [mc "<%s-F> Find" $M1T]
2510 [mc "<%s-G> Move to next find hit" $M1T]
2511 [mc "<Return> Move to next find hit"]
2512 [mc "/ Move to next find hit, or redo find"]
2513 [mc "? Move to previous find hit"]
2514 [mc "f Scroll diff view to next file"]
2515 [mc "<%s-S> Search for next hit in diff view" $M1T]
2516 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2517 [mc "<%s-KP+> Increase font size" $M1T]
2518 [mc "<%s-plus> Increase font size" $M1T]
2519 [mc "<%s-KP-> Decrease font size" $M1T]
2520 [mc "<%s-minus> Decrease font size" $M1T]
2521 [mc "<F5> Update"]
2523 -justify left -bg white -border 2 -relief groove
2524 pack $w.m -side top -fill both -padx 2 -pady 2
2525 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2526 pack $w.ok -side bottom
2527 bind $w <Visibility> "focus $w.ok"
2528 bind $w <Key-Escape> "destroy $w"
2529 bind $w <Key-Return> "destroy $w"
2532 # Procedures for manipulating the file list window at the
2533 # bottom right of the overall window.
2535 proc treeview {w l openlevs} {
2536 global treecontents treediropen treeheight treeparent treeindex
2538 set ix 0
2539 set treeindex() 0
2540 set lev 0
2541 set prefix {}
2542 set prefixend -1
2543 set prefendstack {}
2544 set htstack {}
2545 set ht 0
2546 set treecontents() {}
2547 $w conf -state normal
2548 foreach f $l {
2549 while {[string range $f 0 $prefixend] ne $prefix} {
2550 if {$lev <= $openlevs} {
2551 $w mark set e:$treeindex($prefix) "end -1c"
2552 $w mark gravity e:$treeindex($prefix) left
2554 set treeheight($prefix) $ht
2555 incr ht [lindex $htstack end]
2556 set htstack [lreplace $htstack end end]
2557 set prefixend [lindex $prefendstack end]
2558 set prefendstack [lreplace $prefendstack end end]
2559 set prefix [string range $prefix 0 $prefixend]
2560 incr lev -1
2562 set tail [string range $f [expr {$prefixend+1}] end]
2563 while {[set slash [string first "/" $tail]] >= 0} {
2564 lappend htstack $ht
2565 set ht 0
2566 lappend prefendstack $prefixend
2567 incr prefixend [expr {$slash + 1}]
2568 set d [string range $tail 0 $slash]
2569 lappend treecontents($prefix) $d
2570 set oldprefix $prefix
2571 append prefix $d
2572 set treecontents($prefix) {}
2573 set treeindex($prefix) [incr ix]
2574 set treeparent($prefix) $oldprefix
2575 set tail [string range $tail [expr {$slash+1}] end]
2576 if {$lev <= $openlevs} {
2577 set ht 1
2578 set treediropen($prefix) [expr {$lev < $openlevs}]
2579 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2580 $w mark set d:$ix "end -1c"
2581 $w mark gravity d:$ix left
2582 set str "\n"
2583 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2584 $w insert end $str
2585 $w image create end -align center -image $bm -padx 1 \
2586 -name a:$ix
2587 $w insert end $d [highlight_tag $prefix]
2588 $w mark set s:$ix "end -1c"
2589 $w mark gravity s:$ix left
2591 incr lev
2593 if {$tail ne {}} {
2594 if {$lev <= $openlevs} {
2595 incr ht
2596 set str "\n"
2597 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2598 $w insert end $str
2599 $w insert end $tail [highlight_tag $f]
2601 lappend treecontents($prefix) $tail
2604 while {$htstack ne {}} {
2605 set treeheight($prefix) $ht
2606 incr ht [lindex $htstack end]
2607 set htstack [lreplace $htstack end end]
2608 set prefixend [lindex $prefendstack end]
2609 set prefendstack [lreplace $prefendstack end end]
2610 set prefix [string range $prefix 0 $prefixend]
2612 $w conf -state disabled
2615 proc linetoelt {l} {
2616 global treeheight treecontents
2618 set y 2
2619 set prefix {}
2620 while {1} {
2621 foreach e $treecontents($prefix) {
2622 if {$y == $l} {
2623 return "$prefix$e"
2625 set n 1
2626 if {[string index $e end] eq "/"} {
2627 set n $treeheight($prefix$e)
2628 if {$y + $n > $l} {
2629 append prefix $e
2630 incr y
2631 break
2634 incr y $n
2639 proc highlight_tree {y prefix} {
2640 global treeheight treecontents cflist
2642 foreach e $treecontents($prefix) {
2643 set path $prefix$e
2644 if {[highlight_tag $path] ne {}} {
2645 $cflist tag add bold $y.0 "$y.0 lineend"
2647 incr y
2648 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2649 set y [highlight_tree $y $path]
2652 return $y
2655 proc treeclosedir {w dir} {
2656 global treediropen treeheight treeparent treeindex
2658 set ix $treeindex($dir)
2659 $w conf -state normal
2660 $w delete s:$ix e:$ix
2661 set treediropen($dir) 0
2662 $w image configure a:$ix -image tri-rt
2663 $w conf -state disabled
2664 set n [expr {1 - $treeheight($dir)}]
2665 while {$dir ne {}} {
2666 incr treeheight($dir) $n
2667 set dir $treeparent($dir)
2671 proc treeopendir {w dir} {
2672 global treediropen treeheight treeparent treecontents treeindex
2674 set ix $treeindex($dir)
2675 $w conf -state normal
2676 $w image configure a:$ix -image tri-dn
2677 $w mark set e:$ix s:$ix
2678 $w mark gravity e:$ix right
2679 set lev 0
2680 set str "\n"
2681 set n [llength $treecontents($dir)]
2682 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2683 incr lev
2684 append str "\t"
2685 incr treeheight($x) $n
2687 foreach e $treecontents($dir) {
2688 set de $dir$e
2689 if {[string index $e end] eq "/"} {
2690 set iy $treeindex($de)
2691 $w mark set d:$iy e:$ix
2692 $w mark gravity d:$iy left
2693 $w insert e:$ix $str
2694 set treediropen($de) 0
2695 $w image create e:$ix -align center -image tri-rt -padx 1 \
2696 -name a:$iy
2697 $w insert e:$ix $e [highlight_tag $de]
2698 $w mark set s:$iy e:$ix
2699 $w mark gravity s:$iy left
2700 set treeheight($de) 1
2701 } else {
2702 $w insert e:$ix $str
2703 $w insert e:$ix $e [highlight_tag $de]
2706 $w mark gravity e:$ix left
2707 $w conf -state disabled
2708 set treediropen($dir) 1
2709 set top [lindex [split [$w index @0,0] .] 0]
2710 set ht [$w cget -height]
2711 set l [lindex [split [$w index s:$ix] .] 0]
2712 if {$l < $top} {
2713 $w yview $l.0
2714 } elseif {$l + $n + 1 > $top + $ht} {
2715 set top [expr {$l + $n + 2 - $ht}]
2716 if {$l < $top} {
2717 set top $l
2719 $w yview $top.0
2723 proc treeclick {w x y} {
2724 global treediropen cmitmode ctext cflist cflist_top
2726 if {$cmitmode ne "tree"} return
2727 if {![info exists cflist_top]} return
2728 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2729 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2730 $cflist tag add highlight $l.0 "$l.0 lineend"
2731 set cflist_top $l
2732 if {$l == 1} {
2733 $ctext yview 1.0
2734 return
2736 set e [linetoelt $l]
2737 if {[string index $e end] ne "/"} {
2738 showfile $e
2739 } elseif {$treediropen($e)} {
2740 treeclosedir $w $e
2741 } else {
2742 treeopendir $w $e
2746 proc setfilelist {id} {
2747 global treefilelist cflist
2749 treeview $cflist $treefilelist($id) 0
2752 image create bitmap tri-rt -background black -foreground blue -data {
2753 #define tri-rt_width 13
2754 #define tri-rt_height 13
2755 static unsigned char tri-rt_bits[] = {
2756 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2757 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2758 0x00, 0x00};
2759 } -maskdata {
2760 #define tri-rt-mask_width 13
2761 #define tri-rt-mask_height 13
2762 static unsigned char tri-rt-mask_bits[] = {
2763 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2764 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2765 0x08, 0x00};
2767 image create bitmap tri-dn -background black -foreground blue -data {
2768 #define tri-dn_width 13
2769 #define tri-dn_height 13
2770 static unsigned char tri-dn_bits[] = {
2771 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2772 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2773 0x00, 0x00};
2774 } -maskdata {
2775 #define tri-dn-mask_width 13
2776 #define tri-dn-mask_height 13
2777 static unsigned char tri-dn-mask_bits[] = {
2778 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2779 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2780 0x00, 0x00};
2783 image create bitmap reficon-T -background black -foreground yellow -data {
2784 #define tagicon_width 13
2785 #define tagicon_height 9
2786 static unsigned char tagicon_bits[] = {
2787 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2788 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2789 } -maskdata {
2790 #define tagicon-mask_width 13
2791 #define tagicon-mask_height 9
2792 static unsigned char tagicon-mask_bits[] = {
2793 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2794 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2796 set rectdata {
2797 #define headicon_width 13
2798 #define headicon_height 9
2799 static unsigned char headicon_bits[] = {
2800 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2801 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2803 set rectmask {
2804 #define headicon-mask_width 13
2805 #define headicon-mask_height 9
2806 static unsigned char headicon-mask_bits[] = {
2807 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2808 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2810 image create bitmap reficon-H -background black -foreground green \
2811 -data $rectdata -maskdata $rectmask
2812 image create bitmap reficon-o -background black -foreground "#ddddff" \
2813 -data $rectdata -maskdata $rectmask
2815 proc init_flist {first} {
2816 global cflist cflist_top difffilestart
2818 $cflist conf -state normal
2819 $cflist delete 0.0 end
2820 if {$first ne {}} {
2821 $cflist insert end $first
2822 set cflist_top 1
2823 $cflist tag add highlight 1.0 "1.0 lineend"
2824 } else {
2825 catch {unset cflist_top}
2827 $cflist conf -state disabled
2828 set difffilestart {}
2831 proc highlight_tag {f} {
2832 global highlight_paths
2834 foreach p $highlight_paths {
2835 if {[string match $p $f]} {
2836 return "bold"
2839 return {}
2842 proc highlight_filelist {} {
2843 global cmitmode cflist
2845 $cflist conf -state normal
2846 if {$cmitmode ne "tree"} {
2847 set end [lindex [split [$cflist index end] .] 0]
2848 for {set l 2} {$l < $end} {incr l} {
2849 set line [$cflist get $l.0 "$l.0 lineend"]
2850 if {[highlight_tag $line] ne {}} {
2851 $cflist tag add bold $l.0 "$l.0 lineend"
2854 } else {
2855 highlight_tree 2 {}
2857 $cflist conf -state disabled
2860 proc unhighlight_filelist {} {
2861 global cflist
2863 $cflist conf -state normal
2864 $cflist tag remove bold 1.0 end
2865 $cflist conf -state disabled
2868 proc add_flist {fl} {
2869 global cflist
2871 $cflist conf -state normal
2872 foreach f $fl {
2873 $cflist insert end "\n"
2874 $cflist insert end $f [highlight_tag $f]
2876 $cflist conf -state disabled
2879 proc sel_flist {w x y} {
2880 global ctext difffilestart cflist cflist_top cmitmode
2882 if {$cmitmode eq "tree"} return
2883 if {![info exists cflist_top]} return
2884 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2885 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2886 $cflist tag add highlight $l.0 "$l.0 lineend"
2887 set cflist_top $l
2888 if {$l == 1} {
2889 $ctext yview 1.0
2890 } else {
2891 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2895 proc pop_flist_menu {w X Y x y} {
2896 global ctext cflist cmitmode flist_menu flist_menu_file
2897 global treediffs diffids
2899 stopfinding
2900 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2901 if {$l <= 1} return
2902 if {$cmitmode eq "tree"} {
2903 set e [linetoelt $l]
2904 if {[string index $e end] eq "/"} return
2905 } else {
2906 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2908 set flist_menu_file $e
2909 set xdiffstate "normal"
2910 if {$cmitmode eq "tree"} {
2911 set xdiffstate "disabled"
2913 # Disable "External diff" item in tree mode
2914 $flist_menu entryconf 2 -state $xdiffstate
2915 tk_popup $flist_menu $X $Y
2918 proc flist_hl {only} {
2919 global flist_menu_file findstring gdttype
2921 set x [shellquote $flist_menu_file]
2922 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
2923 set findstring $x
2924 } else {
2925 append findstring " " $x
2927 set gdttype [mc "touching paths:"]
2930 proc save_file_from_commit {filename output what} {
2931 global nullfile
2933 if {[catch {exec git show $filename -- > $output} err]} {
2934 if {[string match "fatal: bad revision *" $err]} {
2935 return $nullfile
2937 error_popup "Error getting \"$filename\" from $what: $err"
2938 return {}
2940 return $output
2943 proc external_diff_get_one_file {diffid filename diffdir} {
2944 global nullid nullid2 nullfile
2945 global gitdir
2947 if {$diffid == $nullid} {
2948 set difffile [file join [file dirname $gitdir] $filename]
2949 if {[file exists $difffile]} {
2950 return $difffile
2952 return $nullfile
2954 if {$diffid == $nullid2} {
2955 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
2956 return [save_file_from_commit :$filename $difffile index]
2958 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
2959 return [save_file_from_commit $diffid:$filename $difffile \
2960 "revision $diffid"]
2963 proc external_diff {} {
2964 global gitktmpdir nullid nullid2
2965 global flist_menu_file
2966 global diffids
2967 global diffnum
2968 global gitdir extdifftool
2970 if {[llength $diffids] == 1} {
2971 # no reference commit given
2972 set diffidto [lindex $diffids 0]
2973 if {$diffidto eq $nullid} {
2974 # diffing working copy with index
2975 set diffidfrom $nullid2
2976 } elseif {$diffidto eq $nullid2} {
2977 # diffing index with HEAD
2978 set diffidfrom "HEAD"
2979 } else {
2980 # use first parent commit
2981 global parentlist selectedline
2982 set diffidfrom [lindex $parentlist $selectedline 0]
2984 } else {
2985 set diffidfrom [lindex $diffids 0]
2986 set diffidto [lindex $diffids 1]
2989 # make sure that several diffs wont collide
2990 if {![info exists gitktmpdir]} {
2991 set gitktmpdir [file join [file dirname $gitdir] \
2992 [format ".gitk-tmp.%s" [pid]]]
2993 if {[catch {file mkdir $gitktmpdir} err]} {
2994 error_popup "Error creating temporary directory $gitktmpdir: $err"
2995 unset gitktmpdir
2996 return
2998 set diffnum 0
3000 incr diffnum
3001 set diffdir [file join $gitktmpdir $diffnum]
3002 if {[catch {file mkdir $diffdir} err]} {
3003 error_popup "Error creating temporary directory $diffdir: $err"
3004 return
3007 # gather files to diff
3008 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3009 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3011 if {$difffromfile ne {} && $difftofile ne {}} {
3012 set cmd [concat | [shellsplit $extdifftool] \
3013 [list $difffromfile $difftofile]]
3014 if {[catch {set fl [open $cmd r]} err]} {
3015 file delete -force $diffdir
3016 error_popup [mc "$extdifftool: command failed: $err"]
3017 } else {
3018 fconfigure $fl -blocking 0
3019 filerun $fl [list delete_at_eof $fl $diffdir]
3024 # delete $dir when we see eof on $f (presumably because the child has exited)
3025 proc delete_at_eof {f dir} {
3026 while {[gets $f line] >= 0} {}
3027 if {[eof $f]} {
3028 if {[catch {close $f} err]} {
3029 error_popup "External diff viewer failed: $err"
3031 file delete -force $dir
3032 return 0
3034 return 1
3037 # Functions for adding and removing shell-type quoting
3039 proc shellquote {str} {
3040 if {![string match "*\['\"\\ \t]*" $str]} {
3041 return $str
3043 if {![string match "*\['\"\\]*" $str]} {
3044 return "\"$str\""
3046 if {![string match "*'*" $str]} {
3047 return "'$str'"
3049 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3052 proc shellarglist {l} {
3053 set str {}
3054 foreach a $l {
3055 if {$str ne {}} {
3056 append str " "
3058 append str [shellquote $a]
3060 return $str
3063 proc shelldequote {str} {
3064 set ret {}
3065 set used -1
3066 while {1} {
3067 incr used
3068 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3069 append ret [string range $str $used end]
3070 set used [string length $str]
3071 break
3073 set first [lindex $first 0]
3074 set ch [string index $str $first]
3075 if {$first > $used} {
3076 append ret [string range $str $used [expr {$first - 1}]]
3077 set used $first
3079 if {$ch eq " " || $ch eq "\t"} break
3080 incr used
3081 if {$ch eq "'"} {
3082 set first [string first "'" $str $used]
3083 if {$first < 0} {
3084 error "unmatched single-quote"
3086 append ret [string range $str $used [expr {$first - 1}]]
3087 set used $first
3088 continue
3090 if {$ch eq "\\"} {
3091 if {$used >= [string length $str]} {
3092 error "trailing backslash"
3094 append ret [string index $str $used]
3095 continue
3097 # here ch == "\""
3098 while {1} {
3099 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3100 error "unmatched double-quote"
3102 set first [lindex $first 0]
3103 set ch [string index $str $first]
3104 if {$first > $used} {
3105 append ret [string range $str $used [expr {$first - 1}]]
3106 set used $first
3108 if {$ch eq "\""} break
3109 incr used
3110 append ret [string index $str $used]
3111 incr used
3114 return [list $used $ret]
3117 proc shellsplit {str} {
3118 set l {}
3119 while {1} {
3120 set str [string trimleft $str]
3121 if {$str eq {}} break
3122 set dq [shelldequote $str]
3123 set n [lindex $dq 0]
3124 set word [lindex $dq 1]
3125 set str [string range $str $n end]
3126 lappend l $word
3128 return $l
3131 # Code to implement multiple views
3133 proc newview {ishighlight} {
3134 global nextviewnum newviewname newviewperm newishighlight
3135 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3137 set newishighlight $ishighlight
3138 set top .gitkview
3139 if {[winfo exists $top]} {
3140 raise $top
3141 return
3143 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3144 set newviewperm($nextviewnum) 0
3145 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3146 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3147 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3150 proc editview {} {
3151 global curview
3152 global viewname viewperm newviewname newviewperm
3153 global viewargs newviewargs viewargscmd newviewargscmd
3155 set top .gitkvedit-$curview
3156 if {[winfo exists $top]} {
3157 raise $top
3158 return
3160 set newviewname($curview) $viewname($curview)
3161 set newviewperm($curview) $viewperm($curview)
3162 set newviewargs($curview) [shellarglist $viewargs($curview)]
3163 set newviewargscmd($curview) $viewargscmd($curview)
3164 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3167 proc vieweditor {top n title} {
3168 global newviewname newviewperm viewfiles bgcolor
3170 toplevel $top
3171 wm title $top $title
3172 label $top.nl -text [mc "Name"]
3173 entry $top.name -width 20 -textvariable newviewname($n)
3174 grid $top.nl $top.name -sticky w -pady 5
3175 checkbutton $top.perm -text [mc "Remember this view"] \
3176 -variable newviewperm($n)
3177 grid $top.perm - -pady 5 -sticky w
3178 message $top.al -aspect 1000 \
3179 -text [mc "Commits to include (arguments to git log):"]
3180 grid $top.al - -sticky w -pady 5
3181 entry $top.args -width 50 -textvariable newviewargs($n) \
3182 -background $bgcolor
3183 grid $top.args - -sticky ew -padx 5
3185 message $top.ac -aspect 1000 \
3186 -text [mc "Command to generate more commits to include:"]
3187 grid $top.ac - -sticky w -pady 5
3188 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3189 -background white
3190 grid $top.argscmd - -sticky ew -padx 5
3192 message $top.l -aspect 1000 \
3193 -text [mc "Enter files and directories to include, one per line:"]
3194 grid $top.l - -sticky w
3195 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3196 if {[info exists viewfiles($n)]} {
3197 foreach f $viewfiles($n) {
3198 $top.t insert end $f
3199 $top.t insert end "\n"
3201 $top.t delete {end - 1c} end
3202 $top.t mark set insert 0.0
3204 grid $top.t - -sticky ew -padx 5
3205 frame $top.buts
3206 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3207 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3208 grid $top.buts.ok $top.buts.can
3209 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3210 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3211 grid $top.buts - -pady 10 -sticky ew
3212 focus $top.t
3215 proc doviewmenu {m first cmd op argv} {
3216 set nmenu [$m index end]
3217 for {set i $first} {$i <= $nmenu} {incr i} {
3218 if {[$m entrycget $i -command] eq $cmd} {
3219 eval $m $op $i $argv
3220 break
3225 proc allviewmenus {n op args} {
3226 # global viewhlmenu
3228 doviewmenu .bar.view 5 [list showview $n] $op $args
3229 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3232 proc newviewok {top n} {
3233 global nextviewnum newviewperm newviewname newishighlight
3234 global viewname viewfiles viewperm selectedview curview
3235 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3237 if {[catch {
3238 set newargs [shellsplit $newviewargs($n)]
3239 } err]} {
3240 error_popup "[mc "Error in commit selection arguments:"] $err"
3241 wm raise $top
3242 focus $top
3243 return
3245 set files {}
3246 foreach f [split [$top.t get 0.0 end] "\n"] {
3247 set ft [string trim $f]
3248 if {$ft ne {}} {
3249 lappend files $ft
3252 if {![info exists viewfiles($n)]} {
3253 # creating a new view
3254 incr nextviewnum
3255 set viewname($n) $newviewname($n)
3256 set viewperm($n) $newviewperm($n)
3257 set viewfiles($n) $files
3258 set viewargs($n) $newargs
3259 set viewargscmd($n) $newviewargscmd($n)
3260 addviewmenu $n
3261 if {!$newishighlight} {
3262 run showview $n
3263 } else {
3264 run addvhighlight $n
3266 } else {
3267 # editing an existing view
3268 set viewperm($n) $newviewperm($n)
3269 if {$newviewname($n) ne $viewname($n)} {
3270 set viewname($n) $newviewname($n)
3271 doviewmenu .bar.view 5 [list showview $n] \
3272 entryconf [list -label $viewname($n)]
3273 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3274 # entryconf [list -label $viewname($n) -value $viewname($n)]
3276 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3277 $newviewargscmd($n) ne $viewargscmd($n)} {
3278 set viewfiles($n) $files
3279 set viewargs($n) $newargs
3280 set viewargscmd($n) $newviewargscmd($n)
3281 if {$curview == $n} {
3282 run reloadcommits
3286 catch {destroy $top}
3289 proc delview {} {
3290 global curview viewperm hlview selectedhlview
3292 if {$curview == 0} return
3293 if {[info exists hlview] && $hlview == $curview} {
3294 set selectedhlview [mc "None"]
3295 unset hlview
3297 allviewmenus $curview delete
3298 set viewperm($curview) 0
3299 showview 0
3302 proc addviewmenu {n} {
3303 global viewname viewhlmenu
3305 .bar.view add radiobutton -label $viewname($n) \
3306 -command [list showview $n] -variable selectedview -value $n
3307 #$viewhlmenu add radiobutton -label $viewname($n) \
3308 # -command [list addvhighlight $n] -variable selectedhlview
3311 proc showview {n} {
3312 global curview cached_commitrow ordertok
3313 global displayorder parentlist rowidlist rowisopt rowfinal
3314 global colormap rowtextx nextcolor canvxmax
3315 global numcommits viewcomplete
3316 global selectedline currentid canv canvy0
3317 global treediffs
3318 global pending_select mainheadid
3319 global commitidx
3320 global selectedview
3321 global hlview selectedhlview commitinterest
3323 if {$n == $curview} return
3324 set selid {}
3325 set ymax [lindex [$canv cget -scrollregion] 3]
3326 set span [$canv yview]
3327 set ytop [expr {[lindex $span 0] * $ymax}]
3328 set ybot [expr {[lindex $span 1] * $ymax}]
3329 set yscreen [expr {($ybot - $ytop) / 2}]
3330 if {$selectedline ne {}} {
3331 set selid $currentid
3332 set y [yc $selectedline]
3333 if {$ytop < $y && $y < $ybot} {
3334 set yscreen [expr {$y - $ytop}]
3336 } elseif {[info exists pending_select]} {
3337 set selid $pending_select
3338 unset pending_select
3340 unselectline
3341 normalline
3342 catch {unset treediffs}
3343 clear_display
3344 if {[info exists hlview] && $hlview == $n} {
3345 unset hlview
3346 set selectedhlview [mc "None"]
3348 catch {unset commitinterest}
3349 catch {unset cached_commitrow}
3350 catch {unset ordertok}
3352 set curview $n
3353 set selectedview $n
3354 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3355 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3357 run refill_reflist
3358 if {![info exists viewcomplete($n)]} {
3359 getcommits $selid
3360 return
3363 set displayorder {}
3364 set parentlist {}
3365 set rowidlist {}
3366 set rowisopt {}
3367 set rowfinal {}
3368 set numcommits $commitidx($n)
3370 catch {unset colormap}
3371 catch {unset rowtextx}
3372 set nextcolor 0
3373 set canvxmax [$canv cget -width]
3374 set curview $n
3375 set row 0
3376 setcanvscroll
3377 set yf 0
3378 set row {}
3379 if {$selid ne {} && [commitinview $selid $n]} {
3380 set row [rowofcommit $selid]
3381 # try to get the selected row in the same position on the screen
3382 set ymax [lindex [$canv cget -scrollregion] 3]
3383 set ytop [expr {[yc $row] - $yscreen}]
3384 if {$ytop < 0} {
3385 set ytop 0
3387 set yf [expr {$ytop * 1.0 / $ymax}]
3389 allcanvs yview moveto $yf
3390 drawvisible
3391 if {$row ne {}} {
3392 selectline $row 0
3393 } elseif {!$viewcomplete($n)} {
3394 reset_pending_select $selid
3395 } else {
3396 reset_pending_select {}
3398 if {[commitinview $pending_select $curview]} {
3399 selectline [rowofcommit $pending_select] 1
3400 } else {
3401 set row [first_real_row]
3402 if {$row < $numcommits} {
3403 selectline $row 0
3407 if {!$viewcomplete($n)} {
3408 if {$numcommits == 0} {
3409 show_status [mc "Reading commits..."]
3411 } elseif {$numcommits == 0} {
3412 show_status [mc "No commits selected"]
3416 # Stuff relating to the highlighting facility
3418 proc ishighlighted {id} {
3419 global vhighlights fhighlights nhighlights rhighlights
3421 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3422 return $nhighlights($id)
3424 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3425 return $vhighlights($id)
3427 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3428 return $fhighlights($id)
3430 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3431 return $rhighlights($id)
3433 return 0
3436 proc bolden {row font} {
3437 global canv linehtag selectedline boldrows
3439 lappend boldrows $row
3440 $canv itemconf $linehtag($row) -font $font
3441 if {$row == $selectedline} {
3442 $canv delete secsel
3443 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3444 -outline {{}} -tags secsel \
3445 -fill [$canv cget -selectbackground]]
3446 $canv lower $t
3450 proc bolden_name {row font} {
3451 global canv2 linentag selectedline boldnamerows
3453 lappend boldnamerows $row
3454 $canv2 itemconf $linentag($row) -font $font
3455 if {$row == $selectedline} {
3456 $canv2 delete secsel
3457 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3458 -outline {{}} -tags secsel \
3459 -fill [$canv2 cget -selectbackground]]
3460 $canv2 lower $t
3464 proc unbolden {} {
3465 global boldrows
3467 set stillbold {}
3468 foreach row $boldrows {
3469 if {![ishighlighted [commitonrow $row]]} {
3470 bolden $row mainfont
3471 } else {
3472 lappend stillbold $row
3475 set boldrows $stillbold
3478 proc addvhighlight {n} {
3479 global hlview viewcomplete curview vhl_done commitidx
3481 if {[info exists hlview]} {
3482 delvhighlight
3484 set hlview $n
3485 if {$n != $curview && ![info exists viewcomplete($n)]} {
3486 start_rev_list $n
3488 set vhl_done $commitidx($hlview)
3489 if {$vhl_done > 0} {
3490 drawvisible
3494 proc delvhighlight {} {
3495 global hlview vhighlights
3497 if {![info exists hlview]} return
3498 unset hlview
3499 catch {unset vhighlights}
3500 unbolden
3503 proc vhighlightmore {} {
3504 global hlview vhl_done commitidx vhighlights curview
3506 set max $commitidx($hlview)
3507 set vr [visiblerows]
3508 set r0 [lindex $vr 0]
3509 set r1 [lindex $vr 1]
3510 for {set i $vhl_done} {$i < $max} {incr i} {
3511 set id [commitonrow $i $hlview]
3512 if {[commitinview $id $curview]} {
3513 set row [rowofcommit $id]
3514 if {$r0 <= $row && $row <= $r1} {
3515 if {![highlighted $row]} {
3516 bolden $row mainfontbold
3518 set vhighlights($id) 1
3522 set vhl_done $max
3523 return 0
3526 proc askvhighlight {row id} {
3527 global hlview vhighlights iddrawn
3529 if {[commitinview $id $hlview]} {
3530 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3531 bolden $row mainfontbold
3533 set vhighlights($id) 1
3534 } else {
3535 set vhighlights($id) 0
3539 proc hfiles_change {} {
3540 global highlight_files filehighlight fhighlights fh_serial
3541 global highlight_paths gdttype
3543 if {[info exists filehighlight]} {
3544 # delete previous highlights
3545 catch {close $filehighlight}
3546 unset filehighlight
3547 catch {unset fhighlights}
3548 unbolden
3549 unhighlight_filelist
3551 set highlight_paths {}
3552 after cancel do_file_hl $fh_serial
3553 incr fh_serial
3554 if {$highlight_files ne {}} {
3555 after 300 do_file_hl $fh_serial
3559 proc gdttype_change {name ix op} {
3560 global gdttype highlight_files findstring findpattern
3562 stopfinding
3563 if {$findstring ne {}} {
3564 if {$gdttype eq [mc "containing:"]} {
3565 if {$highlight_files ne {}} {
3566 set highlight_files {}
3567 hfiles_change
3569 findcom_change
3570 } else {
3571 if {$findpattern ne {}} {
3572 set findpattern {}
3573 findcom_change
3575 set highlight_files $findstring
3576 hfiles_change
3578 drawvisible
3580 # enable/disable findtype/findloc menus too
3583 proc find_change {name ix op} {
3584 global gdttype findstring highlight_files
3586 stopfinding
3587 if {$gdttype eq [mc "containing:"]} {
3588 findcom_change
3589 } else {
3590 if {$highlight_files ne $findstring} {
3591 set highlight_files $findstring
3592 hfiles_change
3595 drawvisible
3598 proc findcom_change args {
3599 global nhighlights boldnamerows
3600 global findpattern findtype findstring gdttype
3602 stopfinding
3603 # delete previous highlights, if any
3604 foreach row $boldnamerows {
3605 bolden_name $row mainfont
3607 set boldnamerows {}
3608 catch {unset nhighlights}
3609 unbolden
3610 unmarkmatches
3611 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3612 set findpattern {}
3613 } elseif {$findtype eq [mc "Regexp"]} {
3614 set findpattern $findstring
3615 } else {
3616 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3617 $findstring]
3618 set findpattern "*$e*"
3622 proc makepatterns {l} {
3623 set ret {}
3624 foreach e $l {
3625 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3626 if {[string index $ee end] eq "/"} {
3627 lappend ret "$ee*"
3628 } else {
3629 lappend ret $ee
3630 lappend ret "$ee/*"
3633 return $ret
3636 proc do_file_hl {serial} {
3637 global highlight_files filehighlight highlight_paths gdttype fhl_list
3639 if {$gdttype eq [mc "touching paths:"]} {
3640 if {[catch {set paths [shellsplit $highlight_files]}]} return
3641 set highlight_paths [makepatterns $paths]
3642 highlight_filelist
3643 set gdtargs [concat -- $paths]
3644 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3645 set gdtargs [list "-S$highlight_files"]
3646 } else {
3647 # must be "containing:", i.e. we're searching commit info
3648 return
3650 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3651 set filehighlight [open $cmd r+]
3652 fconfigure $filehighlight -blocking 0
3653 filerun $filehighlight readfhighlight
3654 set fhl_list {}
3655 drawvisible
3656 flushhighlights
3659 proc flushhighlights {} {
3660 global filehighlight fhl_list
3662 if {[info exists filehighlight]} {
3663 lappend fhl_list {}
3664 puts $filehighlight ""
3665 flush $filehighlight
3669 proc askfilehighlight {row id} {
3670 global filehighlight fhighlights fhl_list
3672 lappend fhl_list $id
3673 set fhighlights($id) -1
3674 puts $filehighlight $id
3677 proc readfhighlight {} {
3678 global filehighlight fhighlights curview iddrawn
3679 global fhl_list find_dirn
3681 if {![info exists filehighlight]} {
3682 return 0
3684 set nr 0
3685 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3686 set line [string trim $line]
3687 set i [lsearch -exact $fhl_list $line]
3688 if {$i < 0} continue
3689 for {set j 0} {$j < $i} {incr j} {
3690 set id [lindex $fhl_list $j]
3691 set fhighlights($id) 0
3693 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3694 if {$line eq {}} continue
3695 if {![commitinview $line $curview]} continue
3696 set row [rowofcommit $line]
3697 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3698 bolden $row mainfontbold
3700 set fhighlights($line) 1
3702 if {[eof $filehighlight]} {
3703 # strange...
3704 puts "oops, git diff-tree died"
3705 catch {close $filehighlight}
3706 unset filehighlight
3707 return 0
3709 if {[info exists find_dirn]} {
3710 run findmore
3712 return 1
3715 proc doesmatch {f} {
3716 global findtype findpattern
3718 if {$findtype eq [mc "Regexp"]} {
3719 return [regexp $findpattern $f]
3720 } elseif {$findtype eq [mc "IgnCase"]} {
3721 return [string match -nocase $findpattern $f]
3722 } else {
3723 return [string match $findpattern $f]
3727 proc askfindhighlight {row id} {
3728 global nhighlights commitinfo iddrawn
3729 global findloc
3730 global markingmatches
3732 if {![info exists commitinfo($id)]} {
3733 getcommit $id
3735 set info $commitinfo($id)
3736 set isbold 0
3737 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3738 foreach f $info ty $fldtypes {
3739 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3740 [doesmatch $f]} {
3741 if {$ty eq [mc "Author"]} {
3742 set isbold 2
3743 break
3745 set isbold 1
3748 if {$isbold && [info exists iddrawn($id)]} {
3749 if {![ishighlighted $id]} {
3750 bolden $row mainfontbold
3751 if {$isbold > 1} {
3752 bolden_name $row mainfontbold
3755 if {$markingmatches} {
3756 markrowmatches $row $id
3759 set nhighlights($id) $isbold
3762 proc markrowmatches {row id} {
3763 global canv canv2 linehtag linentag commitinfo findloc
3765 set headline [lindex $commitinfo($id) 0]
3766 set author [lindex $commitinfo($id) 1]
3767 $canv delete match$row
3768 $canv2 delete match$row
3769 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3770 set m [findmatches $headline]
3771 if {$m ne {}} {
3772 markmatches $canv $row $headline $linehtag($row) $m \
3773 [$canv itemcget $linehtag($row) -font] $row
3776 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3777 set m [findmatches $author]
3778 if {$m ne {}} {
3779 markmatches $canv2 $row $author $linentag($row) $m \
3780 [$canv2 itemcget $linentag($row) -font] $row
3785 proc vrel_change {name ix op} {
3786 global highlight_related
3788 rhighlight_none
3789 if {$highlight_related ne [mc "None"]} {
3790 run drawvisible
3794 # prepare for testing whether commits are descendents or ancestors of a
3795 proc rhighlight_sel {a} {
3796 global descendent desc_todo ancestor anc_todo
3797 global highlight_related
3799 catch {unset descendent}
3800 set desc_todo [list $a]
3801 catch {unset ancestor}
3802 set anc_todo [list $a]
3803 if {$highlight_related ne [mc "None"]} {
3804 rhighlight_none
3805 run drawvisible
3809 proc rhighlight_none {} {
3810 global rhighlights
3812 catch {unset rhighlights}
3813 unbolden
3816 proc is_descendent {a} {
3817 global curview children descendent desc_todo
3819 set v $curview
3820 set la [rowofcommit $a]
3821 set todo $desc_todo
3822 set leftover {}
3823 set done 0
3824 for {set i 0} {$i < [llength $todo]} {incr i} {
3825 set do [lindex $todo $i]
3826 if {[rowofcommit $do] < $la} {
3827 lappend leftover $do
3828 continue
3830 foreach nk $children($v,$do) {
3831 if {![info exists descendent($nk)]} {
3832 set descendent($nk) 1
3833 lappend todo $nk
3834 if {$nk eq $a} {
3835 set done 1
3839 if {$done} {
3840 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3841 return
3844 set descendent($a) 0
3845 set desc_todo $leftover
3848 proc is_ancestor {a} {
3849 global curview parents ancestor anc_todo
3851 set v $curview
3852 set la [rowofcommit $a]
3853 set todo $anc_todo
3854 set leftover {}
3855 set done 0
3856 for {set i 0} {$i < [llength $todo]} {incr i} {
3857 set do [lindex $todo $i]
3858 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3859 lappend leftover $do
3860 continue
3862 foreach np $parents($v,$do) {
3863 if {![info exists ancestor($np)]} {
3864 set ancestor($np) 1
3865 lappend todo $np
3866 if {$np eq $a} {
3867 set done 1
3871 if {$done} {
3872 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3873 return
3876 set ancestor($a) 0
3877 set anc_todo $leftover
3880 proc askrelhighlight {row id} {
3881 global descendent highlight_related iddrawn rhighlights
3882 global selectedline ancestor
3884 if {$selectedline eq {}} return
3885 set isbold 0
3886 if {$highlight_related eq [mc "Descendant"] ||
3887 $highlight_related eq [mc "Not descendant"]} {
3888 if {![info exists descendent($id)]} {
3889 is_descendent $id
3891 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3892 set isbold 1
3894 } elseif {$highlight_related eq [mc "Ancestor"] ||
3895 $highlight_related eq [mc "Not ancestor"]} {
3896 if {![info exists ancestor($id)]} {
3897 is_ancestor $id
3899 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
3900 set isbold 1
3903 if {[info exists iddrawn($id)]} {
3904 if {$isbold && ![ishighlighted $id]} {
3905 bolden $row mainfontbold
3908 set rhighlights($id) $isbold
3911 # Graph layout functions
3913 proc shortids {ids} {
3914 set res {}
3915 foreach id $ids {
3916 if {[llength $id] > 1} {
3917 lappend res [shortids $id]
3918 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3919 lappend res [string range $id 0 7]
3920 } else {
3921 lappend res $id
3924 return $res
3927 proc ntimes {n o} {
3928 set ret {}
3929 set o [list $o]
3930 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3931 if {($n & $mask) != 0} {
3932 set ret [concat $ret $o]
3934 set o [concat $o $o]
3936 return $ret
3939 proc ordertoken {id} {
3940 global ordertok curview varcid varcstart varctok curview parents children
3941 global nullid nullid2
3943 if {[info exists ordertok($id)]} {
3944 return $ordertok($id)
3946 set origid $id
3947 set todo {}
3948 while {1} {
3949 if {[info exists varcid($curview,$id)]} {
3950 set a $varcid($curview,$id)
3951 set p [lindex $varcstart($curview) $a]
3952 } else {
3953 set p [lindex $children($curview,$id) 0]
3955 if {[info exists ordertok($p)]} {
3956 set tok $ordertok($p)
3957 break
3959 set id [first_real_child $curview,$p]
3960 if {$id eq {}} {
3961 # it's a root
3962 set tok [lindex $varctok($curview) $varcid($curview,$p)]
3963 break
3965 if {[llength $parents($curview,$id)] == 1} {
3966 lappend todo [list $p {}]
3967 } else {
3968 set j [lsearch -exact $parents($curview,$id) $p]
3969 if {$j < 0} {
3970 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3972 lappend todo [list $p [strrep $j]]
3975 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3976 set p [lindex $todo $i 0]
3977 append tok [lindex $todo $i 1]
3978 set ordertok($p) $tok
3980 set ordertok($origid) $tok
3981 return $tok
3984 # Work out where id should go in idlist so that order-token
3985 # values increase from left to right
3986 proc idcol {idlist id {i 0}} {
3987 set t [ordertoken $id]
3988 if {$i < 0} {
3989 set i 0
3991 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3992 if {$i > [llength $idlist]} {
3993 set i [llength $idlist]
3995 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3996 incr i
3997 } else {
3998 if {$t > [ordertoken [lindex $idlist $i]]} {
3999 while {[incr i] < [llength $idlist] &&
4000 $t >= [ordertoken [lindex $idlist $i]]} {}
4003 return $i
4006 proc initlayout {} {
4007 global rowidlist rowisopt rowfinal displayorder parentlist
4008 global numcommits canvxmax canv
4009 global nextcolor
4010 global colormap rowtextx
4012 set numcommits 0
4013 set displayorder {}
4014 set parentlist {}
4015 set nextcolor 0
4016 set rowidlist {}
4017 set rowisopt {}
4018 set rowfinal {}
4019 set canvxmax [$canv cget -width]
4020 catch {unset colormap}
4021 catch {unset rowtextx}
4022 setcanvscroll
4025 proc setcanvscroll {} {
4026 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4027 global lastscrollset lastscrollrows
4029 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4030 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4031 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4032 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4033 set lastscrollset [clock clicks -milliseconds]
4034 set lastscrollrows $numcommits
4037 proc visiblerows {} {
4038 global canv numcommits linespc
4040 set ymax [lindex [$canv cget -scrollregion] 3]
4041 if {$ymax eq {} || $ymax == 0} return
4042 set f [$canv yview]
4043 set y0 [expr {int([lindex $f 0] * $ymax)}]
4044 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4045 if {$r0 < 0} {
4046 set r0 0
4048 set y1 [expr {int([lindex $f 1] * $ymax)}]
4049 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4050 if {$r1 >= $numcommits} {
4051 set r1 [expr {$numcommits - 1}]
4053 return [list $r0 $r1]
4056 proc layoutmore {} {
4057 global commitidx viewcomplete curview
4058 global numcommits pending_select curview
4059 global lastscrollset lastscrollrows commitinterest
4061 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4062 [clock clicks -milliseconds] - $lastscrollset > 500} {
4063 setcanvscroll
4065 if {[info exists pending_select] &&
4066 [commitinview $pending_select $curview]} {
4067 update
4068 selectline [rowofcommit $pending_select] 1
4070 drawvisible
4073 proc doshowlocalchanges {} {
4074 global curview mainheadid
4076 if {$mainheadid eq {}} return
4077 if {[commitinview $mainheadid $curview]} {
4078 dodiffindex
4079 } else {
4080 lappend commitinterest($mainheadid) {dodiffindex}
4084 proc dohidelocalchanges {} {
4085 global nullid nullid2 lserial curview
4087 if {[commitinview $nullid $curview]} {
4088 removefakerow $nullid
4090 if {[commitinview $nullid2 $curview]} {
4091 removefakerow $nullid2
4093 incr lserial
4096 # spawn off a process to do git diff-index --cached HEAD
4097 proc dodiffindex {} {
4098 global lserial showlocalchanges
4099 global isworktree
4101 if {!$showlocalchanges || !$isworktree} return
4102 incr lserial
4103 set fd [open "|git diff-index --cached HEAD" r]
4104 fconfigure $fd -blocking 0
4105 set i [reg_instance $fd]
4106 filerun $fd [list readdiffindex $fd $lserial $i]
4109 proc readdiffindex {fd serial inst} {
4110 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4112 set isdiff 1
4113 if {[gets $fd line] < 0} {
4114 if {![eof $fd]} {
4115 return 1
4117 set isdiff 0
4119 # we only need to see one line and we don't really care what it says...
4120 stop_instance $inst
4122 if {$serial != $lserial} {
4123 return 0
4126 # now see if there are any local changes not checked in to the index
4127 set fd [open "|git diff-files" r]
4128 fconfigure $fd -blocking 0
4129 set i [reg_instance $fd]
4130 filerun $fd [list readdifffiles $fd $serial $i]
4132 if {$isdiff && ![commitinview $nullid2 $curview]} {
4133 # add the line for the changes in the index to the graph
4134 set hl [mc "Local changes checked in to index but not committed"]
4135 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4136 set commitdata($nullid2) "\n $hl\n"
4137 if {[commitinview $nullid $curview]} {
4138 removefakerow $nullid
4140 insertfakerow $nullid2 $mainheadid
4141 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4142 removefakerow $nullid2
4144 return 0
4147 proc readdifffiles {fd serial inst} {
4148 global mainheadid nullid nullid2 curview
4149 global commitinfo commitdata lserial
4151 set isdiff 1
4152 if {[gets $fd line] < 0} {
4153 if {![eof $fd]} {
4154 return 1
4156 set isdiff 0
4158 # we only need to see one line and we don't really care what it says...
4159 stop_instance $inst
4161 if {$serial != $lserial} {
4162 return 0
4165 if {$isdiff && ![commitinview $nullid $curview]} {
4166 # add the line for the local diff to the graph
4167 set hl [mc "Local uncommitted changes, not checked in to index"]
4168 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4169 set commitdata($nullid) "\n $hl\n"
4170 if {[commitinview $nullid2 $curview]} {
4171 set p $nullid2
4172 } else {
4173 set p $mainheadid
4175 insertfakerow $nullid $p
4176 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4177 removefakerow $nullid
4179 return 0
4182 proc nextuse {id row} {
4183 global curview children
4185 if {[info exists children($curview,$id)]} {
4186 foreach kid $children($curview,$id) {
4187 if {![commitinview $kid $curview]} {
4188 return -1
4190 if {[rowofcommit $kid] > $row} {
4191 return [rowofcommit $kid]
4195 if {[commitinview $id $curview]} {
4196 return [rowofcommit $id]
4198 return -1
4201 proc prevuse {id row} {
4202 global curview children
4204 set ret -1
4205 if {[info exists children($curview,$id)]} {
4206 foreach kid $children($curview,$id) {
4207 if {![commitinview $kid $curview]} break
4208 if {[rowofcommit $kid] < $row} {
4209 set ret [rowofcommit $kid]
4213 return $ret
4216 proc make_idlist {row} {
4217 global displayorder parentlist uparrowlen downarrowlen mingaplen
4218 global commitidx curview children
4220 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4221 if {$r < 0} {
4222 set r 0
4224 set ra [expr {$row - $downarrowlen}]
4225 if {$ra < 0} {
4226 set ra 0
4228 set rb [expr {$row + $uparrowlen}]
4229 if {$rb > $commitidx($curview)} {
4230 set rb $commitidx($curview)
4232 make_disporder $r [expr {$rb + 1}]
4233 set ids {}
4234 for {} {$r < $ra} {incr r} {
4235 set nextid [lindex $displayorder [expr {$r + 1}]]
4236 foreach p [lindex $parentlist $r] {
4237 if {$p eq $nextid} continue
4238 set rn [nextuse $p $r]
4239 if {$rn >= $row &&
4240 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4241 lappend ids [list [ordertoken $p] $p]
4245 for {} {$r < $row} {incr r} {
4246 set nextid [lindex $displayorder [expr {$r + 1}]]
4247 foreach p [lindex $parentlist $r] {
4248 if {$p eq $nextid} continue
4249 set rn [nextuse $p $r]
4250 if {$rn < 0 || $rn >= $row} {
4251 lappend ids [list [ordertoken $p] $p]
4255 set id [lindex $displayorder $row]
4256 lappend ids [list [ordertoken $id] $id]
4257 while {$r < $rb} {
4258 foreach p [lindex $parentlist $r] {
4259 set firstkid [lindex $children($curview,$p) 0]
4260 if {[rowofcommit $firstkid] < $row} {
4261 lappend ids [list [ordertoken $p] $p]
4264 incr r
4265 set id [lindex $displayorder $r]
4266 if {$id ne {}} {
4267 set firstkid [lindex $children($curview,$id) 0]
4268 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4269 lappend ids [list [ordertoken $id] $id]
4273 set idlist {}
4274 foreach idx [lsort -unique $ids] {
4275 lappend idlist [lindex $idx 1]
4277 return $idlist
4280 proc rowsequal {a b} {
4281 while {[set i [lsearch -exact $a {}]] >= 0} {
4282 set a [lreplace $a $i $i]
4284 while {[set i [lsearch -exact $b {}]] >= 0} {
4285 set b [lreplace $b $i $i]
4287 return [expr {$a eq $b}]
4290 proc makeupline {id row rend col} {
4291 global rowidlist uparrowlen downarrowlen mingaplen
4293 for {set r $rend} {1} {set r $rstart} {
4294 set rstart [prevuse $id $r]
4295 if {$rstart < 0} return
4296 if {$rstart < $row} break
4298 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4299 set rstart [expr {$rend - $uparrowlen - 1}]
4301 for {set r $rstart} {[incr r] <= $row} {} {
4302 set idlist [lindex $rowidlist $r]
4303 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4304 set col [idcol $idlist $id $col]
4305 lset rowidlist $r [linsert $idlist $col $id]
4306 changedrow $r
4311 proc layoutrows {row endrow} {
4312 global rowidlist rowisopt rowfinal displayorder
4313 global uparrowlen downarrowlen maxwidth mingaplen
4314 global children parentlist
4315 global commitidx viewcomplete curview
4317 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4318 set idlist {}
4319 if {$row > 0} {
4320 set rm1 [expr {$row - 1}]
4321 foreach id [lindex $rowidlist $rm1] {
4322 if {$id ne {}} {
4323 lappend idlist $id
4326 set final [lindex $rowfinal $rm1]
4328 for {} {$row < $endrow} {incr row} {
4329 set rm1 [expr {$row - 1}]
4330 if {$rm1 < 0 || $idlist eq {}} {
4331 set idlist [make_idlist $row]
4332 set final 1
4333 } else {
4334 set id [lindex $displayorder $rm1]
4335 set col [lsearch -exact $idlist $id]
4336 set idlist [lreplace $idlist $col $col]
4337 foreach p [lindex $parentlist $rm1] {
4338 if {[lsearch -exact $idlist $p] < 0} {
4339 set col [idcol $idlist $p $col]
4340 set idlist [linsert $idlist $col $p]
4341 # if not the first child, we have to insert a line going up
4342 if {$id ne [lindex $children($curview,$p) 0]} {
4343 makeupline $p $rm1 $row $col
4347 set id [lindex $displayorder $row]
4348 if {$row > $downarrowlen} {
4349 set termrow [expr {$row - $downarrowlen - 1}]
4350 foreach p [lindex $parentlist $termrow] {
4351 set i [lsearch -exact $idlist $p]
4352 if {$i < 0} continue
4353 set nr [nextuse $p $termrow]
4354 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4355 set idlist [lreplace $idlist $i $i]
4359 set col [lsearch -exact $idlist $id]
4360 if {$col < 0} {
4361 set col [idcol $idlist $id]
4362 set idlist [linsert $idlist $col $id]
4363 if {$children($curview,$id) ne {}} {
4364 makeupline $id $rm1 $row $col
4367 set r [expr {$row + $uparrowlen - 1}]
4368 if {$r < $commitidx($curview)} {
4369 set x $col
4370 foreach p [lindex $parentlist $r] {
4371 if {[lsearch -exact $idlist $p] >= 0} continue
4372 set fk [lindex $children($curview,$p) 0]
4373 if {[rowofcommit $fk] < $row} {
4374 set x [idcol $idlist $p $x]
4375 set idlist [linsert $idlist $x $p]
4378 if {[incr r] < $commitidx($curview)} {
4379 set p [lindex $displayorder $r]
4380 if {[lsearch -exact $idlist $p] < 0} {
4381 set fk [lindex $children($curview,$p) 0]
4382 if {$fk ne {} && [rowofcommit $fk] < $row} {
4383 set x [idcol $idlist $p $x]
4384 set idlist [linsert $idlist $x $p]
4390 if {$final && !$viewcomplete($curview) &&
4391 $row + $uparrowlen + $mingaplen + $downarrowlen
4392 >= $commitidx($curview)} {
4393 set final 0
4395 set l [llength $rowidlist]
4396 if {$row == $l} {
4397 lappend rowidlist $idlist
4398 lappend rowisopt 0
4399 lappend rowfinal $final
4400 } elseif {$row < $l} {
4401 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4402 lset rowidlist $row $idlist
4403 changedrow $row
4405 lset rowfinal $row $final
4406 } else {
4407 set pad [ntimes [expr {$row - $l}] {}]
4408 set rowidlist [concat $rowidlist $pad]
4409 lappend rowidlist $idlist
4410 set rowfinal [concat $rowfinal $pad]
4411 lappend rowfinal $final
4412 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4415 return $row
4418 proc changedrow {row} {
4419 global displayorder iddrawn rowisopt need_redisplay
4421 set l [llength $rowisopt]
4422 if {$row < $l} {
4423 lset rowisopt $row 0
4424 if {$row + 1 < $l} {
4425 lset rowisopt [expr {$row + 1}] 0
4426 if {$row + 2 < $l} {
4427 lset rowisopt [expr {$row + 2}] 0
4431 set id [lindex $displayorder $row]
4432 if {[info exists iddrawn($id)]} {
4433 set need_redisplay 1
4437 proc insert_pad {row col npad} {
4438 global rowidlist
4440 set pad [ntimes $npad {}]
4441 set idlist [lindex $rowidlist $row]
4442 set bef [lrange $idlist 0 [expr {$col - 1}]]
4443 set aft [lrange $idlist $col end]
4444 set i [lsearch -exact $aft {}]
4445 if {$i > 0} {
4446 set aft [lreplace $aft $i $i]
4448 lset rowidlist $row [concat $bef $pad $aft]
4449 changedrow $row
4452 proc optimize_rows {row col endrow} {
4453 global rowidlist rowisopt displayorder curview children
4455 if {$row < 1} {
4456 set row 1
4458 for {} {$row < $endrow} {incr row; set col 0} {
4459 if {[lindex $rowisopt $row]} continue
4460 set haspad 0
4461 set y0 [expr {$row - 1}]
4462 set ym [expr {$row - 2}]
4463 set idlist [lindex $rowidlist $row]
4464 set previdlist [lindex $rowidlist $y0]
4465 if {$idlist eq {} || $previdlist eq {}} continue
4466 if {$ym >= 0} {
4467 set pprevidlist [lindex $rowidlist $ym]
4468 if {$pprevidlist eq {}} continue
4469 } else {
4470 set pprevidlist {}
4472 set x0 -1
4473 set xm -1
4474 for {} {$col < [llength $idlist]} {incr col} {
4475 set id [lindex $idlist $col]
4476 if {[lindex $previdlist $col] eq $id} continue
4477 if {$id eq {}} {
4478 set haspad 1
4479 continue
4481 set x0 [lsearch -exact $previdlist $id]
4482 if {$x0 < 0} continue
4483 set z [expr {$x0 - $col}]
4484 set isarrow 0
4485 set z0 {}
4486 if {$ym >= 0} {
4487 set xm [lsearch -exact $pprevidlist $id]
4488 if {$xm >= 0} {
4489 set z0 [expr {$xm - $x0}]
4492 if {$z0 eq {}} {
4493 # if row y0 is the first child of $id then it's not an arrow
4494 if {[lindex $children($curview,$id) 0] ne
4495 [lindex $displayorder $y0]} {
4496 set isarrow 1
4499 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4500 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4501 set isarrow 1
4503 # Looking at lines from this row to the previous row,
4504 # make them go straight up if they end in an arrow on
4505 # the previous row; otherwise make them go straight up
4506 # or at 45 degrees.
4507 if {$z < -1 || ($z < 0 && $isarrow)} {
4508 # Line currently goes left too much;
4509 # insert pads in the previous row, then optimize it
4510 set npad [expr {-1 - $z + $isarrow}]
4511 insert_pad $y0 $x0 $npad
4512 if {$y0 > 0} {
4513 optimize_rows $y0 $x0 $row
4515 set previdlist [lindex $rowidlist $y0]
4516 set x0 [lsearch -exact $previdlist $id]
4517 set z [expr {$x0 - $col}]
4518 if {$z0 ne {}} {
4519 set pprevidlist [lindex $rowidlist $ym]
4520 set xm [lsearch -exact $pprevidlist $id]
4521 set z0 [expr {$xm - $x0}]
4523 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4524 # Line currently goes right too much;
4525 # insert pads in this line
4526 set npad [expr {$z - 1 + $isarrow}]
4527 insert_pad $row $col $npad
4528 set idlist [lindex $rowidlist $row]
4529 incr col $npad
4530 set z [expr {$x0 - $col}]
4531 set haspad 1
4533 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4534 # this line links to its first child on row $row-2
4535 set id [lindex $displayorder $ym]
4536 set xc [lsearch -exact $pprevidlist $id]
4537 if {$xc >= 0} {
4538 set z0 [expr {$xc - $x0}]
4541 # avoid lines jigging left then immediately right
4542 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4543 insert_pad $y0 $x0 1
4544 incr x0
4545 optimize_rows $y0 $x0 $row
4546 set previdlist [lindex $rowidlist $y0]
4549 if {!$haspad} {
4550 # Find the first column that doesn't have a line going right
4551 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4552 set id [lindex $idlist $col]
4553 if {$id eq {}} break
4554 set x0 [lsearch -exact $previdlist $id]
4555 if {$x0 < 0} {
4556 # check if this is the link to the first child
4557 set kid [lindex $displayorder $y0]
4558 if {[lindex $children($curview,$id) 0] eq $kid} {
4559 # it is, work out offset to child
4560 set x0 [lsearch -exact $previdlist $kid]
4563 if {$x0 <= $col} break
4565 # Insert a pad at that column as long as it has a line and
4566 # isn't the last column
4567 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4568 set idlist [linsert $idlist $col {}]
4569 lset rowidlist $row $idlist
4570 changedrow $row
4576 proc xc {row col} {
4577 global canvx0 linespc
4578 return [expr {$canvx0 + $col * $linespc}]
4581 proc yc {row} {
4582 global canvy0 linespc
4583 return [expr {$canvy0 + $row * $linespc}]
4586 proc linewidth {id} {
4587 global thickerline lthickness
4589 set wid $lthickness
4590 if {[info exists thickerline] && $id eq $thickerline} {
4591 set wid [expr {2 * $lthickness}]
4593 return $wid
4596 proc rowranges {id} {
4597 global curview children uparrowlen downarrowlen
4598 global rowidlist
4600 set kids $children($curview,$id)
4601 if {$kids eq {}} {
4602 return {}
4604 set ret {}
4605 lappend kids $id
4606 foreach child $kids {
4607 if {![commitinview $child $curview]} break
4608 set row [rowofcommit $child]
4609 if {![info exists prev]} {
4610 lappend ret [expr {$row + 1}]
4611 } else {
4612 if {$row <= $prevrow} {
4613 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4615 # see if the line extends the whole way from prevrow to row
4616 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4617 [lsearch -exact [lindex $rowidlist \
4618 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4619 # it doesn't, see where it ends
4620 set r [expr {$prevrow + $downarrowlen}]
4621 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4622 while {[incr r -1] > $prevrow &&
4623 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4624 } else {
4625 while {[incr r] <= $row &&
4626 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4627 incr r -1
4629 lappend ret $r
4630 # see where it starts up again
4631 set r [expr {$row - $uparrowlen}]
4632 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4633 while {[incr r] < $row &&
4634 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4635 } else {
4636 while {[incr r -1] >= $prevrow &&
4637 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4638 incr r
4640 lappend ret $r
4643 if {$child eq $id} {
4644 lappend ret $row
4646 set prev $child
4647 set prevrow $row
4649 return $ret
4652 proc drawlineseg {id row endrow arrowlow} {
4653 global rowidlist displayorder iddrawn linesegs
4654 global canv colormap linespc curview maxlinelen parentlist
4656 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4657 set le [expr {$row + 1}]
4658 set arrowhigh 1
4659 while {1} {
4660 set c [lsearch -exact [lindex $rowidlist $le] $id]
4661 if {$c < 0} {
4662 incr le -1
4663 break
4665 lappend cols $c
4666 set x [lindex $displayorder $le]
4667 if {$x eq $id} {
4668 set arrowhigh 0
4669 break
4671 if {[info exists iddrawn($x)] || $le == $endrow} {
4672 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4673 if {$c >= 0} {
4674 lappend cols $c
4675 set arrowhigh 0
4677 break
4679 incr le
4681 if {$le <= $row} {
4682 return $row
4685 set lines {}
4686 set i 0
4687 set joinhigh 0
4688 if {[info exists linesegs($id)]} {
4689 set lines $linesegs($id)
4690 foreach li $lines {
4691 set r0 [lindex $li 0]
4692 if {$r0 > $row} {
4693 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4694 set joinhigh 1
4696 break
4698 incr i
4701 set joinlow 0
4702 if {$i > 0} {
4703 set li [lindex $lines [expr {$i-1}]]
4704 set r1 [lindex $li 1]
4705 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4706 set joinlow 1
4710 set x [lindex $cols [expr {$le - $row}]]
4711 set xp [lindex $cols [expr {$le - 1 - $row}]]
4712 set dir [expr {$xp - $x}]
4713 if {$joinhigh} {
4714 set ith [lindex $lines $i 2]
4715 set coords [$canv coords $ith]
4716 set ah [$canv itemcget $ith -arrow]
4717 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4718 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4719 if {$x2 ne {} && $x - $x2 == $dir} {
4720 set coords [lrange $coords 0 end-2]
4722 } else {
4723 set coords [list [xc $le $x] [yc $le]]
4725 if {$joinlow} {
4726 set itl [lindex $lines [expr {$i-1}] 2]
4727 set al [$canv itemcget $itl -arrow]
4728 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4729 } elseif {$arrowlow} {
4730 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4731 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4732 set arrowlow 0
4735 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4736 for {set y $le} {[incr y -1] > $row} {} {
4737 set x $xp
4738 set xp [lindex $cols [expr {$y - 1 - $row}]]
4739 set ndir [expr {$xp - $x}]
4740 if {$dir != $ndir || $xp < 0} {
4741 lappend coords [xc $y $x] [yc $y]
4743 set dir $ndir
4745 if {!$joinlow} {
4746 if {$xp < 0} {
4747 # join parent line to first child
4748 set ch [lindex $displayorder $row]
4749 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4750 if {$xc < 0} {
4751 puts "oops: drawlineseg: child $ch not on row $row"
4752 } elseif {$xc != $x} {
4753 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4754 set d [expr {int(0.5 * $linespc)}]
4755 set x1 [xc $row $x]
4756 if {$xc < $x} {
4757 set x2 [expr {$x1 - $d}]
4758 } else {
4759 set x2 [expr {$x1 + $d}]
4761 set y2 [yc $row]
4762 set y1 [expr {$y2 + $d}]
4763 lappend coords $x1 $y1 $x2 $y2
4764 } elseif {$xc < $x - 1} {
4765 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4766 } elseif {$xc > $x + 1} {
4767 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4769 set x $xc
4771 lappend coords [xc $row $x] [yc $row]
4772 } else {
4773 set xn [xc $row $xp]
4774 set yn [yc $row]
4775 lappend coords $xn $yn
4777 if {!$joinhigh} {
4778 assigncolor $id
4779 set t [$canv create line $coords -width [linewidth $id] \
4780 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4781 $canv lower $t
4782 bindline $t $id
4783 set lines [linsert $lines $i [list $row $le $t]]
4784 } else {
4785 $canv coords $ith $coords
4786 if {$arrow ne $ah} {
4787 $canv itemconf $ith -arrow $arrow
4789 lset lines $i 0 $row
4791 } else {
4792 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4793 set ndir [expr {$xo - $xp}]
4794 set clow [$canv coords $itl]
4795 if {$dir == $ndir} {
4796 set clow [lrange $clow 2 end]
4798 set coords [concat $coords $clow]
4799 if {!$joinhigh} {
4800 lset lines [expr {$i-1}] 1 $le
4801 } else {
4802 # coalesce two pieces
4803 $canv delete $ith
4804 set b [lindex $lines [expr {$i-1}] 0]
4805 set e [lindex $lines $i 1]
4806 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4808 $canv coords $itl $coords
4809 if {$arrow ne $al} {
4810 $canv itemconf $itl -arrow $arrow
4814 set linesegs($id) $lines
4815 return $le
4818 proc drawparentlinks {id row} {
4819 global rowidlist canv colormap curview parentlist
4820 global idpos linespc
4822 set rowids [lindex $rowidlist $row]
4823 set col [lsearch -exact $rowids $id]
4824 if {$col < 0} return
4825 set olds [lindex $parentlist $row]
4826 set row2 [expr {$row + 1}]
4827 set x [xc $row $col]
4828 set y [yc $row]
4829 set y2 [yc $row2]
4830 set d [expr {int(0.5 * $linespc)}]
4831 set ymid [expr {$y + $d}]
4832 set ids [lindex $rowidlist $row2]
4833 # rmx = right-most X coord used
4834 set rmx 0
4835 foreach p $olds {
4836 set i [lsearch -exact $ids $p]
4837 if {$i < 0} {
4838 puts "oops, parent $p of $id not in list"
4839 continue
4841 set x2 [xc $row2 $i]
4842 if {$x2 > $rmx} {
4843 set rmx $x2
4845 set j [lsearch -exact $rowids $p]
4846 if {$j < 0} {
4847 # drawlineseg will do this one for us
4848 continue
4850 assigncolor $p
4851 # should handle duplicated parents here...
4852 set coords [list $x $y]
4853 if {$i != $col} {
4854 # if attaching to a vertical segment, draw a smaller
4855 # slant for visual distinctness
4856 if {$i == $j} {
4857 if {$i < $col} {
4858 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4859 } else {
4860 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4862 } elseif {$i < $col && $i < $j} {
4863 # segment slants towards us already
4864 lappend coords [xc $row $j] $y
4865 } else {
4866 if {$i < $col - 1} {
4867 lappend coords [expr {$x2 + $linespc}] $y
4868 } elseif {$i > $col + 1} {
4869 lappend coords [expr {$x2 - $linespc}] $y
4871 lappend coords $x2 $y2
4873 } else {
4874 lappend coords $x2 $y2
4876 set t [$canv create line $coords -width [linewidth $p] \
4877 -fill $colormap($p) -tags lines.$p]
4878 $canv lower $t
4879 bindline $t $p
4881 if {$rmx > [lindex $idpos($id) 1]} {
4882 lset idpos($id) 1 $rmx
4883 redrawtags $id
4887 proc drawlines {id} {
4888 global canv
4890 $canv itemconf lines.$id -width [linewidth $id]
4893 proc drawcmittext {id row col} {
4894 global linespc canv canv2 canv3 fgcolor curview
4895 global cmitlisted commitinfo rowidlist parentlist
4896 global rowtextx idpos idtags idheads idotherrefs
4897 global linehtag linentag linedtag selectedline
4898 global canvxmax boldrows boldnamerows fgcolor
4899 global mainheadid nullid nullid2 circleitem circlecolors
4901 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
4902 set listed $cmitlisted($curview,$id)
4903 if {$id eq $nullid} {
4904 set ofill red
4905 } elseif {$id eq $nullid2} {
4906 set ofill green
4907 } elseif {$id eq $mainheadid} {
4908 set ofill yellow
4909 } else {
4910 set ofill [lindex $circlecolors $listed]
4912 set x [xc $row $col]
4913 set y [yc $row]
4914 set orad [expr {$linespc / 3}]
4915 if {$listed <= 2} {
4916 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4917 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4918 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4919 } elseif {$listed == 3} {
4920 # triangle pointing left for left-side commits
4921 set t [$canv create polygon \
4922 [expr {$x - $orad}] $y \
4923 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4924 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4925 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4926 } else {
4927 # triangle pointing right for right-side commits
4928 set t [$canv create polygon \
4929 [expr {$x + $orad - 1}] $y \
4930 [expr {$x - $orad}] [expr {$y - $orad}] \
4931 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4932 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4934 set circleitem($row) $t
4935 $canv raise $t
4936 $canv bind $t <1> {selcanvline {} %x %y}
4937 set rmx [llength [lindex $rowidlist $row]]
4938 set olds [lindex $parentlist $row]
4939 if {$olds ne {}} {
4940 set nextids [lindex $rowidlist [expr {$row + 1}]]
4941 foreach p $olds {
4942 set i [lsearch -exact $nextids $p]
4943 if {$i > $rmx} {
4944 set rmx $i
4948 set xt [xc $row $rmx]
4949 set rowtextx($row) $xt
4950 set idpos($id) [list $x $xt $y]
4951 if {[info exists idtags($id)] || [info exists idheads($id)]
4952 || [info exists idotherrefs($id)]} {
4953 set xt [drawtags $id $x $xt $y]
4955 set headline [lindex $commitinfo($id) 0]
4956 set name [lindex $commitinfo($id) 1]
4957 set date [lindex $commitinfo($id) 2]
4958 set date [formatdate $date]
4959 set font mainfont
4960 set nfont mainfont
4961 set isbold [ishighlighted $id]
4962 if {$isbold > 0} {
4963 lappend boldrows $row
4964 set font mainfontbold
4965 if {$isbold > 1} {
4966 lappend boldnamerows $row
4967 set nfont mainfontbold
4970 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4971 -text $headline -font $font -tags text]
4972 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4973 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4974 -text $name -font $nfont -tags text]
4975 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4976 -text $date -font mainfont -tags text]
4977 if {$selectedline == $row} {
4978 make_secsel $row
4980 set xr [expr {$xt + [font measure $font $headline]}]
4981 if {$xr > $canvxmax} {
4982 set canvxmax $xr
4983 setcanvscroll
4987 proc drawcmitrow {row} {
4988 global displayorder rowidlist nrows_drawn
4989 global iddrawn markingmatches
4990 global commitinfo numcommits
4991 global filehighlight fhighlights findpattern nhighlights
4992 global hlview vhighlights
4993 global highlight_related rhighlights
4995 if {$row >= $numcommits} return
4997 set id [lindex $displayorder $row]
4998 if {[info exists hlview] && ![info exists vhighlights($id)]} {
4999 askvhighlight $row $id
5001 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5002 askfilehighlight $row $id
5004 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5005 askfindhighlight $row $id
5007 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5008 askrelhighlight $row $id
5010 if {![info exists iddrawn($id)]} {
5011 set col [lsearch -exact [lindex $rowidlist $row] $id]
5012 if {$col < 0} {
5013 puts "oops, row $row id $id not in list"
5014 return
5016 if {![info exists commitinfo($id)]} {
5017 getcommit $id
5019 assigncolor $id
5020 drawcmittext $id $row $col
5021 set iddrawn($id) 1
5022 incr nrows_drawn
5024 if {$markingmatches} {
5025 markrowmatches $row $id
5029 proc drawcommits {row {endrow {}}} {
5030 global numcommits iddrawn displayorder curview need_redisplay
5031 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5033 if {$row < 0} {
5034 set row 0
5036 if {$endrow eq {}} {
5037 set endrow $row
5039 if {$endrow >= $numcommits} {
5040 set endrow [expr {$numcommits - 1}]
5043 set rl1 [expr {$row - $downarrowlen - 3}]
5044 if {$rl1 < 0} {
5045 set rl1 0
5047 set ro1 [expr {$row - 3}]
5048 if {$ro1 < 0} {
5049 set ro1 0
5051 set r2 [expr {$endrow + $uparrowlen + 3}]
5052 if {$r2 > $numcommits} {
5053 set r2 $numcommits
5055 for {set r $rl1} {$r < $r2} {incr r} {
5056 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5057 if {$rl1 < $r} {
5058 layoutrows $rl1 $r
5060 set rl1 [expr {$r + 1}]
5063 if {$rl1 < $r} {
5064 layoutrows $rl1 $r
5066 optimize_rows $ro1 0 $r2
5067 if {$need_redisplay || $nrows_drawn > 2000} {
5068 clear_display
5069 drawvisible
5072 # make the lines join to already-drawn rows either side
5073 set r [expr {$row - 1}]
5074 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5075 set r $row
5077 set er [expr {$endrow + 1}]
5078 if {$er >= $numcommits ||
5079 ![info exists iddrawn([lindex $displayorder $er])]} {
5080 set er $endrow
5082 for {} {$r <= $er} {incr r} {
5083 set id [lindex $displayorder $r]
5084 set wasdrawn [info exists iddrawn($id)]
5085 drawcmitrow $r
5086 if {$r == $er} break
5087 set nextid [lindex $displayorder [expr {$r + 1}]]
5088 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5089 drawparentlinks $id $r
5091 set rowids [lindex $rowidlist $r]
5092 foreach lid $rowids {
5093 if {$lid eq {}} continue
5094 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5095 if {$lid eq $id} {
5096 # see if this is the first child of any of its parents
5097 foreach p [lindex $parentlist $r] {
5098 if {[lsearch -exact $rowids $p] < 0} {
5099 # make this line extend up to the child
5100 set lineend($p) [drawlineseg $p $r $er 0]
5103 } else {
5104 set lineend($lid) [drawlineseg $lid $r $er 1]
5110 proc undolayout {row} {
5111 global uparrowlen mingaplen downarrowlen
5112 global rowidlist rowisopt rowfinal need_redisplay
5114 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5115 if {$r < 0} {
5116 set r 0
5118 if {[llength $rowidlist] > $r} {
5119 incr r -1
5120 set rowidlist [lrange $rowidlist 0 $r]
5121 set rowfinal [lrange $rowfinal 0 $r]
5122 set rowisopt [lrange $rowisopt 0 $r]
5123 set need_redisplay 1
5124 run drawvisible
5128 proc drawvisible {} {
5129 global canv linespc curview vrowmod selectedline targetrow targetid
5130 global need_redisplay cscroll numcommits
5132 set fs [$canv yview]
5133 set ymax [lindex [$canv cget -scrollregion] 3]
5134 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5135 set f0 [lindex $fs 0]
5136 set f1 [lindex $fs 1]
5137 set y0 [expr {int($f0 * $ymax)}]
5138 set y1 [expr {int($f1 * $ymax)}]
5140 if {[info exists targetid]} {
5141 if {[commitinview $targetid $curview]} {
5142 set r [rowofcommit $targetid]
5143 if {$r != $targetrow} {
5144 # Fix up the scrollregion and change the scrolling position
5145 # now that our target row has moved.
5146 set diff [expr {($r - $targetrow) * $linespc}]
5147 set targetrow $r
5148 setcanvscroll
5149 set ymax [lindex [$canv cget -scrollregion] 3]
5150 incr y0 $diff
5151 incr y1 $diff
5152 set f0 [expr {$y0 / $ymax}]
5153 set f1 [expr {$y1 / $ymax}]
5154 allcanvs yview moveto $f0
5155 $cscroll set $f0 $f1
5156 set need_redisplay 1
5158 } else {
5159 unset targetid
5163 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5164 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5165 if {$endrow >= $vrowmod($curview)} {
5166 update_arcrows $curview
5168 if {$selectedline ne {} &&
5169 $row <= $selectedline && $selectedline <= $endrow} {
5170 set targetrow $selectedline
5171 } elseif {[info exists targetid]} {
5172 set targetrow [expr {int(($row + $endrow) / 2)}]
5174 if {[info exists targetrow]} {
5175 if {$targetrow >= $numcommits} {
5176 set targetrow [expr {$numcommits - 1}]
5178 set targetid [commitonrow $targetrow]
5180 drawcommits $row $endrow
5183 proc clear_display {} {
5184 global iddrawn linesegs need_redisplay nrows_drawn
5185 global vhighlights fhighlights nhighlights rhighlights
5186 global linehtag linentag linedtag boldrows boldnamerows
5188 allcanvs delete all
5189 catch {unset iddrawn}
5190 catch {unset linesegs}
5191 catch {unset linehtag}
5192 catch {unset linentag}
5193 catch {unset linedtag}
5194 set boldrows {}
5195 set boldnamerows {}
5196 catch {unset vhighlights}
5197 catch {unset fhighlights}
5198 catch {unset nhighlights}
5199 catch {unset rhighlights}
5200 set need_redisplay 0
5201 set nrows_drawn 0
5204 proc findcrossings {id} {
5205 global rowidlist parentlist numcommits displayorder
5207 set cross {}
5208 set ccross {}
5209 foreach {s e} [rowranges $id] {
5210 if {$e >= $numcommits} {
5211 set e [expr {$numcommits - 1}]
5213 if {$e <= $s} continue
5214 for {set row $e} {[incr row -1] >= $s} {} {
5215 set x [lsearch -exact [lindex $rowidlist $row] $id]
5216 if {$x < 0} break
5217 set olds [lindex $parentlist $row]
5218 set kid [lindex $displayorder $row]
5219 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5220 if {$kidx < 0} continue
5221 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5222 foreach p $olds {
5223 set px [lsearch -exact $nextrow $p]
5224 if {$px < 0} continue
5225 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5226 if {[lsearch -exact $ccross $p] >= 0} continue
5227 if {$x == $px + ($kidx < $px? -1: 1)} {
5228 lappend ccross $p
5229 } elseif {[lsearch -exact $cross $p] < 0} {
5230 lappend cross $p
5236 return [concat $ccross {{}} $cross]
5239 proc assigncolor {id} {
5240 global colormap colors nextcolor
5241 global parents children children curview
5243 if {[info exists colormap($id)]} return
5244 set ncolors [llength $colors]
5245 if {[info exists children($curview,$id)]} {
5246 set kids $children($curview,$id)
5247 } else {
5248 set kids {}
5250 if {[llength $kids] == 1} {
5251 set child [lindex $kids 0]
5252 if {[info exists colormap($child)]
5253 && [llength $parents($curview,$child)] == 1} {
5254 set colormap($id) $colormap($child)
5255 return
5258 set badcolors {}
5259 set origbad {}
5260 foreach x [findcrossings $id] {
5261 if {$x eq {}} {
5262 # delimiter between corner crossings and other crossings
5263 if {[llength $badcolors] >= $ncolors - 1} break
5264 set origbad $badcolors
5266 if {[info exists colormap($x)]
5267 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5268 lappend badcolors $colormap($x)
5271 if {[llength $badcolors] >= $ncolors} {
5272 set badcolors $origbad
5274 set origbad $badcolors
5275 if {[llength $badcolors] < $ncolors - 1} {
5276 foreach child $kids {
5277 if {[info exists colormap($child)]
5278 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5279 lappend badcolors $colormap($child)
5281 foreach p $parents($curview,$child) {
5282 if {[info exists colormap($p)]
5283 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5284 lappend badcolors $colormap($p)
5288 if {[llength $badcolors] >= $ncolors} {
5289 set badcolors $origbad
5292 for {set i 0} {$i <= $ncolors} {incr i} {
5293 set c [lindex $colors $nextcolor]
5294 if {[incr nextcolor] >= $ncolors} {
5295 set nextcolor 0
5297 if {[lsearch -exact $badcolors $c]} break
5299 set colormap($id) $c
5302 proc bindline {t id} {
5303 global canv
5305 $canv bind $t <Enter> "lineenter %x %y $id"
5306 $canv bind $t <Motion> "linemotion %x %y $id"
5307 $canv bind $t <Leave> "lineleave $id"
5308 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5311 proc drawtags {id x xt y1} {
5312 global idtags idheads idotherrefs mainhead
5313 global linespc lthickness
5314 global canv rowtextx curview fgcolor bgcolor
5316 set marks {}
5317 set ntags 0
5318 set nheads 0
5319 if {[info exists idtags($id)]} {
5320 set marks $idtags($id)
5321 set ntags [llength $marks]
5323 if {[info exists idheads($id)]} {
5324 set marks [concat $marks $idheads($id)]
5325 set nheads [llength $idheads($id)]
5327 if {[info exists idotherrefs($id)]} {
5328 set marks [concat $marks $idotherrefs($id)]
5330 if {$marks eq {}} {
5331 return $xt
5334 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5335 set yt [expr {$y1 - 0.5 * $linespc}]
5336 set yb [expr {$yt + $linespc - 1}]
5337 set xvals {}
5338 set wvals {}
5339 set i -1
5340 foreach tag $marks {
5341 incr i
5342 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5343 set wid [font measure mainfontbold $tag]
5344 } else {
5345 set wid [font measure mainfont $tag]
5347 lappend xvals $xt
5348 lappend wvals $wid
5349 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5351 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5352 -width $lthickness -fill black -tags tag.$id]
5353 $canv lower $t
5354 foreach tag $marks x $xvals wid $wvals {
5355 set xl [expr {$x + $delta}]
5356 set xr [expr {$x + $delta + $wid + $lthickness}]
5357 set font mainfont
5358 if {[incr ntags -1] >= 0} {
5359 # draw a tag
5360 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5361 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5362 -width 1 -outline black -fill yellow -tags tag.$id]
5363 $canv bind $t <1> [list showtag $tag 1]
5364 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5365 } else {
5366 # draw a head or other ref
5367 if {[incr nheads -1] >= 0} {
5368 set col green
5369 if {$tag eq $mainhead} {
5370 set font mainfontbold
5372 } else {
5373 set col "#ddddff"
5375 set xl [expr {$xl - $delta/2}]
5376 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5377 -width 1 -outline black -fill $col -tags tag.$id
5378 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5379 set rwid [font measure mainfont $remoteprefix]
5380 set xi [expr {$x + 1}]
5381 set yti [expr {$yt + 1}]
5382 set xri [expr {$x + $rwid}]
5383 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5384 -width 0 -fill "#ffddaa" -tags tag.$id
5387 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5388 -font $font -tags [list tag.$id text]]
5389 if {$ntags >= 0} {
5390 $canv bind $t <1> [list showtag $tag 1]
5391 } elseif {$nheads >= 0} {
5392 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
5395 return $xt
5398 proc xcoord {i level ln} {
5399 global canvx0 xspc1 xspc2
5401 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5402 if {$i > 0 && $i == $level} {
5403 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5404 } elseif {$i > $level} {
5405 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5407 return $x
5410 proc show_status {msg} {
5411 global canv fgcolor
5413 clear_display
5414 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5415 -tags text -fill $fgcolor
5418 # Don't change the text pane cursor if it is currently the hand cursor,
5419 # showing that we are over a sha1 ID link.
5420 proc settextcursor {c} {
5421 global ctext curtextcursor
5423 if {[$ctext cget -cursor] == $curtextcursor} {
5424 $ctext config -cursor $c
5426 set curtextcursor $c
5429 proc nowbusy {what {name {}}} {
5430 global isbusy busyname statusw
5432 if {[array names isbusy] eq {}} {
5433 . config -cursor watch
5434 settextcursor watch
5436 set isbusy($what) 1
5437 set busyname($what) $name
5438 if {$name ne {}} {
5439 $statusw conf -text $name
5443 proc notbusy {what} {
5444 global isbusy maincursor textcursor busyname statusw
5446 catch {
5447 unset isbusy($what)
5448 if {$busyname($what) ne {} &&
5449 [$statusw cget -text] eq $busyname($what)} {
5450 $statusw conf -text {}
5453 if {[array names isbusy] eq {}} {
5454 . config -cursor $maincursor
5455 settextcursor $textcursor
5459 proc findmatches {f} {
5460 global findtype findstring
5461 if {$findtype == [mc "Regexp"]} {
5462 set matches [regexp -indices -all -inline $findstring $f]
5463 } else {
5464 set fs $findstring
5465 if {$findtype == [mc "IgnCase"]} {
5466 set f [string tolower $f]
5467 set fs [string tolower $fs]
5469 set matches {}
5470 set i 0
5471 set l [string length $fs]
5472 while {[set j [string first $fs $f $i]] >= 0} {
5473 lappend matches [list $j [expr {$j+$l-1}]]
5474 set i [expr {$j + $l}]
5477 return $matches
5480 proc dofind {{dirn 1} {wrap 1}} {
5481 global findstring findstartline findcurline selectedline numcommits
5482 global gdttype filehighlight fh_serial find_dirn findallowwrap
5484 if {[info exists find_dirn]} {
5485 if {$find_dirn == $dirn} return
5486 stopfinding
5488 focus .
5489 if {$findstring eq {} || $numcommits == 0} return
5490 if {$selectedline eq {}} {
5491 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5492 } else {
5493 set findstartline $selectedline
5495 set findcurline $findstartline
5496 nowbusy finding [mc "Searching"]
5497 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5498 after cancel do_file_hl $fh_serial
5499 do_file_hl $fh_serial
5501 set find_dirn $dirn
5502 set findallowwrap $wrap
5503 run findmore
5506 proc stopfinding {} {
5507 global find_dirn findcurline fprogcoord
5509 if {[info exists find_dirn]} {
5510 unset find_dirn
5511 unset findcurline
5512 notbusy finding
5513 set fprogcoord 0
5514 adjustprogress
5518 proc findmore {} {
5519 global commitdata commitinfo numcommits findpattern findloc
5520 global findstartline findcurline findallowwrap
5521 global find_dirn gdttype fhighlights fprogcoord
5522 global curview varcorder vrownum varccommits vrowmod
5524 if {![info exists find_dirn]} {
5525 return 0
5527 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5528 set l $findcurline
5529 set moretodo 0
5530 if {$find_dirn > 0} {
5531 incr l
5532 if {$l >= $numcommits} {
5533 set l 0
5535 if {$l <= $findstartline} {
5536 set lim [expr {$findstartline + 1}]
5537 } else {
5538 set lim $numcommits
5539 set moretodo $findallowwrap
5541 } else {
5542 if {$l == 0} {
5543 set l $numcommits
5545 incr l -1
5546 if {$l >= $findstartline} {
5547 set lim [expr {$findstartline - 1}]
5548 } else {
5549 set lim -1
5550 set moretodo $findallowwrap
5553 set n [expr {($lim - $l) * $find_dirn}]
5554 if {$n > 500} {
5555 set n 500
5556 set moretodo 1
5558 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5559 update_arcrows $curview
5561 set found 0
5562 set domore 1
5563 set ai [bsearch $vrownum($curview) $l]
5564 set a [lindex $varcorder($curview) $ai]
5565 set arow [lindex $vrownum($curview) $ai]
5566 set ids [lindex $varccommits($curview,$a)]
5567 set arowend [expr {$arow + [llength $ids]}]
5568 if {$gdttype eq [mc "containing:"]} {
5569 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5570 if {$l < $arow || $l >= $arowend} {
5571 incr ai $find_dirn
5572 set a [lindex $varcorder($curview) $ai]
5573 set arow [lindex $vrownum($curview) $ai]
5574 set ids [lindex $varccommits($curview,$a)]
5575 set arowend [expr {$arow + [llength $ids]}]
5577 set id [lindex $ids [expr {$l - $arow}]]
5578 # shouldn't happen unless git log doesn't give all the commits...
5579 if {![info exists commitdata($id)] ||
5580 ![doesmatch $commitdata($id)]} {
5581 continue
5583 if {![info exists commitinfo($id)]} {
5584 getcommit $id
5586 set info $commitinfo($id)
5587 foreach f $info ty $fldtypes {
5588 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5589 [doesmatch $f]} {
5590 set found 1
5591 break
5594 if {$found} break
5596 } else {
5597 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5598 if {$l < $arow || $l >= $arowend} {
5599 incr ai $find_dirn
5600 set a [lindex $varcorder($curview) $ai]
5601 set arow [lindex $vrownum($curview) $ai]
5602 set ids [lindex $varccommits($curview,$a)]
5603 set arowend [expr {$arow + [llength $ids]}]
5605 set id [lindex $ids [expr {$l - $arow}]]
5606 if {![info exists fhighlights($id)]} {
5607 # this sets fhighlights($id) to -1
5608 askfilehighlight $l $id
5610 if {$fhighlights($id) > 0} {
5611 set found $domore
5612 break
5614 if {$fhighlights($id) < 0} {
5615 if {$domore} {
5616 set domore 0
5617 set findcurline [expr {$l - $find_dirn}]
5622 if {$found || ($domore && !$moretodo)} {
5623 unset findcurline
5624 unset find_dirn
5625 notbusy finding
5626 set fprogcoord 0
5627 adjustprogress
5628 if {$found} {
5629 findselectline $l
5630 } else {
5631 bell
5633 return 0
5635 if {!$domore} {
5636 flushhighlights
5637 } else {
5638 set findcurline [expr {$l - $find_dirn}]
5640 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5641 if {$n < 0} {
5642 incr n $numcommits
5644 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5645 adjustprogress
5646 return $domore
5649 proc findselectline {l} {
5650 global findloc commentend ctext findcurline markingmatches gdttype
5652 set markingmatches 1
5653 set findcurline $l
5654 selectline $l 1
5655 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5656 # highlight the matches in the comments
5657 set f [$ctext get 1.0 $commentend]
5658 set matches [findmatches $f]
5659 foreach match $matches {
5660 set start [lindex $match 0]
5661 set end [expr {[lindex $match 1] + 1}]
5662 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5665 drawvisible
5668 # mark the bits of a headline or author that match a find string
5669 proc markmatches {canv l str tag matches font row} {
5670 global selectedline
5672 set bbox [$canv bbox $tag]
5673 set x0 [lindex $bbox 0]
5674 set y0 [lindex $bbox 1]
5675 set y1 [lindex $bbox 3]
5676 foreach match $matches {
5677 set start [lindex $match 0]
5678 set end [lindex $match 1]
5679 if {$start > $end} continue
5680 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5681 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5682 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5683 [expr {$x0+$xlen+2}] $y1 \
5684 -outline {} -tags [list match$l matches] -fill yellow]
5685 $canv lower $t
5686 if {$row == $selectedline} {
5687 $canv raise $t secsel
5692 proc unmarkmatches {} {
5693 global markingmatches
5695 allcanvs delete matches
5696 set markingmatches 0
5697 stopfinding
5700 proc selcanvline {w x y} {
5701 global canv canvy0 ctext linespc
5702 global rowtextx
5703 set ymax [lindex [$canv cget -scrollregion] 3]
5704 if {$ymax == {}} return
5705 set yfrac [lindex [$canv yview] 0]
5706 set y [expr {$y + $yfrac * $ymax}]
5707 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5708 if {$l < 0} {
5709 set l 0
5711 if {$w eq $canv} {
5712 set xmax [lindex [$canv cget -scrollregion] 2]
5713 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5714 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5716 unmarkmatches
5717 selectline $l 1
5720 proc commit_descriptor {p} {
5721 global commitinfo
5722 if {![info exists commitinfo($p)]} {
5723 getcommit $p
5725 set l "..."
5726 if {[llength $commitinfo($p)] > 1} {
5727 set l [lindex $commitinfo($p) 0]
5729 return "$p ($l)\n"
5732 # append some text to the ctext widget, and make any SHA1 ID
5733 # that we know about be a clickable link.
5734 proc appendwithlinks {text tags} {
5735 global ctext linknum curview pendinglinks
5737 set start [$ctext index "end - 1c"]
5738 $ctext insert end $text $tags
5739 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5740 foreach l $links {
5741 set s [lindex $l 0]
5742 set e [lindex $l 1]
5743 set linkid [string range $text $s $e]
5744 incr e
5745 $ctext tag delete link$linknum
5746 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5747 setlink $linkid link$linknum
5748 incr linknum
5752 proc setlink {id lk} {
5753 global curview ctext pendinglinks commitinterest
5755 if {[commitinview $id $curview]} {
5756 $ctext tag conf $lk -foreground blue -underline 1
5757 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5758 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5759 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5760 } else {
5761 lappend pendinglinks($id) $lk
5762 lappend commitinterest($id) {makelink %I}
5766 proc makelink {id} {
5767 global pendinglinks
5769 if {![info exists pendinglinks($id)]} return
5770 foreach lk $pendinglinks($id) {
5771 setlink $id $lk
5773 unset pendinglinks($id)
5776 proc linkcursor {w inc} {
5777 global linkentercount curtextcursor
5779 if {[incr linkentercount $inc] > 0} {
5780 $w configure -cursor hand2
5781 } else {
5782 $w configure -cursor $curtextcursor
5783 if {$linkentercount < 0} {
5784 set linkentercount 0
5789 proc viewnextline {dir} {
5790 global canv linespc
5792 $canv delete hover
5793 set ymax [lindex [$canv cget -scrollregion] 3]
5794 set wnow [$canv yview]
5795 set wtop [expr {[lindex $wnow 0] * $ymax}]
5796 set newtop [expr {$wtop + $dir * $linespc}]
5797 if {$newtop < 0} {
5798 set newtop 0
5799 } elseif {$newtop > $ymax} {
5800 set newtop $ymax
5802 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5805 # add a list of tag or branch names at position pos
5806 # returns the number of names inserted
5807 proc appendrefs {pos ids var} {
5808 global ctext linknum curview $var maxrefs
5810 if {[catch {$ctext index $pos}]} {
5811 return 0
5813 $ctext conf -state normal
5814 $ctext delete $pos "$pos lineend"
5815 set tags {}
5816 foreach id $ids {
5817 foreach tag [set $var\($id\)] {
5818 lappend tags [list $tag $id]
5821 if {[llength $tags] > $maxrefs} {
5822 $ctext insert $pos "many ([llength $tags])"
5823 } else {
5824 set tags [lsort -index 0 -decreasing $tags]
5825 set sep {}
5826 foreach ti $tags {
5827 set id [lindex $ti 1]
5828 set lk link$linknum
5829 incr linknum
5830 $ctext tag delete $lk
5831 $ctext insert $pos $sep
5832 $ctext insert $pos [lindex $ti 0] $lk
5833 setlink $id $lk
5834 set sep ", "
5837 $ctext conf -state disabled
5838 return [llength $tags]
5841 # called when we have finished computing the nearby tags
5842 proc dispneartags {delay} {
5843 global selectedline currentid showneartags tagphase
5845 if {$selectedline eq {} || !$showneartags} return
5846 after cancel dispnexttag
5847 if {$delay} {
5848 after 200 dispnexttag
5849 set tagphase -1
5850 } else {
5851 after idle dispnexttag
5852 set tagphase 0
5856 proc dispnexttag {} {
5857 global selectedline currentid showneartags tagphase ctext
5859 if {$selectedline eq {} || !$showneartags} return
5860 switch -- $tagphase {
5862 set dtags [desctags $currentid]
5863 if {$dtags ne {}} {
5864 appendrefs precedes $dtags idtags
5868 set atags [anctags $currentid]
5869 if {$atags ne {}} {
5870 appendrefs follows $atags idtags
5874 set dheads [descheads $currentid]
5875 if {$dheads ne {}} {
5876 if {[appendrefs branch $dheads idheads] > 1
5877 && [$ctext get "branch -3c"] eq "h"} {
5878 # turn "Branch" into "Branches"
5879 $ctext conf -state normal
5880 $ctext insert "branch -2c" "es"
5881 $ctext conf -state disabled
5886 if {[incr tagphase] <= 2} {
5887 after idle dispnexttag
5891 proc make_secsel {l} {
5892 global linehtag linentag linedtag canv canv2 canv3
5894 if {![info exists linehtag($l)]} return
5895 $canv delete secsel
5896 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5897 -tags secsel -fill [$canv cget -selectbackground]]
5898 $canv lower $t
5899 $canv2 delete secsel
5900 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5901 -tags secsel -fill [$canv2 cget -selectbackground]]
5902 $canv2 lower $t
5903 $canv3 delete secsel
5904 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5905 -tags secsel -fill [$canv3 cget -selectbackground]]
5906 $canv3 lower $t
5909 proc selectline {l isnew} {
5910 global canv ctext commitinfo selectedline
5911 global canvy0 linespc parents children curview
5912 global currentid sha1entry
5913 global commentend idtags linknum
5914 global mergemax numcommits pending_select
5915 global cmitmode showneartags allcommits
5916 global targetrow targetid lastscrollrows
5917 global autoselect
5919 catch {unset pending_select}
5920 $canv delete hover
5921 normalline
5922 unsel_reflist
5923 stopfinding
5924 if {$l < 0 || $l >= $numcommits} return
5925 set id [commitonrow $l]
5926 set targetid $id
5927 set targetrow $l
5928 set selectedline $l
5929 set currentid $id
5930 if {$lastscrollrows < $numcommits} {
5931 setcanvscroll
5934 set y [expr {$canvy0 + $l * $linespc}]
5935 set ymax [lindex [$canv cget -scrollregion] 3]
5936 set ytop [expr {$y - $linespc - 1}]
5937 set ybot [expr {$y + $linespc + 1}]
5938 set wnow [$canv yview]
5939 set wtop [expr {[lindex $wnow 0] * $ymax}]
5940 set wbot [expr {[lindex $wnow 1] * $ymax}]
5941 set wh [expr {$wbot - $wtop}]
5942 set newtop $wtop
5943 if {$ytop < $wtop} {
5944 if {$ybot < $wtop} {
5945 set newtop [expr {$y - $wh / 2.0}]
5946 } else {
5947 set newtop $ytop
5948 if {$newtop > $wtop - $linespc} {
5949 set newtop [expr {$wtop - $linespc}]
5952 } elseif {$ybot > $wbot} {
5953 if {$ytop > $wbot} {
5954 set newtop [expr {$y - $wh / 2.0}]
5955 } else {
5956 set newtop [expr {$ybot - $wh}]
5957 if {$newtop < $wtop + $linespc} {
5958 set newtop [expr {$wtop + $linespc}]
5962 if {$newtop != $wtop} {
5963 if {$newtop < 0} {
5964 set newtop 0
5966 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5967 drawvisible
5970 make_secsel $l
5972 if {$isnew} {
5973 addtohistory [list selbyid $id]
5976 $sha1entry delete 0 end
5977 $sha1entry insert 0 $id
5978 if {$autoselect} {
5979 $sha1entry selection from 0
5980 $sha1entry selection to end
5982 rhighlight_sel $id
5984 $ctext conf -state normal
5985 clear_ctext
5986 set linknum 0
5987 if {![info exists commitinfo($id)]} {
5988 getcommit $id
5990 set info $commitinfo($id)
5991 set date [formatdate [lindex $info 2]]
5992 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
5993 set date [formatdate [lindex $info 4]]
5994 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
5995 if {[info exists idtags($id)]} {
5996 $ctext insert end [mc "Tags:"]
5997 foreach tag $idtags($id) {
5998 $ctext insert end " $tag"
6000 $ctext insert end "\n"
6003 set headers {}
6004 set olds $parents($curview,$id)
6005 if {[llength $olds] > 1} {
6006 set np 0
6007 foreach p $olds {
6008 if {$np >= $mergemax} {
6009 set tag mmax
6010 } else {
6011 set tag m$np
6013 $ctext insert end "[mc "Parent"]: " $tag
6014 appendwithlinks [commit_descriptor $p] {}
6015 incr np
6017 } else {
6018 foreach p $olds {
6019 append headers "[mc "Parent"]: [commit_descriptor $p]"
6023 foreach c $children($curview,$id) {
6024 append headers "[mc "Child"]: [commit_descriptor $c]"
6027 # make anything that looks like a SHA1 ID be a clickable link
6028 appendwithlinks $headers {}
6029 if {$showneartags} {
6030 if {![info exists allcommits]} {
6031 getallcommits
6033 $ctext insert end "[mc "Branch"]: "
6034 $ctext mark set branch "end -1c"
6035 $ctext mark gravity branch left
6036 $ctext insert end "\n[mc "Follows"]: "
6037 $ctext mark set follows "end -1c"
6038 $ctext mark gravity follows left
6039 $ctext insert end "\n[mc "Precedes"]: "
6040 $ctext mark set precedes "end -1c"
6041 $ctext mark gravity precedes left
6042 $ctext insert end "\n"
6043 dispneartags 1
6045 $ctext insert end "\n"
6046 set comment [lindex $info 5]
6047 if {[string first "\r" $comment] >= 0} {
6048 set comment [string map {"\r" "\n "} $comment]
6050 appendwithlinks $comment {comment}
6052 $ctext tag remove found 1.0 end
6053 $ctext conf -state disabled
6054 set commentend [$ctext index "end - 1c"]
6056 init_flist [mc "Comments"]
6057 if {$cmitmode eq "tree"} {
6058 gettree $id
6059 } elseif {[llength $olds] <= 1} {
6060 startdiff $id
6061 } else {
6062 mergediff $id
6066 proc selfirstline {} {
6067 unmarkmatches
6068 selectline 0 1
6071 proc sellastline {} {
6072 global numcommits
6073 unmarkmatches
6074 set l [expr {$numcommits - 1}]
6075 selectline $l 1
6078 proc selnextline {dir} {
6079 global selectedline
6080 focus .
6081 if {$selectedline eq {}} return
6082 set l [expr {$selectedline + $dir}]
6083 unmarkmatches
6084 selectline $l 1
6087 proc selnextpage {dir} {
6088 global canv linespc selectedline numcommits
6090 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6091 if {$lpp < 1} {
6092 set lpp 1
6094 allcanvs yview scroll [expr {$dir * $lpp}] units
6095 drawvisible
6096 if {$selectedline eq {}} return
6097 set l [expr {$selectedline + $dir * $lpp}]
6098 if {$l < 0} {
6099 set l 0
6100 } elseif {$l >= $numcommits} {
6101 set l [expr $numcommits - 1]
6103 unmarkmatches
6104 selectline $l 1
6107 proc unselectline {} {
6108 global selectedline currentid
6110 set selectedline {}
6111 catch {unset currentid}
6112 allcanvs delete secsel
6113 rhighlight_none
6116 proc reselectline {} {
6117 global selectedline
6119 if {$selectedline ne {}} {
6120 selectline $selectedline 0
6124 proc addtohistory {cmd} {
6125 global history historyindex curview
6127 set elt [list $curview $cmd]
6128 if {$historyindex > 0
6129 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6130 return
6133 if {$historyindex < [llength $history]} {
6134 set history [lreplace $history $historyindex end $elt]
6135 } else {
6136 lappend history $elt
6138 incr historyindex
6139 if {$historyindex > 1} {
6140 .tf.bar.leftbut conf -state normal
6141 } else {
6142 .tf.bar.leftbut conf -state disabled
6144 .tf.bar.rightbut conf -state disabled
6147 proc godo {elt} {
6148 global curview
6150 set view [lindex $elt 0]
6151 set cmd [lindex $elt 1]
6152 if {$curview != $view} {
6153 showview $view
6155 eval $cmd
6158 proc goback {} {
6159 global history historyindex
6160 focus .
6162 if {$historyindex > 1} {
6163 incr historyindex -1
6164 godo [lindex $history [expr {$historyindex - 1}]]
6165 .tf.bar.rightbut conf -state normal
6167 if {$historyindex <= 1} {
6168 .tf.bar.leftbut conf -state disabled
6172 proc goforw {} {
6173 global history historyindex
6174 focus .
6176 if {$historyindex < [llength $history]} {
6177 set cmd [lindex $history $historyindex]
6178 incr historyindex
6179 godo $cmd
6180 .tf.bar.leftbut conf -state normal
6182 if {$historyindex >= [llength $history]} {
6183 .tf.bar.rightbut conf -state disabled
6187 proc gettree {id} {
6188 global treefilelist treeidlist diffids diffmergeid treepending
6189 global nullid nullid2
6191 set diffids $id
6192 catch {unset diffmergeid}
6193 if {![info exists treefilelist($id)]} {
6194 if {![info exists treepending]} {
6195 if {$id eq $nullid} {
6196 set cmd [list | git ls-files]
6197 } elseif {$id eq $nullid2} {
6198 set cmd [list | git ls-files --stage -t]
6199 } else {
6200 set cmd [list | git ls-tree -r $id]
6202 if {[catch {set gtf [open $cmd r]}]} {
6203 return
6205 set treepending $id
6206 set treefilelist($id) {}
6207 set treeidlist($id) {}
6208 fconfigure $gtf -blocking 0
6209 filerun $gtf [list gettreeline $gtf $id]
6211 } else {
6212 setfilelist $id
6216 proc gettreeline {gtf id} {
6217 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6219 set nl 0
6220 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6221 if {$diffids eq $nullid} {
6222 set fname $line
6223 } else {
6224 set i [string first "\t" $line]
6225 if {$i < 0} continue
6226 set fname [string range $line [expr {$i+1}] end]
6227 set line [string range $line 0 [expr {$i-1}]]
6228 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6229 set sha1 [lindex $line 2]
6230 if {[string index $fname 0] eq "\""} {
6231 set fname [lindex $fname 0]
6233 lappend treeidlist($id) $sha1
6235 lappend treefilelist($id) $fname
6237 if {![eof $gtf]} {
6238 return [expr {$nl >= 1000? 2: 1}]
6240 close $gtf
6241 unset treepending
6242 if {$cmitmode ne "tree"} {
6243 if {![info exists diffmergeid]} {
6244 gettreediffs $diffids
6246 } elseif {$id ne $diffids} {
6247 gettree $diffids
6248 } else {
6249 setfilelist $id
6251 return 0
6254 proc showfile {f} {
6255 global treefilelist treeidlist diffids nullid nullid2
6256 global ctext commentend
6258 set i [lsearch -exact $treefilelist($diffids) $f]
6259 if {$i < 0} {
6260 puts "oops, $f not in list for id $diffids"
6261 return
6263 if {$diffids eq $nullid} {
6264 if {[catch {set bf [open $f r]} err]} {
6265 puts "oops, can't read $f: $err"
6266 return
6268 } else {
6269 set blob [lindex $treeidlist($diffids) $i]
6270 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6271 puts "oops, error reading blob $blob: $err"
6272 return
6275 fconfigure $bf -blocking 0
6276 filerun $bf [list getblobline $bf $diffids]
6277 $ctext config -state normal
6278 clear_ctext $commentend
6279 $ctext insert end "\n"
6280 $ctext insert end "$f\n" filesep
6281 $ctext config -state disabled
6282 $ctext yview $commentend
6283 settabs 0
6286 proc getblobline {bf id} {
6287 global diffids cmitmode ctext
6289 if {$id ne $diffids || $cmitmode ne "tree"} {
6290 catch {close $bf}
6291 return 0
6293 $ctext config -state normal
6294 set nl 0
6295 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6296 $ctext insert end "$line\n"
6298 if {[eof $bf]} {
6299 # delete last newline
6300 $ctext delete "end - 2c" "end - 1c"
6301 close $bf
6302 return 0
6304 $ctext config -state disabled
6305 return [expr {$nl >= 1000? 2: 1}]
6308 proc mergediff {id} {
6309 global diffmergeid mdifffd
6310 global diffids
6311 global parents
6312 global diffcontext
6313 global limitdiffs vfilelimit curview
6315 set diffmergeid $id
6316 set diffids $id
6317 # this doesn't seem to actually affect anything...
6318 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6319 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6320 set cmd [concat $cmd -- $vfilelimit($curview)]
6322 if {[catch {set mdf [open $cmd r]} err]} {
6323 error_popup "[mc "Error getting merge diffs:"] $err"
6324 return
6326 fconfigure $mdf -blocking 0
6327 set mdifffd($id) $mdf
6328 set np [llength $parents($curview,$id)]
6329 settabs $np
6330 filerun $mdf [list getmergediffline $mdf $id $np]
6333 proc getmergediffline {mdf id np} {
6334 global diffmergeid ctext cflist mergemax
6335 global difffilestart mdifffd
6337 $ctext conf -state normal
6338 set nr 0
6339 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6340 if {![info exists diffmergeid] || $id != $diffmergeid
6341 || $mdf != $mdifffd($id)} {
6342 close $mdf
6343 return 0
6345 if {[regexp {^diff --cc (.*)} $line match fname]} {
6346 # start of a new file
6347 $ctext insert end "\n"
6348 set here [$ctext index "end - 1c"]
6349 lappend difffilestart $here
6350 add_flist [list $fname]
6351 set l [expr {(78 - [string length $fname]) / 2}]
6352 set pad [string range "----------------------------------------" 1 $l]
6353 $ctext insert end "$pad $fname $pad\n" filesep
6354 } elseif {[regexp {^@@} $line]} {
6355 $ctext insert end "$line\n" hunksep
6356 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6357 # do nothing
6358 } else {
6359 # parse the prefix - one ' ', '-' or '+' for each parent
6360 set spaces {}
6361 set minuses {}
6362 set pluses {}
6363 set isbad 0
6364 for {set j 0} {$j < $np} {incr j} {
6365 set c [string range $line $j $j]
6366 if {$c == " "} {
6367 lappend spaces $j
6368 } elseif {$c == "-"} {
6369 lappend minuses $j
6370 } elseif {$c == "+"} {
6371 lappend pluses $j
6372 } else {
6373 set isbad 1
6374 break
6377 set tags {}
6378 set num {}
6379 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6380 # line doesn't appear in result, parents in $minuses have the line
6381 set num [lindex $minuses 0]
6382 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6383 # line appears in result, parents in $pluses don't have the line
6384 lappend tags mresult
6385 set num [lindex $spaces 0]
6387 if {$num ne {}} {
6388 if {$num >= $mergemax} {
6389 set num "max"
6391 lappend tags m$num
6393 $ctext insert end "$line\n" $tags
6396 $ctext conf -state disabled
6397 if {[eof $mdf]} {
6398 close $mdf
6399 return 0
6401 return [expr {$nr >= 1000? 2: 1}]
6404 proc startdiff {ids} {
6405 global treediffs diffids treepending diffmergeid nullid nullid2
6407 settabs 1
6408 set diffids $ids
6409 catch {unset diffmergeid}
6410 if {![info exists treediffs($ids)] ||
6411 [lsearch -exact $ids $nullid] >= 0 ||
6412 [lsearch -exact $ids $nullid2] >= 0} {
6413 if {![info exists treepending]} {
6414 gettreediffs $ids
6416 } else {
6417 addtocflist $ids
6421 proc path_filter {filter name} {
6422 foreach p $filter {
6423 set l [string length $p]
6424 if {[string index $p end] eq "/"} {
6425 if {[string compare -length $l $p $name] == 0} {
6426 return 1
6428 } else {
6429 if {[string compare -length $l $p $name] == 0 &&
6430 ([string length $name] == $l ||
6431 [string index $name $l] eq "/")} {
6432 return 1
6436 return 0
6439 proc addtocflist {ids} {
6440 global treediffs
6442 add_flist $treediffs($ids)
6443 getblobdiffs $ids
6446 proc diffcmd {ids flags} {
6447 global nullid nullid2
6449 set i [lsearch -exact $ids $nullid]
6450 set j [lsearch -exact $ids $nullid2]
6451 if {$i >= 0} {
6452 if {[llength $ids] > 1 && $j < 0} {
6453 # comparing working directory with some specific revision
6454 set cmd [concat | git diff-index $flags]
6455 if {$i == 0} {
6456 lappend cmd -R [lindex $ids 1]
6457 } else {
6458 lappend cmd [lindex $ids 0]
6460 } else {
6461 # comparing working directory with index
6462 set cmd [concat | git diff-files $flags]
6463 if {$j == 1} {
6464 lappend cmd -R
6467 } elseif {$j >= 0} {
6468 set cmd [concat | git diff-index --cached $flags]
6469 if {[llength $ids] > 1} {
6470 # comparing index with specific revision
6471 if {$i == 0} {
6472 lappend cmd -R [lindex $ids 1]
6473 } else {
6474 lappend cmd [lindex $ids 0]
6476 } else {
6477 # comparing index with HEAD
6478 lappend cmd HEAD
6480 } else {
6481 set cmd [concat | git diff-tree -r $flags $ids]
6483 return $cmd
6486 proc gettreediffs {ids} {
6487 global treediff treepending
6489 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6491 set treepending $ids
6492 set treediff {}
6493 fconfigure $gdtf -blocking 0
6494 filerun $gdtf [list gettreediffline $gdtf $ids]
6497 proc gettreediffline {gdtf ids} {
6498 global treediff treediffs treepending diffids diffmergeid
6499 global cmitmode vfilelimit curview limitdiffs
6501 set nr 0
6502 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
6503 set i [string first "\t" $line]
6504 if {$i >= 0} {
6505 set file [string range $line [expr {$i+1}] end]
6506 if {[string index $file 0] eq "\""} {
6507 set file [lindex $file 0]
6509 lappend treediff $file
6512 if {![eof $gdtf]} {
6513 return [expr {$nr >= 1000? 2: 1}]
6515 close $gdtf
6516 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6517 set flist {}
6518 foreach f $treediff {
6519 if {[path_filter $vfilelimit($curview) $f]} {
6520 lappend flist $f
6523 set treediffs($ids) $flist
6524 } else {
6525 set treediffs($ids) $treediff
6527 unset treepending
6528 if {$cmitmode eq "tree"} {
6529 gettree $diffids
6530 } elseif {$ids != $diffids} {
6531 if {![info exists diffmergeid]} {
6532 gettreediffs $diffids
6534 } else {
6535 addtocflist $ids
6537 return 0
6540 # empty string or positive integer
6541 proc diffcontextvalidate {v} {
6542 return [regexp {^(|[1-9][0-9]*)$} $v]
6545 proc diffcontextchange {n1 n2 op} {
6546 global diffcontextstring diffcontext
6548 if {[string is integer -strict $diffcontextstring]} {
6549 if {$diffcontextstring > 0} {
6550 set diffcontext $diffcontextstring
6551 reselectline
6556 proc changeignorespace {} {
6557 reselectline
6560 proc getblobdiffs {ids} {
6561 global blobdifffd diffids env
6562 global diffinhdr treediffs
6563 global diffcontext
6564 global ignorespace
6565 global limitdiffs vfilelimit curview
6567 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6568 if {$ignorespace} {
6569 append cmd " -w"
6571 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6572 set cmd [concat $cmd -- $vfilelimit($curview)]
6574 if {[catch {set bdf [open $cmd r]} err]} {
6575 puts "error getting diffs: $err"
6576 return
6578 set diffinhdr 0
6579 fconfigure $bdf -blocking 0
6580 set blobdifffd($ids) $bdf
6581 filerun $bdf [list getblobdiffline $bdf $diffids]
6584 proc setinlist {var i val} {
6585 global $var
6587 while {[llength [set $var]] < $i} {
6588 lappend $var {}
6590 if {[llength [set $var]] == $i} {
6591 lappend $var $val
6592 } else {
6593 lset $var $i $val
6597 proc makediffhdr {fname ids} {
6598 global ctext curdiffstart treediffs
6600 set i [lsearch -exact $treediffs($ids) $fname]
6601 if {$i >= 0} {
6602 setinlist difffilestart $i $curdiffstart
6604 set l [expr {(78 - [string length $fname]) / 2}]
6605 set pad [string range "----------------------------------------" 1 $l]
6606 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6609 proc getblobdiffline {bdf ids} {
6610 global diffids blobdifffd ctext curdiffstart
6611 global diffnexthead diffnextnote difffilestart
6612 global diffinhdr treediffs
6614 set nr 0
6615 $ctext conf -state normal
6616 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6617 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6618 close $bdf
6619 return 0
6621 if {![string compare -length 11 "diff --git " $line]} {
6622 # trim off "diff --git "
6623 set line [string range $line 11 end]
6624 set diffinhdr 1
6625 # start of a new file
6626 $ctext insert end "\n"
6627 set curdiffstart [$ctext index "end - 1c"]
6628 $ctext insert end "\n" filesep
6629 # If the name hasn't changed the length will be odd,
6630 # the middle char will be a space, and the two bits either
6631 # side will be a/name and b/name, or "a/name" and "b/name".
6632 # If the name has changed we'll get "rename from" and
6633 # "rename to" or "copy from" and "copy to" lines following this,
6634 # and we'll use them to get the filenames.
6635 # This complexity is necessary because spaces in the filename(s)
6636 # don't get escaped.
6637 set l [string length $line]
6638 set i [expr {$l / 2}]
6639 if {!(($l & 1) && [string index $line $i] eq " " &&
6640 [string range $line 2 [expr {$i - 1}]] eq \
6641 [string range $line [expr {$i + 3}] end])} {
6642 continue
6644 # unescape if quoted and chop off the a/ from the front
6645 if {[string index $line 0] eq "\""} {
6646 set fname [string range [lindex $line 0] 2 end]
6647 } else {
6648 set fname [string range $line 2 [expr {$i - 1}]]
6650 makediffhdr $fname $ids
6652 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6653 $line match f1l f1c f2l f2c rest]} {
6654 $ctext insert end "$line\n" hunksep
6655 set diffinhdr 0
6657 } elseif {$diffinhdr} {
6658 if {![string compare -length 12 "rename from " $line]} {
6659 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6660 if {[string index $fname 0] eq "\""} {
6661 set fname [lindex $fname 0]
6663 set i [lsearch -exact $treediffs($ids) $fname]
6664 if {$i >= 0} {
6665 setinlist difffilestart $i $curdiffstart
6667 } elseif {![string compare -length 10 $line "rename to "] ||
6668 ![string compare -length 8 $line "copy to "]} {
6669 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6670 if {[string index $fname 0] eq "\""} {
6671 set fname [lindex $fname 0]
6673 makediffhdr $fname $ids
6674 } elseif {[string compare -length 3 $line "---"] == 0} {
6675 # do nothing
6676 continue
6677 } elseif {[string compare -length 3 $line "+++"] == 0} {
6678 set diffinhdr 0
6679 continue
6681 $ctext insert end "$line\n" filesep
6683 } else {
6684 set x [string range $line 0 0]
6685 if {$x == "-" || $x == "+"} {
6686 set tag [expr {$x == "+"}]
6687 $ctext insert end "$line\n" d$tag
6688 } elseif {$x == " "} {
6689 $ctext insert end "$line\n"
6690 } else {
6691 # "\ No newline at end of file",
6692 # or something else we don't recognize
6693 $ctext insert end "$line\n" hunksep
6697 $ctext conf -state disabled
6698 if {[eof $bdf]} {
6699 close $bdf
6700 return 0
6702 return [expr {$nr >= 1000? 2: 1}]
6705 proc changediffdisp {} {
6706 global ctext diffelide
6708 $ctext tag conf d0 -elide [lindex $diffelide 0]
6709 $ctext tag conf d1 -elide [lindex $diffelide 1]
6712 proc highlightfile {loc cline} {
6713 global ctext cflist cflist_top
6715 $ctext yview $loc
6716 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
6717 $cflist tag add highlight $cline.0 "$cline.0 lineend"
6718 $cflist see $cline.0
6719 set cflist_top $cline
6722 proc prevfile {} {
6723 global difffilestart ctext cmitmode
6725 if {$cmitmode eq "tree"} return
6726 set prev 0.0
6727 set prevline 1
6728 set here [$ctext index @0,0]
6729 foreach loc $difffilestart {
6730 if {[$ctext compare $loc >= $here]} {
6731 highlightfile $prev $prevline
6732 return
6734 set prev $loc
6735 incr prevline
6737 highlightfile $prev $prevline
6740 proc nextfile {} {
6741 global difffilestart ctext cmitmode
6743 if {$cmitmode eq "tree"} return
6744 set here [$ctext index @0,0]
6745 set line 1
6746 foreach loc $difffilestart {
6747 incr line
6748 if {[$ctext compare $loc > $here]} {
6749 highlightfile $loc $line
6750 return
6755 proc clear_ctext {{first 1.0}} {
6756 global ctext smarktop smarkbot
6757 global pendinglinks
6759 set l [lindex [split $first .] 0]
6760 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6761 set smarktop $l
6763 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6764 set smarkbot $l
6766 $ctext delete $first end
6767 if {$first eq "1.0"} {
6768 catch {unset pendinglinks}
6772 proc settabs {{firstab {}}} {
6773 global firsttabstop tabstop ctext have_tk85
6775 if {$firstab ne {} && $have_tk85} {
6776 set firsttabstop $firstab
6778 set w [font measure textfont "0"]
6779 if {$firsttabstop != 0} {
6780 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6781 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6782 } elseif {$have_tk85 || $tabstop != 8} {
6783 $ctext conf -tabs [expr {$tabstop * $w}]
6784 } else {
6785 $ctext conf -tabs {}
6789 proc incrsearch {name ix op} {
6790 global ctext searchstring searchdirn
6792 $ctext tag remove found 1.0 end
6793 if {[catch {$ctext index anchor}]} {
6794 # no anchor set, use start of selection, or of visible area
6795 set sel [$ctext tag ranges sel]
6796 if {$sel ne {}} {
6797 $ctext mark set anchor [lindex $sel 0]
6798 } elseif {$searchdirn eq "-forwards"} {
6799 $ctext mark set anchor @0,0
6800 } else {
6801 $ctext mark set anchor @0,[winfo height $ctext]
6804 if {$searchstring ne {}} {
6805 set here [$ctext search $searchdirn -- $searchstring anchor]
6806 if {$here ne {}} {
6807 $ctext see $here
6809 searchmarkvisible 1
6813 proc dosearch {} {
6814 global sstring ctext searchstring searchdirn
6816 focus $sstring
6817 $sstring icursor end
6818 set searchdirn -forwards
6819 if {$searchstring ne {}} {
6820 set sel [$ctext tag ranges sel]
6821 if {$sel ne {}} {
6822 set start "[lindex $sel 0] + 1c"
6823 } elseif {[catch {set start [$ctext index anchor]}]} {
6824 set start "@0,0"
6826 set match [$ctext search -count mlen -- $searchstring $start]
6827 $ctext tag remove sel 1.0 end
6828 if {$match eq {}} {
6829 bell
6830 return
6832 $ctext see $match
6833 set mend "$match + $mlen c"
6834 $ctext tag add sel $match $mend
6835 $ctext mark unset anchor
6839 proc dosearchback {} {
6840 global sstring ctext searchstring searchdirn
6842 focus $sstring
6843 $sstring icursor end
6844 set searchdirn -backwards
6845 if {$searchstring ne {}} {
6846 set sel [$ctext tag ranges sel]
6847 if {$sel ne {}} {
6848 set start [lindex $sel 0]
6849 } elseif {[catch {set start [$ctext index anchor]}]} {
6850 set start @0,[winfo height $ctext]
6852 set match [$ctext search -backwards -count ml -- $searchstring $start]
6853 $ctext tag remove sel 1.0 end
6854 if {$match eq {}} {
6855 bell
6856 return
6858 $ctext see $match
6859 set mend "$match + $ml c"
6860 $ctext tag add sel $match $mend
6861 $ctext mark unset anchor
6865 proc searchmark {first last} {
6866 global ctext searchstring
6868 set mend $first.0
6869 while {1} {
6870 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6871 if {$match eq {}} break
6872 set mend "$match + $mlen c"
6873 $ctext tag add found $match $mend
6877 proc searchmarkvisible {doall} {
6878 global ctext smarktop smarkbot
6880 set topline [lindex [split [$ctext index @0,0] .] 0]
6881 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6882 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6883 # no overlap with previous
6884 searchmark $topline $botline
6885 set smarktop $topline
6886 set smarkbot $botline
6887 } else {
6888 if {$topline < $smarktop} {
6889 searchmark $topline [expr {$smarktop-1}]
6890 set smarktop $topline
6892 if {$botline > $smarkbot} {
6893 searchmark [expr {$smarkbot+1}] $botline
6894 set smarkbot $botline
6899 proc scrolltext {f0 f1} {
6900 global searchstring
6902 .bleft.bottom.sb set $f0 $f1
6903 if {$searchstring ne {}} {
6904 searchmarkvisible 0
6908 proc setcoords {} {
6909 global linespc charspc canvx0 canvy0
6910 global xspc1 xspc2 lthickness
6912 set linespc [font metrics mainfont -linespace]
6913 set charspc [font measure mainfont "m"]
6914 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6915 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6916 set lthickness [expr {int($linespc / 9) + 1}]
6917 set xspc1(0) $linespc
6918 set xspc2 $linespc
6921 proc redisplay {} {
6922 global canv
6923 global selectedline
6925 set ymax [lindex [$canv cget -scrollregion] 3]
6926 if {$ymax eq {} || $ymax == 0} return
6927 set span [$canv yview]
6928 clear_display
6929 setcanvscroll
6930 allcanvs yview moveto [lindex $span 0]
6931 drawvisible
6932 if {$selectedline ne {}} {
6933 selectline $selectedline 0
6934 allcanvs yview moveto [lindex $span 0]
6938 proc parsefont {f n} {
6939 global fontattr
6941 set fontattr($f,family) [lindex $n 0]
6942 set s [lindex $n 1]
6943 if {$s eq {} || $s == 0} {
6944 set s 10
6945 } elseif {$s < 0} {
6946 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6948 set fontattr($f,size) $s
6949 set fontattr($f,weight) normal
6950 set fontattr($f,slant) roman
6951 foreach style [lrange $n 2 end] {
6952 switch -- $style {
6953 "normal" -
6954 "bold" {set fontattr($f,weight) $style}
6955 "roman" -
6956 "italic" {set fontattr($f,slant) $style}
6961 proc fontflags {f {isbold 0}} {
6962 global fontattr
6964 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6965 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6966 -slant $fontattr($f,slant)]
6969 proc fontname {f} {
6970 global fontattr
6972 set n [list $fontattr($f,family) $fontattr($f,size)]
6973 if {$fontattr($f,weight) eq "bold"} {
6974 lappend n "bold"
6976 if {$fontattr($f,slant) eq "italic"} {
6977 lappend n "italic"
6979 return $n
6982 proc incrfont {inc} {
6983 global mainfont textfont ctext canv cflist showrefstop
6984 global stopped entries fontattr
6986 unmarkmatches
6987 set s $fontattr(mainfont,size)
6988 incr s $inc
6989 if {$s < 1} {
6990 set s 1
6992 set fontattr(mainfont,size) $s
6993 font config mainfont -size $s
6994 font config mainfontbold -size $s
6995 set mainfont [fontname mainfont]
6996 set s $fontattr(textfont,size)
6997 incr s $inc
6998 if {$s < 1} {
6999 set s 1
7001 set fontattr(textfont,size) $s
7002 font config textfont -size $s
7003 font config textfontbold -size $s
7004 set textfont [fontname textfont]
7005 setcoords
7006 settabs
7007 redisplay
7010 proc clearsha1 {} {
7011 global sha1entry sha1string
7012 if {[string length $sha1string] == 40} {
7013 $sha1entry delete 0 end
7017 proc sha1change {n1 n2 op} {
7018 global sha1string currentid sha1but
7019 if {$sha1string == {}
7020 || ([info exists currentid] && $sha1string == $currentid)} {
7021 set state disabled
7022 } else {
7023 set state normal
7025 if {[$sha1but cget -state] == $state} return
7026 if {$state == "normal"} {
7027 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7028 } else {
7029 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7033 proc gotocommit {} {
7034 global sha1string tagids headids curview varcid
7036 if {$sha1string == {}
7037 || ([info exists currentid] && $sha1string == $currentid)} return
7038 if {[info exists tagids($sha1string)]} {
7039 set id $tagids($sha1string)
7040 } elseif {[info exists headids($sha1string)]} {
7041 set id $headids($sha1string)
7042 } else {
7043 set id [string tolower $sha1string]
7044 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7045 set matches [array names varcid "$curview,$id*"]
7046 if {$matches ne {}} {
7047 if {[llength $matches] > 1} {
7048 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7049 return
7051 set id [lindex [split [lindex $matches 0] ","] 1]
7055 if {[commitinview $id $curview]} {
7056 selectline [rowofcommit $id] 1
7057 return
7059 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7060 set msg [mc "SHA1 id %s is not known" $sha1string]
7061 } else {
7062 set msg [mc "Tag/Head %s is not known" $sha1string]
7064 error_popup $msg
7067 proc lineenter {x y id} {
7068 global hoverx hovery hoverid hovertimer
7069 global commitinfo canv
7071 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7072 set hoverx $x
7073 set hovery $y
7074 set hoverid $id
7075 if {[info exists hovertimer]} {
7076 after cancel $hovertimer
7078 set hovertimer [after 500 linehover]
7079 $canv delete hover
7082 proc linemotion {x y id} {
7083 global hoverx hovery hoverid hovertimer
7085 if {[info exists hoverid] && $id == $hoverid} {
7086 set hoverx $x
7087 set hovery $y
7088 if {[info exists hovertimer]} {
7089 after cancel $hovertimer
7091 set hovertimer [after 500 linehover]
7095 proc lineleave {id} {
7096 global hoverid hovertimer canv
7098 if {[info exists hoverid] && $id == $hoverid} {
7099 $canv delete hover
7100 if {[info exists hovertimer]} {
7101 after cancel $hovertimer
7102 unset hovertimer
7104 unset hoverid
7108 proc linehover {} {
7109 global hoverx hovery hoverid hovertimer
7110 global canv linespc lthickness
7111 global commitinfo
7113 set text [lindex $commitinfo($hoverid) 0]
7114 set ymax [lindex [$canv cget -scrollregion] 3]
7115 if {$ymax == {}} return
7116 set yfrac [lindex [$canv yview] 0]
7117 set x [expr {$hoverx + 2 * $linespc}]
7118 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7119 set x0 [expr {$x - 2 * $lthickness}]
7120 set y0 [expr {$y - 2 * $lthickness}]
7121 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7122 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7123 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7124 -fill \#ffff80 -outline black -width 1 -tags hover]
7125 $canv raise $t
7126 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7127 -font mainfont]
7128 $canv raise $t
7131 proc clickisonarrow {id y} {
7132 global lthickness
7134 set ranges [rowranges $id]
7135 set thresh [expr {2 * $lthickness + 6}]
7136 set n [expr {[llength $ranges] - 1}]
7137 for {set i 1} {$i < $n} {incr i} {
7138 set row [lindex $ranges $i]
7139 if {abs([yc $row] - $y) < $thresh} {
7140 return $i
7143 return {}
7146 proc arrowjump {id n y} {
7147 global canv
7149 # 1 <-> 2, 3 <-> 4, etc...
7150 set n [expr {(($n - 1) ^ 1) + 1}]
7151 set row [lindex [rowranges $id] $n]
7152 set yt [yc $row]
7153 set ymax [lindex [$canv cget -scrollregion] 3]
7154 if {$ymax eq {} || $ymax <= 0} return
7155 set view [$canv yview]
7156 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7157 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7158 if {$yfrac < 0} {
7159 set yfrac 0
7161 allcanvs yview moveto $yfrac
7164 proc lineclick {x y id isnew} {
7165 global ctext commitinfo children canv thickerline curview
7167 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7168 unmarkmatches
7169 unselectline
7170 normalline
7171 $canv delete hover
7172 # draw this line thicker than normal
7173 set thickerline $id
7174 drawlines $id
7175 if {$isnew} {
7176 set ymax [lindex [$canv cget -scrollregion] 3]
7177 if {$ymax eq {}} return
7178 set yfrac [lindex [$canv yview] 0]
7179 set y [expr {$y + $yfrac * $ymax}]
7181 set dirn [clickisonarrow $id $y]
7182 if {$dirn ne {}} {
7183 arrowjump $id $dirn $y
7184 return
7187 if {$isnew} {
7188 addtohistory [list lineclick $x $y $id 0]
7190 # fill the details pane with info about this line
7191 $ctext conf -state normal
7192 clear_ctext
7193 settabs 0
7194 $ctext insert end "[mc "Parent"]:\t"
7195 $ctext insert end $id link0
7196 setlink $id link0
7197 set info $commitinfo($id)
7198 $ctext insert end "\n\t[lindex $info 0]\n"
7199 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7200 set date [formatdate [lindex $info 2]]
7201 $ctext insert end "\t[mc "Date"]:\t$date\n"
7202 set kids $children($curview,$id)
7203 if {$kids ne {}} {
7204 $ctext insert end "\n[mc "Children"]:"
7205 set i 0
7206 foreach child $kids {
7207 incr i
7208 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7209 set info $commitinfo($child)
7210 $ctext insert end "\n\t"
7211 $ctext insert end $child link$i
7212 setlink $child link$i
7213 $ctext insert end "\n\t[lindex $info 0]"
7214 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7215 set date [formatdate [lindex $info 2]]
7216 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7219 $ctext conf -state disabled
7220 init_flist {}
7223 proc normalline {} {
7224 global thickerline
7225 if {[info exists thickerline]} {
7226 set id $thickerline
7227 unset thickerline
7228 drawlines $id
7232 proc selbyid {id} {
7233 global curview
7234 if {[commitinview $id $curview]} {
7235 selectline [rowofcommit $id] 1
7239 proc mstime {} {
7240 global startmstime
7241 if {![info exists startmstime]} {
7242 set startmstime [clock clicks -milliseconds]
7244 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7247 proc rowmenu {x y id} {
7248 global rowctxmenu selectedline rowmenuid curview
7249 global nullid nullid2 fakerowmenu mainhead
7251 stopfinding
7252 set rowmenuid $id
7253 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7254 set state disabled
7255 } else {
7256 set state normal
7258 if {$id ne $nullid && $id ne $nullid2} {
7259 set menu $rowctxmenu
7260 if {$mainhead ne {}} {
7261 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7262 } else {
7263 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7265 } else {
7266 set menu $fakerowmenu
7268 $menu entryconfigure [mc "Diff this -> selected"] -state $state
7269 $menu entryconfigure [mc "Diff selected -> this"] -state $state
7270 $menu entryconfigure [mc "Make patch"] -state $state
7271 tk_popup $menu $x $y
7274 proc diffvssel {dirn} {
7275 global rowmenuid selectedline
7277 if {$selectedline eq {}} return
7278 if {$dirn} {
7279 set oldid [commitonrow $selectedline]
7280 set newid $rowmenuid
7281 } else {
7282 set oldid $rowmenuid
7283 set newid [commitonrow $selectedline]
7285 addtohistory [list doseldiff $oldid $newid]
7286 doseldiff $oldid $newid
7289 proc doseldiff {oldid newid} {
7290 global ctext
7291 global commitinfo
7293 $ctext conf -state normal
7294 clear_ctext
7295 init_flist [mc "Top"]
7296 $ctext insert end "[mc "From"] "
7297 $ctext insert end $oldid link0
7298 setlink $oldid link0
7299 $ctext insert end "\n "
7300 $ctext insert end [lindex $commitinfo($oldid) 0]
7301 $ctext insert end "\n\n[mc "To"] "
7302 $ctext insert end $newid link1
7303 setlink $newid link1
7304 $ctext insert end "\n "
7305 $ctext insert end [lindex $commitinfo($newid) 0]
7306 $ctext insert end "\n"
7307 $ctext conf -state disabled
7308 $ctext tag remove found 1.0 end
7309 startdiff [list $oldid $newid]
7312 proc mkpatch {} {
7313 global rowmenuid currentid commitinfo patchtop patchnum
7315 if {![info exists currentid]} return
7316 set oldid $currentid
7317 set oldhead [lindex $commitinfo($oldid) 0]
7318 set newid $rowmenuid
7319 set newhead [lindex $commitinfo($newid) 0]
7320 set top .patch
7321 set patchtop $top
7322 catch {destroy $top}
7323 toplevel $top
7324 label $top.title -text [mc "Generate patch"]
7325 grid $top.title - -pady 10
7326 label $top.from -text [mc "From:"]
7327 entry $top.fromsha1 -width 40 -relief flat
7328 $top.fromsha1 insert 0 $oldid
7329 $top.fromsha1 conf -state readonly
7330 grid $top.from $top.fromsha1 -sticky w
7331 entry $top.fromhead -width 60 -relief flat
7332 $top.fromhead insert 0 $oldhead
7333 $top.fromhead conf -state readonly
7334 grid x $top.fromhead -sticky w
7335 label $top.to -text [mc "To:"]
7336 entry $top.tosha1 -width 40 -relief flat
7337 $top.tosha1 insert 0 $newid
7338 $top.tosha1 conf -state readonly
7339 grid $top.to $top.tosha1 -sticky w
7340 entry $top.tohead -width 60 -relief flat
7341 $top.tohead insert 0 $newhead
7342 $top.tohead conf -state readonly
7343 grid x $top.tohead -sticky w
7344 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7345 grid $top.rev x -pady 10
7346 label $top.flab -text [mc "Output file:"]
7347 entry $top.fname -width 60
7348 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7349 incr patchnum
7350 grid $top.flab $top.fname -sticky w
7351 frame $top.buts
7352 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7353 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7354 grid $top.buts.gen $top.buts.can
7355 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7356 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7357 grid $top.buts - -pady 10 -sticky ew
7358 focus $top.fname
7361 proc mkpatchrev {} {
7362 global patchtop
7364 set oldid [$patchtop.fromsha1 get]
7365 set oldhead [$patchtop.fromhead get]
7366 set newid [$patchtop.tosha1 get]
7367 set newhead [$patchtop.tohead get]
7368 foreach e [list fromsha1 fromhead tosha1 tohead] \
7369 v [list $newid $newhead $oldid $oldhead] {
7370 $patchtop.$e conf -state normal
7371 $patchtop.$e delete 0 end
7372 $patchtop.$e insert 0 $v
7373 $patchtop.$e conf -state readonly
7377 proc mkpatchgo {} {
7378 global patchtop nullid nullid2
7380 set oldid [$patchtop.fromsha1 get]
7381 set newid [$patchtop.tosha1 get]
7382 set fname [$patchtop.fname get]
7383 set cmd [diffcmd [list $oldid $newid] -p]
7384 # trim off the initial "|"
7385 set cmd [lrange $cmd 1 end]
7386 lappend cmd >$fname &
7387 if {[catch {eval exec $cmd} err]} {
7388 error_popup "[mc "Error creating patch:"] $err"
7390 catch {destroy $patchtop}
7391 unset patchtop
7394 proc mkpatchcan {} {
7395 global patchtop
7397 catch {destroy $patchtop}
7398 unset patchtop
7401 proc mktag {} {
7402 global rowmenuid mktagtop commitinfo
7404 set top .maketag
7405 set mktagtop $top
7406 catch {destroy $top}
7407 toplevel $top
7408 label $top.title -text [mc "Create tag"]
7409 grid $top.title - -pady 10
7410 label $top.id -text [mc "ID:"]
7411 entry $top.sha1 -width 40 -relief flat
7412 $top.sha1 insert 0 $rowmenuid
7413 $top.sha1 conf -state readonly
7414 grid $top.id $top.sha1 -sticky w
7415 entry $top.head -width 60 -relief flat
7416 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7417 $top.head conf -state readonly
7418 grid x $top.head -sticky w
7419 label $top.tlab -text [mc "Tag name:"]
7420 entry $top.tag -width 60
7421 grid $top.tlab $top.tag -sticky w
7422 frame $top.buts
7423 button $top.buts.gen -text [mc "Create"] -command mktaggo
7424 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7425 grid $top.buts.gen $top.buts.can
7426 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7427 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7428 grid $top.buts - -pady 10 -sticky ew
7429 focus $top.tag
7432 proc domktag {} {
7433 global mktagtop env tagids idtags
7435 set id [$mktagtop.sha1 get]
7436 set tag [$mktagtop.tag get]
7437 if {$tag == {}} {
7438 error_popup [mc "No tag name specified"]
7439 return
7441 if {[info exists tagids($tag)]} {
7442 error_popup [mc "Tag \"%s\" already exists" $tag]
7443 return
7445 if {[catch {
7446 exec git tag $tag $id
7447 } err]} {
7448 error_popup "[mc "Error creating tag:"] $err"
7449 return
7452 set tagids($tag) $id
7453 lappend idtags($id) $tag
7454 redrawtags $id
7455 addedtag $id
7456 dispneartags 0
7457 run refill_reflist
7460 proc redrawtags {id} {
7461 global canv linehtag idpos currentid curview cmitlisted
7462 global canvxmax iddrawn circleitem mainheadid circlecolors
7464 if {![commitinview $id $curview]} return
7465 if {![info exists iddrawn($id)]} return
7466 set row [rowofcommit $id]
7467 if {$id eq $mainheadid} {
7468 set ofill yellow
7469 } else {
7470 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7472 $canv itemconf $circleitem($row) -fill $ofill
7473 $canv delete tag.$id
7474 set xt [eval drawtags $id $idpos($id)]
7475 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7476 set text [$canv itemcget $linehtag($row) -text]
7477 set font [$canv itemcget $linehtag($row) -font]
7478 set xr [expr {$xt + [font measure $font $text]}]
7479 if {$xr > $canvxmax} {
7480 set canvxmax $xr
7481 setcanvscroll
7483 if {[info exists currentid] && $currentid == $id} {
7484 make_secsel $row
7488 proc mktagcan {} {
7489 global mktagtop
7491 catch {destroy $mktagtop}
7492 unset mktagtop
7495 proc mktaggo {} {
7496 domktag
7497 mktagcan
7500 proc writecommit {} {
7501 global rowmenuid wrcomtop commitinfo wrcomcmd
7503 set top .writecommit
7504 set wrcomtop $top
7505 catch {destroy $top}
7506 toplevel $top
7507 label $top.title -text [mc "Write commit to file"]
7508 grid $top.title - -pady 10
7509 label $top.id -text [mc "ID:"]
7510 entry $top.sha1 -width 40 -relief flat
7511 $top.sha1 insert 0 $rowmenuid
7512 $top.sha1 conf -state readonly
7513 grid $top.id $top.sha1 -sticky w
7514 entry $top.head -width 60 -relief flat
7515 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7516 $top.head conf -state readonly
7517 grid x $top.head -sticky w
7518 label $top.clab -text [mc "Command:"]
7519 entry $top.cmd -width 60 -textvariable wrcomcmd
7520 grid $top.clab $top.cmd -sticky w -pady 10
7521 label $top.flab -text [mc "Output file:"]
7522 entry $top.fname -width 60
7523 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7524 grid $top.flab $top.fname -sticky w
7525 frame $top.buts
7526 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7527 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7528 grid $top.buts.gen $top.buts.can
7529 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7530 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7531 grid $top.buts - -pady 10 -sticky ew
7532 focus $top.fname
7535 proc wrcomgo {} {
7536 global wrcomtop
7538 set id [$wrcomtop.sha1 get]
7539 set cmd "echo $id | [$wrcomtop.cmd get]"
7540 set fname [$wrcomtop.fname get]
7541 if {[catch {exec sh -c $cmd >$fname &} err]} {
7542 error_popup "[mc "Error writing commit:"] $err"
7544 catch {destroy $wrcomtop}
7545 unset wrcomtop
7548 proc wrcomcan {} {
7549 global wrcomtop
7551 catch {destroy $wrcomtop}
7552 unset wrcomtop
7555 proc mkbranch {} {
7556 global rowmenuid mkbrtop
7558 set top .makebranch
7559 catch {destroy $top}
7560 toplevel $top
7561 label $top.title -text [mc "Create new branch"]
7562 grid $top.title - -pady 10
7563 label $top.id -text [mc "ID:"]
7564 entry $top.sha1 -width 40 -relief flat
7565 $top.sha1 insert 0 $rowmenuid
7566 $top.sha1 conf -state readonly
7567 grid $top.id $top.sha1 -sticky w
7568 label $top.nlab -text [mc "Name:"]
7569 entry $top.name -width 40
7570 grid $top.nlab $top.name -sticky w
7571 frame $top.buts
7572 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7573 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7574 grid $top.buts.go $top.buts.can
7575 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7576 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7577 grid $top.buts - -pady 10 -sticky ew
7578 focus $top.name
7581 proc mkbrgo {top} {
7582 global headids idheads
7584 set name [$top.name get]
7585 set id [$top.sha1 get]
7586 if {$name eq {}} {
7587 error_popup [mc "Please specify a name for the new branch"]
7588 return
7590 catch {destroy $top}
7591 nowbusy newbranch
7592 update
7593 if {[catch {
7594 exec git branch $name $id
7595 } err]} {
7596 notbusy newbranch
7597 error_popup $err
7598 } else {
7599 set headids($name) $id
7600 lappend idheads($id) $name
7601 addedhead $id $name
7602 notbusy newbranch
7603 redrawtags $id
7604 dispneartags 0
7605 run refill_reflist
7609 proc cherrypick {} {
7610 global rowmenuid curview
7611 global mainhead mainheadid
7613 set oldhead [exec git rev-parse HEAD]
7614 set dheads [descheads $rowmenuid]
7615 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7616 set ok [confirm_popup [mc "Commit %s is already\
7617 included in branch %s -- really re-apply it?" \
7618 [string range $rowmenuid 0 7] $mainhead]]
7619 if {!$ok} return
7621 nowbusy cherrypick [mc "Cherry-picking"]
7622 update
7623 # Unfortunately git-cherry-pick writes stuff to stderr even when
7624 # no error occurs, and exec takes that as an indication of error...
7625 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7626 notbusy cherrypick
7627 error_popup $err
7628 return
7630 set newhead [exec git rev-parse HEAD]
7631 if {$newhead eq $oldhead} {
7632 notbusy cherrypick
7633 error_popup [mc "No changes committed"]
7634 return
7636 addnewchild $newhead $oldhead
7637 if {[commitinview $oldhead $curview]} {
7638 insertrow $newhead $oldhead $curview
7639 if {$mainhead ne {}} {
7640 movehead $newhead $mainhead
7641 movedhead $newhead $mainhead
7643 set mainheadid $newhead
7644 redrawtags $oldhead
7645 redrawtags $newhead
7646 selbyid $newhead
7648 notbusy cherrypick
7651 proc resethead {} {
7652 global mainhead rowmenuid confirm_ok resettype
7654 set confirm_ok 0
7655 set w ".confirmreset"
7656 toplevel $w
7657 wm transient $w .
7658 wm title $w [mc "Confirm reset"]
7659 message $w.m -text \
7660 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7661 -justify center -aspect 1000
7662 pack $w.m -side top -fill x -padx 20 -pady 20
7663 frame $w.f -relief sunken -border 2
7664 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7665 grid $w.f.rt -sticky w
7666 set resettype mixed
7667 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7668 -text [mc "Soft: Leave working tree and index untouched"]
7669 grid $w.f.soft -sticky w
7670 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7671 -text [mc "Mixed: Leave working tree untouched, reset index"]
7672 grid $w.f.mixed -sticky w
7673 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7674 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7675 grid $w.f.hard -sticky w
7676 pack $w.f -side top -fill x
7677 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7678 pack $w.ok -side left -fill x -padx 20 -pady 20
7679 button $w.cancel -text [mc Cancel] -command "destroy $w"
7680 pack $w.cancel -side right -fill x -padx 20 -pady 20
7681 bind $w <Visibility> "grab $w; focus $w"
7682 tkwait window $w
7683 if {!$confirm_ok} return
7684 if {[catch {set fd [open \
7685 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
7686 error_popup $err
7687 } else {
7688 dohidelocalchanges
7689 filerun $fd [list readresetstat $fd]
7690 nowbusy reset [mc "Resetting"]
7691 selbyid $rowmenuid
7695 proc readresetstat {fd} {
7696 global mainhead mainheadid showlocalchanges rprogcoord
7698 if {[gets $fd line] >= 0} {
7699 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7700 set rprogcoord [expr {1.0 * $m / $n}]
7701 adjustprogress
7703 return 1
7705 set rprogcoord 0
7706 adjustprogress
7707 notbusy reset
7708 if {[catch {close $fd} err]} {
7709 error_popup $err
7711 set oldhead $mainheadid
7712 set newhead [exec git rev-parse HEAD]
7713 if {$newhead ne $oldhead} {
7714 movehead $newhead $mainhead
7715 movedhead $newhead $mainhead
7716 set mainheadid $newhead
7717 redrawtags $oldhead
7718 redrawtags $newhead
7720 if {$showlocalchanges} {
7721 doshowlocalchanges
7723 return 0
7726 # context menu for a head
7727 proc headmenu {x y id head} {
7728 global headmenuid headmenuhead headctxmenu mainhead
7730 stopfinding
7731 set headmenuid $id
7732 set headmenuhead $head
7733 set state normal
7734 if {$head eq $mainhead} {
7735 set state disabled
7737 $headctxmenu entryconfigure 0 -state $state
7738 $headctxmenu entryconfigure 1 -state $state
7739 tk_popup $headctxmenu $x $y
7742 proc cobranch {} {
7743 global headmenuid headmenuhead headids
7744 global showlocalchanges mainheadid
7746 # check the tree is clean first??
7747 nowbusy checkout [mc "Checking out"]
7748 update
7749 dohidelocalchanges
7750 if {[catch {
7751 set fd [open [list | git checkout $headmenuhead 2>@1] r]
7752 } err]} {
7753 notbusy checkout
7754 error_popup $err
7755 if {$showlocalchanges} {
7756 dodiffindex
7758 } else {
7759 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
7763 proc readcheckoutstat {fd newhead newheadid} {
7764 global mainhead mainheadid headids showlocalchanges progresscoords
7766 if {[gets $fd line] >= 0} {
7767 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7768 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
7769 adjustprogress
7771 return 1
7773 set progresscoords {0 0}
7774 adjustprogress
7775 notbusy checkout
7776 if {[catch {close $fd} err]} {
7777 error_popup $err
7779 set oldmainid $mainheadid
7780 set mainhead $newhead
7781 set mainheadid $newheadid
7782 redrawtags $oldmainid
7783 redrawtags $newheadid
7784 selbyid $newheadid
7785 if {$showlocalchanges} {
7786 dodiffindex
7790 proc rmbranch {} {
7791 global headmenuid headmenuhead mainhead
7792 global idheads
7794 set head $headmenuhead
7795 set id $headmenuid
7796 # this check shouldn't be needed any more...
7797 if {$head eq $mainhead} {
7798 error_popup [mc "Cannot delete the currently checked-out branch"]
7799 return
7801 set dheads [descheads $id]
7802 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7803 # the stuff on this branch isn't on any other branch
7804 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7805 branch.\nReally delete branch %s?" $head $head]]} return
7807 nowbusy rmbranch
7808 update
7809 if {[catch {exec git branch -D $head} err]} {
7810 notbusy rmbranch
7811 error_popup $err
7812 return
7814 removehead $id $head
7815 removedhead $id $head
7816 redrawtags $id
7817 notbusy rmbranch
7818 dispneartags 0
7819 run refill_reflist
7822 # Display a list of tags and heads
7823 proc showrefs {} {
7824 global showrefstop bgcolor fgcolor selectbgcolor
7825 global bglist fglist reflistfilter reflist maincursor
7827 set top .showrefs
7828 set showrefstop $top
7829 if {[winfo exists $top]} {
7830 raise $top
7831 refill_reflist
7832 return
7834 toplevel $top
7835 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7836 text $top.list -background $bgcolor -foreground $fgcolor \
7837 -selectbackground $selectbgcolor -font mainfont \
7838 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7839 -width 30 -height 20 -cursor $maincursor \
7840 -spacing1 1 -spacing3 1 -state disabled
7841 $top.list tag configure highlight -background $selectbgcolor
7842 lappend bglist $top.list
7843 lappend fglist $top.list
7844 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7845 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7846 grid $top.list $top.ysb -sticky nsew
7847 grid $top.xsb x -sticky ew
7848 frame $top.f
7849 label $top.f.l -text "[mc "Filter"]: "
7850 entry $top.f.e -width 20 -textvariable reflistfilter
7851 set reflistfilter "*"
7852 trace add variable reflistfilter write reflistfilter_change
7853 pack $top.f.e -side right -fill x -expand 1
7854 pack $top.f.l -side left
7855 grid $top.f - -sticky ew -pady 2
7856 button $top.close -command [list destroy $top] -text [mc "Close"]
7857 grid $top.close -
7858 grid columnconfigure $top 0 -weight 1
7859 grid rowconfigure $top 0 -weight 1
7860 bind $top.list <1> {break}
7861 bind $top.list <B1-Motion> {break}
7862 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7863 set reflist {}
7864 refill_reflist
7867 proc sel_reflist {w x y} {
7868 global showrefstop reflist headids tagids otherrefids
7870 if {![winfo exists $showrefstop]} return
7871 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7872 set ref [lindex $reflist [expr {$l-1}]]
7873 set n [lindex $ref 0]
7874 switch -- [lindex $ref 1] {
7875 "H" {selbyid $headids($n)}
7876 "T" {selbyid $tagids($n)}
7877 "o" {selbyid $otherrefids($n)}
7879 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7882 proc unsel_reflist {} {
7883 global showrefstop
7885 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7886 $showrefstop.list tag remove highlight 0.0 end
7889 proc reflistfilter_change {n1 n2 op} {
7890 global reflistfilter
7892 after cancel refill_reflist
7893 after 200 refill_reflist
7896 proc refill_reflist {} {
7897 global reflist reflistfilter showrefstop headids tagids otherrefids
7898 global curview commitinterest
7900 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7901 set refs {}
7902 foreach n [array names headids] {
7903 if {[string match $reflistfilter $n]} {
7904 if {[commitinview $headids($n) $curview]} {
7905 lappend refs [list $n H]
7906 } else {
7907 set commitinterest($headids($n)) {run refill_reflist}
7911 foreach n [array names tagids] {
7912 if {[string match $reflistfilter $n]} {
7913 if {[commitinview $tagids($n) $curview]} {
7914 lappend refs [list $n T]
7915 } else {
7916 set commitinterest($tagids($n)) {run refill_reflist}
7920 foreach n [array names otherrefids] {
7921 if {[string match $reflistfilter $n]} {
7922 if {[commitinview $otherrefids($n) $curview]} {
7923 lappend refs [list $n o]
7924 } else {
7925 set commitinterest($otherrefids($n)) {run refill_reflist}
7929 set refs [lsort -index 0 $refs]
7930 if {$refs eq $reflist} return
7932 # Update the contents of $showrefstop.list according to the
7933 # differences between $reflist (old) and $refs (new)
7934 $showrefstop.list conf -state normal
7935 $showrefstop.list insert end "\n"
7936 set i 0
7937 set j 0
7938 while {$i < [llength $reflist] || $j < [llength $refs]} {
7939 if {$i < [llength $reflist]} {
7940 if {$j < [llength $refs]} {
7941 set cmp [string compare [lindex $reflist $i 0] \
7942 [lindex $refs $j 0]]
7943 if {$cmp == 0} {
7944 set cmp [string compare [lindex $reflist $i 1] \
7945 [lindex $refs $j 1]]
7947 } else {
7948 set cmp -1
7950 } else {
7951 set cmp 1
7953 switch -- $cmp {
7954 -1 {
7955 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7956 incr i
7959 incr i
7960 incr j
7963 set l [expr {$j + 1}]
7964 $showrefstop.list image create $l.0 -align baseline \
7965 -image reficon-[lindex $refs $j 1] -padx 2
7966 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7967 incr j
7971 set reflist $refs
7972 # delete last newline
7973 $showrefstop.list delete end-2c end-1c
7974 $showrefstop.list conf -state disabled
7977 # Stuff for finding nearby tags
7978 proc getallcommits {} {
7979 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7980 global idheads idtags idotherrefs allparents tagobjid
7982 if {![info exists allcommits]} {
7983 set nextarc 0
7984 set allcommits 0
7985 set seeds {}
7986 set allcwait 0
7987 set cachedarcs 0
7988 set allccache [file join [gitdir] "gitk.cache"]
7989 if {![catch {
7990 set f [open $allccache r]
7991 set allcwait 1
7992 getcache $f
7993 }]} return
7996 if {$allcwait} {
7997 return
7999 set cmd [list | git rev-list --parents]
8000 set allcupdate [expr {$seeds ne {}}]
8001 if {!$allcupdate} {
8002 set ids "--all"
8003 } else {
8004 set refs [concat [array names idheads] [array names idtags] \
8005 [array names idotherrefs]]
8006 set ids {}
8007 set tagobjs {}
8008 foreach name [array names tagobjid] {
8009 lappend tagobjs $tagobjid($name)
8011 foreach id [lsort -unique $refs] {
8012 if {![info exists allparents($id)] &&
8013 [lsearch -exact $tagobjs $id] < 0} {
8014 lappend ids $id
8017 if {$ids ne {}} {
8018 foreach id $seeds {
8019 lappend ids "^$id"
8023 if {$ids ne {}} {
8024 set fd [open [concat $cmd $ids] r]
8025 fconfigure $fd -blocking 0
8026 incr allcommits
8027 nowbusy allcommits
8028 filerun $fd [list getallclines $fd]
8029 } else {
8030 dispneartags 0
8034 # Since most commits have 1 parent and 1 child, we group strings of
8035 # such commits into "arcs" joining branch/merge points (BMPs), which
8036 # are commits that either don't have 1 parent or don't have 1 child.
8038 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8039 # arcout(id) - outgoing arcs for BMP
8040 # arcids(a) - list of IDs on arc including end but not start
8041 # arcstart(a) - BMP ID at start of arc
8042 # arcend(a) - BMP ID at end of arc
8043 # growing(a) - arc a is still growing
8044 # arctags(a) - IDs out of arcids (excluding end) that have tags
8045 # archeads(a) - IDs out of arcids (excluding end) that have heads
8046 # The start of an arc is at the descendent end, so "incoming" means
8047 # coming from descendents, and "outgoing" means going towards ancestors.
8049 proc getallclines {fd} {
8050 global allparents allchildren idtags idheads nextarc
8051 global arcnos arcids arctags arcout arcend arcstart archeads growing
8052 global seeds allcommits cachedarcs allcupdate
8054 set nid 0
8055 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8056 set id [lindex $line 0]
8057 if {[info exists allparents($id)]} {
8058 # seen it already
8059 continue
8061 set cachedarcs 0
8062 set olds [lrange $line 1 end]
8063 set allparents($id) $olds
8064 if {![info exists allchildren($id)]} {
8065 set allchildren($id) {}
8066 set arcnos($id) {}
8067 lappend seeds $id
8068 } else {
8069 set a $arcnos($id)
8070 if {[llength $olds] == 1 && [llength $a] == 1} {
8071 lappend arcids($a) $id
8072 if {[info exists idtags($id)]} {
8073 lappend arctags($a) $id
8075 if {[info exists idheads($id)]} {
8076 lappend archeads($a) $id
8078 if {[info exists allparents($olds)]} {
8079 # seen parent already
8080 if {![info exists arcout($olds)]} {
8081 splitarc $olds
8083 lappend arcids($a) $olds
8084 set arcend($a) $olds
8085 unset growing($a)
8087 lappend allchildren($olds) $id
8088 lappend arcnos($olds) $a
8089 continue
8092 foreach a $arcnos($id) {
8093 lappend arcids($a) $id
8094 set arcend($a) $id
8095 unset growing($a)
8098 set ao {}
8099 foreach p $olds {
8100 lappend allchildren($p) $id
8101 set a [incr nextarc]
8102 set arcstart($a) $id
8103 set archeads($a) {}
8104 set arctags($a) {}
8105 set archeads($a) {}
8106 set arcids($a) {}
8107 lappend ao $a
8108 set growing($a) 1
8109 if {[info exists allparents($p)]} {
8110 # seen it already, may need to make a new branch
8111 if {![info exists arcout($p)]} {
8112 splitarc $p
8114 lappend arcids($a) $p
8115 set arcend($a) $p
8116 unset growing($a)
8118 lappend arcnos($p) $a
8120 set arcout($id) $ao
8122 if {$nid > 0} {
8123 global cached_dheads cached_dtags cached_atags
8124 catch {unset cached_dheads}
8125 catch {unset cached_dtags}
8126 catch {unset cached_atags}
8128 if {![eof $fd]} {
8129 return [expr {$nid >= 1000? 2: 1}]
8131 set cacheok 1
8132 if {[catch {
8133 fconfigure $fd -blocking 1
8134 close $fd
8135 } err]} {
8136 # got an error reading the list of commits
8137 # if we were updating, try rereading the whole thing again
8138 if {$allcupdate} {
8139 incr allcommits -1
8140 dropcache $err
8141 return
8143 error_popup "[mc "Error reading commit topology information;\
8144 branch and preceding/following tag information\
8145 will be incomplete."]\n($err)"
8146 set cacheok 0
8148 if {[incr allcommits -1] == 0} {
8149 notbusy allcommits
8150 if {$cacheok} {
8151 run savecache
8154 dispneartags 0
8155 return 0
8158 proc recalcarc {a} {
8159 global arctags archeads arcids idtags idheads
8161 set at {}
8162 set ah {}
8163 foreach id [lrange $arcids($a) 0 end-1] {
8164 if {[info exists idtags($id)]} {
8165 lappend at $id
8167 if {[info exists idheads($id)]} {
8168 lappend ah $id
8171 set arctags($a) $at
8172 set archeads($a) $ah
8175 proc splitarc {p} {
8176 global arcnos arcids nextarc arctags archeads idtags idheads
8177 global arcstart arcend arcout allparents growing
8179 set a $arcnos($p)
8180 if {[llength $a] != 1} {
8181 puts "oops splitarc called but [llength $a] arcs already"
8182 return
8184 set a [lindex $a 0]
8185 set i [lsearch -exact $arcids($a) $p]
8186 if {$i < 0} {
8187 puts "oops splitarc $p not in arc $a"
8188 return
8190 set na [incr nextarc]
8191 if {[info exists arcend($a)]} {
8192 set arcend($na) $arcend($a)
8193 } else {
8194 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8195 set j [lsearch -exact $arcnos($l) $a]
8196 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8198 set tail [lrange $arcids($a) [expr {$i+1}] end]
8199 set arcids($a) [lrange $arcids($a) 0 $i]
8200 set arcend($a) $p
8201 set arcstart($na) $p
8202 set arcout($p) $na
8203 set arcids($na) $tail
8204 if {[info exists growing($a)]} {
8205 set growing($na) 1
8206 unset growing($a)
8209 foreach id $tail {
8210 if {[llength $arcnos($id)] == 1} {
8211 set arcnos($id) $na
8212 } else {
8213 set j [lsearch -exact $arcnos($id) $a]
8214 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8218 # reconstruct tags and heads lists
8219 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8220 recalcarc $a
8221 recalcarc $na
8222 } else {
8223 set arctags($na) {}
8224 set archeads($na) {}
8228 # Update things for a new commit added that is a child of one
8229 # existing commit. Used when cherry-picking.
8230 proc addnewchild {id p} {
8231 global allparents allchildren idtags nextarc
8232 global arcnos arcids arctags arcout arcend arcstart archeads growing
8233 global seeds allcommits
8235 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8236 set allparents($id) [list $p]
8237 set allchildren($id) {}
8238 set arcnos($id) {}
8239 lappend seeds $id
8240 lappend allchildren($p) $id
8241 set a [incr nextarc]
8242 set arcstart($a) $id
8243 set archeads($a) {}
8244 set arctags($a) {}
8245 set arcids($a) [list $p]
8246 set arcend($a) $p
8247 if {![info exists arcout($p)]} {
8248 splitarc $p
8250 lappend arcnos($p) $a
8251 set arcout($id) [list $a]
8254 # This implements a cache for the topology information.
8255 # The cache saves, for each arc, the start and end of the arc,
8256 # the ids on the arc, and the outgoing arcs from the end.
8257 proc readcache {f} {
8258 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8259 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8260 global allcwait
8262 set a $nextarc
8263 set lim $cachedarcs
8264 if {$lim - $a > 500} {
8265 set lim [expr {$a + 500}]
8267 if {[catch {
8268 if {$a == $lim} {
8269 # finish reading the cache and setting up arctags, etc.
8270 set line [gets $f]
8271 if {$line ne "1"} {error "bad final version"}
8272 close $f
8273 foreach id [array names idtags] {
8274 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8275 [llength $allparents($id)] == 1} {
8276 set a [lindex $arcnos($id) 0]
8277 if {$arctags($a) eq {}} {
8278 recalcarc $a
8282 foreach id [array names idheads] {
8283 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8284 [llength $allparents($id)] == 1} {
8285 set a [lindex $arcnos($id) 0]
8286 if {$archeads($a) eq {}} {
8287 recalcarc $a
8291 foreach id [lsort -unique $possible_seeds] {
8292 if {$arcnos($id) eq {}} {
8293 lappend seeds $id
8296 set allcwait 0
8297 } else {
8298 while {[incr a] <= $lim} {
8299 set line [gets $f]
8300 if {[llength $line] != 3} {error "bad line"}
8301 set s [lindex $line 0]
8302 set arcstart($a) $s
8303 lappend arcout($s) $a
8304 if {![info exists arcnos($s)]} {
8305 lappend possible_seeds $s
8306 set arcnos($s) {}
8308 set e [lindex $line 1]
8309 if {$e eq {}} {
8310 set growing($a) 1
8311 } else {
8312 set arcend($a) $e
8313 if {![info exists arcout($e)]} {
8314 set arcout($e) {}
8317 set arcids($a) [lindex $line 2]
8318 foreach id $arcids($a) {
8319 lappend allparents($s) $id
8320 set s $id
8321 lappend arcnos($id) $a
8323 if {![info exists allparents($s)]} {
8324 set allparents($s) {}
8326 set arctags($a) {}
8327 set archeads($a) {}
8329 set nextarc [expr {$a - 1}]
8331 } err]} {
8332 dropcache $err
8333 return 0
8335 if {!$allcwait} {
8336 getallcommits
8338 return $allcwait
8341 proc getcache {f} {
8342 global nextarc cachedarcs possible_seeds
8344 if {[catch {
8345 set line [gets $f]
8346 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8347 # make sure it's an integer
8348 set cachedarcs [expr {int([lindex $line 1])}]
8349 if {$cachedarcs < 0} {error "bad number of arcs"}
8350 set nextarc 0
8351 set possible_seeds {}
8352 run readcache $f
8353 } err]} {
8354 dropcache $err
8356 return 0
8359 proc dropcache {err} {
8360 global allcwait nextarc cachedarcs seeds
8362 #puts "dropping cache ($err)"
8363 foreach v {arcnos arcout arcids arcstart arcend growing \
8364 arctags archeads allparents allchildren} {
8365 global $v
8366 catch {unset $v}
8368 set allcwait 0
8369 set nextarc 0
8370 set cachedarcs 0
8371 set seeds {}
8372 getallcommits
8375 proc writecache {f} {
8376 global cachearc cachedarcs allccache
8377 global arcstart arcend arcnos arcids arcout
8379 set a $cachearc
8380 set lim $cachedarcs
8381 if {$lim - $a > 1000} {
8382 set lim [expr {$a + 1000}]
8384 if {[catch {
8385 while {[incr a] <= $lim} {
8386 if {[info exists arcend($a)]} {
8387 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8388 } else {
8389 puts $f [list $arcstart($a) {} $arcids($a)]
8392 } err]} {
8393 catch {close $f}
8394 catch {file delete $allccache}
8395 #puts "writing cache failed ($err)"
8396 return 0
8398 set cachearc [expr {$a - 1}]
8399 if {$a > $cachedarcs} {
8400 puts $f "1"
8401 close $f
8402 return 0
8404 return 1
8407 proc savecache {} {
8408 global nextarc cachedarcs cachearc allccache
8410 if {$nextarc == $cachedarcs} return
8411 set cachearc 0
8412 set cachedarcs $nextarc
8413 catch {
8414 set f [open $allccache w]
8415 puts $f [list 1 $cachedarcs]
8416 run writecache $f
8420 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8421 # or 0 if neither is true.
8422 proc anc_or_desc {a b} {
8423 global arcout arcstart arcend arcnos cached_isanc
8425 if {$arcnos($a) eq $arcnos($b)} {
8426 # Both are on the same arc(s); either both are the same BMP,
8427 # or if one is not a BMP, the other is also not a BMP or is
8428 # the BMP at end of the arc (and it only has 1 incoming arc).
8429 # Or both can be BMPs with no incoming arcs.
8430 if {$a eq $b || $arcnos($a) eq {}} {
8431 return 0
8433 # assert {[llength $arcnos($a)] == 1}
8434 set arc [lindex $arcnos($a) 0]
8435 set i [lsearch -exact $arcids($arc) $a]
8436 set j [lsearch -exact $arcids($arc) $b]
8437 if {$i < 0 || $i > $j} {
8438 return 1
8439 } else {
8440 return -1
8444 if {![info exists arcout($a)]} {
8445 set arc [lindex $arcnos($a) 0]
8446 if {[info exists arcend($arc)]} {
8447 set aend $arcend($arc)
8448 } else {
8449 set aend {}
8451 set a $arcstart($arc)
8452 } else {
8453 set aend $a
8455 if {![info exists arcout($b)]} {
8456 set arc [lindex $arcnos($b) 0]
8457 if {[info exists arcend($arc)]} {
8458 set bend $arcend($arc)
8459 } else {
8460 set bend {}
8462 set b $arcstart($arc)
8463 } else {
8464 set bend $b
8466 if {$a eq $bend} {
8467 return 1
8469 if {$b eq $aend} {
8470 return -1
8472 if {[info exists cached_isanc($a,$bend)]} {
8473 if {$cached_isanc($a,$bend)} {
8474 return 1
8477 if {[info exists cached_isanc($b,$aend)]} {
8478 if {$cached_isanc($b,$aend)} {
8479 return -1
8481 if {[info exists cached_isanc($a,$bend)]} {
8482 return 0
8486 set todo [list $a $b]
8487 set anc($a) a
8488 set anc($b) b
8489 for {set i 0} {$i < [llength $todo]} {incr i} {
8490 set x [lindex $todo $i]
8491 if {$anc($x) eq {}} {
8492 continue
8494 foreach arc $arcnos($x) {
8495 set xd $arcstart($arc)
8496 if {$xd eq $bend} {
8497 set cached_isanc($a,$bend) 1
8498 set cached_isanc($b,$aend) 0
8499 return 1
8500 } elseif {$xd eq $aend} {
8501 set cached_isanc($b,$aend) 1
8502 set cached_isanc($a,$bend) 0
8503 return -1
8505 if {![info exists anc($xd)]} {
8506 set anc($xd) $anc($x)
8507 lappend todo $xd
8508 } elseif {$anc($xd) ne $anc($x)} {
8509 set anc($xd) {}
8513 set cached_isanc($a,$bend) 0
8514 set cached_isanc($b,$aend) 0
8515 return 0
8518 # This identifies whether $desc has an ancestor that is
8519 # a growing tip of the graph and which is not an ancestor of $anc
8520 # and returns 0 if so and 1 if not.
8521 # If we subsequently discover a tag on such a growing tip, and that
8522 # turns out to be a descendent of $anc (which it could, since we
8523 # don't necessarily see children before parents), then $desc
8524 # isn't a good choice to display as a descendent tag of
8525 # $anc (since it is the descendent of another tag which is
8526 # a descendent of $anc). Similarly, $anc isn't a good choice to
8527 # display as a ancestor tag of $desc.
8529 proc is_certain {desc anc} {
8530 global arcnos arcout arcstart arcend growing problems
8532 set certain {}
8533 if {[llength $arcnos($anc)] == 1} {
8534 # tags on the same arc are certain
8535 if {$arcnos($desc) eq $arcnos($anc)} {
8536 return 1
8538 if {![info exists arcout($anc)]} {
8539 # if $anc is partway along an arc, use the start of the arc instead
8540 set a [lindex $arcnos($anc) 0]
8541 set anc $arcstart($a)
8544 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8545 set x $desc
8546 } else {
8547 set a [lindex $arcnos($desc) 0]
8548 set x $arcend($a)
8550 if {$x == $anc} {
8551 return 1
8553 set anclist [list $x]
8554 set dl($x) 1
8555 set nnh 1
8556 set ngrowanc 0
8557 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8558 set x [lindex $anclist $i]
8559 if {$dl($x)} {
8560 incr nnh -1
8562 set done($x) 1
8563 foreach a $arcout($x) {
8564 if {[info exists growing($a)]} {
8565 if {![info exists growanc($x)] && $dl($x)} {
8566 set growanc($x) 1
8567 incr ngrowanc
8569 } else {
8570 set y $arcend($a)
8571 if {[info exists dl($y)]} {
8572 if {$dl($y)} {
8573 if {!$dl($x)} {
8574 set dl($y) 0
8575 if {![info exists done($y)]} {
8576 incr nnh -1
8578 if {[info exists growanc($x)]} {
8579 incr ngrowanc -1
8581 set xl [list $y]
8582 for {set k 0} {$k < [llength $xl]} {incr k} {
8583 set z [lindex $xl $k]
8584 foreach c $arcout($z) {
8585 if {[info exists arcend($c)]} {
8586 set v $arcend($c)
8587 if {[info exists dl($v)] && $dl($v)} {
8588 set dl($v) 0
8589 if {![info exists done($v)]} {
8590 incr nnh -1
8592 if {[info exists growanc($v)]} {
8593 incr ngrowanc -1
8595 lappend xl $v
8602 } elseif {$y eq $anc || !$dl($x)} {
8603 set dl($y) 0
8604 lappend anclist $y
8605 } else {
8606 set dl($y) 1
8607 lappend anclist $y
8608 incr nnh
8613 foreach x [array names growanc] {
8614 if {$dl($x)} {
8615 return 0
8617 return 0
8619 return 1
8622 proc validate_arctags {a} {
8623 global arctags idtags
8625 set i -1
8626 set na $arctags($a)
8627 foreach id $arctags($a) {
8628 incr i
8629 if {![info exists idtags($id)]} {
8630 set na [lreplace $na $i $i]
8631 incr i -1
8634 set arctags($a) $na
8637 proc validate_archeads {a} {
8638 global archeads idheads
8640 set i -1
8641 set na $archeads($a)
8642 foreach id $archeads($a) {
8643 incr i
8644 if {![info exists idheads($id)]} {
8645 set na [lreplace $na $i $i]
8646 incr i -1
8649 set archeads($a) $na
8652 # Return the list of IDs that have tags that are descendents of id,
8653 # ignoring IDs that are descendents of IDs already reported.
8654 proc desctags {id} {
8655 global arcnos arcstart arcids arctags idtags allparents
8656 global growing cached_dtags
8658 if {![info exists allparents($id)]} {
8659 return {}
8661 set t1 [clock clicks -milliseconds]
8662 set argid $id
8663 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8664 # part-way along an arc; check that arc first
8665 set a [lindex $arcnos($id) 0]
8666 if {$arctags($a) ne {}} {
8667 validate_arctags $a
8668 set i [lsearch -exact $arcids($a) $id]
8669 set tid {}
8670 foreach t $arctags($a) {
8671 set j [lsearch -exact $arcids($a) $t]
8672 if {$j >= $i} break
8673 set tid $t
8675 if {$tid ne {}} {
8676 return $tid
8679 set id $arcstart($a)
8680 if {[info exists idtags($id)]} {
8681 return $id
8684 if {[info exists cached_dtags($id)]} {
8685 return $cached_dtags($id)
8688 set origid $id
8689 set todo [list $id]
8690 set queued($id) 1
8691 set nc 1
8692 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8693 set id [lindex $todo $i]
8694 set done($id) 1
8695 set ta [info exists hastaggedancestor($id)]
8696 if {!$ta} {
8697 incr nc -1
8699 # ignore tags on starting node
8700 if {!$ta && $i > 0} {
8701 if {[info exists idtags($id)]} {
8702 set tagloc($id) $id
8703 set ta 1
8704 } elseif {[info exists cached_dtags($id)]} {
8705 set tagloc($id) $cached_dtags($id)
8706 set ta 1
8709 foreach a $arcnos($id) {
8710 set d $arcstart($a)
8711 if {!$ta && $arctags($a) ne {}} {
8712 validate_arctags $a
8713 if {$arctags($a) ne {}} {
8714 lappend tagloc($id) [lindex $arctags($a) end]
8717 if {$ta || $arctags($a) ne {}} {
8718 set tomark [list $d]
8719 for {set j 0} {$j < [llength $tomark]} {incr j} {
8720 set dd [lindex $tomark $j]
8721 if {![info exists hastaggedancestor($dd)]} {
8722 if {[info exists done($dd)]} {
8723 foreach b $arcnos($dd) {
8724 lappend tomark $arcstart($b)
8726 if {[info exists tagloc($dd)]} {
8727 unset tagloc($dd)
8729 } elseif {[info exists queued($dd)]} {
8730 incr nc -1
8732 set hastaggedancestor($dd) 1
8736 if {![info exists queued($d)]} {
8737 lappend todo $d
8738 set queued($d) 1
8739 if {![info exists hastaggedancestor($d)]} {
8740 incr nc
8745 set tags {}
8746 foreach id [array names tagloc] {
8747 if {![info exists hastaggedancestor($id)]} {
8748 foreach t $tagloc($id) {
8749 if {[lsearch -exact $tags $t] < 0} {
8750 lappend tags $t
8755 set t2 [clock clicks -milliseconds]
8756 set loopix $i
8758 # remove tags that are descendents of other tags
8759 for {set i 0} {$i < [llength $tags]} {incr i} {
8760 set a [lindex $tags $i]
8761 for {set j 0} {$j < $i} {incr j} {
8762 set b [lindex $tags $j]
8763 set r [anc_or_desc $a $b]
8764 if {$r == 1} {
8765 set tags [lreplace $tags $j $j]
8766 incr j -1
8767 incr i -1
8768 } elseif {$r == -1} {
8769 set tags [lreplace $tags $i $i]
8770 incr i -1
8771 break
8776 if {[array names growing] ne {}} {
8777 # graph isn't finished, need to check if any tag could get
8778 # eclipsed by another tag coming later. Simply ignore any
8779 # tags that could later get eclipsed.
8780 set ctags {}
8781 foreach t $tags {
8782 if {[is_certain $t $origid]} {
8783 lappend ctags $t
8786 if {$tags eq $ctags} {
8787 set cached_dtags($origid) $tags
8788 } else {
8789 set tags $ctags
8791 } else {
8792 set cached_dtags($origid) $tags
8794 set t3 [clock clicks -milliseconds]
8795 if {0 && $t3 - $t1 >= 100} {
8796 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8797 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8799 return $tags
8802 proc anctags {id} {
8803 global arcnos arcids arcout arcend arctags idtags allparents
8804 global growing cached_atags
8806 if {![info exists allparents($id)]} {
8807 return {}
8809 set t1 [clock clicks -milliseconds]
8810 set argid $id
8811 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8812 # part-way along an arc; check that arc first
8813 set a [lindex $arcnos($id) 0]
8814 if {$arctags($a) ne {}} {
8815 validate_arctags $a
8816 set i [lsearch -exact $arcids($a) $id]
8817 foreach t $arctags($a) {
8818 set j [lsearch -exact $arcids($a) $t]
8819 if {$j > $i} {
8820 return $t
8824 if {![info exists arcend($a)]} {
8825 return {}
8827 set id $arcend($a)
8828 if {[info exists idtags($id)]} {
8829 return $id
8832 if {[info exists cached_atags($id)]} {
8833 return $cached_atags($id)
8836 set origid $id
8837 set todo [list $id]
8838 set queued($id) 1
8839 set taglist {}
8840 set nc 1
8841 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8842 set id [lindex $todo $i]
8843 set done($id) 1
8844 set td [info exists hastaggeddescendent($id)]
8845 if {!$td} {
8846 incr nc -1
8848 # ignore tags on starting node
8849 if {!$td && $i > 0} {
8850 if {[info exists idtags($id)]} {
8851 set tagloc($id) $id
8852 set td 1
8853 } elseif {[info exists cached_atags($id)]} {
8854 set tagloc($id) $cached_atags($id)
8855 set td 1
8858 foreach a $arcout($id) {
8859 if {!$td && $arctags($a) ne {}} {
8860 validate_arctags $a
8861 if {$arctags($a) ne {}} {
8862 lappend tagloc($id) [lindex $arctags($a) 0]
8865 if {![info exists arcend($a)]} continue
8866 set d $arcend($a)
8867 if {$td || $arctags($a) ne {}} {
8868 set tomark [list $d]
8869 for {set j 0} {$j < [llength $tomark]} {incr j} {
8870 set dd [lindex $tomark $j]
8871 if {![info exists hastaggeddescendent($dd)]} {
8872 if {[info exists done($dd)]} {
8873 foreach b $arcout($dd) {
8874 if {[info exists arcend($b)]} {
8875 lappend tomark $arcend($b)
8878 if {[info exists tagloc($dd)]} {
8879 unset tagloc($dd)
8881 } elseif {[info exists queued($dd)]} {
8882 incr nc -1
8884 set hastaggeddescendent($dd) 1
8888 if {![info exists queued($d)]} {
8889 lappend todo $d
8890 set queued($d) 1
8891 if {![info exists hastaggeddescendent($d)]} {
8892 incr nc
8897 set t2 [clock clicks -milliseconds]
8898 set loopix $i
8899 set tags {}
8900 foreach id [array names tagloc] {
8901 if {![info exists hastaggeddescendent($id)]} {
8902 foreach t $tagloc($id) {
8903 if {[lsearch -exact $tags $t] < 0} {
8904 lappend tags $t
8910 # remove tags that are ancestors of other tags
8911 for {set i 0} {$i < [llength $tags]} {incr i} {
8912 set a [lindex $tags $i]
8913 for {set j 0} {$j < $i} {incr j} {
8914 set b [lindex $tags $j]
8915 set r [anc_or_desc $a $b]
8916 if {$r == -1} {
8917 set tags [lreplace $tags $j $j]
8918 incr j -1
8919 incr i -1
8920 } elseif {$r == 1} {
8921 set tags [lreplace $tags $i $i]
8922 incr i -1
8923 break
8928 if {[array names growing] ne {}} {
8929 # graph isn't finished, need to check if any tag could get
8930 # eclipsed by another tag coming later. Simply ignore any
8931 # tags that could later get eclipsed.
8932 set ctags {}
8933 foreach t $tags {
8934 if {[is_certain $origid $t]} {
8935 lappend ctags $t
8938 if {$tags eq $ctags} {
8939 set cached_atags($origid) $tags
8940 } else {
8941 set tags $ctags
8943 } else {
8944 set cached_atags($origid) $tags
8946 set t3 [clock clicks -milliseconds]
8947 if {0 && $t3 - $t1 >= 100} {
8948 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8949 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8951 return $tags
8954 # Return the list of IDs that have heads that are descendents of id,
8955 # including id itself if it has a head.
8956 proc descheads {id} {
8957 global arcnos arcstart arcids archeads idheads cached_dheads
8958 global allparents
8960 if {![info exists allparents($id)]} {
8961 return {}
8963 set aret {}
8964 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8965 # part-way along an arc; check it first
8966 set a [lindex $arcnos($id) 0]
8967 if {$archeads($a) ne {}} {
8968 validate_archeads $a
8969 set i [lsearch -exact $arcids($a) $id]
8970 foreach t $archeads($a) {
8971 set j [lsearch -exact $arcids($a) $t]
8972 if {$j > $i} break
8973 lappend aret $t
8976 set id $arcstart($a)
8978 set origid $id
8979 set todo [list $id]
8980 set seen($id) 1
8981 set ret {}
8982 for {set i 0} {$i < [llength $todo]} {incr i} {
8983 set id [lindex $todo $i]
8984 if {[info exists cached_dheads($id)]} {
8985 set ret [concat $ret $cached_dheads($id)]
8986 } else {
8987 if {[info exists idheads($id)]} {
8988 lappend ret $id
8990 foreach a $arcnos($id) {
8991 if {$archeads($a) ne {}} {
8992 validate_archeads $a
8993 if {$archeads($a) ne {}} {
8994 set ret [concat $ret $archeads($a)]
8997 set d $arcstart($a)
8998 if {![info exists seen($d)]} {
8999 lappend todo $d
9000 set seen($d) 1
9005 set ret [lsort -unique $ret]
9006 set cached_dheads($origid) $ret
9007 return [concat $ret $aret]
9010 proc addedtag {id} {
9011 global arcnos arcout cached_dtags cached_atags
9013 if {![info exists arcnos($id)]} return
9014 if {![info exists arcout($id)]} {
9015 recalcarc [lindex $arcnos($id) 0]
9017 catch {unset cached_dtags}
9018 catch {unset cached_atags}
9021 proc addedhead {hid head} {
9022 global arcnos arcout cached_dheads
9024 if {![info exists arcnos($hid)]} return
9025 if {![info exists arcout($hid)]} {
9026 recalcarc [lindex $arcnos($hid) 0]
9028 catch {unset cached_dheads}
9031 proc removedhead {hid head} {
9032 global cached_dheads
9034 catch {unset cached_dheads}
9037 proc movedhead {hid head} {
9038 global arcnos arcout cached_dheads
9040 if {![info exists arcnos($hid)]} return
9041 if {![info exists arcout($hid)]} {
9042 recalcarc [lindex $arcnos($hid) 0]
9044 catch {unset cached_dheads}
9047 proc changedrefs {} {
9048 global cached_dheads cached_dtags cached_atags
9049 global arctags archeads arcnos arcout idheads idtags
9051 foreach id [concat [array names idheads] [array names idtags]] {
9052 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9053 set a [lindex $arcnos($id) 0]
9054 if {![info exists donearc($a)]} {
9055 recalcarc $a
9056 set donearc($a) 1
9060 catch {unset cached_dtags}
9061 catch {unset cached_atags}
9062 catch {unset cached_dheads}
9065 proc rereadrefs {} {
9066 global idtags idheads idotherrefs mainheadid
9068 set refids [concat [array names idtags] \
9069 [array names idheads] [array names idotherrefs]]
9070 foreach id $refids {
9071 if {![info exists ref($id)]} {
9072 set ref($id) [listrefs $id]
9075 set oldmainhead $mainheadid
9076 readrefs
9077 changedrefs
9078 set refids [lsort -unique [concat $refids [array names idtags] \
9079 [array names idheads] [array names idotherrefs]]]
9080 foreach id $refids {
9081 set v [listrefs $id]
9082 if {![info exists ref($id)] || $ref($id) != $v} {
9083 redrawtags $id
9086 if {$oldmainhead ne $mainheadid} {
9087 redrawtags $oldmainhead
9088 redrawtags $mainheadid
9090 run refill_reflist
9093 proc listrefs {id} {
9094 global idtags idheads idotherrefs
9096 set x {}
9097 if {[info exists idtags($id)]} {
9098 set x $idtags($id)
9100 set y {}
9101 if {[info exists idheads($id)]} {
9102 set y $idheads($id)
9104 set z {}
9105 if {[info exists idotherrefs($id)]} {
9106 set z $idotherrefs($id)
9108 return [list $x $y $z]
9111 proc showtag {tag isnew} {
9112 global ctext tagcontents tagids linknum tagobjid
9114 if {$isnew} {
9115 addtohistory [list showtag $tag 0]
9117 $ctext conf -state normal
9118 clear_ctext
9119 settabs 0
9120 set linknum 0
9121 if {![info exists tagcontents($tag)]} {
9122 catch {
9123 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9126 if {[info exists tagcontents($tag)]} {
9127 set text $tagcontents($tag)
9128 } else {
9129 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9131 appendwithlinks $text {}
9132 $ctext conf -state disabled
9133 init_flist {}
9136 proc doquit {} {
9137 global stopped
9138 global gitktmpdir
9140 set stopped 100
9141 savestuff .
9142 destroy .
9144 if {[info exists gitktmpdir]} {
9145 catch {file delete -force $gitktmpdir}
9149 proc mkfontdisp {font top which} {
9150 global fontattr fontpref $font
9152 set fontpref($font) [set $font]
9153 button $top.${font}but -text $which -font optionfont \
9154 -command [list choosefont $font $which]
9155 label $top.$font -relief flat -font $font \
9156 -text $fontattr($font,family) -justify left
9157 grid x $top.${font}but $top.$font -sticky w
9160 proc choosefont {font which} {
9161 global fontparam fontlist fonttop fontattr
9163 set fontparam(which) $which
9164 set fontparam(font) $font
9165 set fontparam(family) [font actual $font -family]
9166 set fontparam(size) $fontattr($font,size)
9167 set fontparam(weight) $fontattr($font,weight)
9168 set fontparam(slant) $fontattr($font,slant)
9169 set top .gitkfont
9170 set fonttop $top
9171 if {![winfo exists $top]} {
9172 font create sample
9173 eval font config sample [font actual $font]
9174 toplevel $top
9175 wm title $top [mc "Gitk font chooser"]
9176 label $top.l -textvariable fontparam(which)
9177 pack $top.l -side top
9178 set fontlist [lsort [font families]]
9179 frame $top.f
9180 listbox $top.f.fam -listvariable fontlist \
9181 -yscrollcommand [list $top.f.sb set]
9182 bind $top.f.fam <<ListboxSelect>> selfontfam
9183 scrollbar $top.f.sb -command [list $top.f.fam yview]
9184 pack $top.f.sb -side right -fill y
9185 pack $top.f.fam -side left -fill both -expand 1
9186 pack $top.f -side top -fill both -expand 1
9187 frame $top.g
9188 spinbox $top.g.size -from 4 -to 40 -width 4 \
9189 -textvariable fontparam(size) \
9190 -validatecommand {string is integer -strict %s}
9191 checkbutton $top.g.bold -padx 5 \
9192 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9193 -variable fontparam(weight) -onvalue bold -offvalue normal
9194 checkbutton $top.g.ital -padx 5 \
9195 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9196 -variable fontparam(slant) -onvalue italic -offvalue roman
9197 pack $top.g.size $top.g.bold $top.g.ital -side left
9198 pack $top.g -side top
9199 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9200 -background white
9201 $top.c create text 100 25 -anchor center -text $which -font sample \
9202 -fill black -tags text
9203 bind $top.c <Configure> [list centertext $top.c]
9204 pack $top.c -side top -fill x
9205 frame $top.buts
9206 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9207 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9208 grid $top.buts.ok $top.buts.can
9209 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9210 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9211 pack $top.buts -side bottom -fill x
9212 trace add variable fontparam write chg_fontparam
9213 } else {
9214 raise $top
9215 $top.c itemconf text -text $which
9217 set i [lsearch -exact $fontlist $fontparam(family)]
9218 if {$i >= 0} {
9219 $top.f.fam selection set $i
9220 $top.f.fam see $i
9224 proc centertext {w} {
9225 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9228 proc fontok {} {
9229 global fontparam fontpref prefstop
9231 set f $fontparam(font)
9232 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9233 if {$fontparam(weight) eq "bold"} {
9234 lappend fontpref($f) "bold"
9236 if {$fontparam(slant) eq "italic"} {
9237 lappend fontpref($f) "italic"
9239 set w $prefstop.$f
9240 $w conf -text $fontparam(family) -font $fontpref($f)
9242 fontcan
9245 proc fontcan {} {
9246 global fonttop fontparam
9248 if {[info exists fonttop]} {
9249 catch {destroy $fonttop}
9250 catch {font delete sample}
9251 unset fonttop
9252 unset fontparam
9256 proc selfontfam {} {
9257 global fonttop fontparam
9259 set i [$fonttop.f.fam curselection]
9260 if {$i ne {}} {
9261 set fontparam(family) [$fonttop.f.fam get $i]
9265 proc chg_fontparam {v sub op} {
9266 global fontparam
9268 font config sample -$sub $fontparam($sub)
9271 proc doprefs {} {
9272 global maxwidth maxgraphpct
9273 global oldprefs prefstop showneartags showlocalchanges
9274 global bgcolor fgcolor ctext diffcolors selectbgcolor
9275 global tabstop limitdiffs autoselect extdifftool
9277 set top .gitkprefs
9278 set prefstop $top
9279 if {[winfo exists $top]} {
9280 raise $top
9281 return
9283 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9284 limitdiffs tabstop} {
9285 set oldprefs($v) [set $v]
9287 toplevel $top
9288 wm title $top [mc "Gitk preferences"]
9289 label $top.ldisp -text [mc "Commit list display options"]
9290 grid $top.ldisp - -sticky w -pady 10
9291 label $top.spacer -text " "
9292 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9293 -font optionfont
9294 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9295 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9296 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9297 -font optionfont
9298 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9299 grid x $top.maxpctl $top.maxpct -sticky w
9300 frame $top.showlocal
9301 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9302 checkbutton $top.showlocal.b -variable showlocalchanges
9303 pack $top.showlocal.b $top.showlocal.l -side left
9304 grid x $top.showlocal -sticky w
9305 frame $top.autoselect
9306 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9307 checkbutton $top.autoselect.b -variable autoselect
9308 pack $top.autoselect.b $top.autoselect.l -side left
9309 grid x $top.autoselect -sticky w
9311 label $top.ddisp -text [mc "Diff display options"]
9312 grid $top.ddisp - -sticky w -pady 10
9313 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9314 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9315 grid x $top.tabstopl $top.tabstop -sticky w
9316 frame $top.ntag
9317 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9318 checkbutton $top.ntag.b -variable showneartags
9319 pack $top.ntag.b $top.ntag.l -side left
9320 grid x $top.ntag -sticky w
9321 frame $top.ldiff
9322 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9323 checkbutton $top.ldiff.b -variable limitdiffs
9324 pack $top.ldiff.b $top.ldiff.l -side left
9325 grid x $top.ldiff -sticky w
9327 entry $top.extdifft -textvariable extdifftool
9328 frame $top.extdifff
9329 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9330 -padx 10
9331 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9332 -command choose_extdiff
9333 pack $top.extdifff.l $top.extdifff.b -side left
9334 grid x $top.extdifff $top.extdifft -sticky w
9336 label $top.cdisp -text [mc "Colors: press to choose"]
9337 grid $top.cdisp - -sticky w -pady 10
9338 label $top.bg -padx 40 -relief sunk -background $bgcolor
9339 button $top.bgbut -text [mc "Background"] -font optionfont \
9340 -command [list choosecolor bgcolor {} $top.bg background setbg]
9341 grid x $top.bgbut $top.bg -sticky w
9342 label $top.fg -padx 40 -relief sunk -background $fgcolor
9343 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9344 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9345 grid x $top.fgbut $top.fg -sticky w
9346 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9347 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9348 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9349 [list $ctext tag conf d0 -foreground]]
9350 grid x $top.diffoldbut $top.diffold -sticky w
9351 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9352 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9353 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9354 [list $ctext tag conf d1 -foreground]]
9355 grid x $top.diffnewbut $top.diffnew -sticky w
9356 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9357 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9358 -command [list choosecolor diffcolors 2 $top.hunksep \
9359 "diff hunk header" \
9360 [list $ctext tag conf hunksep -foreground]]
9361 grid x $top.hunksepbut $top.hunksep -sticky w
9362 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9363 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9364 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9365 grid x $top.selbgbut $top.selbgsep -sticky w
9367 label $top.cfont -text [mc "Fonts: press to choose"]
9368 grid $top.cfont - -sticky w -pady 10
9369 mkfontdisp mainfont $top [mc "Main font"]
9370 mkfontdisp textfont $top [mc "Diff display font"]
9371 mkfontdisp uifont $top [mc "User interface font"]
9373 frame $top.buts
9374 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9375 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9376 grid $top.buts.ok $top.buts.can
9377 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9378 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9379 grid $top.buts - - -pady 10 -sticky ew
9380 bind $top <Visibility> "focus $top.buts.ok"
9383 proc choose_extdiff {} {
9384 global extdifftool
9386 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9387 if {$prog ne {}} {
9388 set extdifftool $prog
9392 proc choosecolor {v vi w x cmd} {
9393 global $v
9395 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9396 -title [mc "Gitk: choose color for %s" $x]]
9397 if {$c eq {}} return
9398 $w conf -background $c
9399 lset $v $vi $c
9400 eval $cmd $c
9403 proc setselbg {c} {
9404 global bglist cflist
9405 foreach w $bglist {
9406 $w configure -selectbackground $c
9408 $cflist tag configure highlight \
9409 -background [$cflist cget -selectbackground]
9410 allcanvs itemconf secsel -fill $c
9413 proc setbg {c} {
9414 global bglist
9416 foreach w $bglist {
9417 $w conf -background $c
9421 proc setfg {c} {
9422 global fglist canv
9424 foreach w $fglist {
9425 $w conf -foreground $c
9427 allcanvs itemconf text -fill $c
9428 $canv itemconf circle -outline $c
9431 proc prefscan {} {
9432 global oldprefs prefstop
9434 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9435 limitdiffs tabstop} {
9436 global $v
9437 set $v $oldprefs($v)
9439 catch {destroy $prefstop}
9440 unset prefstop
9441 fontcan
9444 proc prefsok {} {
9445 global maxwidth maxgraphpct
9446 global oldprefs prefstop showneartags showlocalchanges
9447 global fontpref mainfont textfont uifont
9448 global limitdiffs treediffs
9450 catch {destroy $prefstop}
9451 unset prefstop
9452 fontcan
9453 set fontchanged 0
9454 if {$mainfont ne $fontpref(mainfont)} {
9455 set mainfont $fontpref(mainfont)
9456 parsefont mainfont $mainfont
9457 eval font configure mainfont [fontflags mainfont]
9458 eval font configure mainfontbold [fontflags mainfont 1]
9459 setcoords
9460 set fontchanged 1
9462 if {$textfont ne $fontpref(textfont)} {
9463 set textfont $fontpref(textfont)
9464 parsefont textfont $textfont
9465 eval font configure textfont [fontflags textfont]
9466 eval font configure textfontbold [fontflags textfont 1]
9468 if {$uifont ne $fontpref(uifont)} {
9469 set uifont $fontpref(uifont)
9470 parsefont uifont $uifont
9471 eval font configure uifont [fontflags uifont]
9473 settabs
9474 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9475 if {$showlocalchanges} {
9476 doshowlocalchanges
9477 } else {
9478 dohidelocalchanges
9481 if {$limitdiffs != $oldprefs(limitdiffs)} {
9482 # treediffs elements are limited by path
9483 catch {unset treediffs}
9485 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9486 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9487 redisplay
9488 } elseif {$showneartags != $oldprefs(showneartags) ||
9489 $limitdiffs != $oldprefs(limitdiffs)} {
9490 reselectline
9494 proc formatdate {d} {
9495 global datetimeformat
9496 if {$d ne {}} {
9497 set d [clock format $d -format $datetimeformat]
9499 return $d
9502 # This list of encoding names and aliases is distilled from
9503 # http://www.iana.org/assignments/character-sets.
9504 # Not all of them are supported by Tcl.
9505 set encoding_aliases {
9506 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9507 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9508 { ISO-10646-UTF-1 csISO10646UTF1 }
9509 { ISO_646.basic:1983 ref csISO646basic1983 }
9510 { INVARIANT csINVARIANT }
9511 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9512 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9513 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9514 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9515 { NATS-DANO iso-ir-9-1 csNATSDANO }
9516 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9517 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9518 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9519 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9520 { ISO-2022-KR csISO2022KR }
9521 { EUC-KR csEUCKR }
9522 { ISO-2022-JP csISO2022JP }
9523 { ISO-2022-JP-2 csISO2022JP2 }
9524 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9525 csISO13JISC6220jp }
9526 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9527 { IT iso-ir-15 ISO646-IT csISO15Italian }
9528 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9529 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9530 { greek7-old iso-ir-18 csISO18Greek7Old }
9531 { latin-greek iso-ir-19 csISO19LatinGreek }
9532 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9533 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9534 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9535 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9536 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9537 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9538 { INIS iso-ir-49 csISO49INIS }
9539 { INIS-8 iso-ir-50 csISO50INIS8 }
9540 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9541 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9542 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9543 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9544 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9545 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9546 csISO60Norwegian1 }
9547 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9548 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9549 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9550 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9551 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9552 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9553 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9554 { greek7 iso-ir-88 csISO88Greek7 }
9555 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9556 { iso-ir-90 csISO90 }
9557 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9558 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9559 csISO92JISC62991984b }
9560 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9561 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9562 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9563 csISO95JIS62291984handadd }
9564 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9565 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9566 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9567 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9568 CP819 csISOLatin1 }
9569 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9570 { T.61-7bit iso-ir-102 csISO102T617bit }
9571 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9572 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9573 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9574 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9575 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9576 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9577 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9578 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9579 arabic csISOLatinArabic }
9580 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9581 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9582 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9583 greek greek8 csISOLatinGreek }
9584 { T.101-G2 iso-ir-128 csISO128T101G2 }
9585 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9586 csISOLatinHebrew }
9587 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9588 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9589 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9590 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9591 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9592 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9593 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9594 csISOLatinCyrillic }
9595 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9596 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9597 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9598 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9599 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9600 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9601 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9602 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9603 { ISO_10367-box iso-ir-155 csISO10367Box }
9604 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9605 { latin-lap lap iso-ir-158 csISO158Lap }
9606 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9607 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9608 { us-dk csUSDK }
9609 { dk-us csDKUS }
9610 { JIS_X0201 X0201 csHalfWidthKatakana }
9611 { KSC5636 ISO646-KR csKSC5636 }
9612 { ISO-10646-UCS-2 csUnicode }
9613 { ISO-10646-UCS-4 csUCS4 }
9614 { DEC-MCS dec csDECMCS }
9615 { hp-roman8 roman8 r8 csHPRoman8 }
9616 { macintosh mac csMacintosh }
9617 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9618 csIBM037 }
9619 { IBM038 EBCDIC-INT cp038 csIBM038 }
9620 { IBM273 CP273 csIBM273 }
9621 { IBM274 EBCDIC-BE CP274 csIBM274 }
9622 { IBM275 EBCDIC-BR cp275 csIBM275 }
9623 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9624 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9625 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9626 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9627 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9628 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9629 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9630 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9631 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9632 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9633 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9634 { IBM437 cp437 437 csPC8CodePage437 }
9635 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9636 { IBM775 cp775 csPC775Baltic }
9637 { IBM850 cp850 850 csPC850Multilingual }
9638 { IBM851 cp851 851 csIBM851 }
9639 { IBM852 cp852 852 csPCp852 }
9640 { IBM855 cp855 855 csIBM855 }
9641 { IBM857 cp857 857 csIBM857 }
9642 { IBM860 cp860 860 csIBM860 }
9643 { IBM861 cp861 861 cp-is csIBM861 }
9644 { IBM862 cp862 862 csPC862LatinHebrew }
9645 { IBM863 cp863 863 csIBM863 }
9646 { IBM864 cp864 csIBM864 }
9647 { IBM865 cp865 865 csIBM865 }
9648 { IBM866 cp866 866 csIBM866 }
9649 { IBM868 CP868 cp-ar csIBM868 }
9650 { IBM869 cp869 869 cp-gr csIBM869 }
9651 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9652 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9653 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9654 { IBM891 cp891 csIBM891 }
9655 { IBM903 cp903 csIBM903 }
9656 { IBM904 cp904 904 csIBBM904 }
9657 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9658 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9659 { IBM1026 CP1026 csIBM1026 }
9660 { EBCDIC-AT-DE csIBMEBCDICATDE }
9661 { EBCDIC-AT-DE-A csEBCDICATDEA }
9662 { EBCDIC-CA-FR csEBCDICCAFR }
9663 { EBCDIC-DK-NO csEBCDICDKNO }
9664 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9665 { EBCDIC-FI-SE csEBCDICFISE }
9666 { EBCDIC-FI-SE-A csEBCDICFISEA }
9667 { EBCDIC-FR csEBCDICFR }
9668 { EBCDIC-IT csEBCDICIT }
9669 { EBCDIC-PT csEBCDICPT }
9670 { EBCDIC-ES csEBCDICES }
9671 { EBCDIC-ES-A csEBCDICESA }
9672 { EBCDIC-ES-S csEBCDICESS }
9673 { EBCDIC-UK csEBCDICUK }
9674 { EBCDIC-US csEBCDICUS }
9675 { UNKNOWN-8BIT csUnknown8BiT }
9676 { MNEMONIC csMnemonic }
9677 { MNEM csMnem }
9678 { VISCII csVISCII }
9679 { VIQR csVIQR }
9680 { KOI8-R csKOI8R }
9681 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9682 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9683 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9684 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9685 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9686 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9687 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9688 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9689 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9690 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9691 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9692 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9693 { IBM1047 IBM-1047 }
9694 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9695 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9696 { UNICODE-1-1 csUnicode11 }
9697 { CESU-8 csCESU-8 }
9698 { BOCU-1 csBOCU-1 }
9699 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9700 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9701 l8 }
9702 { ISO-8859-15 ISO_8859-15 Latin-9 }
9703 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9704 { GBK CP936 MS936 windows-936 }
9705 { JIS_Encoding csJISEncoding }
9706 { Shift_JIS MS_Kanji csShiftJIS }
9707 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9708 EUC-JP }
9709 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9710 { ISO-10646-UCS-Basic csUnicodeASCII }
9711 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9712 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9713 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9714 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9715 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9716 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9717 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9718 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9719 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9720 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9721 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9722 { Ventura-US csVenturaUS }
9723 { Ventura-International csVenturaInternational }
9724 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9725 { PC8-Turkish csPC8Turkish }
9726 { IBM-Symbols csIBMSymbols }
9727 { IBM-Thai csIBMThai }
9728 { HP-Legal csHPLegal }
9729 { HP-Pi-font csHPPiFont }
9730 { HP-Math8 csHPMath8 }
9731 { Adobe-Symbol-Encoding csHPPSMath }
9732 { HP-DeskTop csHPDesktop }
9733 { Ventura-Math csVenturaMath }
9734 { Microsoft-Publishing csMicrosoftPublishing }
9735 { Windows-31J csWindows31J }
9736 { GB2312 csGB2312 }
9737 { Big5 csBig5 }
9740 proc tcl_encoding {enc} {
9741 global encoding_aliases
9742 set names [encoding names]
9743 set lcnames [string tolower $names]
9744 set enc [string tolower $enc]
9745 set i [lsearch -exact $lcnames $enc]
9746 if {$i < 0} {
9747 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9748 if {[regsub {^iso[-_]} $enc iso encx]} {
9749 set i [lsearch -exact $lcnames $encx]
9752 if {$i < 0} {
9753 foreach l $encoding_aliases {
9754 set ll [string tolower $l]
9755 if {[lsearch -exact $ll $enc] < 0} continue
9756 # look through the aliases for one that tcl knows about
9757 foreach e $ll {
9758 set i [lsearch -exact $lcnames $e]
9759 if {$i < 0} {
9760 if {[regsub {^iso[-_]} $e iso ex]} {
9761 set i [lsearch -exact $lcnames $ex]
9764 if {$i >= 0} break
9766 break
9769 if {$i >= 0} {
9770 return [lindex $names $i]
9772 return {}
9775 # First check that Tcl/Tk is recent enough
9776 if {[catch {package require Tk 8.4} err]} {
9777 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9778 Gitk requires at least Tcl/Tk 8.4."]
9779 exit 1
9782 # defaults...
9783 set wrcomcmd "git diff-tree --stdin -p --pretty"
9785 set gitencoding {}
9786 catch {
9787 set gitencoding [exec git config --get i18n.commitencoding]
9789 if {$gitencoding == ""} {
9790 set gitencoding "utf-8"
9792 set tclencoding [tcl_encoding $gitencoding]
9793 if {$tclencoding == {}} {
9794 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9797 set mainfont {Helvetica 9}
9798 set textfont {Courier 9}
9799 set uifont {Helvetica 9 bold}
9800 set tabstop 8
9801 set findmergefiles 0
9802 set maxgraphpct 50
9803 set maxwidth 16
9804 set revlistorder 0
9805 set fastdate 0
9806 set uparrowlen 5
9807 set downarrowlen 5
9808 set mingaplen 100
9809 set cmitmode "patch"
9810 set wrapcomment "none"
9811 set showneartags 1
9812 set maxrefs 20
9813 set maxlinelen 200
9814 set showlocalchanges 1
9815 set limitdiffs 1
9816 set datetimeformat "%Y-%m-%d %H:%M:%S"
9817 set autoselect 1
9819 set extdifftool "meld"
9821 set colors {green red blue magenta darkgrey brown orange}
9822 set bgcolor white
9823 set fgcolor black
9824 set diffcolors {red "#00a000" blue}
9825 set diffcontext 3
9826 set ignorespace 0
9827 set selectbgcolor gray85
9829 set circlecolors {white blue gray blue blue}
9831 ## For msgcat loading, first locate the installation location.
9832 if { [info exists ::env(GITK_MSGSDIR)] } {
9833 ## Msgsdir was manually set in the environment.
9834 set gitk_msgsdir $::env(GITK_MSGSDIR)
9835 } else {
9836 ## Let's guess the prefix from argv0.
9837 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
9838 set gitk_libdir [file join $gitk_prefix share gitk lib]
9839 set gitk_msgsdir [file join $gitk_libdir msgs]
9840 unset gitk_prefix
9843 ## Internationalization (i18n) through msgcat and gettext. See
9844 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
9845 package require msgcat
9846 namespace import ::msgcat::mc
9847 ## And eventually load the actual message catalog
9848 ::msgcat::mcload $gitk_msgsdir
9850 catch {source ~/.gitk}
9852 font create optionfont -family sans-serif -size -12
9854 parsefont mainfont $mainfont
9855 eval font create mainfont [fontflags mainfont]
9856 eval font create mainfontbold [fontflags mainfont 1]
9858 parsefont textfont $textfont
9859 eval font create textfont [fontflags textfont]
9860 eval font create textfontbold [fontflags textfont 1]
9862 parsefont uifont $uifont
9863 eval font create uifont [fontflags uifont]
9865 setoptions
9867 # check that we can find a .git directory somewhere...
9868 if {[catch {set gitdir [gitdir]}]} {
9869 show_error {} . [mc "Cannot find a git repository here."]
9870 exit 1
9872 if {![file isdirectory $gitdir]} {
9873 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
9874 exit 1
9877 set selecthead {}
9878 set selectheadid {}
9880 set revtreeargs {}
9881 set cmdline_files {}
9882 set i 0
9883 set revtreeargscmd {}
9884 foreach arg $argv {
9885 switch -glob -- $arg {
9886 "" { }
9887 "--" {
9888 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9889 break
9891 "--select-commit=*" {
9892 set selecthead [string range $arg 16 end]
9894 "--argscmd=*" {
9895 set revtreeargscmd [string range $arg 10 end]
9897 default {
9898 lappend revtreeargs $arg
9901 incr i
9904 if {$selecthead eq "HEAD"} {
9905 set selecthead {}
9908 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9909 # no -- on command line, but some arguments (other than --argscmd)
9910 if {[catch {
9911 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9912 set cmdline_files [split $f "\n"]
9913 set n [llength $cmdline_files]
9914 set revtreeargs [lrange $revtreeargs 0 end-$n]
9915 # Unfortunately git rev-parse doesn't produce an error when
9916 # something is both a revision and a filename. To be consistent
9917 # with git log and git rev-list, check revtreeargs for filenames.
9918 foreach arg $revtreeargs {
9919 if {[file exists $arg]} {
9920 show_error {} . [mc "Ambiguous argument '%s': both revision\
9921 and filename" $arg]
9922 exit 1
9925 } err]} {
9926 # unfortunately we get both stdout and stderr in $err,
9927 # so look for "fatal:".
9928 set i [string first "fatal:" $err]
9929 if {$i > 0} {
9930 set err [string range $err [expr {$i + 6}] end]
9932 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
9933 exit 1
9937 set nullid "0000000000000000000000000000000000000000"
9938 set nullid2 "0000000000000000000000000000000000000001"
9939 set nullfile "/dev/null"
9941 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9943 set runq {}
9944 set history {}
9945 set historyindex 0
9946 set fh_serial 0
9947 set nhl_names {}
9948 set highlight_paths {}
9949 set findpattern {}
9950 set searchdirn -forwards
9951 set boldrows {}
9952 set boldnamerows {}
9953 set diffelide {0 0}
9954 set markingmatches 0
9955 set linkentercount 0
9956 set need_redisplay 0
9957 set nrows_drawn 0
9958 set firsttabstop 0
9960 set nextviewnum 1
9961 set curview 0
9962 set selectedview 0
9963 set selectedhlview [mc "None"]
9964 set highlight_related [mc "None"]
9965 set highlight_files {}
9966 set viewfiles(0) {}
9967 set viewperm(0) 0
9968 set viewargs(0) {}
9969 set viewargscmd(0) {}
9971 set selectedline {}
9972 set numcommits 0
9973 set loginstance 0
9974 set cmdlineok 0
9975 set stopped 0
9976 set stuffsaved 0
9977 set patchnum 0
9978 set lserial 0
9979 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
9980 setcoords
9981 makewindow
9982 # wait for the window to become visible
9983 tkwait visibility .
9984 wm title . "[file tail $argv0]: [file tail [pwd]]"
9985 readrefs
9987 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
9988 # create a view for the files/dirs specified on the command line
9989 set curview 1
9990 set selectedview 1
9991 set nextviewnum 2
9992 set viewname(1) [mc "Command line"]
9993 set viewfiles(1) $cmdline_files
9994 set viewargs(1) $revtreeargs
9995 set viewargscmd(1) $revtreeargscmd
9996 set viewperm(1) 0
9997 set vdatemode(1) 0
9998 addviewmenu 1
9999 .bar.view entryconf [mc "Edit view..."] -state normal
10000 .bar.view entryconf [mc "Delete view"] -state normal
10003 if {[info exists permviews]} {
10004 foreach v $permviews {
10005 set n $nextviewnum
10006 incr nextviewnum
10007 set viewname($n) [lindex $v 0]
10008 set viewfiles($n) [lindex $v 1]
10009 set viewargs($n) [lindex $v 2]
10010 set viewargscmd($n) [lindex $v 3]
10011 set viewperm($n) 1
10012 addviewmenu $n
10015 getcommits {}