Windows ANSI - hook write()
[git/mingw/4msysgit/peterh.git] / gitk-git / gitk
bloba1d176d64359f1c4a3557d90abbca7c6ca553759
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright © 2005-2008 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25 global isonrunq runq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {}} {
30 after idle dorunq
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
41 global runq
43 fileevent $fd readable {}
44 if {$runq eq {}} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc nukefile {fd} {
51 global runq
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
56 } else {
57 incr i
62 proc dorunq {} {
63 global isonrunq runq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set repeat [eval $script]
71 set t1 [clock clicks -milliseconds]
72 set t [expr {$t1 - $t0}]
73 set runq [lrange $runq 1 end]
74 if {$repeat ne {} && $repeat} {
75 if {$fd eq {} || $repeat == 2} {
76 # script returns 1 if it wants to be readded
77 # file readers return 2 if they could do more straight away
78 lappend runq [list $fd $script]
79 } else {
80 fileevent $fd readable [list filereadable $fd $script]
82 } elseif {$fd eq {}} {
83 unset isonrunq($script)
85 set t0 $t1
86 if {$t1 - $tstart >= 80} break
88 if {$runq ne {}} {
89 after idle dorunq
93 proc unmerged_files {files} {
94 global nr_unmerged
96 # find the list of unmerged files
97 set mlist {}
98 set nr_unmerged 0
99 if {[catch {
100 set fd [open "| git ls-files -u" r]
101 } err]} {
102 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
103 exit 1
105 while {[gets $fd line] >= 0} {
106 set i [string first "\t" $line]
107 if {$i < 0} continue
108 set fname [string range $line [expr {$i+1}] end]
109 if {[lsearch -exact $mlist $fname] >= 0} continue
110 incr nr_unmerged
111 if {$files eq {} || [path_filter $files $fname]} {
112 lappend mlist $fname
115 catch {close $fd}
116 return $mlist
119 proc parseviewargs {n arglist} {
120 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
122 set vdatemode($n) 0
123 set vmergeonly($n) 0
124 set glflags {}
125 set diffargs {}
126 set nextisval 0
127 set revargs {}
128 set origargs $arglist
129 set allknown 1
130 set filtered 0
131 set i -1
132 foreach arg $arglist {
133 incr i
134 if {$nextisval} {
135 lappend glflags $arg
136 set nextisval 0
137 continue
139 switch -glob -- $arg {
140 "-d" -
141 "--date-order" {
142 set vdatemode($n) 1
143 # remove from origargs in case we hit an unknown option
144 set origargs [lreplace $origargs $i $i]
145 incr i -1
147 # These request or affect diff output, which we don't want.
148 # Some could be used to set our defaults for diff display.
149 "-[puabwcrRBMC]" -
150 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
151 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
152 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
153 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
154 "--ignore-space-change" - "-U*" - "--unified=*" {
155 lappend diffargs $arg
157 # These cause our parsing of git log's output to fail, or else
158 # they're options we want to set ourselves, so ignore them.
159 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
160 "--name-only" - "--name-status" - "--color" - "--color-words" -
161 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
162 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
163 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
164 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
165 "--objects" - "--objects-edge" - "--reverse" {
167 # These are harmless, and some are even useful
168 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
169 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
170 "--full-history" - "--dense" - "--sparse" -
171 "--follow" - "--left-right" - "--encoding=*" {
172 lappend glflags $arg
174 # These mean that we get a subset of the commits
175 "--diff-filter=*" - "--no-merges" - "--unpacked" -
176 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
177 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
178 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
179 "--remove-empty" - "--first-parent" - "--cherry-pick" -
180 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
181 set filtered 1
182 lappend glflags $arg
184 # This appears to be the only one that has a value as a
185 # separate word following it
186 "-n" {
187 set filtered 1
188 set nextisval 1
189 lappend glflags $arg
191 "--not" {
192 set notflag [expr {!$notflag}]
193 lappend revargs $arg
195 "--all" {
196 lappend revargs $arg
198 "--merge" {
199 set vmergeonly($n) 1
200 # git rev-parse doesn't understand --merge
201 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
203 # Other flag arguments including -<n>
204 "-*" {
205 if {[string is digit -strict [string range $arg 1 end]]} {
206 set filtered 1
207 } else {
208 # a flag argument that we don't recognize;
209 # that means we can't optimize
210 set allknown 0
212 lappend glflags $arg
214 # Non-flag arguments specify commits or ranges of commits
215 default {
216 if {[string match "*...*" $arg]} {
217 lappend revargs --gitk-symmetric-diff-marker
219 lappend revargs $arg
223 set vdflags($n) $diffargs
224 set vflags($n) $glflags
225 set vrevs($n) $revargs
226 set vfiltered($n) $filtered
227 set vorigargs($n) $origargs
228 return $allknown
231 proc parseviewrevs {view revs} {
232 global vposids vnegids
234 if {$revs eq {}} {
235 set revs HEAD
237 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
238 # we get stdout followed by stderr in $err
239 # for an unknown rev, git rev-parse echoes it and then errors out
240 set errlines [split $err "\n"]
241 set badrev {}
242 for {set l 0} {$l < [llength $errlines]} {incr l} {
243 set line [lindex $errlines $l]
244 if {!([string length $line] == 40 && [string is xdigit $line])} {
245 if {[string match "fatal:*" $line]} {
246 if {[string match "fatal: ambiguous argument*" $line]
247 && $badrev ne {}} {
248 if {[llength $badrev] == 1} {
249 set err "unknown revision $badrev"
250 } else {
251 set err "unknown revisions: [join $badrev ", "]"
253 } else {
254 set err [join [lrange $errlines $l end] "\n"]
256 break
258 lappend badrev $line
261 error_popup "Error parsing revisions: $err"
262 return {}
264 set ret {}
265 set pos {}
266 set neg {}
267 set sdm 0
268 foreach id [split $ids "\n"] {
269 if {$id eq "--gitk-symmetric-diff-marker"} {
270 set sdm 4
271 } elseif {[string match "^*" $id]} {
272 if {$sdm != 1} {
273 lappend ret $id
274 if {$sdm == 3} {
275 set sdm 0
278 lappend neg [string range $id 1 end]
279 } else {
280 if {$sdm != 2} {
281 lappend ret $id
282 } else {
283 lset ret end [lindex $ret end]...$id
285 lappend pos $id
287 incr sdm -1
289 set vposids($view) $pos
290 set vnegids($view) $neg
291 return $ret
294 # Start off a git log process and arrange to read its output
295 proc start_rev_list {view} {
296 global startmsecs commitidx viewcomplete curview
297 global commfd leftover tclencoding
298 global viewargs viewargscmd viewfiles vfilelimit
299 global showlocalchanges commitinterest
300 global viewactive loginstance viewinstances vmergeonly
301 global pending_select mainheadid
302 global vcanopt vflags vrevs vorigargs
304 set startmsecs [clock clicks -milliseconds]
305 set commitidx($view) 0
306 # these are set this way for the error exits
307 set viewcomplete($view) 1
308 set viewactive($view) 0
309 varcinit $view
311 set args $viewargs($view)
312 if {$viewargscmd($view) ne {}} {
313 if {[catch {
314 set str [exec sh -c $viewargscmd($view)]
315 } err]} {
316 error_popup "Error executing --argscmd command: $err"
317 return 0
319 set args [concat $args [split $str "\n"]]
321 set vcanopt($view) [parseviewargs $view $args]
323 set files $viewfiles($view)
324 if {$vmergeonly($view)} {
325 set files [unmerged_files $files]
326 if {$files eq {}} {
327 global nr_unmerged
328 if {$nr_unmerged == 0} {
329 error_popup [mc "No files selected: --merge specified but\
330 no files are unmerged."]
331 } else {
332 error_popup [mc "No files selected: --merge specified but\
333 no unmerged files are within file limit."]
335 return 0
338 set vfilelimit($view) $files
340 if {$vcanopt($view)} {
341 set revs [parseviewrevs $view $vrevs($view)]
342 if {$revs eq {}} {
343 return 0
345 set args [concat $vflags($view) $revs]
346 } else {
347 set args $vorigargs($view)
350 if {[catch {
351 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
352 --boundary $args "--" $files] r]
353 } err]} {
354 error_popup "[mc "Error executing git log:"] $err"
355 return 0
357 set i [incr loginstance]
358 set viewinstances($view) [list $i]
359 set commfd($i) $fd
360 set leftover($i) {}
361 if {$showlocalchanges && $mainheadid ne {}} {
362 lappend commitinterest($mainheadid) {dodiffindex}
364 fconfigure $fd -blocking 0 -translation lf -eofchar {}
365 if {$tclencoding != {}} {
366 fconfigure $fd -encoding $tclencoding
368 filerun $fd [list getcommitlines $fd $i $view 0]
369 nowbusy $view [mc "Reading"]
370 if {$view == $curview} {
371 set pending_select $mainheadid
373 set viewcomplete($view) 0
374 set viewactive($view) 1
375 return 1
378 proc stop_rev_list {view} {
379 global commfd viewinstances leftover
381 foreach inst $viewinstances($view) {
382 set fd $commfd($inst)
383 catch {
384 set pid [pid $fd]
385 exec kill $pid
387 catch {close $fd}
388 nukefile $fd
389 unset commfd($inst)
390 unset leftover($inst)
392 set viewinstances($view) {}
395 proc getcommits {} {
396 global canv curview need_redisplay viewactive
398 initlayout
399 if {[start_rev_list $curview]} {
400 show_status [mc "Reading commits..."]
401 set need_redisplay 1
402 } else {
403 show_status [mc "No commits selected"]
407 proc updatecommits {} {
408 global curview vcanopt vorigargs vfilelimit viewinstances
409 global viewactive viewcomplete loginstance tclencoding
410 global startmsecs commfd showneartags showlocalchanges leftover
411 global mainheadid pending_select
412 global isworktree
413 global varcid vposids vnegids vflags vrevs
415 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
416 set oldmainid $mainheadid
417 rereadrefs
418 if {$showlocalchanges} {
419 if {$mainheadid ne $oldmainid} {
420 dohidelocalchanges
422 if {[commitinview $mainheadid $curview]} {
423 dodiffindex
426 set view $curview
427 if {$vcanopt($view)} {
428 set oldpos $vposids($view)
429 set oldneg $vnegids($view)
430 set revs [parseviewrevs $view $vrevs($view)]
431 if {$revs eq {}} {
432 return
434 # note: getting the delta when negative refs change is hard,
435 # and could require multiple git log invocations, so in that
436 # case we ask git log for all the commits (not just the delta)
437 if {$oldneg eq $vnegids($view)} {
438 set newrevs {}
439 set npos 0
440 # take out positive refs that we asked for before or
441 # that we have already seen
442 foreach rev $revs {
443 if {[string length $rev] == 40} {
444 if {[lsearch -exact $oldpos $rev] < 0
445 && ![info exists varcid($view,$rev)]} {
446 lappend newrevs $rev
447 incr npos
449 } else {
450 lappend $newrevs $rev
453 if {$npos == 0} return
454 set revs $newrevs
455 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
457 set args [concat $vflags($view) $revs --not $oldpos]
458 } else {
459 set args $vorigargs($view)
461 if {[catch {
462 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
463 --boundary $args "--" $vfilelimit($view)] r]
464 } err]} {
465 error_popup "Error executing git log: $err"
466 return
468 if {$viewactive($view) == 0} {
469 set startmsecs [clock clicks -milliseconds]
471 set i [incr loginstance]
472 lappend viewinstances($view) $i
473 set commfd($i) $fd
474 set leftover($i) {}
475 fconfigure $fd -blocking 0 -translation lf -eofchar {}
476 if {$tclencoding != {}} {
477 fconfigure $fd -encoding $tclencoding
479 filerun $fd [list getcommitlines $fd $i $view 1]
480 incr viewactive($view)
481 set viewcomplete($view) 0
482 set pending_select $mainheadid
483 nowbusy $view "Reading"
484 if {$showneartags} {
485 getallcommits
489 proc reloadcommits {} {
490 global curview viewcomplete selectedline currentid thickerline
491 global showneartags treediffs commitinterest cached_commitrow
492 global targetid
494 if {!$viewcomplete($curview)} {
495 stop_rev_list $curview
497 resetvarcs $curview
498 set selectedline {}
499 catch {unset currentid}
500 catch {unset thickerline}
501 catch {unset treediffs}
502 readrefs
503 changedrefs
504 if {$showneartags} {
505 getallcommits
507 clear_display
508 catch {unset commitinterest}
509 catch {unset cached_commitrow}
510 catch {unset targetid}
511 setcanvscroll
512 getcommits
513 return 0
516 # This makes a string representation of a positive integer which
517 # sorts as a string in numerical order
518 proc strrep {n} {
519 if {$n < 16} {
520 return [format "%x" $n]
521 } elseif {$n < 256} {
522 return [format "x%.2x" $n]
523 } elseif {$n < 65536} {
524 return [format "y%.4x" $n]
526 return [format "z%.8x" $n]
529 # Procedures used in reordering commits from git log (without
530 # --topo-order) into the order for display.
532 proc varcinit {view} {
533 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
534 global vtokmod varcmod vrowmod varcix vlastins
536 set varcstart($view) {{}}
537 set vupptr($view) {0}
538 set vdownptr($view) {0}
539 set vleftptr($view) {0}
540 set vbackptr($view) {0}
541 set varctok($view) {{}}
542 set varcrow($view) {{}}
543 set vtokmod($view) {}
544 set varcmod($view) 0
545 set vrowmod($view) 0
546 set varcix($view) {{}}
547 set vlastins($view) {0}
550 proc resetvarcs {view} {
551 global varcid varccommits parents children vseedcount ordertok
553 foreach vid [array names varcid $view,*] {
554 unset varcid($vid)
555 unset children($vid)
556 unset parents($vid)
558 # some commits might have children but haven't been seen yet
559 foreach vid [array names children $view,*] {
560 unset children($vid)
562 foreach va [array names varccommits $view,*] {
563 unset varccommits($va)
565 foreach vd [array names vseedcount $view,*] {
566 unset vseedcount($vd)
568 catch {unset ordertok}
571 # returns a list of the commits with no children
572 proc seeds {v} {
573 global vdownptr vleftptr varcstart
575 set ret {}
576 set a [lindex $vdownptr($v) 0]
577 while {$a != 0} {
578 lappend ret [lindex $varcstart($v) $a]
579 set a [lindex $vleftptr($v) $a]
581 return $ret
584 proc newvarc {view id} {
585 global varcid varctok parents children vdatemode
586 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
587 global commitdata commitinfo vseedcount varccommits vlastins
589 set a [llength $varctok($view)]
590 set vid $view,$id
591 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
592 if {![info exists commitinfo($id)]} {
593 parsecommit $id $commitdata($id) 1
595 set cdate [lindex $commitinfo($id) 4]
596 if {![string is integer -strict $cdate]} {
597 set cdate 0
599 if {![info exists vseedcount($view,$cdate)]} {
600 set vseedcount($view,$cdate) -1
602 set c [incr vseedcount($view,$cdate)]
603 set cdate [expr {$cdate ^ 0xffffffff}]
604 set tok "s[strrep $cdate][strrep $c]"
605 } else {
606 set tok {}
608 set ka 0
609 if {[llength $children($vid)] > 0} {
610 set kid [lindex $children($vid) end]
611 set k $varcid($view,$kid)
612 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
613 set ki $kid
614 set ka $k
615 set tok [lindex $varctok($view) $k]
618 if {$ka != 0} {
619 set i [lsearch -exact $parents($view,$ki) $id]
620 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
621 append tok [strrep $j]
623 set c [lindex $vlastins($view) $ka]
624 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
625 set c $ka
626 set b [lindex $vdownptr($view) $ka]
627 } else {
628 set b [lindex $vleftptr($view) $c]
630 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
631 set c $b
632 set b [lindex $vleftptr($view) $c]
634 if {$c == $ka} {
635 lset vdownptr($view) $ka $a
636 lappend vbackptr($view) 0
637 } else {
638 lset vleftptr($view) $c $a
639 lappend vbackptr($view) $c
641 lset vlastins($view) $ka $a
642 lappend vupptr($view) $ka
643 lappend vleftptr($view) $b
644 if {$b != 0} {
645 lset vbackptr($view) $b $a
647 lappend varctok($view) $tok
648 lappend varcstart($view) $id
649 lappend vdownptr($view) 0
650 lappend varcrow($view) {}
651 lappend varcix($view) {}
652 set varccommits($view,$a) {}
653 lappend vlastins($view) 0
654 return $a
657 proc splitvarc {p v} {
658 global varcid varcstart varccommits varctok
659 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
661 set oa $varcid($v,$p)
662 set ac $varccommits($v,$oa)
663 set i [lsearch -exact $varccommits($v,$oa) $p]
664 if {$i <= 0} return
665 set na [llength $varctok($v)]
666 # "%" sorts before "0"...
667 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
668 lappend varctok($v) $tok
669 lappend varcrow($v) {}
670 lappend varcix($v) {}
671 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
672 set varccommits($v,$na) [lrange $ac $i end]
673 lappend varcstart($v) $p
674 foreach id $varccommits($v,$na) {
675 set varcid($v,$id) $na
677 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
678 lappend vlastins($v) [lindex $vlastins($v) $oa]
679 lset vdownptr($v) $oa $na
680 lset vlastins($v) $oa 0
681 lappend vupptr($v) $oa
682 lappend vleftptr($v) 0
683 lappend vbackptr($v) 0
684 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
685 lset vupptr($v) $b $na
689 proc renumbervarc {a v} {
690 global parents children varctok varcstart varccommits
691 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
693 set t1 [clock clicks -milliseconds]
694 set todo {}
695 set isrelated($a) 1
696 set kidchanged($a) 1
697 set ntot 0
698 while {$a != 0} {
699 if {[info exists isrelated($a)]} {
700 lappend todo $a
701 set id [lindex $varccommits($v,$a) end]
702 foreach p $parents($v,$id) {
703 if {[info exists varcid($v,$p)]} {
704 set isrelated($varcid($v,$p)) 1
708 incr ntot
709 set b [lindex $vdownptr($v) $a]
710 if {$b == 0} {
711 while {$a != 0} {
712 set b [lindex $vleftptr($v) $a]
713 if {$b != 0} break
714 set a [lindex $vupptr($v) $a]
717 set a $b
719 foreach a $todo {
720 if {![info exists kidchanged($a)]} continue
721 set id [lindex $varcstart($v) $a]
722 if {[llength $children($v,$id)] > 1} {
723 set children($v,$id) [lsort -command [list vtokcmp $v] \
724 $children($v,$id)]
726 set oldtok [lindex $varctok($v) $a]
727 if {!$vdatemode($v)} {
728 set tok {}
729 } else {
730 set tok $oldtok
732 set ka 0
733 set kid [last_real_child $v,$id]
734 if {$kid ne {}} {
735 set k $varcid($v,$kid)
736 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
737 set ki $kid
738 set ka $k
739 set tok [lindex $varctok($v) $k]
742 if {$ka != 0} {
743 set i [lsearch -exact $parents($v,$ki) $id]
744 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
745 append tok [strrep $j]
747 if {$tok eq $oldtok} {
748 continue
750 set id [lindex $varccommits($v,$a) end]
751 foreach p $parents($v,$id) {
752 if {[info exists varcid($v,$p)]} {
753 set kidchanged($varcid($v,$p)) 1
754 } else {
755 set sortkids($p) 1
758 lset varctok($v) $a $tok
759 set b [lindex $vupptr($v) $a]
760 if {$b != $ka} {
761 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
762 modify_arc $v $ka
764 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
765 modify_arc $v $b
767 set c [lindex $vbackptr($v) $a]
768 set d [lindex $vleftptr($v) $a]
769 if {$c == 0} {
770 lset vdownptr($v) $b $d
771 } else {
772 lset vleftptr($v) $c $d
774 if {$d != 0} {
775 lset vbackptr($v) $d $c
777 if {[lindex $vlastins($v) $b] == $a} {
778 lset vlastins($v) $b $c
780 lset vupptr($v) $a $ka
781 set c [lindex $vlastins($v) $ka]
782 if {$c == 0 || \
783 [string compare $tok [lindex $varctok($v) $c]] < 0} {
784 set c $ka
785 set b [lindex $vdownptr($v) $ka]
786 } else {
787 set b [lindex $vleftptr($v) $c]
789 while {$b != 0 && \
790 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
791 set c $b
792 set b [lindex $vleftptr($v) $c]
794 if {$c == $ka} {
795 lset vdownptr($v) $ka $a
796 lset vbackptr($v) $a 0
797 } else {
798 lset vleftptr($v) $c $a
799 lset vbackptr($v) $a $c
801 lset vleftptr($v) $a $b
802 if {$b != 0} {
803 lset vbackptr($v) $b $a
805 lset vlastins($v) $ka $a
808 foreach id [array names sortkids] {
809 if {[llength $children($v,$id)] > 1} {
810 set children($v,$id) [lsort -command [list vtokcmp $v] \
811 $children($v,$id)]
814 set t2 [clock clicks -milliseconds]
815 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
818 # Fix up the graph after we have found out that in view $v,
819 # $p (a commit that we have already seen) is actually the parent
820 # of the last commit in arc $a.
821 proc fix_reversal {p a v} {
822 global varcid varcstart varctok vupptr
824 set pa $varcid($v,$p)
825 if {$p ne [lindex $varcstart($v) $pa]} {
826 splitvarc $p $v
827 set pa $varcid($v,$p)
829 # seeds always need to be renumbered
830 if {[lindex $vupptr($v) $pa] == 0 ||
831 [string compare [lindex $varctok($v) $a] \
832 [lindex $varctok($v) $pa]] > 0} {
833 renumbervarc $pa $v
837 proc insertrow {id p v} {
838 global cmitlisted children parents varcid varctok vtokmod
839 global varccommits ordertok commitidx numcommits curview
840 global targetid targetrow
842 readcommit $id
843 set vid $v,$id
844 set cmitlisted($vid) 1
845 set children($vid) {}
846 set parents($vid) [list $p]
847 set a [newvarc $v $id]
848 set varcid($vid) $a
849 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
850 modify_arc $v $a
852 lappend varccommits($v,$a) $id
853 set vp $v,$p
854 if {[llength [lappend children($vp) $id]] > 1} {
855 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
856 catch {unset ordertok}
858 fix_reversal $p $a $v
859 incr commitidx($v)
860 if {$v == $curview} {
861 set numcommits $commitidx($v)
862 setcanvscroll
863 if {[info exists targetid]} {
864 if {![comes_before $targetid $p]} {
865 incr targetrow
871 proc insertfakerow {id p} {
872 global varcid varccommits parents children cmitlisted
873 global commitidx varctok vtokmod targetid targetrow curview numcommits
875 set v $curview
876 set a $varcid($v,$p)
877 set i [lsearch -exact $varccommits($v,$a) $p]
878 if {$i < 0} {
879 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
880 return
882 set children($v,$id) {}
883 set parents($v,$id) [list $p]
884 set varcid($v,$id) $a
885 lappend children($v,$p) $id
886 set cmitlisted($v,$id) 1
887 set numcommits [incr commitidx($v)]
888 # note we deliberately don't update varcstart($v) even if $i == 0
889 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
890 modify_arc $v $a $i
891 if {[info exists targetid]} {
892 if {![comes_before $targetid $p]} {
893 incr targetrow
896 setcanvscroll
897 drawvisible
900 proc removefakerow {id} {
901 global varcid varccommits parents children commitidx
902 global varctok vtokmod cmitlisted currentid selectedline
903 global targetid curview numcommits
905 set v $curview
906 if {[llength $parents($v,$id)] != 1} {
907 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
908 return
910 set p [lindex $parents($v,$id) 0]
911 set a $varcid($v,$id)
912 set i [lsearch -exact $varccommits($v,$a) $id]
913 if {$i < 0} {
914 puts "oops: removefakerow can't find [shortids $id] on arc $a"
915 return
917 unset varcid($v,$id)
918 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
919 unset parents($v,$id)
920 unset children($v,$id)
921 unset cmitlisted($v,$id)
922 set numcommits [incr commitidx($v) -1]
923 set j [lsearch -exact $children($v,$p) $id]
924 if {$j >= 0} {
925 set children($v,$p) [lreplace $children($v,$p) $j $j]
927 modify_arc $v $a $i
928 if {[info exist currentid] && $id eq $currentid} {
929 unset currentid
930 set selectedline {}
932 if {[info exists targetid] && $targetid eq $id} {
933 set targetid $p
935 setcanvscroll
936 drawvisible
939 proc first_real_child {vp} {
940 global children nullid nullid2
942 foreach id $children($vp) {
943 if {$id ne $nullid && $id ne $nullid2} {
944 return $id
947 return {}
950 proc last_real_child {vp} {
951 global children nullid nullid2
953 set kids $children($vp)
954 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
955 set id [lindex $kids $i]
956 if {$id ne $nullid && $id ne $nullid2} {
957 return $id
960 return {}
963 proc vtokcmp {v a b} {
964 global varctok varcid
966 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
967 [lindex $varctok($v) $varcid($v,$b)]]
970 # This assumes that if lim is not given, the caller has checked that
971 # arc a's token is less than $vtokmod($v)
972 proc modify_arc {v a {lim {}}} {
973 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
975 if {$lim ne {}} {
976 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
977 if {$c > 0} return
978 if {$c == 0} {
979 set r [lindex $varcrow($v) $a]
980 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
983 set vtokmod($v) [lindex $varctok($v) $a]
984 set varcmod($v) $a
985 if {$v == $curview} {
986 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
987 set a [lindex $vupptr($v) $a]
988 set lim {}
990 set r 0
991 if {$a != 0} {
992 if {$lim eq {}} {
993 set lim [llength $varccommits($v,$a)]
995 set r [expr {[lindex $varcrow($v) $a] + $lim}]
997 set vrowmod($v) $r
998 undolayout $r
1002 proc update_arcrows {v} {
1003 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1004 global varcid vrownum varcorder varcix varccommits
1005 global vupptr vdownptr vleftptr varctok
1006 global displayorder parentlist curview cached_commitrow
1008 if {$vrowmod($v) == $commitidx($v)} return
1009 if {$v == $curview} {
1010 if {[llength $displayorder] > $vrowmod($v)} {
1011 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1012 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1014 catch {unset cached_commitrow}
1016 set narctot [expr {[llength $varctok($v)] - 1}]
1017 set a $varcmod($v)
1018 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1019 # go up the tree until we find something that has a row number,
1020 # or we get to a seed
1021 set a [lindex $vupptr($v) $a]
1023 if {$a == 0} {
1024 set a [lindex $vdownptr($v) 0]
1025 if {$a == 0} return
1026 set vrownum($v) {0}
1027 set varcorder($v) [list $a]
1028 lset varcix($v) $a 0
1029 lset varcrow($v) $a 0
1030 set arcn 0
1031 set row 0
1032 } else {
1033 set arcn [lindex $varcix($v) $a]
1034 if {[llength $vrownum($v)] > $arcn + 1} {
1035 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1036 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1038 set row [lindex $varcrow($v) $a]
1040 while {1} {
1041 set p $a
1042 incr row [llength $varccommits($v,$a)]
1043 # go down if possible
1044 set b [lindex $vdownptr($v) $a]
1045 if {$b == 0} {
1046 # if not, go left, or go up until we can go left
1047 while {$a != 0} {
1048 set b [lindex $vleftptr($v) $a]
1049 if {$b != 0} break
1050 set a [lindex $vupptr($v) $a]
1052 if {$a == 0} break
1054 set a $b
1055 incr arcn
1056 lappend vrownum($v) $row
1057 lappend varcorder($v) $a
1058 lset varcix($v) $a $arcn
1059 lset varcrow($v) $a $row
1061 set vtokmod($v) [lindex $varctok($v) $p]
1062 set varcmod($v) $p
1063 set vrowmod($v) $row
1064 if {[info exists currentid]} {
1065 set selectedline [rowofcommit $currentid]
1069 # Test whether view $v contains commit $id
1070 proc commitinview {id v} {
1071 global varcid
1073 return [info exists varcid($v,$id)]
1076 # Return the row number for commit $id in the current view
1077 proc rowofcommit {id} {
1078 global varcid varccommits varcrow curview cached_commitrow
1079 global varctok vtokmod
1081 set v $curview
1082 if {![info exists varcid($v,$id)]} {
1083 puts "oops rowofcommit no arc for [shortids $id]"
1084 return {}
1086 set a $varcid($v,$id)
1087 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1088 update_arcrows $v
1090 if {[info exists cached_commitrow($id)]} {
1091 return $cached_commitrow($id)
1093 set i [lsearch -exact $varccommits($v,$a) $id]
1094 if {$i < 0} {
1095 puts "oops didn't find commit [shortids $id] in arc $a"
1096 return {}
1098 incr i [lindex $varcrow($v) $a]
1099 set cached_commitrow($id) $i
1100 return $i
1103 # Returns 1 if a is on an earlier row than b, otherwise 0
1104 proc comes_before {a b} {
1105 global varcid varctok curview
1107 set v $curview
1108 if {$a eq $b || ![info exists varcid($v,$a)] || \
1109 ![info exists varcid($v,$b)]} {
1110 return 0
1112 if {$varcid($v,$a) != $varcid($v,$b)} {
1113 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1114 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1116 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1119 proc bsearch {l elt} {
1120 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1121 return 0
1123 set lo 0
1124 set hi [llength $l]
1125 while {$hi - $lo > 1} {
1126 set mid [expr {int(($lo + $hi) / 2)}]
1127 set t [lindex $l $mid]
1128 if {$elt < $t} {
1129 set hi $mid
1130 } elseif {$elt > $t} {
1131 set lo $mid
1132 } else {
1133 return $mid
1136 return $lo
1139 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1140 proc make_disporder {start end} {
1141 global vrownum curview commitidx displayorder parentlist
1142 global varccommits varcorder parents vrowmod varcrow
1143 global d_valid_start d_valid_end
1145 if {$end > $vrowmod($curview)} {
1146 update_arcrows $curview
1148 set ai [bsearch $vrownum($curview) $start]
1149 set start [lindex $vrownum($curview) $ai]
1150 set narc [llength $vrownum($curview)]
1151 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1152 set a [lindex $varcorder($curview) $ai]
1153 set l [llength $displayorder]
1154 set al [llength $varccommits($curview,$a)]
1155 if {$l < $r + $al} {
1156 if {$l < $r} {
1157 set pad [ntimes [expr {$r - $l}] {}]
1158 set displayorder [concat $displayorder $pad]
1159 set parentlist [concat $parentlist $pad]
1160 } elseif {$l > $r} {
1161 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1162 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1164 foreach id $varccommits($curview,$a) {
1165 lappend displayorder $id
1166 lappend parentlist $parents($curview,$id)
1168 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1169 set i $r
1170 foreach id $varccommits($curview,$a) {
1171 lset displayorder $i $id
1172 lset parentlist $i $parents($curview,$id)
1173 incr i
1176 incr r $al
1180 proc commitonrow {row} {
1181 global displayorder
1183 set id [lindex $displayorder $row]
1184 if {$id eq {}} {
1185 make_disporder $row [expr {$row + 1}]
1186 set id [lindex $displayorder $row]
1188 return $id
1191 proc closevarcs {v} {
1192 global varctok varccommits varcid parents children
1193 global cmitlisted commitidx commitinterest vtokmod
1195 set missing_parents 0
1196 set scripts {}
1197 set narcs [llength $varctok($v)]
1198 for {set a 1} {$a < $narcs} {incr a} {
1199 set id [lindex $varccommits($v,$a) end]
1200 foreach p $parents($v,$id) {
1201 if {[info exists varcid($v,$p)]} continue
1202 # add p as a new commit
1203 incr missing_parents
1204 set cmitlisted($v,$p) 0
1205 set parents($v,$p) {}
1206 if {[llength $children($v,$p)] == 1 &&
1207 [llength $parents($v,$id)] == 1} {
1208 set b $a
1209 } else {
1210 set b [newvarc $v $p]
1212 set varcid($v,$p) $b
1213 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1214 modify_arc $v $b
1216 lappend varccommits($v,$b) $p
1217 incr commitidx($v)
1218 if {[info exists commitinterest($p)]} {
1219 foreach script $commitinterest($p) {
1220 lappend scripts [string map [list "%I" $p] $script]
1222 unset commitinterest($id)
1226 if {$missing_parents > 0} {
1227 foreach s $scripts {
1228 eval $s
1233 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1234 # Assumes we already have an arc for $rwid.
1235 proc rewrite_commit {v id rwid} {
1236 global children parents varcid varctok vtokmod varccommits
1238 foreach ch $children($v,$id) {
1239 # make $rwid be $ch's parent in place of $id
1240 set i [lsearch -exact $parents($v,$ch) $id]
1241 if {$i < 0} {
1242 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1244 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1245 # add $ch to $rwid's children and sort the list if necessary
1246 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1247 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1248 $children($v,$rwid)]
1250 # fix the graph after joining $id to $rwid
1251 set a $varcid($v,$ch)
1252 fix_reversal $rwid $a $v
1253 # parentlist is wrong for the last element of arc $a
1254 # even if displayorder is right, hence the 3rd arg here
1255 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1259 proc getcommitlines {fd inst view updating} {
1260 global cmitlisted commitinterest leftover
1261 global commitidx commitdata vdatemode
1262 global parents children curview hlview
1263 global idpending ordertok
1264 global varccommits varcid varctok vtokmod vfilelimit
1266 set stuff [read $fd 500000]
1267 # git log doesn't terminate the last commit with a null...
1268 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1269 set stuff "\0"
1271 if {$stuff == {}} {
1272 if {![eof $fd]} {
1273 return 1
1275 global commfd viewcomplete viewactive viewname
1276 global viewinstances
1277 unset commfd($inst)
1278 set i [lsearch -exact $viewinstances($view) $inst]
1279 if {$i >= 0} {
1280 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1282 # set it blocking so we wait for the process to terminate
1283 fconfigure $fd -blocking 1
1284 if {[catch {close $fd} err]} {
1285 set fv {}
1286 if {$view != $curview} {
1287 set fv " for the \"$viewname($view)\" view"
1289 if {[string range $err 0 4] == "usage"} {
1290 set err "Gitk: error reading commits$fv:\
1291 bad arguments to git log."
1292 if {$viewname($view) eq "Command line"} {
1293 append err \
1294 " (Note: arguments to gitk are passed to git log\
1295 to allow selection of commits to be displayed.)"
1297 } else {
1298 set err "Error reading commits$fv: $err"
1300 error_popup $err
1302 if {[incr viewactive($view) -1] <= 0} {
1303 set viewcomplete($view) 1
1304 # Check if we have seen any ids listed as parents that haven't
1305 # appeared in the list
1306 closevarcs $view
1307 notbusy $view
1309 if {$view == $curview} {
1310 run chewcommits
1312 return 0
1314 set start 0
1315 set gotsome 0
1316 set scripts {}
1317 while 1 {
1318 set i [string first "\0" $stuff $start]
1319 if {$i < 0} {
1320 append leftover($inst) [string range $stuff $start end]
1321 break
1323 if {$start == 0} {
1324 set cmit $leftover($inst)
1325 append cmit [string range $stuff 0 [expr {$i - 1}]]
1326 set leftover($inst) {}
1327 } else {
1328 set cmit [string range $stuff $start [expr {$i - 1}]]
1330 set start [expr {$i + 1}]
1331 set j [string first "\n" $cmit]
1332 set ok 0
1333 set listed 1
1334 if {$j >= 0 && [string match "commit *" $cmit]} {
1335 set ids [string range $cmit 7 [expr {$j - 1}]]
1336 if {[string match {[-^<>]*} $ids]} {
1337 switch -- [string index $ids 0] {
1338 "-" {set listed 0}
1339 "^" {set listed 2}
1340 "<" {set listed 3}
1341 ">" {set listed 4}
1343 set ids [string range $ids 1 end]
1345 set ok 1
1346 foreach id $ids {
1347 if {[string length $id] != 40} {
1348 set ok 0
1349 break
1353 if {!$ok} {
1354 set shortcmit $cmit
1355 if {[string length $shortcmit] > 80} {
1356 set shortcmit "[string range $shortcmit 0 80]..."
1358 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1359 exit 1
1361 set id [lindex $ids 0]
1362 set vid $view,$id
1364 if {!$listed && $updating && ![info exists varcid($vid)] &&
1365 $vfilelimit($view) ne {}} {
1366 # git log doesn't rewrite parents for unlisted commits
1367 # when doing path limiting, so work around that here
1368 # by working out the rewritten parent with git rev-list
1369 # and if we already know about it, using the rewritten
1370 # parent as a substitute parent for $id's children.
1371 if {![catch {
1372 set rwid [exec git rev-list --first-parent --max-count=1 \
1373 $id -- $vfilelimit($view)]
1374 }]} {
1375 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1376 # use $rwid in place of $id
1377 rewrite_commit $view $id $rwid
1378 continue
1383 set a 0
1384 if {[info exists varcid($vid)]} {
1385 if {$cmitlisted($vid) || !$listed} continue
1386 set a $varcid($vid)
1388 if {$listed} {
1389 set olds [lrange $ids 1 end]
1390 } else {
1391 set olds {}
1393 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1394 set cmitlisted($vid) $listed
1395 set parents($vid) $olds
1396 if {![info exists children($vid)]} {
1397 set children($vid) {}
1398 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1399 set k [lindex $children($vid) 0]
1400 if {[llength $parents($view,$k)] == 1 &&
1401 (!$vdatemode($view) ||
1402 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1403 set a $varcid($view,$k)
1406 if {$a == 0} {
1407 # new arc
1408 set a [newvarc $view $id]
1410 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1411 modify_arc $view $a
1413 if {![info exists varcid($vid)]} {
1414 set varcid($vid) $a
1415 lappend varccommits($view,$a) $id
1416 incr commitidx($view)
1419 set i 0
1420 foreach p $olds {
1421 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1422 set vp $view,$p
1423 if {[llength [lappend children($vp) $id]] > 1 &&
1424 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1425 set children($vp) [lsort -command [list vtokcmp $view] \
1426 $children($vp)]
1427 catch {unset ordertok}
1429 if {[info exists varcid($view,$p)]} {
1430 fix_reversal $p $a $view
1433 incr i
1436 if {[info exists commitinterest($id)]} {
1437 foreach script $commitinterest($id) {
1438 lappend scripts [string map [list "%I" $id] $script]
1440 unset commitinterest($id)
1442 set gotsome 1
1444 if {$gotsome} {
1445 global numcommits hlview
1447 if {$view == $curview} {
1448 set numcommits $commitidx($view)
1449 run chewcommits
1451 if {[info exists hlview] && $view == $hlview} {
1452 # we never actually get here...
1453 run vhighlightmore
1455 foreach s $scripts {
1456 eval $s
1459 return 2
1462 proc chewcommits {} {
1463 global curview hlview viewcomplete
1464 global pending_select
1466 layoutmore
1467 if {$viewcomplete($curview)} {
1468 global commitidx varctok
1469 global numcommits startmsecs
1471 if {[info exists pending_select]} {
1472 set row [first_real_row]
1473 selectline $row 1
1475 if {$commitidx($curview) > 0} {
1476 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1477 #puts "overall $ms ms for $numcommits commits"
1478 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1479 } else {
1480 show_status [mc "No commits selected"]
1482 notbusy layout
1484 return 0
1487 proc readcommit {id} {
1488 if {[catch {set contents [exec git cat-file commit $id]}]} return
1489 parsecommit $id $contents 0
1492 proc parsecommit {id contents listed} {
1493 global commitinfo cdate
1495 set inhdr 1
1496 set comment {}
1497 set headline {}
1498 set auname {}
1499 set audate {}
1500 set comname {}
1501 set comdate {}
1502 set hdrend [string first "\n\n" $contents]
1503 if {$hdrend < 0} {
1504 # should never happen...
1505 set hdrend [string length $contents]
1507 set header [string range $contents 0 [expr {$hdrend - 1}]]
1508 set comment [string range $contents [expr {$hdrend + 2}] end]
1509 foreach line [split $header "\n"] {
1510 set tag [lindex $line 0]
1511 if {$tag == "author"} {
1512 set audate [lindex $line end-1]
1513 set auname [lrange $line 1 end-2]
1514 } elseif {$tag == "committer"} {
1515 set comdate [lindex $line end-1]
1516 set comname [lrange $line 1 end-2]
1519 set headline {}
1520 # take the first non-blank line of the comment as the headline
1521 set headline [string trimleft $comment]
1522 set i [string first "\n" $headline]
1523 if {$i >= 0} {
1524 set headline [string range $headline 0 $i]
1526 set headline [string trimright $headline]
1527 set i [string first "\r" $headline]
1528 if {$i >= 0} {
1529 set headline [string trimright [string range $headline 0 $i]]
1531 if {!$listed} {
1532 # git log indents the comment by 4 spaces;
1533 # if we got this via git cat-file, add the indentation
1534 set newcomment {}
1535 foreach line [split $comment "\n"] {
1536 append newcomment " "
1537 append newcomment $line
1538 append newcomment "\n"
1540 set comment $newcomment
1542 if {$comdate != {}} {
1543 set cdate($id) $comdate
1545 set commitinfo($id) [list $headline $auname $audate \
1546 $comname $comdate $comment]
1549 proc getcommit {id} {
1550 global commitdata commitinfo
1552 if {[info exists commitdata($id)]} {
1553 parsecommit $id $commitdata($id) 1
1554 } else {
1555 readcommit $id
1556 if {![info exists commitinfo($id)]} {
1557 set commitinfo($id) [list [mc "No commit information available"]]
1560 return 1
1563 proc readrefs {} {
1564 global tagids idtags headids idheads tagobjid
1565 global otherrefids idotherrefs mainhead mainheadid
1567 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1568 catch {unset $v}
1570 set refd [open [list | git show-ref -d] r]
1571 while {[gets $refd line] >= 0} {
1572 if {[string index $line 40] ne " "} continue
1573 set id [string range $line 0 39]
1574 set ref [string range $line 41 end]
1575 if {![string match "refs/*" $ref]} continue
1576 set name [string range $ref 5 end]
1577 if {[string match "remotes/*" $name]} {
1578 if {![string match "*/HEAD" $name]} {
1579 set headids($name) $id
1580 lappend idheads($id) $name
1582 } elseif {[string match "heads/*" $name]} {
1583 set name [string range $name 6 end]
1584 set headids($name) $id
1585 lappend idheads($id) $name
1586 } elseif {[string match "tags/*" $name]} {
1587 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1588 # which is what we want since the former is the commit ID
1589 set name [string range $name 5 end]
1590 if {[string match "*^{}" $name]} {
1591 set name [string range $name 0 end-3]
1592 } else {
1593 set tagobjid($name) $id
1595 set tagids($name) $id
1596 lappend idtags($id) $name
1597 } else {
1598 set otherrefids($name) $id
1599 lappend idotherrefs($id) $name
1602 catch {close $refd}
1603 set mainhead {}
1604 set mainheadid {}
1605 catch {
1606 set mainheadid [exec git rev-parse HEAD]
1607 set thehead [exec git symbolic-ref HEAD]
1608 if {[string match "refs/heads/*" $thehead]} {
1609 set mainhead [string range $thehead 11 end]
1614 # skip over fake commits
1615 proc first_real_row {} {
1616 global nullid nullid2 numcommits
1618 for {set row 0} {$row < $numcommits} {incr row} {
1619 set id [commitonrow $row]
1620 if {$id ne $nullid && $id ne $nullid2} {
1621 break
1624 return $row
1627 # update things for a head moved to a child of its previous location
1628 proc movehead {id name} {
1629 global headids idheads
1631 removehead $headids($name) $name
1632 set headids($name) $id
1633 lappend idheads($id) $name
1636 # update things when a head has been removed
1637 proc removehead {id name} {
1638 global headids idheads
1640 if {$idheads($id) eq $name} {
1641 unset idheads($id)
1642 } else {
1643 set i [lsearch -exact $idheads($id) $name]
1644 if {$i >= 0} {
1645 set idheads($id) [lreplace $idheads($id) $i $i]
1648 unset headids($name)
1651 proc show_error {w top msg} {
1652 message $w.m -text $msg -justify center -aspect 400
1653 pack $w.m -side top -fill x -padx 20 -pady 20
1654 button $w.ok -text [mc OK] -command "destroy $top"
1655 pack $w.ok -side bottom -fill x
1656 bind $top <Visibility> "grab $top; focus $top"
1657 bind $top <Key-Return> "destroy $top"
1658 tkwait window $top
1661 proc error_popup msg {
1662 set w .error
1663 toplevel $w
1664 wm transient $w .
1665 show_error $w $w $msg
1668 proc confirm_popup msg {
1669 global confirm_ok
1670 set confirm_ok 0
1671 set w .confirm
1672 toplevel $w
1673 wm transient $w .
1674 message $w.m -text $msg -justify center -aspect 400
1675 pack $w.m -side top -fill x -padx 20 -pady 20
1676 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1677 pack $w.ok -side left -fill x
1678 button $w.cancel -text [mc Cancel] -command "destroy $w"
1679 pack $w.cancel -side right -fill x
1680 bind $w <Visibility> "grab $w; focus $w"
1681 tkwait window $w
1682 return $confirm_ok
1685 proc setoptions {} {
1686 option add *Panedwindow.showHandle 1 startupFile
1687 option add *Panedwindow.sashRelief raised startupFile
1688 option add *Button.font uifont startupFile
1689 option add *Checkbutton.font uifont startupFile
1690 option add *Radiobutton.font uifont startupFile
1691 option add *Menu.font uifont startupFile
1692 option add *Menubutton.font uifont startupFile
1693 option add *Label.font uifont startupFile
1694 option add *Message.font uifont startupFile
1695 option add *Entry.font uifont startupFile
1698 proc makewindow {} {
1699 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1700 global tabstop
1701 global findtype findtypemenu findloc findstring fstring geometry
1702 global entries sha1entry sha1string sha1but
1703 global diffcontextstring diffcontext
1704 global ignorespace
1705 global maincursor textcursor curtextcursor
1706 global rowctxmenu fakerowmenu mergemax wrapcomment
1707 global highlight_files gdttype
1708 global searchstring sstring
1709 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1710 global headctxmenu progresscanv progressitem progresscoords statusw
1711 global fprogitem fprogcoord lastprogupdate progupdatepending
1712 global rprogitem rprogcoord rownumsel numcommits
1713 global have_tk85
1715 menu .bar
1716 .bar add cascade -label [mc "File"] -menu .bar.file
1717 menu .bar.file
1718 .bar.file add command -label [mc "Update"] -command updatecommits
1719 .bar.file add command -label [mc "Reload"] -command reloadcommits
1720 .bar.file add command -label [mc "Reread references"] -command rereadrefs
1721 .bar.file add command -label [mc "List references"] -command showrefs
1722 .bar.file add command -label [mc "Quit"] -command doquit
1723 menu .bar.edit
1724 .bar add cascade -label [mc "Edit"] -menu .bar.edit
1725 .bar.edit add command -label [mc "Preferences"] -command doprefs
1727 menu .bar.view
1728 .bar add cascade -label [mc "View"] -menu .bar.view
1729 .bar.view add command -label [mc "New view..."] -command {newview 0}
1730 .bar.view add command -label [mc "Edit view..."] -command editview \
1731 -state disabled
1732 .bar.view add command -label [mc "Delete view"] -command delview -state disabled
1733 .bar.view add separator
1734 .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
1735 -variable selectedview -value 0
1737 menu .bar.help
1738 .bar add cascade -label [mc "Help"] -menu .bar.help
1739 .bar.help add command -label [mc "About gitk"] -command about
1740 .bar.help add command -label [mc "Key bindings"] -command keys
1741 .bar.help configure
1742 . configure -menu .bar
1744 # the gui has upper and lower half, parts of a paned window.
1745 panedwindow .ctop -orient vertical
1747 # possibly use assumed geometry
1748 if {![info exists geometry(pwsash0)]} {
1749 set geometry(topheight) [expr {15 * $linespc}]
1750 set geometry(topwidth) [expr {80 * $charspc}]
1751 set geometry(botheight) [expr {15 * $linespc}]
1752 set geometry(botwidth) [expr {50 * $charspc}]
1753 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1754 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1757 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1758 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1759 frame .tf.histframe
1760 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1762 # create three canvases
1763 set cscroll .tf.histframe.csb
1764 set canv .tf.histframe.pwclist.canv
1765 canvas $canv \
1766 -selectbackground $selectbgcolor \
1767 -background $bgcolor -bd 0 \
1768 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1769 .tf.histframe.pwclist add $canv
1770 set canv2 .tf.histframe.pwclist.canv2
1771 canvas $canv2 \
1772 -selectbackground $selectbgcolor \
1773 -background $bgcolor -bd 0 -yscrollincr $linespc
1774 .tf.histframe.pwclist add $canv2
1775 set canv3 .tf.histframe.pwclist.canv3
1776 canvas $canv3 \
1777 -selectbackground $selectbgcolor \
1778 -background $bgcolor -bd 0 -yscrollincr $linespc
1779 .tf.histframe.pwclist add $canv3
1780 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1781 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1783 # a scroll bar to rule them
1784 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1785 pack $cscroll -side right -fill y
1786 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1787 lappend bglist $canv $canv2 $canv3
1788 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1790 # we have two button bars at bottom of top frame. Bar 1
1791 frame .tf.bar
1792 frame .tf.lbar -height 15
1794 set sha1entry .tf.bar.sha1
1795 set entries $sha1entry
1796 set sha1but .tf.bar.sha1label
1797 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1798 -command gotocommit -width 8
1799 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1800 pack .tf.bar.sha1label -side left
1801 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1802 trace add variable sha1string write sha1change
1803 pack $sha1entry -side left -pady 2
1805 image create bitmap bm-left -data {
1806 #define left_width 16
1807 #define left_height 16
1808 static unsigned char left_bits[] = {
1809 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1810 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1811 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1813 image create bitmap bm-right -data {
1814 #define right_width 16
1815 #define right_height 16
1816 static unsigned char right_bits[] = {
1817 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1818 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1819 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1821 button .tf.bar.leftbut -image bm-left -command goback \
1822 -state disabled -width 26
1823 pack .tf.bar.leftbut -side left -fill y
1824 button .tf.bar.rightbut -image bm-right -command goforw \
1825 -state disabled -width 26
1826 pack .tf.bar.rightbut -side left -fill y
1828 label .tf.bar.rowlabel -text [mc "Row"]
1829 set rownumsel {}
1830 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1831 -relief sunken -anchor e
1832 label .tf.bar.rowlabel2 -text "/"
1833 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1834 -relief sunken -anchor e
1835 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1836 -side left
1837 global selectedline
1838 trace add variable selectedline write selectedline_change
1840 # Status label and progress bar
1841 set statusw .tf.bar.status
1842 label $statusw -width 15 -relief sunken
1843 pack $statusw -side left -padx 5
1844 set h [expr {[font metrics uifont -linespace] + 2}]
1845 set progresscanv .tf.bar.progress
1846 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1847 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1848 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1849 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1850 pack $progresscanv -side right -expand 1 -fill x
1851 set progresscoords {0 0}
1852 set fprogcoord 0
1853 set rprogcoord 0
1854 bind $progresscanv <Configure> adjustprogress
1855 set lastprogupdate [clock clicks -milliseconds]
1856 set progupdatepending 0
1858 # build up the bottom bar of upper window
1859 label .tf.lbar.flabel -text "[mc "Find"] "
1860 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1861 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1862 label .tf.lbar.flab2 -text " [mc "commit"] "
1863 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1864 -side left -fill y
1865 set gdttype [mc "containing:"]
1866 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1867 [mc "containing:"] \
1868 [mc "touching paths:"] \
1869 [mc "adding/removing string:"]]
1870 trace add variable gdttype write gdttype_change
1871 pack .tf.lbar.gdttype -side left -fill y
1873 set findstring {}
1874 set fstring .tf.lbar.findstring
1875 lappend entries $fstring
1876 entry $fstring -width 30 -font textfont -textvariable findstring
1877 trace add variable findstring write find_change
1878 set findtype [mc "Exact"]
1879 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1880 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
1881 trace add variable findtype write findcom_change
1882 set findloc [mc "All fields"]
1883 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
1884 [mc "Comments"] [mc "Author"] [mc "Committer"]
1885 trace add variable findloc write find_change
1886 pack .tf.lbar.findloc -side right
1887 pack .tf.lbar.findtype -side right
1888 pack $fstring -side left -expand 1 -fill x
1890 # Finish putting the upper half of the viewer together
1891 pack .tf.lbar -in .tf -side bottom -fill x
1892 pack .tf.bar -in .tf -side bottom -fill x
1893 pack .tf.histframe -fill both -side top -expand 1
1894 .ctop add .tf
1895 .ctop paneconfigure .tf -height $geometry(topheight)
1896 .ctop paneconfigure .tf -width $geometry(topwidth)
1898 # now build up the bottom
1899 panedwindow .pwbottom -orient horizontal
1901 # lower left, a text box over search bar, scroll bar to the right
1902 # if we know window height, then that will set the lower text height, otherwise
1903 # we set lower text height which will drive window height
1904 if {[info exists geometry(main)]} {
1905 frame .bleft -width $geometry(botwidth)
1906 } else {
1907 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1909 frame .bleft.top
1910 frame .bleft.mid
1911 frame .bleft.bottom
1913 button .bleft.top.search -text [mc "Search"] -command dosearch
1914 pack .bleft.top.search -side left -padx 5
1915 set sstring .bleft.top.sstring
1916 entry $sstring -width 20 -font textfont -textvariable searchstring
1917 lappend entries $sstring
1918 trace add variable searchstring write incrsearch
1919 pack $sstring -side left -expand 1 -fill x
1920 radiobutton .bleft.mid.diff -text [mc "Diff"] \
1921 -command changediffdisp -variable diffelide -value {0 0}
1922 radiobutton .bleft.mid.old -text [mc "Old version"] \
1923 -command changediffdisp -variable diffelide -value {0 1}
1924 radiobutton .bleft.mid.new -text [mc "New version"] \
1925 -command changediffdisp -variable diffelide -value {1 0}
1926 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
1927 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1928 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1929 -from 1 -increment 1 -to 10000000 \
1930 -validate all -validatecommand "diffcontextvalidate %P" \
1931 -textvariable diffcontextstring
1932 .bleft.mid.diffcontext set $diffcontext
1933 trace add variable diffcontextstring write diffcontextchange
1934 lappend entries .bleft.mid.diffcontext
1935 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1936 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
1937 -command changeignorespace -variable ignorespace
1938 pack .bleft.mid.ignspace -side left -padx 5
1939 set ctext .bleft.bottom.ctext
1940 text $ctext -background $bgcolor -foreground $fgcolor \
1941 -state disabled -font textfont \
1942 -yscrollcommand scrolltext -wrap none \
1943 -xscrollcommand ".bleft.bottom.sbhorizontal set"
1944 if {$have_tk85} {
1945 $ctext conf -tabstyle wordprocessor
1947 scrollbar .bleft.bottom.sb -command "$ctext yview"
1948 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
1949 -width 10
1950 pack .bleft.top -side top -fill x
1951 pack .bleft.mid -side top -fill x
1952 grid $ctext .bleft.bottom.sb -sticky nsew
1953 grid .bleft.bottom.sbhorizontal -sticky ew
1954 grid columnconfigure .bleft.bottom 0 -weight 1
1955 grid rowconfigure .bleft.bottom 0 -weight 1
1956 grid rowconfigure .bleft.bottom 1 -weight 0
1957 pack .bleft.bottom -side top -fill both -expand 1
1958 lappend bglist $ctext
1959 lappend fglist $ctext
1961 $ctext tag conf comment -wrap $wrapcomment
1962 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
1963 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
1964 $ctext tag conf d0 -fore [lindex $diffcolors 0]
1965 $ctext tag conf d1 -fore [lindex $diffcolors 1]
1966 $ctext tag conf m0 -fore red
1967 $ctext tag conf m1 -fore blue
1968 $ctext tag conf m2 -fore green
1969 $ctext tag conf m3 -fore purple
1970 $ctext tag conf m4 -fore brown
1971 $ctext tag conf m5 -fore "#009090"
1972 $ctext tag conf m6 -fore magenta
1973 $ctext tag conf m7 -fore "#808000"
1974 $ctext tag conf m8 -fore "#009000"
1975 $ctext tag conf m9 -fore "#ff0080"
1976 $ctext tag conf m10 -fore cyan
1977 $ctext tag conf m11 -fore "#b07070"
1978 $ctext tag conf m12 -fore "#70b0f0"
1979 $ctext tag conf m13 -fore "#70f0b0"
1980 $ctext tag conf m14 -fore "#f0b070"
1981 $ctext tag conf m15 -fore "#ff70b0"
1982 $ctext tag conf mmax -fore darkgrey
1983 set mergemax 16
1984 $ctext tag conf mresult -font textfontbold
1985 $ctext tag conf msep -font textfontbold
1986 $ctext tag conf found -back yellow
1988 .pwbottom add .bleft
1989 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
1991 # lower right
1992 frame .bright
1993 frame .bright.mode
1994 radiobutton .bright.mode.patch -text [mc "Patch"] \
1995 -command reselectline -variable cmitmode -value "patch"
1996 radiobutton .bright.mode.tree -text [mc "Tree"] \
1997 -command reselectline -variable cmitmode -value "tree"
1998 grid .bright.mode.patch .bright.mode.tree -sticky ew
1999 pack .bright.mode -side top -fill x
2000 set cflist .bright.cfiles
2001 set indent [font measure mainfont "nn"]
2002 text $cflist \
2003 -selectbackground $selectbgcolor \
2004 -background $bgcolor -foreground $fgcolor \
2005 -font mainfont \
2006 -tabs [list $indent [expr {2 * $indent}]] \
2007 -yscrollcommand ".bright.sb set" \
2008 -cursor [. cget -cursor] \
2009 -spacing1 1 -spacing3 1
2010 lappend bglist $cflist
2011 lappend fglist $cflist
2012 scrollbar .bright.sb -command "$cflist yview"
2013 pack .bright.sb -side right -fill y
2014 pack $cflist -side left -fill both -expand 1
2015 $cflist tag configure highlight \
2016 -background [$cflist cget -selectbackground]
2017 $cflist tag configure bold -font mainfontbold
2019 .pwbottom add .bright
2020 .ctop add .pwbottom
2022 # restore window width & height if known
2023 if {[info exists geometry(main)]} {
2024 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2025 if {$w > [winfo screenwidth .]} {
2026 set w [winfo screenwidth .]
2028 if {$h > [winfo screenheight .]} {
2029 set h [winfo screenheight .]
2031 wm geometry . "${w}x$h"
2035 if {[tk windowingsystem] eq {aqua}} {
2036 set M1B M1
2037 } else {
2038 set M1B Control
2041 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2042 pack .ctop -fill both -expand 1
2043 bindall <1> {selcanvline %W %x %y}
2044 #bindall <B1-Motion> {selcanvline %W %x %y}
2045 if {[tk windowingsystem] == "win32"} {
2046 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2047 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2048 } else {
2049 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2050 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2051 if {[tk windowingsystem] eq "aqua"} {
2052 bindall <MouseWheel> {
2053 set delta [expr {- (%D)}]
2054 allcanvs yview scroll $delta units
2058 bindall <2> "canvscan mark %W %x %y"
2059 bindall <B2-Motion> "canvscan dragto %W %x %y"
2060 bindkey <Home> selfirstline
2061 bindkey <End> sellastline
2062 bind . <Key-Up> "selnextline -1"
2063 bind . <Key-Down> "selnextline 1"
2064 bind . <Shift-Key-Up> "dofind -1 0"
2065 bind . <Shift-Key-Down> "dofind 1 0"
2066 bindkey <Key-Right> "goforw"
2067 bindkey <Key-Left> "goback"
2068 bind . <Key-Prior> "selnextpage -1"
2069 bind . <Key-Next> "selnextpage 1"
2070 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2071 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2072 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2073 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2074 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2075 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2076 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2077 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2078 bindkey <Key-space> "$ctext yview scroll 1 pages"
2079 bindkey p "selnextline -1"
2080 bindkey n "selnextline 1"
2081 bindkey z "goback"
2082 bindkey x "goforw"
2083 bindkey i "selnextline -1"
2084 bindkey k "selnextline 1"
2085 bindkey j "goback"
2086 bindkey l "goforw"
2087 bindkey b prevfile
2088 bindkey d "$ctext yview scroll 18 units"
2089 bindkey u "$ctext yview scroll -18 units"
2090 bindkey / {dofind 1 1}
2091 bindkey <Key-Return> {dofind 1 1}
2092 bindkey ? {dofind -1 1}
2093 bindkey f nextfile
2094 bindkey <F5> updatecommits
2095 bind . <$M1B-q> doquit
2096 bind . <$M1B-f> {dofind 1 1}
2097 bind . <$M1B-g> {dofind 1 0}
2098 bind . <$M1B-r> dosearchback
2099 bind . <$M1B-s> dosearch
2100 bind . <$M1B-equal> {incrfont 1}
2101 bind . <$M1B-plus> {incrfont 1}
2102 bind . <$M1B-KP_Add> {incrfont 1}
2103 bind . <$M1B-minus> {incrfont -1}
2104 bind . <$M1B-KP_Subtract> {incrfont -1}
2105 wm protocol . WM_DELETE_WINDOW doquit
2106 bind . <Button-1> "click %W"
2107 bind $fstring <Key-Return> {dofind 1 1}
2108 bind $sha1entry <Key-Return> gotocommit
2109 bind $sha1entry <<PasteSelection>> clearsha1
2110 bind $cflist <1> {sel_flist %W %x %y; break}
2111 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2112 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2113 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
2115 set maincursor [. cget -cursor]
2116 set textcursor [$ctext cget -cursor]
2117 set curtextcursor $textcursor
2119 set rowctxmenu .rowctxmenu
2120 menu $rowctxmenu -tearoff 0
2121 $rowctxmenu add command -label [mc "Diff this -> selected"] \
2122 -command {diffvssel 0}
2123 $rowctxmenu add command -label [mc "Diff selected -> this"] \
2124 -command {diffvssel 1}
2125 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
2126 $rowctxmenu add command -label [mc "Create tag"] -command mktag
2127 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
2128 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
2129 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
2130 -command cherrypick
2131 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
2132 -command resethead
2134 set fakerowmenu .fakerowmenu
2135 menu $fakerowmenu -tearoff 0
2136 $fakerowmenu add command -label [mc "Diff this -> selected"] \
2137 -command {diffvssel 0}
2138 $fakerowmenu add command -label [mc "Diff selected -> this"] \
2139 -command {diffvssel 1}
2140 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
2141 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
2142 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
2143 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
2145 set headctxmenu .headctxmenu
2146 menu $headctxmenu -tearoff 0
2147 $headctxmenu add command -label [mc "Check out this branch"] \
2148 -command cobranch
2149 $headctxmenu add command -label [mc "Remove this branch"] \
2150 -command rmbranch
2152 global flist_menu
2153 set flist_menu .flistctxmenu
2154 menu $flist_menu -tearoff 0
2155 $flist_menu add command -label [mc "Highlight this too"] \
2156 -command {flist_hl 0}
2157 $flist_menu add command -label [mc "Highlight this only"] \
2158 -command {flist_hl 1}
2159 $flist_menu add command -label [mc "External diff"] \
2160 -command {external_diff}
2163 # Windows sends all mouse wheel events to the current focused window, not
2164 # the one where the mouse hovers, so bind those events here and redirect
2165 # to the correct window
2166 proc windows_mousewheel_redirector {W X Y D} {
2167 global canv canv2 canv3
2168 set w [winfo containing -displayof $W $X $Y]
2169 if {$w ne ""} {
2170 set u [expr {$D < 0 ? 5 : -5}]
2171 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2172 allcanvs yview scroll $u units
2173 } else {
2174 catch {
2175 $w yview scroll $u units
2181 # Update row number label when selectedline changes
2182 proc selectedline_change {n1 n2 op} {
2183 global selectedline rownumsel
2185 if {$selectedline eq {}} {
2186 set rownumsel {}
2187 } else {
2188 set rownumsel [expr {$selectedline + 1}]
2192 # mouse-2 makes all windows scan vertically, but only the one
2193 # the cursor is in scans horizontally
2194 proc canvscan {op w x y} {
2195 global canv canv2 canv3
2196 foreach c [list $canv $canv2 $canv3] {
2197 if {$c == $w} {
2198 $c scan $op $x $y
2199 } else {
2200 $c scan $op 0 $y
2205 proc scrollcanv {cscroll f0 f1} {
2206 $cscroll set $f0 $f1
2207 drawvisible
2208 flushhighlights
2211 # when we make a key binding for the toplevel, make sure
2212 # it doesn't get triggered when that key is pressed in the
2213 # find string entry widget.
2214 proc bindkey {ev script} {
2215 global entries
2216 bind . $ev $script
2217 set escript [bind Entry $ev]
2218 if {$escript == {}} {
2219 set escript [bind Entry <Key>]
2221 foreach e $entries {
2222 bind $e $ev "$escript; break"
2226 # set the focus back to the toplevel for any click outside
2227 # the entry widgets
2228 proc click {w} {
2229 global ctext entries
2230 foreach e [concat $entries $ctext] {
2231 if {$w == $e} return
2233 focus .
2236 # Adjust the progress bar for a change in requested extent or canvas size
2237 proc adjustprogress {} {
2238 global progresscanv progressitem progresscoords
2239 global fprogitem fprogcoord lastprogupdate progupdatepending
2240 global rprogitem rprogcoord
2242 set w [expr {[winfo width $progresscanv] - 4}]
2243 set x0 [expr {$w * [lindex $progresscoords 0]}]
2244 set x1 [expr {$w * [lindex $progresscoords 1]}]
2245 set h [winfo height $progresscanv]
2246 $progresscanv coords $progressitem $x0 0 $x1 $h
2247 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2248 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2249 set now [clock clicks -milliseconds]
2250 if {$now >= $lastprogupdate + 100} {
2251 set progupdatepending 0
2252 update
2253 } elseif {!$progupdatepending} {
2254 set progupdatepending 1
2255 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2259 proc doprogupdate {} {
2260 global lastprogupdate progupdatepending
2262 if {$progupdatepending} {
2263 set progupdatepending 0
2264 set lastprogupdate [clock clicks -milliseconds]
2265 update
2269 proc savestuff {w} {
2270 global canv canv2 canv3 mainfont textfont uifont tabstop
2271 global stuffsaved findmergefiles maxgraphpct
2272 global maxwidth showneartags showlocalchanges
2273 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2274 global cmitmode wrapcomment datetimeformat limitdiffs
2275 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2276 global autoselect extdifftool
2278 if {$stuffsaved} return
2279 if {![winfo viewable .]} return
2280 catch {
2281 set f [open "~/.gitk-new" w]
2282 puts $f [list set mainfont $mainfont]
2283 puts $f [list set textfont $textfont]
2284 puts $f [list set uifont $uifont]
2285 puts $f [list set tabstop $tabstop]
2286 puts $f [list set findmergefiles $findmergefiles]
2287 puts $f [list set maxgraphpct $maxgraphpct]
2288 puts $f [list set maxwidth $maxwidth]
2289 puts $f [list set cmitmode $cmitmode]
2290 puts $f [list set wrapcomment $wrapcomment]
2291 puts $f [list set autoselect $autoselect]
2292 puts $f [list set showneartags $showneartags]
2293 puts $f [list set showlocalchanges $showlocalchanges]
2294 puts $f [list set datetimeformat $datetimeformat]
2295 puts $f [list set limitdiffs $limitdiffs]
2296 puts $f [list set bgcolor $bgcolor]
2297 puts $f [list set fgcolor $fgcolor]
2298 puts $f [list set colors $colors]
2299 puts $f [list set diffcolors $diffcolors]
2300 puts $f [list set diffcontext $diffcontext]
2301 puts $f [list set selectbgcolor $selectbgcolor]
2302 puts $f [list set extdifftool $extdifftool]
2304 puts $f "set geometry(main) [wm geometry .]"
2305 puts $f "set geometry(topwidth) [winfo width .tf]"
2306 puts $f "set geometry(topheight) [winfo height .tf]"
2307 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2308 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2309 puts $f "set geometry(botwidth) [winfo width .bleft]"
2310 puts $f "set geometry(botheight) [winfo height .bleft]"
2312 puts -nonewline $f "set permviews {"
2313 for {set v 0} {$v < $nextviewnum} {incr v} {
2314 if {$viewperm($v)} {
2315 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2318 puts $f "}"
2319 close $f
2320 catch {file delete "~/.gitk"}
2321 file rename -force "~/.gitk-new" "~/.gitk"
2323 set stuffsaved 1
2326 proc resizeclistpanes {win w} {
2327 global oldwidth
2328 if {[info exists oldwidth($win)]} {
2329 set s0 [$win sash coord 0]
2330 set s1 [$win sash coord 1]
2331 if {$w < 60} {
2332 set sash0 [expr {int($w/2 - 2)}]
2333 set sash1 [expr {int($w*5/6 - 2)}]
2334 } else {
2335 set factor [expr {1.0 * $w / $oldwidth($win)}]
2336 set sash0 [expr {int($factor * [lindex $s0 0])}]
2337 set sash1 [expr {int($factor * [lindex $s1 0])}]
2338 if {$sash0 < 30} {
2339 set sash0 30
2341 if {$sash1 < $sash0 + 20} {
2342 set sash1 [expr {$sash0 + 20}]
2344 if {$sash1 > $w - 10} {
2345 set sash1 [expr {$w - 10}]
2346 if {$sash0 > $sash1 - 20} {
2347 set sash0 [expr {$sash1 - 20}]
2351 $win sash place 0 $sash0 [lindex $s0 1]
2352 $win sash place 1 $sash1 [lindex $s1 1]
2354 set oldwidth($win) $w
2357 proc resizecdetpanes {win w} {
2358 global oldwidth
2359 if {[info exists oldwidth($win)]} {
2360 set s0 [$win sash coord 0]
2361 if {$w < 60} {
2362 set sash0 [expr {int($w*3/4 - 2)}]
2363 } else {
2364 set factor [expr {1.0 * $w / $oldwidth($win)}]
2365 set sash0 [expr {int($factor * [lindex $s0 0])}]
2366 if {$sash0 < 45} {
2367 set sash0 45
2369 if {$sash0 > $w - 15} {
2370 set sash0 [expr {$w - 15}]
2373 $win sash place 0 $sash0 [lindex $s0 1]
2375 set oldwidth($win) $w
2378 proc allcanvs args {
2379 global canv canv2 canv3
2380 eval $canv $args
2381 eval $canv2 $args
2382 eval $canv3 $args
2385 proc bindall {event action} {
2386 global canv canv2 canv3
2387 bind $canv $event $action
2388 bind $canv2 $event $action
2389 bind $canv3 $event $action
2392 proc about {} {
2393 global uifont
2394 set w .about
2395 if {[winfo exists $w]} {
2396 raise $w
2397 return
2399 toplevel $w
2400 wm title $w [mc "About gitk"]
2401 message $w.m -text [mc "
2402 Gitk - a commit viewer for git
2404 Copyright © 2005-2008 Paul Mackerras
2406 Use and redistribute under the terms of the GNU General Public License"] \
2407 -justify center -aspect 400 -border 2 -bg white -relief groove
2408 pack $w.m -side top -fill x -padx 2 -pady 2
2409 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2410 pack $w.ok -side bottom
2411 bind $w <Visibility> "focus $w.ok"
2412 bind $w <Key-Escape> "destroy $w"
2413 bind $w <Key-Return> "destroy $w"
2416 proc keys {} {
2417 set w .keys
2418 if {[winfo exists $w]} {
2419 raise $w
2420 return
2422 if {[tk windowingsystem] eq {aqua}} {
2423 set M1T Cmd
2424 } else {
2425 set M1T Ctrl
2427 toplevel $w
2428 wm title $w [mc "Gitk key bindings"]
2429 message $w.m -text "
2430 [mc "Gitk key bindings:"]
2432 [mc "<%s-Q> Quit" $M1T]
2433 [mc "<Home> Move to first commit"]
2434 [mc "<End> Move to last commit"]
2435 [mc "<Up>, p, i Move up one commit"]
2436 [mc "<Down>, n, k Move down one commit"]
2437 [mc "<Left>, z, j Go back in history list"]
2438 [mc "<Right>, x, l Go forward in history list"]
2439 [mc "<PageUp> Move up one page in commit list"]
2440 [mc "<PageDown> Move down one page in commit list"]
2441 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2442 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2443 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2444 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2445 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2446 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2447 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2448 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2449 [mc "<Delete>, b Scroll diff view up one page"]
2450 [mc "<Backspace> Scroll diff view up one page"]
2451 [mc "<Space> Scroll diff view down one page"]
2452 [mc "u Scroll diff view up 18 lines"]
2453 [mc "d Scroll diff view down 18 lines"]
2454 [mc "<%s-F> Find" $M1T]
2455 [mc "<%s-G> Move to next find hit" $M1T]
2456 [mc "<Return> Move to next find hit"]
2457 [mc "/ Move to next find hit, or redo find"]
2458 [mc "? Move to previous find hit"]
2459 [mc "f Scroll diff view to next file"]
2460 [mc "<%s-S> Search for next hit in diff view" $M1T]
2461 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2462 [mc "<%s-KP+> Increase font size" $M1T]
2463 [mc "<%s-plus> Increase font size" $M1T]
2464 [mc "<%s-KP-> Decrease font size" $M1T]
2465 [mc "<%s-minus> Decrease font size" $M1T]
2466 [mc "<F5> Update"]
2468 -justify left -bg white -border 2 -relief groove
2469 pack $w.m -side top -fill both -padx 2 -pady 2
2470 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2471 pack $w.ok -side bottom
2472 bind $w <Visibility> "focus $w.ok"
2473 bind $w <Key-Escape> "destroy $w"
2474 bind $w <Key-Return> "destroy $w"
2477 # Procedures for manipulating the file list window at the
2478 # bottom right of the overall window.
2480 proc treeview {w l openlevs} {
2481 global treecontents treediropen treeheight treeparent treeindex
2483 set ix 0
2484 set treeindex() 0
2485 set lev 0
2486 set prefix {}
2487 set prefixend -1
2488 set prefendstack {}
2489 set htstack {}
2490 set ht 0
2491 set treecontents() {}
2492 $w conf -state normal
2493 foreach f $l {
2494 while {[string range $f 0 $prefixend] ne $prefix} {
2495 if {$lev <= $openlevs} {
2496 $w mark set e:$treeindex($prefix) "end -1c"
2497 $w mark gravity e:$treeindex($prefix) left
2499 set treeheight($prefix) $ht
2500 incr ht [lindex $htstack end]
2501 set htstack [lreplace $htstack end end]
2502 set prefixend [lindex $prefendstack end]
2503 set prefendstack [lreplace $prefendstack end end]
2504 set prefix [string range $prefix 0 $prefixend]
2505 incr lev -1
2507 set tail [string range $f [expr {$prefixend+1}] end]
2508 while {[set slash [string first "/" $tail]] >= 0} {
2509 lappend htstack $ht
2510 set ht 0
2511 lappend prefendstack $prefixend
2512 incr prefixend [expr {$slash + 1}]
2513 set d [string range $tail 0 $slash]
2514 lappend treecontents($prefix) $d
2515 set oldprefix $prefix
2516 append prefix $d
2517 set treecontents($prefix) {}
2518 set treeindex($prefix) [incr ix]
2519 set treeparent($prefix) $oldprefix
2520 set tail [string range $tail [expr {$slash+1}] end]
2521 if {$lev <= $openlevs} {
2522 set ht 1
2523 set treediropen($prefix) [expr {$lev < $openlevs}]
2524 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2525 $w mark set d:$ix "end -1c"
2526 $w mark gravity d:$ix left
2527 set str "\n"
2528 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2529 $w insert end $str
2530 $w image create end -align center -image $bm -padx 1 \
2531 -name a:$ix
2532 $w insert end $d [highlight_tag $prefix]
2533 $w mark set s:$ix "end -1c"
2534 $w mark gravity s:$ix left
2536 incr lev
2538 if {$tail ne {}} {
2539 if {$lev <= $openlevs} {
2540 incr ht
2541 set str "\n"
2542 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2543 $w insert end $str
2544 $w insert end $tail [highlight_tag $f]
2546 lappend treecontents($prefix) $tail
2549 while {$htstack ne {}} {
2550 set treeheight($prefix) $ht
2551 incr ht [lindex $htstack end]
2552 set htstack [lreplace $htstack end end]
2553 set prefixend [lindex $prefendstack end]
2554 set prefendstack [lreplace $prefendstack end end]
2555 set prefix [string range $prefix 0 $prefixend]
2557 $w conf -state disabled
2560 proc linetoelt {l} {
2561 global treeheight treecontents
2563 set y 2
2564 set prefix {}
2565 while {1} {
2566 foreach e $treecontents($prefix) {
2567 if {$y == $l} {
2568 return "$prefix$e"
2570 set n 1
2571 if {[string index $e end] eq "/"} {
2572 set n $treeheight($prefix$e)
2573 if {$y + $n > $l} {
2574 append prefix $e
2575 incr y
2576 break
2579 incr y $n
2584 proc highlight_tree {y prefix} {
2585 global treeheight treecontents cflist
2587 foreach e $treecontents($prefix) {
2588 set path $prefix$e
2589 if {[highlight_tag $path] ne {}} {
2590 $cflist tag add bold $y.0 "$y.0 lineend"
2592 incr y
2593 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2594 set y [highlight_tree $y $path]
2597 return $y
2600 proc treeclosedir {w dir} {
2601 global treediropen treeheight treeparent treeindex
2603 set ix $treeindex($dir)
2604 $w conf -state normal
2605 $w delete s:$ix e:$ix
2606 set treediropen($dir) 0
2607 $w image configure a:$ix -image tri-rt
2608 $w conf -state disabled
2609 set n [expr {1 - $treeheight($dir)}]
2610 while {$dir ne {}} {
2611 incr treeheight($dir) $n
2612 set dir $treeparent($dir)
2616 proc treeopendir {w dir} {
2617 global treediropen treeheight treeparent treecontents treeindex
2619 set ix $treeindex($dir)
2620 $w conf -state normal
2621 $w image configure a:$ix -image tri-dn
2622 $w mark set e:$ix s:$ix
2623 $w mark gravity e:$ix right
2624 set lev 0
2625 set str "\n"
2626 set n [llength $treecontents($dir)]
2627 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2628 incr lev
2629 append str "\t"
2630 incr treeheight($x) $n
2632 foreach e $treecontents($dir) {
2633 set de $dir$e
2634 if {[string index $e end] eq "/"} {
2635 set iy $treeindex($de)
2636 $w mark set d:$iy e:$ix
2637 $w mark gravity d:$iy left
2638 $w insert e:$ix $str
2639 set treediropen($de) 0
2640 $w image create e:$ix -align center -image tri-rt -padx 1 \
2641 -name a:$iy
2642 $w insert e:$ix $e [highlight_tag $de]
2643 $w mark set s:$iy e:$ix
2644 $w mark gravity s:$iy left
2645 set treeheight($de) 1
2646 } else {
2647 $w insert e:$ix $str
2648 $w insert e:$ix $e [highlight_tag $de]
2651 $w mark gravity e:$ix left
2652 $w conf -state disabled
2653 set treediropen($dir) 1
2654 set top [lindex [split [$w index @0,0] .] 0]
2655 set ht [$w cget -height]
2656 set l [lindex [split [$w index s:$ix] .] 0]
2657 if {$l < $top} {
2658 $w yview $l.0
2659 } elseif {$l + $n + 1 > $top + $ht} {
2660 set top [expr {$l + $n + 2 - $ht}]
2661 if {$l < $top} {
2662 set top $l
2664 $w yview $top.0
2668 proc treeclick {w x y} {
2669 global treediropen cmitmode ctext cflist cflist_top
2671 if {$cmitmode ne "tree"} return
2672 if {![info exists cflist_top]} return
2673 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2674 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2675 $cflist tag add highlight $l.0 "$l.0 lineend"
2676 set cflist_top $l
2677 if {$l == 1} {
2678 $ctext yview 1.0
2679 return
2681 set e [linetoelt $l]
2682 if {[string index $e end] ne "/"} {
2683 showfile $e
2684 } elseif {$treediropen($e)} {
2685 treeclosedir $w $e
2686 } else {
2687 treeopendir $w $e
2691 proc setfilelist {id} {
2692 global treefilelist cflist
2694 treeview $cflist $treefilelist($id) 0
2697 image create bitmap tri-rt -background black -foreground blue -data {
2698 #define tri-rt_width 13
2699 #define tri-rt_height 13
2700 static unsigned char tri-rt_bits[] = {
2701 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2702 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2703 0x00, 0x00};
2704 } -maskdata {
2705 #define tri-rt-mask_width 13
2706 #define tri-rt-mask_height 13
2707 static unsigned char tri-rt-mask_bits[] = {
2708 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2709 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2710 0x08, 0x00};
2712 image create bitmap tri-dn -background black -foreground blue -data {
2713 #define tri-dn_width 13
2714 #define tri-dn_height 13
2715 static unsigned char tri-dn_bits[] = {
2716 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2717 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2718 0x00, 0x00};
2719 } -maskdata {
2720 #define tri-dn-mask_width 13
2721 #define tri-dn-mask_height 13
2722 static unsigned char tri-dn-mask_bits[] = {
2723 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2724 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2725 0x00, 0x00};
2728 image create bitmap reficon-T -background black -foreground yellow -data {
2729 #define tagicon_width 13
2730 #define tagicon_height 9
2731 static unsigned char tagicon_bits[] = {
2732 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2733 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2734 } -maskdata {
2735 #define tagicon-mask_width 13
2736 #define tagicon-mask_height 9
2737 static unsigned char tagicon-mask_bits[] = {
2738 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2739 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2741 set rectdata {
2742 #define headicon_width 13
2743 #define headicon_height 9
2744 static unsigned char headicon_bits[] = {
2745 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2746 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2748 set rectmask {
2749 #define headicon-mask_width 13
2750 #define headicon-mask_height 9
2751 static unsigned char headicon-mask_bits[] = {
2752 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2753 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2755 image create bitmap reficon-H -background black -foreground green \
2756 -data $rectdata -maskdata $rectmask
2757 image create bitmap reficon-o -background black -foreground "#ddddff" \
2758 -data $rectdata -maskdata $rectmask
2760 proc init_flist {first} {
2761 global cflist cflist_top difffilestart
2763 $cflist conf -state normal
2764 $cflist delete 0.0 end
2765 if {$first ne {}} {
2766 $cflist insert end $first
2767 set cflist_top 1
2768 $cflist tag add highlight 1.0 "1.0 lineend"
2769 } else {
2770 catch {unset cflist_top}
2772 $cflist conf -state disabled
2773 set difffilestart {}
2776 proc highlight_tag {f} {
2777 global highlight_paths
2779 foreach p $highlight_paths {
2780 if {[string match $p $f]} {
2781 return "bold"
2784 return {}
2787 proc highlight_filelist {} {
2788 global cmitmode cflist
2790 $cflist conf -state normal
2791 if {$cmitmode ne "tree"} {
2792 set end [lindex [split [$cflist index end] .] 0]
2793 for {set l 2} {$l < $end} {incr l} {
2794 set line [$cflist get $l.0 "$l.0 lineend"]
2795 if {[highlight_tag $line] ne {}} {
2796 $cflist tag add bold $l.0 "$l.0 lineend"
2799 } else {
2800 highlight_tree 2 {}
2802 $cflist conf -state disabled
2805 proc unhighlight_filelist {} {
2806 global cflist
2808 $cflist conf -state normal
2809 $cflist tag remove bold 1.0 end
2810 $cflist conf -state disabled
2813 proc add_flist {fl} {
2814 global cflist
2816 $cflist conf -state normal
2817 foreach f $fl {
2818 $cflist insert end "\n"
2819 $cflist insert end $f [highlight_tag $f]
2821 $cflist conf -state disabled
2824 proc sel_flist {w x y} {
2825 global ctext difffilestart cflist cflist_top cmitmode
2827 if {$cmitmode eq "tree"} return
2828 if {![info exists cflist_top]} return
2829 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2830 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2831 $cflist tag add highlight $l.0 "$l.0 lineend"
2832 set cflist_top $l
2833 if {$l == 1} {
2834 $ctext yview 1.0
2835 } else {
2836 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2840 proc pop_flist_menu {w X Y x y} {
2841 global ctext cflist cmitmode flist_menu flist_menu_file
2842 global treediffs diffids
2844 stopfinding
2845 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2846 if {$l <= 1} return
2847 if {$cmitmode eq "tree"} {
2848 set e [linetoelt $l]
2849 if {[string index $e end] eq "/"} return
2850 } else {
2851 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2853 set flist_menu_file $e
2854 set xdiffstate "normal"
2855 if {$cmitmode eq "tree"} {
2856 set xdiffstate "disabled"
2858 # Disable "External diff" item in tree mode
2859 $flist_menu entryconf 2 -state $xdiffstate
2860 tk_popup $flist_menu $X $Y
2863 proc flist_hl {only} {
2864 global flist_menu_file findstring gdttype
2866 set x [shellquote $flist_menu_file]
2867 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
2868 set findstring $x
2869 } else {
2870 append findstring " " $x
2872 set gdttype [mc "touching paths:"]
2875 proc save_file_from_commit {filename output what} {
2876 global nullfile
2878 if {[catch {exec git show $filename -- > $output} err]} {
2879 if {[string match "fatal: bad revision *" $err]} {
2880 return $nullfile
2882 error_popup "Error getting \"$filename\" from $what: $err"
2883 return {}
2885 return $output
2888 proc external_diff_get_one_file {diffid filename diffdir} {
2889 global nullid nullid2 nullfile
2890 global gitdir
2892 if {$diffid == $nullid} {
2893 set difffile [file join [file dirname $gitdir] $filename]
2894 if {[file exists $difffile]} {
2895 return $difffile
2897 return $nullfile
2899 if {$diffid == $nullid2} {
2900 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
2901 return [save_file_from_commit :$filename $difffile index]
2903 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
2904 return [save_file_from_commit $diffid:$filename $difffile \
2905 "revision $diffid"]
2908 proc external_diff {} {
2909 global gitktmpdir nullid nullid2
2910 global flist_menu_file
2911 global diffids
2912 global diffnum
2913 global gitdir extdifftool
2915 if {[llength $diffids] == 1} {
2916 # no reference commit given
2917 set diffidto [lindex $diffids 0]
2918 if {$diffidto eq $nullid} {
2919 # diffing working copy with index
2920 set diffidfrom $nullid2
2921 } elseif {$diffidto eq $nullid2} {
2922 # diffing index with HEAD
2923 set diffidfrom "HEAD"
2924 } else {
2925 # use first parent commit
2926 global parentlist selectedline
2927 set diffidfrom [lindex $parentlist $selectedline 0]
2929 } else {
2930 set diffidfrom [lindex $diffids 0]
2931 set diffidto [lindex $diffids 1]
2934 # make sure that several diffs wont collide
2935 if {![info exists gitktmpdir]} {
2936 set gitktmpdir [file join [file dirname $gitdir] \
2937 [format ".gitk-tmp.%s" [pid]]]
2938 if {[catch {file mkdir $gitktmpdir} err]} {
2939 error_popup "Error creating temporary directory $gitktmpdir: $err"
2940 unset gitktmpdir
2941 return
2943 set diffnum 0
2945 incr diffnum
2946 set diffdir [file join $gitktmpdir $diffnum]
2947 if {[catch {file mkdir $diffdir} err]} {
2948 error_popup "Error creating temporary directory $diffdir: $err"
2949 return
2952 # gather files to diff
2953 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
2954 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
2956 if {$difffromfile ne {} && $difftofile ne {}} {
2957 set cmd [concat | [shellsplit $extdifftool] \
2958 [list $difffromfile $difftofile]]
2959 if {[catch {set fl [open $cmd r]} err]} {
2960 file delete -force $diffdir
2961 error_popup [mc "$extdifftool: command failed: $err"]
2962 } else {
2963 fconfigure $fl -blocking 0
2964 filerun $fl [list delete_at_eof $fl $diffdir]
2969 # delete $dir when we see eof on $f (presumably because the child has exited)
2970 proc delete_at_eof {f dir} {
2971 while {[gets $f line] >= 0} {}
2972 if {[eof $f]} {
2973 if {[catch {close $f} err]} {
2974 error_popup "External diff viewer failed: $err"
2976 file delete -force $dir
2977 return 0
2979 return 1
2982 # Functions for adding and removing shell-type quoting
2984 proc shellquote {str} {
2985 if {![string match "*\['\"\\ \t]*" $str]} {
2986 return $str
2988 if {![string match "*\['\"\\]*" $str]} {
2989 return "\"$str\""
2991 if {![string match "*'*" $str]} {
2992 return "'$str'"
2994 return "\"[string map {\" \\\" \\ \\\\} $str]\""
2997 proc shellarglist {l} {
2998 set str {}
2999 foreach a $l {
3000 if {$str ne {}} {
3001 append str " "
3003 append str [shellquote $a]
3005 return $str
3008 proc shelldequote {str} {
3009 set ret {}
3010 set used -1
3011 while {1} {
3012 incr used
3013 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3014 append ret [string range $str $used end]
3015 set used [string length $str]
3016 break
3018 set first [lindex $first 0]
3019 set ch [string index $str $first]
3020 if {$first > $used} {
3021 append ret [string range $str $used [expr {$first - 1}]]
3022 set used $first
3024 if {$ch eq " " || $ch eq "\t"} break
3025 incr used
3026 if {$ch eq "'"} {
3027 set first [string first "'" $str $used]
3028 if {$first < 0} {
3029 error "unmatched single-quote"
3031 append ret [string range $str $used [expr {$first - 1}]]
3032 set used $first
3033 continue
3035 if {$ch eq "\\"} {
3036 if {$used >= [string length $str]} {
3037 error "trailing backslash"
3039 append ret [string index $str $used]
3040 continue
3042 # here ch == "\""
3043 while {1} {
3044 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3045 error "unmatched double-quote"
3047 set first [lindex $first 0]
3048 set ch [string index $str $first]
3049 if {$first > $used} {
3050 append ret [string range $str $used [expr {$first - 1}]]
3051 set used $first
3053 if {$ch eq "\""} break
3054 incr used
3055 append ret [string index $str $used]
3056 incr used
3059 return [list $used $ret]
3062 proc shellsplit {str} {
3063 set l {}
3064 while {1} {
3065 set str [string trimleft $str]
3066 if {$str eq {}} break
3067 set dq [shelldequote $str]
3068 set n [lindex $dq 0]
3069 set word [lindex $dq 1]
3070 set str [string range $str $n end]
3071 lappend l $word
3073 return $l
3076 # Code to implement multiple views
3078 proc newview {ishighlight} {
3079 global nextviewnum newviewname newviewperm newishighlight
3080 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3082 set newishighlight $ishighlight
3083 set top .gitkview
3084 if {[winfo exists $top]} {
3085 raise $top
3086 return
3088 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3089 set newviewperm($nextviewnum) 0
3090 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3091 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3092 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3095 proc editview {} {
3096 global curview
3097 global viewname viewperm newviewname newviewperm
3098 global viewargs newviewargs viewargscmd newviewargscmd
3100 set top .gitkvedit-$curview
3101 if {[winfo exists $top]} {
3102 raise $top
3103 return
3105 set newviewname($curview) $viewname($curview)
3106 set newviewperm($curview) $viewperm($curview)
3107 set newviewargs($curview) [shellarglist $viewargs($curview)]
3108 set newviewargscmd($curview) $viewargscmd($curview)
3109 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3112 proc vieweditor {top n title} {
3113 global newviewname newviewperm viewfiles bgcolor
3115 toplevel $top
3116 wm title $top $title
3117 label $top.nl -text [mc "Name"]
3118 entry $top.name -width 20 -textvariable newviewname($n)
3119 grid $top.nl $top.name -sticky w -pady 5
3120 checkbutton $top.perm -text [mc "Remember this view"] \
3121 -variable newviewperm($n)
3122 grid $top.perm - -pady 5 -sticky w
3123 message $top.al -aspect 1000 \
3124 -text [mc "Commits to include (arguments to git log):"]
3125 grid $top.al - -sticky w -pady 5
3126 entry $top.args -width 50 -textvariable newviewargs($n) \
3127 -background $bgcolor
3128 grid $top.args - -sticky ew -padx 5
3130 message $top.ac -aspect 1000 \
3131 -text [mc "Command to generate more commits to include:"]
3132 grid $top.ac - -sticky w -pady 5
3133 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3134 -background white
3135 grid $top.argscmd - -sticky ew -padx 5
3137 message $top.l -aspect 1000 \
3138 -text [mc "Enter files and directories to include, one per line:"]
3139 grid $top.l - -sticky w
3140 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3141 if {[info exists viewfiles($n)]} {
3142 foreach f $viewfiles($n) {
3143 $top.t insert end $f
3144 $top.t insert end "\n"
3146 $top.t delete {end - 1c} end
3147 $top.t mark set insert 0.0
3149 grid $top.t - -sticky ew -padx 5
3150 frame $top.buts
3151 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3152 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3153 grid $top.buts.ok $top.buts.can
3154 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3155 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3156 grid $top.buts - -pady 10 -sticky ew
3157 focus $top.t
3160 proc doviewmenu {m first cmd op argv} {
3161 set nmenu [$m index end]
3162 for {set i $first} {$i <= $nmenu} {incr i} {
3163 if {[$m entrycget $i -command] eq $cmd} {
3164 eval $m $op $i $argv
3165 break
3170 proc allviewmenus {n op args} {
3171 # global viewhlmenu
3173 doviewmenu .bar.view 5 [list showview $n] $op $args
3174 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3177 proc newviewok {top n} {
3178 global nextviewnum newviewperm newviewname newishighlight
3179 global viewname viewfiles viewperm selectedview curview
3180 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3182 if {[catch {
3183 set newargs [shellsplit $newviewargs($n)]
3184 } err]} {
3185 error_popup "[mc "Error in commit selection arguments:"] $err"
3186 wm raise $top
3187 focus $top
3188 return
3190 set files {}
3191 foreach f [split [$top.t get 0.0 end] "\n"] {
3192 set ft [string trim $f]
3193 if {$ft ne {}} {
3194 lappend files $ft
3197 if {![info exists viewfiles($n)]} {
3198 # creating a new view
3199 incr nextviewnum
3200 set viewname($n) $newviewname($n)
3201 set viewperm($n) $newviewperm($n)
3202 set viewfiles($n) $files
3203 set viewargs($n) $newargs
3204 set viewargscmd($n) $newviewargscmd($n)
3205 addviewmenu $n
3206 if {!$newishighlight} {
3207 run showview $n
3208 } else {
3209 run addvhighlight $n
3211 } else {
3212 # editing an existing view
3213 set viewperm($n) $newviewperm($n)
3214 if {$newviewname($n) ne $viewname($n)} {
3215 set viewname($n) $newviewname($n)
3216 doviewmenu .bar.view 5 [list showview $n] \
3217 entryconf [list -label $viewname($n)]
3218 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3219 # entryconf [list -label $viewname($n) -value $viewname($n)]
3221 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3222 $newviewargscmd($n) ne $viewargscmd($n)} {
3223 set viewfiles($n) $files
3224 set viewargs($n) $newargs
3225 set viewargscmd($n) $newviewargscmd($n)
3226 if {$curview == $n} {
3227 run reloadcommits
3231 catch {destroy $top}
3234 proc delview {} {
3235 global curview viewperm hlview selectedhlview
3237 if {$curview == 0} return
3238 if {[info exists hlview] && $hlview == $curview} {
3239 set selectedhlview [mc "None"]
3240 unset hlview
3242 allviewmenus $curview delete
3243 set viewperm($curview) 0
3244 showview 0
3247 proc addviewmenu {n} {
3248 global viewname viewhlmenu
3250 .bar.view add radiobutton -label $viewname($n) \
3251 -command [list showview $n] -variable selectedview -value $n
3252 #$viewhlmenu add radiobutton -label $viewname($n) \
3253 # -command [list addvhighlight $n] -variable selectedhlview
3256 proc showview {n} {
3257 global curview cached_commitrow ordertok
3258 global displayorder parentlist rowidlist rowisopt rowfinal
3259 global colormap rowtextx nextcolor canvxmax
3260 global numcommits viewcomplete
3261 global selectedline currentid canv canvy0
3262 global treediffs
3263 global pending_select mainheadid
3264 global commitidx
3265 global selectedview
3266 global hlview selectedhlview commitinterest
3268 if {$n == $curview} return
3269 set selid {}
3270 set ymax [lindex [$canv cget -scrollregion] 3]
3271 set span [$canv yview]
3272 set ytop [expr {[lindex $span 0] * $ymax}]
3273 set ybot [expr {[lindex $span 1] * $ymax}]
3274 set yscreen [expr {($ybot - $ytop) / 2}]
3275 if {$selectedline ne {}} {
3276 set selid $currentid
3277 set y [yc $selectedline]
3278 if {$ytop < $y && $y < $ybot} {
3279 set yscreen [expr {$y - $ytop}]
3281 } elseif {[info exists pending_select]} {
3282 set selid $pending_select
3283 unset pending_select
3285 unselectline
3286 normalline
3287 catch {unset treediffs}
3288 clear_display
3289 if {[info exists hlview] && $hlview == $n} {
3290 unset hlview
3291 set selectedhlview [mc "None"]
3293 catch {unset commitinterest}
3294 catch {unset cached_commitrow}
3295 catch {unset ordertok}
3297 set curview $n
3298 set selectedview $n
3299 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3300 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3302 run refill_reflist
3303 if {![info exists viewcomplete($n)]} {
3304 if {$selid ne {}} {
3305 set pending_select $selid
3307 getcommits
3308 return
3311 set displayorder {}
3312 set parentlist {}
3313 set rowidlist {}
3314 set rowisopt {}
3315 set rowfinal {}
3316 set numcommits $commitidx($n)
3318 catch {unset colormap}
3319 catch {unset rowtextx}
3320 set nextcolor 0
3321 set canvxmax [$canv cget -width]
3322 set curview $n
3323 set row 0
3324 setcanvscroll
3325 set yf 0
3326 set row {}
3327 if {$selid ne {} && [commitinview $selid $n]} {
3328 set row [rowofcommit $selid]
3329 # try to get the selected row in the same position on the screen
3330 set ymax [lindex [$canv cget -scrollregion] 3]
3331 set ytop [expr {[yc $row] - $yscreen}]
3332 if {$ytop < 0} {
3333 set ytop 0
3335 set yf [expr {$ytop * 1.0 / $ymax}]
3337 allcanvs yview moveto $yf
3338 drawvisible
3339 if {$row ne {}} {
3340 selectline $row 0
3341 } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
3342 selectline [rowofcommit $mainheadid] 1
3343 } elseif {!$viewcomplete($n)} {
3344 if {$selid ne {}} {
3345 set pending_select $selid
3346 } else {
3347 set pending_select $mainheadid
3349 } else {
3350 set row [first_real_row]
3351 if {$row < $numcommits} {
3352 selectline $row 0
3355 if {!$viewcomplete($n)} {
3356 if {$numcommits == 0} {
3357 show_status [mc "Reading commits..."]
3359 } elseif {$numcommits == 0} {
3360 show_status [mc "No commits selected"]
3364 # Stuff relating to the highlighting facility
3366 proc ishighlighted {id} {
3367 global vhighlights fhighlights nhighlights rhighlights
3369 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3370 return $nhighlights($id)
3372 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3373 return $vhighlights($id)
3375 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3376 return $fhighlights($id)
3378 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3379 return $rhighlights($id)
3381 return 0
3384 proc bolden {row font} {
3385 global canv linehtag selectedline boldrows
3387 lappend boldrows $row
3388 $canv itemconf $linehtag($row) -font $font
3389 if {$row == $selectedline} {
3390 $canv delete secsel
3391 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3392 -outline {{}} -tags secsel \
3393 -fill [$canv cget -selectbackground]]
3394 $canv lower $t
3398 proc bolden_name {row font} {
3399 global canv2 linentag selectedline boldnamerows
3401 lappend boldnamerows $row
3402 $canv2 itemconf $linentag($row) -font $font
3403 if {$row == $selectedline} {
3404 $canv2 delete secsel
3405 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3406 -outline {{}} -tags secsel \
3407 -fill [$canv2 cget -selectbackground]]
3408 $canv2 lower $t
3412 proc unbolden {} {
3413 global boldrows
3415 set stillbold {}
3416 foreach row $boldrows {
3417 if {![ishighlighted [commitonrow $row]]} {
3418 bolden $row mainfont
3419 } else {
3420 lappend stillbold $row
3423 set boldrows $stillbold
3426 proc addvhighlight {n} {
3427 global hlview viewcomplete curview vhl_done commitidx
3429 if {[info exists hlview]} {
3430 delvhighlight
3432 set hlview $n
3433 if {$n != $curview && ![info exists viewcomplete($n)]} {
3434 start_rev_list $n
3436 set vhl_done $commitidx($hlview)
3437 if {$vhl_done > 0} {
3438 drawvisible
3442 proc delvhighlight {} {
3443 global hlview vhighlights
3445 if {![info exists hlview]} return
3446 unset hlview
3447 catch {unset vhighlights}
3448 unbolden
3451 proc vhighlightmore {} {
3452 global hlview vhl_done commitidx vhighlights curview
3454 set max $commitidx($hlview)
3455 set vr [visiblerows]
3456 set r0 [lindex $vr 0]
3457 set r1 [lindex $vr 1]
3458 for {set i $vhl_done} {$i < $max} {incr i} {
3459 set id [commitonrow $i $hlview]
3460 if {[commitinview $id $curview]} {
3461 set row [rowofcommit $id]
3462 if {$r0 <= $row && $row <= $r1} {
3463 if {![highlighted $row]} {
3464 bolden $row mainfontbold
3466 set vhighlights($id) 1
3470 set vhl_done $max
3471 return 0
3474 proc askvhighlight {row id} {
3475 global hlview vhighlights iddrawn
3477 if {[commitinview $id $hlview]} {
3478 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3479 bolden $row mainfontbold
3481 set vhighlights($id) 1
3482 } else {
3483 set vhighlights($id) 0
3487 proc hfiles_change {} {
3488 global highlight_files filehighlight fhighlights fh_serial
3489 global highlight_paths gdttype
3491 if {[info exists filehighlight]} {
3492 # delete previous highlights
3493 catch {close $filehighlight}
3494 unset filehighlight
3495 catch {unset fhighlights}
3496 unbolden
3497 unhighlight_filelist
3499 set highlight_paths {}
3500 after cancel do_file_hl $fh_serial
3501 incr fh_serial
3502 if {$highlight_files ne {}} {
3503 after 300 do_file_hl $fh_serial
3507 proc gdttype_change {name ix op} {
3508 global gdttype highlight_files findstring findpattern
3510 stopfinding
3511 if {$findstring ne {}} {
3512 if {$gdttype eq [mc "containing:"]} {
3513 if {$highlight_files ne {}} {
3514 set highlight_files {}
3515 hfiles_change
3517 findcom_change
3518 } else {
3519 if {$findpattern ne {}} {
3520 set findpattern {}
3521 findcom_change
3523 set highlight_files $findstring
3524 hfiles_change
3526 drawvisible
3528 # enable/disable findtype/findloc menus too
3531 proc find_change {name ix op} {
3532 global gdttype findstring highlight_files
3534 stopfinding
3535 if {$gdttype eq [mc "containing:"]} {
3536 findcom_change
3537 } else {
3538 if {$highlight_files ne $findstring} {
3539 set highlight_files $findstring
3540 hfiles_change
3543 drawvisible
3546 proc findcom_change args {
3547 global nhighlights boldnamerows
3548 global findpattern findtype findstring gdttype
3550 stopfinding
3551 # delete previous highlights, if any
3552 foreach row $boldnamerows {
3553 bolden_name $row mainfont
3555 set boldnamerows {}
3556 catch {unset nhighlights}
3557 unbolden
3558 unmarkmatches
3559 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3560 set findpattern {}
3561 } elseif {$findtype eq [mc "Regexp"]} {
3562 set findpattern $findstring
3563 } else {
3564 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3565 $findstring]
3566 set findpattern "*$e*"
3570 proc makepatterns {l} {
3571 set ret {}
3572 foreach e $l {
3573 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3574 if {[string index $ee end] eq "/"} {
3575 lappend ret "$ee*"
3576 } else {
3577 lappend ret $ee
3578 lappend ret "$ee/*"
3581 return $ret
3584 proc do_file_hl {serial} {
3585 global highlight_files filehighlight highlight_paths gdttype fhl_list
3587 if {$gdttype eq [mc "touching paths:"]} {
3588 if {[catch {set paths [shellsplit $highlight_files]}]} return
3589 set highlight_paths [makepatterns $paths]
3590 highlight_filelist
3591 set gdtargs [concat -- $paths]
3592 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3593 set gdtargs [list "-S$highlight_files"]
3594 } else {
3595 # must be "containing:", i.e. we're searching commit info
3596 return
3598 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3599 set filehighlight [open $cmd r+]
3600 fconfigure $filehighlight -blocking 0
3601 filerun $filehighlight readfhighlight
3602 set fhl_list {}
3603 drawvisible
3604 flushhighlights
3607 proc flushhighlights {} {
3608 global filehighlight fhl_list
3610 if {[info exists filehighlight]} {
3611 lappend fhl_list {}
3612 puts $filehighlight ""
3613 flush $filehighlight
3617 proc askfilehighlight {row id} {
3618 global filehighlight fhighlights fhl_list
3620 lappend fhl_list $id
3621 set fhighlights($id) -1
3622 puts $filehighlight $id
3625 proc readfhighlight {} {
3626 global filehighlight fhighlights curview iddrawn
3627 global fhl_list find_dirn
3629 if {![info exists filehighlight]} {
3630 return 0
3632 set nr 0
3633 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3634 set line [string trim $line]
3635 set i [lsearch -exact $fhl_list $line]
3636 if {$i < 0} continue
3637 for {set j 0} {$j < $i} {incr j} {
3638 set id [lindex $fhl_list $j]
3639 set fhighlights($id) 0
3641 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3642 if {$line eq {}} continue
3643 if {![commitinview $line $curview]} continue
3644 set row [rowofcommit $line]
3645 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3646 bolden $row mainfontbold
3648 set fhighlights($line) 1
3650 if {[eof $filehighlight]} {
3651 # strange...
3652 puts "oops, git diff-tree died"
3653 catch {close $filehighlight}
3654 unset filehighlight
3655 return 0
3657 if {[info exists find_dirn]} {
3658 run findmore
3660 return 1
3663 proc doesmatch {f} {
3664 global findtype findpattern
3666 if {$findtype eq [mc "Regexp"]} {
3667 return [regexp $findpattern $f]
3668 } elseif {$findtype eq [mc "IgnCase"]} {
3669 return [string match -nocase $findpattern $f]
3670 } else {
3671 return [string match $findpattern $f]
3675 proc askfindhighlight {row id} {
3676 global nhighlights commitinfo iddrawn
3677 global findloc
3678 global markingmatches
3680 if {![info exists commitinfo($id)]} {
3681 getcommit $id
3683 set info $commitinfo($id)
3684 set isbold 0
3685 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3686 foreach f $info ty $fldtypes {
3687 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3688 [doesmatch $f]} {
3689 if {$ty eq [mc "Author"]} {
3690 set isbold 2
3691 break
3693 set isbold 1
3696 if {$isbold && [info exists iddrawn($id)]} {
3697 if {![ishighlighted $id]} {
3698 bolden $row mainfontbold
3699 if {$isbold > 1} {
3700 bolden_name $row mainfontbold
3703 if {$markingmatches} {
3704 markrowmatches $row $id
3707 set nhighlights($id) $isbold
3710 proc markrowmatches {row id} {
3711 global canv canv2 linehtag linentag commitinfo findloc
3713 set headline [lindex $commitinfo($id) 0]
3714 set author [lindex $commitinfo($id) 1]
3715 $canv delete match$row
3716 $canv2 delete match$row
3717 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3718 set m [findmatches $headline]
3719 if {$m ne {}} {
3720 markmatches $canv $row $headline $linehtag($row) $m \
3721 [$canv itemcget $linehtag($row) -font] $row
3724 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3725 set m [findmatches $author]
3726 if {$m ne {}} {
3727 markmatches $canv2 $row $author $linentag($row) $m \
3728 [$canv2 itemcget $linentag($row) -font] $row
3733 proc vrel_change {name ix op} {
3734 global highlight_related
3736 rhighlight_none
3737 if {$highlight_related ne [mc "None"]} {
3738 run drawvisible
3742 # prepare for testing whether commits are descendents or ancestors of a
3743 proc rhighlight_sel {a} {
3744 global descendent desc_todo ancestor anc_todo
3745 global highlight_related
3747 catch {unset descendent}
3748 set desc_todo [list $a]
3749 catch {unset ancestor}
3750 set anc_todo [list $a]
3751 if {$highlight_related ne [mc "None"]} {
3752 rhighlight_none
3753 run drawvisible
3757 proc rhighlight_none {} {
3758 global rhighlights
3760 catch {unset rhighlights}
3761 unbolden
3764 proc is_descendent {a} {
3765 global curview children descendent desc_todo
3767 set v $curview
3768 set la [rowofcommit $a]
3769 set todo $desc_todo
3770 set leftover {}
3771 set done 0
3772 for {set i 0} {$i < [llength $todo]} {incr i} {
3773 set do [lindex $todo $i]
3774 if {[rowofcommit $do] < $la} {
3775 lappend leftover $do
3776 continue
3778 foreach nk $children($v,$do) {
3779 if {![info exists descendent($nk)]} {
3780 set descendent($nk) 1
3781 lappend todo $nk
3782 if {$nk eq $a} {
3783 set done 1
3787 if {$done} {
3788 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3789 return
3792 set descendent($a) 0
3793 set desc_todo $leftover
3796 proc is_ancestor {a} {
3797 global curview parents ancestor anc_todo
3799 set v $curview
3800 set la [rowofcommit $a]
3801 set todo $anc_todo
3802 set leftover {}
3803 set done 0
3804 for {set i 0} {$i < [llength $todo]} {incr i} {
3805 set do [lindex $todo $i]
3806 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3807 lappend leftover $do
3808 continue
3810 foreach np $parents($v,$do) {
3811 if {![info exists ancestor($np)]} {
3812 set ancestor($np) 1
3813 lappend todo $np
3814 if {$np eq $a} {
3815 set done 1
3819 if {$done} {
3820 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3821 return
3824 set ancestor($a) 0
3825 set anc_todo $leftover
3828 proc askrelhighlight {row id} {
3829 global descendent highlight_related iddrawn rhighlights
3830 global selectedline ancestor
3832 if {$selectedline eq {}} return
3833 set isbold 0
3834 if {$highlight_related eq [mc "Descendant"] ||
3835 $highlight_related eq [mc "Not descendant"]} {
3836 if {![info exists descendent($id)]} {
3837 is_descendent $id
3839 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3840 set isbold 1
3842 } elseif {$highlight_related eq [mc "Ancestor"] ||
3843 $highlight_related eq [mc "Not ancestor"]} {
3844 if {![info exists ancestor($id)]} {
3845 is_ancestor $id
3847 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
3848 set isbold 1
3851 if {[info exists iddrawn($id)]} {
3852 if {$isbold && ![ishighlighted $id]} {
3853 bolden $row mainfontbold
3856 set rhighlights($id) $isbold
3859 # Graph layout functions
3861 proc shortids {ids} {
3862 set res {}
3863 foreach id $ids {
3864 if {[llength $id] > 1} {
3865 lappend res [shortids $id]
3866 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3867 lappend res [string range $id 0 7]
3868 } else {
3869 lappend res $id
3872 return $res
3875 proc ntimes {n o} {
3876 set ret {}
3877 set o [list $o]
3878 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3879 if {($n & $mask) != 0} {
3880 set ret [concat $ret $o]
3882 set o [concat $o $o]
3884 return $ret
3887 proc ordertoken {id} {
3888 global ordertok curview varcid varcstart varctok curview parents children
3889 global nullid nullid2
3891 if {[info exists ordertok($id)]} {
3892 return $ordertok($id)
3894 set origid $id
3895 set todo {}
3896 while {1} {
3897 if {[info exists varcid($curview,$id)]} {
3898 set a $varcid($curview,$id)
3899 set p [lindex $varcstart($curview) $a]
3900 } else {
3901 set p [lindex $children($curview,$id) 0]
3903 if {[info exists ordertok($p)]} {
3904 set tok $ordertok($p)
3905 break
3907 set id [first_real_child $curview,$p]
3908 if {$id eq {}} {
3909 # it's a root
3910 set tok [lindex $varctok($curview) $varcid($curview,$p)]
3911 break
3913 if {[llength $parents($curview,$id)] == 1} {
3914 lappend todo [list $p {}]
3915 } else {
3916 set j [lsearch -exact $parents($curview,$id) $p]
3917 if {$j < 0} {
3918 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3920 lappend todo [list $p [strrep $j]]
3923 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3924 set p [lindex $todo $i 0]
3925 append tok [lindex $todo $i 1]
3926 set ordertok($p) $tok
3928 set ordertok($origid) $tok
3929 return $tok
3932 # Work out where id should go in idlist so that order-token
3933 # values increase from left to right
3934 proc idcol {idlist id {i 0}} {
3935 set t [ordertoken $id]
3936 if {$i < 0} {
3937 set i 0
3939 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3940 if {$i > [llength $idlist]} {
3941 set i [llength $idlist]
3943 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3944 incr i
3945 } else {
3946 if {$t > [ordertoken [lindex $idlist $i]]} {
3947 while {[incr i] < [llength $idlist] &&
3948 $t >= [ordertoken [lindex $idlist $i]]} {}
3951 return $i
3954 proc initlayout {} {
3955 global rowidlist rowisopt rowfinal displayorder parentlist
3956 global numcommits canvxmax canv
3957 global nextcolor
3958 global colormap rowtextx
3960 set numcommits 0
3961 set displayorder {}
3962 set parentlist {}
3963 set nextcolor 0
3964 set rowidlist {}
3965 set rowisopt {}
3966 set rowfinal {}
3967 set canvxmax [$canv cget -width]
3968 catch {unset colormap}
3969 catch {unset rowtextx}
3970 setcanvscroll
3973 proc setcanvscroll {} {
3974 global canv canv2 canv3 numcommits linespc canvxmax canvy0
3975 global lastscrollset lastscrollrows
3977 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
3978 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
3979 $canv2 conf -scrollregion [list 0 0 0 $ymax]
3980 $canv3 conf -scrollregion [list 0 0 0 $ymax]
3981 set lastscrollset [clock clicks -milliseconds]
3982 set lastscrollrows $numcommits
3985 proc visiblerows {} {
3986 global canv numcommits linespc
3988 set ymax [lindex [$canv cget -scrollregion] 3]
3989 if {$ymax eq {} || $ymax == 0} return
3990 set f [$canv yview]
3991 set y0 [expr {int([lindex $f 0] * $ymax)}]
3992 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
3993 if {$r0 < 0} {
3994 set r0 0
3996 set y1 [expr {int([lindex $f 1] * $ymax)}]
3997 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
3998 if {$r1 >= $numcommits} {
3999 set r1 [expr {$numcommits - 1}]
4001 return [list $r0 $r1]
4004 proc layoutmore {} {
4005 global commitidx viewcomplete curview
4006 global numcommits pending_select curview
4007 global lastscrollset lastscrollrows commitinterest
4009 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4010 [clock clicks -milliseconds] - $lastscrollset > 500} {
4011 setcanvscroll
4013 if {[info exists pending_select] &&
4014 [commitinview $pending_select $curview]} {
4015 selectline [rowofcommit $pending_select] 1
4017 drawvisible
4020 proc doshowlocalchanges {} {
4021 global curview mainheadid
4023 if {$mainheadid eq {}} return
4024 if {[commitinview $mainheadid $curview]} {
4025 dodiffindex
4026 } else {
4027 lappend commitinterest($mainheadid) {dodiffindex}
4031 proc dohidelocalchanges {} {
4032 global nullid nullid2 lserial curview
4034 if {[commitinview $nullid $curview]} {
4035 removefakerow $nullid
4037 if {[commitinview $nullid2 $curview]} {
4038 removefakerow $nullid2
4040 incr lserial
4043 # spawn off a process to do git diff-index --cached HEAD
4044 proc dodiffindex {} {
4045 global lserial showlocalchanges
4046 global isworktree
4048 if {!$showlocalchanges || !$isworktree} return
4049 incr lserial
4050 set fd [open "|git diff-index --cached HEAD" r]
4051 fconfigure $fd -blocking 0
4052 filerun $fd [list readdiffindex $fd $lserial]
4055 proc readdiffindex {fd serial} {
4056 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4058 set isdiff 1
4059 if {[gets $fd line] < 0} {
4060 if {![eof $fd]} {
4061 return 1
4063 set isdiff 0
4065 # we only need to see one line and we don't really care what it says...
4066 close $fd
4068 if {$serial != $lserial} {
4069 return 0
4072 # now see if there are any local changes not checked in to the index
4073 set fd [open "|git diff-files" r]
4074 fconfigure $fd -blocking 0
4075 filerun $fd [list readdifffiles $fd $serial]
4077 if {$isdiff && ![commitinview $nullid2 $curview]} {
4078 # add the line for the changes in the index to the graph
4079 set hl [mc "Local changes checked in to index but not committed"]
4080 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4081 set commitdata($nullid2) "\n $hl\n"
4082 if {[commitinview $nullid $curview]} {
4083 removefakerow $nullid
4085 insertfakerow $nullid2 $mainheadid
4086 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4087 removefakerow $nullid2
4089 return 0
4092 proc readdifffiles {fd serial} {
4093 global mainheadid nullid nullid2 curview
4094 global commitinfo commitdata lserial
4096 set isdiff 1
4097 if {[gets $fd line] < 0} {
4098 if {![eof $fd]} {
4099 return 1
4101 set isdiff 0
4103 # we only need to see one line and we don't really care what it says...
4104 close $fd
4106 if {$serial != $lserial} {
4107 return 0
4110 if {$isdiff && ![commitinview $nullid $curview]} {
4111 # add the line for the local diff to the graph
4112 set hl [mc "Local uncommitted changes, not checked in to index"]
4113 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4114 set commitdata($nullid) "\n $hl\n"
4115 if {[commitinview $nullid2 $curview]} {
4116 set p $nullid2
4117 } else {
4118 set p $mainheadid
4120 insertfakerow $nullid $p
4121 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4122 removefakerow $nullid
4124 return 0
4127 proc nextuse {id row} {
4128 global curview children
4130 if {[info exists children($curview,$id)]} {
4131 foreach kid $children($curview,$id) {
4132 if {![commitinview $kid $curview]} {
4133 return -1
4135 if {[rowofcommit $kid] > $row} {
4136 return [rowofcommit $kid]
4140 if {[commitinview $id $curview]} {
4141 return [rowofcommit $id]
4143 return -1
4146 proc prevuse {id row} {
4147 global curview children
4149 set ret -1
4150 if {[info exists children($curview,$id)]} {
4151 foreach kid $children($curview,$id) {
4152 if {![commitinview $kid $curview]} break
4153 if {[rowofcommit $kid] < $row} {
4154 set ret [rowofcommit $kid]
4158 return $ret
4161 proc make_idlist {row} {
4162 global displayorder parentlist uparrowlen downarrowlen mingaplen
4163 global commitidx curview children
4165 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4166 if {$r < 0} {
4167 set r 0
4169 set ra [expr {$row - $downarrowlen}]
4170 if {$ra < 0} {
4171 set ra 0
4173 set rb [expr {$row + $uparrowlen}]
4174 if {$rb > $commitidx($curview)} {
4175 set rb $commitidx($curview)
4177 make_disporder $r [expr {$rb + 1}]
4178 set ids {}
4179 for {} {$r < $ra} {incr r} {
4180 set nextid [lindex $displayorder [expr {$r + 1}]]
4181 foreach p [lindex $parentlist $r] {
4182 if {$p eq $nextid} continue
4183 set rn [nextuse $p $r]
4184 if {$rn >= $row &&
4185 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4186 lappend ids [list [ordertoken $p] $p]
4190 for {} {$r < $row} {incr r} {
4191 set nextid [lindex $displayorder [expr {$r + 1}]]
4192 foreach p [lindex $parentlist $r] {
4193 if {$p eq $nextid} continue
4194 set rn [nextuse $p $r]
4195 if {$rn < 0 || $rn >= $row} {
4196 lappend ids [list [ordertoken $p] $p]
4200 set id [lindex $displayorder $row]
4201 lappend ids [list [ordertoken $id] $id]
4202 while {$r < $rb} {
4203 foreach p [lindex $parentlist $r] {
4204 set firstkid [lindex $children($curview,$p) 0]
4205 if {[rowofcommit $firstkid] < $row} {
4206 lappend ids [list [ordertoken $p] $p]
4209 incr r
4210 set id [lindex $displayorder $r]
4211 if {$id ne {}} {
4212 set firstkid [lindex $children($curview,$id) 0]
4213 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4214 lappend ids [list [ordertoken $id] $id]
4218 set idlist {}
4219 foreach idx [lsort -unique $ids] {
4220 lappend idlist [lindex $idx 1]
4222 return $idlist
4225 proc rowsequal {a b} {
4226 while {[set i [lsearch -exact $a {}]] >= 0} {
4227 set a [lreplace $a $i $i]
4229 while {[set i [lsearch -exact $b {}]] >= 0} {
4230 set b [lreplace $b $i $i]
4232 return [expr {$a eq $b}]
4235 proc makeupline {id row rend col} {
4236 global rowidlist uparrowlen downarrowlen mingaplen
4238 for {set r $rend} {1} {set r $rstart} {
4239 set rstart [prevuse $id $r]
4240 if {$rstart < 0} return
4241 if {$rstart < $row} break
4243 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4244 set rstart [expr {$rend - $uparrowlen - 1}]
4246 for {set r $rstart} {[incr r] <= $row} {} {
4247 set idlist [lindex $rowidlist $r]
4248 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4249 set col [idcol $idlist $id $col]
4250 lset rowidlist $r [linsert $idlist $col $id]
4251 changedrow $r
4256 proc layoutrows {row endrow} {
4257 global rowidlist rowisopt rowfinal displayorder
4258 global uparrowlen downarrowlen maxwidth mingaplen
4259 global children parentlist
4260 global commitidx viewcomplete curview
4262 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4263 set idlist {}
4264 if {$row > 0} {
4265 set rm1 [expr {$row - 1}]
4266 foreach id [lindex $rowidlist $rm1] {
4267 if {$id ne {}} {
4268 lappend idlist $id
4271 set final [lindex $rowfinal $rm1]
4273 for {} {$row < $endrow} {incr row} {
4274 set rm1 [expr {$row - 1}]
4275 if {$rm1 < 0 || $idlist eq {}} {
4276 set idlist [make_idlist $row]
4277 set final 1
4278 } else {
4279 set id [lindex $displayorder $rm1]
4280 set col [lsearch -exact $idlist $id]
4281 set idlist [lreplace $idlist $col $col]
4282 foreach p [lindex $parentlist $rm1] {
4283 if {[lsearch -exact $idlist $p] < 0} {
4284 set col [idcol $idlist $p $col]
4285 set idlist [linsert $idlist $col $p]
4286 # if not the first child, we have to insert a line going up
4287 if {$id ne [lindex $children($curview,$p) 0]} {
4288 makeupline $p $rm1 $row $col
4292 set id [lindex $displayorder $row]
4293 if {$row > $downarrowlen} {
4294 set termrow [expr {$row - $downarrowlen - 1}]
4295 foreach p [lindex $parentlist $termrow] {
4296 set i [lsearch -exact $idlist $p]
4297 if {$i < 0} continue
4298 set nr [nextuse $p $termrow]
4299 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4300 set idlist [lreplace $idlist $i $i]
4304 set col [lsearch -exact $idlist $id]
4305 if {$col < 0} {
4306 set col [idcol $idlist $id]
4307 set idlist [linsert $idlist $col $id]
4308 if {$children($curview,$id) ne {}} {
4309 makeupline $id $rm1 $row $col
4312 set r [expr {$row + $uparrowlen - 1}]
4313 if {$r < $commitidx($curview)} {
4314 set x $col
4315 foreach p [lindex $parentlist $r] {
4316 if {[lsearch -exact $idlist $p] >= 0} continue
4317 set fk [lindex $children($curview,$p) 0]
4318 if {[rowofcommit $fk] < $row} {
4319 set x [idcol $idlist $p $x]
4320 set idlist [linsert $idlist $x $p]
4323 if {[incr r] < $commitidx($curview)} {
4324 set p [lindex $displayorder $r]
4325 if {[lsearch -exact $idlist $p] < 0} {
4326 set fk [lindex $children($curview,$p) 0]
4327 if {$fk ne {} && [rowofcommit $fk] < $row} {
4328 set x [idcol $idlist $p $x]
4329 set idlist [linsert $idlist $x $p]
4335 if {$final && !$viewcomplete($curview) &&
4336 $row + $uparrowlen + $mingaplen + $downarrowlen
4337 >= $commitidx($curview)} {
4338 set final 0
4340 set l [llength $rowidlist]
4341 if {$row == $l} {
4342 lappend rowidlist $idlist
4343 lappend rowisopt 0
4344 lappend rowfinal $final
4345 } elseif {$row < $l} {
4346 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4347 lset rowidlist $row $idlist
4348 changedrow $row
4350 lset rowfinal $row $final
4351 } else {
4352 set pad [ntimes [expr {$row - $l}] {}]
4353 set rowidlist [concat $rowidlist $pad]
4354 lappend rowidlist $idlist
4355 set rowfinal [concat $rowfinal $pad]
4356 lappend rowfinal $final
4357 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4360 return $row
4363 proc changedrow {row} {
4364 global displayorder iddrawn rowisopt need_redisplay
4366 set l [llength $rowisopt]
4367 if {$row < $l} {
4368 lset rowisopt $row 0
4369 if {$row + 1 < $l} {
4370 lset rowisopt [expr {$row + 1}] 0
4371 if {$row + 2 < $l} {
4372 lset rowisopt [expr {$row + 2}] 0
4376 set id [lindex $displayorder $row]
4377 if {[info exists iddrawn($id)]} {
4378 set need_redisplay 1
4382 proc insert_pad {row col npad} {
4383 global rowidlist
4385 set pad [ntimes $npad {}]
4386 set idlist [lindex $rowidlist $row]
4387 set bef [lrange $idlist 0 [expr {$col - 1}]]
4388 set aft [lrange $idlist $col end]
4389 set i [lsearch -exact $aft {}]
4390 if {$i > 0} {
4391 set aft [lreplace $aft $i $i]
4393 lset rowidlist $row [concat $bef $pad $aft]
4394 changedrow $row
4397 proc optimize_rows {row col endrow} {
4398 global rowidlist rowisopt displayorder curview children
4400 if {$row < 1} {
4401 set row 1
4403 for {} {$row < $endrow} {incr row; set col 0} {
4404 if {[lindex $rowisopt $row]} continue
4405 set haspad 0
4406 set y0 [expr {$row - 1}]
4407 set ym [expr {$row - 2}]
4408 set idlist [lindex $rowidlist $row]
4409 set previdlist [lindex $rowidlist $y0]
4410 if {$idlist eq {} || $previdlist eq {}} continue
4411 if {$ym >= 0} {
4412 set pprevidlist [lindex $rowidlist $ym]
4413 if {$pprevidlist eq {}} continue
4414 } else {
4415 set pprevidlist {}
4417 set x0 -1
4418 set xm -1
4419 for {} {$col < [llength $idlist]} {incr col} {
4420 set id [lindex $idlist $col]
4421 if {[lindex $previdlist $col] eq $id} continue
4422 if {$id eq {}} {
4423 set haspad 1
4424 continue
4426 set x0 [lsearch -exact $previdlist $id]
4427 if {$x0 < 0} continue
4428 set z [expr {$x0 - $col}]
4429 set isarrow 0
4430 set z0 {}
4431 if {$ym >= 0} {
4432 set xm [lsearch -exact $pprevidlist $id]
4433 if {$xm >= 0} {
4434 set z0 [expr {$xm - $x0}]
4437 if {$z0 eq {}} {
4438 # if row y0 is the first child of $id then it's not an arrow
4439 if {[lindex $children($curview,$id) 0] ne
4440 [lindex $displayorder $y0]} {
4441 set isarrow 1
4444 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4445 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4446 set isarrow 1
4448 # Looking at lines from this row to the previous row,
4449 # make them go straight up if they end in an arrow on
4450 # the previous row; otherwise make them go straight up
4451 # or at 45 degrees.
4452 if {$z < -1 || ($z < 0 && $isarrow)} {
4453 # Line currently goes left too much;
4454 # insert pads in the previous row, then optimize it
4455 set npad [expr {-1 - $z + $isarrow}]
4456 insert_pad $y0 $x0 $npad
4457 if {$y0 > 0} {
4458 optimize_rows $y0 $x0 $row
4460 set previdlist [lindex $rowidlist $y0]
4461 set x0 [lsearch -exact $previdlist $id]
4462 set z [expr {$x0 - $col}]
4463 if {$z0 ne {}} {
4464 set pprevidlist [lindex $rowidlist $ym]
4465 set xm [lsearch -exact $pprevidlist $id]
4466 set z0 [expr {$xm - $x0}]
4468 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4469 # Line currently goes right too much;
4470 # insert pads in this line
4471 set npad [expr {$z - 1 + $isarrow}]
4472 insert_pad $row $col $npad
4473 set idlist [lindex $rowidlist $row]
4474 incr col $npad
4475 set z [expr {$x0 - $col}]
4476 set haspad 1
4478 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4479 # this line links to its first child on row $row-2
4480 set id [lindex $displayorder $ym]
4481 set xc [lsearch -exact $pprevidlist $id]
4482 if {$xc >= 0} {
4483 set z0 [expr {$xc - $x0}]
4486 # avoid lines jigging left then immediately right
4487 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4488 insert_pad $y0 $x0 1
4489 incr x0
4490 optimize_rows $y0 $x0 $row
4491 set previdlist [lindex $rowidlist $y0]
4494 if {!$haspad} {
4495 # Find the first column that doesn't have a line going right
4496 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4497 set id [lindex $idlist $col]
4498 if {$id eq {}} break
4499 set x0 [lsearch -exact $previdlist $id]
4500 if {$x0 < 0} {
4501 # check if this is the link to the first child
4502 set kid [lindex $displayorder $y0]
4503 if {[lindex $children($curview,$id) 0] eq $kid} {
4504 # it is, work out offset to child
4505 set x0 [lsearch -exact $previdlist $kid]
4508 if {$x0 <= $col} break
4510 # Insert a pad at that column as long as it has a line and
4511 # isn't the last column
4512 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4513 set idlist [linsert $idlist $col {}]
4514 lset rowidlist $row $idlist
4515 changedrow $row
4521 proc xc {row col} {
4522 global canvx0 linespc
4523 return [expr {$canvx0 + $col * $linespc}]
4526 proc yc {row} {
4527 global canvy0 linespc
4528 return [expr {$canvy0 + $row * $linespc}]
4531 proc linewidth {id} {
4532 global thickerline lthickness
4534 set wid $lthickness
4535 if {[info exists thickerline] && $id eq $thickerline} {
4536 set wid [expr {2 * $lthickness}]
4538 return $wid
4541 proc rowranges {id} {
4542 global curview children uparrowlen downarrowlen
4543 global rowidlist
4545 set kids $children($curview,$id)
4546 if {$kids eq {}} {
4547 return {}
4549 set ret {}
4550 lappend kids $id
4551 foreach child $kids {
4552 if {![commitinview $child $curview]} break
4553 set row [rowofcommit $child]
4554 if {![info exists prev]} {
4555 lappend ret [expr {$row + 1}]
4556 } else {
4557 if {$row <= $prevrow} {
4558 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4560 # see if the line extends the whole way from prevrow to row
4561 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4562 [lsearch -exact [lindex $rowidlist \
4563 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4564 # it doesn't, see where it ends
4565 set r [expr {$prevrow + $downarrowlen}]
4566 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4567 while {[incr r -1] > $prevrow &&
4568 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4569 } else {
4570 while {[incr r] <= $row &&
4571 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4572 incr r -1
4574 lappend ret $r
4575 # see where it starts up again
4576 set r [expr {$row - $uparrowlen}]
4577 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4578 while {[incr r] < $row &&
4579 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4580 } else {
4581 while {[incr r -1] >= $prevrow &&
4582 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4583 incr r
4585 lappend ret $r
4588 if {$child eq $id} {
4589 lappend ret $row
4591 set prev $child
4592 set prevrow $row
4594 return $ret
4597 proc drawlineseg {id row endrow arrowlow} {
4598 global rowidlist displayorder iddrawn linesegs
4599 global canv colormap linespc curview maxlinelen parentlist
4601 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4602 set le [expr {$row + 1}]
4603 set arrowhigh 1
4604 while {1} {
4605 set c [lsearch -exact [lindex $rowidlist $le] $id]
4606 if {$c < 0} {
4607 incr le -1
4608 break
4610 lappend cols $c
4611 set x [lindex $displayorder $le]
4612 if {$x eq $id} {
4613 set arrowhigh 0
4614 break
4616 if {[info exists iddrawn($x)] || $le == $endrow} {
4617 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4618 if {$c >= 0} {
4619 lappend cols $c
4620 set arrowhigh 0
4622 break
4624 incr le
4626 if {$le <= $row} {
4627 return $row
4630 set lines {}
4631 set i 0
4632 set joinhigh 0
4633 if {[info exists linesegs($id)]} {
4634 set lines $linesegs($id)
4635 foreach li $lines {
4636 set r0 [lindex $li 0]
4637 if {$r0 > $row} {
4638 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4639 set joinhigh 1
4641 break
4643 incr i
4646 set joinlow 0
4647 if {$i > 0} {
4648 set li [lindex $lines [expr {$i-1}]]
4649 set r1 [lindex $li 1]
4650 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4651 set joinlow 1
4655 set x [lindex $cols [expr {$le - $row}]]
4656 set xp [lindex $cols [expr {$le - 1 - $row}]]
4657 set dir [expr {$xp - $x}]
4658 if {$joinhigh} {
4659 set ith [lindex $lines $i 2]
4660 set coords [$canv coords $ith]
4661 set ah [$canv itemcget $ith -arrow]
4662 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4663 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4664 if {$x2 ne {} && $x - $x2 == $dir} {
4665 set coords [lrange $coords 0 end-2]
4667 } else {
4668 set coords [list [xc $le $x] [yc $le]]
4670 if {$joinlow} {
4671 set itl [lindex $lines [expr {$i-1}] 2]
4672 set al [$canv itemcget $itl -arrow]
4673 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4674 } elseif {$arrowlow} {
4675 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4676 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4677 set arrowlow 0
4680 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4681 for {set y $le} {[incr y -1] > $row} {} {
4682 set x $xp
4683 set xp [lindex $cols [expr {$y - 1 - $row}]]
4684 set ndir [expr {$xp - $x}]
4685 if {$dir != $ndir || $xp < 0} {
4686 lappend coords [xc $y $x] [yc $y]
4688 set dir $ndir
4690 if {!$joinlow} {
4691 if {$xp < 0} {
4692 # join parent line to first child
4693 set ch [lindex $displayorder $row]
4694 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4695 if {$xc < 0} {
4696 puts "oops: drawlineseg: child $ch not on row $row"
4697 } elseif {$xc != $x} {
4698 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4699 set d [expr {int(0.5 * $linespc)}]
4700 set x1 [xc $row $x]
4701 if {$xc < $x} {
4702 set x2 [expr {$x1 - $d}]
4703 } else {
4704 set x2 [expr {$x1 + $d}]
4706 set y2 [yc $row]
4707 set y1 [expr {$y2 + $d}]
4708 lappend coords $x1 $y1 $x2 $y2
4709 } elseif {$xc < $x - 1} {
4710 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4711 } elseif {$xc > $x + 1} {
4712 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4714 set x $xc
4716 lappend coords [xc $row $x] [yc $row]
4717 } else {
4718 set xn [xc $row $xp]
4719 set yn [yc $row]
4720 lappend coords $xn $yn
4722 if {!$joinhigh} {
4723 assigncolor $id
4724 set t [$canv create line $coords -width [linewidth $id] \
4725 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4726 $canv lower $t
4727 bindline $t $id
4728 set lines [linsert $lines $i [list $row $le $t]]
4729 } else {
4730 $canv coords $ith $coords
4731 if {$arrow ne $ah} {
4732 $canv itemconf $ith -arrow $arrow
4734 lset lines $i 0 $row
4736 } else {
4737 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4738 set ndir [expr {$xo - $xp}]
4739 set clow [$canv coords $itl]
4740 if {$dir == $ndir} {
4741 set clow [lrange $clow 2 end]
4743 set coords [concat $coords $clow]
4744 if {!$joinhigh} {
4745 lset lines [expr {$i-1}] 1 $le
4746 } else {
4747 # coalesce two pieces
4748 $canv delete $ith
4749 set b [lindex $lines [expr {$i-1}] 0]
4750 set e [lindex $lines $i 1]
4751 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4753 $canv coords $itl $coords
4754 if {$arrow ne $al} {
4755 $canv itemconf $itl -arrow $arrow
4759 set linesegs($id) $lines
4760 return $le
4763 proc drawparentlinks {id row} {
4764 global rowidlist canv colormap curview parentlist
4765 global idpos linespc
4767 set rowids [lindex $rowidlist $row]
4768 set col [lsearch -exact $rowids $id]
4769 if {$col < 0} return
4770 set olds [lindex $parentlist $row]
4771 set row2 [expr {$row + 1}]
4772 set x [xc $row $col]
4773 set y [yc $row]
4774 set y2 [yc $row2]
4775 set d [expr {int(0.5 * $linespc)}]
4776 set ymid [expr {$y + $d}]
4777 set ids [lindex $rowidlist $row2]
4778 # rmx = right-most X coord used
4779 set rmx 0
4780 foreach p $olds {
4781 set i [lsearch -exact $ids $p]
4782 if {$i < 0} {
4783 puts "oops, parent $p of $id not in list"
4784 continue
4786 set x2 [xc $row2 $i]
4787 if {$x2 > $rmx} {
4788 set rmx $x2
4790 set j [lsearch -exact $rowids $p]
4791 if {$j < 0} {
4792 # drawlineseg will do this one for us
4793 continue
4795 assigncolor $p
4796 # should handle duplicated parents here...
4797 set coords [list $x $y]
4798 if {$i != $col} {
4799 # if attaching to a vertical segment, draw a smaller
4800 # slant for visual distinctness
4801 if {$i == $j} {
4802 if {$i < $col} {
4803 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4804 } else {
4805 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4807 } elseif {$i < $col && $i < $j} {
4808 # segment slants towards us already
4809 lappend coords [xc $row $j] $y
4810 } else {
4811 if {$i < $col - 1} {
4812 lappend coords [expr {$x2 + $linespc}] $y
4813 } elseif {$i > $col + 1} {
4814 lappend coords [expr {$x2 - $linespc}] $y
4816 lappend coords $x2 $y2
4818 } else {
4819 lappend coords $x2 $y2
4821 set t [$canv create line $coords -width [linewidth $p] \
4822 -fill $colormap($p) -tags lines.$p]
4823 $canv lower $t
4824 bindline $t $p
4826 if {$rmx > [lindex $idpos($id) 1]} {
4827 lset idpos($id) 1 $rmx
4828 redrawtags $id
4832 proc drawlines {id} {
4833 global canv
4835 $canv itemconf lines.$id -width [linewidth $id]
4838 proc drawcmittext {id row col} {
4839 global linespc canv canv2 canv3 fgcolor curview
4840 global cmitlisted commitinfo rowidlist parentlist
4841 global rowtextx idpos idtags idheads idotherrefs
4842 global linehtag linentag linedtag selectedline
4843 global canvxmax boldrows boldnamerows fgcolor
4844 global mainheadid nullid nullid2 circleitem circlecolors
4846 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
4847 set listed $cmitlisted($curview,$id)
4848 if {$id eq $nullid} {
4849 set ofill red
4850 } elseif {$id eq $nullid2} {
4851 set ofill green
4852 } elseif {$id eq $mainheadid} {
4853 set ofill yellow
4854 } else {
4855 set ofill [lindex $circlecolors $listed]
4857 set x [xc $row $col]
4858 set y [yc $row]
4859 set orad [expr {$linespc / 3}]
4860 if {$listed <= 2} {
4861 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4862 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4863 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4864 } elseif {$listed == 3} {
4865 # triangle pointing left for left-side commits
4866 set t [$canv create polygon \
4867 [expr {$x - $orad}] $y \
4868 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4869 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4870 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4871 } else {
4872 # triangle pointing right for right-side commits
4873 set t [$canv create polygon \
4874 [expr {$x + $orad - 1}] $y \
4875 [expr {$x - $orad}] [expr {$y - $orad}] \
4876 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4877 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4879 set circleitem($row) $t
4880 $canv raise $t
4881 $canv bind $t <1> {selcanvline {} %x %y}
4882 set rmx [llength [lindex $rowidlist $row]]
4883 set olds [lindex $parentlist $row]
4884 if {$olds ne {}} {
4885 set nextids [lindex $rowidlist [expr {$row + 1}]]
4886 foreach p $olds {
4887 set i [lsearch -exact $nextids $p]
4888 if {$i > $rmx} {
4889 set rmx $i
4893 set xt [xc $row $rmx]
4894 set rowtextx($row) $xt
4895 set idpos($id) [list $x $xt $y]
4896 if {[info exists idtags($id)] || [info exists idheads($id)]
4897 || [info exists idotherrefs($id)]} {
4898 set xt [drawtags $id $x $xt $y]
4900 set headline [lindex $commitinfo($id) 0]
4901 set name [lindex $commitinfo($id) 1]
4902 set date [lindex $commitinfo($id) 2]
4903 set date [formatdate $date]
4904 set font mainfont
4905 set nfont mainfont
4906 set isbold [ishighlighted $id]
4907 if {$isbold > 0} {
4908 lappend boldrows $row
4909 set font mainfontbold
4910 if {$isbold > 1} {
4911 lappend boldnamerows $row
4912 set nfont mainfontbold
4915 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4916 -text $headline -font $font -tags text]
4917 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4918 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4919 -text $name -font $nfont -tags text]
4920 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4921 -text $date -font mainfont -tags text]
4922 if {$selectedline == $row} {
4923 make_secsel $row
4925 set xr [expr {$xt + [font measure $font $headline]}]
4926 if {$xr > $canvxmax} {
4927 set canvxmax $xr
4928 setcanvscroll
4932 proc drawcmitrow {row} {
4933 global displayorder rowidlist nrows_drawn
4934 global iddrawn markingmatches
4935 global commitinfo numcommits
4936 global filehighlight fhighlights findpattern nhighlights
4937 global hlview vhighlights
4938 global highlight_related rhighlights
4940 if {$row >= $numcommits} return
4942 set id [lindex $displayorder $row]
4943 if {[info exists hlview] && ![info exists vhighlights($id)]} {
4944 askvhighlight $row $id
4946 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
4947 askfilehighlight $row $id
4949 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
4950 askfindhighlight $row $id
4952 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
4953 askrelhighlight $row $id
4955 if {![info exists iddrawn($id)]} {
4956 set col [lsearch -exact [lindex $rowidlist $row] $id]
4957 if {$col < 0} {
4958 puts "oops, row $row id $id not in list"
4959 return
4961 if {![info exists commitinfo($id)]} {
4962 getcommit $id
4964 assigncolor $id
4965 drawcmittext $id $row $col
4966 set iddrawn($id) 1
4967 incr nrows_drawn
4969 if {$markingmatches} {
4970 markrowmatches $row $id
4974 proc drawcommits {row {endrow {}}} {
4975 global numcommits iddrawn displayorder curview need_redisplay
4976 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
4978 if {$row < 0} {
4979 set row 0
4981 if {$endrow eq {}} {
4982 set endrow $row
4984 if {$endrow >= $numcommits} {
4985 set endrow [expr {$numcommits - 1}]
4988 set rl1 [expr {$row - $downarrowlen - 3}]
4989 if {$rl1 < 0} {
4990 set rl1 0
4992 set ro1 [expr {$row - 3}]
4993 if {$ro1 < 0} {
4994 set ro1 0
4996 set r2 [expr {$endrow + $uparrowlen + 3}]
4997 if {$r2 > $numcommits} {
4998 set r2 $numcommits
5000 for {set r $rl1} {$r < $r2} {incr r} {
5001 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5002 if {$rl1 < $r} {
5003 layoutrows $rl1 $r
5005 set rl1 [expr {$r + 1}]
5008 if {$rl1 < $r} {
5009 layoutrows $rl1 $r
5011 optimize_rows $ro1 0 $r2
5012 if {$need_redisplay || $nrows_drawn > 2000} {
5013 clear_display
5014 drawvisible
5017 # make the lines join to already-drawn rows either side
5018 set r [expr {$row - 1}]
5019 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5020 set r $row
5022 set er [expr {$endrow + 1}]
5023 if {$er >= $numcommits ||
5024 ![info exists iddrawn([lindex $displayorder $er])]} {
5025 set er $endrow
5027 for {} {$r <= $er} {incr r} {
5028 set id [lindex $displayorder $r]
5029 set wasdrawn [info exists iddrawn($id)]
5030 drawcmitrow $r
5031 if {$r == $er} break
5032 set nextid [lindex $displayorder [expr {$r + 1}]]
5033 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5034 drawparentlinks $id $r
5036 set rowids [lindex $rowidlist $r]
5037 foreach lid $rowids {
5038 if {$lid eq {}} continue
5039 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5040 if {$lid eq $id} {
5041 # see if this is the first child of any of its parents
5042 foreach p [lindex $parentlist $r] {
5043 if {[lsearch -exact $rowids $p] < 0} {
5044 # make this line extend up to the child
5045 set lineend($p) [drawlineseg $p $r $er 0]
5048 } else {
5049 set lineend($lid) [drawlineseg $lid $r $er 1]
5055 proc undolayout {row} {
5056 global uparrowlen mingaplen downarrowlen
5057 global rowidlist rowisopt rowfinal need_redisplay
5059 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5060 if {$r < 0} {
5061 set r 0
5063 if {[llength $rowidlist] > $r} {
5064 incr r -1
5065 set rowidlist [lrange $rowidlist 0 $r]
5066 set rowfinal [lrange $rowfinal 0 $r]
5067 set rowisopt [lrange $rowisopt 0 $r]
5068 set need_redisplay 1
5069 run drawvisible
5073 proc drawvisible {} {
5074 global canv linespc curview vrowmod selectedline targetrow targetid
5075 global need_redisplay cscroll numcommits
5077 set fs [$canv yview]
5078 set ymax [lindex [$canv cget -scrollregion] 3]
5079 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5080 set f0 [lindex $fs 0]
5081 set f1 [lindex $fs 1]
5082 set y0 [expr {int($f0 * $ymax)}]
5083 set y1 [expr {int($f1 * $ymax)}]
5085 if {[info exists targetid]} {
5086 if {[commitinview $targetid $curview]} {
5087 set r [rowofcommit $targetid]
5088 if {$r != $targetrow} {
5089 # Fix up the scrollregion and change the scrolling position
5090 # now that our target row has moved.
5091 set diff [expr {($r - $targetrow) * $linespc}]
5092 set targetrow $r
5093 setcanvscroll
5094 set ymax [lindex [$canv cget -scrollregion] 3]
5095 incr y0 $diff
5096 incr y1 $diff
5097 set f0 [expr {$y0 / $ymax}]
5098 set f1 [expr {$y1 / $ymax}]
5099 allcanvs yview moveto $f0
5100 $cscroll set $f0 $f1
5101 set need_redisplay 1
5103 } else {
5104 unset targetid
5108 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5109 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5110 if {$endrow >= $vrowmod($curview)} {
5111 update_arcrows $curview
5113 if {$selectedline ne {} &&
5114 $row <= $selectedline && $selectedline <= $endrow} {
5115 set targetrow $selectedline
5116 } elseif {[info exists targetid]} {
5117 set targetrow [expr {int(($row + $endrow) / 2)}]
5119 if {[info exists targetrow]} {
5120 if {$targetrow >= $numcommits} {
5121 set targetrow [expr {$numcommits - 1}]
5123 set targetid [commitonrow $targetrow]
5125 drawcommits $row $endrow
5128 proc clear_display {} {
5129 global iddrawn linesegs need_redisplay nrows_drawn
5130 global vhighlights fhighlights nhighlights rhighlights
5131 global linehtag linentag linedtag boldrows boldnamerows
5133 allcanvs delete all
5134 catch {unset iddrawn}
5135 catch {unset linesegs}
5136 catch {unset linehtag}
5137 catch {unset linentag}
5138 catch {unset linedtag}
5139 set boldrows {}
5140 set boldnamerows {}
5141 catch {unset vhighlights}
5142 catch {unset fhighlights}
5143 catch {unset nhighlights}
5144 catch {unset rhighlights}
5145 set need_redisplay 0
5146 set nrows_drawn 0
5149 proc findcrossings {id} {
5150 global rowidlist parentlist numcommits displayorder
5152 set cross {}
5153 set ccross {}
5154 foreach {s e} [rowranges $id] {
5155 if {$e >= $numcommits} {
5156 set e [expr {$numcommits - 1}]
5158 if {$e <= $s} continue
5159 for {set row $e} {[incr row -1] >= $s} {} {
5160 set x [lsearch -exact [lindex $rowidlist $row] $id]
5161 if {$x < 0} break
5162 set olds [lindex $parentlist $row]
5163 set kid [lindex $displayorder $row]
5164 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5165 if {$kidx < 0} continue
5166 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5167 foreach p $olds {
5168 set px [lsearch -exact $nextrow $p]
5169 if {$px < 0} continue
5170 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5171 if {[lsearch -exact $ccross $p] >= 0} continue
5172 if {$x == $px + ($kidx < $px? -1: 1)} {
5173 lappend ccross $p
5174 } elseif {[lsearch -exact $cross $p] < 0} {
5175 lappend cross $p
5181 return [concat $ccross {{}} $cross]
5184 proc assigncolor {id} {
5185 global colormap colors nextcolor
5186 global parents children children curview
5188 if {[info exists colormap($id)]} return
5189 set ncolors [llength $colors]
5190 if {[info exists children($curview,$id)]} {
5191 set kids $children($curview,$id)
5192 } else {
5193 set kids {}
5195 if {[llength $kids] == 1} {
5196 set child [lindex $kids 0]
5197 if {[info exists colormap($child)]
5198 && [llength $parents($curview,$child)] == 1} {
5199 set colormap($id) $colormap($child)
5200 return
5203 set badcolors {}
5204 set origbad {}
5205 foreach x [findcrossings $id] {
5206 if {$x eq {}} {
5207 # delimiter between corner crossings and other crossings
5208 if {[llength $badcolors] >= $ncolors - 1} break
5209 set origbad $badcolors
5211 if {[info exists colormap($x)]
5212 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5213 lappend badcolors $colormap($x)
5216 if {[llength $badcolors] >= $ncolors} {
5217 set badcolors $origbad
5219 set origbad $badcolors
5220 if {[llength $badcolors] < $ncolors - 1} {
5221 foreach child $kids {
5222 if {[info exists colormap($child)]
5223 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5224 lappend badcolors $colormap($child)
5226 foreach p $parents($curview,$child) {
5227 if {[info exists colormap($p)]
5228 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5229 lappend badcolors $colormap($p)
5233 if {[llength $badcolors] >= $ncolors} {
5234 set badcolors $origbad
5237 for {set i 0} {$i <= $ncolors} {incr i} {
5238 set c [lindex $colors $nextcolor]
5239 if {[incr nextcolor] >= $ncolors} {
5240 set nextcolor 0
5242 if {[lsearch -exact $badcolors $c]} break
5244 set colormap($id) $c
5247 proc bindline {t id} {
5248 global canv
5250 $canv bind $t <Enter> "lineenter %x %y $id"
5251 $canv bind $t <Motion> "linemotion %x %y $id"
5252 $canv bind $t <Leave> "lineleave $id"
5253 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5256 proc drawtags {id x xt y1} {
5257 global idtags idheads idotherrefs mainhead
5258 global linespc lthickness
5259 global canv rowtextx curview fgcolor bgcolor
5261 set marks {}
5262 set ntags 0
5263 set nheads 0
5264 if {[info exists idtags($id)]} {
5265 set marks $idtags($id)
5266 set ntags [llength $marks]
5268 if {[info exists idheads($id)]} {
5269 set marks [concat $marks $idheads($id)]
5270 set nheads [llength $idheads($id)]
5272 if {[info exists idotherrefs($id)]} {
5273 set marks [concat $marks $idotherrefs($id)]
5275 if {$marks eq {}} {
5276 return $xt
5279 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5280 set yt [expr {$y1 - 0.5 * $linespc}]
5281 set yb [expr {$yt + $linespc - 1}]
5282 set xvals {}
5283 set wvals {}
5284 set i -1
5285 foreach tag $marks {
5286 incr i
5287 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5288 set wid [font measure mainfontbold $tag]
5289 } else {
5290 set wid [font measure mainfont $tag]
5292 lappend xvals $xt
5293 lappend wvals $wid
5294 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5296 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5297 -width $lthickness -fill black -tags tag.$id]
5298 $canv lower $t
5299 foreach tag $marks x $xvals wid $wvals {
5300 set xl [expr {$x + $delta}]
5301 set xr [expr {$x + $delta + $wid + $lthickness}]
5302 set font mainfont
5303 if {[incr ntags -1] >= 0} {
5304 # draw a tag
5305 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5306 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5307 -width 1 -outline black -fill yellow -tags tag.$id]
5308 $canv bind $t <1> [list showtag $tag 1]
5309 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5310 } else {
5311 # draw a head or other ref
5312 if {[incr nheads -1] >= 0} {
5313 set col green
5314 if {$tag eq $mainhead} {
5315 set font mainfontbold
5317 } else {
5318 set col "#ddddff"
5320 set xl [expr {$xl - $delta/2}]
5321 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5322 -width 1 -outline black -fill $col -tags tag.$id
5323 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5324 set rwid [font measure mainfont $remoteprefix]
5325 set xi [expr {$x + 1}]
5326 set yti [expr {$yt + 1}]
5327 set xri [expr {$x + $rwid}]
5328 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5329 -width 0 -fill "#ffddaa" -tags tag.$id
5332 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5333 -font $font -tags [list tag.$id text]]
5334 if {$ntags >= 0} {
5335 $canv bind $t <1> [list showtag $tag 1]
5336 } elseif {$nheads >= 0} {
5337 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
5340 return $xt
5343 proc xcoord {i level ln} {
5344 global canvx0 xspc1 xspc2
5346 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5347 if {$i > 0 && $i == $level} {
5348 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5349 } elseif {$i > $level} {
5350 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5352 return $x
5355 proc show_status {msg} {
5356 global canv fgcolor
5358 clear_display
5359 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5360 -tags text -fill $fgcolor
5363 # Don't change the text pane cursor if it is currently the hand cursor,
5364 # showing that we are over a sha1 ID link.
5365 proc settextcursor {c} {
5366 global ctext curtextcursor
5368 if {[$ctext cget -cursor] == $curtextcursor} {
5369 $ctext config -cursor $c
5371 set curtextcursor $c
5374 proc nowbusy {what {name {}}} {
5375 global isbusy busyname statusw
5377 if {[array names isbusy] eq {}} {
5378 . config -cursor watch
5379 settextcursor watch
5381 set isbusy($what) 1
5382 set busyname($what) $name
5383 if {$name ne {}} {
5384 $statusw conf -text $name
5388 proc notbusy {what} {
5389 global isbusy maincursor textcursor busyname statusw
5391 catch {
5392 unset isbusy($what)
5393 if {$busyname($what) ne {} &&
5394 [$statusw cget -text] eq $busyname($what)} {
5395 $statusw conf -text {}
5398 if {[array names isbusy] eq {}} {
5399 . config -cursor $maincursor
5400 settextcursor $textcursor
5404 proc findmatches {f} {
5405 global findtype findstring
5406 if {$findtype == [mc "Regexp"]} {
5407 set matches [regexp -indices -all -inline $findstring $f]
5408 } else {
5409 set fs $findstring
5410 if {$findtype == [mc "IgnCase"]} {
5411 set f [string tolower $f]
5412 set fs [string tolower $fs]
5414 set matches {}
5415 set i 0
5416 set l [string length $fs]
5417 while {[set j [string first $fs $f $i]] >= 0} {
5418 lappend matches [list $j [expr {$j+$l-1}]]
5419 set i [expr {$j + $l}]
5422 return $matches
5425 proc dofind {{dirn 1} {wrap 1}} {
5426 global findstring findstartline findcurline selectedline numcommits
5427 global gdttype filehighlight fh_serial find_dirn findallowwrap
5429 if {[info exists find_dirn]} {
5430 if {$find_dirn == $dirn} return
5431 stopfinding
5433 focus .
5434 if {$findstring eq {} || $numcommits == 0} return
5435 if {$selectedline eq {}} {
5436 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5437 } else {
5438 set findstartline $selectedline
5440 set findcurline $findstartline
5441 nowbusy finding [mc "Searching"]
5442 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5443 after cancel do_file_hl $fh_serial
5444 do_file_hl $fh_serial
5446 set find_dirn $dirn
5447 set findallowwrap $wrap
5448 run findmore
5451 proc stopfinding {} {
5452 global find_dirn findcurline fprogcoord
5454 if {[info exists find_dirn]} {
5455 unset find_dirn
5456 unset findcurline
5457 notbusy finding
5458 set fprogcoord 0
5459 adjustprogress
5463 proc findmore {} {
5464 global commitdata commitinfo numcommits findpattern findloc
5465 global findstartline findcurline findallowwrap
5466 global find_dirn gdttype fhighlights fprogcoord
5467 global curview varcorder vrownum varccommits vrowmod
5469 if {![info exists find_dirn]} {
5470 return 0
5472 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5473 set l $findcurline
5474 set moretodo 0
5475 if {$find_dirn > 0} {
5476 incr l
5477 if {$l >= $numcommits} {
5478 set l 0
5480 if {$l <= $findstartline} {
5481 set lim [expr {$findstartline + 1}]
5482 } else {
5483 set lim $numcommits
5484 set moretodo $findallowwrap
5486 } else {
5487 if {$l == 0} {
5488 set l $numcommits
5490 incr l -1
5491 if {$l >= $findstartline} {
5492 set lim [expr {$findstartline - 1}]
5493 } else {
5494 set lim -1
5495 set moretodo $findallowwrap
5498 set n [expr {($lim - $l) * $find_dirn}]
5499 if {$n > 500} {
5500 set n 500
5501 set moretodo 1
5503 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5504 update_arcrows $curview
5506 set found 0
5507 set domore 1
5508 set ai [bsearch $vrownum($curview) $l]
5509 set a [lindex $varcorder($curview) $ai]
5510 set arow [lindex $vrownum($curview) $ai]
5511 set ids [lindex $varccommits($curview,$a)]
5512 set arowend [expr {$arow + [llength $ids]}]
5513 if {$gdttype eq [mc "containing:"]} {
5514 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5515 if {$l < $arow || $l >= $arowend} {
5516 incr ai $find_dirn
5517 set a [lindex $varcorder($curview) $ai]
5518 set arow [lindex $vrownum($curview) $ai]
5519 set ids [lindex $varccommits($curview,$a)]
5520 set arowend [expr {$arow + [llength $ids]}]
5522 set id [lindex $ids [expr {$l - $arow}]]
5523 # shouldn't happen unless git log doesn't give all the commits...
5524 if {![info exists commitdata($id)] ||
5525 ![doesmatch $commitdata($id)]} {
5526 continue
5528 if {![info exists commitinfo($id)]} {
5529 getcommit $id
5531 set info $commitinfo($id)
5532 foreach f $info ty $fldtypes {
5533 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5534 [doesmatch $f]} {
5535 set found 1
5536 break
5539 if {$found} break
5541 } else {
5542 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5543 if {$l < $arow || $l >= $arowend} {
5544 incr ai $find_dirn
5545 set a [lindex $varcorder($curview) $ai]
5546 set arow [lindex $vrownum($curview) $ai]
5547 set ids [lindex $varccommits($curview,$a)]
5548 set arowend [expr {$arow + [llength $ids]}]
5550 set id [lindex $ids [expr {$l - $arow}]]
5551 if {![info exists fhighlights($id)]} {
5552 # this sets fhighlights($id) to -1
5553 askfilehighlight $l $id
5555 if {$fhighlights($id) > 0} {
5556 set found $domore
5557 break
5559 if {$fhighlights($id) < 0} {
5560 if {$domore} {
5561 set domore 0
5562 set findcurline [expr {$l - $find_dirn}]
5567 if {$found || ($domore && !$moretodo)} {
5568 unset findcurline
5569 unset find_dirn
5570 notbusy finding
5571 set fprogcoord 0
5572 adjustprogress
5573 if {$found} {
5574 findselectline $l
5575 } else {
5576 bell
5578 return 0
5580 if {!$domore} {
5581 flushhighlights
5582 } else {
5583 set findcurline [expr {$l - $find_dirn}]
5585 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5586 if {$n < 0} {
5587 incr n $numcommits
5589 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5590 adjustprogress
5591 return $domore
5594 proc findselectline {l} {
5595 global findloc commentend ctext findcurline markingmatches gdttype
5597 set markingmatches 1
5598 set findcurline $l
5599 selectline $l 1
5600 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5601 # highlight the matches in the comments
5602 set f [$ctext get 1.0 $commentend]
5603 set matches [findmatches $f]
5604 foreach match $matches {
5605 set start [lindex $match 0]
5606 set end [expr {[lindex $match 1] + 1}]
5607 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5610 drawvisible
5613 # mark the bits of a headline or author that match a find string
5614 proc markmatches {canv l str tag matches font row} {
5615 global selectedline
5617 set bbox [$canv bbox $tag]
5618 set x0 [lindex $bbox 0]
5619 set y0 [lindex $bbox 1]
5620 set y1 [lindex $bbox 3]
5621 foreach match $matches {
5622 set start [lindex $match 0]
5623 set end [lindex $match 1]
5624 if {$start > $end} continue
5625 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5626 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5627 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5628 [expr {$x0+$xlen+2}] $y1 \
5629 -outline {} -tags [list match$l matches] -fill yellow]
5630 $canv lower $t
5631 if {$row == $selectedline} {
5632 $canv raise $t secsel
5637 proc unmarkmatches {} {
5638 global markingmatches
5640 allcanvs delete matches
5641 set markingmatches 0
5642 stopfinding
5645 proc selcanvline {w x y} {
5646 global canv canvy0 ctext linespc
5647 global rowtextx
5648 set ymax [lindex [$canv cget -scrollregion] 3]
5649 if {$ymax == {}} return
5650 set yfrac [lindex [$canv yview] 0]
5651 set y [expr {$y + $yfrac * $ymax}]
5652 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5653 if {$l < 0} {
5654 set l 0
5656 if {$w eq $canv} {
5657 set xmax [lindex [$canv cget -scrollregion] 2]
5658 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5659 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5661 unmarkmatches
5662 selectline $l 1
5665 proc commit_descriptor {p} {
5666 global commitinfo
5667 if {![info exists commitinfo($p)]} {
5668 getcommit $p
5670 set l "..."
5671 if {[llength $commitinfo($p)] > 1} {
5672 set l [lindex $commitinfo($p) 0]
5674 return "$p ($l)\n"
5677 # append some text to the ctext widget, and make any SHA1 ID
5678 # that we know about be a clickable link.
5679 proc appendwithlinks {text tags} {
5680 global ctext linknum curview pendinglinks
5682 set start [$ctext index "end - 1c"]
5683 $ctext insert end $text $tags
5684 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5685 foreach l $links {
5686 set s [lindex $l 0]
5687 set e [lindex $l 1]
5688 set linkid [string range $text $s $e]
5689 incr e
5690 $ctext tag delete link$linknum
5691 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5692 setlink $linkid link$linknum
5693 incr linknum
5697 proc setlink {id lk} {
5698 global curview ctext pendinglinks commitinterest
5700 if {[commitinview $id $curview]} {
5701 $ctext tag conf $lk -foreground blue -underline 1
5702 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5703 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5704 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5705 } else {
5706 lappend pendinglinks($id) $lk
5707 lappend commitinterest($id) {makelink %I}
5711 proc makelink {id} {
5712 global pendinglinks
5714 if {![info exists pendinglinks($id)]} return
5715 foreach lk $pendinglinks($id) {
5716 setlink $id $lk
5718 unset pendinglinks($id)
5721 proc linkcursor {w inc} {
5722 global linkentercount curtextcursor
5724 if {[incr linkentercount $inc] > 0} {
5725 $w configure -cursor hand2
5726 } else {
5727 $w configure -cursor $curtextcursor
5728 if {$linkentercount < 0} {
5729 set linkentercount 0
5734 proc viewnextline {dir} {
5735 global canv linespc
5737 $canv delete hover
5738 set ymax [lindex [$canv cget -scrollregion] 3]
5739 set wnow [$canv yview]
5740 set wtop [expr {[lindex $wnow 0] * $ymax}]
5741 set newtop [expr {$wtop + $dir * $linespc}]
5742 if {$newtop < 0} {
5743 set newtop 0
5744 } elseif {$newtop > $ymax} {
5745 set newtop $ymax
5747 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5750 # add a list of tag or branch names at position pos
5751 # returns the number of names inserted
5752 proc appendrefs {pos ids var} {
5753 global ctext linknum curview $var maxrefs
5755 if {[catch {$ctext index $pos}]} {
5756 return 0
5758 $ctext conf -state normal
5759 $ctext delete $pos "$pos lineend"
5760 set tags {}
5761 foreach id $ids {
5762 foreach tag [set $var\($id\)] {
5763 lappend tags [list $tag $id]
5766 if {[llength $tags] > $maxrefs} {
5767 $ctext insert $pos "many ([llength $tags])"
5768 } else {
5769 set tags [lsort -index 0 -decreasing $tags]
5770 set sep {}
5771 foreach ti $tags {
5772 set id [lindex $ti 1]
5773 set lk link$linknum
5774 incr linknum
5775 $ctext tag delete $lk
5776 $ctext insert $pos $sep
5777 $ctext insert $pos [lindex $ti 0] $lk
5778 setlink $id $lk
5779 set sep ", "
5782 $ctext conf -state disabled
5783 return [llength $tags]
5786 # called when we have finished computing the nearby tags
5787 proc dispneartags {delay} {
5788 global selectedline currentid showneartags tagphase
5790 if {$selectedline eq {} || !$showneartags} return
5791 after cancel dispnexttag
5792 if {$delay} {
5793 after 200 dispnexttag
5794 set tagphase -1
5795 } else {
5796 after idle dispnexttag
5797 set tagphase 0
5801 proc dispnexttag {} {
5802 global selectedline currentid showneartags tagphase ctext
5804 if {$selectedline eq {} || !$showneartags} return
5805 switch -- $tagphase {
5807 set dtags [desctags $currentid]
5808 if {$dtags ne {}} {
5809 appendrefs precedes $dtags idtags
5813 set atags [anctags $currentid]
5814 if {$atags ne {}} {
5815 appendrefs follows $atags idtags
5819 set dheads [descheads $currentid]
5820 if {$dheads ne {}} {
5821 if {[appendrefs branch $dheads idheads] > 1
5822 && [$ctext get "branch -3c"] eq "h"} {
5823 # turn "Branch" into "Branches"
5824 $ctext conf -state normal
5825 $ctext insert "branch -2c" "es"
5826 $ctext conf -state disabled
5831 if {[incr tagphase] <= 2} {
5832 after idle dispnexttag
5836 proc make_secsel {l} {
5837 global linehtag linentag linedtag canv canv2 canv3
5839 if {![info exists linehtag($l)]} return
5840 $canv delete secsel
5841 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5842 -tags secsel -fill [$canv cget -selectbackground]]
5843 $canv lower $t
5844 $canv2 delete secsel
5845 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5846 -tags secsel -fill [$canv2 cget -selectbackground]]
5847 $canv2 lower $t
5848 $canv3 delete secsel
5849 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5850 -tags secsel -fill [$canv3 cget -selectbackground]]
5851 $canv3 lower $t
5854 proc selectline {l isnew} {
5855 global canv ctext commitinfo selectedline
5856 global canvy0 linespc parents children curview
5857 global currentid sha1entry
5858 global commentend idtags linknum
5859 global mergemax numcommits pending_select
5860 global cmitmode showneartags allcommits
5861 global targetrow targetid lastscrollrows
5862 global autoselect
5864 catch {unset pending_select}
5865 $canv delete hover
5866 normalline
5867 unsel_reflist
5868 stopfinding
5869 if {$l < 0 || $l >= $numcommits} return
5870 set id [commitonrow $l]
5871 set targetid $id
5872 set targetrow $l
5873 set selectedline $l
5874 set currentid $id
5875 if {$lastscrollrows < $numcommits} {
5876 setcanvscroll
5879 set y [expr {$canvy0 + $l * $linespc}]
5880 set ymax [lindex [$canv cget -scrollregion] 3]
5881 set ytop [expr {$y - $linespc - 1}]
5882 set ybot [expr {$y + $linespc + 1}]
5883 set wnow [$canv yview]
5884 set wtop [expr {[lindex $wnow 0] * $ymax}]
5885 set wbot [expr {[lindex $wnow 1] * $ymax}]
5886 set wh [expr {$wbot - $wtop}]
5887 set newtop $wtop
5888 if {$ytop < $wtop} {
5889 if {$ybot < $wtop} {
5890 set newtop [expr {$y - $wh / 2.0}]
5891 } else {
5892 set newtop $ytop
5893 if {$newtop > $wtop - $linespc} {
5894 set newtop [expr {$wtop - $linespc}]
5897 } elseif {$ybot > $wbot} {
5898 if {$ytop > $wbot} {
5899 set newtop [expr {$y - $wh / 2.0}]
5900 } else {
5901 set newtop [expr {$ybot - $wh}]
5902 if {$newtop < $wtop + $linespc} {
5903 set newtop [expr {$wtop + $linespc}]
5907 if {$newtop != $wtop} {
5908 if {$newtop < 0} {
5909 set newtop 0
5911 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5912 drawvisible
5915 make_secsel $l
5917 if {$isnew} {
5918 addtohistory [list selbyid $id]
5921 $sha1entry delete 0 end
5922 $sha1entry insert 0 $id
5923 if {$autoselect} {
5924 $sha1entry selection from 0
5925 $sha1entry selection to end
5927 rhighlight_sel $id
5929 $ctext conf -state normal
5930 clear_ctext
5931 set linknum 0
5932 if {![info exists commitinfo($id)]} {
5933 getcommit $id
5935 set info $commitinfo($id)
5936 set date [formatdate [lindex $info 2]]
5937 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
5938 set date [formatdate [lindex $info 4]]
5939 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
5940 if {[info exists idtags($id)]} {
5941 $ctext insert end [mc "Tags:"]
5942 foreach tag $idtags($id) {
5943 $ctext insert end " $tag"
5945 $ctext insert end "\n"
5948 set headers {}
5949 set olds $parents($curview,$id)
5950 if {[llength $olds] > 1} {
5951 set np 0
5952 foreach p $olds {
5953 if {$np >= $mergemax} {
5954 set tag mmax
5955 } else {
5956 set tag m$np
5958 $ctext insert end "[mc "Parent"]: " $tag
5959 appendwithlinks [commit_descriptor $p] {}
5960 incr np
5962 } else {
5963 foreach p $olds {
5964 append headers "[mc "Parent"]: [commit_descriptor $p]"
5968 foreach c $children($curview,$id) {
5969 append headers "[mc "Child"]: [commit_descriptor $c]"
5972 # make anything that looks like a SHA1 ID be a clickable link
5973 appendwithlinks $headers {}
5974 if {$showneartags} {
5975 if {![info exists allcommits]} {
5976 getallcommits
5978 $ctext insert end "[mc "Branch"]: "
5979 $ctext mark set branch "end -1c"
5980 $ctext mark gravity branch left
5981 $ctext insert end "\n[mc "Follows"]: "
5982 $ctext mark set follows "end -1c"
5983 $ctext mark gravity follows left
5984 $ctext insert end "\n[mc "Precedes"]: "
5985 $ctext mark set precedes "end -1c"
5986 $ctext mark gravity precedes left
5987 $ctext insert end "\n"
5988 dispneartags 1
5990 $ctext insert end "\n"
5991 set comment [lindex $info 5]
5992 if {[string first "\r" $comment] >= 0} {
5993 set comment [string map {"\r" "\n "} $comment]
5995 appendwithlinks $comment {comment}
5997 $ctext tag remove found 1.0 end
5998 $ctext conf -state disabled
5999 set commentend [$ctext index "end - 1c"]
6001 init_flist [mc "Comments"]
6002 if {$cmitmode eq "tree"} {
6003 gettree $id
6004 } elseif {[llength $olds] <= 1} {
6005 startdiff $id
6006 } else {
6007 mergediff $id
6011 proc selfirstline {} {
6012 unmarkmatches
6013 selectline 0 1
6016 proc sellastline {} {
6017 global numcommits
6018 unmarkmatches
6019 set l [expr {$numcommits - 1}]
6020 selectline $l 1
6023 proc selnextline {dir} {
6024 global selectedline
6025 focus .
6026 if {$selectedline eq {}} return
6027 set l [expr {$selectedline + $dir}]
6028 unmarkmatches
6029 selectline $l 1
6032 proc selnextpage {dir} {
6033 global canv linespc selectedline numcommits
6035 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6036 if {$lpp < 1} {
6037 set lpp 1
6039 allcanvs yview scroll [expr {$dir * $lpp}] units
6040 drawvisible
6041 if {$selectedline eq {}} return
6042 set l [expr {$selectedline + $dir * $lpp}]
6043 if {$l < 0} {
6044 set l 0
6045 } elseif {$l >= $numcommits} {
6046 set l [expr $numcommits - 1]
6048 unmarkmatches
6049 selectline $l 1
6052 proc unselectline {} {
6053 global selectedline currentid
6055 set selectedline {}
6056 catch {unset currentid}
6057 allcanvs delete secsel
6058 rhighlight_none
6061 proc reselectline {} {
6062 global selectedline
6064 if {$selectedline ne {}} {
6065 selectline $selectedline 0
6069 proc addtohistory {cmd} {
6070 global history historyindex curview
6072 set elt [list $curview $cmd]
6073 if {$historyindex > 0
6074 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6075 return
6078 if {$historyindex < [llength $history]} {
6079 set history [lreplace $history $historyindex end $elt]
6080 } else {
6081 lappend history $elt
6083 incr historyindex
6084 if {$historyindex > 1} {
6085 .tf.bar.leftbut conf -state normal
6086 } else {
6087 .tf.bar.leftbut conf -state disabled
6089 .tf.bar.rightbut conf -state disabled
6092 proc godo {elt} {
6093 global curview
6095 set view [lindex $elt 0]
6096 set cmd [lindex $elt 1]
6097 if {$curview != $view} {
6098 showview $view
6100 eval $cmd
6103 proc goback {} {
6104 global history historyindex
6105 focus .
6107 if {$historyindex > 1} {
6108 incr historyindex -1
6109 godo [lindex $history [expr {$historyindex - 1}]]
6110 .tf.bar.rightbut conf -state normal
6112 if {$historyindex <= 1} {
6113 .tf.bar.leftbut conf -state disabled
6117 proc goforw {} {
6118 global history historyindex
6119 focus .
6121 if {$historyindex < [llength $history]} {
6122 set cmd [lindex $history $historyindex]
6123 incr historyindex
6124 godo $cmd
6125 .tf.bar.leftbut conf -state normal
6127 if {$historyindex >= [llength $history]} {
6128 .tf.bar.rightbut conf -state disabled
6132 proc gettree {id} {
6133 global treefilelist treeidlist diffids diffmergeid treepending
6134 global nullid nullid2
6136 set diffids $id
6137 catch {unset diffmergeid}
6138 if {![info exists treefilelist($id)]} {
6139 if {![info exists treepending]} {
6140 if {$id eq $nullid} {
6141 set cmd [list | git ls-files]
6142 } elseif {$id eq $nullid2} {
6143 set cmd [list | git ls-files --stage -t]
6144 } else {
6145 set cmd [list | git ls-tree -r $id]
6147 if {[catch {set gtf [open $cmd r]}]} {
6148 return
6150 set treepending $id
6151 set treefilelist($id) {}
6152 set treeidlist($id) {}
6153 fconfigure $gtf -blocking 0
6154 filerun $gtf [list gettreeline $gtf $id]
6156 } else {
6157 setfilelist $id
6161 proc gettreeline {gtf id} {
6162 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6164 set nl 0
6165 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6166 if {$diffids eq $nullid} {
6167 set fname $line
6168 } else {
6169 set i [string first "\t" $line]
6170 if {$i < 0} continue
6171 set fname [string range $line [expr {$i+1}] end]
6172 set line [string range $line 0 [expr {$i-1}]]
6173 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6174 set sha1 [lindex $line 2]
6175 if {[string index $fname 0] eq "\""} {
6176 set fname [lindex $fname 0]
6178 lappend treeidlist($id) $sha1
6180 lappend treefilelist($id) $fname
6182 if {![eof $gtf]} {
6183 return [expr {$nl >= 1000? 2: 1}]
6185 close $gtf
6186 unset treepending
6187 if {$cmitmode ne "tree"} {
6188 if {![info exists diffmergeid]} {
6189 gettreediffs $diffids
6191 } elseif {$id ne $diffids} {
6192 gettree $diffids
6193 } else {
6194 setfilelist $id
6196 return 0
6199 proc showfile {f} {
6200 global treefilelist treeidlist diffids nullid nullid2
6201 global ctext commentend
6203 set i [lsearch -exact $treefilelist($diffids) $f]
6204 if {$i < 0} {
6205 puts "oops, $f not in list for id $diffids"
6206 return
6208 if {$diffids eq $nullid} {
6209 if {[catch {set bf [open $f r]} err]} {
6210 puts "oops, can't read $f: $err"
6211 return
6213 } else {
6214 set blob [lindex $treeidlist($diffids) $i]
6215 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6216 puts "oops, error reading blob $blob: $err"
6217 return
6220 fconfigure $bf -blocking 0
6221 filerun $bf [list getblobline $bf $diffids]
6222 $ctext config -state normal
6223 clear_ctext $commentend
6224 $ctext insert end "\n"
6225 $ctext insert end "$f\n" filesep
6226 $ctext config -state disabled
6227 $ctext yview $commentend
6228 settabs 0
6231 proc getblobline {bf id} {
6232 global diffids cmitmode ctext
6234 if {$id ne $diffids || $cmitmode ne "tree"} {
6235 catch {close $bf}
6236 return 0
6238 $ctext config -state normal
6239 set nl 0
6240 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6241 $ctext insert end "$line\n"
6243 if {[eof $bf]} {
6244 # delete last newline
6245 $ctext delete "end - 2c" "end - 1c"
6246 close $bf
6247 return 0
6249 $ctext config -state disabled
6250 return [expr {$nl >= 1000? 2: 1}]
6253 proc mergediff {id} {
6254 global diffmergeid mdifffd
6255 global diffids
6256 global parents
6257 global diffcontext
6258 global limitdiffs vfilelimit curview
6260 set diffmergeid $id
6261 set diffids $id
6262 # this doesn't seem to actually affect anything...
6263 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6264 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6265 set cmd [concat $cmd -- $vfilelimit($curview)]
6267 if {[catch {set mdf [open $cmd r]} err]} {
6268 error_popup "[mc "Error getting merge diffs:"] $err"
6269 return
6271 fconfigure $mdf -blocking 0
6272 set mdifffd($id) $mdf
6273 set np [llength $parents($curview,$id)]
6274 settabs $np
6275 filerun $mdf [list getmergediffline $mdf $id $np]
6278 proc getmergediffline {mdf id np} {
6279 global diffmergeid ctext cflist mergemax
6280 global difffilestart mdifffd
6282 $ctext conf -state normal
6283 set nr 0
6284 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6285 if {![info exists diffmergeid] || $id != $diffmergeid
6286 || $mdf != $mdifffd($id)} {
6287 close $mdf
6288 return 0
6290 if {[regexp {^diff --cc (.*)} $line match fname]} {
6291 # start of a new file
6292 $ctext insert end "\n"
6293 set here [$ctext index "end - 1c"]
6294 lappend difffilestart $here
6295 add_flist [list $fname]
6296 set l [expr {(78 - [string length $fname]) / 2}]
6297 set pad [string range "----------------------------------------" 1 $l]
6298 $ctext insert end "$pad $fname $pad\n" filesep
6299 } elseif {[regexp {^@@} $line]} {
6300 $ctext insert end "$line\n" hunksep
6301 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6302 # do nothing
6303 } else {
6304 # parse the prefix - one ' ', '-' or '+' for each parent
6305 set spaces {}
6306 set minuses {}
6307 set pluses {}
6308 set isbad 0
6309 for {set j 0} {$j < $np} {incr j} {
6310 set c [string range $line $j $j]
6311 if {$c == " "} {
6312 lappend spaces $j
6313 } elseif {$c == "-"} {
6314 lappend minuses $j
6315 } elseif {$c == "+"} {
6316 lappend pluses $j
6317 } else {
6318 set isbad 1
6319 break
6322 set tags {}
6323 set num {}
6324 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6325 # line doesn't appear in result, parents in $minuses have the line
6326 set num [lindex $minuses 0]
6327 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6328 # line appears in result, parents in $pluses don't have the line
6329 lappend tags mresult
6330 set num [lindex $spaces 0]
6332 if {$num ne {}} {
6333 if {$num >= $mergemax} {
6334 set num "max"
6336 lappend tags m$num
6338 $ctext insert end "$line\n" $tags
6341 $ctext conf -state disabled
6342 if {[eof $mdf]} {
6343 close $mdf
6344 return 0
6346 return [expr {$nr >= 1000? 2: 1}]
6349 proc startdiff {ids} {
6350 global treediffs diffids treepending diffmergeid nullid nullid2
6352 settabs 1
6353 set diffids $ids
6354 catch {unset diffmergeid}
6355 if {![info exists treediffs($ids)] ||
6356 [lsearch -exact $ids $nullid] >= 0 ||
6357 [lsearch -exact $ids $nullid2] >= 0} {
6358 if {![info exists treepending]} {
6359 gettreediffs $ids
6361 } else {
6362 addtocflist $ids
6366 proc path_filter {filter name} {
6367 foreach p $filter {
6368 set l [string length $p]
6369 if {[string index $p end] eq "/"} {
6370 if {[string compare -length $l $p $name] == 0} {
6371 return 1
6373 } else {
6374 if {[string compare -length $l $p $name] == 0 &&
6375 ([string length $name] == $l ||
6376 [string index $name $l] eq "/")} {
6377 return 1
6381 return 0
6384 proc addtocflist {ids} {
6385 global treediffs
6387 add_flist $treediffs($ids)
6388 getblobdiffs $ids
6391 proc diffcmd {ids flags} {
6392 global nullid nullid2
6394 set i [lsearch -exact $ids $nullid]
6395 set j [lsearch -exact $ids $nullid2]
6396 if {$i >= 0} {
6397 if {[llength $ids] > 1 && $j < 0} {
6398 # comparing working directory with some specific revision
6399 set cmd [concat | git diff-index $flags]
6400 if {$i == 0} {
6401 lappend cmd -R [lindex $ids 1]
6402 } else {
6403 lappend cmd [lindex $ids 0]
6405 } else {
6406 # comparing working directory with index
6407 set cmd [concat | git diff-files $flags]
6408 if {$j == 1} {
6409 lappend cmd -R
6412 } elseif {$j >= 0} {
6413 set cmd [concat | git diff-index --cached $flags]
6414 if {[llength $ids] > 1} {
6415 # comparing index with specific revision
6416 if {$i == 0} {
6417 lappend cmd -R [lindex $ids 1]
6418 } else {
6419 lappend cmd [lindex $ids 0]
6421 } else {
6422 # comparing index with HEAD
6423 lappend cmd HEAD
6425 } else {
6426 set cmd [concat | git diff-tree -r $flags $ids]
6428 return $cmd
6431 proc gettreediffs {ids} {
6432 global treediff treepending
6434 set treepending $ids
6435 set treediff {}
6436 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6437 fconfigure $gdtf -blocking 0
6438 filerun $gdtf [list gettreediffline $gdtf $ids]
6441 proc gettreediffline {gdtf ids} {
6442 global treediff treediffs treepending diffids diffmergeid
6443 global cmitmode vfilelimit curview limitdiffs
6445 set nr 0
6446 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
6447 set i [string first "\t" $line]
6448 if {$i >= 0} {
6449 set file [string range $line [expr {$i+1}] end]
6450 if {[string index $file 0] eq "\""} {
6451 set file [lindex $file 0]
6453 lappend treediff $file
6456 if {![eof $gdtf]} {
6457 return [expr {$nr >= 1000? 2: 1}]
6459 close $gdtf
6460 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6461 set flist {}
6462 foreach f $treediff {
6463 if {[path_filter $vfilelimit($curview) $f]} {
6464 lappend flist $f
6467 set treediffs($ids) $flist
6468 } else {
6469 set treediffs($ids) $treediff
6471 unset treepending
6472 if {$cmitmode eq "tree"} {
6473 gettree $diffids
6474 } elseif {$ids != $diffids} {
6475 if {![info exists diffmergeid]} {
6476 gettreediffs $diffids
6478 } else {
6479 addtocflist $ids
6481 return 0
6484 # empty string or positive integer
6485 proc diffcontextvalidate {v} {
6486 return [regexp {^(|[1-9][0-9]*)$} $v]
6489 proc diffcontextchange {n1 n2 op} {
6490 global diffcontextstring diffcontext
6492 if {[string is integer -strict $diffcontextstring]} {
6493 if {$diffcontextstring > 0} {
6494 set diffcontext $diffcontextstring
6495 reselectline
6500 proc changeignorespace {} {
6501 reselectline
6504 proc getblobdiffs {ids} {
6505 global blobdifffd diffids env
6506 global diffinhdr treediffs
6507 global diffcontext
6508 global ignorespace
6509 global limitdiffs vfilelimit curview
6511 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6512 if {$ignorespace} {
6513 append cmd " -w"
6515 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6516 set cmd [concat $cmd -- $vfilelimit($curview)]
6518 if {[catch {set bdf [open $cmd r]} err]} {
6519 puts "error getting diffs: $err"
6520 return
6522 set diffinhdr 0
6523 fconfigure $bdf -blocking 0
6524 set blobdifffd($ids) $bdf
6525 filerun $bdf [list getblobdiffline $bdf $diffids]
6528 proc setinlist {var i val} {
6529 global $var
6531 while {[llength [set $var]] < $i} {
6532 lappend $var {}
6534 if {[llength [set $var]] == $i} {
6535 lappend $var $val
6536 } else {
6537 lset $var $i $val
6541 proc makediffhdr {fname ids} {
6542 global ctext curdiffstart treediffs
6544 set i [lsearch -exact $treediffs($ids) $fname]
6545 if {$i >= 0} {
6546 setinlist difffilestart $i $curdiffstart
6548 set l [expr {(78 - [string length $fname]) / 2}]
6549 set pad [string range "----------------------------------------" 1 $l]
6550 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6553 proc getblobdiffline {bdf ids} {
6554 global diffids blobdifffd ctext curdiffstart
6555 global diffnexthead diffnextnote difffilestart
6556 global diffinhdr treediffs
6558 set nr 0
6559 $ctext conf -state normal
6560 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6561 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6562 close $bdf
6563 return 0
6565 if {![string compare -length 11 "diff --git " $line]} {
6566 # trim off "diff --git "
6567 set line [string range $line 11 end]
6568 set diffinhdr 1
6569 # start of a new file
6570 $ctext insert end "\n"
6571 set curdiffstart [$ctext index "end - 1c"]
6572 $ctext insert end "\n" filesep
6573 # If the name hasn't changed the length will be odd,
6574 # the middle char will be a space, and the two bits either
6575 # side will be a/name and b/name, or "a/name" and "b/name".
6576 # If the name has changed we'll get "rename from" and
6577 # "rename to" or "copy from" and "copy to" lines following this,
6578 # and we'll use them to get the filenames.
6579 # This complexity is necessary because spaces in the filename(s)
6580 # don't get escaped.
6581 set l [string length $line]
6582 set i [expr {$l / 2}]
6583 if {!(($l & 1) && [string index $line $i] eq " " &&
6584 [string range $line 2 [expr {$i - 1}]] eq \
6585 [string range $line [expr {$i + 3}] end])} {
6586 continue
6588 # unescape if quoted and chop off the a/ from the front
6589 if {[string index $line 0] eq "\""} {
6590 set fname [string range [lindex $line 0] 2 end]
6591 } else {
6592 set fname [string range $line 2 [expr {$i - 1}]]
6594 makediffhdr $fname $ids
6596 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6597 $line match f1l f1c f2l f2c rest]} {
6598 $ctext insert end "$line\n" hunksep
6599 set diffinhdr 0
6601 } elseif {$diffinhdr} {
6602 if {![string compare -length 12 "rename from " $line]} {
6603 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6604 if {[string index $fname 0] eq "\""} {
6605 set fname [lindex $fname 0]
6607 set i [lsearch -exact $treediffs($ids) $fname]
6608 if {$i >= 0} {
6609 setinlist difffilestart $i $curdiffstart
6611 } elseif {![string compare -length 10 $line "rename to "] ||
6612 ![string compare -length 8 $line "copy to "]} {
6613 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6614 if {[string index $fname 0] eq "\""} {
6615 set fname [lindex $fname 0]
6617 makediffhdr $fname $ids
6618 } elseif {[string compare -length 3 $line "---"] == 0} {
6619 # do nothing
6620 continue
6621 } elseif {[string compare -length 3 $line "+++"] == 0} {
6622 set diffinhdr 0
6623 continue
6625 $ctext insert end "$line\n" filesep
6627 } else {
6628 set x [string range $line 0 0]
6629 if {$x == "-" || $x == "+"} {
6630 set tag [expr {$x == "+"}]
6631 $ctext insert end "$line\n" d$tag
6632 } elseif {$x == " "} {
6633 $ctext insert end "$line\n"
6634 } else {
6635 # "\ No newline at end of file",
6636 # or something else we don't recognize
6637 $ctext insert end "$line\n" hunksep
6641 $ctext conf -state disabled
6642 if {[eof $bdf]} {
6643 close $bdf
6644 return 0
6646 return [expr {$nr >= 1000? 2: 1}]
6649 proc changediffdisp {} {
6650 global ctext diffelide
6652 $ctext tag conf d0 -elide [lindex $diffelide 0]
6653 $ctext tag conf d1 -elide [lindex $diffelide 1]
6656 proc highlightfile {loc cline} {
6657 global ctext cflist cflist_top
6659 $ctext yview $loc
6660 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
6661 $cflist tag add highlight $cline.0 "$cline.0 lineend"
6662 $cflist see $cline.0
6663 set cflist_top $cline
6666 proc prevfile {} {
6667 global difffilestart ctext cmitmode
6669 if {$cmitmode eq "tree"} return
6670 set prev 0.0
6671 set prevline 1
6672 set here [$ctext index @0,0]
6673 foreach loc $difffilestart {
6674 if {[$ctext compare $loc >= $here]} {
6675 highlightfile $prev $prevline
6676 return
6678 set prev $loc
6679 incr prevline
6681 highlightfile $prev $prevline
6684 proc nextfile {} {
6685 global difffilestart ctext cmitmode
6687 if {$cmitmode eq "tree"} return
6688 set here [$ctext index @0,0]
6689 set line 1
6690 foreach loc $difffilestart {
6691 incr line
6692 if {[$ctext compare $loc > $here]} {
6693 highlightfile $loc $line
6694 return
6699 proc clear_ctext {{first 1.0}} {
6700 global ctext smarktop smarkbot
6701 global pendinglinks
6703 set l [lindex [split $first .] 0]
6704 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6705 set smarktop $l
6707 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6708 set smarkbot $l
6710 $ctext delete $first end
6711 if {$first eq "1.0"} {
6712 catch {unset pendinglinks}
6716 proc settabs {{firstab {}}} {
6717 global firsttabstop tabstop ctext have_tk85
6719 if {$firstab ne {} && $have_tk85} {
6720 set firsttabstop $firstab
6722 set w [font measure textfont "0"]
6723 if {$firsttabstop != 0} {
6724 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6725 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6726 } elseif {$have_tk85 || $tabstop != 8} {
6727 $ctext conf -tabs [expr {$tabstop * $w}]
6728 } else {
6729 $ctext conf -tabs {}
6733 proc incrsearch {name ix op} {
6734 global ctext searchstring searchdirn
6736 $ctext tag remove found 1.0 end
6737 if {[catch {$ctext index anchor}]} {
6738 # no anchor set, use start of selection, or of visible area
6739 set sel [$ctext tag ranges sel]
6740 if {$sel ne {}} {
6741 $ctext mark set anchor [lindex $sel 0]
6742 } elseif {$searchdirn eq "-forwards"} {
6743 $ctext mark set anchor @0,0
6744 } else {
6745 $ctext mark set anchor @0,[winfo height $ctext]
6748 if {$searchstring ne {}} {
6749 set here [$ctext search $searchdirn -- $searchstring anchor]
6750 if {$here ne {}} {
6751 $ctext see $here
6753 searchmarkvisible 1
6757 proc dosearch {} {
6758 global sstring ctext searchstring searchdirn
6760 focus $sstring
6761 $sstring icursor end
6762 set searchdirn -forwards
6763 if {$searchstring ne {}} {
6764 set sel [$ctext tag ranges sel]
6765 if {$sel ne {}} {
6766 set start "[lindex $sel 0] + 1c"
6767 } elseif {[catch {set start [$ctext index anchor]}]} {
6768 set start "@0,0"
6770 set match [$ctext search -count mlen -- $searchstring $start]
6771 $ctext tag remove sel 1.0 end
6772 if {$match eq {}} {
6773 bell
6774 return
6776 $ctext see $match
6777 set mend "$match + $mlen c"
6778 $ctext tag add sel $match $mend
6779 $ctext mark unset anchor
6783 proc dosearchback {} {
6784 global sstring ctext searchstring searchdirn
6786 focus $sstring
6787 $sstring icursor end
6788 set searchdirn -backwards
6789 if {$searchstring ne {}} {
6790 set sel [$ctext tag ranges sel]
6791 if {$sel ne {}} {
6792 set start [lindex $sel 0]
6793 } elseif {[catch {set start [$ctext index anchor]}]} {
6794 set start @0,[winfo height $ctext]
6796 set match [$ctext search -backwards -count ml -- $searchstring $start]
6797 $ctext tag remove sel 1.0 end
6798 if {$match eq {}} {
6799 bell
6800 return
6802 $ctext see $match
6803 set mend "$match + $ml c"
6804 $ctext tag add sel $match $mend
6805 $ctext mark unset anchor
6809 proc searchmark {first last} {
6810 global ctext searchstring
6812 set mend $first.0
6813 while {1} {
6814 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6815 if {$match eq {}} break
6816 set mend "$match + $mlen c"
6817 $ctext tag add found $match $mend
6821 proc searchmarkvisible {doall} {
6822 global ctext smarktop smarkbot
6824 set topline [lindex [split [$ctext index @0,0] .] 0]
6825 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6826 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6827 # no overlap with previous
6828 searchmark $topline $botline
6829 set smarktop $topline
6830 set smarkbot $botline
6831 } else {
6832 if {$topline < $smarktop} {
6833 searchmark $topline [expr {$smarktop-1}]
6834 set smarktop $topline
6836 if {$botline > $smarkbot} {
6837 searchmark [expr {$smarkbot+1}] $botline
6838 set smarkbot $botline
6843 proc scrolltext {f0 f1} {
6844 global searchstring
6846 .bleft.bottom.sb set $f0 $f1
6847 if {$searchstring ne {}} {
6848 searchmarkvisible 0
6852 proc setcoords {} {
6853 global linespc charspc canvx0 canvy0
6854 global xspc1 xspc2 lthickness
6856 set linespc [font metrics mainfont -linespace]
6857 set charspc [font measure mainfont "m"]
6858 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6859 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6860 set lthickness [expr {int($linespc / 9) + 1}]
6861 set xspc1(0) $linespc
6862 set xspc2 $linespc
6865 proc redisplay {} {
6866 global canv
6867 global selectedline
6869 set ymax [lindex [$canv cget -scrollregion] 3]
6870 if {$ymax eq {} || $ymax == 0} return
6871 set span [$canv yview]
6872 clear_display
6873 setcanvscroll
6874 allcanvs yview moveto [lindex $span 0]
6875 drawvisible
6876 if {$selectedline ne {}} {
6877 selectline $selectedline 0
6878 allcanvs yview moveto [lindex $span 0]
6882 proc parsefont {f n} {
6883 global fontattr
6885 set fontattr($f,family) [lindex $n 0]
6886 set s [lindex $n 1]
6887 if {$s eq {} || $s == 0} {
6888 set s 10
6889 } elseif {$s < 0} {
6890 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6892 set fontattr($f,size) $s
6893 set fontattr($f,weight) normal
6894 set fontattr($f,slant) roman
6895 foreach style [lrange $n 2 end] {
6896 switch -- $style {
6897 "normal" -
6898 "bold" {set fontattr($f,weight) $style}
6899 "roman" -
6900 "italic" {set fontattr($f,slant) $style}
6905 proc fontflags {f {isbold 0}} {
6906 global fontattr
6908 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6909 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6910 -slant $fontattr($f,slant)]
6913 proc fontname {f} {
6914 global fontattr
6916 set n [list $fontattr($f,family) $fontattr($f,size)]
6917 if {$fontattr($f,weight) eq "bold"} {
6918 lappend n "bold"
6920 if {$fontattr($f,slant) eq "italic"} {
6921 lappend n "italic"
6923 return $n
6926 proc incrfont {inc} {
6927 global mainfont textfont ctext canv cflist showrefstop
6928 global stopped entries fontattr
6930 unmarkmatches
6931 set s $fontattr(mainfont,size)
6932 incr s $inc
6933 if {$s < 1} {
6934 set s 1
6936 set fontattr(mainfont,size) $s
6937 font config mainfont -size $s
6938 font config mainfontbold -size $s
6939 set mainfont [fontname mainfont]
6940 set s $fontattr(textfont,size)
6941 incr s $inc
6942 if {$s < 1} {
6943 set s 1
6945 set fontattr(textfont,size) $s
6946 font config textfont -size $s
6947 font config textfontbold -size $s
6948 set textfont [fontname textfont]
6949 setcoords
6950 settabs
6951 redisplay
6954 proc clearsha1 {} {
6955 global sha1entry sha1string
6956 if {[string length $sha1string] == 40} {
6957 $sha1entry delete 0 end
6961 proc sha1change {n1 n2 op} {
6962 global sha1string currentid sha1but
6963 if {$sha1string == {}
6964 || ([info exists currentid] && $sha1string == $currentid)} {
6965 set state disabled
6966 } else {
6967 set state normal
6969 if {[$sha1but cget -state] == $state} return
6970 if {$state == "normal"} {
6971 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
6972 } else {
6973 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
6977 proc gotocommit {} {
6978 global sha1string tagids headids curview varcid
6980 if {$sha1string == {}
6981 || ([info exists currentid] && $sha1string == $currentid)} return
6982 if {[info exists tagids($sha1string)]} {
6983 set id $tagids($sha1string)
6984 } elseif {[info exists headids($sha1string)]} {
6985 set id $headids($sha1string)
6986 } else {
6987 set id [string tolower $sha1string]
6988 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
6989 set matches [array names varcid "$curview,$id*"]
6990 if {$matches ne {}} {
6991 if {[llength $matches] > 1} {
6992 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
6993 return
6995 set id [lindex [split [lindex $matches 0] ","] 1]
6999 if {[commitinview $id $curview]} {
7000 selectline [rowofcommit $id] 1
7001 return
7003 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7004 set msg [mc "SHA1 id %s is not known" $sha1string]
7005 } else {
7006 set msg [mc "Tag/Head %s is not known" $sha1string]
7008 error_popup $msg
7011 proc lineenter {x y id} {
7012 global hoverx hovery hoverid hovertimer
7013 global commitinfo canv
7015 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7016 set hoverx $x
7017 set hovery $y
7018 set hoverid $id
7019 if {[info exists hovertimer]} {
7020 after cancel $hovertimer
7022 set hovertimer [after 500 linehover]
7023 $canv delete hover
7026 proc linemotion {x y id} {
7027 global hoverx hovery hoverid hovertimer
7029 if {[info exists hoverid] && $id == $hoverid} {
7030 set hoverx $x
7031 set hovery $y
7032 if {[info exists hovertimer]} {
7033 after cancel $hovertimer
7035 set hovertimer [after 500 linehover]
7039 proc lineleave {id} {
7040 global hoverid hovertimer canv
7042 if {[info exists hoverid] && $id == $hoverid} {
7043 $canv delete hover
7044 if {[info exists hovertimer]} {
7045 after cancel $hovertimer
7046 unset hovertimer
7048 unset hoverid
7052 proc linehover {} {
7053 global hoverx hovery hoverid hovertimer
7054 global canv linespc lthickness
7055 global commitinfo
7057 set text [lindex $commitinfo($hoverid) 0]
7058 set ymax [lindex [$canv cget -scrollregion] 3]
7059 if {$ymax == {}} return
7060 set yfrac [lindex [$canv yview] 0]
7061 set x [expr {$hoverx + 2 * $linespc}]
7062 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7063 set x0 [expr {$x - 2 * $lthickness}]
7064 set y0 [expr {$y - 2 * $lthickness}]
7065 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7066 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7067 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7068 -fill \#ffff80 -outline black -width 1 -tags hover]
7069 $canv raise $t
7070 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7071 -font mainfont]
7072 $canv raise $t
7075 proc clickisonarrow {id y} {
7076 global lthickness
7078 set ranges [rowranges $id]
7079 set thresh [expr {2 * $lthickness + 6}]
7080 set n [expr {[llength $ranges] - 1}]
7081 for {set i 1} {$i < $n} {incr i} {
7082 set row [lindex $ranges $i]
7083 if {abs([yc $row] - $y) < $thresh} {
7084 return $i
7087 return {}
7090 proc arrowjump {id n y} {
7091 global canv
7093 # 1 <-> 2, 3 <-> 4, etc...
7094 set n [expr {(($n - 1) ^ 1) + 1}]
7095 set row [lindex [rowranges $id] $n]
7096 set yt [yc $row]
7097 set ymax [lindex [$canv cget -scrollregion] 3]
7098 if {$ymax eq {} || $ymax <= 0} return
7099 set view [$canv yview]
7100 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7101 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7102 if {$yfrac < 0} {
7103 set yfrac 0
7105 allcanvs yview moveto $yfrac
7108 proc lineclick {x y id isnew} {
7109 global ctext commitinfo children canv thickerline curview
7111 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7112 unmarkmatches
7113 unselectline
7114 normalline
7115 $canv delete hover
7116 # draw this line thicker than normal
7117 set thickerline $id
7118 drawlines $id
7119 if {$isnew} {
7120 set ymax [lindex [$canv cget -scrollregion] 3]
7121 if {$ymax eq {}} return
7122 set yfrac [lindex [$canv yview] 0]
7123 set y [expr {$y + $yfrac * $ymax}]
7125 set dirn [clickisonarrow $id $y]
7126 if {$dirn ne {}} {
7127 arrowjump $id $dirn $y
7128 return
7131 if {$isnew} {
7132 addtohistory [list lineclick $x $y $id 0]
7134 # fill the details pane with info about this line
7135 $ctext conf -state normal
7136 clear_ctext
7137 settabs 0
7138 $ctext insert end "[mc "Parent"]:\t"
7139 $ctext insert end $id link0
7140 setlink $id link0
7141 set info $commitinfo($id)
7142 $ctext insert end "\n\t[lindex $info 0]\n"
7143 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7144 set date [formatdate [lindex $info 2]]
7145 $ctext insert end "\t[mc "Date"]:\t$date\n"
7146 set kids $children($curview,$id)
7147 if {$kids ne {}} {
7148 $ctext insert end "\n[mc "Children"]:"
7149 set i 0
7150 foreach child $kids {
7151 incr i
7152 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7153 set info $commitinfo($child)
7154 $ctext insert end "\n\t"
7155 $ctext insert end $child link$i
7156 setlink $child link$i
7157 $ctext insert end "\n\t[lindex $info 0]"
7158 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7159 set date [formatdate [lindex $info 2]]
7160 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7163 $ctext conf -state disabled
7164 init_flist {}
7167 proc normalline {} {
7168 global thickerline
7169 if {[info exists thickerline]} {
7170 set id $thickerline
7171 unset thickerline
7172 drawlines $id
7176 proc selbyid {id} {
7177 global curview
7178 if {[commitinview $id $curview]} {
7179 selectline [rowofcommit $id] 1
7183 proc mstime {} {
7184 global startmstime
7185 if {![info exists startmstime]} {
7186 set startmstime [clock clicks -milliseconds]
7188 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7191 proc rowmenu {x y id} {
7192 global rowctxmenu selectedline rowmenuid curview
7193 global nullid nullid2 fakerowmenu mainhead
7195 stopfinding
7196 set rowmenuid $id
7197 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7198 set state disabled
7199 } else {
7200 set state normal
7202 if {$id ne $nullid && $id ne $nullid2} {
7203 set menu $rowctxmenu
7204 if {$mainhead ne {}} {
7205 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7206 } else {
7207 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7209 } else {
7210 set menu $fakerowmenu
7212 $menu entryconfigure [mc "Diff this -> selected"] -state $state
7213 $menu entryconfigure [mc "Diff selected -> this"] -state $state
7214 $menu entryconfigure [mc "Make patch"] -state $state
7215 tk_popup $menu $x $y
7218 proc diffvssel {dirn} {
7219 global rowmenuid selectedline
7221 if {$selectedline eq {}} return
7222 if {$dirn} {
7223 set oldid [commitonrow $selectedline]
7224 set newid $rowmenuid
7225 } else {
7226 set oldid $rowmenuid
7227 set newid [commitonrow $selectedline]
7229 addtohistory [list doseldiff $oldid $newid]
7230 doseldiff $oldid $newid
7233 proc doseldiff {oldid newid} {
7234 global ctext
7235 global commitinfo
7237 $ctext conf -state normal
7238 clear_ctext
7239 init_flist [mc "Top"]
7240 $ctext insert end "[mc "From"] "
7241 $ctext insert end $oldid link0
7242 setlink $oldid link0
7243 $ctext insert end "\n "
7244 $ctext insert end [lindex $commitinfo($oldid) 0]
7245 $ctext insert end "\n\n[mc "To"] "
7246 $ctext insert end $newid link1
7247 setlink $newid link1
7248 $ctext insert end "\n "
7249 $ctext insert end [lindex $commitinfo($newid) 0]
7250 $ctext insert end "\n"
7251 $ctext conf -state disabled
7252 $ctext tag remove found 1.0 end
7253 startdiff [list $oldid $newid]
7256 proc mkpatch {} {
7257 global rowmenuid currentid commitinfo patchtop patchnum
7259 if {![info exists currentid]} return
7260 set oldid $currentid
7261 set oldhead [lindex $commitinfo($oldid) 0]
7262 set newid $rowmenuid
7263 set newhead [lindex $commitinfo($newid) 0]
7264 set top .patch
7265 set patchtop $top
7266 catch {destroy $top}
7267 toplevel $top
7268 label $top.title -text [mc "Generate patch"]
7269 grid $top.title - -pady 10
7270 label $top.from -text [mc "From:"]
7271 entry $top.fromsha1 -width 40 -relief flat
7272 $top.fromsha1 insert 0 $oldid
7273 $top.fromsha1 conf -state readonly
7274 grid $top.from $top.fromsha1 -sticky w
7275 entry $top.fromhead -width 60 -relief flat
7276 $top.fromhead insert 0 $oldhead
7277 $top.fromhead conf -state readonly
7278 grid x $top.fromhead -sticky w
7279 label $top.to -text [mc "To:"]
7280 entry $top.tosha1 -width 40 -relief flat
7281 $top.tosha1 insert 0 $newid
7282 $top.tosha1 conf -state readonly
7283 grid $top.to $top.tosha1 -sticky w
7284 entry $top.tohead -width 60 -relief flat
7285 $top.tohead insert 0 $newhead
7286 $top.tohead conf -state readonly
7287 grid x $top.tohead -sticky w
7288 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7289 grid $top.rev x -pady 10
7290 label $top.flab -text [mc "Output file:"]
7291 entry $top.fname -width 60
7292 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7293 incr patchnum
7294 grid $top.flab $top.fname -sticky w
7295 frame $top.buts
7296 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7297 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7298 grid $top.buts.gen $top.buts.can
7299 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7300 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7301 grid $top.buts - -pady 10 -sticky ew
7302 focus $top.fname
7305 proc mkpatchrev {} {
7306 global patchtop
7308 set oldid [$patchtop.fromsha1 get]
7309 set oldhead [$patchtop.fromhead get]
7310 set newid [$patchtop.tosha1 get]
7311 set newhead [$patchtop.tohead get]
7312 foreach e [list fromsha1 fromhead tosha1 tohead] \
7313 v [list $newid $newhead $oldid $oldhead] {
7314 $patchtop.$e conf -state normal
7315 $patchtop.$e delete 0 end
7316 $patchtop.$e insert 0 $v
7317 $patchtop.$e conf -state readonly
7321 proc mkpatchgo {} {
7322 global patchtop nullid nullid2
7324 set oldid [$patchtop.fromsha1 get]
7325 set newid [$patchtop.tosha1 get]
7326 set fname [$patchtop.fname get]
7327 set cmd [diffcmd [list $oldid $newid] -p]
7328 # trim off the initial "|"
7329 set cmd [lrange $cmd 1 end]
7330 lappend cmd >$fname &
7331 if {[catch {eval exec $cmd} err]} {
7332 error_popup "[mc "Error creating patch:"] $err"
7334 catch {destroy $patchtop}
7335 unset patchtop
7338 proc mkpatchcan {} {
7339 global patchtop
7341 catch {destroy $patchtop}
7342 unset patchtop
7345 proc mktag {} {
7346 global rowmenuid mktagtop commitinfo
7348 set top .maketag
7349 set mktagtop $top
7350 catch {destroy $top}
7351 toplevel $top
7352 label $top.title -text [mc "Create tag"]
7353 grid $top.title - -pady 10
7354 label $top.id -text [mc "ID:"]
7355 entry $top.sha1 -width 40 -relief flat
7356 $top.sha1 insert 0 $rowmenuid
7357 $top.sha1 conf -state readonly
7358 grid $top.id $top.sha1 -sticky w
7359 entry $top.head -width 60 -relief flat
7360 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7361 $top.head conf -state readonly
7362 grid x $top.head -sticky w
7363 label $top.tlab -text [mc "Tag name:"]
7364 entry $top.tag -width 60
7365 grid $top.tlab $top.tag -sticky w
7366 frame $top.buts
7367 button $top.buts.gen -text [mc "Create"] -command mktaggo
7368 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7369 grid $top.buts.gen $top.buts.can
7370 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7371 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7372 grid $top.buts - -pady 10 -sticky ew
7373 focus $top.tag
7376 proc domktag {} {
7377 global mktagtop env tagids idtags
7379 set id [$mktagtop.sha1 get]
7380 set tag [$mktagtop.tag get]
7381 if {$tag == {}} {
7382 error_popup [mc "No tag name specified"]
7383 return
7385 if {[info exists tagids($tag)]} {
7386 error_popup [mc "Tag \"%s\" already exists" $tag]
7387 return
7389 if {[catch {
7390 exec git tag $tag $id
7391 } err]} {
7392 error_popup "[mc "Error creating tag:"] $err"
7393 return
7396 set tagids($tag) $id
7397 lappend idtags($id) $tag
7398 redrawtags $id
7399 addedtag $id
7400 dispneartags 0
7401 run refill_reflist
7404 proc redrawtags {id} {
7405 global canv linehtag idpos currentid curview cmitlisted
7406 global canvxmax iddrawn circleitem mainheadid circlecolors
7408 if {![commitinview $id $curview]} return
7409 if {![info exists iddrawn($id)]} return
7410 set row [rowofcommit $id]
7411 if {$id eq $mainheadid} {
7412 set ofill yellow
7413 } else {
7414 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7416 $canv itemconf $circleitem($row) -fill $ofill
7417 $canv delete tag.$id
7418 set xt [eval drawtags $id $idpos($id)]
7419 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7420 set text [$canv itemcget $linehtag($row) -text]
7421 set font [$canv itemcget $linehtag($row) -font]
7422 set xr [expr {$xt + [font measure $font $text]}]
7423 if {$xr > $canvxmax} {
7424 set canvxmax $xr
7425 setcanvscroll
7427 if {[info exists currentid] && $currentid == $id} {
7428 make_secsel $row
7432 proc mktagcan {} {
7433 global mktagtop
7435 catch {destroy $mktagtop}
7436 unset mktagtop
7439 proc mktaggo {} {
7440 domktag
7441 mktagcan
7444 proc writecommit {} {
7445 global rowmenuid wrcomtop commitinfo wrcomcmd
7447 set top .writecommit
7448 set wrcomtop $top
7449 catch {destroy $top}
7450 toplevel $top
7451 label $top.title -text [mc "Write commit to file"]
7452 grid $top.title - -pady 10
7453 label $top.id -text [mc "ID:"]
7454 entry $top.sha1 -width 40 -relief flat
7455 $top.sha1 insert 0 $rowmenuid
7456 $top.sha1 conf -state readonly
7457 grid $top.id $top.sha1 -sticky w
7458 entry $top.head -width 60 -relief flat
7459 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7460 $top.head conf -state readonly
7461 grid x $top.head -sticky w
7462 label $top.clab -text [mc "Command:"]
7463 entry $top.cmd -width 60 -textvariable wrcomcmd
7464 grid $top.clab $top.cmd -sticky w -pady 10
7465 label $top.flab -text [mc "Output file:"]
7466 entry $top.fname -width 60
7467 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7468 grid $top.flab $top.fname -sticky w
7469 frame $top.buts
7470 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7471 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7472 grid $top.buts.gen $top.buts.can
7473 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7474 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7475 grid $top.buts - -pady 10 -sticky ew
7476 focus $top.fname
7479 proc wrcomgo {} {
7480 global wrcomtop
7482 set id [$wrcomtop.sha1 get]
7483 set cmd "echo $id | [$wrcomtop.cmd get]"
7484 set fname [$wrcomtop.fname get]
7485 if {[catch {exec sh -c $cmd >$fname &} err]} {
7486 error_popup "[mc "Error writing commit:"] $err"
7488 catch {destroy $wrcomtop}
7489 unset wrcomtop
7492 proc wrcomcan {} {
7493 global wrcomtop
7495 catch {destroy $wrcomtop}
7496 unset wrcomtop
7499 proc mkbranch {} {
7500 global rowmenuid mkbrtop
7502 set top .makebranch
7503 catch {destroy $top}
7504 toplevel $top
7505 label $top.title -text [mc "Create new branch"]
7506 grid $top.title - -pady 10
7507 label $top.id -text [mc "ID:"]
7508 entry $top.sha1 -width 40 -relief flat
7509 $top.sha1 insert 0 $rowmenuid
7510 $top.sha1 conf -state readonly
7511 grid $top.id $top.sha1 -sticky w
7512 label $top.nlab -text [mc "Name:"]
7513 entry $top.name -width 40
7514 grid $top.nlab $top.name -sticky w
7515 frame $top.buts
7516 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7517 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7518 grid $top.buts.go $top.buts.can
7519 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7520 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7521 grid $top.buts - -pady 10 -sticky ew
7522 focus $top.name
7525 proc mkbrgo {top} {
7526 global headids idheads
7528 set name [$top.name get]
7529 set id [$top.sha1 get]
7530 if {$name eq {}} {
7531 error_popup [mc "Please specify a name for the new branch"]
7532 return
7534 catch {destroy $top}
7535 nowbusy newbranch
7536 update
7537 if {[catch {
7538 exec git branch $name $id
7539 } err]} {
7540 notbusy newbranch
7541 error_popup $err
7542 } else {
7543 set headids($name) $id
7544 lappend idheads($id) $name
7545 addedhead $id $name
7546 notbusy newbranch
7547 redrawtags $id
7548 dispneartags 0
7549 run refill_reflist
7553 proc cherrypick {} {
7554 global rowmenuid curview
7555 global mainhead mainheadid
7557 set oldhead [exec git rev-parse HEAD]
7558 set dheads [descheads $rowmenuid]
7559 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7560 set ok [confirm_popup [mc "Commit %s is already\
7561 included in branch %s -- really re-apply it?" \
7562 [string range $rowmenuid 0 7] $mainhead]]
7563 if {!$ok} return
7565 nowbusy cherrypick [mc "Cherry-picking"]
7566 update
7567 # Unfortunately git-cherry-pick writes stuff to stderr even when
7568 # no error occurs, and exec takes that as an indication of error...
7569 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7570 notbusy cherrypick
7571 error_popup $err
7572 return
7574 set newhead [exec git rev-parse HEAD]
7575 if {$newhead eq $oldhead} {
7576 notbusy cherrypick
7577 error_popup [mc "No changes committed"]
7578 return
7580 addnewchild $newhead $oldhead
7581 if {[commitinview $oldhead $curview]} {
7582 insertrow $newhead $oldhead $curview
7583 if {$mainhead ne {}} {
7584 movehead $newhead $mainhead
7585 movedhead $newhead $mainhead
7587 set mainheadid $newhead
7588 redrawtags $oldhead
7589 redrawtags $newhead
7590 selbyid $newhead
7592 notbusy cherrypick
7595 proc resethead {} {
7596 global mainhead rowmenuid confirm_ok resettype
7598 set confirm_ok 0
7599 set w ".confirmreset"
7600 toplevel $w
7601 wm transient $w .
7602 wm title $w [mc "Confirm reset"]
7603 message $w.m -text \
7604 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7605 -justify center -aspect 1000
7606 pack $w.m -side top -fill x -padx 20 -pady 20
7607 frame $w.f -relief sunken -border 2
7608 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7609 grid $w.f.rt -sticky w
7610 set resettype mixed
7611 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7612 -text [mc "Soft: Leave working tree and index untouched"]
7613 grid $w.f.soft -sticky w
7614 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7615 -text [mc "Mixed: Leave working tree untouched, reset index"]
7616 grid $w.f.mixed -sticky w
7617 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7618 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7619 grid $w.f.hard -sticky w
7620 pack $w.f -side top -fill x
7621 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7622 pack $w.ok -side left -fill x -padx 20 -pady 20
7623 button $w.cancel -text [mc Cancel] -command "destroy $w"
7624 pack $w.cancel -side right -fill x -padx 20 -pady 20
7625 bind $w <Visibility> "grab $w; focus $w"
7626 tkwait window $w
7627 if {!$confirm_ok} return
7628 if {[catch {set fd [open \
7629 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
7630 error_popup $err
7631 } else {
7632 dohidelocalchanges
7633 filerun $fd [list readresetstat $fd]
7634 nowbusy reset [mc "Resetting"]
7635 selbyid $rowmenuid
7639 proc readresetstat {fd} {
7640 global mainhead mainheadid showlocalchanges rprogcoord
7642 if {[gets $fd line] >= 0} {
7643 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7644 set rprogcoord [expr {1.0 * $m / $n}]
7645 adjustprogress
7647 return 1
7649 set rprogcoord 0
7650 adjustprogress
7651 notbusy reset
7652 if {[catch {close $fd} err]} {
7653 error_popup $err
7655 set oldhead $mainheadid
7656 set newhead [exec git rev-parse HEAD]
7657 if {$newhead ne $oldhead} {
7658 movehead $newhead $mainhead
7659 movedhead $newhead $mainhead
7660 set mainheadid $newhead
7661 redrawtags $oldhead
7662 redrawtags $newhead
7664 if {$showlocalchanges} {
7665 doshowlocalchanges
7667 return 0
7670 # context menu for a head
7671 proc headmenu {x y id head} {
7672 global headmenuid headmenuhead headctxmenu mainhead
7674 stopfinding
7675 set headmenuid $id
7676 set headmenuhead $head
7677 set state normal
7678 if {$head eq $mainhead} {
7679 set state disabled
7681 $headctxmenu entryconfigure 0 -state $state
7682 $headctxmenu entryconfigure 1 -state $state
7683 tk_popup $headctxmenu $x $y
7686 proc cobranch {} {
7687 global headmenuid headmenuhead headids
7688 global showlocalchanges mainheadid
7690 # check the tree is clean first??
7691 nowbusy checkout [mc "Checking out"]
7692 update
7693 dohidelocalchanges
7694 if {[catch {
7695 set fd [open [list | git checkout $headmenuhead 2>@1] r]
7696 } err]} {
7697 notbusy checkout
7698 error_popup $err
7699 if {$showlocalchanges} {
7700 dodiffindex
7702 } else {
7703 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
7707 proc readcheckoutstat {fd newhead newheadid} {
7708 global mainhead mainheadid headids showlocalchanges progresscoords
7710 if {[gets $fd line] >= 0} {
7711 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7712 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
7713 adjustprogress
7715 return 1
7717 set progresscoords {0 0}
7718 adjustprogress
7719 notbusy checkout
7720 if {[catch {close $fd} err]} {
7721 error_popup $err
7723 set oldmainid $mainheadid
7724 set mainhead $newhead
7725 set mainheadid $newheadid
7726 redrawtags $oldmainid
7727 redrawtags $newheadid
7728 selbyid $newheadid
7729 if {$showlocalchanges} {
7730 dodiffindex
7734 proc rmbranch {} {
7735 global headmenuid headmenuhead mainhead
7736 global idheads
7738 set head $headmenuhead
7739 set id $headmenuid
7740 # this check shouldn't be needed any more...
7741 if {$head eq $mainhead} {
7742 error_popup [mc "Cannot delete the currently checked-out branch"]
7743 return
7745 set dheads [descheads $id]
7746 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7747 # the stuff on this branch isn't on any other branch
7748 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7749 branch.\nReally delete branch %s?" $head $head]]} return
7751 nowbusy rmbranch
7752 update
7753 if {[catch {exec git branch -D $head} err]} {
7754 notbusy rmbranch
7755 error_popup $err
7756 return
7758 removehead $id $head
7759 removedhead $id $head
7760 redrawtags $id
7761 notbusy rmbranch
7762 dispneartags 0
7763 run refill_reflist
7766 # Display a list of tags and heads
7767 proc showrefs {} {
7768 global showrefstop bgcolor fgcolor selectbgcolor
7769 global bglist fglist reflistfilter reflist maincursor
7771 set top .showrefs
7772 set showrefstop $top
7773 if {[winfo exists $top]} {
7774 raise $top
7775 refill_reflist
7776 return
7778 toplevel $top
7779 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7780 text $top.list -background $bgcolor -foreground $fgcolor \
7781 -selectbackground $selectbgcolor -font mainfont \
7782 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7783 -width 30 -height 20 -cursor $maincursor \
7784 -spacing1 1 -spacing3 1 -state disabled
7785 $top.list tag configure highlight -background $selectbgcolor
7786 lappend bglist $top.list
7787 lappend fglist $top.list
7788 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7789 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7790 grid $top.list $top.ysb -sticky nsew
7791 grid $top.xsb x -sticky ew
7792 frame $top.f
7793 label $top.f.l -text "[mc "Filter"]: "
7794 entry $top.f.e -width 20 -textvariable reflistfilter
7795 set reflistfilter "*"
7796 trace add variable reflistfilter write reflistfilter_change
7797 pack $top.f.e -side right -fill x -expand 1
7798 pack $top.f.l -side left
7799 grid $top.f - -sticky ew -pady 2
7800 button $top.close -command [list destroy $top] -text [mc "Close"]
7801 grid $top.close -
7802 grid columnconfigure $top 0 -weight 1
7803 grid rowconfigure $top 0 -weight 1
7804 bind $top.list <1> {break}
7805 bind $top.list <B1-Motion> {break}
7806 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7807 set reflist {}
7808 refill_reflist
7811 proc sel_reflist {w x y} {
7812 global showrefstop reflist headids tagids otherrefids
7814 if {![winfo exists $showrefstop]} return
7815 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7816 set ref [lindex $reflist [expr {$l-1}]]
7817 set n [lindex $ref 0]
7818 switch -- [lindex $ref 1] {
7819 "H" {selbyid $headids($n)}
7820 "T" {selbyid $tagids($n)}
7821 "o" {selbyid $otherrefids($n)}
7823 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7826 proc unsel_reflist {} {
7827 global showrefstop
7829 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7830 $showrefstop.list tag remove highlight 0.0 end
7833 proc reflistfilter_change {n1 n2 op} {
7834 global reflistfilter
7836 after cancel refill_reflist
7837 after 200 refill_reflist
7840 proc refill_reflist {} {
7841 global reflist reflistfilter showrefstop headids tagids otherrefids
7842 global curview commitinterest
7844 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7845 set refs {}
7846 foreach n [array names headids] {
7847 if {[string match $reflistfilter $n]} {
7848 if {[commitinview $headids($n) $curview]} {
7849 lappend refs [list $n H]
7850 } else {
7851 set commitinterest($headids($n)) {run refill_reflist}
7855 foreach n [array names tagids] {
7856 if {[string match $reflistfilter $n]} {
7857 if {[commitinview $tagids($n) $curview]} {
7858 lappend refs [list $n T]
7859 } else {
7860 set commitinterest($tagids($n)) {run refill_reflist}
7864 foreach n [array names otherrefids] {
7865 if {[string match $reflistfilter $n]} {
7866 if {[commitinview $otherrefids($n) $curview]} {
7867 lappend refs [list $n o]
7868 } else {
7869 set commitinterest($otherrefids($n)) {run refill_reflist}
7873 set refs [lsort -index 0 $refs]
7874 if {$refs eq $reflist} return
7876 # Update the contents of $showrefstop.list according to the
7877 # differences between $reflist (old) and $refs (new)
7878 $showrefstop.list conf -state normal
7879 $showrefstop.list insert end "\n"
7880 set i 0
7881 set j 0
7882 while {$i < [llength $reflist] || $j < [llength $refs]} {
7883 if {$i < [llength $reflist]} {
7884 if {$j < [llength $refs]} {
7885 set cmp [string compare [lindex $reflist $i 0] \
7886 [lindex $refs $j 0]]
7887 if {$cmp == 0} {
7888 set cmp [string compare [lindex $reflist $i 1] \
7889 [lindex $refs $j 1]]
7891 } else {
7892 set cmp -1
7894 } else {
7895 set cmp 1
7897 switch -- $cmp {
7898 -1 {
7899 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7900 incr i
7903 incr i
7904 incr j
7907 set l [expr {$j + 1}]
7908 $showrefstop.list image create $l.0 -align baseline \
7909 -image reficon-[lindex $refs $j 1] -padx 2
7910 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7911 incr j
7915 set reflist $refs
7916 # delete last newline
7917 $showrefstop.list delete end-2c end-1c
7918 $showrefstop.list conf -state disabled
7921 # Stuff for finding nearby tags
7922 proc getallcommits {} {
7923 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7924 global idheads idtags idotherrefs allparents tagobjid
7926 if {![info exists allcommits]} {
7927 set nextarc 0
7928 set allcommits 0
7929 set seeds {}
7930 set allcwait 0
7931 set cachedarcs 0
7932 set allccache [file join [gitdir] "gitk.cache"]
7933 if {![catch {
7934 set f [open $allccache r]
7935 set allcwait 1
7936 getcache $f
7937 }]} return
7940 if {$allcwait} {
7941 return
7943 set cmd [list | git rev-list --parents]
7944 set allcupdate [expr {$seeds ne {}}]
7945 if {!$allcupdate} {
7946 set ids "--all"
7947 } else {
7948 set refs [concat [array names idheads] [array names idtags] \
7949 [array names idotherrefs]]
7950 set ids {}
7951 set tagobjs {}
7952 foreach name [array names tagobjid] {
7953 lappend tagobjs $tagobjid($name)
7955 foreach id [lsort -unique $refs] {
7956 if {![info exists allparents($id)] &&
7957 [lsearch -exact $tagobjs $id] < 0} {
7958 lappend ids $id
7961 if {$ids ne {}} {
7962 foreach id $seeds {
7963 lappend ids "^$id"
7967 if {$ids ne {}} {
7968 set fd [open [concat $cmd $ids] r]
7969 fconfigure $fd -blocking 0
7970 incr allcommits
7971 nowbusy allcommits
7972 filerun $fd [list getallclines $fd]
7973 } else {
7974 dispneartags 0
7978 # Since most commits have 1 parent and 1 child, we group strings of
7979 # such commits into "arcs" joining branch/merge points (BMPs), which
7980 # are commits that either don't have 1 parent or don't have 1 child.
7982 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
7983 # arcout(id) - outgoing arcs for BMP
7984 # arcids(a) - list of IDs on arc including end but not start
7985 # arcstart(a) - BMP ID at start of arc
7986 # arcend(a) - BMP ID at end of arc
7987 # growing(a) - arc a is still growing
7988 # arctags(a) - IDs out of arcids (excluding end) that have tags
7989 # archeads(a) - IDs out of arcids (excluding end) that have heads
7990 # The start of an arc is at the descendent end, so "incoming" means
7991 # coming from descendents, and "outgoing" means going towards ancestors.
7993 proc getallclines {fd} {
7994 global allparents allchildren idtags idheads nextarc
7995 global arcnos arcids arctags arcout arcend arcstart archeads growing
7996 global seeds allcommits cachedarcs allcupdate
7998 set nid 0
7999 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8000 set id [lindex $line 0]
8001 if {[info exists allparents($id)]} {
8002 # seen it already
8003 continue
8005 set cachedarcs 0
8006 set olds [lrange $line 1 end]
8007 set allparents($id) $olds
8008 if {![info exists allchildren($id)]} {
8009 set allchildren($id) {}
8010 set arcnos($id) {}
8011 lappend seeds $id
8012 } else {
8013 set a $arcnos($id)
8014 if {[llength $olds] == 1 && [llength $a] == 1} {
8015 lappend arcids($a) $id
8016 if {[info exists idtags($id)]} {
8017 lappend arctags($a) $id
8019 if {[info exists idheads($id)]} {
8020 lappend archeads($a) $id
8022 if {[info exists allparents($olds)]} {
8023 # seen parent already
8024 if {![info exists arcout($olds)]} {
8025 splitarc $olds
8027 lappend arcids($a) $olds
8028 set arcend($a) $olds
8029 unset growing($a)
8031 lappend allchildren($olds) $id
8032 lappend arcnos($olds) $a
8033 continue
8036 foreach a $arcnos($id) {
8037 lappend arcids($a) $id
8038 set arcend($a) $id
8039 unset growing($a)
8042 set ao {}
8043 foreach p $olds {
8044 lappend allchildren($p) $id
8045 set a [incr nextarc]
8046 set arcstart($a) $id
8047 set archeads($a) {}
8048 set arctags($a) {}
8049 set archeads($a) {}
8050 set arcids($a) {}
8051 lappend ao $a
8052 set growing($a) 1
8053 if {[info exists allparents($p)]} {
8054 # seen it already, may need to make a new branch
8055 if {![info exists arcout($p)]} {
8056 splitarc $p
8058 lappend arcids($a) $p
8059 set arcend($a) $p
8060 unset growing($a)
8062 lappend arcnos($p) $a
8064 set arcout($id) $ao
8066 if {$nid > 0} {
8067 global cached_dheads cached_dtags cached_atags
8068 catch {unset cached_dheads}
8069 catch {unset cached_dtags}
8070 catch {unset cached_atags}
8072 if {![eof $fd]} {
8073 return [expr {$nid >= 1000? 2: 1}]
8075 set cacheok 1
8076 if {[catch {
8077 fconfigure $fd -blocking 1
8078 close $fd
8079 } err]} {
8080 # got an error reading the list of commits
8081 # if we were updating, try rereading the whole thing again
8082 if {$allcupdate} {
8083 incr allcommits -1
8084 dropcache $err
8085 return
8087 error_popup "[mc "Error reading commit topology information;\
8088 branch and preceding/following tag information\
8089 will be incomplete."]\n($err)"
8090 set cacheok 0
8092 if {[incr allcommits -1] == 0} {
8093 notbusy allcommits
8094 if {$cacheok} {
8095 run savecache
8098 dispneartags 0
8099 return 0
8102 proc recalcarc {a} {
8103 global arctags archeads arcids idtags idheads
8105 set at {}
8106 set ah {}
8107 foreach id [lrange $arcids($a) 0 end-1] {
8108 if {[info exists idtags($id)]} {
8109 lappend at $id
8111 if {[info exists idheads($id)]} {
8112 lappend ah $id
8115 set arctags($a) $at
8116 set archeads($a) $ah
8119 proc splitarc {p} {
8120 global arcnos arcids nextarc arctags archeads idtags idheads
8121 global arcstart arcend arcout allparents growing
8123 set a $arcnos($p)
8124 if {[llength $a] != 1} {
8125 puts "oops splitarc called but [llength $a] arcs already"
8126 return
8128 set a [lindex $a 0]
8129 set i [lsearch -exact $arcids($a) $p]
8130 if {$i < 0} {
8131 puts "oops splitarc $p not in arc $a"
8132 return
8134 set na [incr nextarc]
8135 if {[info exists arcend($a)]} {
8136 set arcend($na) $arcend($a)
8137 } else {
8138 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8139 set j [lsearch -exact $arcnos($l) $a]
8140 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8142 set tail [lrange $arcids($a) [expr {$i+1}] end]
8143 set arcids($a) [lrange $arcids($a) 0 $i]
8144 set arcend($a) $p
8145 set arcstart($na) $p
8146 set arcout($p) $na
8147 set arcids($na) $tail
8148 if {[info exists growing($a)]} {
8149 set growing($na) 1
8150 unset growing($a)
8153 foreach id $tail {
8154 if {[llength $arcnos($id)] == 1} {
8155 set arcnos($id) $na
8156 } else {
8157 set j [lsearch -exact $arcnos($id) $a]
8158 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8162 # reconstruct tags and heads lists
8163 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8164 recalcarc $a
8165 recalcarc $na
8166 } else {
8167 set arctags($na) {}
8168 set archeads($na) {}
8172 # Update things for a new commit added that is a child of one
8173 # existing commit. Used when cherry-picking.
8174 proc addnewchild {id p} {
8175 global allparents allchildren idtags nextarc
8176 global arcnos arcids arctags arcout arcend arcstart archeads growing
8177 global seeds allcommits
8179 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8180 set allparents($id) [list $p]
8181 set allchildren($id) {}
8182 set arcnos($id) {}
8183 lappend seeds $id
8184 lappend allchildren($p) $id
8185 set a [incr nextarc]
8186 set arcstart($a) $id
8187 set archeads($a) {}
8188 set arctags($a) {}
8189 set arcids($a) [list $p]
8190 set arcend($a) $p
8191 if {![info exists arcout($p)]} {
8192 splitarc $p
8194 lappend arcnos($p) $a
8195 set arcout($id) [list $a]
8198 # This implements a cache for the topology information.
8199 # The cache saves, for each arc, the start and end of the arc,
8200 # the ids on the arc, and the outgoing arcs from the end.
8201 proc readcache {f} {
8202 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8203 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8204 global allcwait
8206 set a $nextarc
8207 set lim $cachedarcs
8208 if {$lim - $a > 500} {
8209 set lim [expr {$a + 500}]
8211 if {[catch {
8212 if {$a == $lim} {
8213 # finish reading the cache and setting up arctags, etc.
8214 set line [gets $f]
8215 if {$line ne "1"} {error "bad final version"}
8216 close $f
8217 foreach id [array names idtags] {
8218 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8219 [llength $allparents($id)] == 1} {
8220 set a [lindex $arcnos($id) 0]
8221 if {$arctags($a) eq {}} {
8222 recalcarc $a
8226 foreach id [array names idheads] {
8227 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8228 [llength $allparents($id)] == 1} {
8229 set a [lindex $arcnos($id) 0]
8230 if {$archeads($a) eq {}} {
8231 recalcarc $a
8235 foreach id [lsort -unique $possible_seeds] {
8236 if {$arcnos($id) eq {}} {
8237 lappend seeds $id
8240 set allcwait 0
8241 } else {
8242 while {[incr a] <= $lim} {
8243 set line [gets $f]
8244 if {[llength $line] != 3} {error "bad line"}
8245 set s [lindex $line 0]
8246 set arcstart($a) $s
8247 lappend arcout($s) $a
8248 if {![info exists arcnos($s)]} {
8249 lappend possible_seeds $s
8250 set arcnos($s) {}
8252 set e [lindex $line 1]
8253 if {$e eq {}} {
8254 set growing($a) 1
8255 } else {
8256 set arcend($a) $e
8257 if {![info exists arcout($e)]} {
8258 set arcout($e) {}
8261 set arcids($a) [lindex $line 2]
8262 foreach id $arcids($a) {
8263 lappend allparents($s) $id
8264 set s $id
8265 lappend arcnos($id) $a
8267 if {![info exists allparents($s)]} {
8268 set allparents($s) {}
8270 set arctags($a) {}
8271 set archeads($a) {}
8273 set nextarc [expr {$a - 1}]
8275 } err]} {
8276 dropcache $err
8277 return 0
8279 if {!$allcwait} {
8280 getallcommits
8282 return $allcwait
8285 proc getcache {f} {
8286 global nextarc cachedarcs possible_seeds
8288 if {[catch {
8289 set line [gets $f]
8290 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8291 # make sure it's an integer
8292 set cachedarcs [expr {int([lindex $line 1])}]
8293 if {$cachedarcs < 0} {error "bad number of arcs"}
8294 set nextarc 0
8295 set possible_seeds {}
8296 run readcache $f
8297 } err]} {
8298 dropcache $err
8300 return 0
8303 proc dropcache {err} {
8304 global allcwait nextarc cachedarcs seeds
8306 #puts "dropping cache ($err)"
8307 foreach v {arcnos arcout arcids arcstart arcend growing \
8308 arctags archeads allparents allchildren} {
8309 global $v
8310 catch {unset $v}
8312 set allcwait 0
8313 set nextarc 0
8314 set cachedarcs 0
8315 set seeds {}
8316 getallcommits
8319 proc writecache {f} {
8320 global cachearc cachedarcs allccache
8321 global arcstart arcend arcnos arcids arcout
8323 set a $cachearc
8324 set lim $cachedarcs
8325 if {$lim - $a > 1000} {
8326 set lim [expr {$a + 1000}]
8328 if {[catch {
8329 while {[incr a] <= $lim} {
8330 if {[info exists arcend($a)]} {
8331 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8332 } else {
8333 puts $f [list $arcstart($a) {} $arcids($a)]
8336 } err]} {
8337 catch {close $f}
8338 catch {file delete $allccache}
8339 #puts "writing cache failed ($err)"
8340 return 0
8342 set cachearc [expr {$a - 1}]
8343 if {$a > $cachedarcs} {
8344 puts $f "1"
8345 close $f
8346 return 0
8348 return 1
8351 proc savecache {} {
8352 global nextarc cachedarcs cachearc allccache
8354 if {$nextarc == $cachedarcs} return
8355 set cachearc 0
8356 set cachedarcs $nextarc
8357 catch {
8358 set f [open $allccache w]
8359 puts $f [list 1 $cachedarcs]
8360 run writecache $f
8364 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8365 # or 0 if neither is true.
8366 proc anc_or_desc {a b} {
8367 global arcout arcstart arcend arcnos cached_isanc
8369 if {$arcnos($a) eq $arcnos($b)} {
8370 # Both are on the same arc(s); either both are the same BMP,
8371 # or if one is not a BMP, the other is also not a BMP or is
8372 # the BMP at end of the arc (and it only has 1 incoming arc).
8373 # Or both can be BMPs with no incoming arcs.
8374 if {$a eq $b || $arcnos($a) eq {}} {
8375 return 0
8377 # assert {[llength $arcnos($a)] == 1}
8378 set arc [lindex $arcnos($a) 0]
8379 set i [lsearch -exact $arcids($arc) $a]
8380 set j [lsearch -exact $arcids($arc) $b]
8381 if {$i < 0 || $i > $j} {
8382 return 1
8383 } else {
8384 return -1
8388 if {![info exists arcout($a)]} {
8389 set arc [lindex $arcnos($a) 0]
8390 if {[info exists arcend($arc)]} {
8391 set aend $arcend($arc)
8392 } else {
8393 set aend {}
8395 set a $arcstart($arc)
8396 } else {
8397 set aend $a
8399 if {![info exists arcout($b)]} {
8400 set arc [lindex $arcnos($b) 0]
8401 if {[info exists arcend($arc)]} {
8402 set bend $arcend($arc)
8403 } else {
8404 set bend {}
8406 set b $arcstart($arc)
8407 } else {
8408 set bend $b
8410 if {$a eq $bend} {
8411 return 1
8413 if {$b eq $aend} {
8414 return -1
8416 if {[info exists cached_isanc($a,$bend)]} {
8417 if {$cached_isanc($a,$bend)} {
8418 return 1
8421 if {[info exists cached_isanc($b,$aend)]} {
8422 if {$cached_isanc($b,$aend)} {
8423 return -1
8425 if {[info exists cached_isanc($a,$bend)]} {
8426 return 0
8430 set todo [list $a $b]
8431 set anc($a) a
8432 set anc($b) b
8433 for {set i 0} {$i < [llength $todo]} {incr i} {
8434 set x [lindex $todo $i]
8435 if {$anc($x) eq {}} {
8436 continue
8438 foreach arc $arcnos($x) {
8439 set xd $arcstart($arc)
8440 if {$xd eq $bend} {
8441 set cached_isanc($a,$bend) 1
8442 set cached_isanc($b,$aend) 0
8443 return 1
8444 } elseif {$xd eq $aend} {
8445 set cached_isanc($b,$aend) 1
8446 set cached_isanc($a,$bend) 0
8447 return -1
8449 if {![info exists anc($xd)]} {
8450 set anc($xd) $anc($x)
8451 lappend todo $xd
8452 } elseif {$anc($xd) ne $anc($x)} {
8453 set anc($xd) {}
8457 set cached_isanc($a,$bend) 0
8458 set cached_isanc($b,$aend) 0
8459 return 0
8462 # This identifies whether $desc has an ancestor that is
8463 # a growing tip of the graph and which is not an ancestor of $anc
8464 # and returns 0 if so and 1 if not.
8465 # If we subsequently discover a tag on such a growing tip, and that
8466 # turns out to be a descendent of $anc (which it could, since we
8467 # don't necessarily see children before parents), then $desc
8468 # isn't a good choice to display as a descendent tag of
8469 # $anc (since it is the descendent of another tag which is
8470 # a descendent of $anc). Similarly, $anc isn't a good choice to
8471 # display as a ancestor tag of $desc.
8473 proc is_certain {desc anc} {
8474 global arcnos arcout arcstart arcend growing problems
8476 set certain {}
8477 if {[llength $arcnos($anc)] == 1} {
8478 # tags on the same arc are certain
8479 if {$arcnos($desc) eq $arcnos($anc)} {
8480 return 1
8482 if {![info exists arcout($anc)]} {
8483 # if $anc is partway along an arc, use the start of the arc instead
8484 set a [lindex $arcnos($anc) 0]
8485 set anc $arcstart($a)
8488 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8489 set x $desc
8490 } else {
8491 set a [lindex $arcnos($desc) 0]
8492 set x $arcend($a)
8494 if {$x == $anc} {
8495 return 1
8497 set anclist [list $x]
8498 set dl($x) 1
8499 set nnh 1
8500 set ngrowanc 0
8501 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8502 set x [lindex $anclist $i]
8503 if {$dl($x)} {
8504 incr nnh -1
8506 set done($x) 1
8507 foreach a $arcout($x) {
8508 if {[info exists growing($a)]} {
8509 if {![info exists growanc($x)] && $dl($x)} {
8510 set growanc($x) 1
8511 incr ngrowanc
8513 } else {
8514 set y $arcend($a)
8515 if {[info exists dl($y)]} {
8516 if {$dl($y)} {
8517 if {!$dl($x)} {
8518 set dl($y) 0
8519 if {![info exists done($y)]} {
8520 incr nnh -1
8522 if {[info exists growanc($x)]} {
8523 incr ngrowanc -1
8525 set xl [list $y]
8526 for {set k 0} {$k < [llength $xl]} {incr k} {
8527 set z [lindex $xl $k]
8528 foreach c $arcout($z) {
8529 if {[info exists arcend($c)]} {
8530 set v $arcend($c)
8531 if {[info exists dl($v)] && $dl($v)} {
8532 set dl($v) 0
8533 if {![info exists done($v)]} {
8534 incr nnh -1
8536 if {[info exists growanc($v)]} {
8537 incr ngrowanc -1
8539 lappend xl $v
8546 } elseif {$y eq $anc || !$dl($x)} {
8547 set dl($y) 0
8548 lappend anclist $y
8549 } else {
8550 set dl($y) 1
8551 lappend anclist $y
8552 incr nnh
8557 foreach x [array names growanc] {
8558 if {$dl($x)} {
8559 return 0
8561 return 0
8563 return 1
8566 proc validate_arctags {a} {
8567 global arctags idtags
8569 set i -1
8570 set na $arctags($a)
8571 foreach id $arctags($a) {
8572 incr i
8573 if {![info exists idtags($id)]} {
8574 set na [lreplace $na $i $i]
8575 incr i -1
8578 set arctags($a) $na
8581 proc validate_archeads {a} {
8582 global archeads idheads
8584 set i -1
8585 set na $archeads($a)
8586 foreach id $archeads($a) {
8587 incr i
8588 if {![info exists idheads($id)]} {
8589 set na [lreplace $na $i $i]
8590 incr i -1
8593 set archeads($a) $na
8596 # Return the list of IDs that have tags that are descendents of id,
8597 # ignoring IDs that are descendents of IDs already reported.
8598 proc desctags {id} {
8599 global arcnos arcstart arcids arctags idtags allparents
8600 global growing cached_dtags
8602 if {![info exists allparents($id)]} {
8603 return {}
8605 set t1 [clock clicks -milliseconds]
8606 set argid $id
8607 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8608 # part-way along an arc; check that arc first
8609 set a [lindex $arcnos($id) 0]
8610 if {$arctags($a) ne {}} {
8611 validate_arctags $a
8612 set i [lsearch -exact $arcids($a) $id]
8613 set tid {}
8614 foreach t $arctags($a) {
8615 set j [lsearch -exact $arcids($a) $t]
8616 if {$j >= $i} break
8617 set tid $t
8619 if {$tid ne {}} {
8620 return $tid
8623 set id $arcstart($a)
8624 if {[info exists idtags($id)]} {
8625 return $id
8628 if {[info exists cached_dtags($id)]} {
8629 return $cached_dtags($id)
8632 set origid $id
8633 set todo [list $id]
8634 set queued($id) 1
8635 set nc 1
8636 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8637 set id [lindex $todo $i]
8638 set done($id) 1
8639 set ta [info exists hastaggedancestor($id)]
8640 if {!$ta} {
8641 incr nc -1
8643 # ignore tags on starting node
8644 if {!$ta && $i > 0} {
8645 if {[info exists idtags($id)]} {
8646 set tagloc($id) $id
8647 set ta 1
8648 } elseif {[info exists cached_dtags($id)]} {
8649 set tagloc($id) $cached_dtags($id)
8650 set ta 1
8653 foreach a $arcnos($id) {
8654 set d $arcstart($a)
8655 if {!$ta && $arctags($a) ne {}} {
8656 validate_arctags $a
8657 if {$arctags($a) ne {}} {
8658 lappend tagloc($id) [lindex $arctags($a) end]
8661 if {$ta || $arctags($a) ne {}} {
8662 set tomark [list $d]
8663 for {set j 0} {$j < [llength $tomark]} {incr j} {
8664 set dd [lindex $tomark $j]
8665 if {![info exists hastaggedancestor($dd)]} {
8666 if {[info exists done($dd)]} {
8667 foreach b $arcnos($dd) {
8668 lappend tomark $arcstart($b)
8670 if {[info exists tagloc($dd)]} {
8671 unset tagloc($dd)
8673 } elseif {[info exists queued($dd)]} {
8674 incr nc -1
8676 set hastaggedancestor($dd) 1
8680 if {![info exists queued($d)]} {
8681 lappend todo $d
8682 set queued($d) 1
8683 if {![info exists hastaggedancestor($d)]} {
8684 incr nc
8689 set tags {}
8690 foreach id [array names tagloc] {
8691 if {![info exists hastaggedancestor($id)]} {
8692 foreach t $tagloc($id) {
8693 if {[lsearch -exact $tags $t] < 0} {
8694 lappend tags $t
8699 set t2 [clock clicks -milliseconds]
8700 set loopix $i
8702 # remove tags that are descendents of other tags
8703 for {set i 0} {$i < [llength $tags]} {incr i} {
8704 set a [lindex $tags $i]
8705 for {set j 0} {$j < $i} {incr j} {
8706 set b [lindex $tags $j]
8707 set r [anc_or_desc $a $b]
8708 if {$r == 1} {
8709 set tags [lreplace $tags $j $j]
8710 incr j -1
8711 incr i -1
8712 } elseif {$r == -1} {
8713 set tags [lreplace $tags $i $i]
8714 incr i -1
8715 break
8720 if {[array names growing] ne {}} {
8721 # graph isn't finished, need to check if any tag could get
8722 # eclipsed by another tag coming later. Simply ignore any
8723 # tags that could later get eclipsed.
8724 set ctags {}
8725 foreach t $tags {
8726 if {[is_certain $t $origid]} {
8727 lappend ctags $t
8730 if {$tags eq $ctags} {
8731 set cached_dtags($origid) $tags
8732 } else {
8733 set tags $ctags
8735 } else {
8736 set cached_dtags($origid) $tags
8738 set t3 [clock clicks -milliseconds]
8739 if {0 && $t3 - $t1 >= 100} {
8740 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8741 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8743 return $tags
8746 proc anctags {id} {
8747 global arcnos arcids arcout arcend arctags idtags allparents
8748 global growing cached_atags
8750 if {![info exists allparents($id)]} {
8751 return {}
8753 set t1 [clock clicks -milliseconds]
8754 set argid $id
8755 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8756 # part-way along an arc; check that arc first
8757 set a [lindex $arcnos($id) 0]
8758 if {$arctags($a) ne {}} {
8759 validate_arctags $a
8760 set i [lsearch -exact $arcids($a) $id]
8761 foreach t $arctags($a) {
8762 set j [lsearch -exact $arcids($a) $t]
8763 if {$j > $i} {
8764 return $t
8768 if {![info exists arcend($a)]} {
8769 return {}
8771 set id $arcend($a)
8772 if {[info exists idtags($id)]} {
8773 return $id
8776 if {[info exists cached_atags($id)]} {
8777 return $cached_atags($id)
8780 set origid $id
8781 set todo [list $id]
8782 set queued($id) 1
8783 set taglist {}
8784 set nc 1
8785 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8786 set id [lindex $todo $i]
8787 set done($id) 1
8788 set td [info exists hastaggeddescendent($id)]
8789 if {!$td} {
8790 incr nc -1
8792 # ignore tags on starting node
8793 if {!$td && $i > 0} {
8794 if {[info exists idtags($id)]} {
8795 set tagloc($id) $id
8796 set td 1
8797 } elseif {[info exists cached_atags($id)]} {
8798 set tagloc($id) $cached_atags($id)
8799 set td 1
8802 foreach a $arcout($id) {
8803 if {!$td && $arctags($a) ne {}} {
8804 validate_arctags $a
8805 if {$arctags($a) ne {}} {
8806 lappend tagloc($id) [lindex $arctags($a) 0]
8809 if {![info exists arcend($a)]} continue
8810 set d $arcend($a)
8811 if {$td || $arctags($a) ne {}} {
8812 set tomark [list $d]
8813 for {set j 0} {$j < [llength $tomark]} {incr j} {
8814 set dd [lindex $tomark $j]
8815 if {![info exists hastaggeddescendent($dd)]} {
8816 if {[info exists done($dd)]} {
8817 foreach b $arcout($dd) {
8818 if {[info exists arcend($b)]} {
8819 lappend tomark $arcend($b)
8822 if {[info exists tagloc($dd)]} {
8823 unset tagloc($dd)
8825 } elseif {[info exists queued($dd)]} {
8826 incr nc -1
8828 set hastaggeddescendent($dd) 1
8832 if {![info exists queued($d)]} {
8833 lappend todo $d
8834 set queued($d) 1
8835 if {![info exists hastaggeddescendent($d)]} {
8836 incr nc
8841 set t2 [clock clicks -milliseconds]
8842 set loopix $i
8843 set tags {}
8844 foreach id [array names tagloc] {
8845 if {![info exists hastaggeddescendent($id)]} {
8846 foreach t $tagloc($id) {
8847 if {[lsearch -exact $tags $t] < 0} {
8848 lappend tags $t
8854 # remove tags that are ancestors of other tags
8855 for {set i 0} {$i < [llength $tags]} {incr i} {
8856 set a [lindex $tags $i]
8857 for {set j 0} {$j < $i} {incr j} {
8858 set b [lindex $tags $j]
8859 set r [anc_or_desc $a $b]
8860 if {$r == -1} {
8861 set tags [lreplace $tags $j $j]
8862 incr j -1
8863 incr i -1
8864 } elseif {$r == 1} {
8865 set tags [lreplace $tags $i $i]
8866 incr i -1
8867 break
8872 if {[array names growing] ne {}} {
8873 # graph isn't finished, need to check if any tag could get
8874 # eclipsed by another tag coming later. Simply ignore any
8875 # tags that could later get eclipsed.
8876 set ctags {}
8877 foreach t $tags {
8878 if {[is_certain $origid $t]} {
8879 lappend ctags $t
8882 if {$tags eq $ctags} {
8883 set cached_atags($origid) $tags
8884 } else {
8885 set tags $ctags
8887 } else {
8888 set cached_atags($origid) $tags
8890 set t3 [clock clicks -milliseconds]
8891 if {0 && $t3 - $t1 >= 100} {
8892 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8893 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8895 return $tags
8898 # Return the list of IDs that have heads that are descendents of id,
8899 # including id itself if it has a head.
8900 proc descheads {id} {
8901 global arcnos arcstart arcids archeads idheads cached_dheads
8902 global allparents
8904 if {![info exists allparents($id)]} {
8905 return {}
8907 set aret {}
8908 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8909 # part-way along an arc; check it first
8910 set a [lindex $arcnos($id) 0]
8911 if {$archeads($a) ne {}} {
8912 validate_archeads $a
8913 set i [lsearch -exact $arcids($a) $id]
8914 foreach t $archeads($a) {
8915 set j [lsearch -exact $arcids($a) $t]
8916 if {$j > $i} break
8917 lappend aret $t
8920 set id $arcstart($a)
8922 set origid $id
8923 set todo [list $id]
8924 set seen($id) 1
8925 set ret {}
8926 for {set i 0} {$i < [llength $todo]} {incr i} {
8927 set id [lindex $todo $i]
8928 if {[info exists cached_dheads($id)]} {
8929 set ret [concat $ret $cached_dheads($id)]
8930 } else {
8931 if {[info exists idheads($id)]} {
8932 lappend ret $id
8934 foreach a $arcnos($id) {
8935 if {$archeads($a) ne {}} {
8936 validate_archeads $a
8937 if {$archeads($a) ne {}} {
8938 set ret [concat $ret $archeads($a)]
8941 set d $arcstart($a)
8942 if {![info exists seen($d)]} {
8943 lappend todo $d
8944 set seen($d) 1
8949 set ret [lsort -unique $ret]
8950 set cached_dheads($origid) $ret
8951 return [concat $ret $aret]
8954 proc addedtag {id} {
8955 global arcnos arcout cached_dtags cached_atags
8957 if {![info exists arcnos($id)]} return
8958 if {![info exists arcout($id)]} {
8959 recalcarc [lindex $arcnos($id) 0]
8961 catch {unset cached_dtags}
8962 catch {unset cached_atags}
8965 proc addedhead {hid head} {
8966 global arcnos arcout cached_dheads
8968 if {![info exists arcnos($hid)]} return
8969 if {![info exists arcout($hid)]} {
8970 recalcarc [lindex $arcnos($hid) 0]
8972 catch {unset cached_dheads}
8975 proc removedhead {hid head} {
8976 global cached_dheads
8978 catch {unset cached_dheads}
8981 proc movedhead {hid head} {
8982 global arcnos arcout cached_dheads
8984 if {![info exists arcnos($hid)]} return
8985 if {![info exists arcout($hid)]} {
8986 recalcarc [lindex $arcnos($hid) 0]
8988 catch {unset cached_dheads}
8991 proc changedrefs {} {
8992 global cached_dheads cached_dtags cached_atags
8993 global arctags archeads arcnos arcout idheads idtags
8995 foreach id [concat [array names idheads] [array names idtags]] {
8996 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
8997 set a [lindex $arcnos($id) 0]
8998 if {![info exists donearc($a)]} {
8999 recalcarc $a
9000 set donearc($a) 1
9004 catch {unset cached_dtags}
9005 catch {unset cached_atags}
9006 catch {unset cached_dheads}
9009 proc rereadrefs {} {
9010 global idtags idheads idotherrefs mainheadid
9012 set refids [concat [array names idtags] \
9013 [array names idheads] [array names idotherrefs]]
9014 foreach id $refids {
9015 if {![info exists ref($id)]} {
9016 set ref($id) [listrefs $id]
9019 set oldmainhead $mainheadid
9020 readrefs
9021 changedrefs
9022 set refids [lsort -unique [concat $refids [array names idtags] \
9023 [array names idheads] [array names idotherrefs]]]
9024 foreach id $refids {
9025 set v [listrefs $id]
9026 if {![info exists ref($id)] || $ref($id) != $v} {
9027 redrawtags $id
9030 if {$oldmainhead ne $mainheadid} {
9031 redrawtags $oldmainhead
9032 redrawtags $mainheadid
9034 run refill_reflist
9037 proc listrefs {id} {
9038 global idtags idheads idotherrefs
9040 set x {}
9041 if {[info exists idtags($id)]} {
9042 set x $idtags($id)
9044 set y {}
9045 if {[info exists idheads($id)]} {
9046 set y $idheads($id)
9048 set z {}
9049 if {[info exists idotherrefs($id)]} {
9050 set z $idotherrefs($id)
9052 return [list $x $y $z]
9055 proc showtag {tag isnew} {
9056 global ctext tagcontents tagids linknum tagobjid
9058 if {$isnew} {
9059 addtohistory [list showtag $tag 0]
9061 $ctext conf -state normal
9062 clear_ctext
9063 settabs 0
9064 set linknum 0
9065 if {![info exists tagcontents($tag)]} {
9066 catch {
9067 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9070 if {[info exists tagcontents($tag)]} {
9071 set text $tagcontents($tag)
9072 } else {
9073 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9075 appendwithlinks $text {}
9076 $ctext conf -state disabled
9077 init_flist {}
9080 proc doquit {} {
9081 global stopped
9082 global gitktmpdir
9084 set stopped 100
9085 savestuff .
9086 destroy .
9088 if {[info exists gitktmpdir]} {
9089 catch {file delete -force $gitktmpdir}
9093 proc mkfontdisp {font top which} {
9094 global fontattr fontpref $font
9096 set fontpref($font) [set $font]
9097 button $top.${font}but -text $which -font optionfont \
9098 -command [list choosefont $font $which]
9099 label $top.$font -relief flat -font $font \
9100 -text $fontattr($font,family) -justify left
9101 grid x $top.${font}but $top.$font -sticky w
9104 proc choosefont {font which} {
9105 global fontparam fontlist fonttop fontattr
9107 set fontparam(which) $which
9108 set fontparam(font) $font
9109 set fontparam(family) [font actual $font -family]
9110 set fontparam(size) $fontattr($font,size)
9111 set fontparam(weight) $fontattr($font,weight)
9112 set fontparam(slant) $fontattr($font,slant)
9113 set top .gitkfont
9114 set fonttop $top
9115 if {![winfo exists $top]} {
9116 font create sample
9117 eval font config sample [font actual $font]
9118 toplevel $top
9119 wm title $top [mc "Gitk font chooser"]
9120 label $top.l -textvariable fontparam(which)
9121 pack $top.l -side top
9122 set fontlist [lsort [font families]]
9123 frame $top.f
9124 listbox $top.f.fam -listvariable fontlist \
9125 -yscrollcommand [list $top.f.sb set]
9126 bind $top.f.fam <<ListboxSelect>> selfontfam
9127 scrollbar $top.f.sb -command [list $top.f.fam yview]
9128 pack $top.f.sb -side right -fill y
9129 pack $top.f.fam -side left -fill both -expand 1
9130 pack $top.f -side top -fill both -expand 1
9131 frame $top.g
9132 spinbox $top.g.size -from 4 -to 40 -width 4 \
9133 -textvariable fontparam(size) \
9134 -validatecommand {string is integer -strict %s}
9135 checkbutton $top.g.bold -padx 5 \
9136 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9137 -variable fontparam(weight) -onvalue bold -offvalue normal
9138 checkbutton $top.g.ital -padx 5 \
9139 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9140 -variable fontparam(slant) -onvalue italic -offvalue roman
9141 pack $top.g.size $top.g.bold $top.g.ital -side left
9142 pack $top.g -side top
9143 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9144 -background white
9145 $top.c create text 100 25 -anchor center -text $which -font sample \
9146 -fill black -tags text
9147 bind $top.c <Configure> [list centertext $top.c]
9148 pack $top.c -side top -fill x
9149 frame $top.buts
9150 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9151 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9152 grid $top.buts.ok $top.buts.can
9153 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9154 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9155 pack $top.buts -side bottom -fill x
9156 trace add variable fontparam write chg_fontparam
9157 } else {
9158 raise $top
9159 $top.c itemconf text -text $which
9161 set i [lsearch -exact $fontlist $fontparam(family)]
9162 if {$i >= 0} {
9163 $top.f.fam selection set $i
9164 $top.f.fam see $i
9168 proc centertext {w} {
9169 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9172 proc fontok {} {
9173 global fontparam fontpref prefstop
9175 set f $fontparam(font)
9176 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9177 if {$fontparam(weight) eq "bold"} {
9178 lappend fontpref($f) "bold"
9180 if {$fontparam(slant) eq "italic"} {
9181 lappend fontpref($f) "italic"
9183 set w $prefstop.$f
9184 $w conf -text $fontparam(family) -font $fontpref($f)
9186 fontcan
9189 proc fontcan {} {
9190 global fonttop fontparam
9192 if {[info exists fonttop]} {
9193 catch {destroy $fonttop}
9194 catch {font delete sample}
9195 unset fonttop
9196 unset fontparam
9200 proc selfontfam {} {
9201 global fonttop fontparam
9203 set i [$fonttop.f.fam curselection]
9204 if {$i ne {}} {
9205 set fontparam(family) [$fonttop.f.fam get $i]
9209 proc chg_fontparam {v sub op} {
9210 global fontparam
9212 font config sample -$sub $fontparam($sub)
9215 proc doprefs {} {
9216 global maxwidth maxgraphpct
9217 global oldprefs prefstop showneartags showlocalchanges
9218 global bgcolor fgcolor ctext diffcolors selectbgcolor
9219 global tabstop limitdiffs autoselect extdifftool
9221 set top .gitkprefs
9222 set prefstop $top
9223 if {[winfo exists $top]} {
9224 raise $top
9225 return
9227 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9228 limitdiffs tabstop} {
9229 set oldprefs($v) [set $v]
9231 toplevel $top
9232 wm title $top [mc "Gitk preferences"]
9233 label $top.ldisp -text [mc "Commit list display options"]
9234 grid $top.ldisp - -sticky w -pady 10
9235 label $top.spacer -text " "
9236 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9237 -font optionfont
9238 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9239 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9240 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9241 -font optionfont
9242 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9243 grid x $top.maxpctl $top.maxpct -sticky w
9244 frame $top.showlocal
9245 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9246 checkbutton $top.showlocal.b -variable showlocalchanges
9247 pack $top.showlocal.b $top.showlocal.l -side left
9248 grid x $top.showlocal -sticky w
9249 frame $top.autoselect
9250 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9251 checkbutton $top.autoselect.b -variable autoselect
9252 pack $top.autoselect.b $top.autoselect.l -side left
9253 grid x $top.autoselect -sticky w
9255 label $top.ddisp -text [mc "Diff display options"]
9256 grid $top.ddisp - -sticky w -pady 10
9257 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9258 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9259 grid x $top.tabstopl $top.tabstop -sticky w
9260 frame $top.ntag
9261 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9262 checkbutton $top.ntag.b -variable showneartags
9263 pack $top.ntag.b $top.ntag.l -side left
9264 grid x $top.ntag -sticky w
9265 frame $top.ldiff
9266 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9267 checkbutton $top.ldiff.b -variable limitdiffs
9268 pack $top.ldiff.b $top.ldiff.l -side left
9269 grid x $top.ldiff -sticky w
9271 entry $top.extdifft -textvariable extdifftool
9272 frame $top.extdifff
9273 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9274 -padx 10
9275 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9276 -command choose_extdiff
9277 pack $top.extdifff.l $top.extdifff.b -side left
9278 grid x $top.extdifff $top.extdifft -sticky w
9280 label $top.cdisp -text [mc "Colors: press to choose"]
9281 grid $top.cdisp - -sticky w -pady 10
9282 label $top.bg -padx 40 -relief sunk -background $bgcolor
9283 button $top.bgbut -text [mc "Background"] -font optionfont \
9284 -command [list choosecolor bgcolor {} $top.bg background setbg]
9285 grid x $top.bgbut $top.bg -sticky w
9286 label $top.fg -padx 40 -relief sunk -background $fgcolor
9287 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9288 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9289 grid x $top.fgbut $top.fg -sticky w
9290 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9291 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9292 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9293 [list $ctext tag conf d0 -foreground]]
9294 grid x $top.diffoldbut $top.diffold -sticky w
9295 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9296 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9297 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9298 [list $ctext tag conf d1 -foreground]]
9299 grid x $top.diffnewbut $top.diffnew -sticky w
9300 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9301 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9302 -command [list choosecolor diffcolors 2 $top.hunksep \
9303 "diff hunk header" \
9304 [list $ctext tag conf hunksep -foreground]]
9305 grid x $top.hunksepbut $top.hunksep -sticky w
9306 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9307 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9308 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9309 grid x $top.selbgbut $top.selbgsep -sticky w
9311 label $top.cfont -text [mc "Fonts: press to choose"]
9312 grid $top.cfont - -sticky w -pady 10
9313 mkfontdisp mainfont $top [mc "Main font"]
9314 mkfontdisp textfont $top [mc "Diff display font"]
9315 mkfontdisp uifont $top [mc "User interface font"]
9317 frame $top.buts
9318 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9319 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9320 grid $top.buts.ok $top.buts.can
9321 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9322 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9323 grid $top.buts - - -pady 10 -sticky ew
9324 bind $top <Visibility> "focus $top.buts.ok"
9327 proc choose_extdiff {} {
9328 global extdifftool
9330 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9331 if {$prog ne {}} {
9332 set extdifftool $prog
9336 proc choosecolor {v vi w x cmd} {
9337 global $v
9339 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9340 -title [mc "Gitk: choose color for %s" $x]]
9341 if {$c eq {}} return
9342 $w conf -background $c
9343 lset $v $vi $c
9344 eval $cmd $c
9347 proc setselbg {c} {
9348 global bglist cflist
9349 foreach w $bglist {
9350 $w configure -selectbackground $c
9352 $cflist tag configure highlight \
9353 -background [$cflist cget -selectbackground]
9354 allcanvs itemconf secsel -fill $c
9357 proc setbg {c} {
9358 global bglist
9360 foreach w $bglist {
9361 $w conf -background $c
9365 proc setfg {c} {
9366 global fglist canv
9368 foreach w $fglist {
9369 $w conf -foreground $c
9371 allcanvs itemconf text -fill $c
9372 $canv itemconf circle -outline $c
9375 proc prefscan {} {
9376 global oldprefs prefstop
9378 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9379 limitdiffs tabstop} {
9380 global $v
9381 set $v $oldprefs($v)
9383 catch {destroy $prefstop}
9384 unset prefstop
9385 fontcan
9388 proc prefsok {} {
9389 global maxwidth maxgraphpct
9390 global oldprefs prefstop showneartags showlocalchanges
9391 global fontpref mainfont textfont uifont
9392 global limitdiffs treediffs
9394 catch {destroy $prefstop}
9395 unset prefstop
9396 fontcan
9397 set fontchanged 0
9398 if {$mainfont ne $fontpref(mainfont)} {
9399 set mainfont $fontpref(mainfont)
9400 parsefont mainfont $mainfont
9401 eval font configure mainfont [fontflags mainfont]
9402 eval font configure mainfontbold [fontflags mainfont 1]
9403 setcoords
9404 set fontchanged 1
9406 if {$textfont ne $fontpref(textfont)} {
9407 set textfont $fontpref(textfont)
9408 parsefont textfont $textfont
9409 eval font configure textfont [fontflags textfont]
9410 eval font configure textfontbold [fontflags textfont 1]
9412 if {$uifont ne $fontpref(uifont)} {
9413 set uifont $fontpref(uifont)
9414 parsefont uifont $uifont
9415 eval font configure uifont [fontflags uifont]
9417 settabs
9418 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9419 if {$showlocalchanges} {
9420 doshowlocalchanges
9421 } else {
9422 dohidelocalchanges
9425 if {$limitdiffs != $oldprefs(limitdiffs)} {
9426 # treediffs elements are limited by path
9427 catch {unset treediffs}
9429 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9430 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9431 redisplay
9432 } elseif {$showneartags != $oldprefs(showneartags) ||
9433 $limitdiffs != $oldprefs(limitdiffs)} {
9434 reselectline
9438 proc formatdate {d} {
9439 global datetimeformat
9440 if {$d ne {}} {
9441 set d [clock format $d -format $datetimeformat]
9443 return $d
9446 # This list of encoding names and aliases is distilled from
9447 # http://www.iana.org/assignments/character-sets.
9448 # Not all of them are supported by Tcl.
9449 set encoding_aliases {
9450 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9451 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9452 { ISO-10646-UTF-1 csISO10646UTF1 }
9453 { ISO_646.basic:1983 ref csISO646basic1983 }
9454 { INVARIANT csINVARIANT }
9455 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9456 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9457 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9458 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9459 { NATS-DANO iso-ir-9-1 csNATSDANO }
9460 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9461 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9462 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9463 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9464 { ISO-2022-KR csISO2022KR }
9465 { EUC-KR csEUCKR }
9466 { ISO-2022-JP csISO2022JP }
9467 { ISO-2022-JP-2 csISO2022JP2 }
9468 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9469 csISO13JISC6220jp }
9470 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9471 { IT iso-ir-15 ISO646-IT csISO15Italian }
9472 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9473 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9474 { greek7-old iso-ir-18 csISO18Greek7Old }
9475 { latin-greek iso-ir-19 csISO19LatinGreek }
9476 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9477 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9478 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9479 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9480 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9481 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9482 { INIS iso-ir-49 csISO49INIS }
9483 { INIS-8 iso-ir-50 csISO50INIS8 }
9484 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9485 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9486 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9487 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9488 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9489 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9490 csISO60Norwegian1 }
9491 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9492 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9493 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9494 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9495 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9496 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9497 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9498 { greek7 iso-ir-88 csISO88Greek7 }
9499 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9500 { iso-ir-90 csISO90 }
9501 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9502 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9503 csISO92JISC62991984b }
9504 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9505 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9506 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9507 csISO95JIS62291984handadd }
9508 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9509 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9510 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9511 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9512 CP819 csISOLatin1 }
9513 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9514 { T.61-7bit iso-ir-102 csISO102T617bit }
9515 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9516 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9517 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9518 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9519 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9520 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9521 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9522 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9523 arabic csISOLatinArabic }
9524 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9525 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9526 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9527 greek greek8 csISOLatinGreek }
9528 { T.101-G2 iso-ir-128 csISO128T101G2 }
9529 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9530 csISOLatinHebrew }
9531 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9532 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9533 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9534 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9535 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9536 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9537 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9538 csISOLatinCyrillic }
9539 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9540 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9541 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9542 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9543 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9544 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9545 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9546 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9547 { ISO_10367-box iso-ir-155 csISO10367Box }
9548 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9549 { latin-lap lap iso-ir-158 csISO158Lap }
9550 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9551 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9552 { us-dk csUSDK }
9553 { dk-us csDKUS }
9554 { JIS_X0201 X0201 csHalfWidthKatakana }
9555 { KSC5636 ISO646-KR csKSC5636 }
9556 { ISO-10646-UCS-2 csUnicode }
9557 { ISO-10646-UCS-4 csUCS4 }
9558 { DEC-MCS dec csDECMCS }
9559 { hp-roman8 roman8 r8 csHPRoman8 }
9560 { macintosh mac csMacintosh }
9561 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9562 csIBM037 }
9563 { IBM038 EBCDIC-INT cp038 csIBM038 }
9564 { IBM273 CP273 csIBM273 }
9565 { IBM274 EBCDIC-BE CP274 csIBM274 }
9566 { IBM275 EBCDIC-BR cp275 csIBM275 }
9567 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9568 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9569 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9570 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9571 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9572 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9573 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9574 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9575 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9576 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9577 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9578 { IBM437 cp437 437 csPC8CodePage437 }
9579 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9580 { IBM775 cp775 csPC775Baltic }
9581 { IBM850 cp850 850 csPC850Multilingual }
9582 { IBM851 cp851 851 csIBM851 }
9583 { IBM852 cp852 852 csPCp852 }
9584 { IBM855 cp855 855 csIBM855 }
9585 { IBM857 cp857 857 csIBM857 }
9586 { IBM860 cp860 860 csIBM860 }
9587 { IBM861 cp861 861 cp-is csIBM861 }
9588 { IBM862 cp862 862 csPC862LatinHebrew }
9589 { IBM863 cp863 863 csIBM863 }
9590 { IBM864 cp864 csIBM864 }
9591 { IBM865 cp865 865 csIBM865 }
9592 { IBM866 cp866 866 csIBM866 }
9593 { IBM868 CP868 cp-ar csIBM868 }
9594 { IBM869 cp869 869 cp-gr csIBM869 }
9595 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9596 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9597 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9598 { IBM891 cp891 csIBM891 }
9599 { IBM903 cp903 csIBM903 }
9600 { IBM904 cp904 904 csIBBM904 }
9601 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9602 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9603 { IBM1026 CP1026 csIBM1026 }
9604 { EBCDIC-AT-DE csIBMEBCDICATDE }
9605 { EBCDIC-AT-DE-A csEBCDICATDEA }
9606 { EBCDIC-CA-FR csEBCDICCAFR }
9607 { EBCDIC-DK-NO csEBCDICDKNO }
9608 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9609 { EBCDIC-FI-SE csEBCDICFISE }
9610 { EBCDIC-FI-SE-A csEBCDICFISEA }
9611 { EBCDIC-FR csEBCDICFR }
9612 { EBCDIC-IT csEBCDICIT }
9613 { EBCDIC-PT csEBCDICPT }
9614 { EBCDIC-ES csEBCDICES }
9615 { EBCDIC-ES-A csEBCDICESA }
9616 { EBCDIC-ES-S csEBCDICESS }
9617 { EBCDIC-UK csEBCDICUK }
9618 { EBCDIC-US csEBCDICUS }
9619 { UNKNOWN-8BIT csUnknown8BiT }
9620 { MNEMONIC csMnemonic }
9621 { MNEM csMnem }
9622 { VISCII csVISCII }
9623 { VIQR csVIQR }
9624 { KOI8-R csKOI8R }
9625 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9626 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9627 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9628 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9629 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9630 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9631 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9632 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9633 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9634 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9635 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9636 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9637 { IBM1047 IBM-1047 }
9638 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9639 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9640 { UNICODE-1-1 csUnicode11 }
9641 { CESU-8 csCESU-8 }
9642 { BOCU-1 csBOCU-1 }
9643 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9644 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9645 l8 }
9646 { ISO-8859-15 ISO_8859-15 Latin-9 }
9647 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9648 { GBK CP936 MS936 windows-936 }
9649 { JIS_Encoding csJISEncoding }
9650 { Shift_JIS MS_Kanji csShiftJIS }
9651 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9652 EUC-JP }
9653 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9654 { ISO-10646-UCS-Basic csUnicodeASCII }
9655 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9656 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9657 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9658 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9659 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9660 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9661 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9662 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9663 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9664 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9665 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9666 { Ventura-US csVenturaUS }
9667 { Ventura-International csVenturaInternational }
9668 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9669 { PC8-Turkish csPC8Turkish }
9670 { IBM-Symbols csIBMSymbols }
9671 { IBM-Thai csIBMThai }
9672 { HP-Legal csHPLegal }
9673 { HP-Pi-font csHPPiFont }
9674 { HP-Math8 csHPMath8 }
9675 { Adobe-Symbol-Encoding csHPPSMath }
9676 { HP-DeskTop csHPDesktop }
9677 { Ventura-Math csVenturaMath }
9678 { Microsoft-Publishing csMicrosoftPublishing }
9679 { Windows-31J csWindows31J }
9680 { GB2312 csGB2312 }
9681 { Big5 csBig5 }
9684 proc tcl_encoding {enc} {
9685 global encoding_aliases
9686 set names [encoding names]
9687 set lcnames [string tolower $names]
9688 set enc [string tolower $enc]
9689 set i [lsearch -exact $lcnames $enc]
9690 if {$i < 0} {
9691 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9692 if {[regsub {^iso[-_]} $enc iso encx]} {
9693 set i [lsearch -exact $lcnames $encx]
9696 if {$i < 0} {
9697 foreach l $encoding_aliases {
9698 set ll [string tolower $l]
9699 if {[lsearch -exact $ll $enc] < 0} continue
9700 # look through the aliases for one that tcl knows about
9701 foreach e $ll {
9702 set i [lsearch -exact $lcnames $e]
9703 if {$i < 0} {
9704 if {[regsub {^iso[-_]} $e iso ex]} {
9705 set i [lsearch -exact $lcnames $ex]
9708 if {$i >= 0} break
9710 break
9713 if {$i >= 0} {
9714 return [lindex $names $i]
9716 return {}
9719 # First check that Tcl/Tk is recent enough
9720 if {[catch {package require Tk 8.4} err]} {
9721 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9722 Gitk requires at least Tcl/Tk 8.4."]
9723 exit 1
9726 # defaults...
9727 set wrcomcmd "git diff-tree --stdin -p --pretty"
9729 set gitencoding {}
9730 catch {
9731 set gitencoding [exec git config --get i18n.commitencoding]
9733 if {$gitencoding == ""} {
9734 set gitencoding "utf-8"
9736 set tclencoding [tcl_encoding $gitencoding]
9737 if {$tclencoding == {}} {
9738 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9741 set mainfont {Helvetica 9}
9742 set textfont {Courier 9}
9743 set uifont {Helvetica 9 bold}
9744 set tabstop 8
9745 set findmergefiles 0
9746 set maxgraphpct 50
9747 set maxwidth 16
9748 set revlistorder 0
9749 set fastdate 0
9750 set uparrowlen 5
9751 set downarrowlen 5
9752 set mingaplen 100
9753 set cmitmode "patch"
9754 set wrapcomment "none"
9755 set showneartags 1
9756 set maxrefs 20
9757 set maxlinelen 200
9758 set showlocalchanges 1
9759 set limitdiffs 1
9760 set datetimeformat "%Y-%m-%d %H:%M:%S"
9761 set autoselect 1
9763 set extdifftool "meld"
9765 set colors {green red blue magenta darkgrey brown orange}
9766 set bgcolor white
9767 set fgcolor black
9768 set diffcolors {red "#00a000" blue}
9769 set diffcontext 3
9770 set ignorespace 0
9771 set selectbgcolor gray85
9773 set circlecolors {white blue gray blue blue}
9775 ## For msgcat loading, first locate the installation location.
9776 if { [info exists ::env(GITK_MSGSDIR)] } {
9777 ## Msgsdir was manually set in the environment.
9778 set gitk_msgsdir $::env(GITK_MSGSDIR)
9779 } else {
9780 ## Let's guess the prefix from argv0.
9781 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
9782 set gitk_libdir [file join $gitk_prefix share gitk lib]
9783 set gitk_msgsdir [file join $gitk_libdir msgs]
9784 unset gitk_prefix
9787 ## Internationalization (i18n) through msgcat and gettext. See
9788 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
9789 package require msgcat
9790 namespace import ::msgcat::mc
9791 ## And eventually load the actual message catalog
9792 ::msgcat::mcload $gitk_msgsdir
9794 catch {source ~/.gitk}
9796 font create optionfont -family sans-serif -size -12
9798 parsefont mainfont $mainfont
9799 eval font create mainfont [fontflags mainfont]
9800 eval font create mainfontbold [fontflags mainfont 1]
9802 parsefont textfont $textfont
9803 eval font create textfont [fontflags textfont]
9804 eval font create textfontbold [fontflags textfont 1]
9806 parsefont uifont $uifont
9807 eval font create uifont [fontflags uifont]
9809 setoptions
9811 # check that we can find a .git directory somewhere...
9812 if {[catch {set gitdir [gitdir]}]} {
9813 show_error {} . [mc "Cannot find a git repository here."]
9814 exit 1
9816 if {![file isdirectory $gitdir]} {
9817 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
9818 exit 1
9821 set revtreeargs {}
9822 set cmdline_files {}
9823 set i 0
9824 set revtreeargscmd {}
9825 foreach arg $argv {
9826 switch -glob -- $arg {
9827 "" { }
9828 "--" {
9829 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9830 break
9832 "--argscmd=*" {
9833 set revtreeargscmd [string range $arg 10 end]
9835 default {
9836 lappend revtreeargs $arg
9839 incr i
9842 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9843 # no -- on command line, but some arguments (other than --argscmd)
9844 if {[catch {
9845 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9846 set cmdline_files [split $f "\n"]
9847 set n [llength $cmdline_files]
9848 set revtreeargs [lrange $revtreeargs 0 end-$n]
9849 # Unfortunately git rev-parse doesn't produce an error when
9850 # something is both a revision and a filename. To be consistent
9851 # with git log and git rev-list, check revtreeargs for filenames.
9852 foreach arg $revtreeargs {
9853 if {[file exists $arg]} {
9854 show_error {} . [mc "Ambiguous argument '%s': both revision\
9855 and filename" $arg]
9856 exit 1
9859 } err]} {
9860 # unfortunately we get both stdout and stderr in $err,
9861 # so look for "fatal:".
9862 set i [string first "fatal:" $err]
9863 if {$i > 0} {
9864 set err [string range $err [expr {$i + 6}] end]
9866 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
9867 exit 1
9871 set nullid "0000000000000000000000000000000000000000"
9872 set nullid2 "0000000000000000000000000000000000000001"
9873 set nullfile "/dev/null"
9875 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9877 set runq {}
9878 set history {}
9879 set historyindex 0
9880 set fh_serial 0
9881 set nhl_names {}
9882 set highlight_paths {}
9883 set findpattern {}
9884 set searchdirn -forwards
9885 set boldrows {}
9886 set boldnamerows {}
9887 set diffelide {0 0}
9888 set markingmatches 0
9889 set linkentercount 0
9890 set need_redisplay 0
9891 set nrows_drawn 0
9892 set firsttabstop 0
9894 set nextviewnum 1
9895 set curview 0
9896 set selectedview 0
9897 set selectedhlview [mc "None"]
9898 set highlight_related [mc "None"]
9899 set highlight_files {}
9900 set viewfiles(0) {}
9901 set viewperm(0) 0
9902 set viewargs(0) {}
9903 set viewargscmd(0) {}
9905 set selectedline {}
9906 set numcommits 0
9907 set loginstance 0
9908 set cmdlineok 0
9909 set stopped 0
9910 set stuffsaved 0
9911 set patchnum 0
9912 set lserial 0
9913 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
9914 setcoords
9915 makewindow
9916 # wait for the window to become visible
9917 tkwait visibility .
9918 wm title . "[file tail $argv0]: [file tail [pwd]]"
9919 readrefs
9921 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
9922 # create a view for the files/dirs specified on the command line
9923 set curview 1
9924 set selectedview 1
9925 set nextviewnum 2
9926 set viewname(1) [mc "Command line"]
9927 set viewfiles(1) $cmdline_files
9928 set viewargs(1) $revtreeargs
9929 set viewargscmd(1) $revtreeargscmd
9930 set viewperm(1) 0
9931 set vdatemode(1) 0
9932 addviewmenu 1
9933 .bar.view entryconf [mc "Edit view..."] -state normal
9934 .bar.view entryconf [mc "Delete view"] -state normal
9937 if {[info exists permviews]} {
9938 foreach v $permviews {
9939 set n $nextviewnum
9940 incr nextviewnum
9941 set viewname($n) [lindex $v 0]
9942 set viewfiles($n) [lindex $v 1]
9943 set viewargs($n) [lindex $v 2]
9944 set viewargscmd($n) [lindex $v 3]
9945 set viewperm($n) 1
9946 addviewmenu $n
9949 focus -force .
9950 getcommits