Merge branch 'master' of git://repo.or.cz/alt-git
[git/mingw.git] / gitk-git / gitk
blobfb5220cc592e664c92e7b398c18b7bf3263e27f9
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright © 2005-2008 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25 global isonrunq runq currunq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {} && ![info exists currunq]} {
30 after idle dorunq
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
41 global runq currunq
43 fileevent $fd readable {}
44 if {$runq eq {} && ![info exists currunq]} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc nukefile {fd} {
51 global runq
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
56 } else {
57 incr i
62 proc dorunq {} {
63 global isonrunq runq currunq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set currunq [lindex $runq 0]
71 set runq [lrange $runq 1 end]
72 set repeat [eval $script]
73 unset currunq
74 set t1 [clock clicks -milliseconds]
75 set t [expr {$t1 - $t0}]
76 if {$repeat ne {} && $repeat} {
77 if {$fd eq {} || $repeat == 2} {
78 # script returns 1 if it wants to be readded
79 # file readers return 2 if they could do more straight away
80 lappend runq [list $fd $script]
81 } else {
82 fileevent $fd readable [list filereadable $fd $script]
84 } elseif {$fd eq {}} {
85 unset isonrunq($script)
87 set t0 $t1
88 if {$t1 - $tstart >= 80} break
90 if {$runq ne {}} {
91 after idle dorunq
95 proc reg_instance {fd} {
96 global commfd leftover loginstance
98 set i [incr loginstance]
99 set commfd($i) $fd
100 set leftover($i) {}
101 return $i
104 proc unmerged_files {files} {
105 global nr_unmerged
107 # find the list of unmerged files
108 set mlist {}
109 set nr_unmerged 0
110 if {[catch {
111 set fd [open "| git ls-files -u" r]
112 } err]} {
113 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
114 exit 1
116 while {[gets $fd line] >= 0} {
117 set i [string first "\t" $line]
118 if {$i < 0} continue
119 set fname [string range $line [expr {$i+1}] end]
120 if {[lsearch -exact $mlist $fname] >= 0} continue
121 incr nr_unmerged
122 if {$files eq {} || [path_filter $files $fname]} {
123 lappend mlist $fname
126 catch {close $fd}
127 return $mlist
130 proc parseviewargs {n arglist} {
131 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
133 set vdatemode($n) 0
134 set vmergeonly($n) 0
135 set glflags {}
136 set diffargs {}
137 set nextisval 0
138 set revargs {}
139 set origargs $arglist
140 set allknown 1
141 set filtered 0
142 set i -1
143 foreach arg $arglist {
144 incr i
145 if {$nextisval} {
146 lappend glflags $arg
147 set nextisval 0
148 continue
150 switch -glob -- $arg {
151 "-d" -
152 "--date-order" {
153 set vdatemode($n) 1
154 # remove from origargs in case we hit an unknown option
155 set origargs [lreplace $origargs $i $i]
156 incr i -1
158 # These request or affect diff output, which we don't want.
159 # Some could be used to set our defaults for diff display.
160 "-[puabwcrRBMC]" -
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 lappend diffargs $arg
168 # These cause our parsing of git log's output to fail, or else
169 # they're options we want to set ourselves, so ignore them.
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
178 # These are harmless, and some are even useful
179 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
180 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
181 "--full-history" - "--dense" - "--sparse" -
182 "--follow" - "--left-right" - "--encoding=*" {
183 lappend glflags $arg
185 # These mean that we get a subset of the commits
186 "--diff-filter=*" - "--no-merges" - "--unpacked" -
187 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
188 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
189 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
190 "--remove-empty" - "--first-parent" - "--cherry-pick" -
191 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
192 set filtered 1
193 lappend glflags $arg
195 # This appears to be the only one that has a value as a
196 # separate word following it
197 "-n" {
198 set filtered 1
199 set nextisval 1
200 lappend glflags $arg
202 "--not" {
203 set notflag [expr {!$notflag}]
204 lappend revargs $arg
206 "--all" {
207 lappend revargs $arg
209 "--merge" {
210 set vmergeonly($n) 1
211 # git rev-parse doesn't understand --merge
212 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
214 # Other flag arguments including -<n>
215 "-*" {
216 if {[string is digit -strict [string range $arg 1 end]]} {
217 set filtered 1
218 } else {
219 # a flag argument that we don't recognize;
220 # that means we can't optimize
221 set allknown 0
223 lappend glflags $arg
225 # Non-flag arguments specify commits or ranges of commits
226 default {
227 if {[string match "*...*" $arg]} {
228 lappend revargs --gitk-symmetric-diff-marker
230 lappend revargs $arg
234 set vdflags($n) $diffargs
235 set vflags($n) $glflags
236 set vrevs($n) $revargs
237 set vfiltered($n) $filtered
238 set vorigargs($n) $origargs
239 return $allknown
242 proc parseviewrevs {view revs} {
243 global vposids vnegids
245 if {$revs eq {}} {
246 set revs HEAD
248 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
249 # we get stdout followed by stderr in $err
250 # for an unknown rev, git rev-parse echoes it and then errors out
251 set errlines [split $err "\n"]
252 set badrev {}
253 for {set l 0} {$l < [llength $errlines]} {incr l} {
254 set line [lindex $errlines $l]
255 if {!([string length $line] == 40 && [string is xdigit $line])} {
256 if {[string match "fatal:*" $line]} {
257 if {[string match "fatal: ambiguous argument*" $line]
258 && $badrev ne {}} {
259 if {[llength $badrev] == 1} {
260 set err "unknown revision $badrev"
261 } else {
262 set err "unknown revisions: [join $badrev ", "]"
264 } else {
265 set err [join [lrange $errlines $l end] "\n"]
267 break
269 lappend badrev $line
272 error_popup "Error parsing revisions: $err"
273 return {}
275 set ret {}
276 set pos {}
277 set neg {}
278 set sdm 0
279 foreach id [split $ids "\n"] {
280 if {$id eq "--gitk-symmetric-diff-marker"} {
281 set sdm 4
282 } elseif {[string match "^*" $id]} {
283 if {$sdm != 1} {
284 lappend ret $id
285 if {$sdm == 3} {
286 set sdm 0
289 lappend neg [string range $id 1 end]
290 } else {
291 if {$sdm != 2} {
292 lappend ret $id
293 } else {
294 lset ret end [lindex $ret end]...$id
296 lappend pos $id
298 incr sdm -1
300 set vposids($view) $pos
301 set vnegids($view) $neg
302 return $ret
305 # Start off a git log process and arrange to read its output
306 proc start_rev_list {view} {
307 global startmsecs commitidx viewcomplete curview
308 global tclencoding
309 global viewargs viewargscmd viewfiles vfilelimit
310 global showlocalchanges commitinterest
311 global viewactive viewinstances vmergeonly
312 global mainheadid
313 global vcanopt vflags vrevs vorigargs
315 set startmsecs [clock clicks -milliseconds]
316 set commitidx($view) 0
317 # these are set this way for the error exits
318 set viewcomplete($view) 1
319 set viewactive($view) 0
320 varcinit $view
322 set args $viewargs($view)
323 if {$viewargscmd($view) ne {}} {
324 if {[catch {
325 set str [exec sh -c $viewargscmd($view)]
326 } err]} {
327 error_popup "Error executing --argscmd command: $err"
328 return 0
330 set args [concat $args [split $str "\n"]]
332 set vcanopt($view) [parseviewargs $view $args]
334 set files $viewfiles($view)
335 if {$vmergeonly($view)} {
336 set files [unmerged_files $files]
337 if {$files eq {}} {
338 global nr_unmerged
339 if {$nr_unmerged == 0} {
340 error_popup [mc "No files selected: --merge specified but\
341 no files are unmerged."]
342 } else {
343 error_popup [mc "No files selected: --merge specified but\
344 no unmerged files are within file limit."]
346 return 0
349 set vfilelimit($view) $files
351 if {$vcanopt($view)} {
352 set revs [parseviewrevs $view $vrevs($view)]
353 if {$revs eq {}} {
354 return 0
356 set args [concat $vflags($view) $revs]
357 } else {
358 set args $vorigargs($view)
361 if {[catch {
362 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
363 --boundary $args "--" $files] r]
364 } err]} {
365 error_popup "[mc "Error executing git log:"] $err"
366 return 0
368 set i [reg_instance $fd]
369 set viewinstances($view) [list $i]
370 if {$showlocalchanges && $mainheadid ne {}} {
371 lappend commitinterest($mainheadid) {dodiffindex}
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
381 return 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
388 catch {
389 set pid [pid $fd]
391 if {$::tcl_platform(platform) eq {windows}} {
392 exec kill -f $pid
393 } else {
394 exec kill $pid
397 catch {close $fd}
398 nukefile $fd
399 unset commfd($inst)
400 unset leftover($inst)
403 proc stop_backends {} {
404 global commfd
406 foreach inst [array names commfd] {
407 stop_instance $inst
411 proc stop_rev_list {view} {
412 global viewinstances
414 foreach inst $viewinstances($view) {
415 stop_instance $inst
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid
423 if {$selid ne {}} {
424 set pending_select $selid
425 } else {
426 set pending_select $mainheadid
430 proc getcommits {selid} {
431 global canv curview need_redisplay viewactive
433 initlayout
434 if {[start_rev_list $curview]} {
435 reset_pending_select $selid
436 show_status [mc "Reading commits..."]
437 set need_redisplay 1
438 } else {
439 show_status [mc "No commits selected"]
443 proc updatecommits {} {
444 global curview vcanopt vorigargs vfilelimit viewinstances
445 global viewactive viewcomplete tclencoding
446 global startmsecs showneartags showlocalchanges
447 global mainheadid pending_select
448 global isworktree
449 global varcid vposids vnegids vflags vrevs
451 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
452 set oldmainid $mainheadid
453 rereadrefs
454 if {$showlocalchanges} {
455 if {$mainheadid ne $oldmainid} {
456 dohidelocalchanges
458 if {[commitinview $mainheadid $curview]} {
459 dodiffindex
462 set view $curview
463 if {$vcanopt($view)} {
464 set oldpos $vposids($view)
465 set oldneg $vnegids($view)
466 set revs [parseviewrevs $view $vrevs($view)]
467 if {$revs eq {}} {
468 return
470 # note: getting the delta when negative refs change is hard,
471 # and could require multiple git log invocations, so in that
472 # case we ask git log for all the commits (not just the delta)
473 if {$oldneg eq $vnegids($view)} {
474 set newrevs {}
475 set npos 0
476 # take out positive refs that we asked for before or
477 # that we have already seen
478 foreach rev $revs {
479 if {[string length $rev] == 40} {
480 if {[lsearch -exact $oldpos $rev] < 0
481 && ![info exists varcid($view,$rev)]} {
482 lappend newrevs $rev
483 incr npos
485 } else {
486 lappend $newrevs $rev
489 if {$npos == 0} return
490 set revs $newrevs
491 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
493 set args [concat $vflags($view) $revs --not $oldpos]
494 } else {
495 set args $vorigargs($view)
497 if {[catch {
498 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
499 --boundary $args "--" $vfilelimit($view)] r]
500 } err]} {
501 error_popup "Error executing git log: $err"
502 return
504 if {$viewactive($view) == 0} {
505 set startmsecs [clock clicks -milliseconds]
507 set i [reg_instance $fd]
508 lappend viewinstances($view) $i
509 fconfigure $fd -blocking 0 -translation lf -eofchar {}
510 if {$tclencoding != {}} {
511 fconfigure $fd -encoding $tclencoding
513 filerun $fd [list getcommitlines $fd $i $view 1]
514 incr viewactive($view)
515 set viewcomplete($view) 0
516 reset_pending_select {}
517 nowbusy $view "Reading"
518 if {$showneartags} {
519 getallcommits
523 proc reloadcommits {} {
524 global curview viewcomplete selectedline currentid thickerline
525 global showneartags treediffs commitinterest cached_commitrow
526 global targetid
528 set selid {}
529 if {$selectedline ne {}} {
530 set selid $currentid
533 if {!$viewcomplete($curview)} {
534 stop_rev_list $curview
536 resetvarcs $curview
537 set selectedline {}
538 catch {unset currentid}
539 catch {unset thickerline}
540 catch {unset treediffs}
541 readrefs
542 changedrefs
543 if {$showneartags} {
544 getallcommits
546 clear_display
547 catch {unset commitinterest}
548 catch {unset cached_commitrow}
549 catch {unset targetid}
550 setcanvscroll
551 getcommits $selid
552 return 0
555 # This makes a string representation of a positive integer which
556 # sorts as a string in numerical order
557 proc strrep {n} {
558 if {$n < 16} {
559 return [format "%x" $n]
560 } elseif {$n < 256} {
561 return [format "x%.2x" $n]
562 } elseif {$n < 65536} {
563 return [format "y%.4x" $n]
565 return [format "z%.8x" $n]
568 # Procedures used in reordering commits from git log (without
569 # --topo-order) into the order for display.
571 proc varcinit {view} {
572 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
573 global vtokmod varcmod vrowmod varcix vlastins
575 set varcstart($view) {{}}
576 set vupptr($view) {0}
577 set vdownptr($view) {0}
578 set vleftptr($view) {0}
579 set vbackptr($view) {0}
580 set varctok($view) {{}}
581 set varcrow($view) {{}}
582 set vtokmod($view) {}
583 set varcmod($view) 0
584 set vrowmod($view) 0
585 set varcix($view) {{}}
586 set vlastins($view) {0}
589 proc resetvarcs {view} {
590 global varcid varccommits parents children vseedcount ordertok
592 foreach vid [array names varcid $view,*] {
593 unset varcid($vid)
594 unset children($vid)
595 unset parents($vid)
597 # some commits might have children but haven't been seen yet
598 foreach vid [array names children $view,*] {
599 unset children($vid)
601 foreach va [array names varccommits $view,*] {
602 unset varccommits($va)
604 foreach vd [array names vseedcount $view,*] {
605 unset vseedcount($vd)
607 catch {unset ordertok}
610 # returns a list of the commits with no children
611 proc seeds {v} {
612 global vdownptr vleftptr varcstart
614 set ret {}
615 set a [lindex $vdownptr($v) 0]
616 while {$a != 0} {
617 lappend ret [lindex $varcstart($v) $a]
618 set a [lindex $vleftptr($v) $a]
620 return $ret
623 proc newvarc {view id} {
624 global varcid varctok parents children vdatemode
625 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
626 global commitdata commitinfo vseedcount varccommits vlastins
628 set a [llength $varctok($view)]
629 set vid $view,$id
630 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
631 if {![info exists commitinfo($id)]} {
632 parsecommit $id $commitdata($id) 1
634 set cdate [lindex $commitinfo($id) 4]
635 if {![string is integer -strict $cdate]} {
636 set cdate 0
638 if {![info exists vseedcount($view,$cdate)]} {
639 set vseedcount($view,$cdate) -1
641 set c [incr vseedcount($view,$cdate)]
642 set cdate [expr {$cdate ^ 0xffffffff}]
643 set tok "s[strrep $cdate][strrep $c]"
644 } else {
645 set tok {}
647 set ka 0
648 if {[llength $children($vid)] > 0} {
649 set kid [lindex $children($vid) end]
650 set k $varcid($view,$kid)
651 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
652 set ki $kid
653 set ka $k
654 set tok [lindex $varctok($view) $k]
657 if {$ka != 0} {
658 set i [lsearch -exact $parents($view,$ki) $id]
659 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
660 append tok [strrep $j]
662 set c [lindex $vlastins($view) $ka]
663 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
664 set c $ka
665 set b [lindex $vdownptr($view) $ka]
666 } else {
667 set b [lindex $vleftptr($view) $c]
669 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
670 set c $b
671 set b [lindex $vleftptr($view) $c]
673 if {$c == $ka} {
674 lset vdownptr($view) $ka $a
675 lappend vbackptr($view) 0
676 } else {
677 lset vleftptr($view) $c $a
678 lappend vbackptr($view) $c
680 lset vlastins($view) $ka $a
681 lappend vupptr($view) $ka
682 lappend vleftptr($view) $b
683 if {$b != 0} {
684 lset vbackptr($view) $b $a
686 lappend varctok($view) $tok
687 lappend varcstart($view) $id
688 lappend vdownptr($view) 0
689 lappend varcrow($view) {}
690 lappend varcix($view) {}
691 set varccommits($view,$a) {}
692 lappend vlastins($view) 0
693 return $a
696 proc splitvarc {p v} {
697 global varcid varcstart varccommits varctok
698 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
700 set oa $varcid($v,$p)
701 set ac $varccommits($v,$oa)
702 set i [lsearch -exact $varccommits($v,$oa) $p]
703 if {$i <= 0} return
704 set na [llength $varctok($v)]
705 # "%" sorts before "0"...
706 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
707 lappend varctok($v) $tok
708 lappend varcrow($v) {}
709 lappend varcix($v) {}
710 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
711 set varccommits($v,$na) [lrange $ac $i end]
712 lappend varcstart($v) $p
713 foreach id $varccommits($v,$na) {
714 set varcid($v,$id) $na
716 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
717 lappend vlastins($v) [lindex $vlastins($v) $oa]
718 lset vdownptr($v) $oa $na
719 lset vlastins($v) $oa 0
720 lappend vupptr($v) $oa
721 lappend vleftptr($v) 0
722 lappend vbackptr($v) 0
723 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
724 lset vupptr($v) $b $na
728 proc renumbervarc {a v} {
729 global parents children varctok varcstart varccommits
730 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
732 set t1 [clock clicks -milliseconds]
733 set todo {}
734 set isrelated($a) 1
735 set kidchanged($a) 1
736 set ntot 0
737 while {$a != 0} {
738 if {[info exists isrelated($a)]} {
739 lappend todo $a
740 set id [lindex $varccommits($v,$a) end]
741 foreach p $parents($v,$id) {
742 if {[info exists varcid($v,$p)]} {
743 set isrelated($varcid($v,$p)) 1
747 incr ntot
748 set b [lindex $vdownptr($v) $a]
749 if {$b == 0} {
750 while {$a != 0} {
751 set b [lindex $vleftptr($v) $a]
752 if {$b != 0} break
753 set a [lindex $vupptr($v) $a]
756 set a $b
758 foreach a $todo {
759 if {![info exists kidchanged($a)]} continue
760 set id [lindex $varcstart($v) $a]
761 if {[llength $children($v,$id)] > 1} {
762 set children($v,$id) [lsort -command [list vtokcmp $v] \
763 $children($v,$id)]
765 set oldtok [lindex $varctok($v) $a]
766 if {!$vdatemode($v)} {
767 set tok {}
768 } else {
769 set tok $oldtok
771 set ka 0
772 set kid [last_real_child $v,$id]
773 if {$kid ne {}} {
774 set k $varcid($v,$kid)
775 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
776 set ki $kid
777 set ka $k
778 set tok [lindex $varctok($v) $k]
781 if {$ka != 0} {
782 set i [lsearch -exact $parents($v,$ki) $id]
783 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
784 append tok [strrep $j]
786 if {$tok eq $oldtok} {
787 continue
789 set id [lindex $varccommits($v,$a) end]
790 foreach p $parents($v,$id) {
791 if {[info exists varcid($v,$p)]} {
792 set kidchanged($varcid($v,$p)) 1
793 } else {
794 set sortkids($p) 1
797 lset varctok($v) $a $tok
798 set b [lindex $vupptr($v) $a]
799 if {$b != $ka} {
800 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
801 modify_arc $v $ka
803 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
804 modify_arc $v $b
806 set c [lindex $vbackptr($v) $a]
807 set d [lindex $vleftptr($v) $a]
808 if {$c == 0} {
809 lset vdownptr($v) $b $d
810 } else {
811 lset vleftptr($v) $c $d
813 if {$d != 0} {
814 lset vbackptr($v) $d $c
816 if {[lindex $vlastins($v) $b] == $a} {
817 lset vlastins($v) $b $c
819 lset vupptr($v) $a $ka
820 set c [lindex $vlastins($v) $ka]
821 if {$c == 0 || \
822 [string compare $tok [lindex $varctok($v) $c]] < 0} {
823 set c $ka
824 set b [lindex $vdownptr($v) $ka]
825 } else {
826 set b [lindex $vleftptr($v) $c]
828 while {$b != 0 && \
829 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
830 set c $b
831 set b [lindex $vleftptr($v) $c]
833 if {$c == $ka} {
834 lset vdownptr($v) $ka $a
835 lset vbackptr($v) $a 0
836 } else {
837 lset vleftptr($v) $c $a
838 lset vbackptr($v) $a $c
840 lset vleftptr($v) $a $b
841 if {$b != 0} {
842 lset vbackptr($v) $b $a
844 lset vlastins($v) $ka $a
847 foreach id [array names sortkids] {
848 if {[llength $children($v,$id)] > 1} {
849 set children($v,$id) [lsort -command [list vtokcmp $v] \
850 $children($v,$id)]
853 set t2 [clock clicks -milliseconds]
854 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
857 # Fix up the graph after we have found out that in view $v,
858 # $p (a commit that we have already seen) is actually the parent
859 # of the last commit in arc $a.
860 proc fix_reversal {p a v} {
861 global varcid varcstart varctok vupptr
863 set pa $varcid($v,$p)
864 if {$p ne [lindex $varcstart($v) $pa]} {
865 splitvarc $p $v
866 set pa $varcid($v,$p)
868 # seeds always need to be renumbered
869 if {[lindex $vupptr($v) $pa] == 0 ||
870 [string compare [lindex $varctok($v) $a] \
871 [lindex $varctok($v) $pa]] > 0} {
872 renumbervarc $pa $v
876 proc insertrow {id p v} {
877 global cmitlisted children parents varcid varctok vtokmod
878 global varccommits ordertok commitidx numcommits curview
879 global targetid targetrow
881 readcommit $id
882 set vid $v,$id
883 set cmitlisted($vid) 1
884 set children($vid) {}
885 set parents($vid) [list $p]
886 set a [newvarc $v $id]
887 set varcid($vid) $a
888 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
889 modify_arc $v $a
891 lappend varccommits($v,$a) $id
892 set vp $v,$p
893 if {[llength [lappend children($vp) $id]] > 1} {
894 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
895 catch {unset ordertok}
897 fix_reversal $p $a $v
898 incr commitidx($v)
899 if {$v == $curview} {
900 set numcommits $commitidx($v)
901 setcanvscroll
902 if {[info exists targetid]} {
903 if {![comes_before $targetid $p]} {
904 incr targetrow
910 proc insertfakerow {id p} {
911 global varcid varccommits parents children cmitlisted
912 global commitidx varctok vtokmod targetid targetrow curview numcommits
914 set v $curview
915 set a $varcid($v,$p)
916 set i [lsearch -exact $varccommits($v,$a) $p]
917 if {$i < 0} {
918 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
919 return
921 set children($v,$id) {}
922 set parents($v,$id) [list $p]
923 set varcid($v,$id) $a
924 lappend children($v,$p) $id
925 set cmitlisted($v,$id) 1
926 set numcommits [incr commitidx($v)]
927 # note we deliberately don't update varcstart($v) even if $i == 0
928 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
929 modify_arc $v $a $i
930 if {[info exists targetid]} {
931 if {![comes_before $targetid $p]} {
932 incr targetrow
935 setcanvscroll
936 drawvisible
939 proc removefakerow {id} {
940 global varcid varccommits parents children commitidx
941 global varctok vtokmod cmitlisted currentid selectedline
942 global targetid curview numcommits
944 set v $curview
945 if {[llength $parents($v,$id)] != 1} {
946 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
947 return
949 set p [lindex $parents($v,$id) 0]
950 set a $varcid($v,$id)
951 set i [lsearch -exact $varccommits($v,$a) $id]
952 if {$i < 0} {
953 puts "oops: removefakerow can't find [shortids $id] on arc $a"
954 return
956 unset varcid($v,$id)
957 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
958 unset parents($v,$id)
959 unset children($v,$id)
960 unset cmitlisted($v,$id)
961 set numcommits [incr commitidx($v) -1]
962 set j [lsearch -exact $children($v,$p) $id]
963 if {$j >= 0} {
964 set children($v,$p) [lreplace $children($v,$p) $j $j]
966 modify_arc $v $a $i
967 if {[info exist currentid] && $id eq $currentid} {
968 unset currentid
969 set selectedline {}
971 if {[info exists targetid] && $targetid eq $id} {
972 set targetid $p
974 setcanvscroll
975 drawvisible
978 proc first_real_child {vp} {
979 global children nullid nullid2
981 foreach id $children($vp) {
982 if {$id ne $nullid && $id ne $nullid2} {
983 return $id
986 return {}
989 proc last_real_child {vp} {
990 global children nullid nullid2
992 set kids $children($vp)
993 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
994 set id [lindex $kids $i]
995 if {$id ne $nullid && $id ne $nullid2} {
996 return $id
999 return {}
1002 proc vtokcmp {v a b} {
1003 global varctok varcid
1005 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1006 [lindex $varctok($v) $varcid($v,$b)]]
1009 # This assumes that if lim is not given, the caller has checked that
1010 # arc a's token is less than $vtokmod($v)
1011 proc modify_arc {v a {lim {}}} {
1012 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1014 if {$lim ne {}} {
1015 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1016 if {$c > 0} return
1017 if {$c == 0} {
1018 set r [lindex $varcrow($v) $a]
1019 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1022 set vtokmod($v) [lindex $varctok($v) $a]
1023 set varcmod($v) $a
1024 if {$v == $curview} {
1025 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1026 set a [lindex $vupptr($v) $a]
1027 set lim {}
1029 set r 0
1030 if {$a != 0} {
1031 if {$lim eq {}} {
1032 set lim [llength $varccommits($v,$a)]
1034 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1036 set vrowmod($v) $r
1037 undolayout $r
1041 proc update_arcrows {v} {
1042 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1043 global varcid vrownum varcorder varcix varccommits
1044 global vupptr vdownptr vleftptr varctok
1045 global displayorder parentlist curview cached_commitrow
1047 if {$vrowmod($v) == $commitidx($v)} return
1048 if {$v == $curview} {
1049 if {[llength $displayorder] > $vrowmod($v)} {
1050 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1051 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1053 catch {unset cached_commitrow}
1055 set narctot [expr {[llength $varctok($v)] - 1}]
1056 set a $varcmod($v)
1057 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1058 # go up the tree until we find something that has a row number,
1059 # or we get to a seed
1060 set a [lindex $vupptr($v) $a]
1062 if {$a == 0} {
1063 set a [lindex $vdownptr($v) 0]
1064 if {$a == 0} return
1065 set vrownum($v) {0}
1066 set varcorder($v) [list $a]
1067 lset varcix($v) $a 0
1068 lset varcrow($v) $a 0
1069 set arcn 0
1070 set row 0
1071 } else {
1072 set arcn [lindex $varcix($v) $a]
1073 if {[llength $vrownum($v)] > $arcn + 1} {
1074 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1075 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1077 set row [lindex $varcrow($v) $a]
1079 while {1} {
1080 set p $a
1081 incr row [llength $varccommits($v,$a)]
1082 # go down if possible
1083 set b [lindex $vdownptr($v) $a]
1084 if {$b == 0} {
1085 # if not, go left, or go up until we can go left
1086 while {$a != 0} {
1087 set b [lindex $vleftptr($v) $a]
1088 if {$b != 0} break
1089 set a [lindex $vupptr($v) $a]
1091 if {$a == 0} break
1093 set a $b
1094 incr arcn
1095 lappend vrownum($v) $row
1096 lappend varcorder($v) $a
1097 lset varcix($v) $a $arcn
1098 lset varcrow($v) $a $row
1100 set vtokmod($v) [lindex $varctok($v) $p]
1101 set varcmod($v) $p
1102 set vrowmod($v) $row
1103 if {[info exists currentid]} {
1104 set selectedline [rowofcommit $currentid]
1108 # Test whether view $v contains commit $id
1109 proc commitinview {id v} {
1110 global varcid
1112 return [info exists varcid($v,$id)]
1115 # Return the row number for commit $id in the current view
1116 proc rowofcommit {id} {
1117 global varcid varccommits varcrow curview cached_commitrow
1118 global varctok vtokmod
1120 set v $curview
1121 if {![info exists varcid($v,$id)]} {
1122 puts "oops rowofcommit no arc for [shortids $id]"
1123 return {}
1125 set a $varcid($v,$id)
1126 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1127 update_arcrows $v
1129 if {[info exists cached_commitrow($id)]} {
1130 return $cached_commitrow($id)
1132 set i [lsearch -exact $varccommits($v,$a) $id]
1133 if {$i < 0} {
1134 puts "oops didn't find commit [shortids $id] in arc $a"
1135 return {}
1137 incr i [lindex $varcrow($v) $a]
1138 set cached_commitrow($id) $i
1139 return $i
1142 # Returns 1 if a is on an earlier row than b, otherwise 0
1143 proc comes_before {a b} {
1144 global varcid varctok curview
1146 set v $curview
1147 if {$a eq $b || ![info exists varcid($v,$a)] || \
1148 ![info exists varcid($v,$b)]} {
1149 return 0
1151 if {$varcid($v,$a) != $varcid($v,$b)} {
1152 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1153 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1155 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1158 proc bsearch {l elt} {
1159 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1160 return 0
1162 set lo 0
1163 set hi [llength $l]
1164 while {$hi - $lo > 1} {
1165 set mid [expr {int(($lo + $hi) / 2)}]
1166 set t [lindex $l $mid]
1167 if {$elt < $t} {
1168 set hi $mid
1169 } elseif {$elt > $t} {
1170 set lo $mid
1171 } else {
1172 return $mid
1175 return $lo
1178 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1179 proc make_disporder {start end} {
1180 global vrownum curview commitidx displayorder parentlist
1181 global varccommits varcorder parents vrowmod varcrow
1182 global d_valid_start d_valid_end
1184 if {$end > $vrowmod($curview)} {
1185 update_arcrows $curview
1187 set ai [bsearch $vrownum($curview) $start]
1188 set start [lindex $vrownum($curview) $ai]
1189 set narc [llength $vrownum($curview)]
1190 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1191 set a [lindex $varcorder($curview) $ai]
1192 set l [llength $displayorder]
1193 set al [llength $varccommits($curview,$a)]
1194 if {$l < $r + $al} {
1195 if {$l < $r} {
1196 set pad [ntimes [expr {$r - $l}] {}]
1197 set displayorder [concat $displayorder $pad]
1198 set parentlist [concat $parentlist $pad]
1199 } elseif {$l > $r} {
1200 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1201 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1203 foreach id $varccommits($curview,$a) {
1204 lappend displayorder $id
1205 lappend parentlist $parents($curview,$id)
1207 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1208 set i $r
1209 foreach id $varccommits($curview,$a) {
1210 lset displayorder $i $id
1211 lset parentlist $i $parents($curview,$id)
1212 incr i
1215 incr r $al
1219 proc commitonrow {row} {
1220 global displayorder
1222 set id [lindex $displayorder $row]
1223 if {$id eq {}} {
1224 make_disporder $row [expr {$row + 1}]
1225 set id [lindex $displayorder $row]
1227 return $id
1230 proc closevarcs {v} {
1231 global varctok varccommits varcid parents children
1232 global cmitlisted commitidx commitinterest vtokmod
1234 set missing_parents 0
1235 set scripts {}
1236 set narcs [llength $varctok($v)]
1237 for {set a 1} {$a < $narcs} {incr a} {
1238 set id [lindex $varccommits($v,$a) end]
1239 foreach p $parents($v,$id) {
1240 if {[info exists varcid($v,$p)]} continue
1241 # add p as a new commit
1242 incr missing_parents
1243 set cmitlisted($v,$p) 0
1244 set parents($v,$p) {}
1245 if {[llength $children($v,$p)] == 1 &&
1246 [llength $parents($v,$id)] == 1} {
1247 set b $a
1248 } else {
1249 set b [newvarc $v $p]
1251 set varcid($v,$p) $b
1252 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1253 modify_arc $v $b
1255 lappend varccommits($v,$b) $p
1256 incr commitidx($v)
1257 if {[info exists commitinterest($p)]} {
1258 foreach script $commitinterest($p) {
1259 lappend scripts [string map [list "%I" $p] $script]
1261 unset commitinterest($id)
1265 if {$missing_parents > 0} {
1266 foreach s $scripts {
1267 eval $s
1272 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1273 # Assumes we already have an arc for $rwid.
1274 proc rewrite_commit {v id rwid} {
1275 global children parents varcid varctok vtokmod varccommits
1277 foreach ch $children($v,$id) {
1278 # make $rwid be $ch's parent in place of $id
1279 set i [lsearch -exact $parents($v,$ch) $id]
1280 if {$i < 0} {
1281 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1283 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1284 # add $ch to $rwid's children and sort the list if necessary
1285 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1286 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1287 $children($v,$rwid)]
1289 # fix the graph after joining $id to $rwid
1290 set a $varcid($v,$ch)
1291 fix_reversal $rwid $a $v
1292 # parentlist is wrong for the last element of arc $a
1293 # even if displayorder is right, hence the 3rd arg here
1294 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1298 proc getcommitlines {fd inst view updating} {
1299 global cmitlisted commitinterest leftover
1300 global commitidx commitdata vdatemode
1301 global parents children curview hlview
1302 global idpending ordertok
1303 global varccommits varcid varctok vtokmod vfilelimit
1305 set stuff [read $fd 500000]
1306 # git log doesn't terminate the last commit with a null...
1307 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1308 set stuff "\0"
1310 if {$stuff == {}} {
1311 if {![eof $fd]} {
1312 return 1
1314 global commfd viewcomplete viewactive viewname
1315 global viewinstances
1316 unset commfd($inst)
1317 set i [lsearch -exact $viewinstances($view) $inst]
1318 if {$i >= 0} {
1319 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1321 # set it blocking so we wait for the process to terminate
1322 fconfigure $fd -blocking 1
1323 if {[catch {close $fd} err]} {
1324 set fv {}
1325 if {$view != $curview} {
1326 set fv " for the \"$viewname($view)\" view"
1328 if {[string range $err 0 4] == "usage"} {
1329 set err "Gitk: error reading commits$fv:\
1330 bad arguments to git log."
1331 if {$viewname($view) eq "Command line"} {
1332 append err \
1333 " (Note: arguments to gitk are passed to git log\
1334 to allow selection of commits to be displayed.)"
1336 } else {
1337 set err "Error reading commits$fv: $err"
1339 error_popup $err
1341 if {[incr viewactive($view) -1] <= 0} {
1342 set viewcomplete($view) 1
1343 # Check if we have seen any ids listed as parents that haven't
1344 # appeared in the list
1345 closevarcs $view
1346 notbusy $view
1348 if {$view == $curview} {
1349 run chewcommits
1351 return 0
1353 set start 0
1354 set gotsome 0
1355 set scripts {}
1356 while 1 {
1357 set i [string first "\0" $stuff $start]
1358 if {$i < 0} {
1359 append leftover($inst) [string range $stuff $start end]
1360 break
1362 if {$start == 0} {
1363 set cmit $leftover($inst)
1364 append cmit [string range $stuff 0 [expr {$i - 1}]]
1365 set leftover($inst) {}
1366 } else {
1367 set cmit [string range $stuff $start [expr {$i - 1}]]
1369 set start [expr {$i + 1}]
1370 set j [string first "\n" $cmit]
1371 set ok 0
1372 set listed 1
1373 if {$j >= 0 && [string match "commit *" $cmit]} {
1374 set ids [string range $cmit 7 [expr {$j - 1}]]
1375 if {[string match {[-^<>]*} $ids]} {
1376 switch -- [string index $ids 0] {
1377 "-" {set listed 0}
1378 "^" {set listed 2}
1379 "<" {set listed 3}
1380 ">" {set listed 4}
1382 set ids [string range $ids 1 end]
1384 set ok 1
1385 foreach id $ids {
1386 if {[string length $id] != 40} {
1387 set ok 0
1388 break
1392 if {!$ok} {
1393 set shortcmit $cmit
1394 if {[string length $shortcmit] > 80} {
1395 set shortcmit "[string range $shortcmit 0 80]..."
1397 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1398 exit 1
1400 set id [lindex $ids 0]
1401 set vid $view,$id
1403 if {!$listed && $updating && ![info exists varcid($vid)] &&
1404 $vfilelimit($view) ne {}} {
1405 # git log doesn't rewrite parents for unlisted commits
1406 # when doing path limiting, so work around that here
1407 # by working out the rewritten parent with git rev-list
1408 # and if we already know about it, using the rewritten
1409 # parent as a substitute parent for $id's children.
1410 if {![catch {
1411 set rwid [exec git rev-list --first-parent --max-count=1 \
1412 $id -- $vfilelimit($view)]
1413 }]} {
1414 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1415 # use $rwid in place of $id
1416 rewrite_commit $view $id $rwid
1417 continue
1422 set a 0
1423 if {[info exists varcid($vid)]} {
1424 if {$cmitlisted($vid) || !$listed} continue
1425 set a $varcid($vid)
1427 if {$listed} {
1428 set olds [lrange $ids 1 end]
1429 } else {
1430 set olds {}
1432 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1433 set cmitlisted($vid) $listed
1434 set parents($vid) $olds
1435 if {![info exists children($vid)]} {
1436 set children($vid) {}
1437 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1438 set k [lindex $children($vid) 0]
1439 if {[llength $parents($view,$k)] == 1 &&
1440 (!$vdatemode($view) ||
1441 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1442 set a $varcid($view,$k)
1445 if {$a == 0} {
1446 # new arc
1447 set a [newvarc $view $id]
1449 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1450 modify_arc $view $a
1452 if {![info exists varcid($vid)]} {
1453 set varcid($vid) $a
1454 lappend varccommits($view,$a) $id
1455 incr commitidx($view)
1458 set i 0
1459 foreach p $olds {
1460 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1461 set vp $view,$p
1462 if {[llength [lappend children($vp) $id]] > 1 &&
1463 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1464 set children($vp) [lsort -command [list vtokcmp $view] \
1465 $children($vp)]
1466 catch {unset ordertok}
1468 if {[info exists varcid($view,$p)]} {
1469 fix_reversal $p $a $view
1472 incr i
1475 if {[info exists commitinterest($id)]} {
1476 foreach script $commitinterest($id) {
1477 lappend scripts [string map [list "%I" $id] $script]
1479 unset commitinterest($id)
1481 set gotsome 1
1483 if {$gotsome} {
1484 global numcommits hlview
1486 if {$view == $curview} {
1487 set numcommits $commitidx($view)
1488 run chewcommits
1490 if {[info exists hlview] && $view == $hlview} {
1491 # we never actually get here...
1492 run vhighlightmore
1494 foreach s $scripts {
1495 eval $s
1498 return 2
1501 proc chewcommits {} {
1502 global curview hlview viewcomplete
1503 global pending_select
1505 layoutmore
1506 if {$viewcomplete($curview)} {
1507 global commitidx varctok
1508 global numcommits startmsecs
1510 if {[info exists pending_select]} {
1511 update
1512 reset_pending_select {}
1514 if {[commitinview $pending_select $curview]} {
1515 selectline [rowofcommit $pending_select] 1
1516 } else {
1517 set row [first_real_row]
1518 selectline $row 1
1521 if {$commitidx($curview) > 0} {
1522 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1523 #puts "overall $ms ms for $numcommits commits"
1524 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1525 } else {
1526 show_status [mc "No commits selected"]
1528 notbusy layout
1530 return 0
1533 proc readcommit {id} {
1534 if {[catch {set contents [exec git cat-file commit $id]}]} return
1535 parsecommit $id $contents 0
1538 proc parsecommit {id contents listed} {
1539 global commitinfo cdate
1541 set inhdr 1
1542 set comment {}
1543 set headline {}
1544 set auname {}
1545 set audate {}
1546 set comname {}
1547 set comdate {}
1548 set hdrend [string first "\n\n" $contents]
1549 if {$hdrend < 0} {
1550 # should never happen...
1551 set hdrend [string length $contents]
1553 set header [string range $contents 0 [expr {$hdrend - 1}]]
1554 set comment [string range $contents [expr {$hdrend + 2}] end]
1555 foreach line [split $header "\n"] {
1556 set tag [lindex $line 0]
1557 if {$tag == "author"} {
1558 set audate [lindex $line end-1]
1559 set auname [lrange $line 1 end-2]
1560 } elseif {$tag == "committer"} {
1561 set comdate [lindex $line end-1]
1562 set comname [lrange $line 1 end-2]
1565 set headline {}
1566 # take the first non-blank line of the comment as the headline
1567 set headline [string trimleft $comment]
1568 set i [string first "\n" $headline]
1569 if {$i >= 0} {
1570 set headline [string range $headline 0 $i]
1572 set headline [string trimright $headline]
1573 set i [string first "\r" $headline]
1574 if {$i >= 0} {
1575 set headline [string trimright [string range $headline 0 $i]]
1577 if {!$listed} {
1578 # git log indents the comment by 4 spaces;
1579 # if we got this via git cat-file, add the indentation
1580 set newcomment {}
1581 foreach line [split $comment "\n"] {
1582 append newcomment " "
1583 append newcomment $line
1584 append newcomment "\n"
1586 set comment $newcomment
1588 if {$comdate != {}} {
1589 set cdate($id) $comdate
1591 set commitinfo($id) [list $headline $auname $audate \
1592 $comname $comdate $comment]
1595 proc getcommit {id} {
1596 global commitdata commitinfo
1598 if {[info exists commitdata($id)]} {
1599 parsecommit $id $commitdata($id) 1
1600 } else {
1601 readcommit $id
1602 if {![info exists commitinfo($id)]} {
1603 set commitinfo($id) [list [mc "No commit information available"]]
1606 return 1
1609 proc readrefs {} {
1610 global tagids idtags headids idheads tagobjid
1611 global otherrefids idotherrefs mainhead mainheadid
1613 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1614 catch {unset $v}
1616 set refd [open [list | git show-ref -d] r]
1617 while {[gets $refd line] >= 0} {
1618 if {[string index $line 40] ne " "} continue
1619 set id [string range $line 0 39]
1620 set ref [string range $line 41 end]
1621 if {![string match "refs/*" $ref]} continue
1622 set name [string range $ref 5 end]
1623 if {[string match "remotes/*" $name]} {
1624 if {![string match "*/HEAD" $name]} {
1625 set headids($name) $id
1626 lappend idheads($id) $name
1628 } elseif {[string match "heads/*" $name]} {
1629 set name [string range $name 6 end]
1630 set headids($name) $id
1631 lappend idheads($id) $name
1632 } elseif {[string match "tags/*" $name]} {
1633 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1634 # which is what we want since the former is the commit ID
1635 set name [string range $name 5 end]
1636 if {[string match "*^{}" $name]} {
1637 set name [string range $name 0 end-3]
1638 } else {
1639 set tagobjid($name) $id
1641 set tagids($name) $id
1642 lappend idtags($id) $name
1643 } else {
1644 set otherrefids($name) $id
1645 lappend idotherrefs($id) $name
1648 catch {close $refd}
1649 set mainhead {}
1650 set mainheadid {}
1651 catch {
1652 set mainheadid [exec git rev-parse HEAD]
1653 set thehead [exec git symbolic-ref HEAD]
1654 if {[string match "refs/heads/*" $thehead]} {
1655 set mainhead [string range $thehead 11 end]
1660 # skip over fake commits
1661 proc first_real_row {} {
1662 global nullid nullid2 numcommits
1664 for {set row 0} {$row < $numcommits} {incr row} {
1665 set id [commitonrow $row]
1666 if {$id ne $nullid && $id ne $nullid2} {
1667 break
1670 return $row
1673 # update things for a head moved to a child of its previous location
1674 proc movehead {id name} {
1675 global headids idheads
1677 removehead $headids($name) $name
1678 set headids($name) $id
1679 lappend idheads($id) $name
1682 # update things when a head has been removed
1683 proc removehead {id name} {
1684 global headids idheads
1686 if {$idheads($id) eq $name} {
1687 unset idheads($id)
1688 } else {
1689 set i [lsearch -exact $idheads($id) $name]
1690 if {$i >= 0} {
1691 set idheads($id) [lreplace $idheads($id) $i $i]
1694 unset headids($name)
1697 proc show_error {w top msg} {
1698 message $w.m -text $msg -justify center -aspect 400
1699 pack $w.m -side top -fill x -padx 20 -pady 20
1700 button $w.ok -text [mc OK] -command "destroy $top"
1701 pack $w.ok -side bottom -fill x
1702 bind $top <Visibility> "grab $top; focus $top"
1703 bind $top <Key-Return> "destroy $top"
1704 tkwait window $top
1707 proc error_popup msg {
1708 set w .error
1709 toplevel $w
1710 wm transient $w .
1711 show_error $w $w $msg
1714 proc confirm_popup msg {
1715 global confirm_ok
1716 set confirm_ok 0
1717 set w .confirm
1718 toplevel $w
1719 wm transient $w .
1720 message $w.m -text $msg -justify center -aspect 400
1721 pack $w.m -side top -fill x -padx 20 -pady 20
1722 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1723 pack $w.ok -side left -fill x
1724 button $w.cancel -text [mc Cancel] -command "destroy $w"
1725 pack $w.cancel -side right -fill x
1726 bind $w <Visibility> "grab $w; focus $w"
1727 tkwait window $w
1728 return $confirm_ok
1731 proc setoptions {} {
1732 option add *Panedwindow.showHandle 1 startupFile
1733 option add *Panedwindow.sashRelief raised startupFile
1734 option add *Button.font uifont startupFile
1735 option add *Checkbutton.font uifont startupFile
1736 option add *Radiobutton.font uifont startupFile
1737 option add *Menu.font uifont startupFile
1738 option add *Menubutton.font uifont startupFile
1739 option add *Label.font uifont startupFile
1740 option add *Message.font uifont startupFile
1741 option add *Entry.font uifont startupFile
1744 proc makewindow {} {
1745 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1746 global tabstop
1747 global findtype findtypemenu findloc findstring fstring geometry
1748 global entries sha1entry sha1string sha1but
1749 global diffcontextstring diffcontext
1750 global ignorespace
1751 global maincursor textcursor curtextcursor
1752 global rowctxmenu fakerowmenu mergemax wrapcomment
1753 global highlight_files gdttype
1754 global searchstring sstring
1755 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1756 global headctxmenu progresscanv progressitem progresscoords statusw
1757 global fprogitem fprogcoord lastprogupdate progupdatepending
1758 global rprogitem rprogcoord rownumsel numcommits
1759 global have_tk85
1761 menu .bar
1762 .bar add cascade -label [mc "File"] -menu .bar.file
1763 menu .bar.file
1764 .bar.file add command -label [mc "Update"] -command updatecommits
1765 .bar.file add command -label [mc "Reload"] -command reloadcommits
1766 .bar.file add command -label [mc "Reread references"] -command rereadrefs
1767 .bar.file add command -label [mc "List references"] -command showrefs
1768 .bar.file add command -label [mc "Quit"] -command doquit
1769 menu .bar.edit
1770 .bar add cascade -label [mc "Edit"] -menu .bar.edit
1771 .bar.edit add command -label [mc "Preferences"] -command doprefs
1773 menu .bar.view
1774 .bar add cascade -label [mc "View"] -menu .bar.view
1775 .bar.view add command -label [mc "New view..."] -command {newview 0}
1776 .bar.view add command -label [mc "Edit view..."] -command editview \
1777 -state disabled
1778 .bar.view add command -label [mc "Delete view"] -command delview -state disabled
1779 .bar.view add separator
1780 .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
1781 -variable selectedview -value 0
1783 menu .bar.help
1784 .bar add cascade -label [mc "Help"] -menu .bar.help
1785 .bar.help add command -label [mc "About gitk"] -command about
1786 .bar.help add command -label [mc "Key bindings"] -command keys
1787 .bar.help configure
1788 . configure -menu .bar
1790 # the gui has upper and lower half, parts of a paned window.
1791 panedwindow .ctop -orient vertical
1793 # possibly use assumed geometry
1794 if {![info exists geometry(pwsash0)]} {
1795 set geometry(topheight) [expr {15 * $linespc}]
1796 set geometry(topwidth) [expr {80 * $charspc}]
1797 set geometry(botheight) [expr {15 * $linespc}]
1798 set geometry(botwidth) [expr {50 * $charspc}]
1799 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1800 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1803 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1804 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1805 frame .tf.histframe
1806 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1808 # create three canvases
1809 set cscroll .tf.histframe.csb
1810 set canv .tf.histframe.pwclist.canv
1811 canvas $canv \
1812 -selectbackground $selectbgcolor \
1813 -background $bgcolor -bd 0 \
1814 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1815 .tf.histframe.pwclist add $canv
1816 set canv2 .tf.histframe.pwclist.canv2
1817 canvas $canv2 \
1818 -selectbackground $selectbgcolor \
1819 -background $bgcolor -bd 0 -yscrollincr $linespc
1820 .tf.histframe.pwclist add $canv2
1821 set canv3 .tf.histframe.pwclist.canv3
1822 canvas $canv3 \
1823 -selectbackground $selectbgcolor \
1824 -background $bgcolor -bd 0 -yscrollincr $linespc
1825 .tf.histframe.pwclist add $canv3
1826 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1827 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1829 # a scroll bar to rule them
1830 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1831 pack $cscroll -side right -fill y
1832 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1833 lappend bglist $canv $canv2 $canv3
1834 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1836 # we have two button bars at bottom of top frame. Bar 1
1837 frame .tf.bar
1838 frame .tf.lbar -height 15
1840 set sha1entry .tf.bar.sha1
1841 set entries $sha1entry
1842 set sha1but .tf.bar.sha1label
1843 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1844 -command gotocommit -width 8
1845 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1846 pack .tf.bar.sha1label -side left
1847 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1848 trace add variable sha1string write sha1change
1849 pack $sha1entry -side left -pady 2
1851 image create bitmap bm-left -data {
1852 #define left_width 16
1853 #define left_height 16
1854 static unsigned char left_bits[] = {
1855 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1856 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1857 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1859 image create bitmap bm-right -data {
1860 #define right_width 16
1861 #define right_height 16
1862 static unsigned char right_bits[] = {
1863 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1864 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1865 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1867 button .tf.bar.leftbut -image bm-left -command goback \
1868 -state disabled -width 26
1869 pack .tf.bar.leftbut -side left -fill y
1870 button .tf.bar.rightbut -image bm-right -command goforw \
1871 -state disabled -width 26
1872 pack .tf.bar.rightbut -side left -fill y
1874 label .tf.bar.rowlabel -text [mc "Row"]
1875 set rownumsel {}
1876 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1877 -relief sunken -anchor e
1878 label .tf.bar.rowlabel2 -text "/"
1879 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1880 -relief sunken -anchor e
1881 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1882 -side left
1883 global selectedline
1884 trace add variable selectedline write selectedline_change
1886 # Status label and progress bar
1887 set statusw .tf.bar.status
1888 label $statusw -width 15 -relief sunken
1889 pack $statusw -side left -padx 5
1890 set h [expr {[font metrics uifont -linespace] + 2}]
1891 set progresscanv .tf.bar.progress
1892 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1893 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1894 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1895 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1896 pack $progresscanv -side right -expand 1 -fill x
1897 set progresscoords {0 0}
1898 set fprogcoord 0
1899 set rprogcoord 0
1900 bind $progresscanv <Configure> adjustprogress
1901 set lastprogupdate [clock clicks -milliseconds]
1902 set progupdatepending 0
1904 # build up the bottom bar of upper window
1905 label .tf.lbar.flabel -text "[mc "Find"] "
1906 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1907 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1908 label .tf.lbar.flab2 -text " [mc "commit"] "
1909 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
1910 -side left -fill y
1911 set gdttype [mc "containing:"]
1912 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
1913 [mc "containing:"] \
1914 [mc "touching paths:"] \
1915 [mc "adding/removing string:"]]
1916 trace add variable gdttype write gdttype_change
1917 pack .tf.lbar.gdttype -side left -fill y
1919 set findstring {}
1920 set fstring .tf.lbar.findstring
1921 lappend entries $fstring
1922 entry $fstring -width 30 -font textfont -textvariable findstring
1923 trace add variable findstring write find_change
1924 set findtype [mc "Exact"]
1925 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
1926 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
1927 trace add variable findtype write findcom_change
1928 set findloc [mc "All fields"]
1929 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
1930 [mc "Comments"] [mc "Author"] [mc "Committer"]
1931 trace add variable findloc write find_change
1932 pack .tf.lbar.findloc -side right
1933 pack .tf.lbar.findtype -side right
1934 pack $fstring -side left -expand 1 -fill x
1936 # Finish putting the upper half of the viewer together
1937 pack .tf.lbar -in .tf -side bottom -fill x
1938 pack .tf.bar -in .tf -side bottom -fill x
1939 pack .tf.histframe -fill both -side top -expand 1
1940 .ctop add .tf
1941 .ctop paneconfigure .tf -height $geometry(topheight)
1942 .ctop paneconfigure .tf -width $geometry(topwidth)
1944 # now build up the bottom
1945 panedwindow .pwbottom -orient horizontal
1947 # lower left, a text box over search bar, scroll bar to the right
1948 # if we know window height, then that will set the lower text height, otherwise
1949 # we set lower text height which will drive window height
1950 if {[info exists geometry(main)]} {
1951 frame .bleft -width $geometry(botwidth)
1952 } else {
1953 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
1955 frame .bleft.top
1956 frame .bleft.mid
1957 frame .bleft.bottom
1959 button .bleft.top.search -text [mc "Search"] -command dosearch
1960 pack .bleft.top.search -side left -padx 5
1961 set sstring .bleft.top.sstring
1962 entry $sstring -width 20 -font textfont -textvariable searchstring
1963 lappend entries $sstring
1964 trace add variable searchstring write incrsearch
1965 pack $sstring -side left -expand 1 -fill x
1966 radiobutton .bleft.mid.diff -text [mc "Diff"] \
1967 -command changediffdisp -variable diffelide -value {0 0}
1968 radiobutton .bleft.mid.old -text [mc "Old version"] \
1969 -command changediffdisp -variable diffelide -value {0 1}
1970 radiobutton .bleft.mid.new -text [mc "New version"] \
1971 -command changediffdisp -variable diffelide -value {1 0}
1972 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
1973 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
1974 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
1975 -from 1 -increment 1 -to 10000000 \
1976 -validate all -validatecommand "diffcontextvalidate %P" \
1977 -textvariable diffcontextstring
1978 .bleft.mid.diffcontext set $diffcontext
1979 trace add variable diffcontextstring write diffcontextchange
1980 lappend entries .bleft.mid.diffcontext
1981 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
1982 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
1983 -command changeignorespace -variable ignorespace
1984 pack .bleft.mid.ignspace -side left -padx 5
1985 set ctext .bleft.bottom.ctext
1986 text $ctext -background $bgcolor -foreground $fgcolor \
1987 -state disabled -font textfont \
1988 -yscrollcommand scrolltext -wrap none \
1989 -xscrollcommand ".bleft.bottom.sbhorizontal set"
1990 if {$have_tk85} {
1991 $ctext conf -tabstyle wordprocessor
1993 scrollbar .bleft.bottom.sb -command "$ctext yview"
1994 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
1995 -width 10
1996 pack .bleft.top -side top -fill x
1997 pack .bleft.mid -side top -fill x
1998 grid $ctext .bleft.bottom.sb -sticky nsew
1999 grid .bleft.bottom.sbhorizontal -sticky ew
2000 grid columnconfigure .bleft.bottom 0 -weight 1
2001 grid rowconfigure .bleft.bottom 0 -weight 1
2002 grid rowconfigure .bleft.bottom 1 -weight 0
2003 pack .bleft.bottom -side top -fill both -expand 1
2004 lappend bglist $ctext
2005 lappend fglist $ctext
2007 $ctext tag conf comment -wrap $wrapcomment
2008 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2009 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2010 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2011 $ctext tag conf d1 -fore [lindex $diffcolors 1]
2012 $ctext tag conf m0 -fore red
2013 $ctext tag conf m1 -fore blue
2014 $ctext tag conf m2 -fore green
2015 $ctext tag conf m3 -fore purple
2016 $ctext tag conf m4 -fore brown
2017 $ctext tag conf m5 -fore "#009090"
2018 $ctext tag conf m6 -fore magenta
2019 $ctext tag conf m7 -fore "#808000"
2020 $ctext tag conf m8 -fore "#009000"
2021 $ctext tag conf m9 -fore "#ff0080"
2022 $ctext tag conf m10 -fore cyan
2023 $ctext tag conf m11 -fore "#b07070"
2024 $ctext tag conf m12 -fore "#70b0f0"
2025 $ctext tag conf m13 -fore "#70f0b0"
2026 $ctext tag conf m14 -fore "#f0b070"
2027 $ctext tag conf m15 -fore "#ff70b0"
2028 $ctext tag conf mmax -fore darkgrey
2029 set mergemax 16
2030 $ctext tag conf mresult -font textfontbold
2031 $ctext tag conf msep -font textfontbold
2032 $ctext tag conf found -back yellow
2034 .pwbottom add .bleft
2035 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2037 # lower right
2038 frame .bright
2039 frame .bright.mode
2040 radiobutton .bright.mode.patch -text [mc "Patch"] \
2041 -command reselectline -variable cmitmode -value "patch"
2042 radiobutton .bright.mode.tree -text [mc "Tree"] \
2043 -command reselectline -variable cmitmode -value "tree"
2044 grid .bright.mode.patch .bright.mode.tree -sticky ew
2045 pack .bright.mode -side top -fill x
2046 set cflist .bright.cfiles
2047 set indent [font measure mainfont "nn"]
2048 text $cflist \
2049 -selectbackground $selectbgcolor \
2050 -background $bgcolor -foreground $fgcolor \
2051 -font mainfont \
2052 -tabs [list $indent [expr {2 * $indent}]] \
2053 -yscrollcommand ".bright.sb set" \
2054 -cursor [. cget -cursor] \
2055 -spacing1 1 -spacing3 1
2056 lappend bglist $cflist
2057 lappend fglist $cflist
2058 scrollbar .bright.sb -command "$cflist yview"
2059 pack .bright.sb -side right -fill y
2060 pack $cflist -side left -fill both -expand 1
2061 $cflist tag configure highlight \
2062 -background [$cflist cget -selectbackground]
2063 $cflist tag configure bold -font mainfontbold
2065 .pwbottom add .bright
2066 .ctop add .pwbottom
2068 # restore window width & height if known
2069 if {[info exists geometry(main)]} {
2070 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2071 if {$w > [winfo screenwidth .]} {
2072 set w [winfo screenwidth .]
2074 if {$h > [winfo screenheight .]} {
2075 set h [winfo screenheight .]
2077 wm geometry . "${w}x$h"
2081 if {[tk windowingsystem] eq {aqua}} {
2082 set M1B M1
2083 } else {
2084 set M1B Control
2087 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2088 pack .ctop -fill both -expand 1
2089 bindall <1> {selcanvline %W %x %y}
2090 #bindall <B1-Motion> {selcanvline %W %x %y}
2091 if {[tk windowingsystem] == "win32"} {
2092 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2093 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2094 } else {
2095 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2096 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2097 if {[tk windowingsystem] eq "aqua"} {
2098 bindall <MouseWheel> {
2099 set delta [expr {- (%D)}]
2100 allcanvs yview scroll $delta units
2104 bindall <2> "canvscan mark %W %x %y"
2105 bindall <B2-Motion> "canvscan dragto %W %x %y"
2106 bindkey <Home> selfirstline
2107 bindkey <End> sellastline
2108 bind . <Key-Up> "selnextline -1"
2109 bind . <Key-Down> "selnextline 1"
2110 bind . <Shift-Key-Up> "dofind -1 0"
2111 bind . <Shift-Key-Down> "dofind 1 0"
2112 bindkey <Key-Right> "goforw"
2113 bindkey <Key-Left> "goback"
2114 bind . <Key-Prior> "selnextpage -1"
2115 bind . <Key-Next> "selnextpage 1"
2116 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2117 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2118 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2119 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2120 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2121 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2122 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2123 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2124 bindkey <Key-space> "$ctext yview scroll 1 pages"
2125 bindkey p "selnextline -1"
2126 bindkey n "selnextline 1"
2127 bindkey z "goback"
2128 bindkey x "goforw"
2129 bindkey i "selnextline -1"
2130 bindkey k "selnextline 1"
2131 bindkey j "goback"
2132 bindkey l "goforw"
2133 bindkey b prevfile
2134 bindkey d "$ctext yview scroll 18 units"
2135 bindkey u "$ctext yview scroll -18 units"
2136 bindkey / {dofind 1 1}
2137 bindkey <Key-Return> {dofind 1 1}
2138 bindkey ? {dofind -1 1}
2139 bindkey f nextfile
2140 bindkey <F5> updatecommits
2141 bind . <$M1B-q> doquit
2142 bind . <$M1B-f> {dofind 1 1}
2143 bind . <$M1B-g> {dofind 1 0}
2144 bind . <$M1B-r> dosearchback
2145 bind . <$M1B-s> dosearch
2146 bind . <$M1B-equal> {incrfont 1}
2147 bind . <$M1B-plus> {incrfont 1}
2148 bind . <$M1B-KP_Add> {incrfont 1}
2149 bind . <$M1B-minus> {incrfont -1}
2150 bind . <$M1B-KP_Subtract> {incrfont -1}
2151 wm protocol . WM_DELETE_WINDOW doquit
2152 bind . <Destroy> {stop_backends}
2153 bind . <Button-1> "click %W"
2154 bind $fstring <Key-Return> {dofind 1 1}
2155 bind $sha1entry <Key-Return> gotocommit
2156 bind $sha1entry <<PasteSelection>> clearsha1
2157 bind $cflist <1> {sel_flist %W %x %y; break}
2158 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2159 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2160 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
2162 set maincursor [. cget -cursor]
2163 set textcursor [$ctext cget -cursor]
2164 set curtextcursor $textcursor
2166 set rowctxmenu .rowctxmenu
2167 menu $rowctxmenu -tearoff 0
2168 $rowctxmenu add command -label [mc "Diff this -> selected"] \
2169 -command {diffvssel 0}
2170 $rowctxmenu add command -label [mc "Diff selected -> this"] \
2171 -command {diffvssel 1}
2172 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
2173 $rowctxmenu add command -label [mc "Create tag"] -command mktag
2174 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
2175 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
2176 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
2177 -command cherrypick
2178 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
2179 -command resethead
2181 set fakerowmenu .fakerowmenu
2182 menu $fakerowmenu -tearoff 0
2183 $fakerowmenu add command -label [mc "Diff this -> selected"] \
2184 -command {diffvssel 0}
2185 $fakerowmenu add command -label [mc "Diff selected -> this"] \
2186 -command {diffvssel 1}
2187 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
2188 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
2189 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
2190 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
2192 set headctxmenu .headctxmenu
2193 menu $headctxmenu -tearoff 0
2194 $headctxmenu add command -label [mc "Check out this branch"] \
2195 -command cobranch
2196 $headctxmenu add command -label [mc "Remove this branch"] \
2197 -command rmbranch
2199 global flist_menu
2200 set flist_menu .flistctxmenu
2201 menu $flist_menu -tearoff 0
2202 $flist_menu add command -label [mc "Highlight this too"] \
2203 -command {flist_hl 0}
2204 $flist_menu add command -label [mc "Highlight this only"] \
2205 -command {flist_hl 1}
2206 $flist_menu add command -label [mc "External diff"] \
2207 -command {external_diff}
2210 # Windows sends all mouse wheel events to the current focused window, not
2211 # the one where the mouse hovers, so bind those events here and redirect
2212 # to the correct window
2213 proc windows_mousewheel_redirector {W X Y D} {
2214 global canv canv2 canv3
2215 set w [winfo containing -displayof $W $X $Y]
2216 if {$w ne ""} {
2217 set u [expr {$D < 0 ? 5 : -5}]
2218 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2219 allcanvs yview scroll $u units
2220 } else {
2221 catch {
2222 $w yview scroll $u units
2228 # Update row number label when selectedline changes
2229 proc selectedline_change {n1 n2 op} {
2230 global selectedline rownumsel
2232 if {$selectedline eq {}} {
2233 set rownumsel {}
2234 } else {
2235 set rownumsel [expr {$selectedline + 1}]
2239 # mouse-2 makes all windows scan vertically, but only the one
2240 # the cursor is in scans horizontally
2241 proc canvscan {op w x y} {
2242 global canv canv2 canv3
2243 foreach c [list $canv $canv2 $canv3] {
2244 if {$c == $w} {
2245 $c scan $op $x $y
2246 } else {
2247 $c scan $op 0 $y
2252 proc scrollcanv {cscroll f0 f1} {
2253 $cscroll set $f0 $f1
2254 drawvisible
2255 flushhighlights
2258 # when we make a key binding for the toplevel, make sure
2259 # it doesn't get triggered when that key is pressed in the
2260 # find string entry widget.
2261 proc bindkey {ev script} {
2262 global entries
2263 bind . $ev $script
2264 set escript [bind Entry $ev]
2265 if {$escript == {}} {
2266 set escript [bind Entry <Key>]
2268 foreach e $entries {
2269 bind $e $ev "$escript; break"
2273 # set the focus back to the toplevel for any click outside
2274 # the entry widgets
2275 proc click {w} {
2276 global ctext entries
2277 foreach e [concat $entries $ctext] {
2278 if {$w == $e} return
2280 focus .
2283 # Adjust the progress bar for a change in requested extent or canvas size
2284 proc adjustprogress {} {
2285 global progresscanv progressitem progresscoords
2286 global fprogitem fprogcoord lastprogupdate progupdatepending
2287 global rprogitem rprogcoord
2289 set w [expr {[winfo width $progresscanv] - 4}]
2290 set x0 [expr {$w * [lindex $progresscoords 0]}]
2291 set x1 [expr {$w * [lindex $progresscoords 1]}]
2292 set h [winfo height $progresscanv]
2293 $progresscanv coords $progressitem $x0 0 $x1 $h
2294 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2295 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2296 set now [clock clicks -milliseconds]
2297 if {$now >= $lastprogupdate + 100} {
2298 set progupdatepending 0
2299 update
2300 } elseif {!$progupdatepending} {
2301 set progupdatepending 1
2302 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2306 proc doprogupdate {} {
2307 global lastprogupdate progupdatepending
2309 if {$progupdatepending} {
2310 set progupdatepending 0
2311 set lastprogupdate [clock clicks -milliseconds]
2312 update
2316 proc savestuff {w} {
2317 global canv canv2 canv3 mainfont textfont uifont tabstop
2318 global stuffsaved findmergefiles maxgraphpct
2319 global maxwidth showneartags showlocalchanges
2320 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2321 global cmitmode wrapcomment datetimeformat limitdiffs
2322 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2323 global autoselect extdifftool
2325 if {$stuffsaved} return
2326 if {![winfo viewable .]} return
2327 catch {
2328 set f [open "~/.gitk-new" w]
2329 puts $f [list set mainfont $mainfont]
2330 puts $f [list set textfont $textfont]
2331 puts $f [list set uifont $uifont]
2332 puts $f [list set tabstop $tabstop]
2333 puts $f [list set findmergefiles $findmergefiles]
2334 puts $f [list set maxgraphpct $maxgraphpct]
2335 puts $f [list set maxwidth $maxwidth]
2336 puts $f [list set cmitmode $cmitmode]
2337 puts $f [list set wrapcomment $wrapcomment]
2338 puts $f [list set autoselect $autoselect]
2339 puts $f [list set showneartags $showneartags]
2340 puts $f [list set showlocalchanges $showlocalchanges]
2341 puts $f [list set datetimeformat $datetimeformat]
2342 puts $f [list set limitdiffs $limitdiffs]
2343 puts $f [list set bgcolor $bgcolor]
2344 puts $f [list set fgcolor $fgcolor]
2345 puts $f [list set colors $colors]
2346 puts $f [list set diffcolors $diffcolors]
2347 puts $f [list set diffcontext $diffcontext]
2348 puts $f [list set selectbgcolor $selectbgcolor]
2349 puts $f [list set extdifftool $extdifftool]
2351 puts $f "set geometry(main) [wm geometry .]"
2352 puts $f "set geometry(topwidth) [winfo width .tf]"
2353 puts $f "set geometry(topheight) [winfo height .tf]"
2354 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2355 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2356 puts $f "set geometry(botwidth) [winfo width .bleft]"
2357 puts $f "set geometry(botheight) [winfo height .bleft]"
2359 puts -nonewline $f "set permviews {"
2360 for {set v 0} {$v < $nextviewnum} {incr v} {
2361 if {$viewperm($v)} {
2362 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2365 puts $f "}"
2366 close $f
2367 catch {file delete "~/.gitk"}
2368 file rename -force "~/.gitk-new" "~/.gitk"
2370 set stuffsaved 1
2373 proc resizeclistpanes {win w} {
2374 global oldwidth
2375 if {[info exists oldwidth($win)]} {
2376 set s0 [$win sash coord 0]
2377 set s1 [$win sash coord 1]
2378 if {$w < 60} {
2379 set sash0 [expr {int($w/2 - 2)}]
2380 set sash1 [expr {int($w*5/6 - 2)}]
2381 } else {
2382 set factor [expr {1.0 * $w / $oldwidth($win)}]
2383 set sash0 [expr {int($factor * [lindex $s0 0])}]
2384 set sash1 [expr {int($factor * [lindex $s1 0])}]
2385 if {$sash0 < 30} {
2386 set sash0 30
2388 if {$sash1 < $sash0 + 20} {
2389 set sash1 [expr {$sash0 + 20}]
2391 if {$sash1 > $w - 10} {
2392 set sash1 [expr {$w - 10}]
2393 if {$sash0 > $sash1 - 20} {
2394 set sash0 [expr {$sash1 - 20}]
2398 $win sash place 0 $sash0 [lindex $s0 1]
2399 $win sash place 1 $sash1 [lindex $s1 1]
2401 set oldwidth($win) $w
2404 proc resizecdetpanes {win w} {
2405 global oldwidth
2406 if {[info exists oldwidth($win)]} {
2407 set s0 [$win sash coord 0]
2408 if {$w < 60} {
2409 set sash0 [expr {int($w*3/4 - 2)}]
2410 } else {
2411 set factor [expr {1.0 * $w / $oldwidth($win)}]
2412 set sash0 [expr {int($factor * [lindex $s0 0])}]
2413 if {$sash0 < 45} {
2414 set sash0 45
2416 if {$sash0 > $w - 15} {
2417 set sash0 [expr {$w - 15}]
2420 $win sash place 0 $sash0 [lindex $s0 1]
2422 set oldwidth($win) $w
2425 proc allcanvs args {
2426 global canv canv2 canv3
2427 eval $canv $args
2428 eval $canv2 $args
2429 eval $canv3 $args
2432 proc bindall {event action} {
2433 global canv canv2 canv3
2434 bind $canv $event $action
2435 bind $canv2 $event $action
2436 bind $canv3 $event $action
2439 proc about {} {
2440 global uifont
2441 set w .about
2442 if {[winfo exists $w]} {
2443 raise $w
2444 return
2446 toplevel $w
2447 wm title $w [mc "About gitk"]
2448 message $w.m -text [mc "
2449 Gitk - a commit viewer for git
2451 Copyright © 2005-2008 Paul Mackerras
2453 Use and redistribute under the terms of the GNU General Public License"] \
2454 -justify center -aspect 400 -border 2 -bg white -relief groove
2455 pack $w.m -side top -fill x -padx 2 -pady 2
2456 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2457 pack $w.ok -side bottom
2458 bind $w <Visibility> "focus $w.ok"
2459 bind $w <Key-Escape> "destroy $w"
2460 bind $w <Key-Return> "destroy $w"
2463 proc keys {} {
2464 set w .keys
2465 if {[winfo exists $w]} {
2466 raise $w
2467 return
2469 if {[tk windowingsystem] eq {aqua}} {
2470 set M1T Cmd
2471 } else {
2472 set M1T Ctrl
2474 toplevel $w
2475 wm title $w [mc "Gitk key bindings"]
2476 message $w.m -text "
2477 [mc "Gitk key bindings:"]
2479 [mc "<%s-Q> Quit" $M1T]
2480 [mc "<Home> Move to first commit"]
2481 [mc "<End> Move to last commit"]
2482 [mc "<Up>, p, i Move up one commit"]
2483 [mc "<Down>, n, k Move down one commit"]
2484 [mc "<Left>, z, j Go back in history list"]
2485 [mc "<Right>, x, l Go forward in history list"]
2486 [mc "<PageUp> Move up one page in commit list"]
2487 [mc "<PageDown> Move down one page in commit list"]
2488 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2489 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2490 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2491 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2492 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2493 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2494 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2495 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2496 [mc "<Delete>, b Scroll diff view up one page"]
2497 [mc "<Backspace> Scroll diff view up one page"]
2498 [mc "<Space> Scroll diff view down one page"]
2499 [mc "u Scroll diff view up 18 lines"]
2500 [mc "d Scroll diff view down 18 lines"]
2501 [mc "<%s-F> Find" $M1T]
2502 [mc "<%s-G> Move to next find hit" $M1T]
2503 [mc "<Return> Move to next find hit"]
2504 [mc "/ Move to next find hit, or redo find"]
2505 [mc "? Move to previous find hit"]
2506 [mc "f Scroll diff view to next file"]
2507 [mc "<%s-S> Search for next hit in diff view" $M1T]
2508 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2509 [mc "<%s-KP+> Increase font size" $M1T]
2510 [mc "<%s-plus> Increase font size" $M1T]
2511 [mc "<%s-KP-> Decrease font size" $M1T]
2512 [mc "<%s-minus> Decrease font size" $M1T]
2513 [mc "<F5> Update"]
2515 -justify left -bg white -border 2 -relief groove
2516 pack $w.m -side top -fill both -padx 2 -pady 2
2517 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2518 pack $w.ok -side bottom
2519 bind $w <Visibility> "focus $w.ok"
2520 bind $w <Key-Escape> "destroy $w"
2521 bind $w <Key-Return> "destroy $w"
2524 # Procedures for manipulating the file list window at the
2525 # bottom right of the overall window.
2527 proc treeview {w l openlevs} {
2528 global treecontents treediropen treeheight treeparent treeindex
2530 set ix 0
2531 set treeindex() 0
2532 set lev 0
2533 set prefix {}
2534 set prefixend -1
2535 set prefendstack {}
2536 set htstack {}
2537 set ht 0
2538 set treecontents() {}
2539 $w conf -state normal
2540 foreach f $l {
2541 while {[string range $f 0 $prefixend] ne $prefix} {
2542 if {$lev <= $openlevs} {
2543 $w mark set e:$treeindex($prefix) "end -1c"
2544 $w mark gravity e:$treeindex($prefix) left
2546 set treeheight($prefix) $ht
2547 incr ht [lindex $htstack end]
2548 set htstack [lreplace $htstack end end]
2549 set prefixend [lindex $prefendstack end]
2550 set prefendstack [lreplace $prefendstack end end]
2551 set prefix [string range $prefix 0 $prefixend]
2552 incr lev -1
2554 set tail [string range $f [expr {$prefixend+1}] end]
2555 while {[set slash [string first "/" $tail]] >= 0} {
2556 lappend htstack $ht
2557 set ht 0
2558 lappend prefendstack $prefixend
2559 incr prefixend [expr {$slash + 1}]
2560 set d [string range $tail 0 $slash]
2561 lappend treecontents($prefix) $d
2562 set oldprefix $prefix
2563 append prefix $d
2564 set treecontents($prefix) {}
2565 set treeindex($prefix) [incr ix]
2566 set treeparent($prefix) $oldprefix
2567 set tail [string range $tail [expr {$slash+1}] end]
2568 if {$lev <= $openlevs} {
2569 set ht 1
2570 set treediropen($prefix) [expr {$lev < $openlevs}]
2571 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2572 $w mark set d:$ix "end -1c"
2573 $w mark gravity d:$ix left
2574 set str "\n"
2575 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2576 $w insert end $str
2577 $w image create end -align center -image $bm -padx 1 \
2578 -name a:$ix
2579 $w insert end $d [highlight_tag $prefix]
2580 $w mark set s:$ix "end -1c"
2581 $w mark gravity s:$ix left
2583 incr lev
2585 if {$tail ne {}} {
2586 if {$lev <= $openlevs} {
2587 incr ht
2588 set str "\n"
2589 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2590 $w insert end $str
2591 $w insert end $tail [highlight_tag $f]
2593 lappend treecontents($prefix) $tail
2596 while {$htstack ne {}} {
2597 set treeheight($prefix) $ht
2598 incr ht [lindex $htstack end]
2599 set htstack [lreplace $htstack end end]
2600 set prefixend [lindex $prefendstack end]
2601 set prefendstack [lreplace $prefendstack end end]
2602 set prefix [string range $prefix 0 $prefixend]
2604 $w conf -state disabled
2607 proc linetoelt {l} {
2608 global treeheight treecontents
2610 set y 2
2611 set prefix {}
2612 while {1} {
2613 foreach e $treecontents($prefix) {
2614 if {$y == $l} {
2615 return "$prefix$e"
2617 set n 1
2618 if {[string index $e end] eq "/"} {
2619 set n $treeheight($prefix$e)
2620 if {$y + $n > $l} {
2621 append prefix $e
2622 incr y
2623 break
2626 incr y $n
2631 proc highlight_tree {y prefix} {
2632 global treeheight treecontents cflist
2634 foreach e $treecontents($prefix) {
2635 set path $prefix$e
2636 if {[highlight_tag $path] ne {}} {
2637 $cflist tag add bold $y.0 "$y.0 lineend"
2639 incr y
2640 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2641 set y [highlight_tree $y $path]
2644 return $y
2647 proc treeclosedir {w dir} {
2648 global treediropen treeheight treeparent treeindex
2650 set ix $treeindex($dir)
2651 $w conf -state normal
2652 $w delete s:$ix e:$ix
2653 set treediropen($dir) 0
2654 $w image configure a:$ix -image tri-rt
2655 $w conf -state disabled
2656 set n [expr {1 - $treeheight($dir)}]
2657 while {$dir ne {}} {
2658 incr treeheight($dir) $n
2659 set dir $treeparent($dir)
2663 proc treeopendir {w dir} {
2664 global treediropen treeheight treeparent treecontents treeindex
2666 set ix $treeindex($dir)
2667 $w conf -state normal
2668 $w image configure a:$ix -image tri-dn
2669 $w mark set e:$ix s:$ix
2670 $w mark gravity e:$ix right
2671 set lev 0
2672 set str "\n"
2673 set n [llength $treecontents($dir)]
2674 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2675 incr lev
2676 append str "\t"
2677 incr treeheight($x) $n
2679 foreach e $treecontents($dir) {
2680 set de $dir$e
2681 if {[string index $e end] eq "/"} {
2682 set iy $treeindex($de)
2683 $w mark set d:$iy e:$ix
2684 $w mark gravity d:$iy left
2685 $w insert e:$ix $str
2686 set treediropen($de) 0
2687 $w image create e:$ix -align center -image tri-rt -padx 1 \
2688 -name a:$iy
2689 $w insert e:$ix $e [highlight_tag $de]
2690 $w mark set s:$iy e:$ix
2691 $w mark gravity s:$iy left
2692 set treeheight($de) 1
2693 } else {
2694 $w insert e:$ix $str
2695 $w insert e:$ix $e [highlight_tag $de]
2698 $w mark gravity e:$ix left
2699 $w conf -state disabled
2700 set treediropen($dir) 1
2701 set top [lindex [split [$w index @0,0] .] 0]
2702 set ht [$w cget -height]
2703 set l [lindex [split [$w index s:$ix] .] 0]
2704 if {$l < $top} {
2705 $w yview $l.0
2706 } elseif {$l + $n + 1 > $top + $ht} {
2707 set top [expr {$l + $n + 2 - $ht}]
2708 if {$l < $top} {
2709 set top $l
2711 $w yview $top.0
2715 proc treeclick {w x y} {
2716 global treediropen cmitmode ctext cflist cflist_top
2718 if {$cmitmode ne "tree"} return
2719 if {![info exists cflist_top]} return
2720 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2721 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2722 $cflist tag add highlight $l.0 "$l.0 lineend"
2723 set cflist_top $l
2724 if {$l == 1} {
2725 $ctext yview 1.0
2726 return
2728 set e [linetoelt $l]
2729 if {[string index $e end] ne "/"} {
2730 showfile $e
2731 } elseif {$treediropen($e)} {
2732 treeclosedir $w $e
2733 } else {
2734 treeopendir $w $e
2738 proc setfilelist {id} {
2739 global treefilelist cflist
2741 treeview $cflist $treefilelist($id) 0
2744 image create bitmap tri-rt -background black -foreground blue -data {
2745 #define tri-rt_width 13
2746 #define tri-rt_height 13
2747 static unsigned char tri-rt_bits[] = {
2748 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2749 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2750 0x00, 0x00};
2751 } -maskdata {
2752 #define tri-rt-mask_width 13
2753 #define tri-rt-mask_height 13
2754 static unsigned char tri-rt-mask_bits[] = {
2755 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2756 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2757 0x08, 0x00};
2759 image create bitmap tri-dn -background black -foreground blue -data {
2760 #define tri-dn_width 13
2761 #define tri-dn_height 13
2762 static unsigned char tri-dn_bits[] = {
2763 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2764 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2765 0x00, 0x00};
2766 } -maskdata {
2767 #define tri-dn-mask_width 13
2768 #define tri-dn-mask_height 13
2769 static unsigned char tri-dn-mask_bits[] = {
2770 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2771 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2772 0x00, 0x00};
2775 image create bitmap reficon-T -background black -foreground yellow -data {
2776 #define tagicon_width 13
2777 #define tagicon_height 9
2778 static unsigned char tagicon_bits[] = {
2779 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2780 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2781 } -maskdata {
2782 #define tagicon-mask_width 13
2783 #define tagicon-mask_height 9
2784 static unsigned char tagicon-mask_bits[] = {
2785 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2786 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2788 set rectdata {
2789 #define headicon_width 13
2790 #define headicon_height 9
2791 static unsigned char headicon_bits[] = {
2792 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2793 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2795 set rectmask {
2796 #define headicon-mask_width 13
2797 #define headicon-mask_height 9
2798 static unsigned char headicon-mask_bits[] = {
2799 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2800 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2802 image create bitmap reficon-H -background black -foreground green \
2803 -data $rectdata -maskdata $rectmask
2804 image create bitmap reficon-o -background black -foreground "#ddddff" \
2805 -data $rectdata -maskdata $rectmask
2807 proc init_flist {first} {
2808 global cflist cflist_top difffilestart
2810 $cflist conf -state normal
2811 $cflist delete 0.0 end
2812 if {$first ne {}} {
2813 $cflist insert end $first
2814 set cflist_top 1
2815 $cflist tag add highlight 1.0 "1.0 lineend"
2816 } else {
2817 catch {unset cflist_top}
2819 $cflist conf -state disabled
2820 set difffilestart {}
2823 proc highlight_tag {f} {
2824 global highlight_paths
2826 foreach p $highlight_paths {
2827 if {[string match $p $f]} {
2828 return "bold"
2831 return {}
2834 proc highlight_filelist {} {
2835 global cmitmode cflist
2837 $cflist conf -state normal
2838 if {$cmitmode ne "tree"} {
2839 set end [lindex [split [$cflist index end] .] 0]
2840 for {set l 2} {$l < $end} {incr l} {
2841 set line [$cflist get $l.0 "$l.0 lineend"]
2842 if {[highlight_tag $line] ne {}} {
2843 $cflist tag add bold $l.0 "$l.0 lineend"
2846 } else {
2847 highlight_tree 2 {}
2849 $cflist conf -state disabled
2852 proc unhighlight_filelist {} {
2853 global cflist
2855 $cflist conf -state normal
2856 $cflist tag remove bold 1.0 end
2857 $cflist conf -state disabled
2860 proc add_flist {fl} {
2861 global cflist
2863 $cflist conf -state normal
2864 foreach f $fl {
2865 $cflist insert end "\n"
2866 $cflist insert end $f [highlight_tag $f]
2868 $cflist conf -state disabled
2871 proc sel_flist {w x y} {
2872 global ctext difffilestart cflist cflist_top cmitmode
2874 if {$cmitmode eq "tree"} return
2875 if {![info exists cflist_top]} return
2876 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2877 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2878 $cflist tag add highlight $l.0 "$l.0 lineend"
2879 set cflist_top $l
2880 if {$l == 1} {
2881 $ctext yview 1.0
2882 } else {
2883 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2887 proc pop_flist_menu {w X Y x y} {
2888 global ctext cflist cmitmode flist_menu flist_menu_file
2889 global treediffs diffids
2891 stopfinding
2892 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2893 if {$l <= 1} return
2894 if {$cmitmode eq "tree"} {
2895 set e [linetoelt $l]
2896 if {[string index $e end] eq "/"} return
2897 } else {
2898 set e [lindex $treediffs($diffids) [expr {$l-2}]]
2900 set flist_menu_file $e
2901 set xdiffstate "normal"
2902 if {$cmitmode eq "tree"} {
2903 set xdiffstate "disabled"
2905 # Disable "External diff" item in tree mode
2906 $flist_menu entryconf 2 -state $xdiffstate
2907 tk_popup $flist_menu $X $Y
2910 proc flist_hl {only} {
2911 global flist_menu_file findstring gdttype
2913 set x [shellquote $flist_menu_file]
2914 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
2915 set findstring $x
2916 } else {
2917 append findstring " " $x
2919 set gdttype [mc "touching paths:"]
2922 proc save_file_from_commit {filename output what} {
2923 global nullfile
2925 if {[catch {exec git show $filename -- > $output} err]} {
2926 if {[string match "fatal: bad revision *" $err]} {
2927 return $nullfile
2929 error_popup "Error getting \"$filename\" from $what: $err"
2930 return {}
2932 return $output
2935 proc external_diff_get_one_file {diffid filename diffdir} {
2936 global nullid nullid2 nullfile
2937 global gitdir
2939 if {$diffid == $nullid} {
2940 set difffile [file join [file dirname $gitdir] $filename]
2941 if {[file exists $difffile]} {
2942 return $difffile
2944 return $nullfile
2946 if {$diffid == $nullid2} {
2947 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
2948 return [save_file_from_commit :$filename $difffile index]
2950 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
2951 return [save_file_from_commit $diffid:$filename $difffile \
2952 "revision $diffid"]
2955 proc external_diff {} {
2956 global gitktmpdir nullid nullid2
2957 global flist_menu_file
2958 global diffids
2959 global diffnum
2960 global gitdir extdifftool
2962 if {[llength $diffids] == 1} {
2963 # no reference commit given
2964 set diffidto [lindex $diffids 0]
2965 if {$diffidto eq $nullid} {
2966 # diffing working copy with index
2967 set diffidfrom $nullid2
2968 } elseif {$diffidto eq $nullid2} {
2969 # diffing index with HEAD
2970 set diffidfrom "HEAD"
2971 } else {
2972 # use first parent commit
2973 global parentlist selectedline
2974 set diffidfrom [lindex $parentlist $selectedline 0]
2976 } else {
2977 set diffidfrom [lindex $diffids 0]
2978 set diffidto [lindex $diffids 1]
2981 # make sure that several diffs wont collide
2982 if {![info exists gitktmpdir]} {
2983 set gitktmpdir [file join [file dirname $gitdir] \
2984 [format ".gitk-tmp.%s" [pid]]]
2985 if {[catch {file mkdir $gitktmpdir} err]} {
2986 error_popup "Error creating temporary directory $gitktmpdir: $err"
2987 unset gitktmpdir
2988 return
2990 set diffnum 0
2992 incr diffnum
2993 set diffdir [file join $gitktmpdir $diffnum]
2994 if {[catch {file mkdir $diffdir} err]} {
2995 error_popup "Error creating temporary directory $diffdir: $err"
2996 return
2999 # gather files to diff
3000 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3001 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3003 if {$difffromfile ne {} && $difftofile ne {}} {
3004 set cmd [concat | [shellsplit $extdifftool] \
3005 [list $difffromfile $difftofile]]
3006 if {[catch {set fl [open $cmd r]} err]} {
3007 file delete -force $diffdir
3008 error_popup [mc "$extdifftool: command failed: $err"]
3009 } else {
3010 fconfigure $fl -blocking 0
3011 filerun $fl [list delete_at_eof $fl $diffdir]
3016 # delete $dir when we see eof on $f (presumably because the child has exited)
3017 proc delete_at_eof {f dir} {
3018 while {[gets $f line] >= 0} {}
3019 if {[eof $f]} {
3020 if {[catch {close $f} err]} {
3021 error_popup "External diff viewer failed: $err"
3023 file delete -force $dir
3024 return 0
3026 return 1
3029 # Functions for adding and removing shell-type quoting
3031 proc shellquote {str} {
3032 if {![string match "*\['\"\\ \t]*" $str]} {
3033 return $str
3035 if {![string match "*\['\"\\]*" $str]} {
3036 return "\"$str\""
3038 if {![string match "*'*" $str]} {
3039 return "'$str'"
3041 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3044 proc shellarglist {l} {
3045 set str {}
3046 foreach a $l {
3047 if {$str ne {}} {
3048 append str " "
3050 append str [shellquote $a]
3052 return $str
3055 proc shelldequote {str} {
3056 set ret {}
3057 set used -1
3058 while {1} {
3059 incr used
3060 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3061 append ret [string range $str $used end]
3062 set used [string length $str]
3063 break
3065 set first [lindex $first 0]
3066 set ch [string index $str $first]
3067 if {$first > $used} {
3068 append ret [string range $str $used [expr {$first - 1}]]
3069 set used $first
3071 if {$ch eq " " || $ch eq "\t"} break
3072 incr used
3073 if {$ch eq "'"} {
3074 set first [string first "'" $str $used]
3075 if {$first < 0} {
3076 error "unmatched single-quote"
3078 append ret [string range $str $used [expr {$first - 1}]]
3079 set used $first
3080 continue
3082 if {$ch eq "\\"} {
3083 if {$used >= [string length $str]} {
3084 error "trailing backslash"
3086 append ret [string index $str $used]
3087 continue
3089 # here ch == "\""
3090 while {1} {
3091 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3092 error "unmatched double-quote"
3094 set first [lindex $first 0]
3095 set ch [string index $str $first]
3096 if {$first > $used} {
3097 append ret [string range $str $used [expr {$first - 1}]]
3098 set used $first
3100 if {$ch eq "\""} break
3101 incr used
3102 append ret [string index $str $used]
3103 incr used
3106 return [list $used $ret]
3109 proc shellsplit {str} {
3110 set l {}
3111 while {1} {
3112 set str [string trimleft $str]
3113 if {$str eq {}} break
3114 set dq [shelldequote $str]
3115 set n [lindex $dq 0]
3116 set word [lindex $dq 1]
3117 set str [string range $str $n end]
3118 lappend l $word
3120 return $l
3123 # Code to implement multiple views
3125 proc newview {ishighlight} {
3126 global nextviewnum newviewname newviewperm newishighlight
3127 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3129 set newishighlight $ishighlight
3130 set top .gitkview
3131 if {[winfo exists $top]} {
3132 raise $top
3133 return
3135 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3136 set newviewperm($nextviewnum) 0
3137 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3138 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3139 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3142 proc editview {} {
3143 global curview
3144 global viewname viewperm newviewname newviewperm
3145 global viewargs newviewargs viewargscmd newviewargscmd
3147 set top .gitkvedit-$curview
3148 if {[winfo exists $top]} {
3149 raise $top
3150 return
3152 set newviewname($curview) $viewname($curview)
3153 set newviewperm($curview) $viewperm($curview)
3154 set newviewargs($curview) [shellarglist $viewargs($curview)]
3155 set newviewargscmd($curview) $viewargscmd($curview)
3156 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3159 proc vieweditor {top n title} {
3160 global newviewname newviewperm viewfiles bgcolor
3162 toplevel $top
3163 wm title $top $title
3164 label $top.nl -text [mc "Name"]
3165 entry $top.name -width 20 -textvariable newviewname($n)
3166 grid $top.nl $top.name -sticky w -pady 5
3167 checkbutton $top.perm -text [mc "Remember this view"] \
3168 -variable newviewperm($n)
3169 grid $top.perm - -pady 5 -sticky w
3170 message $top.al -aspect 1000 \
3171 -text [mc "Commits to include (arguments to git log):"]
3172 grid $top.al - -sticky w -pady 5
3173 entry $top.args -width 50 -textvariable newviewargs($n) \
3174 -background $bgcolor
3175 grid $top.args - -sticky ew -padx 5
3177 message $top.ac -aspect 1000 \
3178 -text [mc "Command to generate more commits to include:"]
3179 grid $top.ac - -sticky w -pady 5
3180 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3181 -background white
3182 grid $top.argscmd - -sticky ew -padx 5
3184 message $top.l -aspect 1000 \
3185 -text [mc "Enter files and directories to include, one per line:"]
3186 grid $top.l - -sticky w
3187 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3188 if {[info exists viewfiles($n)]} {
3189 foreach f $viewfiles($n) {
3190 $top.t insert end $f
3191 $top.t insert end "\n"
3193 $top.t delete {end - 1c} end
3194 $top.t mark set insert 0.0
3196 grid $top.t - -sticky ew -padx 5
3197 frame $top.buts
3198 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3199 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3200 grid $top.buts.ok $top.buts.can
3201 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3202 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3203 grid $top.buts - -pady 10 -sticky ew
3204 focus $top.t
3207 proc doviewmenu {m first cmd op argv} {
3208 set nmenu [$m index end]
3209 for {set i $first} {$i <= $nmenu} {incr i} {
3210 if {[$m entrycget $i -command] eq $cmd} {
3211 eval $m $op $i $argv
3212 break
3217 proc allviewmenus {n op args} {
3218 # global viewhlmenu
3220 doviewmenu .bar.view 5 [list showview $n] $op $args
3221 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3224 proc newviewok {top n} {
3225 global nextviewnum newviewperm newviewname newishighlight
3226 global viewname viewfiles viewperm selectedview curview
3227 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3229 if {[catch {
3230 set newargs [shellsplit $newviewargs($n)]
3231 } err]} {
3232 error_popup "[mc "Error in commit selection arguments:"] $err"
3233 wm raise $top
3234 focus $top
3235 return
3237 set files {}
3238 foreach f [split [$top.t get 0.0 end] "\n"] {
3239 set ft [string trim $f]
3240 if {$ft ne {}} {
3241 lappend files $ft
3244 if {![info exists viewfiles($n)]} {
3245 # creating a new view
3246 incr nextviewnum
3247 set viewname($n) $newviewname($n)
3248 set viewperm($n) $newviewperm($n)
3249 set viewfiles($n) $files
3250 set viewargs($n) $newargs
3251 set viewargscmd($n) $newviewargscmd($n)
3252 addviewmenu $n
3253 if {!$newishighlight} {
3254 run showview $n
3255 } else {
3256 run addvhighlight $n
3258 } else {
3259 # editing an existing view
3260 set viewperm($n) $newviewperm($n)
3261 if {$newviewname($n) ne $viewname($n)} {
3262 set viewname($n) $newviewname($n)
3263 doviewmenu .bar.view 5 [list showview $n] \
3264 entryconf [list -label $viewname($n)]
3265 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3266 # entryconf [list -label $viewname($n) -value $viewname($n)]
3268 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3269 $newviewargscmd($n) ne $viewargscmd($n)} {
3270 set viewfiles($n) $files
3271 set viewargs($n) $newargs
3272 set viewargscmd($n) $newviewargscmd($n)
3273 if {$curview == $n} {
3274 run reloadcommits
3278 catch {destroy $top}
3281 proc delview {} {
3282 global curview viewperm hlview selectedhlview
3284 if {$curview == 0} return
3285 if {[info exists hlview] && $hlview == $curview} {
3286 set selectedhlview [mc "None"]
3287 unset hlview
3289 allviewmenus $curview delete
3290 set viewperm($curview) 0
3291 showview 0
3294 proc addviewmenu {n} {
3295 global viewname viewhlmenu
3297 .bar.view add radiobutton -label $viewname($n) \
3298 -command [list showview $n] -variable selectedview -value $n
3299 #$viewhlmenu add radiobutton -label $viewname($n) \
3300 # -command [list addvhighlight $n] -variable selectedhlview
3303 proc showview {n} {
3304 global curview cached_commitrow ordertok
3305 global displayorder parentlist rowidlist rowisopt rowfinal
3306 global colormap rowtextx nextcolor canvxmax
3307 global numcommits viewcomplete
3308 global selectedline currentid canv canvy0
3309 global treediffs
3310 global pending_select mainheadid
3311 global commitidx
3312 global selectedview
3313 global hlview selectedhlview commitinterest
3315 if {$n == $curview} return
3316 set selid {}
3317 set ymax [lindex [$canv cget -scrollregion] 3]
3318 set span [$canv yview]
3319 set ytop [expr {[lindex $span 0] * $ymax}]
3320 set ybot [expr {[lindex $span 1] * $ymax}]
3321 set yscreen [expr {($ybot - $ytop) / 2}]
3322 if {$selectedline ne {}} {
3323 set selid $currentid
3324 set y [yc $selectedline]
3325 if {$ytop < $y && $y < $ybot} {
3326 set yscreen [expr {$y - $ytop}]
3328 } elseif {[info exists pending_select]} {
3329 set selid $pending_select
3330 unset pending_select
3332 unselectline
3333 normalline
3334 catch {unset treediffs}
3335 clear_display
3336 if {[info exists hlview] && $hlview == $n} {
3337 unset hlview
3338 set selectedhlview [mc "None"]
3340 catch {unset commitinterest}
3341 catch {unset cached_commitrow}
3342 catch {unset ordertok}
3344 set curview $n
3345 set selectedview $n
3346 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3347 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3349 run refill_reflist
3350 if {![info exists viewcomplete($n)]} {
3351 getcommits $selid
3352 return
3355 set displayorder {}
3356 set parentlist {}
3357 set rowidlist {}
3358 set rowisopt {}
3359 set rowfinal {}
3360 set numcommits $commitidx($n)
3362 catch {unset colormap}
3363 catch {unset rowtextx}
3364 set nextcolor 0
3365 set canvxmax [$canv cget -width]
3366 set curview $n
3367 set row 0
3368 setcanvscroll
3369 set yf 0
3370 set row {}
3371 if {$selid ne {} && [commitinview $selid $n]} {
3372 set row [rowofcommit $selid]
3373 # try to get the selected row in the same position on the screen
3374 set ymax [lindex [$canv cget -scrollregion] 3]
3375 set ytop [expr {[yc $row] - $yscreen}]
3376 if {$ytop < 0} {
3377 set ytop 0
3379 set yf [expr {$ytop * 1.0 / $ymax}]
3381 allcanvs yview moveto $yf
3382 drawvisible
3383 if {$row ne {}} {
3384 selectline $row 0
3385 } elseif {!$viewcomplete($n)} {
3386 reset_pending_select $selid
3387 } else {
3388 reset_pending_select {}
3390 if {[commitinview $pending_select $curview]} {
3391 selectline [rowofcommit $pending_select] 1
3392 } else {
3393 set row [first_real_row]
3394 if {$row < $numcommits} {
3395 selectline $row 0
3399 if {!$viewcomplete($n)} {
3400 if {$numcommits == 0} {
3401 show_status [mc "Reading commits..."]
3403 } elseif {$numcommits == 0} {
3404 show_status [mc "No commits selected"]
3408 # Stuff relating to the highlighting facility
3410 proc ishighlighted {id} {
3411 global vhighlights fhighlights nhighlights rhighlights
3413 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3414 return $nhighlights($id)
3416 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3417 return $vhighlights($id)
3419 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3420 return $fhighlights($id)
3422 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3423 return $rhighlights($id)
3425 return 0
3428 proc bolden {row font} {
3429 global canv linehtag selectedline boldrows
3431 lappend boldrows $row
3432 $canv itemconf $linehtag($row) -font $font
3433 if {$row == $selectedline} {
3434 $canv delete secsel
3435 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3436 -outline {{}} -tags secsel \
3437 -fill [$canv cget -selectbackground]]
3438 $canv lower $t
3442 proc bolden_name {row font} {
3443 global canv2 linentag selectedline boldnamerows
3445 lappend boldnamerows $row
3446 $canv2 itemconf $linentag($row) -font $font
3447 if {$row == $selectedline} {
3448 $canv2 delete secsel
3449 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3450 -outline {{}} -tags secsel \
3451 -fill [$canv2 cget -selectbackground]]
3452 $canv2 lower $t
3456 proc unbolden {} {
3457 global boldrows
3459 set stillbold {}
3460 foreach row $boldrows {
3461 if {![ishighlighted [commitonrow $row]]} {
3462 bolden $row mainfont
3463 } else {
3464 lappend stillbold $row
3467 set boldrows $stillbold
3470 proc addvhighlight {n} {
3471 global hlview viewcomplete curview vhl_done commitidx
3473 if {[info exists hlview]} {
3474 delvhighlight
3476 set hlview $n
3477 if {$n != $curview && ![info exists viewcomplete($n)]} {
3478 start_rev_list $n
3480 set vhl_done $commitidx($hlview)
3481 if {$vhl_done > 0} {
3482 drawvisible
3486 proc delvhighlight {} {
3487 global hlview vhighlights
3489 if {![info exists hlview]} return
3490 unset hlview
3491 catch {unset vhighlights}
3492 unbolden
3495 proc vhighlightmore {} {
3496 global hlview vhl_done commitidx vhighlights curview
3498 set max $commitidx($hlview)
3499 set vr [visiblerows]
3500 set r0 [lindex $vr 0]
3501 set r1 [lindex $vr 1]
3502 for {set i $vhl_done} {$i < $max} {incr i} {
3503 set id [commitonrow $i $hlview]
3504 if {[commitinview $id $curview]} {
3505 set row [rowofcommit $id]
3506 if {$r0 <= $row && $row <= $r1} {
3507 if {![highlighted $row]} {
3508 bolden $row mainfontbold
3510 set vhighlights($id) 1
3514 set vhl_done $max
3515 return 0
3518 proc askvhighlight {row id} {
3519 global hlview vhighlights iddrawn
3521 if {[commitinview $id $hlview]} {
3522 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3523 bolden $row mainfontbold
3525 set vhighlights($id) 1
3526 } else {
3527 set vhighlights($id) 0
3531 proc hfiles_change {} {
3532 global highlight_files filehighlight fhighlights fh_serial
3533 global highlight_paths gdttype
3535 if {[info exists filehighlight]} {
3536 # delete previous highlights
3537 catch {close $filehighlight}
3538 unset filehighlight
3539 catch {unset fhighlights}
3540 unbolden
3541 unhighlight_filelist
3543 set highlight_paths {}
3544 after cancel do_file_hl $fh_serial
3545 incr fh_serial
3546 if {$highlight_files ne {}} {
3547 after 300 do_file_hl $fh_serial
3551 proc gdttype_change {name ix op} {
3552 global gdttype highlight_files findstring findpattern
3554 stopfinding
3555 if {$findstring ne {}} {
3556 if {$gdttype eq [mc "containing:"]} {
3557 if {$highlight_files ne {}} {
3558 set highlight_files {}
3559 hfiles_change
3561 findcom_change
3562 } else {
3563 if {$findpattern ne {}} {
3564 set findpattern {}
3565 findcom_change
3567 set highlight_files $findstring
3568 hfiles_change
3570 drawvisible
3572 # enable/disable findtype/findloc menus too
3575 proc find_change {name ix op} {
3576 global gdttype findstring highlight_files
3578 stopfinding
3579 if {$gdttype eq [mc "containing:"]} {
3580 findcom_change
3581 } else {
3582 if {$highlight_files ne $findstring} {
3583 set highlight_files $findstring
3584 hfiles_change
3587 drawvisible
3590 proc findcom_change args {
3591 global nhighlights boldnamerows
3592 global findpattern findtype findstring gdttype
3594 stopfinding
3595 # delete previous highlights, if any
3596 foreach row $boldnamerows {
3597 bolden_name $row mainfont
3599 set boldnamerows {}
3600 catch {unset nhighlights}
3601 unbolden
3602 unmarkmatches
3603 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3604 set findpattern {}
3605 } elseif {$findtype eq [mc "Regexp"]} {
3606 set findpattern $findstring
3607 } else {
3608 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3609 $findstring]
3610 set findpattern "*$e*"
3614 proc makepatterns {l} {
3615 set ret {}
3616 foreach e $l {
3617 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3618 if {[string index $ee end] eq "/"} {
3619 lappend ret "$ee*"
3620 } else {
3621 lappend ret $ee
3622 lappend ret "$ee/*"
3625 return $ret
3628 proc do_file_hl {serial} {
3629 global highlight_files filehighlight highlight_paths gdttype fhl_list
3631 if {$gdttype eq [mc "touching paths:"]} {
3632 if {[catch {set paths [shellsplit $highlight_files]}]} return
3633 set highlight_paths [makepatterns $paths]
3634 highlight_filelist
3635 set gdtargs [concat -- $paths]
3636 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3637 set gdtargs [list "-S$highlight_files"]
3638 } else {
3639 # must be "containing:", i.e. we're searching commit info
3640 return
3642 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3643 set filehighlight [open $cmd r+]
3644 fconfigure $filehighlight -blocking 0
3645 filerun $filehighlight readfhighlight
3646 set fhl_list {}
3647 drawvisible
3648 flushhighlights
3651 proc flushhighlights {} {
3652 global filehighlight fhl_list
3654 if {[info exists filehighlight]} {
3655 lappend fhl_list {}
3656 puts $filehighlight ""
3657 flush $filehighlight
3661 proc askfilehighlight {row id} {
3662 global filehighlight fhighlights fhl_list
3664 lappend fhl_list $id
3665 set fhighlights($id) -1
3666 puts $filehighlight $id
3669 proc readfhighlight {} {
3670 global filehighlight fhighlights curview iddrawn
3671 global fhl_list find_dirn
3673 if {![info exists filehighlight]} {
3674 return 0
3676 set nr 0
3677 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
3678 set line [string trim $line]
3679 set i [lsearch -exact $fhl_list $line]
3680 if {$i < 0} continue
3681 for {set j 0} {$j < $i} {incr j} {
3682 set id [lindex $fhl_list $j]
3683 set fhighlights($id) 0
3685 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
3686 if {$line eq {}} continue
3687 if {![commitinview $line $curview]} continue
3688 set row [rowofcommit $line]
3689 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
3690 bolden $row mainfontbold
3692 set fhighlights($line) 1
3694 if {[eof $filehighlight]} {
3695 # strange...
3696 puts "oops, git diff-tree died"
3697 catch {close $filehighlight}
3698 unset filehighlight
3699 return 0
3701 if {[info exists find_dirn]} {
3702 run findmore
3704 return 1
3707 proc doesmatch {f} {
3708 global findtype findpattern
3710 if {$findtype eq [mc "Regexp"]} {
3711 return [regexp $findpattern $f]
3712 } elseif {$findtype eq [mc "IgnCase"]} {
3713 return [string match -nocase $findpattern $f]
3714 } else {
3715 return [string match $findpattern $f]
3719 proc askfindhighlight {row id} {
3720 global nhighlights commitinfo iddrawn
3721 global findloc
3722 global markingmatches
3724 if {![info exists commitinfo($id)]} {
3725 getcommit $id
3727 set info $commitinfo($id)
3728 set isbold 0
3729 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
3730 foreach f $info ty $fldtypes {
3731 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
3732 [doesmatch $f]} {
3733 if {$ty eq [mc "Author"]} {
3734 set isbold 2
3735 break
3737 set isbold 1
3740 if {$isbold && [info exists iddrawn($id)]} {
3741 if {![ishighlighted $id]} {
3742 bolden $row mainfontbold
3743 if {$isbold > 1} {
3744 bolden_name $row mainfontbold
3747 if {$markingmatches} {
3748 markrowmatches $row $id
3751 set nhighlights($id) $isbold
3754 proc markrowmatches {row id} {
3755 global canv canv2 linehtag linentag commitinfo findloc
3757 set headline [lindex $commitinfo($id) 0]
3758 set author [lindex $commitinfo($id) 1]
3759 $canv delete match$row
3760 $canv2 delete match$row
3761 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
3762 set m [findmatches $headline]
3763 if {$m ne {}} {
3764 markmatches $canv $row $headline $linehtag($row) $m \
3765 [$canv itemcget $linehtag($row) -font] $row
3768 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
3769 set m [findmatches $author]
3770 if {$m ne {}} {
3771 markmatches $canv2 $row $author $linentag($row) $m \
3772 [$canv2 itemcget $linentag($row) -font] $row
3777 proc vrel_change {name ix op} {
3778 global highlight_related
3780 rhighlight_none
3781 if {$highlight_related ne [mc "None"]} {
3782 run drawvisible
3786 # prepare for testing whether commits are descendents or ancestors of a
3787 proc rhighlight_sel {a} {
3788 global descendent desc_todo ancestor anc_todo
3789 global highlight_related
3791 catch {unset descendent}
3792 set desc_todo [list $a]
3793 catch {unset ancestor}
3794 set anc_todo [list $a]
3795 if {$highlight_related ne [mc "None"]} {
3796 rhighlight_none
3797 run drawvisible
3801 proc rhighlight_none {} {
3802 global rhighlights
3804 catch {unset rhighlights}
3805 unbolden
3808 proc is_descendent {a} {
3809 global curview children descendent desc_todo
3811 set v $curview
3812 set la [rowofcommit $a]
3813 set todo $desc_todo
3814 set leftover {}
3815 set done 0
3816 for {set i 0} {$i < [llength $todo]} {incr i} {
3817 set do [lindex $todo $i]
3818 if {[rowofcommit $do] < $la} {
3819 lappend leftover $do
3820 continue
3822 foreach nk $children($v,$do) {
3823 if {![info exists descendent($nk)]} {
3824 set descendent($nk) 1
3825 lappend todo $nk
3826 if {$nk eq $a} {
3827 set done 1
3831 if {$done} {
3832 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3833 return
3836 set descendent($a) 0
3837 set desc_todo $leftover
3840 proc is_ancestor {a} {
3841 global curview parents ancestor anc_todo
3843 set v $curview
3844 set la [rowofcommit $a]
3845 set todo $anc_todo
3846 set leftover {}
3847 set done 0
3848 for {set i 0} {$i < [llength $todo]} {incr i} {
3849 set do [lindex $todo $i]
3850 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
3851 lappend leftover $do
3852 continue
3854 foreach np $parents($v,$do) {
3855 if {![info exists ancestor($np)]} {
3856 set ancestor($np) 1
3857 lappend todo $np
3858 if {$np eq $a} {
3859 set done 1
3863 if {$done} {
3864 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
3865 return
3868 set ancestor($a) 0
3869 set anc_todo $leftover
3872 proc askrelhighlight {row id} {
3873 global descendent highlight_related iddrawn rhighlights
3874 global selectedline ancestor
3876 if {$selectedline eq {}} return
3877 set isbold 0
3878 if {$highlight_related eq [mc "Descendant"] ||
3879 $highlight_related eq [mc "Not descendant"]} {
3880 if {![info exists descendent($id)]} {
3881 is_descendent $id
3883 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
3884 set isbold 1
3886 } elseif {$highlight_related eq [mc "Ancestor"] ||
3887 $highlight_related eq [mc "Not ancestor"]} {
3888 if {![info exists ancestor($id)]} {
3889 is_ancestor $id
3891 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
3892 set isbold 1
3895 if {[info exists iddrawn($id)]} {
3896 if {$isbold && ![ishighlighted $id]} {
3897 bolden $row mainfontbold
3900 set rhighlights($id) $isbold
3903 # Graph layout functions
3905 proc shortids {ids} {
3906 set res {}
3907 foreach id $ids {
3908 if {[llength $id] > 1} {
3909 lappend res [shortids $id]
3910 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
3911 lappend res [string range $id 0 7]
3912 } else {
3913 lappend res $id
3916 return $res
3919 proc ntimes {n o} {
3920 set ret {}
3921 set o [list $o]
3922 for {set mask 1} {$mask <= $n} {incr mask $mask} {
3923 if {($n & $mask) != 0} {
3924 set ret [concat $ret $o]
3926 set o [concat $o $o]
3928 return $ret
3931 proc ordertoken {id} {
3932 global ordertok curview varcid varcstart varctok curview parents children
3933 global nullid nullid2
3935 if {[info exists ordertok($id)]} {
3936 return $ordertok($id)
3938 set origid $id
3939 set todo {}
3940 while {1} {
3941 if {[info exists varcid($curview,$id)]} {
3942 set a $varcid($curview,$id)
3943 set p [lindex $varcstart($curview) $a]
3944 } else {
3945 set p [lindex $children($curview,$id) 0]
3947 if {[info exists ordertok($p)]} {
3948 set tok $ordertok($p)
3949 break
3951 set id [first_real_child $curview,$p]
3952 if {$id eq {}} {
3953 # it's a root
3954 set tok [lindex $varctok($curview) $varcid($curview,$p)]
3955 break
3957 if {[llength $parents($curview,$id)] == 1} {
3958 lappend todo [list $p {}]
3959 } else {
3960 set j [lsearch -exact $parents($curview,$id) $p]
3961 if {$j < 0} {
3962 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
3964 lappend todo [list $p [strrep $j]]
3967 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
3968 set p [lindex $todo $i 0]
3969 append tok [lindex $todo $i 1]
3970 set ordertok($p) $tok
3972 set ordertok($origid) $tok
3973 return $tok
3976 # Work out where id should go in idlist so that order-token
3977 # values increase from left to right
3978 proc idcol {idlist id {i 0}} {
3979 set t [ordertoken $id]
3980 if {$i < 0} {
3981 set i 0
3983 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
3984 if {$i > [llength $idlist]} {
3985 set i [llength $idlist]
3987 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
3988 incr i
3989 } else {
3990 if {$t > [ordertoken [lindex $idlist $i]]} {
3991 while {[incr i] < [llength $idlist] &&
3992 $t >= [ordertoken [lindex $idlist $i]]} {}
3995 return $i
3998 proc initlayout {} {
3999 global rowidlist rowisopt rowfinal displayorder parentlist
4000 global numcommits canvxmax canv
4001 global nextcolor
4002 global colormap rowtextx
4004 set numcommits 0
4005 set displayorder {}
4006 set parentlist {}
4007 set nextcolor 0
4008 set rowidlist {}
4009 set rowisopt {}
4010 set rowfinal {}
4011 set canvxmax [$canv cget -width]
4012 catch {unset colormap}
4013 catch {unset rowtextx}
4014 setcanvscroll
4017 proc setcanvscroll {} {
4018 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4019 global lastscrollset lastscrollrows
4021 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4022 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4023 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4024 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4025 set lastscrollset [clock clicks -milliseconds]
4026 set lastscrollrows $numcommits
4029 proc visiblerows {} {
4030 global canv numcommits linespc
4032 set ymax [lindex [$canv cget -scrollregion] 3]
4033 if {$ymax eq {} || $ymax == 0} return
4034 set f [$canv yview]
4035 set y0 [expr {int([lindex $f 0] * $ymax)}]
4036 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4037 if {$r0 < 0} {
4038 set r0 0
4040 set y1 [expr {int([lindex $f 1] * $ymax)}]
4041 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4042 if {$r1 >= $numcommits} {
4043 set r1 [expr {$numcommits - 1}]
4045 return [list $r0 $r1]
4048 proc layoutmore {} {
4049 global commitidx viewcomplete curview
4050 global numcommits pending_select curview
4051 global lastscrollset lastscrollrows commitinterest
4053 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4054 [clock clicks -milliseconds] - $lastscrollset > 500} {
4055 setcanvscroll
4057 if {[info exists pending_select] &&
4058 [commitinview $pending_select $curview]} {
4059 update
4060 selectline [rowofcommit $pending_select] 1
4062 drawvisible
4065 proc doshowlocalchanges {} {
4066 global curview mainheadid
4068 if {$mainheadid eq {}} return
4069 if {[commitinview $mainheadid $curview]} {
4070 dodiffindex
4071 } else {
4072 lappend commitinterest($mainheadid) {dodiffindex}
4076 proc dohidelocalchanges {} {
4077 global nullid nullid2 lserial curview
4079 if {[commitinview $nullid $curview]} {
4080 removefakerow $nullid
4082 if {[commitinview $nullid2 $curview]} {
4083 removefakerow $nullid2
4085 incr lserial
4088 # spawn off a process to do git diff-index --cached HEAD
4089 proc dodiffindex {} {
4090 global lserial showlocalchanges
4091 global isworktree
4093 if {!$showlocalchanges || !$isworktree} return
4094 incr lserial
4095 set fd [open "|git diff-index --cached HEAD" r]
4096 fconfigure $fd -blocking 0
4097 set i [reg_instance $fd]
4098 filerun $fd [list readdiffindex $fd $lserial $i]
4101 proc readdiffindex {fd serial inst} {
4102 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4104 set isdiff 1
4105 if {[gets $fd line] < 0} {
4106 if {![eof $fd]} {
4107 return 1
4109 set isdiff 0
4111 # we only need to see one line and we don't really care what it says...
4112 stop_instance $inst
4114 if {$serial != $lserial} {
4115 return 0
4118 # now see if there are any local changes not checked in to the index
4119 set fd [open "|git diff-files" r]
4120 fconfigure $fd -blocking 0
4121 set i [reg_instance $fd]
4122 filerun $fd [list readdifffiles $fd $serial $i]
4124 if {$isdiff && ![commitinview $nullid2 $curview]} {
4125 # add the line for the changes in the index to the graph
4126 set hl [mc "Local changes checked in to index but not committed"]
4127 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4128 set commitdata($nullid2) "\n $hl\n"
4129 if {[commitinview $nullid $curview]} {
4130 removefakerow $nullid
4132 insertfakerow $nullid2 $mainheadid
4133 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4134 removefakerow $nullid2
4136 return 0
4139 proc readdifffiles {fd serial inst} {
4140 global mainheadid nullid nullid2 curview
4141 global commitinfo commitdata lserial
4143 set isdiff 1
4144 if {[gets $fd line] < 0} {
4145 if {![eof $fd]} {
4146 return 1
4148 set isdiff 0
4150 # we only need to see one line and we don't really care what it says...
4151 stop_instance $inst
4153 if {$serial != $lserial} {
4154 return 0
4157 if {$isdiff && ![commitinview $nullid $curview]} {
4158 # add the line for the local diff to the graph
4159 set hl [mc "Local uncommitted changes, not checked in to index"]
4160 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4161 set commitdata($nullid) "\n $hl\n"
4162 if {[commitinview $nullid2 $curview]} {
4163 set p $nullid2
4164 } else {
4165 set p $mainheadid
4167 insertfakerow $nullid $p
4168 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4169 removefakerow $nullid
4171 return 0
4174 proc nextuse {id row} {
4175 global curview children
4177 if {[info exists children($curview,$id)]} {
4178 foreach kid $children($curview,$id) {
4179 if {![commitinview $kid $curview]} {
4180 return -1
4182 if {[rowofcommit $kid] > $row} {
4183 return [rowofcommit $kid]
4187 if {[commitinview $id $curview]} {
4188 return [rowofcommit $id]
4190 return -1
4193 proc prevuse {id row} {
4194 global curview children
4196 set ret -1
4197 if {[info exists children($curview,$id)]} {
4198 foreach kid $children($curview,$id) {
4199 if {![commitinview $kid $curview]} break
4200 if {[rowofcommit $kid] < $row} {
4201 set ret [rowofcommit $kid]
4205 return $ret
4208 proc make_idlist {row} {
4209 global displayorder parentlist uparrowlen downarrowlen mingaplen
4210 global commitidx curview children
4212 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4213 if {$r < 0} {
4214 set r 0
4216 set ra [expr {$row - $downarrowlen}]
4217 if {$ra < 0} {
4218 set ra 0
4220 set rb [expr {$row + $uparrowlen}]
4221 if {$rb > $commitidx($curview)} {
4222 set rb $commitidx($curview)
4224 make_disporder $r [expr {$rb + 1}]
4225 set ids {}
4226 for {} {$r < $ra} {incr r} {
4227 set nextid [lindex $displayorder [expr {$r + 1}]]
4228 foreach p [lindex $parentlist $r] {
4229 if {$p eq $nextid} continue
4230 set rn [nextuse $p $r]
4231 if {$rn >= $row &&
4232 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4233 lappend ids [list [ordertoken $p] $p]
4237 for {} {$r < $row} {incr r} {
4238 set nextid [lindex $displayorder [expr {$r + 1}]]
4239 foreach p [lindex $parentlist $r] {
4240 if {$p eq $nextid} continue
4241 set rn [nextuse $p $r]
4242 if {$rn < 0 || $rn >= $row} {
4243 lappend ids [list [ordertoken $p] $p]
4247 set id [lindex $displayorder $row]
4248 lappend ids [list [ordertoken $id] $id]
4249 while {$r < $rb} {
4250 foreach p [lindex $parentlist $r] {
4251 set firstkid [lindex $children($curview,$p) 0]
4252 if {[rowofcommit $firstkid] < $row} {
4253 lappend ids [list [ordertoken $p] $p]
4256 incr r
4257 set id [lindex $displayorder $r]
4258 if {$id ne {}} {
4259 set firstkid [lindex $children($curview,$id) 0]
4260 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4261 lappend ids [list [ordertoken $id] $id]
4265 set idlist {}
4266 foreach idx [lsort -unique $ids] {
4267 lappend idlist [lindex $idx 1]
4269 return $idlist
4272 proc rowsequal {a b} {
4273 while {[set i [lsearch -exact $a {}]] >= 0} {
4274 set a [lreplace $a $i $i]
4276 while {[set i [lsearch -exact $b {}]] >= 0} {
4277 set b [lreplace $b $i $i]
4279 return [expr {$a eq $b}]
4282 proc makeupline {id row rend col} {
4283 global rowidlist uparrowlen downarrowlen mingaplen
4285 for {set r $rend} {1} {set r $rstart} {
4286 set rstart [prevuse $id $r]
4287 if {$rstart < 0} return
4288 if {$rstart < $row} break
4290 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4291 set rstart [expr {$rend - $uparrowlen - 1}]
4293 for {set r $rstart} {[incr r] <= $row} {} {
4294 set idlist [lindex $rowidlist $r]
4295 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4296 set col [idcol $idlist $id $col]
4297 lset rowidlist $r [linsert $idlist $col $id]
4298 changedrow $r
4303 proc layoutrows {row endrow} {
4304 global rowidlist rowisopt rowfinal displayorder
4305 global uparrowlen downarrowlen maxwidth mingaplen
4306 global children parentlist
4307 global commitidx viewcomplete curview
4309 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4310 set idlist {}
4311 if {$row > 0} {
4312 set rm1 [expr {$row - 1}]
4313 foreach id [lindex $rowidlist $rm1] {
4314 if {$id ne {}} {
4315 lappend idlist $id
4318 set final [lindex $rowfinal $rm1]
4320 for {} {$row < $endrow} {incr row} {
4321 set rm1 [expr {$row - 1}]
4322 if {$rm1 < 0 || $idlist eq {}} {
4323 set idlist [make_idlist $row]
4324 set final 1
4325 } else {
4326 set id [lindex $displayorder $rm1]
4327 set col [lsearch -exact $idlist $id]
4328 set idlist [lreplace $idlist $col $col]
4329 foreach p [lindex $parentlist $rm1] {
4330 if {[lsearch -exact $idlist $p] < 0} {
4331 set col [idcol $idlist $p $col]
4332 set idlist [linsert $idlist $col $p]
4333 # if not the first child, we have to insert a line going up
4334 if {$id ne [lindex $children($curview,$p) 0]} {
4335 makeupline $p $rm1 $row $col
4339 set id [lindex $displayorder $row]
4340 if {$row > $downarrowlen} {
4341 set termrow [expr {$row - $downarrowlen - 1}]
4342 foreach p [lindex $parentlist $termrow] {
4343 set i [lsearch -exact $idlist $p]
4344 if {$i < 0} continue
4345 set nr [nextuse $p $termrow]
4346 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4347 set idlist [lreplace $idlist $i $i]
4351 set col [lsearch -exact $idlist $id]
4352 if {$col < 0} {
4353 set col [idcol $idlist $id]
4354 set idlist [linsert $idlist $col $id]
4355 if {$children($curview,$id) ne {}} {
4356 makeupline $id $rm1 $row $col
4359 set r [expr {$row + $uparrowlen - 1}]
4360 if {$r < $commitidx($curview)} {
4361 set x $col
4362 foreach p [lindex $parentlist $r] {
4363 if {[lsearch -exact $idlist $p] >= 0} continue
4364 set fk [lindex $children($curview,$p) 0]
4365 if {[rowofcommit $fk] < $row} {
4366 set x [idcol $idlist $p $x]
4367 set idlist [linsert $idlist $x $p]
4370 if {[incr r] < $commitidx($curview)} {
4371 set p [lindex $displayorder $r]
4372 if {[lsearch -exact $idlist $p] < 0} {
4373 set fk [lindex $children($curview,$p) 0]
4374 if {$fk ne {} && [rowofcommit $fk] < $row} {
4375 set x [idcol $idlist $p $x]
4376 set idlist [linsert $idlist $x $p]
4382 if {$final && !$viewcomplete($curview) &&
4383 $row + $uparrowlen + $mingaplen + $downarrowlen
4384 >= $commitidx($curview)} {
4385 set final 0
4387 set l [llength $rowidlist]
4388 if {$row == $l} {
4389 lappend rowidlist $idlist
4390 lappend rowisopt 0
4391 lappend rowfinal $final
4392 } elseif {$row < $l} {
4393 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4394 lset rowidlist $row $idlist
4395 changedrow $row
4397 lset rowfinal $row $final
4398 } else {
4399 set pad [ntimes [expr {$row - $l}] {}]
4400 set rowidlist [concat $rowidlist $pad]
4401 lappend rowidlist $idlist
4402 set rowfinal [concat $rowfinal $pad]
4403 lappend rowfinal $final
4404 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4407 return $row
4410 proc changedrow {row} {
4411 global displayorder iddrawn rowisopt need_redisplay
4413 set l [llength $rowisopt]
4414 if {$row < $l} {
4415 lset rowisopt $row 0
4416 if {$row + 1 < $l} {
4417 lset rowisopt [expr {$row + 1}] 0
4418 if {$row + 2 < $l} {
4419 lset rowisopt [expr {$row + 2}] 0
4423 set id [lindex $displayorder $row]
4424 if {[info exists iddrawn($id)]} {
4425 set need_redisplay 1
4429 proc insert_pad {row col npad} {
4430 global rowidlist
4432 set pad [ntimes $npad {}]
4433 set idlist [lindex $rowidlist $row]
4434 set bef [lrange $idlist 0 [expr {$col - 1}]]
4435 set aft [lrange $idlist $col end]
4436 set i [lsearch -exact $aft {}]
4437 if {$i > 0} {
4438 set aft [lreplace $aft $i $i]
4440 lset rowidlist $row [concat $bef $pad $aft]
4441 changedrow $row
4444 proc optimize_rows {row col endrow} {
4445 global rowidlist rowisopt displayorder curview children
4447 if {$row < 1} {
4448 set row 1
4450 for {} {$row < $endrow} {incr row; set col 0} {
4451 if {[lindex $rowisopt $row]} continue
4452 set haspad 0
4453 set y0 [expr {$row - 1}]
4454 set ym [expr {$row - 2}]
4455 set idlist [lindex $rowidlist $row]
4456 set previdlist [lindex $rowidlist $y0]
4457 if {$idlist eq {} || $previdlist eq {}} continue
4458 if {$ym >= 0} {
4459 set pprevidlist [lindex $rowidlist $ym]
4460 if {$pprevidlist eq {}} continue
4461 } else {
4462 set pprevidlist {}
4464 set x0 -1
4465 set xm -1
4466 for {} {$col < [llength $idlist]} {incr col} {
4467 set id [lindex $idlist $col]
4468 if {[lindex $previdlist $col] eq $id} continue
4469 if {$id eq {}} {
4470 set haspad 1
4471 continue
4473 set x0 [lsearch -exact $previdlist $id]
4474 if {$x0 < 0} continue
4475 set z [expr {$x0 - $col}]
4476 set isarrow 0
4477 set z0 {}
4478 if {$ym >= 0} {
4479 set xm [lsearch -exact $pprevidlist $id]
4480 if {$xm >= 0} {
4481 set z0 [expr {$xm - $x0}]
4484 if {$z0 eq {}} {
4485 # if row y0 is the first child of $id then it's not an arrow
4486 if {[lindex $children($curview,$id) 0] ne
4487 [lindex $displayorder $y0]} {
4488 set isarrow 1
4491 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4492 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4493 set isarrow 1
4495 # Looking at lines from this row to the previous row,
4496 # make them go straight up if they end in an arrow on
4497 # the previous row; otherwise make them go straight up
4498 # or at 45 degrees.
4499 if {$z < -1 || ($z < 0 && $isarrow)} {
4500 # Line currently goes left too much;
4501 # insert pads in the previous row, then optimize it
4502 set npad [expr {-1 - $z + $isarrow}]
4503 insert_pad $y0 $x0 $npad
4504 if {$y0 > 0} {
4505 optimize_rows $y0 $x0 $row
4507 set previdlist [lindex $rowidlist $y0]
4508 set x0 [lsearch -exact $previdlist $id]
4509 set z [expr {$x0 - $col}]
4510 if {$z0 ne {}} {
4511 set pprevidlist [lindex $rowidlist $ym]
4512 set xm [lsearch -exact $pprevidlist $id]
4513 set z0 [expr {$xm - $x0}]
4515 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4516 # Line currently goes right too much;
4517 # insert pads in this line
4518 set npad [expr {$z - 1 + $isarrow}]
4519 insert_pad $row $col $npad
4520 set idlist [lindex $rowidlist $row]
4521 incr col $npad
4522 set z [expr {$x0 - $col}]
4523 set haspad 1
4525 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4526 # this line links to its first child on row $row-2
4527 set id [lindex $displayorder $ym]
4528 set xc [lsearch -exact $pprevidlist $id]
4529 if {$xc >= 0} {
4530 set z0 [expr {$xc - $x0}]
4533 # avoid lines jigging left then immediately right
4534 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4535 insert_pad $y0 $x0 1
4536 incr x0
4537 optimize_rows $y0 $x0 $row
4538 set previdlist [lindex $rowidlist $y0]
4541 if {!$haspad} {
4542 # Find the first column that doesn't have a line going right
4543 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4544 set id [lindex $idlist $col]
4545 if {$id eq {}} break
4546 set x0 [lsearch -exact $previdlist $id]
4547 if {$x0 < 0} {
4548 # check if this is the link to the first child
4549 set kid [lindex $displayorder $y0]
4550 if {[lindex $children($curview,$id) 0] eq $kid} {
4551 # it is, work out offset to child
4552 set x0 [lsearch -exact $previdlist $kid]
4555 if {$x0 <= $col} break
4557 # Insert a pad at that column as long as it has a line and
4558 # isn't the last column
4559 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4560 set idlist [linsert $idlist $col {}]
4561 lset rowidlist $row $idlist
4562 changedrow $row
4568 proc xc {row col} {
4569 global canvx0 linespc
4570 return [expr {$canvx0 + $col * $linespc}]
4573 proc yc {row} {
4574 global canvy0 linespc
4575 return [expr {$canvy0 + $row * $linespc}]
4578 proc linewidth {id} {
4579 global thickerline lthickness
4581 set wid $lthickness
4582 if {[info exists thickerline] && $id eq $thickerline} {
4583 set wid [expr {2 * $lthickness}]
4585 return $wid
4588 proc rowranges {id} {
4589 global curview children uparrowlen downarrowlen
4590 global rowidlist
4592 set kids $children($curview,$id)
4593 if {$kids eq {}} {
4594 return {}
4596 set ret {}
4597 lappend kids $id
4598 foreach child $kids {
4599 if {![commitinview $child $curview]} break
4600 set row [rowofcommit $child]
4601 if {![info exists prev]} {
4602 lappend ret [expr {$row + 1}]
4603 } else {
4604 if {$row <= $prevrow} {
4605 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4607 # see if the line extends the whole way from prevrow to row
4608 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4609 [lsearch -exact [lindex $rowidlist \
4610 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4611 # it doesn't, see where it ends
4612 set r [expr {$prevrow + $downarrowlen}]
4613 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4614 while {[incr r -1] > $prevrow &&
4615 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4616 } else {
4617 while {[incr r] <= $row &&
4618 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4619 incr r -1
4621 lappend ret $r
4622 # see where it starts up again
4623 set r [expr {$row - $uparrowlen}]
4624 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4625 while {[incr r] < $row &&
4626 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4627 } else {
4628 while {[incr r -1] >= $prevrow &&
4629 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4630 incr r
4632 lappend ret $r
4635 if {$child eq $id} {
4636 lappend ret $row
4638 set prev $child
4639 set prevrow $row
4641 return $ret
4644 proc drawlineseg {id row endrow arrowlow} {
4645 global rowidlist displayorder iddrawn linesegs
4646 global canv colormap linespc curview maxlinelen parentlist
4648 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4649 set le [expr {$row + 1}]
4650 set arrowhigh 1
4651 while {1} {
4652 set c [lsearch -exact [lindex $rowidlist $le] $id]
4653 if {$c < 0} {
4654 incr le -1
4655 break
4657 lappend cols $c
4658 set x [lindex $displayorder $le]
4659 if {$x eq $id} {
4660 set arrowhigh 0
4661 break
4663 if {[info exists iddrawn($x)] || $le == $endrow} {
4664 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
4665 if {$c >= 0} {
4666 lappend cols $c
4667 set arrowhigh 0
4669 break
4671 incr le
4673 if {$le <= $row} {
4674 return $row
4677 set lines {}
4678 set i 0
4679 set joinhigh 0
4680 if {[info exists linesegs($id)]} {
4681 set lines $linesegs($id)
4682 foreach li $lines {
4683 set r0 [lindex $li 0]
4684 if {$r0 > $row} {
4685 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
4686 set joinhigh 1
4688 break
4690 incr i
4693 set joinlow 0
4694 if {$i > 0} {
4695 set li [lindex $lines [expr {$i-1}]]
4696 set r1 [lindex $li 1]
4697 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
4698 set joinlow 1
4702 set x [lindex $cols [expr {$le - $row}]]
4703 set xp [lindex $cols [expr {$le - 1 - $row}]]
4704 set dir [expr {$xp - $x}]
4705 if {$joinhigh} {
4706 set ith [lindex $lines $i 2]
4707 set coords [$canv coords $ith]
4708 set ah [$canv itemcget $ith -arrow]
4709 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
4710 set x2 [lindex $cols [expr {$le + 1 - $row}]]
4711 if {$x2 ne {} && $x - $x2 == $dir} {
4712 set coords [lrange $coords 0 end-2]
4714 } else {
4715 set coords [list [xc $le $x] [yc $le]]
4717 if {$joinlow} {
4718 set itl [lindex $lines [expr {$i-1}] 2]
4719 set al [$canv itemcget $itl -arrow]
4720 set arrowlow [expr {$al eq "last" || $al eq "both"}]
4721 } elseif {$arrowlow} {
4722 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
4723 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
4724 set arrowlow 0
4727 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
4728 for {set y $le} {[incr y -1] > $row} {} {
4729 set x $xp
4730 set xp [lindex $cols [expr {$y - 1 - $row}]]
4731 set ndir [expr {$xp - $x}]
4732 if {$dir != $ndir || $xp < 0} {
4733 lappend coords [xc $y $x] [yc $y]
4735 set dir $ndir
4737 if {!$joinlow} {
4738 if {$xp < 0} {
4739 # join parent line to first child
4740 set ch [lindex $displayorder $row]
4741 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
4742 if {$xc < 0} {
4743 puts "oops: drawlineseg: child $ch not on row $row"
4744 } elseif {$xc != $x} {
4745 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
4746 set d [expr {int(0.5 * $linespc)}]
4747 set x1 [xc $row $x]
4748 if {$xc < $x} {
4749 set x2 [expr {$x1 - $d}]
4750 } else {
4751 set x2 [expr {$x1 + $d}]
4753 set y2 [yc $row]
4754 set y1 [expr {$y2 + $d}]
4755 lappend coords $x1 $y1 $x2 $y2
4756 } elseif {$xc < $x - 1} {
4757 lappend coords [xc $row [expr {$x-1}]] [yc $row]
4758 } elseif {$xc > $x + 1} {
4759 lappend coords [xc $row [expr {$x+1}]] [yc $row]
4761 set x $xc
4763 lappend coords [xc $row $x] [yc $row]
4764 } else {
4765 set xn [xc $row $xp]
4766 set yn [yc $row]
4767 lappend coords $xn $yn
4769 if {!$joinhigh} {
4770 assigncolor $id
4771 set t [$canv create line $coords -width [linewidth $id] \
4772 -fill $colormap($id) -tags lines.$id -arrow $arrow]
4773 $canv lower $t
4774 bindline $t $id
4775 set lines [linsert $lines $i [list $row $le $t]]
4776 } else {
4777 $canv coords $ith $coords
4778 if {$arrow ne $ah} {
4779 $canv itemconf $ith -arrow $arrow
4781 lset lines $i 0 $row
4783 } else {
4784 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
4785 set ndir [expr {$xo - $xp}]
4786 set clow [$canv coords $itl]
4787 if {$dir == $ndir} {
4788 set clow [lrange $clow 2 end]
4790 set coords [concat $coords $clow]
4791 if {!$joinhigh} {
4792 lset lines [expr {$i-1}] 1 $le
4793 } else {
4794 # coalesce two pieces
4795 $canv delete $ith
4796 set b [lindex $lines [expr {$i-1}] 0]
4797 set e [lindex $lines $i 1]
4798 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
4800 $canv coords $itl $coords
4801 if {$arrow ne $al} {
4802 $canv itemconf $itl -arrow $arrow
4806 set linesegs($id) $lines
4807 return $le
4810 proc drawparentlinks {id row} {
4811 global rowidlist canv colormap curview parentlist
4812 global idpos linespc
4814 set rowids [lindex $rowidlist $row]
4815 set col [lsearch -exact $rowids $id]
4816 if {$col < 0} return
4817 set olds [lindex $parentlist $row]
4818 set row2 [expr {$row + 1}]
4819 set x [xc $row $col]
4820 set y [yc $row]
4821 set y2 [yc $row2]
4822 set d [expr {int(0.5 * $linespc)}]
4823 set ymid [expr {$y + $d}]
4824 set ids [lindex $rowidlist $row2]
4825 # rmx = right-most X coord used
4826 set rmx 0
4827 foreach p $olds {
4828 set i [lsearch -exact $ids $p]
4829 if {$i < 0} {
4830 puts "oops, parent $p of $id not in list"
4831 continue
4833 set x2 [xc $row2 $i]
4834 if {$x2 > $rmx} {
4835 set rmx $x2
4837 set j [lsearch -exact $rowids $p]
4838 if {$j < 0} {
4839 # drawlineseg will do this one for us
4840 continue
4842 assigncolor $p
4843 # should handle duplicated parents here...
4844 set coords [list $x $y]
4845 if {$i != $col} {
4846 # if attaching to a vertical segment, draw a smaller
4847 # slant for visual distinctness
4848 if {$i == $j} {
4849 if {$i < $col} {
4850 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
4851 } else {
4852 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
4854 } elseif {$i < $col && $i < $j} {
4855 # segment slants towards us already
4856 lappend coords [xc $row $j] $y
4857 } else {
4858 if {$i < $col - 1} {
4859 lappend coords [expr {$x2 + $linespc}] $y
4860 } elseif {$i > $col + 1} {
4861 lappend coords [expr {$x2 - $linespc}] $y
4863 lappend coords $x2 $y2
4865 } else {
4866 lappend coords $x2 $y2
4868 set t [$canv create line $coords -width [linewidth $p] \
4869 -fill $colormap($p) -tags lines.$p]
4870 $canv lower $t
4871 bindline $t $p
4873 if {$rmx > [lindex $idpos($id) 1]} {
4874 lset idpos($id) 1 $rmx
4875 redrawtags $id
4879 proc drawlines {id} {
4880 global canv
4882 $canv itemconf lines.$id -width [linewidth $id]
4885 proc drawcmittext {id row col} {
4886 global linespc canv canv2 canv3 fgcolor curview
4887 global cmitlisted commitinfo rowidlist parentlist
4888 global rowtextx idpos idtags idheads idotherrefs
4889 global linehtag linentag linedtag selectedline
4890 global canvxmax boldrows boldnamerows fgcolor
4891 global mainheadid nullid nullid2 circleitem circlecolors
4893 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
4894 set listed $cmitlisted($curview,$id)
4895 if {$id eq $nullid} {
4896 set ofill red
4897 } elseif {$id eq $nullid2} {
4898 set ofill green
4899 } elseif {$id eq $mainheadid} {
4900 set ofill yellow
4901 } else {
4902 set ofill [lindex $circlecolors $listed]
4904 set x [xc $row $col]
4905 set y [yc $row]
4906 set orad [expr {$linespc / 3}]
4907 if {$listed <= 2} {
4908 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
4909 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4910 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4911 } elseif {$listed == 3} {
4912 # triangle pointing left for left-side commits
4913 set t [$canv create polygon \
4914 [expr {$x - $orad}] $y \
4915 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
4916 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
4917 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4918 } else {
4919 # triangle pointing right for right-side commits
4920 set t [$canv create polygon \
4921 [expr {$x + $orad - 1}] $y \
4922 [expr {$x - $orad}] [expr {$y - $orad}] \
4923 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
4924 -fill $ofill -outline $fgcolor -width 1 -tags circle]
4926 set circleitem($row) $t
4927 $canv raise $t
4928 $canv bind $t <1> {selcanvline {} %x %y}
4929 set rmx [llength [lindex $rowidlist $row]]
4930 set olds [lindex $parentlist $row]
4931 if {$olds ne {}} {
4932 set nextids [lindex $rowidlist [expr {$row + 1}]]
4933 foreach p $olds {
4934 set i [lsearch -exact $nextids $p]
4935 if {$i > $rmx} {
4936 set rmx $i
4940 set xt [xc $row $rmx]
4941 set rowtextx($row) $xt
4942 set idpos($id) [list $x $xt $y]
4943 if {[info exists idtags($id)] || [info exists idheads($id)]
4944 || [info exists idotherrefs($id)]} {
4945 set xt [drawtags $id $x $xt $y]
4947 set headline [lindex $commitinfo($id) 0]
4948 set name [lindex $commitinfo($id) 1]
4949 set date [lindex $commitinfo($id) 2]
4950 set date [formatdate $date]
4951 set font mainfont
4952 set nfont mainfont
4953 set isbold [ishighlighted $id]
4954 if {$isbold > 0} {
4955 lappend boldrows $row
4956 set font mainfontbold
4957 if {$isbold > 1} {
4958 lappend boldnamerows $row
4959 set nfont mainfontbold
4962 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
4963 -text $headline -font $font -tags text]
4964 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
4965 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
4966 -text $name -font $nfont -tags text]
4967 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
4968 -text $date -font mainfont -tags text]
4969 if {$selectedline == $row} {
4970 make_secsel $row
4972 set xr [expr {$xt + [font measure $font $headline]}]
4973 if {$xr > $canvxmax} {
4974 set canvxmax $xr
4975 setcanvscroll
4979 proc drawcmitrow {row} {
4980 global displayorder rowidlist nrows_drawn
4981 global iddrawn markingmatches
4982 global commitinfo numcommits
4983 global filehighlight fhighlights findpattern nhighlights
4984 global hlview vhighlights
4985 global highlight_related rhighlights
4987 if {$row >= $numcommits} return
4989 set id [lindex $displayorder $row]
4990 if {[info exists hlview] && ![info exists vhighlights($id)]} {
4991 askvhighlight $row $id
4993 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
4994 askfilehighlight $row $id
4996 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
4997 askfindhighlight $row $id
4999 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5000 askrelhighlight $row $id
5002 if {![info exists iddrawn($id)]} {
5003 set col [lsearch -exact [lindex $rowidlist $row] $id]
5004 if {$col < 0} {
5005 puts "oops, row $row id $id not in list"
5006 return
5008 if {![info exists commitinfo($id)]} {
5009 getcommit $id
5011 assigncolor $id
5012 drawcmittext $id $row $col
5013 set iddrawn($id) 1
5014 incr nrows_drawn
5016 if {$markingmatches} {
5017 markrowmatches $row $id
5021 proc drawcommits {row {endrow {}}} {
5022 global numcommits iddrawn displayorder curview need_redisplay
5023 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5025 if {$row < 0} {
5026 set row 0
5028 if {$endrow eq {}} {
5029 set endrow $row
5031 if {$endrow >= $numcommits} {
5032 set endrow [expr {$numcommits - 1}]
5035 set rl1 [expr {$row - $downarrowlen - 3}]
5036 if {$rl1 < 0} {
5037 set rl1 0
5039 set ro1 [expr {$row - 3}]
5040 if {$ro1 < 0} {
5041 set ro1 0
5043 set r2 [expr {$endrow + $uparrowlen + 3}]
5044 if {$r2 > $numcommits} {
5045 set r2 $numcommits
5047 for {set r $rl1} {$r < $r2} {incr r} {
5048 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5049 if {$rl1 < $r} {
5050 layoutrows $rl1 $r
5052 set rl1 [expr {$r + 1}]
5055 if {$rl1 < $r} {
5056 layoutrows $rl1 $r
5058 optimize_rows $ro1 0 $r2
5059 if {$need_redisplay || $nrows_drawn > 2000} {
5060 clear_display
5061 drawvisible
5064 # make the lines join to already-drawn rows either side
5065 set r [expr {$row - 1}]
5066 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5067 set r $row
5069 set er [expr {$endrow + 1}]
5070 if {$er >= $numcommits ||
5071 ![info exists iddrawn([lindex $displayorder $er])]} {
5072 set er $endrow
5074 for {} {$r <= $er} {incr r} {
5075 set id [lindex $displayorder $r]
5076 set wasdrawn [info exists iddrawn($id)]
5077 drawcmitrow $r
5078 if {$r == $er} break
5079 set nextid [lindex $displayorder [expr {$r + 1}]]
5080 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5081 drawparentlinks $id $r
5083 set rowids [lindex $rowidlist $r]
5084 foreach lid $rowids {
5085 if {$lid eq {}} continue
5086 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5087 if {$lid eq $id} {
5088 # see if this is the first child of any of its parents
5089 foreach p [lindex $parentlist $r] {
5090 if {[lsearch -exact $rowids $p] < 0} {
5091 # make this line extend up to the child
5092 set lineend($p) [drawlineseg $p $r $er 0]
5095 } else {
5096 set lineend($lid) [drawlineseg $lid $r $er 1]
5102 proc undolayout {row} {
5103 global uparrowlen mingaplen downarrowlen
5104 global rowidlist rowisopt rowfinal need_redisplay
5106 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5107 if {$r < 0} {
5108 set r 0
5110 if {[llength $rowidlist] > $r} {
5111 incr r -1
5112 set rowidlist [lrange $rowidlist 0 $r]
5113 set rowfinal [lrange $rowfinal 0 $r]
5114 set rowisopt [lrange $rowisopt 0 $r]
5115 set need_redisplay 1
5116 run drawvisible
5120 proc drawvisible {} {
5121 global canv linespc curview vrowmod selectedline targetrow targetid
5122 global need_redisplay cscroll numcommits
5124 set fs [$canv yview]
5125 set ymax [lindex [$canv cget -scrollregion] 3]
5126 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5127 set f0 [lindex $fs 0]
5128 set f1 [lindex $fs 1]
5129 set y0 [expr {int($f0 * $ymax)}]
5130 set y1 [expr {int($f1 * $ymax)}]
5132 if {[info exists targetid]} {
5133 if {[commitinview $targetid $curview]} {
5134 set r [rowofcommit $targetid]
5135 if {$r != $targetrow} {
5136 # Fix up the scrollregion and change the scrolling position
5137 # now that our target row has moved.
5138 set diff [expr {($r - $targetrow) * $linespc}]
5139 set targetrow $r
5140 setcanvscroll
5141 set ymax [lindex [$canv cget -scrollregion] 3]
5142 incr y0 $diff
5143 incr y1 $diff
5144 set f0 [expr {$y0 / $ymax}]
5145 set f1 [expr {$y1 / $ymax}]
5146 allcanvs yview moveto $f0
5147 $cscroll set $f0 $f1
5148 set need_redisplay 1
5150 } else {
5151 unset targetid
5155 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5156 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5157 if {$endrow >= $vrowmod($curview)} {
5158 update_arcrows $curview
5160 if {$selectedline ne {} &&
5161 $row <= $selectedline && $selectedline <= $endrow} {
5162 set targetrow $selectedline
5163 } elseif {[info exists targetid]} {
5164 set targetrow [expr {int(($row + $endrow) / 2)}]
5166 if {[info exists targetrow]} {
5167 if {$targetrow >= $numcommits} {
5168 set targetrow [expr {$numcommits - 1}]
5170 set targetid [commitonrow $targetrow]
5172 drawcommits $row $endrow
5175 proc clear_display {} {
5176 global iddrawn linesegs need_redisplay nrows_drawn
5177 global vhighlights fhighlights nhighlights rhighlights
5178 global linehtag linentag linedtag boldrows boldnamerows
5180 allcanvs delete all
5181 catch {unset iddrawn}
5182 catch {unset linesegs}
5183 catch {unset linehtag}
5184 catch {unset linentag}
5185 catch {unset linedtag}
5186 set boldrows {}
5187 set boldnamerows {}
5188 catch {unset vhighlights}
5189 catch {unset fhighlights}
5190 catch {unset nhighlights}
5191 catch {unset rhighlights}
5192 set need_redisplay 0
5193 set nrows_drawn 0
5196 proc findcrossings {id} {
5197 global rowidlist parentlist numcommits displayorder
5199 set cross {}
5200 set ccross {}
5201 foreach {s e} [rowranges $id] {
5202 if {$e >= $numcommits} {
5203 set e [expr {$numcommits - 1}]
5205 if {$e <= $s} continue
5206 for {set row $e} {[incr row -1] >= $s} {} {
5207 set x [lsearch -exact [lindex $rowidlist $row] $id]
5208 if {$x < 0} break
5209 set olds [lindex $parentlist $row]
5210 set kid [lindex $displayorder $row]
5211 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5212 if {$kidx < 0} continue
5213 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5214 foreach p $olds {
5215 set px [lsearch -exact $nextrow $p]
5216 if {$px < 0} continue
5217 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5218 if {[lsearch -exact $ccross $p] >= 0} continue
5219 if {$x == $px + ($kidx < $px? -1: 1)} {
5220 lappend ccross $p
5221 } elseif {[lsearch -exact $cross $p] < 0} {
5222 lappend cross $p
5228 return [concat $ccross {{}} $cross]
5231 proc assigncolor {id} {
5232 global colormap colors nextcolor
5233 global parents children children curview
5235 if {[info exists colormap($id)]} return
5236 set ncolors [llength $colors]
5237 if {[info exists children($curview,$id)]} {
5238 set kids $children($curview,$id)
5239 } else {
5240 set kids {}
5242 if {[llength $kids] == 1} {
5243 set child [lindex $kids 0]
5244 if {[info exists colormap($child)]
5245 && [llength $parents($curview,$child)] == 1} {
5246 set colormap($id) $colormap($child)
5247 return
5250 set badcolors {}
5251 set origbad {}
5252 foreach x [findcrossings $id] {
5253 if {$x eq {}} {
5254 # delimiter between corner crossings and other crossings
5255 if {[llength $badcolors] >= $ncolors - 1} break
5256 set origbad $badcolors
5258 if {[info exists colormap($x)]
5259 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5260 lappend badcolors $colormap($x)
5263 if {[llength $badcolors] >= $ncolors} {
5264 set badcolors $origbad
5266 set origbad $badcolors
5267 if {[llength $badcolors] < $ncolors - 1} {
5268 foreach child $kids {
5269 if {[info exists colormap($child)]
5270 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5271 lappend badcolors $colormap($child)
5273 foreach p $parents($curview,$child) {
5274 if {[info exists colormap($p)]
5275 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5276 lappend badcolors $colormap($p)
5280 if {[llength $badcolors] >= $ncolors} {
5281 set badcolors $origbad
5284 for {set i 0} {$i <= $ncolors} {incr i} {
5285 set c [lindex $colors $nextcolor]
5286 if {[incr nextcolor] >= $ncolors} {
5287 set nextcolor 0
5289 if {[lsearch -exact $badcolors $c]} break
5291 set colormap($id) $c
5294 proc bindline {t id} {
5295 global canv
5297 $canv bind $t <Enter> "lineenter %x %y $id"
5298 $canv bind $t <Motion> "linemotion %x %y $id"
5299 $canv bind $t <Leave> "lineleave $id"
5300 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5303 proc drawtags {id x xt y1} {
5304 global idtags idheads idotherrefs mainhead
5305 global linespc lthickness
5306 global canv rowtextx curview fgcolor bgcolor
5308 set marks {}
5309 set ntags 0
5310 set nheads 0
5311 if {[info exists idtags($id)]} {
5312 set marks $idtags($id)
5313 set ntags [llength $marks]
5315 if {[info exists idheads($id)]} {
5316 set marks [concat $marks $idheads($id)]
5317 set nheads [llength $idheads($id)]
5319 if {[info exists idotherrefs($id)]} {
5320 set marks [concat $marks $idotherrefs($id)]
5322 if {$marks eq {}} {
5323 return $xt
5326 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5327 set yt [expr {$y1 - 0.5 * $linespc}]
5328 set yb [expr {$yt + $linespc - 1}]
5329 set xvals {}
5330 set wvals {}
5331 set i -1
5332 foreach tag $marks {
5333 incr i
5334 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5335 set wid [font measure mainfontbold $tag]
5336 } else {
5337 set wid [font measure mainfont $tag]
5339 lappend xvals $xt
5340 lappend wvals $wid
5341 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5343 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5344 -width $lthickness -fill black -tags tag.$id]
5345 $canv lower $t
5346 foreach tag $marks x $xvals wid $wvals {
5347 set xl [expr {$x + $delta}]
5348 set xr [expr {$x + $delta + $wid + $lthickness}]
5349 set font mainfont
5350 if {[incr ntags -1] >= 0} {
5351 # draw a tag
5352 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5353 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5354 -width 1 -outline black -fill yellow -tags tag.$id]
5355 $canv bind $t <1> [list showtag $tag 1]
5356 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5357 } else {
5358 # draw a head or other ref
5359 if {[incr nheads -1] >= 0} {
5360 set col green
5361 if {$tag eq $mainhead} {
5362 set font mainfontbold
5364 } else {
5365 set col "#ddddff"
5367 set xl [expr {$xl - $delta/2}]
5368 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5369 -width 1 -outline black -fill $col -tags tag.$id
5370 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5371 set rwid [font measure mainfont $remoteprefix]
5372 set xi [expr {$x + 1}]
5373 set yti [expr {$yt + 1}]
5374 set xri [expr {$x + $rwid}]
5375 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5376 -width 0 -fill "#ffddaa" -tags tag.$id
5379 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5380 -font $font -tags [list tag.$id text]]
5381 if {$ntags >= 0} {
5382 $canv bind $t <1> [list showtag $tag 1]
5383 } elseif {$nheads >= 0} {
5384 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
5387 return $xt
5390 proc xcoord {i level ln} {
5391 global canvx0 xspc1 xspc2
5393 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5394 if {$i > 0 && $i == $level} {
5395 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5396 } elseif {$i > $level} {
5397 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5399 return $x
5402 proc show_status {msg} {
5403 global canv fgcolor
5405 clear_display
5406 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5407 -tags text -fill $fgcolor
5410 # Don't change the text pane cursor if it is currently the hand cursor,
5411 # showing that we are over a sha1 ID link.
5412 proc settextcursor {c} {
5413 global ctext curtextcursor
5415 if {[$ctext cget -cursor] == $curtextcursor} {
5416 $ctext config -cursor $c
5418 set curtextcursor $c
5421 proc nowbusy {what {name {}}} {
5422 global isbusy busyname statusw
5424 if {[array names isbusy] eq {}} {
5425 . config -cursor watch
5426 settextcursor watch
5428 set isbusy($what) 1
5429 set busyname($what) $name
5430 if {$name ne {}} {
5431 $statusw conf -text $name
5435 proc notbusy {what} {
5436 global isbusy maincursor textcursor busyname statusw
5438 catch {
5439 unset isbusy($what)
5440 if {$busyname($what) ne {} &&
5441 [$statusw cget -text] eq $busyname($what)} {
5442 $statusw conf -text {}
5445 if {[array names isbusy] eq {}} {
5446 . config -cursor $maincursor
5447 settextcursor $textcursor
5451 proc findmatches {f} {
5452 global findtype findstring
5453 if {$findtype == [mc "Regexp"]} {
5454 set matches [regexp -indices -all -inline $findstring $f]
5455 } else {
5456 set fs $findstring
5457 if {$findtype == [mc "IgnCase"]} {
5458 set f [string tolower $f]
5459 set fs [string tolower $fs]
5461 set matches {}
5462 set i 0
5463 set l [string length $fs]
5464 while {[set j [string first $fs $f $i]] >= 0} {
5465 lappend matches [list $j [expr {$j+$l-1}]]
5466 set i [expr {$j + $l}]
5469 return $matches
5472 proc dofind {{dirn 1} {wrap 1}} {
5473 global findstring findstartline findcurline selectedline numcommits
5474 global gdttype filehighlight fh_serial find_dirn findallowwrap
5476 if {[info exists find_dirn]} {
5477 if {$find_dirn == $dirn} return
5478 stopfinding
5480 focus .
5481 if {$findstring eq {} || $numcommits == 0} return
5482 if {$selectedline eq {}} {
5483 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5484 } else {
5485 set findstartline $selectedline
5487 set findcurline $findstartline
5488 nowbusy finding [mc "Searching"]
5489 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5490 after cancel do_file_hl $fh_serial
5491 do_file_hl $fh_serial
5493 set find_dirn $dirn
5494 set findallowwrap $wrap
5495 run findmore
5498 proc stopfinding {} {
5499 global find_dirn findcurline fprogcoord
5501 if {[info exists find_dirn]} {
5502 unset find_dirn
5503 unset findcurline
5504 notbusy finding
5505 set fprogcoord 0
5506 adjustprogress
5510 proc findmore {} {
5511 global commitdata commitinfo numcommits findpattern findloc
5512 global findstartline findcurline findallowwrap
5513 global find_dirn gdttype fhighlights fprogcoord
5514 global curview varcorder vrownum varccommits vrowmod
5516 if {![info exists find_dirn]} {
5517 return 0
5519 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5520 set l $findcurline
5521 set moretodo 0
5522 if {$find_dirn > 0} {
5523 incr l
5524 if {$l >= $numcommits} {
5525 set l 0
5527 if {$l <= $findstartline} {
5528 set lim [expr {$findstartline + 1}]
5529 } else {
5530 set lim $numcommits
5531 set moretodo $findallowwrap
5533 } else {
5534 if {$l == 0} {
5535 set l $numcommits
5537 incr l -1
5538 if {$l >= $findstartline} {
5539 set lim [expr {$findstartline - 1}]
5540 } else {
5541 set lim -1
5542 set moretodo $findallowwrap
5545 set n [expr {($lim - $l) * $find_dirn}]
5546 if {$n > 500} {
5547 set n 500
5548 set moretodo 1
5550 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5551 update_arcrows $curview
5553 set found 0
5554 set domore 1
5555 set ai [bsearch $vrownum($curview) $l]
5556 set a [lindex $varcorder($curview) $ai]
5557 set arow [lindex $vrownum($curview) $ai]
5558 set ids [lindex $varccommits($curview,$a)]
5559 set arowend [expr {$arow + [llength $ids]}]
5560 if {$gdttype eq [mc "containing:"]} {
5561 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5562 if {$l < $arow || $l >= $arowend} {
5563 incr ai $find_dirn
5564 set a [lindex $varcorder($curview) $ai]
5565 set arow [lindex $vrownum($curview) $ai]
5566 set ids [lindex $varccommits($curview,$a)]
5567 set arowend [expr {$arow + [llength $ids]}]
5569 set id [lindex $ids [expr {$l - $arow}]]
5570 # shouldn't happen unless git log doesn't give all the commits...
5571 if {![info exists commitdata($id)] ||
5572 ![doesmatch $commitdata($id)]} {
5573 continue
5575 if {![info exists commitinfo($id)]} {
5576 getcommit $id
5578 set info $commitinfo($id)
5579 foreach f $info ty $fldtypes {
5580 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5581 [doesmatch $f]} {
5582 set found 1
5583 break
5586 if {$found} break
5588 } else {
5589 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5590 if {$l < $arow || $l >= $arowend} {
5591 incr ai $find_dirn
5592 set a [lindex $varcorder($curview) $ai]
5593 set arow [lindex $vrownum($curview) $ai]
5594 set ids [lindex $varccommits($curview,$a)]
5595 set arowend [expr {$arow + [llength $ids]}]
5597 set id [lindex $ids [expr {$l - $arow}]]
5598 if {![info exists fhighlights($id)]} {
5599 # this sets fhighlights($id) to -1
5600 askfilehighlight $l $id
5602 if {$fhighlights($id) > 0} {
5603 set found $domore
5604 break
5606 if {$fhighlights($id) < 0} {
5607 if {$domore} {
5608 set domore 0
5609 set findcurline [expr {$l - $find_dirn}]
5614 if {$found || ($domore && !$moretodo)} {
5615 unset findcurline
5616 unset find_dirn
5617 notbusy finding
5618 set fprogcoord 0
5619 adjustprogress
5620 if {$found} {
5621 findselectline $l
5622 } else {
5623 bell
5625 return 0
5627 if {!$domore} {
5628 flushhighlights
5629 } else {
5630 set findcurline [expr {$l - $find_dirn}]
5632 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5633 if {$n < 0} {
5634 incr n $numcommits
5636 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5637 adjustprogress
5638 return $domore
5641 proc findselectline {l} {
5642 global findloc commentend ctext findcurline markingmatches gdttype
5644 set markingmatches 1
5645 set findcurline $l
5646 selectline $l 1
5647 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5648 # highlight the matches in the comments
5649 set f [$ctext get 1.0 $commentend]
5650 set matches [findmatches $f]
5651 foreach match $matches {
5652 set start [lindex $match 0]
5653 set end [expr {[lindex $match 1] + 1}]
5654 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5657 drawvisible
5660 # mark the bits of a headline or author that match a find string
5661 proc markmatches {canv l str tag matches font row} {
5662 global selectedline
5664 set bbox [$canv bbox $tag]
5665 set x0 [lindex $bbox 0]
5666 set y0 [lindex $bbox 1]
5667 set y1 [lindex $bbox 3]
5668 foreach match $matches {
5669 set start [lindex $match 0]
5670 set end [lindex $match 1]
5671 if {$start > $end} continue
5672 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
5673 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
5674 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
5675 [expr {$x0+$xlen+2}] $y1 \
5676 -outline {} -tags [list match$l matches] -fill yellow]
5677 $canv lower $t
5678 if {$row == $selectedline} {
5679 $canv raise $t secsel
5684 proc unmarkmatches {} {
5685 global markingmatches
5687 allcanvs delete matches
5688 set markingmatches 0
5689 stopfinding
5692 proc selcanvline {w x y} {
5693 global canv canvy0 ctext linespc
5694 global rowtextx
5695 set ymax [lindex [$canv cget -scrollregion] 3]
5696 if {$ymax == {}} return
5697 set yfrac [lindex [$canv yview] 0]
5698 set y [expr {$y + $yfrac * $ymax}]
5699 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
5700 if {$l < 0} {
5701 set l 0
5703 if {$w eq $canv} {
5704 set xmax [lindex [$canv cget -scrollregion] 2]
5705 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
5706 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
5708 unmarkmatches
5709 selectline $l 1
5712 proc commit_descriptor {p} {
5713 global commitinfo
5714 if {![info exists commitinfo($p)]} {
5715 getcommit $p
5717 set l "..."
5718 if {[llength $commitinfo($p)] > 1} {
5719 set l [lindex $commitinfo($p) 0]
5721 return "$p ($l)\n"
5724 # append some text to the ctext widget, and make any SHA1 ID
5725 # that we know about be a clickable link.
5726 proc appendwithlinks {text tags} {
5727 global ctext linknum curview pendinglinks
5729 set start [$ctext index "end - 1c"]
5730 $ctext insert end $text $tags
5731 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
5732 foreach l $links {
5733 set s [lindex $l 0]
5734 set e [lindex $l 1]
5735 set linkid [string range $text $s $e]
5736 incr e
5737 $ctext tag delete link$linknum
5738 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
5739 setlink $linkid link$linknum
5740 incr linknum
5744 proc setlink {id lk} {
5745 global curview ctext pendinglinks commitinterest
5747 if {[commitinview $id $curview]} {
5748 $ctext tag conf $lk -foreground blue -underline 1
5749 $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
5750 $ctext tag bind $lk <Enter> {linkcursor %W 1}
5751 $ctext tag bind $lk <Leave> {linkcursor %W -1}
5752 } else {
5753 lappend pendinglinks($id) $lk
5754 lappend commitinterest($id) {makelink %I}
5758 proc makelink {id} {
5759 global pendinglinks
5761 if {![info exists pendinglinks($id)]} return
5762 foreach lk $pendinglinks($id) {
5763 setlink $id $lk
5765 unset pendinglinks($id)
5768 proc linkcursor {w inc} {
5769 global linkentercount curtextcursor
5771 if {[incr linkentercount $inc] > 0} {
5772 $w configure -cursor hand2
5773 } else {
5774 $w configure -cursor $curtextcursor
5775 if {$linkentercount < 0} {
5776 set linkentercount 0
5781 proc viewnextline {dir} {
5782 global canv linespc
5784 $canv delete hover
5785 set ymax [lindex [$canv cget -scrollregion] 3]
5786 set wnow [$canv yview]
5787 set wtop [expr {[lindex $wnow 0] * $ymax}]
5788 set newtop [expr {$wtop + $dir * $linespc}]
5789 if {$newtop < 0} {
5790 set newtop 0
5791 } elseif {$newtop > $ymax} {
5792 set newtop $ymax
5794 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5797 # add a list of tag or branch names at position pos
5798 # returns the number of names inserted
5799 proc appendrefs {pos ids var} {
5800 global ctext linknum curview $var maxrefs
5802 if {[catch {$ctext index $pos}]} {
5803 return 0
5805 $ctext conf -state normal
5806 $ctext delete $pos "$pos lineend"
5807 set tags {}
5808 foreach id $ids {
5809 foreach tag [set $var\($id\)] {
5810 lappend tags [list $tag $id]
5813 if {[llength $tags] > $maxrefs} {
5814 $ctext insert $pos "many ([llength $tags])"
5815 } else {
5816 set tags [lsort -index 0 -decreasing $tags]
5817 set sep {}
5818 foreach ti $tags {
5819 set id [lindex $ti 1]
5820 set lk link$linknum
5821 incr linknum
5822 $ctext tag delete $lk
5823 $ctext insert $pos $sep
5824 $ctext insert $pos [lindex $ti 0] $lk
5825 setlink $id $lk
5826 set sep ", "
5829 $ctext conf -state disabled
5830 return [llength $tags]
5833 # called when we have finished computing the nearby tags
5834 proc dispneartags {delay} {
5835 global selectedline currentid showneartags tagphase
5837 if {$selectedline eq {} || !$showneartags} return
5838 after cancel dispnexttag
5839 if {$delay} {
5840 after 200 dispnexttag
5841 set tagphase -1
5842 } else {
5843 after idle dispnexttag
5844 set tagphase 0
5848 proc dispnexttag {} {
5849 global selectedline currentid showneartags tagphase ctext
5851 if {$selectedline eq {} || !$showneartags} return
5852 switch -- $tagphase {
5854 set dtags [desctags $currentid]
5855 if {$dtags ne {}} {
5856 appendrefs precedes $dtags idtags
5860 set atags [anctags $currentid]
5861 if {$atags ne {}} {
5862 appendrefs follows $atags idtags
5866 set dheads [descheads $currentid]
5867 if {$dheads ne {}} {
5868 if {[appendrefs branch $dheads idheads] > 1
5869 && [$ctext get "branch -3c"] eq "h"} {
5870 # turn "Branch" into "Branches"
5871 $ctext conf -state normal
5872 $ctext insert "branch -2c" "es"
5873 $ctext conf -state disabled
5878 if {[incr tagphase] <= 2} {
5879 after idle dispnexttag
5883 proc make_secsel {l} {
5884 global linehtag linentag linedtag canv canv2 canv3
5886 if {![info exists linehtag($l)]} return
5887 $canv delete secsel
5888 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
5889 -tags secsel -fill [$canv cget -selectbackground]]
5890 $canv lower $t
5891 $canv2 delete secsel
5892 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
5893 -tags secsel -fill [$canv2 cget -selectbackground]]
5894 $canv2 lower $t
5895 $canv3 delete secsel
5896 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
5897 -tags secsel -fill [$canv3 cget -selectbackground]]
5898 $canv3 lower $t
5901 proc selectline {l isnew} {
5902 global canv ctext commitinfo selectedline
5903 global canvy0 linespc parents children curview
5904 global currentid sha1entry
5905 global commentend idtags linknum
5906 global mergemax numcommits pending_select
5907 global cmitmode showneartags allcommits
5908 global targetrow targetid lastscrollrows
5909 global autoselect
5911 catch {unset pending_select}
5912 $canv delete hover
5913 normalline
5914 unsel_reflist
5915 stopfinding
5916 if {$l < 0 || $l >= $numcommits} return
5917 set id [commitonrow $l]
5918 set targetid $id
5919 set targetrow $l
5920 set selectedline $l
5921 set currentid $id
5922 if {$lastscrollrows < $numcommits} {
5923 setcanvscroll
5926 set y [expr {$canvy0 + $l * $linespc}]
5927 set ymax [lindex [$canv cget -scrollregion] 3]
5928 set ytop [expr {$y - $linespc - 1}]
5929 set ybot [expr {$y + $linespc + 1}]
5930 set wnow [$canv yview]
5931 set wtop [expr {[lindex $wnow 0] * $ymax}]
5932 set wbot [expr {[lindex $wnow 1] * $ymax}]
5933 set wh [expr {$wbot - $wtop}]
5934 set newtop $wtop
5935 if {$ytop < $wtop} {
5936 if {$ybot < $wtop} {
5937 set newtop [expr {$y - $wh / 2.0}]
5938 } else {
5939 set newtop $ytop
5940 if {$newtop > $wtop - $linespc} {
5941 set newtop [expr {$wtop - $linespc}]
5944 } elseif {$ybot > $wbot} {
5945 if {$ytop > $wbot} {
5946 set newtop [expr {$y - $wh / 2.0}]
5947 } else {
5948 set newtop [expr {$ybot - $wh}]
5949 if {$newtop < $wtop + $linespc} {
5950 set newtop [expr {$wtop + $linespc}]
5954 if {$newtop != $wtop} {
5955 if {$newtop < 0} {
5956 set newtop 0
5958 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
5959 drawvisible
5962 make_secsel $l
5964 if {$isnew} {
5965 addtohistory [list selbyid $id]
5968 $sha1entry delete 0 end
5969 $sha1entry insert 0 $id
5970 if {$autoselect} {
5971 $sha1entry selection from 0
5972 $sha1entry selection to end
5974 rhighlight_sel $id
5976 $ctext conf -state normal
5977 clear_ctext
5978 set linknum 0
5979 if {![info exists commitinfo($id)]} {
5980 getcommit $id
5982 set info $commitinfo($id)
5983 set date [formatdate [lindex $info 2]]
5984 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
5985 set date [formatdate [lindex $info 4]]
5986 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
5987 if {[info exists idtags($id)]} {
5988 $ctext insert end [mc "Tags:"]
5989 foreach tag $idtags($id) {
5990 $ctext insert end " $tag"
5992 $ctext insert end "\n"
5995 set headers {}
5996 set olds $parents($curview,$id)
5997 if {[llength $olds] > 1} {
5998 set np 0
5999 foreach p $olds {
6000 if {$np >= $mergemax} {
6001 set tag mmax
6002 } else {
6003 set tag m$np
6005 $ctext insert end "[mc "Parent"]: " $tag
6006 appendwithlinks [commit_descriptor $p] {}
6007 incr np
6009 } else {
6010 foreach p $olds {
6011 append headers "[mc "Parent"]: [commit_descriptor $p]"
6015 foreach c $children($curview,$id) {
6016 append headers "[mc "Child"]: [commit_descriptor $c]"
6019 # make anything that looks like a SHA1 ID be a clickable link
6020 appendwithlinks $headers {}
6021 if {$showneartags} {
6022 if {![info exists allcommits]} {
6023 getallcommits
6025 $ctext insert end "[mc "Branch"]: "
6026 $ctext mark set branch "end -1c"
6027 $ctext mark gravity branch left
6028 $ctext insert end "\n[mc "Follows"]: "
6029 $ctext mark set follows "end -1c"
6030 $ctext mark gravity follows left
6031 $ctext insert end "\n[mc "Precedes"]: "
6032 $ctext mark set precedes "end -1c"
6033 $ctext mark gravity precedes left
6034 $ctext insert end "\n"
6035 dispneartags 1
6037 $ctext insert end "\n"
6038 set comment [lindex $info 5]
6039 if {[string first "\r" $comment] >= 0} {
6040 set comment [string map {"\r" "\n "} $comment]
6042 appendwithlinks $comment {comment}
6044 $ctext tag remove found 1.0 end
6045 $ctext conf -state disabled
6046 set commentend [$ctext index "end - 1c"]
6048 init_flist [mc "Comments"]
6049 if {$cmitmode eq "tree"} {
6050 gettree $id
6051 } elseif {[llength $olds] <= 1} {
6052 startdiff $id
6053 } else {
6054 mergediff $id
6058 proc selfirstline {} {
6059 unmarkmatches
6060 selectline 0 1
6063 proc sellastline {} {
6064 global numcommits
6065 unmarkmatches
6066 set l [expr {$numcommits - 1}]
6067 selectline $l 1
6070 proc selnextline {dir} {
6071 global selectedline
6072 focus .
6073 if {$selectedline eq {}} return
6074 set l [expr {$selectedline + $dir}]
6075 unmarkmatches
6076 selectline $l 1
6079 proc selnextpage {dir} {
6080 global canv linespc selectedline numcommits
6082 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6083 if {$lpp < 1} {
6084 set lpp 1
6086 allcanvs yview scroll [expr {$dir * $lpp}] units
6087 drawvisible
6088 if {$selectedline eq {}} return
6089 set l [expr {$selectedline + $dir * $lpp}]
6090 if {$l < 0} {
6091 set l 0
6092 } elseif {$l >= $numcommits} {
6093 set l [expr $numcommits - 1]
6095 unmarkmatches
6096 selectline $l 1
6099 proc unselectline {} {
6100 global selectedline currentid
6102 set selectedline {}
6103 catch {unset currentid}
6104 allcanvs delete secsel
6105 rhighlight_none
6108 proc reselectline {} {
6109 global selectedline
6111 if {$selectedline ne {}} {
6112 selectline $selectedline 0
6116 proc addtohistory {cmd} {
6117 global history historyindex curview
6119 set elt [list $curview $cmd]
6120 if {$historyindex > 0
6121 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6122 return
6125 if {$historyindex < [llength $history]} {
6126 set history [lreplace $history $historyindex end $elt]
6127 } else {
6128 lappend history $elt
6130 incr historyindex
6131 if {$historyindex > 1} {
6132 .tf.bar.leftbut conf -state normal
6133 } else {
6134 .tf.bar.leftbut conf -state disabled
6136 .tf.bar.rightbut conf -state disabled
6139 proc godo {elt} {
6140 global curview
6142 set view [lindex $elt 0]
6143 set cmd [lindex $elt 1]
6144 if {$curview != $view} {
6145 showview $view
6147 eval $cmd
6150 proc goback {} {
6151 global history historyindex
6152 focus .
6154 if {$historyindex > 1} {
6155 incr historyindex -1
6156 godo [lindex $history [expr {$historyindex - 1}]]
6157 .tf.bar.rightbut conf -state normal
6159 if {$historyindex <= 1} {
6160 .tf.bar.leftbut conf -state disabled
6164 proc goforw {} {
6165 global history historyindex
6166 focus .
6168 if {$historyindex < [llength $history]} {
6169 set cmd [lindex $history $historyindex]
6170 incr historyindex
6171 godo $cmd
6172 .tf.bar.leftbut conf -state normal
6174 if {$historyindex >= [llength $history]} {
6175 .tf.bar.rightbut conf -state disabled
6179 proc gettree {id} {
6180 global treefilelist treeidlist diffids diffmergeid treepending
6181 global nullid nullid2
6183 set diffids $id
6184 catch {unset diffmergeid}
6185 if {![info exists treefilelist($id)]} {
6186 if {![info exists treepending]} {
6187 if {$id eq $nullid} {
6188 set cmd [list | git ls-files]
6189 } elseif {$id eq $nullid2} {
6190 set cmd [list | git ls-files --stage -t]
6191 } else {
6192 set cmd [list | git ls-tree -r $id]
6194 if {[catch {set gtf [open $cmd r]}]} {
6195 return
6197 set treepending $id
6198 set treefilelist($id) {}
6199 set treeidlist($id) {}
6200 fconfigure $gtf -blocking 0
6201 filerun $gtf [list gettreeline $gtf $id]
6203 } else {
6204 setfilelist $id
6208 proc gettreeline {gtf id} {
6209 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6211 set nl 0
6212 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6213 if {$diffids eq $nullid} {
6214 set fname $line
6215 } else {
6216 set i [string first "\t" $line]
6217 if {$i < 0} continue
6218 set fname [string range $line [expr {$i+1}] end]
6219 set line [string range $line 0 [expr {$i-1}]]
6220 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6221 set sha1 [lindex $line 2]
6222 if {[string index $fname 0] eq "\""} {
6223 set fname [lindex $fname 0]
6225 lappend treeidlist($id) $sha1
6227 lappend treefilelist($id) $fname
6229 if {![eof $gtf]} {
6230 return [expr {$nl >= 1000? 2: 1}]
6232 close $gtf
6233 unset treepending
6234 if {$cmitmode ne "tree"} {
6235 if {![info exists diffmergeid]} {
6236 gettreediffs $diffids
6238 } elseif {$id ne $diffids} {
6239 gettree $diffids
6240 } else {
6241 setfilelist $id
6243 return 0
6246 proc showfile {f} {
6247 global treefilelist treeidlist diffids nullid nullid2
6248 global ctext commentend
6250 set i [lsearch -exact $treefilelist($diffids) $f]
6251 if {$i < 0} {
6252 puts "oops, $f not in list for id $diffids"
6253 return
6255 if {$diffids eq $nullid} {
6256 if {[catch {set bf [open $f r]} err]} {
6257 puts "oops, can't read $f: $err"
6258 return
6260 } else {
6261 set blob [lindex $treeidlist($diffids) $i]
6262 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6263 puts "oops, error reading blob $blob: $err"
6264 return
6267 fconfigure $bf -blocking 0
6268 filerun $bf [list getblobline $bf $diffids]
6269 $ctext config -state normal
6270 clear_ctext $commentend
6271 $ctext insert end "\n"
6272 $ctext insert end "$f\n" filesep
6273 $ctext config -state disabled
6274 $ctext yview $commentend
6275 settabs 0
6278 proc getblobline {bf id} {
6279 global diffids cmitmode ctext
6281 if {$id ne $diffids || $cmitmode ne "tree"} {
6282 catch {close $bf}
6283 return 0
6285 $ctext config -state normal
6286 set nl 0
6287 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6288 $ctext insert end "$line\n"
6290 if {[eof $bf]} {
6291 # delete last newline
6292 $ctext delete "end - 2c" "end - 1c"
6293 close $bf
6294 return 0
6296 $ctext config -state disabled
6297 return [expr {$nl >= 1000? 2: 1}]
6300 proc mergediff {id} {
6301 global diffmergeid mdifffd
6302 global diffids
6303 global parents
6304 global diffcontext
6305 global limitdiffs vfilelimit curview
6307 set diffmergeid $id
6308 set diffids $id
6309 # this doesn't seem to actually affect anything...
6310 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6311 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6312 set cmd [concat $cmd -- $vfilelimit($curview)]
6314 if {[catch {set mdf [open $cmd r]} err]} {
6315 error_popup "[mc "Error getting merge diffs:"] $err"
6316 return
6318 fconfigure $mdf -blocking 0
6319 set mdifffd($id) $mdf
6320 set np [llength $parents($curview,$id)]
6321 settabs $np
6322 filerun $mdf [list getmergediffline $mdf $id $np]
6325 proc getmergediffline {mdf id np} {
6326 global diffmergeid ctext cflist mergemax
6327 global difffilestart mdifffd
6329 $ctext conf -state normal
6330 set nr 0
6331 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6332 if {![info exists diffmergeid] || $id != $diffmergeid
6333 || $mdf != $mdifffd($id)} {
6334 close $mdf
6335 return 0
6337 if {[regexp {^diff --cc (.*)} $line match fname]} {
6338 # start of a new file
6339 $ctext insert end "\n"
6340 set here [$ctext index "end - 1c"]
6341 lappend difffilestart $here
6342 add_flist [list $fname]
6343 set l [expr {(78 - [string length $fname]) / 2}]
6344 set pad [string range "----------------------------------------" 1 $l]
6345 $ctext insert end "$pad $fname $pad\n" filesep
6346 } elseif {[regexp {^@@} $line]} {
6347 $ctext insert end "$line\n" hunksep
6348 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6349 # do nothing
6350 } else {
6351 # parse the prefix - one ' ', '-' or '+' for each parent
6352 set spaces {}
6353 set minuses {}
6354 set pluses {}
6355 set isbad 0
6356 for {set j 0} {$j < $np} {incr j} {
6357 set c [string range $line $j $j]
6358 if {$c == " "} {
6359 lappend spaces $j
6360 } elseif {$c == "-"} {
6361 lappend minuses $j
6362 } elseif {$c == "+"} {
6363 lappend pluses $j
6364 } else {
6365 set isbad 1
6366 break
6369 set tags {}
6370 set num {}
6371 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6372 # line doesn't appear in result, parents in $minuses have the line
6373 set num [lindex $minuses 0]
6374 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6375 # line appears in result, parents in $pluses don't have the line
6376 lappend tags mresult
6377 set num [lindex $spaces 0]
6379 if {$num ne {}} {
6380 if {$num >= $mergemax} {
6381 set num "max"
6383 lappend tags m$num
6385 $ctext insert end "$line\n" $tags
6388 $ctext conf -state disabled
6389 if {[eof $mdf]} {
6390 close $mdf
6391 return 0
6393 return [expr {$nr >= 1000? 2: 1}]
6396 proc startdiff {ids} {
6397 global treediffs diffids treepending diffmergeid nullid nullid2
6399 settabs 1
6400 set diffids $ids
6401 catch {unset diffmergeid}
6402 if {![info exists treediffs($ids)] ||
6403 [lsearch -exact $ids $nullid] >= 0 ||
6404 [lsearch -exact $ids $nullid2] >= 0} {
6405 if {![info exists treepending]} {
6406 gettreediffs $ids
6408 } else {
6409 addtocflist $ids
6413 proc path_filter {filter name} {
6414 foreach p $filter {
6415 set l [string length $p]
6416 if {[string index $p end] eq "/"} {
6417 if {[string compare -length $l $p $name] == 0} {
6418 return 1
6420 } else {
6421 if {[string compare -length $l $p $name] == 0 &&
6422 ([string length $name] == $l ||
6423 [string index $name $l] eq "/")} {
6424 return 1
6428 return 0
6431 proc addtocflist {ids} {
6432 global treediffs
6434 add_flist $treediffs($ids)
6435 getblobdiffs $ids
6438 proc diffcmd {ids flags} {
6439 global nullid nullid2
6441 set i [lsearch -exact $ids $nullid]
6442 set j [lsearch -exact $ids $nullid2]
6443 if {$i >= 0} {
6444 if {[llength $ids] > 1 && $j < 0} {
6445 # comparing working directory with some specific revision
6446 set cmd [concat | git diff-index $flags]
6447 if {$i == 0} {
6448 lappend cmd -R [lindex $ids 1]
6449 } else {
6450 lappend cmd [lindex $ids 0]
6452 } else {
6453 # comparing working directory with index
6454 set cmd [concat | git diff-files $flags]
6455 if {$j == 1} {
6456 lappend cmd -R
6459 } elseif {$j >= 0} {
6460 set cmd [concat | git diff-index --cached $flags]
6461 if {[llength $ids] > 1} {
6462 # comparing index with specific revision
6463 if {$i == 0} {
6464 lappend cmd -R [lindex $ids 1]
6465 } else {
6466 lappend cmd [lindex $ids 0]
6468 } else {
6469 # comparing index with HEAD
6470 lappend cmd HEAD
6472 } else {
6473 set cmd [concat | git diff-tree -r $flags $ids]
6475 return $cmd
6478 proc gettreediffs {ids} {
6479 global treediff treepending
6481 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6483 set treepending $ids
6484 set treediff {}
6485 fconfigure $gdtf -blocking 0
6486 filerun $gdtf [list gettreediffline $gdtf $ids]
6489 proc gettreediffline {gdtf ids} {
6490 global treediff treediffs treepending diffids diffmergeid
6491 global cmitmode vfilelimit curview limitdiffs
6493 set nr 0
6494 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
6495 set i [string first "\t" $line]
6496 if {$i >= 0} {
6497 set file [string range $line [expr {$i+1}] end]
6498 if {[string index $file 0] eq "\""} {
6499 set file [lindex $file 0]
6501 lappend treediff $file
6504 if {![eof $gdtf]} {
6505 return [expr {$nr >= 1000? 2: 1}]
6507 close $gdtf
6508 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6509 set flist {}
6510 foreach f $treediff {
6511 if {[path_filter $vfilelimit($curview) $f]} {
6512 lappend flist $f
6515 set treediffs($ids) $flist
6516 } else {
6517 set treediffs($ids) $treediff
6519 unset treepending
6520 if {$cmitmode eq "tree"} {
6521 gettree $diffids
6522 } elseif {$ids != $diffids} {
6523 if {![info exists diffmergeid]} {
6524 gettreediffs $diffids
6526 } else {
6527 addtocflist $ids
6529 return 0
6532 # empty string or positive integer
6533 proc diffcontextvalidate {v} {
6534 return [regexp {^(|[1-9][0-9]*)$} $v]
6537 proc diffcontextchange {n1 n2 op} {
6538 global diffcontextstring diffcontext
6540 if {[string is integer -strict $diffcontextstring]} {
6541 if {$diffcontextstring > 0} {
6542 set diffcontext $diffcontextstring
6543 reselectline
6548 proc changeignorespace {} {
6549 reselectline
6552 proc getblobdiffs {ids} {
6553 global blobdifffd diffids env
6554 global diffinhdr treediffs
6555 global diffcontext
6556 global ignorespace
6557 global limitdiffs vfilelimit curview
6559 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6560 if {$ignorespace} {
6561 append cmd " -w"
6563 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6564 set cmd [concat $cmd -- $vfilelimit($curview)]
6566 if {[catch {set bdf [open $cmd r]} err]} {
6567 puts "error getting diffs: $err"
6568 return
6570 set diffinhdr 0
6571 fconfigure $bdf -blocking 0
6572 set blobdifffd($ids) $bdf
6573 filerun $bdf [list getblobdiffline $bdf $diffids]
6576 proc setinlist {var i val} {
6577 global $var
6579 while {[llength [set $var]] < $i} {
6580 lappend $var {}
6582 if {[llength [set $var]] == $i} {
6583 lappend $var $val
6584 } else {
6585 lset $var $i $val
6589 proc makediffhdr {fname ids} {
6590 global ctext curdiffstart treediffs
6592 set i [lsearch -exact $treediffs($ids) $fname]
6593 if {$i >= 0} {
6594 setinlist difffilestart $i $curdiffstart
6596 set l [expr {(78 - [string length $fname]) / 2}]
6597 set pad [string range "----------------------------------------" 1 $l]
6598 $ctext insert $curdiffstart "$pad $fname $pad" filesep
6601 proc getblobdiffline {bdf ids} {
6602 global diffids blobdifffd ctext curdiffstart
6603 global diffnexthead diffnextnote difffilestart
6604 global diffinhdr treediffs
6606 set nr 0
6607 $ctext conf -state normal
6608 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
6609 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
6610 close $bdf
6611 return 0
6613 if {![string compare -length 11 "diff --git " $line]} {
6614 # trim off "diff --git "
6615 set line [string range $line 11 end]
6616 set diffinhdr 1
6617 # start of a new file
6618 $ctext insert end "\n"
6619 set curdiffstart [$ctext index "end - 1c"]
6620 $ctext insert end "\n" filesep
6621 # If the name hasn't changed the length will be odd,
6622 # the middle char will be a space, and the two bits either
6623 # side will be a/name and b/name, or "a/name" and "b/name".
6624 # If the name has changed we'll get "rename from" and
6625 # "rename to" or "copy from" and "copy to" lines following this,
6626 # and we'll use them to get the filenames.
6627 # This complexity is necessary because spaces in the filename(s)
6628 # don't get escaped.
6629 set l [string length $line]
6630 set i [expr {$l / 2}]
6631 if {!(($l & 1) && [string index $line $i] eq " " &&
6632 [string range $line 2 [expr {$i - 1}]] eq \
6633 [string range $line [expr {$i + 3}] end])} {
6634 continue
6636 # unescape if quoted and chop off the a/ from the front
6637 if {[string index $line 0] eq "\""} {
6638 set fname [string range [lindex $line 0] 2 end]
6639 } else {
6640 set fname [string range $line 2 [expr {$i - 1}]]
6642 makediffhdr $fname $ids
6644 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
6645 $line match f1l f1c f2l f2c rest]} {
6646 $ctext insert end "$line\n" hunksep
6647 set diffinhdr 0
6649 } elseif {$diffinhdr} {
6650 if {![string compare -length 12 "rename from " $line]} {
6651 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
6652 if {[string index $fname 0] eq "\""} {
6653 set fname [lindex $fname 0]
6655 set i [lsearch -exact $treediffs($ids) $fname]
6656 if {$i >= 0} {
6657 setinlist difffilestart $i $curdiffstart
6659 } elseif {![string compare -length 10 $line "rename to "] ||
6660 ![string compare -length 8 $line "copy to "]} {
6661 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
6662 if {[string index $fname 0] eq "\""} {
6663 set fname [lindex $fname 0]
6665 makediffhdr $fname $ids
6666 } elseif {[string compare -length 3 $line "---"] == 0} {
6667 # do nothing
6668 continue
6669 } elseif {[string compare -length 3 $line "+++"] == 0} {
6670 set diffinhdr 0
6671 continue
6673 $ctext insert end "$line\n" filesep
6675 } else {
6676 set x [string range $line 0 0]
6677 if {$x == "-" || $x == "+"} {
6678 set tag [expr {$x == "+"}]
6679 $ctext insert end "$line\n" d$tag
6680 } elseif {$x == " "} {
6681 $ctext insert end "$line\n"
6682 } else {
6683 # "\ No newline at end of file",
6684 # or something else we don't recognize
6685 $ctext insert end "$line\n" hunksep
6689 $ctext conf -state disabled
6690 if {[eof $bdf]} {
6691 close $bdf
6692 return 0
6694 return [expr {$nr >= 1000? 2: 1}]
6697 proc changediffdisp {} {
6698 global ctext diffelide
6700 $ctext tag conf d0 -elide [lindex $diffelide 0]
6701 $ctext tag conf d1 -elide [lindex $diffelide 1]
6704 proc highlightfile {loc cline} {
6705 global ctext cflist cflist_top
6707 $ctext yview $loc
6708 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
6709 $cflist tag add highlight $cline.0 "$cline.0 lineend"
6710 $cflist see $cline.0
6711 set cflist_top $cline
6714 proc prevfile {} {
6715 global difffilestart ctext cmitmode
6717 if {$cmitmode eq "tree"} return
6718 set prev 0.0
6719 set prevline 1
6720 set here [$ctext index @0,0]
6721 foreach loc $difffilestart {
6722 if {[$ctext compare $loc >= $here]} {
6723 highlightfile $prev $prevline
6724 return
6726 set prev $loc
6727 incr prevline
6729 highlightfile $prev $prevline
6732 proc nextfile {} {
6733 global difffilestart ctext cmitmode
6735 if {$cmitmode eq "tree"} return
6736 set here [$ctext index @0,0]
6737 set line 1
6738 foreach loc $difffilestart {
6739 incr line
6740 if {[$ctext compare $loc > $here]} {
6741 highlightfile $loc $line
6742 return
6747 proc clear_ctext {{first 1.0}} {
6748 global ctext smarktop smarkbot
6749 global pendinglinks
6751 set l [lindex [split $first .] 0]
6752 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
6753 set smarktop $l
6755 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
6756 set smarkbot $l
6758 $ctext delete $first end
6759 if {$first eq "1.0"} {
6760 catch {unset pendinglinks}
6764 proc settabs {{firstab {}}} {
6765 global firsttabstop tabstop ctext have_tk85
6767 if {$firstab ne {} && $have_tk85} {
6768 set firsttabstop $firstab
6770 set w [font measure textfont "0"]
6771 if {$firsttabstop != 0} {
6772 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
6773 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
6774 } elseif {$have_tk85 || $tabstop != 8} {
6775 $ctext conf -tabs [expr {$tabstop * $w}]
6776 } else {
6777 $ctext conf -tabs {}
6781 proc incrsearch {name ix op} {
6782 global ctext searchstring searchdirn
6784 $ctext tag remove found 1.0 end
6785 if {[catch {$ctext index anchor}]} {
6786 # no anchor set, use start of selection, or of visible area
6787 set sel [$ctext tag ranges sel]
6788 if {$sel ne {}} {
6789 $ctext mark set anchor [lindex $sel 0]
6790 } elseif {$searchdirn eq "-forwards"} {
6791 $ctext mark set anchor @0,0
6792 } else {
6793 $ctext mark set anchor @0,[winfo height $ctext]
6796 if {$searchstring ne {}} {
6797 set here [$ctext search $searchdirn -- $searchstring anchor]
6798 if {$here ne {}} {
6799 $ctext see $here
6801 searchmarkvisible 1
6805 proc dosearch {} {
6806 global sstring ctext searchstring searchdirn
6808 focus $sstring
6809 $sstring icursor end
6810 set searchdirn -forwards
6811 if {$searchstring ne {}} {
6812 set sel [$ctext tag ranges sel]
6813 if {$sel ne {}} {
6814 set start "[lindex $sel 0] + 1c"
6815 } elseif {[catch {set start [$ctext index anchor]}]} {
6816 set start "@0,0"
6818 set match [$ctext search -count mlen -- $searchstring $start]
6819 $ctext tag remove sel 1.0 end
6820 if {$match eq {}} {
6821 bell
6822 return
6824 $ctext see $match
6825 set mend "$match + $mlen c"
6826 $ctext tag add sel $match $mend
6827 $ctext mark unset anchor
6831 proc dosearchback {} {
6832 global sstring ctext searchstring searchdirn
6834 focus $sstring
6835 $sstring icursor end
6836 set searchdirn -backwards
6837 if {$searchstring ne {}} {
6838 set sel [$ctext tag ranges sel]
6839 if {$sel ne {}} {
6840 set start [lindex $sel 0]
6841 } elseif {[catch {set start [$ctext index anchor]}]} {
6842 set start @0,[winfo height $ctext]
6844 set match [$ctext search -backwards -count ml -- $searchstring $start]
6845 $ctext tag remove sel 1.0 end
6846 if {$match eq {}} {
6847 bell
6848 return
6850 $ctext see $match
6851 set mend "$match + $ml c"
6852 $ctext tag add sel $match $mend
6853 $ctext mark unset anchor
6857 proc searchmark {first last} {
6858 global ctext searchstring
6860 set mend $first.0
6861 while {1} {
6862 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
6863 if {$match eq {}} break
6864 set mend "$match + $mlen c"
6865 $ctext tag add found $match $mend
6869 proc searchmarkvisible {doall} {
6870 global ctext smarktop smarkbot
6872 set topline [lindex [split [$ctext index @0,0] .] 0]
6873 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
6874 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
6875 # no overlap with previous
6876 searchmark $topline $botline
6877 set smarktop $topline
6878 set smarkbot $botline
6879 } else {
6880 if {$topline < $smarktop} {
6881 searchmark $topline [expr {$smarktop-1}]
6882 set smarktop $topline
6884 if {$botline > $smarkbot} {
6885 searchmark [expr {$smarkbot+1}] $botline
6886 set smarkbot $botline
6891 proc scrolltext {f0 f1} {
6892 global searchstring
6894 .bleft.bottom.sb set $f0 $f1
6895 if {$searchstring ne {}} {
6896 searchmarkvisible 0
6900 proc setcoords {} {
6901 global linespc charspc canvx0 canvy0
6902 global xspc1 xspc2 lthickness
6904 set linespc [font metrics mainfont -linespace]
6905 set charspc [font measure mainfont "m"]
6906 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
6907 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
6908 set lthickness [expr {int($linespc / 9) + 1}]
6909 set xspc1(0) $linespc
6910 set xspc2 $linespc
6913 proc redisplay {} {
6914 global canv
6915 global selectedline
6917 set ymax [lindex [$canv cget -scrollregion] 3]
6918 if {$ymax eq {} || $ymax == 0} return
6919 set span [$canv yview]
6920 clear_display
6921 setcanvscroll
6922 allcanvs yview moveto [lindex $span 0]
6923 drawvisible
6924 if {$selectedline ne {}} {
6925 selectline $selectedline 0
6926 allcanvs yview moveto [lindex $span 0]
6930 proc parsefont {f n} {
6931 global fontattr
6933 set fontattr($f,family) [lindex $n 0]
6934 set s [lindex $n 1]
6935 if {$s eq {} || $s == 0} {
6936 set s 10
6937 } elseif {$s < 0} {
6938 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
6940 set fontattr($f,size) $s
6941 set fontattr($f,weight) normal
6942 set fontattr($f,slant) roman
6943 foreach style [lrange $n 2 end] {
6944 switch -- $style {
6945 "normal" -
6946 "bold" {set fontattr($f,weight) $style}
6947 "roman" -
6948 "italic" {set fontattr($f,slant) $style}
6953 proc fontflags {f {isbold 0}} {
6954 global fontattr
6956 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
6957 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
6958 -slant $fontattr($f,slant)]
6961 proc fontname {f} {
6962 global fontattr
6964 set n [list $fontattr($f,family) $fontattr($f,size)]
6965 if {$fontattr($f,weight) eq "bold"} {
6966 lappend n "bold"
6968 if {$fontattr($f,slant) eq "italic"} {
6969 lappend n "italic"
6971 return $n
6974 proc incrfont {inc} {
6975 global mainfont textfont ctext canv cflist showrefstop
6976 global stopped entries fontattr
6978 unmarkmatches
6979 set s $fontattr(mainfont,size)
6980 incr s $inc
6981 if {$s < 1} {
6982 set s 1
6984 set fontattr(mainfont,size) $s
6985 font config mainfont -size $s
6986 font config mainfontbold -size $s
6987 set mainfont [fontname mainfont]
6988 set s $fontattr(textfont,size)
6989 incr s $inc
6990 if {$s < 1} {
6991 set s 1
6993 set fontattr(textfont,size) $s
6994 font config textfont -size $s
6995 font config textfontbold -size $s
6996 set textfont [fontname textfont]
6997 setcoords
6998 settabs
6999 redisplay
7002 proc clearsha1 {} {
7003 global sha1entry sha1string
7004 if {[string length $sha1string] == 40} {
7005 $sha1entry delete 0 end
7009 proc sha1change {n1 n2 op} {
7010 global sha1string currentid sha1but
7011 if {$sha1string == {}
7012 || ([info exists currentid] && $sha1string == $currentid)} {
7013 set state disabled
7014 } else {
7015 set state normal
7017 if {[$sha1but cget -state] == $state} return
7018 if {$state == "normal"} {
7019 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7020 } else {
7021 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7025 proc gotocommit {} {
7026 global sha1string tagids headids curview varcid
7028 if {$sha1string == {}
7029 || ([info exists currentid] && $sha1string == $currentid)} return
7030 if {[info exists tagids($sha1string)]} {
7031 set id $tagids($sha1string)
7032 } elseif {[info exists headids($sha1string)]} {
7033 set id $headids($sha1string)
7034 } else {
7035 set id [string tolower $sha1string]
7036 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7037 set matches [array names varcid "$curview,$id*"]
7038 if {$matches ne {}} {
7039 if {[llength $matches] > 1} {
7040 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7041 return
7043 set id [lindex [split [lindex $matches 0] ","] 1]
7047 if {[commitinview $id $curview]} {
7048 selectline [rowofcommit $id] 1
7049 return
7051 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7052 set msg [mc "SHA1 id %s is not known" $sha1string]
7053 } else {
7054 set msg [mc "Tag/Head %s is not known" $sha1string]
7056 error_popup $msg
7059 proc lineenter {x y id} {
7060 global hoverx hovery hoverid hovertimer
7061 global commitinfo canv
7063 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7064 set hoverx $x
7065 set hovery $y
7066 set hoverid $id
7067 if {[info exists hovertimer]} {
7068 after cancel $hovertimer
7070 set hovertimer [after 500 linehover]
7071 $canv delete hover
7074 proc linemotion {x y id} {
7075 global hoverx hovery hoverid hovertimer
7077 if {[info exists hoverid] && $id == $hoverid} {
7078 set hoverx $x
7079 set hovery $y
7080 if {[info exists hovertimer]} {
7081 after cancel $hovertimer
7083 set hovertimer [after 500 linehover]
7087 proc lineleave {id} {
7088 global hoverid hovertimer canv
7090 if {[info exists hoverid] && $id == $hoverid} {
7091 $canv delete hover
7092 if {[info exists hovertimer]} {
7093 after cancel $hovertimer
7094 unset hovertimer
7096 unset hoverid
7100 proc linehover {} {
7101 global hoverx hovery hoverid hovertimer
7102 global canv linespc lthickness
7103 global commitinfo
7105 set text [lindex $commitinfo($hoverid) 0]
7106 set ymax [lindex [$canv cget -scrollregion] 3]
7107 if {$ymax == {}} return
7108 set yfrac [lindex [$canv yview] 0]
7109 set x [expr {$hoverx + 2 * $linespc}]
7110 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7111 set x0 [expr {$x - 2 * $lthickness}]
7112 set y0 [expr {$y - 2 * $lthickness}]
7113 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7114 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7115 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7116 -fill \#ffff80 -outline black -width 1 -tags hover]
7117 $canv raise $t
7118 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7119 -font mainfont]
7120 $canv raise $t
7123 proc clickisonarrow {id y} {
7124 global lthickness
7126 set ranges [rowranges $id]
7127 set thresh [expr {2 * $lthickness + 6}]
7128 set n [expr {[llength $ranges] - 1}]
7129 for {set i 1} {$i < $n} {incr i} {
7130 set row [lindex $ranges $i]
7131 if {abs([yc $row] - $y) < $thresh} {
7132 return $i
7135 return {}
7138 proc arrowjump {id n y} {
7139 global canv
7141 # 1 <-> 2, 3 <-> 4, etc...
7142 set n [expr {(($n - 1) ^ 1) + 1}]
7143 set row [lindex [rowranges $id] $n]
7144 set yt [yc $row]
7145 set ymax [lindex [$canv cget -scrollregion] 3]
7146 if {$ymax eq {} || $ymax <= 0} return
7147 set view [$canv yview]
7148 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7149 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7150 if {$yfrac < 0} {
7151 set yfrac 0
7153 allcanvs yview moveto $yfrac
7156 proc lineclick {x y id isnew} {
7157 global ctext commitinfo children canv thickerline curview
7159 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7160 unmarkmatches
7161 unselectline
7162 normalline
7163 $canv delete hover
7164 # draw this line thicker than normal
7165 set thickerline $id
7166 drawlines $id
7167 if {$isnew} {
7168 set ymax [lindex [$canv cget -scrollregion] 3]
7169 if {$ymax eq {}} return
7170 set yfrac [lindex [$canv yview] 0]
7171 set y [expr {$y + $yfrac * $ymax}]
7173 set dirn [clickisonarrow $id $y]
7174 if {$dirn ne {}} {
7175 arrowjump $id $dirn $y
7176 return
7179 if {$isnew} {
7180 addtohistory [list lineclick $x $y $id 0]
7182 # fill the details pane with info about this line
7183 $ctext conf -state normal
7184 clear_ctext
7185 settabs 0
7186 $ctext insert end "[mc "Parent"]:\t"
7187 $ctext insert end $id link0
7188 setlink $id link0
7189 set info $commitinfo($id)
7190 $ctext insert end "\n\t[lindex $info 0]\n"
7191 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7192 set date [formatdate [lindex $info 2]]
7193 $ctext insert end "\t[mc "Date"]:\t$date\n"
7194 set kids $children($curview,$id)
7195 if {$kids ne {}} {
7196 $ctext insert end "\n[mc "Children"]:"
7197 set i 0
7198 foreach child $kids {
7199 incr i
7200 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7201 set info $commitinfo($child)
7202 $ctext insert end "\n\t"
7203 $ctext insert end $child link$i
7204 setlink $child link$i
7205 $ctext insert end "\n\t[lindex $info 0]"
7206 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7207 set date [formatdate [lindex $info 2]]
7208 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7211 $ctext conf -state disabled
7212 init_flist {}
7215 proc normalline {} {
7216 global thickerline
7217 if {[info exists thickerline]} {
7218 set id $thickerline
7219 unset thickerline
7220 drawlines $id
7224 proc selbyid {id} {
7225 global curview
7226 if {[commitinview $id $curview]} {
7227 selectline [rowofcommit $id] 1
7231 proc mstime {} {
7232 global startmstime
7233 if {![info exists startmstime]} {
7234 set startmstime [clock clicks -milliseconds]
7236 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7239 proc rowmenu {x y id} {
7240 global rowctxmenu selectedline rowmenuid curview
7241 global nullid nullid2 fakerowmenu mainhead
7243 stopfinding
7244 set rowmenuid $id
7245 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7246 set state disabled
7247 } else {
7248 set state normal
7250 if {$id ne $nullid && $id ne $nullid2} {
7251 set menu $rowctxmenu
7252 if {$mainhead ne {}} {
7253 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7254 } else {
7255 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7257 } else {
7258 set menu $fakerowmenu
7260 $menu entryconfigure [mc "Diff this -> selected"] -state $state
7261 $menu entryconfigure [mc "Diff selected -> this"] -state $state
7262 $menu entryconfigure [mc "Make patch"] -state $state
7263 tk_popup $menu $x $y
7266 proc diffvssel {dirn} {
7267 global rowmenuid selectedline
7269 if {$selectedline eq {}} return
7270 if {$dirn} {
7271 set oldid [commitonrow $selectedline]
7272 set newid $rowmenuid
7273 } else {
7274 set oldid $rowmenuid
7275 set newid [commitonrow $selectedline]
7277 addtohistory [list doseldiff $oldid $newid]
7278 doseldiff $oldid $newid
7281 proc doseldiff {oldid newid} {
7282 global ctext
7283 global commitinfo
7285 $ctext conf -state normal
7286 clear_ctext
7287 init_flist [mc "Top"]
7288 $ctext insert end "[mc "From"] "
7289 $ctext insert end $oldid link0
7290 setlink $oldid link0
7291 $ctext insert end "\n "
7292 $ctext insert end [lindex $commitinfo($oldid) 0]
7293 $ctext insert end "\n\n[mc "To"] "
7294 $ctext insert end $newid link1
7295 setlink $newid link1
7296 $ctext insert end "\n "
7297 $ctext insert end [lindex $commitinfo($newid) 0]
7298 $ctext insert end "\n"
7299 $ctext conf -state disabled
7300 $ctext tag remove found 1.0 end
7301 startdiff [list $oldid $newid]
7304 proc mkpatch {} {
7305 global rowmenuid currentid commitinfo patchtop patchnum
7307 if {![info exists currentid]} return
7308 set oldid $currentid
7309 set oldhead [lindex $commitinfo($oldid) 0]
7310 set newid $rowmenuid
7311 set newhead [lindex $commitinfo($newid) 0]
7312 set top .patch
7313 set patchtop $top
7314 catch {destroy $top}
7315 toplevel $top
7316 label $top.title -text [mc "Generate patch"]
7317 grid $top.title - -pady 10
7318 label $top.from -text [mc "From:"]
7319 entry $top.fromsha1 -width 40 -relief flat
7320 $top.fromsha1 insert 0 $oldid
7321 $top.fromsha1 conf -state readonly
7322 grid $top.from $top.fromsha1 -sticky w
7323 entry $top.fromhead -width 60 -relief flat
7324 $top.fromhead insert 0 $oldhead
7325 $top.fromhead conf -state readonly
7326 grid x $top.fromhead -sticky w
7327 label $top.to -text [mc "To:"]
7328 entry $top.tosha1 -width 40 -relief flat
7329 $top.tosha1 insert 0 $newid
7330 $top.tosha1 conf -state readonly
7331 grid $top.to $top.tosha1 -sticky w
7332 entry $top.tohead -width 60 -relief flat
7333 $top.tohead insert 0 $newhead
7334 $top.tohead conf -state readonly
7335 grid x $top.tohead -sticky w
7336 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7337 grid $top.rev x -pady 10
7338 label $top.flab -text [mc "Output file:"]
7339 entry $top.fname -width 60
7340 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7341 incr patchnum
7342 grid $top.flab $top.fname -sticky w
7343 frame $top.buts
7344 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7345 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7346 grid $top.buts.gen $top.buts.can
7347 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7348 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7349 grid $top.buts - -pady 10 -sticky ew
7350 focus $top.fname
7353 proc mkpatchrev {} {
7354 global patchtop
7356 set oldid [$patchtop.fromsha1 get]
7357 set oldhead [$patchtop.fromhead get]
7358 set newid [$patchtop.tosha1 get]
7359 set newhead [$patchtop.tohead get]
7360 foreach e [list fromsha1 fromhead tosha1 tohead] \
7361 v [list $newid $newhead $oldid $oldhead] {
7362 $patchtop.$e conf -state normal
7363 $patchtop.$e delete 0 end
7364 $patchtop.$e insert 0 $v
7365 $patchtop.$e conf -state readonly
7369 proc mkpatchgo {} {
7370 global patchtop nullid nullid2
7372 set oldid [$patchtop.fromsha1 get]
7373 set newid [$patchtop.tosha1 get]
7374 set fname [$patchtop.fname get]
7375 set cmd [diffcmd [list $oldid $newid] -p]
7376 # trim off the initial "|"
7377 set cmd [lrange $cmd 1 end]
7378 lappend cmd >$fname &
7379 if {[catch {eval exec $cmd} err]} {
7380 error_popup "[mc "Error creating patch:"] $err"
7382 catch {destroy $patchtop}
7383 unset patchtop
7386 proc mkpatchcan {} {
7387 global patchtop
7389 catch {destroy $patchtop}
7390 unset patchtop
7393 proc mktag {} {
7394 global rowmenuid mktagtop commitinfo
7396 set top .maketag
7397 set mktagtop $top
7398 catch {destroy $top}
7399 toplevel $top
7400 label $top.title -text [mc "Create tag"]
7401 grid $top.title - -pady 10
7402 label $top.id -text [mc "ID:"]
7403 entry $top.sha1 -width 40 -relief flat
7404 $top.sha1 insert 0 $rowmenuid
7405 $top.sha1 conf -state readonly
7406 grid $top.id $top.sha1 -sticky w
7407 entry $top.head -width 60 -relief flat
7408 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7409 $top.head conf -state readonly
7410 grid x $top.head -sticky w
7411 label $top.tlab -text [mc "Tag name:"]
7412 entry $top.tag -width 60
7413 grid $top.tlab $top.tag -sticky w
7414 frame $top.buts
7415 button $top.buts.gen -text [mc "Create"] -command mktaggo
7416 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7417 grid $top.buts.gen $top.buts.can
7418 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7419 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7420 grid $top.buts - -pady 10 -sticky ew
7421 focus $top.tag
7424 proc domktag {} {
7425 global mktagtop env tagids idtags
7427 set id [$mktagtop.sha1 get]
7428 set tag [$mktagtop.tag get]
7429 if {$tag == {}} {
7430 error_popup [mc "No tag name specified"]
7431 return
7433 if {[info exists tagids($tag)]} {
7434 error_popup [mc "Tag \"%s\" already exists" $tag]
7435 return
7437 if {[catch {
7438 exec git tag $tag $id
7439 } err]} {
7440 error_popup "[mc "Error creating tag:"] $err"
7441 return
7444 set tagids($tag) $id
7445 lappend idtags($id) $tag
7446 redrawtags $id
7447 addedtag $id
7448 dispneartags 0
7449 run refill_reflist
7452 proc redrawtags {id} {
7453 global canv linehtag idpos currentid curview cmitlisted
7454 global canvxmax iddrawn circleitem mainheadid circlecolors
7456 if {![commitinview $id $curview]} return
7457 if {![info exists iddrawn($id)]} return
7458 set row [rowofcommit $id]
7459 if {$id eq $mainheadid} {
7460 set ofill yellow
7461 } else {
7462 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7464 $canv itemconf $circleitem($row) -fill $ofill
7465 $canv delete tag.$id
7466 set xt [eval drawtags $id $idpos($id)]
7467 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7468 set text [$canv itemcget $linehtag($row) -text]
7469 set font [$canv itemcget $linehtag($row) -font]
7470 set xr [expr {$xt + [font measure $font $text]}]
7471 if {$xr > $canvxmax} {
7472 set canvxmax $xr
7473 setcanvscroll
7475 if {[info exists currentid] && $currentid == $id} {
7476 make_secsel $row
7480 proc mktagcan {} {
7481 global mktagtop
7483 catch {destroy $mktagtop}
7484 unset mktagtop
7487 proc mktaggo {} {
7488 domktag
7489 mktagcan
7492 proc writecommit {} {
7493 global rowmenuid wrcomtop commitinfo wrcomcmd
7495 set top .writecommit
7496 set wrcomtop $top
7497 catch {destroy $top}
7498 toplevel $top
7499 label $top.title -text [mc "Write commit to file"]
7500 grid $top.title - -pady 10
7501 label $top.id -text [mc "ID:"]
7502 entry $top.sha1 -width 40 -relief flat
7503 $top.sha1 insert 0 $rowmenuid
7504 $top.sha1 conf -state readonly
7505 grid $top.id $top.sha1 -sticky w
7506 entry $top.head -width 60 -relief flat
7507 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7508 $top.head conf -state readonly
7509 grid x $top.head -sticky w
7510 label $top.clab -text [mc "Command:"]
7511 entry $top.cmd -width 60 -textvariable wrcomcmd
7512 grid $top.clab $top.cmd -sticky w -pady 10
7513 label $top.flab -text [mc "Output file:"]
7514 entry $top.fname -width 60
7515 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7516 grid $top.flab $top.fname -sticky w
7517 frame $top.buts
7518 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7519 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7520 grid $top.buts.gen $top.buts.can
7521 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7522 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7523 grid $top.buts - -pady 10 -sticky ew
7524 focus $top.fname
7527 proc wrcomgo {} {
7528 global wrcomtop
7530 set id [$wrcomtop.sha1 get]
7531 set cmd "echo $id | [$wrcomtop.cmd get]"
7532 set fname [$wrcomtop.fname get]
7533 if {[catch {exec sh -c $cmd >$fname &} err]} {
7534 error_popup "[mc "Error writing commit:"] $err"
7536 catch {destroy $wrcomtop}
7537 unset wrcomtop
7540 proc wrcomcan {} {
7541 global wrcomtop
7543 catch {destroy $wrcomtop}
7544 unset wrcomtop
7547 proc mkbranch {} {
7548 global rowmenuid mkbrtop
7550 set top .makebranch
7551 catch {destroy $top}
7552 toplevel $top
7553 label $top.title -text [mc "Create new branch"]
7554 grid $top.title - -pady 10
7555 label $top.id -text [mc "ID:"]
7556 entry $top.sha1 -width 40 -relief flat
7557 $top.sha1 insert 0 $rowmenuid
7558 $top.sha1 conf -state readonly
7559 grid $top.id $top.sha1 -sticky w
7560 label $top.nlab -text [mc "Name:"]
7561 entry $top.name -width 40
7562 grid $top.nlab $top.name -sticky w
7563 frame $top.buts
7564 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
7565 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
7566 grid $top.buts.go $top.buts.can
7567 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7568 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7569 grid $top.buts - -pady 10 -sticky ew
7570 focus $top.name
7573 proc mkbrgo {top} {
7574 global headids idheads
7576 set name [$top.name get]
7577 set id [$top.sha1 get]
7578 if {$name eq {}} {
7579 error_popup [mc "Please specify a name for the new branch"]
7580 return
7582 catch {destroy $top}
7583 nowbusy newbranch
7584 update
7585 if {[catch {
7586 exec git branch $name $id
7587 } err]} {
7588 notbusy newbranch
7589 error_popup $err
7590 } else {
7591 set headids($name) $id
7592 lappend idheads($id) $name
7593 addedhead $id $name
7594 notbusy newbranch
7595 redrawtags $id
7596 dispneartags 0
7597 run refill_reflist
7601 proc cherrypick {} {
7602 global rowmenuid curview
7603 global mainhead mainheadid
7605 set oldhead [exec git rev-parse HEAD]
7606 set dheads [descheads $rowmenuid]
7607 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
7608 set ok [confirm_popup [mc "Commit %s is already\
7609 included in branch %s -- really re-apply it?" \
7610 [string range $rowmenuid 0 7] $mainhead]]
7611 if {!$ok} return
7613 nowbusy cherrypick [mc "Cherry-picking"]
7614 update
7615 # Unfortunately git-cherry-pick writes stuff to stderr even when
7616 # no error occurs, and exec takes that as an indication of error...
7617 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
7618 notbusy cherrypick
7619 error_popup $err
7620 return
7622 set newhead [exec git rev-parse HEAD]
7623 if {$newhead eq $oldhead} {
7624 notbusy cherrypick
7625 error_popup [mc "No changes committed"]
7626 return
7628 addnewchild $newhead $oldhead
7629 if {[commitinview $oldhead $curview]} {
7630 insertrow $newhead $oldhead $curview
7631 if {$mainhead ne {}} {
7632 movehead $newhead $mainhead
7633 movedhead $newhead $mainhead
7635 set mainheadid $newhead
7636 redrawtags $oldhead
7637 redrawtags $newhead
7638 selbyid $newhead
7640 notbusy cherrypick
7643 proc resethead {} {
7644 global mainhead rowmenuid confirm_ok resettype
7646 set confirm_ok 0
7647 set w ".confirmreset"
7648 toplevel $w
7649 wm transient $w .
7650 wm title $w [mc "Confirm reset"]
7651 message $w.m -text \
7652 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
7653 -justify center -aspect 1000
7654 pack $w.m -side top -fill x -padx 20 -pady 20
7655 frame $w.f -relief sunken -border 2
7656 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
7657 grid $w.f.rt -sticky w
7658 set resettype mixed
7659 radiobutton $w.f.soft -value soft -variable resettype -justify left \
7660 -text [mc "Soft: Leave working tree and index untouched"]
7661 grid $w.f.soft -sticky w
7662 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
7663 -text [mc "Mixed: Leave working tree untouched, reset index"]
7664 grid $w.f.mixed -sticky w
7665 radiobutton $w.f.hard -value hard -variable resettype -justify left \
7666 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
7667 grid $w.f.hard -sticky w
7668 pack $w.f -side top -fill x
7669 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
7670 pack $w.ok -side left -fill x -padx 20 -pady 20
7671 button $w.cancel -text [mc Cancel] -command "destroy $w"
7672 pack $w.cancel -side right -fill x -padx 20 -pady 20
7673 bind $w <Visibility> "grab $w; focus $w"
7674 tkwait window $w
7675 if {!$confirm_ok} return
7676 if {[catch {set fd [open \
7677 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
7678 error_popup $err
7679 } else {
7680 dohidelocalchanges
7681 filerun $fd [list readresetstat $fd]
7682 nowbusy reset [mc "Resetting"]
7683 selbyid $rowmenuid
7687 proc readresetstat {fd} {
7688 global mainhead mainheadid showlocalchanges rprogcoord
7690 if {[gets $fd line] >= 0} {
7691 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7692 set rprogcoord [expr {1.0 * $m / $n}]
7693 adjustprogress
7695 return 1
7697 set rprogcoord 0
7698 adjustprogress
7699 notbusy reset
7700 if {[catch {close $fd} err]} {
7701 error_popup $err
7703 set oldhead $mainheadid
7704 set newhead [exec git rev-parse HEAD]
7705 if {$newhead ne $oldhead} {
7706 movehead $newhead $mainhead
7707 movedhead $newhead $mainhead
7708 set mainheadid $newhead
7709 redrawtags $oldhead
7710 redrawtags $newhead
7712 if {$showlocalchanges} {
7713 doshowlocalchanges
7715 return 0
7718 # context menu for a head
7719 proc headmenu {x y id head} {
7720 global headmenuid headmenuhead headctxmenu mainhead
7722 stopfinding
7723 set headmenuid $id
7724 set headmenuhead $head
7725 set state normal
7726 if {$head eq $mainhead} {
7727 set state disabled
7729 $headctxmenu entryconfigure 0 -state $state
7730 $headctxmenu entryconfigure 1 -state $state
7731 tk_popup $headctxmenu $x $y
7734 proc cobranch {} {
7735 global headmenuid headmenuhead headids
7736 global showlocalchanges mainheadid
7738 # check the tree is clean first??
7739 nowbusy checkout [mc "Checking out"]
7740 update
7741 dohidelocalchanges
7742 if {[catch {
7743 set fd [open [list | git checkout $headmenuhead 2>@1] r]
7744 } err]} {
7745 notbusy checkout
7746 error_popup $err
7747 if {$showlocalchanges} {
7748 dodiffindex
7750 } else {
7751 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
7755 proc readcheckoutstat {fd newhead newheadid} {
7756 global mainhead mainheadid headids showlocalchanges progresscoords
7758 if {[gets $fd line] >= 0} {
7759 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
7760 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
7761 adjustprogress
7763 return 1
7765 set progresscoords {0 0}
7766 adjustprogress
7767 notbusy checkout
7768 if {[catch {close $fd} err]} {
7769 error_popup $err
7771 set oldmainid $mainheadid
7772 set mainhead $newhead
7773 set mainheadid $newheadid
7774 redrawtags $oldmainid
7775 redrawtags $newheadid
7776 selbyid $newheadid
7777 if {$showlocalchanges} {
7778 dodiffindex
7782 proc rmbranch {} {
7783 global headmenuid headmenuhead mainhead
7784 global idheads
7786 set head $headmenuhead
7787 set id $headmenuid
7788 # this check shouldn't be needed any more...
7789 if {$head eq $mainhead} {
7790 error_popup [mc "Cannot delete the currently checked-out branch"]
7791 return
7793 set dheads [descheads $id]
7794 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
7795 # the stuff on this branch isn't on any other branch
7796 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
7797 branch.\nReally delete branch %s?" $head $head]]} return
7799 nowbusy rmbranch
7800 update
7801 if {[catch {exec git branch -D $head} err]} {
7802 notbusy rmbranch
7803 error_popup $err
7804 return
7806 removehead $id $head
7807 removedhead $id $head
7808 redrawtags $id
7809 notbusy rmbranch
7810 dispneartags 0
7811 run refill_reflist
7814 # Display a list of tags and heads
7815 proc showrefs {} {
7816 global showrefstop bgcolor fgcolor selectbgcolor
7817 global bglist fglist reflistfilter reflist maincursor
7819 set top .showrefs
7820 set showrefstop $top
7821 if {[winfo exists $top]} {
7822 raise $top
7823 refill_reflist
7824 return
7826 toplevel $top
7827 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
7828 text $top.list -background $bgcolor -foreground $fgcolor \
7829 -selectbackground $selectbgcolor -font mainfont \
7830 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
7831 -width 30 -height 20 -cursor $maincursor \
7832 -spacing1 1 -spacing3 1 -state disabled
7833 $top.list tag configure highlight -background $selectbgcolor
7834 lappend bglist $top.list
7835 lappend fglist $top.list
7836 scrollbar $top.ysb -command "$top.list yview" -orient vertical
7837 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
7838 grid $top.list $top.ysb -sticky nsew
7839 grid $top.xsb x -sticky ew
7840 frame $top.f
7841 label $top.f.l -text "[mc "Filter"]: "
7842 entry $top.f.e -width 20 -textvariable reflistfilter
7843 set reflistfilter "*"
7844 trace add variable reflistfilter write reflistfilter_change
7845 pack $top.f.e -side right -fill x -expand 1
7846 pack $top.f.l -side left
7847 grid $top.f - -sticky ew -pady 2
7848 button $top.close -command [list destroy $top] -text [mc "Close"]
7849 grid $top.close -
7850 grid columnconfigure $top 0 -weight 1
7851 grid rowconfigure $top 0 -weight 1
7852 bind $top.list <1> {break}
7853 bind $top.list <B1-Motion> {break}
7854 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
7855 set reflist {}
7856 refill_reflist
7859 proc sel_reflist {w x y} {
7860 global showrefstop reflist headids tagids otherrefids
7862 if {![winfo exists $showrefstop]} return
7863 set l [lindex [split [$w index "@$x,$y"] "."] 0]
7864 set ref [lindex $reflist [expr {$l-1}]]
7865 set n [lindex $ref 0]
7866 switch -- [lindex $ref 1] {
7867 "H" {selbyid $headids($n)}
7868 "T" {selbyid $tagids($n)}
7869 "o" {selbyid $otherrefids($n)}
7871 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
7874 proc unsel_reflist {} {
7875 global showrefstop
7877 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7878 $showrefstop.list tag remove highlight 0.0 end
7881 proc reflistfilter_change {n1 n2 op} {
7882 global reflistfilter
7884 after cancel refill_reflist
7885 after 200 refill_reflist
7888 proc refill_reflist {} {
7889 global reflist reflistfilter showrefstop headids tagids otherrefids
7890 global curview commitinterest
7892 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
7893 set refs {}
7894 foreach n [array names headids] {
7895 if {[string match $reflistfilter $n]} {
7896 if {[commitinview $headids($n) $curview]} {
7897 lappend refs [list $n H]
7898 } else {
7899 set commitinterest($headids($n)) {run refill_reflist}
7903 foreach n [array names tagids] {
7904 if {[string match $reflistfilter $n]} {
7905 if {[commitinview $tagids($n) $curview]} {
7906 lappend refs [list $n T]
7907 } else {
7908 set commitinterest($tagids($n)) {run refill_reflist}
7912 foreach n [array names otherrefids] {
7913 if {[string match $reflistfilter $n]} {
7914 if {[commitinview $otherrefids($n) $curview]} {
7915 lappend refs [list $n o]
7916 } else {
7917 set commitinterest($otherrefids($n)) {run refill_reflist}
7921 set refs [lsort -index 0 $refs]
7922 if {$refs eq $reflist} return
7924 # Update the contents of $showrefstop.list according to the
7925 # differences between $reflist (old) and $refs (new)
7926 $showrefstop.list conf -state normal
7927 $showrefstop.list insert end "\n"
7928 set i 0
7929 set j 0
7930 while {$i < [llength $reflist] || $j < [llength $refs]} {
7931 if {$i < [llength $reflist]} {
7932 if {$j < [llength $refs]} {
7933 set cmp [string compare [lindex $reflist $i 0] \
7934 [lindex $refs $j 0]]
7935 if {$cmp == 0} {
7936 set cmp [string compare [lindex $reflist $i 1] \
7937 [lindex $refs $j 1]]
7939 } else {
7940 set cmp -1
7942 } else {
7943 set cmp 1
7945 switch -- $cmp {
7946 -1 {
7947 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
7948 incr i
7951 incr i
7952 incr j
7955 set l [expr {$j + 1}]
7956 $showrefstop.list image create $l.0 -align baseline \
7957 -image reficon-[lindex $refs $j 1] -padx 2
7958 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
7959 incr j
7963 set reflist $refs
7964 # delete last newline
7965 $showrefstop.list delete end-2c end-1c
7966 $showrefstop.list conf -state disabled
7969 # Stuff for finding nearby tags
7970 proc getallcommits {} {
7971 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
7972 global idheads idtags idotherrefs allparents tagobjid
7974 if {![info exists allcommits]} {
7975 set nextarc 0
7976 set allcommits 0
7977 set seeds {}
7978 set allcwait 0
7979 set cachedarcs 0
7980 set allccache [file join [gitdir] "gitk.cache"]
7981 if {![catch {
7982 set f [open $allccache r]
7983 set allcwait 1
7984 getcache $f
7985 }]} return
7988 if {$allcwait} {
7989 return
7991 set cmd [list | git rev-list --parents]
7992 set allcupdate [expr {$seeds ne {}}]
7993 if {!$allcupdate} {
7994 set ids "--all"
7995 } else {
7996 set refs [concat [array names idheads] [array names idtags] \
7997 [array names idotherrefs]]
7998 set ids {}
7999 set tagobjs {}
8000 foreach name [array names tagobjid] {
8001 lappend tagobjs $tagobjid($name)
8003 foreach id [lsort -unique $refs] {
8004 if {![info exists allparents($id)] &&
8005 [lsearch -exact $tagobjs $id] < 0} {
8006 lappend ids $id
8009 if {$ids ne {}} {
8010 foreach id $seeds {
8011 lappend ids "^$id"
8015 if {$ids ne {}} {
8016 set fd [open [concat $cmd $ids] r]
8017 fconfigure $fd -blocking 0
8018 incr allcommits
8019 nowbusy allcommits
8020 filerun $fd [list getallclines $fd]
8021 } else {
8022 dispneartags 0
8026 # Since most commits have 1 parent and 1 child, we group strings of
8027 # such commits into "arcs" joining branch/merge points (BMPs), which
8028 # are commits that either don't have 1 parent or don't have 1 child.
8030 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8031 # arcout(id) - outgoing arcs for BMP
8032 # arcids(a) - list of IDs on arc including end but not start
8033 # arcstart(a) - BMP ID at start of arc
8034 # arcend(a) - BMP ID at end of arc
8035 # growing(a) - arc a is still growing
8036 # arctags(a) - IDs out of arcids (excluding end) that have tags
8037 # archeads(a) - IDs out of arcids (excluding end) that have heads
8038 # The start of an arc is at the descendent end, so "incoming" means
8039 # coming from descendents, and "outgoing" means going towards ancestors.
8041 proc getallclines {fd} {
8042 global allparents allchildren idtags idheads nextarc
8043 global arcnos arcids arctags arcout arcend arcstart archeads growing
8044 global seeds allcommits cachedarcs allcupdate
8046 set nid 0
8047 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8048 set id [lindex $line 0]
8049 if {[info exists allparents($id)]} {
8050 # seen it already
8051 continue
8053 set cachedarcs 0
8054 set olds [lrange $line 1 end]
8055 set allparents($id) $olds
8056 if {![info exists allchildren($id)]} {
8057 set allchildren($id) {}
8058 set arcnos($id) {}
8059 lappend seeds $id
8060 } else {
8061 set a $arcnos($id)
8062 if {[llength $olds] == 1 && [llength $a] == 1} {
8063 lappend arcids($a) $id
8064 if {[info exists idtags($id)]} {
8065 lappend arctags($a) $id
8067 if {[info exists idheads($id)]} {
8068 lappend archeads($a) $id
8070 if {[info exists allparents($olds)]} {
8071 # seen parent already
8072 if {![info exists arcout($olds)]} {
8073 splitarc $olds
8075 lappend arcids($a) $olds
8076 set arcend($a) $olds
8077 unset growing($a)
8079 lappend allchildren($olds) $id
8080 lappend arcnos($olds) $a
8081 continue
8084 foreach a $arcnos($id) {
8085 lappend arcids($a) $id
8086 set arcend($a) $id
8087 unset growing($a)
8090 set ao {}
8091 foreach p $olds {
8092 lappend allchildren($p) $id
8093 set a [incr nextarc]
8094 set arcstart($a) $id
8095 set archeads($a) {}
8096 set arctags($a) {}
8097 set archeads($a) {}
8098 set arcids($a) {}
8099 lappend ao $a
8100 set growing($a) 1
8101 if {[info exists allparents($p)]} {
8102 # seen it already, may need to make a new branch
8103 if {![info exists arcout($p)]} {
8104 splitarc $p
8106 lappend arcids($a) $p
8107 set arcend($a) $p
8108 unset growing($a)
8110 lappend arcnos($p) $a
8112 set arcout($id) $ao
8114 if {$nid > 0} {
8115 global cached_dheads cached_dtags cached_atags
8116 catch {unset cached_dheads}
8117 catch {unset cached_dtags}
8118 catch {unset cached_atags}
8120 if {![eof $fd]} {
8121 return [expr {$nid >= 1000? 2: 1}]
8123 set cacheok 1
8124 if {[catch {
8125 fconfigure $fd -blocking 1
8126 close $fd
8127 } err]} {
8128 # got an error reading the list of commits
8129 # if we were updating, try rereading the whole thing again
8130 if {$allcupdate} {
8131 incr allcommits -1
8132 dropcache $err
8133 return
8135 error_popup "[mc "Error reading commit topology information;\
8136 branch and preceding/following tag information\
8137 will be incomplete."]\n($err)"
8138 set cacheok 0
8140 if {[incr allcommits -1] == 0} {
8141 notbusy allcommits
8142 if {$cacheok} {
8143 run savecache
8146 dispneartags 0
8147 return 0
8150 proc recalcarc {a} {
8151 global arctags archeads arcids idtags idheads
8153 set at {}
8154 set ah {}
8155 foreach id [lrange $arcids($a) 0 end-1] {
8156 if {[info exists idtags($id)]} {
8157 lappend at $id
8159 if {[info exists idheads($id)]} {
8160 lappend ah $id
8163 set arctags($a) $at
8164 set archeads($a) $ah
8167 proc splitarc {p} {
8168 global arcnos arcids nextarc arctags archeads idtags idheads
8169 global arcstart arcend arcout allparents growing
8171 set a $arcnos($p)
8172 if {[llength $a] != 1} {
8173 puts "oops splitarc called but [llength $a] arcs already"
8174 return
8176 set a [lindex $a 0]
8177 set i [lsearch -exact $arcids($a) $p]
8178 if {$i < 0} {
8179 puts "oops splitarc $p not in arc $a"
8180 return
8182 set na [incr nextarc]
8183 if {[info exists arcend($a)]} {
8184 set arcend($na) $arcend($a)
8185 } else {
8186 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8187 set j [lsearch -exact $arcnos($l) $a]
8188 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8190 set tail [lrange $arcids($a) [expr {$i+1}] end]
8191 set arcids($a) [lrange $arcids($a) 0 $i]
8192 set arcend($a) $p
8193 set arcstart($na) $p
8194 set arcout($p) $na
8195 set arcids($na) $tail
8196 if {[info exists growing($a)]} {
8197 set growing($na) 1
8198 unset growing($a)
8201 foreach id $tail {
8202 if {[llength $arcnos($id)] == 1} {
8203 set arcnos($id) $na
8204 } else {
8205 set j [lsearch -exact $arcnos($id) $a]
8206 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8210 # reconstruct tags and heads lists
8211 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8212 recalcarc $a
8213 recalcarc $na
8214 } else {
8215 set arctags($na) {}
8216 set archeads($na) {}
8220 # Update things for a new commit added that is a child of one
8221 # existing commit. Used when cherry-picking.
8222 proc addnewchild {id p} {
8223 global allparents allchildren idtags nextarc
8224 global arcnos arcids arctags arcout arcend arcstart archeads growing
8225 global seeds allcommits
8227 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8228 set allparents($id) [list $p]
8229 set allchildren($id) {}
8230 set arcnos($id) {}
8231 lappend seeds $id
8232 lappend allchildren($p) $id
8233 set a [incr nextarc]
8234 set arcstart($a) $id
8235 set archeads($a) {}
8236 set arctags($a) {}
8237 set arcids($a) [list $p]
8238 set arcend($a) $p
8239 if {![info exists arcout($p)]} {
8240 splitarc $p
8242 lappend arcnos($p) $a
8243 set arcout($id) [list $a]
8246 # This implements a cache for the topology information.
8247 # The cache saves, for each arc, the start and end of the arc,
8248 # the ids on the arc, and the outgoing arcs from the end.
8249 proc readcache {f} {
8250 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8251 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8252 global allcwait
8254 set a $nextarc
8255 set lim $cachedarcs
8256 if {$lim - $a > 500} {
8257 set lim [expr {$a + 500}]
8259 if {[catch {
8260 if {$a == $lim} {
8261 # finish reading the cache and setting up arctags, etc.
8262 set line [gets $f]
8263 if {$line ne "1"} {error "bad final version"}
8264 close $f
8265 foreach id [array names idtags] {
8266 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8267 [llength $allparents($id)] == 1} {
8268 set a [lindex $arcnos($id) 0]
8269 if {$arctags($a) eq {}} {
8270 recalcarc $a
8274 foreach id [array names idheads] {
8275 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8276 [llength $allparents($id)] == 1} {
8277 set a [lindex $arcnos($id) 0]
8278 if {$archeads($a) eq {}} {
8279 recalcarc $a
8283 foreach id [lsort -unique $possible_seeds] {
8284 if {$arcnos($id) eq {}} {
8285 lappend seeds $id
8288 set allcwait 0
8289 } else {
8290 while {[incr a] <= $lim} {
8291 set line [gets $f]
8292 if {[llength $line] != 3} {error "bad line"}
8293 set s [lindex $line 0]
8294 set arcstart($a) $s
8295 lappend arcout($s) $a
8296 if {![info exists arcnos($s)]} {
8297 lappend possible_seeds $s
8298 set arcnos($s) {}
8300 set e [lindex $line 1]
8301 if {$e eq {}} {
8302 set growing($a) 1
8303 } else {
8304 set arcend($a) $e
8305 if {![info exists arcout($e)]} {
8306 set arcout($e) {}
8309 set arcids($a) [lindex $line 2]
8310 foreach id $arcids($a) {
8311 lappend allparents($s) $id
8312 set s $id
8313 lappend arcnos($id) $a
8315 if {![info exists allparents($s)]} {
8316 set allparents($s) {}
8318 set arctags($a) {}
8319 set archeads($a) {}
8321 set nextarc [expr {$a - 1}]
8323 } err]} {
8324 dropcache $err
8325 return 0
8327 if {!$allcwait} {
8328 getallcommits
8330 return $allcwait
8333 proc getcache {f} {
8334 global nextarc cachedarcs possible_seeds
8336 if {[catch {
8337 set line [gets $f]
8338 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8339 # make sure it's an integer
8340 set cachedarcs [expr {int([lindex $line 1])}]
8341 if {$cachedarcs < 0} {error "bad number of arcs"}
8342 set nextarc 0
8343 set possible_seeds {}
8344 run readcache $f
8345 } err]} {
8346 dropcache $err
8348 return 0
8351 proc dropcache {err} {
8352 global allcwait nextarc cachedarcs seeds
8354 #puts "dropping cache ($err)"
8355 foreach v {arcnos arcout arcids arcstart arcend growing \
8356 arctags archeads allparents allchildren} {
8357 global $v
8358 catch {unset $v}
8360 set allcwait 0
8361 set nextarc 0
8362 set cachedarcs 0
8363 set seeds {}
8364 getallcommits
8367 proc writecache {f} {
8368 global cachearc cachedarcs allccache
8369 global arcstart arcend arcnos arcids arcout
8371 set a $cachearc
8372 set lim $cachedarcs
8373 if {$lim - $a > 1000} {
8374 set lim [expr {$a + 1000}]
8376 if {[catch {
8377 while {[incr a] <= $lim} {
8378 if {[info exists arcend($a)]} {
8379 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8380 } else {
8381 puts $f [list $arcstart($a) {} $arcids($a)]
8384 } err]} {
8385 catch {close $f}
8386 catch {file delete $allccache}
8387 #puts "writing cache failed ($err)"
8388 return 0
8390 set cachearc [expr {$a - 1}]
8391 if {$a > $cachedarcs} {
8392 puts $f "1"
8393 close $f
8394 return 0
8396 return 1
8399 proc savecache {} {
8400 global nextarc cachedarcs cachearc allccache
8402 if {$nextarc == $cachedarcs} return
8403 set cachearc 0
8404 set cachedarcs $nextarc
8405 catch {
8406 set f [open $allccache w]
8407 puts $f [list 1 $cachedarcs]
8408 run writecache $f
8412 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8413 # or 0 if neither is true.
8414 proc anc_or_desc {a b} {
8415 global arcout arcstart arcend arcnos cached_isanc
8417 if {$arcnos($a) eq $arcnos($b)} {
8418 # Both are on the same arc(s); either both are the same BMP,
8419 # or if one is not a BMP, the other is also not a BMP or is
8420 # the BMP at end of the arc (and it only has 1 incoming arc).
8421 # Or both can be BMPs with no incoming arcs.
8422 if {$a eq $b || $arcnos($a) eq {}} {
8423 return 0
8425 # assert {[llength $arcnos($a)] == 1}
8426 set arc [lindex $arcnos($a) 0]
8427 set i [lsearch -exact $arcids($arc) $a]
8428 set j [lsearch -exact $arcids($arc) $b]
8429 if {$i < 0 || $i > $j} {
8430 return 1
8431 } else {
8432 return -1
8436 if {![info exists arcout($a)]} {
8437 set arc [lindex $arcnos($a) 0]
8438 if {[info exists arcend($arc)]} {
8439 set aend $arcend($arc)
8440 } else {
8441 set aend {}
8443 set a $arcstart($arc)
8444 } else {
8445 set aend $a
8447 if {![info exists arcout($b)]} {
8448 set arc [lindex $arcnos($b) 0]
8449 if {[info exists arcend($arc)]} {
8450 set bend $arcend($arc)
8451 } else {
8452 set bend {}
8454 set b $arcstart($arc)
8455 } else {
8456 set bend $b
8458 if {$a eq $bend} {
8459 return 1
8461 if {$b eq $aend} {
8462 return -1
8464 if {[info exists cached_isanc($a,$bend)]} {
8465 if {$cached_isanc($a,$bend)} {
8466 return 1
8469 if {[info exists cached_isanc($b,$aend)]} {
8470 if {$cached_isanc($b,$aend)} {
8471 return -1
8473 if {[info exists cached_isanc($a,$bend)]} {
8474 return 0
8478 set todo [list $a $b]
8479 set anc($a) a
8480 set anc($b) b
8481 for {set i 0} {$i < [llength $todo]} {incr i} {
8482 set x [lindex $todo $i]
8483 if {$anc($x) eq {}} {
8484 continue
8486 foreach arc $arcnos($x) {
8487 set xd $arcstart($arc)
8488 if {$xd eq $bend} {
8489 set cached_isanc($a,$bend) 1
8490 set cached_isanc($b,$aend) 0
8491 return 1
8492 } elseif {$xd eq $aend} {
8493 set cached_isanc($b,$aend) 1
8494 set cached_isanc($a,$bend) 0
8495 return -1
8497 if {![info exists anc($xd)]} {
8498 set anc($xd) $anc($x)
8499 lappend todo $xd
8500 } elseif {$anc($xd) ne $anc($x)} {
8501 set anc($xd) {}
8505 set cached_isanc($a,$bend) 0
8506 set cached_isanc($b,$aend) 0
8507 return 0
8510 # This identifies whether $desc has an ancestor that is
8511 # a growing tip of the graph and which is not an ancestor of $anc
8512 # and returns 0 if so and 1 if not.
8513 # If we subsequently discover a tag on such a growing tip, and that
8514 # turns out to be a descendent of $anc (which it could, since we
8515 # don't necessarily see children before parents), then $desc
8516 # isn't a good choice to display as a descendent tag of
8517 # $anc (since it is the descendent of another tag which is
8518 # a descendent of $anc). Similarly, $anc isn't a good choice to
8519 # display as a ancestor tag of $desc.
8521 proc is_certain {desc anc} {
8522 global arcnos arcout arcstart arcend growing problems
8524 set certain {}
8525 if {[llength $arcnos($anc)] == 1} {
8526 # tags on the same arc are certain
8527 if {$arcnos($desc) eq $arcnos($anc)} {
8528 return 1
8530 if {![info exists arcout($anc)]} {
8531 # if $anc is partway along an arc, use the start of the arc instead
8532 set a [lindex $arcnos($anc) 0]
8533 set anc $arcstart($a)
8536 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
8537 set x $desc
8538 } else {
8539 set a [lindex $arcnos($desc) 0]
8540 set x $arcend($a)
8542 if {$x == $anc} {
8543 return 1
8545 set anclist [list $x]
8546 set dl($x) 1
8547 set nnh 1
8548 set ngrowanc 0
8549 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
8550 set x [lindex $anclist $i]
8551 if {$dl($x)} {
8552 incr nnh -1
8554 set done($x) 1
8555 foreach a $arcout($x) {
8556 if {[info exists growing($a)]} {
8557 if {![info exists growanc($x)] && $dl($x)} {
8558 set growanc($x) 1
8559 incr ngrowanc
8561 } else {
8562 set y $arcend($a)
8563 if {[info exists dl($y)]} {
8564 if {$dl($y)} {
8565 if {!$dl($x)} {
8566 set dl($y) 0
8567 if {![info exists done($y)]} {
8568 incr nnh -1
8570 if {[info exists growanc($x)]} {
8571 incr ngrowanc -1
8573 set xl [list $y]
8574 for {set k 0} {$k < [llength $xl]} {incr k} {
8575 set z [lindex $xl $k]
8576 foreach c $arcout($z) {
8577 if {[info exists arcend($c)]} {
8578 set v $arcend($c)
8579 if {[info exists dl($v)] && $dl($v)} {
8580 set dl($v) 0
8581 if {![info exists done($v)]} {
8582 incr nnh -1
8584 if {[info exists growanc($v)]} {
8585 incr ngrowanc -1
8587 lappend xl $v
8594 } elseif {$y eq $anc || !$dl($x)} {
8595 set dl($y) 0
8596 lappend anclist $y
8597 } else {
8598 set dl($y) 1
8599 lappend anclist $y
8600 incr nnh
8605 foreach x [array names growanc] {
8606 if {$dl($x)} {
8607 return 0
8609 return 0
8611 return 1
8614 proc validate_arctags {a} {
8615 global arctags idtags
8617 set i -1
8618 set na $arctags($a)
8619 foreach id $arctags($a) {
8620 incr i
8621 if {![info exists idtags($id)]} {
8622 set na [lreplace $na $i $i]
8623 incr i -1
8626 set arctags($a) $na
8629 proc validate_archeads {a} {
8630 global archeads idheads
8632 set i -1
8633 set na $archeads($a)
8634 foreach id $archeads($a) {
8635 incr i
8636 if {![info exists idheads($id)]} {
8637 set na [lreplace $na $i $i]
8638 incr i -1
8641 set archeads($a) $na
8644 # Return the list of IDs that have tags that are descendents of id,
8645 # ignoring IDs that are descendents of IDs already reported.
8646 proc desctags {id} {
8647 global arcnos arcstart arcids arctags idtags allparents
8648 global growing cached_dtags
8650 if {![info exists allparents($id)]} {
8651 return {}
8653 set t1 [clock clicks -milliseconds]
8654 set argid $id
8655 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8656 # part-way along an arc; check that arc first
8657 set a [lindex $arcnos($id) 0]
8658 if {$arctags($a) ne {}} {
8659 validate_arctags $a
8660 set i [lsearch -exact $arcids($a) $id]
8661 set tid {}
8662 foreach t $arctags($a) {
8663 set j [lsearch -exact $arcids($a) $t]
8664 if {$j >= $i} break
8665 set tid $t
8667 if {$tid ne {}} {
8668 return $tid
8671 set id $arcstart($a)
8672 if {[info exists idtags($id)]} {
8673 return $id
8676 if {[info exists cached_dtags($id)]} {
8677 return $cached_dtags($id)
8680 set origid $id
8681 set todo [list $id]
8682 set queued($id) 1
8683 set nc 1
8684 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8685 set id [lindex $todo $i]
8686 set done($id) 1
8687 set ta [info exists hastaggedancestor($id)]
8688 if {!$ta} {
8689 incr nc -1
8691 # ignore tags on starting node
8692 if {!$ta && $i > 0} {
8693 if {[info exists idtags($id)]} {
8694 set tagloc($id) $id
8695 set ta 1
8696 } elseif {[info exists cached_dtags($id)]} {
8697 set tagloc($id) $cached_dtags($id)
8698 set ta 1
8701 foreach a $arcnos($id) {
8702 set d $arcstart($a)
8703 if {!$ta && $arctags($a) ne {}} {
8704 validate_arctags $a
8705 if {$arctags($a) ne {}} {
8706 lappend tagloc($id) [lindex $arctags($a) end]
8709 if {$ta || $arctags($a) ne {}} {
8710 set tomark [list $d]
8711 for {set j 0} {$j < [llength $tomark]} {incr j} {
8712 set dd [lindex $tomark $j]
8713 if {![info exists hastaggedancestor($dd)]} {
8714 if {[info exists done($dd)]} {
8715 foreach b $arcnos($dd) {
8716 lappend tomark $arcstart($b)
8718 if {[info exists tagloc($dd)]} {
8719 unset tagloc($dd)
8721 } elseif {[info exists queued($dd)]} {
8722 incr nc -1
8724 set hastaggedancestor($dd) 1
8728 if {![info exists queued($d)]} {
8729 lappend todo $d
8730 set queued($d) 1
8731 if {![info exists hastaggedancestor($d)]} {
8732 incr nc
8737 set tags {}
8738 foreach id [array names tagloc] {
8739 if {![info exists hastaggedancestor($id)]} {
8740 foreach t $tagloc($id) {
8741 if {[lsearch -exact $tags $t] < 0} {
8742 lappend tags $t
8747 set t2 [clock clicks -milliseconds]
8748 set loopix $i
8750 # remove tags that are descendents of other tags
8751 for {set i 0} {$i < [llength $tags]} {incr i} {
8752 set a [lindex $tags $i]
8753 for {set j 0} {$j < $i} {incr j} {
8754 set b [lindex $tags $j]
8755 set r [anc_or_desc $a $b]
8756 if {$r == 1} {
8757 set tags [lreplace $tags $j $j]
8758 incr j -1
8759 incr i -1
8760 } elseif {$r == -1} {
8761 set tags [lreplace $tags $i $i]
8762 incr i -1
8763 break
8768 if {[array names growing] ne {}} {
8769 # graph isn't finished, need to check if any tag could get
8770 # eclipsed by another tag coming later. Simply ignore any
8771 # tags that could later get eclipsed.
8772 set ctags {}
8773 foreach t $tags {
8774 if {[is_certain $t $origid]} {
8775 lappend ctags $t
8778 if {$tags eq $ctags} {
8779 set cached_dtags($origid) $tags
8780 } else {
8781 set tags $ctags
8783 } else {
8784 set cached_dtags($origid) $tags
8786 set t3 [clock clicks -milliseconds]
8787 if {0 && $t3 - $t1 >= 100} {
8788 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
8789 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8791 return $tags
8794 proc anctags {id} {
8795 global arcnos arcids arcout arcend arctags idtags allparents
8796 global growing cached_atags
8798 if {![info exists allparents($id)]} {
8799 return {}
8801 set t1 [clock clicks -milliseconds]
8802 set argid $id
8803 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8804 # part-way along an arc; check that arc first
8805 set a [lindex $arcnos($id) 0]
8806 if {$arctags($a) ne {}} {
8807 validate_arctags $a
8808 set i [lsearch -exact $arcids($a) $id]
8809 foreach t $arctags($a) {
8810 set j [lsearch -exact $arcids($a) $t]
8811 if {$j > $i} {
8812 return $t
8816 if {![info exists arcend($a)]} {
8817 return {}
8819 set id $arcend($a)
8820 if {[info exists idtags($id)]} {
8821 return $id
8824 if {[info exists cached_atags($id)]} {
8825 return $cached_atags($id)
8828 set origid $id
8829 set todo [list $id]
8830 set queued($id) 1
8831 set taglist {}
8832 set nc 1
8833 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
8834 set id [lindex $todo $i]
8835 set done($id) 1
8836 set td [info exists hastaggeddescendent($id)]
8837 if {!$td} {
8838 incr nc -1
8840 # ignore tags on starting node
8841 if {!$td && $i > 0} {
8842 if {[info exists idtags($id)]} {
8843 set tagloc($id) $id
8844 set td 1
8845 } elseif {[info exists cached_atags($id)]} {
8846 set tagloc($id) $cached_atags($id)
8847 set td 1
8850 foreach a $arcout($id) {
8851 if {!$td && $arctags($a) ne {}} {
8852 validate_arctags $a
8853 if {$arctags($a) ne {}} {
8854 lappend tagloc($id) [lindex $arctags($a) 0]
8857 if {![info exists arcend($a)]} continue
8858 set d $arcend($a)
8859 if {$td || $arctags($a) ne {}} {
8860 set tomark [list $d]
8861 for {set j 0} {$j < [llength $tomark]} {incr j} {
8862 set dd [lindex $tomark $j]
8863 if {![info exists hastaggeddescendent($dd)]} {
8864 if {[info exists done($dd)]} {
8865 foreach b $arcout($dd) {
8866 if {[info exists arcend($b)]} {
8867 lappend tomark $arcend($b)
8870 if {[info exists tagloc($dd)]} {
8871 unset tagloc($dd)
8873 } elseif {[info exists queued($dd)]} {
8874 incr nc -1
8876 set hastaggeddescendent($dd) 1
8880 if {![info exists queued($d)]} {
8881 lappend todo $d
8882 set queued($d) 1
8883 if {![info exists hastaggeddescendent($d)]} {
8884 incr nc
8889 set t2 [clock clicks -milliseconds]
8890 set loopix $i
8891 set tags {}
8892 foreach id [array names tagloc] {
8893 if {![info exists hastaggeddescendent($id)]} {
8894 foreach t $tagloc($id) {
8895 if {[lsearch -exact $tags $t] < 0} {
8896 lappend tags $t
8902 # remove tags that are ancestors of other tags
8903 for {set i 0} {$i < [llength $tags]} {incr i} {
8904 set a [lindex $tags $i]
8905 for {set j 0} {$j < $i} {incr j} {
8906 set b [lindex $tags $j]
8907 set r [anc_or_desc $a $b]
8908 if {$r == -1} {
8909 set tags [lreplace $tags $j $j]
8910 incr j -1
8911 incr i -1
8912 } elseif {$r == 1} {
8913 set tags [lreplace $tags $i $i]
8914 incr i -1
8915 break
8920 if {[array names growing] ne {}} {
8921 # graph isn't finished, need to check if any tag could get
8922 # eclipsed by another tag coming later. Simply ignore any
8923 # tags that could later get eclipsed.
8924 set ctags {}
8925 foreach t $tags {
8926 if {[is_certain $origid $t]} {
8927 lappend ctags $t
8930 if {$tags eq $ctags} {
8931 set cached_atags($origid) $tags
8932 } else {
8933 set tags $ctags
8935 } else {
8936 set cached_atags($origid) $tags
8938 set t3 [clock clicks -milliseconds]
8939 if {0 && $t3 - $t1 >= 100} {
8940 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
8941 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
8943 return $tags
8946 # Return the list of IDs that have heads that are descendents of id,
8947 # including id itself if it has a head.
8948 proc descheads {id} {
8949 global arcnos arcstart arcids archeads idheads cached_dheads
8950 global allparents
8952 if {![info exists allparents($id)]} {
8953 return {}
8955 set aret {}
8956 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
8957 # part-way along an arc; check it first
8958 set a [lindex $arcnos($id) 0]
8959 if {$archeads($a) ne {}} {
8960 validate_archeads $a
8961 set i [lsearch -exact $arcids($a) $id]
8962 foreach t $archeads($a) {
8963 set j [lsearch -exact $arcids($a) $t]
8964 if {$j > $i} break
8965 lappend aret $t
8968 set id $arcstart($a)
8970 set origid $id
8971 set todo [list $id]
8972 set seen($id) 1
8973 set ret {}
8974 for {set i 0} {$i < [llength $todo]} {incr i} {
8975 set id [lindex $todo $i]
8976 if {[info exists cached_dheads($id)]} {
8977 set ret [concat $ret $cached_dheads($id)]
8978 } else {
8979 if {[info exists idheads($id)]} {
8980 lappend ret $id
8982 foreach a $arcnos($id) {
8983 if {$archeads($a) ne {}} {
8984 validate_archeads $a
8985 if {$archeads($a) ne {}} {
8986 set ret [concat $ret $archeads($a)]
8989 set d $arcstart($a)
8990 if {![info exists seen($d)]} {
8991 lappend todo $d
8992 set seen($d) 1
8997 set ret [lsort -unique $ret]
8998 set cached_dheads($origid) $ret
8999 return [concat $ret $aret]
9002 proc addedtag {id} {
9003 global arcnos arcout cached_dtags cached_atags
9005 if {![info exists arcnos($id)]} return
9006 if {![info exists arcout($id)]} {
9007 recalcarc [lindex $arcnos($id) 0]
9009 catch {unset cached_dtags}
9010 catch {unset cached_atags}
9013 proc addedhead {hid head} {
9014 global arcnos arcout cached_dheads
9016 if {![info exists arcnos($hid)]} return
9017 if {![info exists arcout($hid)]} {
9018 recalcarc [lindex $arcnos($hid) 0]
9020 catch {unset cached_dheads}
9023 proc removedhead {hid head} {
9024 global cached_dheads
9026 catch {unset cached_dheads}
9029 proc movedhead {hid head} {
9030 global arcnos arcout cached_dheads
9032 if {![info exists arcnos($hid)]} return
9033 if {![info exists arcout($hid)]} {
9034 recalcarc [lindex $arcnos($hid) 0]
9036 catch {unset cached_dheads}
9039 proc changedrefs {} {
9040 global cached_dheads cached_dtags cached_atags
9041 global arctags archeads arcnos arcout idheads idtags
9043 foreach id [concat [array names idheads] [array names idtags]] {
9044 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9045 set a [lindex $arcnos($id) 0]
9046 if {![info exists donearc($a)]} {
9047 recalcarc $a
9048 set donearc($a) 1
9052 catch {unset cached_dtags}
9053 catch {unset cached_atags}
9054 catch {unset cached_dheads}
9057 proc rereadrefs {} {
9058 global idtags idheads idotherrefs mainheadid
9060 set refids [concat [array names idtags] \
9061 [array names idheads] [array names idotherrefs]]
9062 foreach id $refids {
9063 if {![info exists ref($id)]} {
9064 set ref($id) [listrefs $id]
9067 set oldmainhead $mainheadid
9068 readrefs
9069 changedrefs
9070 set refids [lsort -unique [concat $refids [array names idtags] \
9071 [array names idheads] [array names idotherrefs]]]
9072 foreach id $refids {
9073 set v [listrefs $id]
9074 if {![info exists ref($id)] || $ref($id) != $v} {
9075 redrawtags $id
9078 if {$oldmainhead ne $mainheadid} {
9079 redrawtags $oldmainhead
9080 redrawtags $mainheadid
9082 run refill_reflist
9085 proc listrefs {id} {
9086 global idtags idheads idotherrefs
9088 set x {}
9089 if {[info exists idtags($id)]} {
9090 set x $idtags($id)
9092 set y {}
9093 if {[info exists idheads($id)]} {
9094 set y $idheads($id)
9096 set z {}
9097 if {[info exists idotherrefs($id)]} {
9098 set z $idotherrefs($id)
9100 return [list $x $y $z]
9103 proc showtag {tag isnew} {
9104 global ctext tagcontents tagids linknum tagobjid
9106 if {$isnew} {
9107 addtohistory [list showtag $tag 0]
9109 $ctext conf -state normal
9110 clear_ctext
9111 settabs 0
9112 set linknum 0
9113 if {![info exists tagcontents($tag)]} {
9114 catch {
9115 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9118 if {[info exists tagcontents($tag)]} {
9119 set text $tagcontents($tag)
9120 } else {
9121 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9123 appendwithlinks $text {}
9124 $ctext conf -state disabled
9125 init_flist {}
9128 proc doquit {} {
9129 global stopped
9130 global gitktmpdir
9132 set stopped 100
9133 savestuff .
9134 destroy .
9136 if {[info exists gitktmpdir]} {
9137 catch {file delete -force $gitktmpdir}
9141 proc mkfontdisp {font top which} {
9142 global fontattr fontpref $font
9144 set fontpref($font) [set $font]
9145 button $top.${font}but -text $which -font optionfont \
9146 -command [list choosefont $font $which]
9147 label $top.$font -relief flat -font $font \
9148 -text $fontattr($font,family) -justify left
9149 grid x $top.${font}but $top.$font -sticky w
9152 proc choosefont {font which} {
9153 global fontparam fontlist fonttop fontattr
9155 set fontparam(which) $which
9156 set fontparam(font) $font
9157 set fontparam(family) [font actual $font -family]
9158 set fontparam(size) $fontattr($font,size)
9159 set fontparam(weight) $fontattr($font,weight)
9160 set fontparam(slant) $fontattr($font,slant)
9161 set top .gitkfont
9162 set fonttop $top
9163 if {![winfo exists $top]} {
9164 font create sample
9165 eval font config sample [font actual $font]
9166 toplevel $top
9167 wm title $top [mc "Gitk font chooser"]
9168 label $top.l -textvariable fontparam(which)
9169 pack $top.l -side top
9170 set fontlist [lsort [font families]]
9171 frame $top.f
9172 listbox $top.f.fam -listvariable fontlist \
9173 -yscrollcommand [list $top.f.sb set]
9174 bind $top.f.fam <<ListboxSelect>> selfontfam
9175 scrollbar $top.f.sb -command [list $top.f.fam yview]
9176 pack $top.f.sb -side right -fill y
9177 pack $top.f.fam -side left -fill both -expand 1
9178 pack $top.f -side top -fill both -expand 1
9179 frame $top.g
9180 spinbox $top.g.size -from 4 -to 40 -width 4 \
9181 -textvariable fontparam(size) \
9182 -validatecommand {string is integer -strict %s}
9183 checkbutton $top.g.bold -padx 5 \
9184 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9185 -variable fontparam(weight) -onvalue bold -offvalue normal
9186 checkbutton $top.g.ital -padx 5 \
9187 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9188 -variable fontparam(slant) -onvalue italic -offvalue roman
9189 pack $top.g.size $top.g.bold $top.g.ital -side left
9190 pack $top.g -side top
9191 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9192 -background white
9193 $top.c create text 100 25 -anchor center -text $which -font sample \
9194 -fill black -tags text
9195 bind $top.c <Configure> [list centertext $top.c]
9196 pack $top.c -side top -fill x
9197 frame $top.buts
9198 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9199 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9200 grid $top.buts.ok $top.buts.can
9201 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9202 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9203 pack $top.buts -side bottom -fill x
9204 trace add variable fontparam write chg_fontparam
9205 } else {
9206 raise $top
9207 $top.c itemconf text -text $which
9209 set i [lsearch -exact $fontlist $fontparam(family)]
9210 if {$i >= 0} {
9211 $top.f.fam selection set $i
9212 $top.f.fam see $i
9216 proc centertext {w} {
9217 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9220 proc fontok {} {
9221 global fontparam fontpref prefstop
9223 set f $fontparam(font)
9224 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9225 if {$fontparam(weight) eq "bold"} {
9226 lappend fontpref($f) "bold"
9228 if {$fontparam(slant) eq "italic"} {
9229 lappend fontpref($f) "italic"
9231 set w $prefstop.$f
9232 $w conf -text $fontparam(family) -font $fontpref($f)
9234 fontcan
9237 proc fontcan {} {
9238 global fonttop fontparam
9240 if {[info exists fonttop]} {
9241 catch {destroy $fonttop}
9242 catch {font delete sample}
9243 unset fonttop
9244 unset fontparam
9248 proc selfontfam {} {
9249 global fonttop fontparam
9251 set i [$fonttop.f.fam curselection]
9252 if {$i ne {}} {
9253 set fontparam(family) [$fonttop.f.fam get $i]
9257 proc chg_fontparam {v sub op} {
9258 global fontparam
9260 font config sample -$sub $fontparam($sub)
9263 proc doprefs {} {
9264 global maxwidth maxgraphpct
9265 global oldprefs prefstop showneartags showlocalchanges
9266 global bgcolor fgcolor ctext diffcolors selectbgcolor
9267 global tabstop limitdiffs autoselect extdifftool
9269 set top .gitkprefs
9270 set prefstop $top
9271 if {[winfo exists $top]} {
9272 raise $top
9273 return
9275 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9276 limitdiffs tabstop} {
9277 set oldprefs($v) [set $v]
9279 toplevel $top
9280 wm title $top [mc "Gitk preferences"]
9281 label $top.ldisp -text [mc "Commit list display options"]
9282 grid $top.ldisp - -sticky w -pady 10
9283 label $top.spacer -text " "
9284 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9285 -font optionfont
9286 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9287 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9288 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9289 -font optionfont
9290 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9291 grid x $top.maxpctl $top.maxpct -sticky w
9292 frame $top.showlocal
9293 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9294 checkbutton $top.showlocal.b -variable showlocalchanges
9295 pack $top.showlocal.b $top.showlocal.l -side left
9296 grid x $top.showlocal -sticky w
9297 frame $top.autoselect
9298 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9299 checkbutton $top.autoselect.b -variable autoselect
9300 pack $top.autoselect.b $top.autoselect.l -side left
9301 grid x $top.autoselect -sticky w
9303 label $top.ddisp -text [mc "Diff display options"]
9304 grid $top.ddisp - -sticky w -pady 10
9305 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9306 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9307 grid x $top.tabstopl $top.tabstop -sticky w
9308 frame $top.ntag
9309 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9310 checkbutton $top.ntag.b -variable showneartags
9311 pack $top.ntag.b $top.ntag.l -side left
9312 grid x $top.ntag -sticky w
9313 frame $top.ldiff
9314 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9315 checkbutton $top.ldiff.b -variable limitdiffs
9316 pack $top.ldiff.b $top.ldiff.l -side left
9317 grid x $top.ldiff -sticky w
9319 entry $top.extdifft -textvariable extdifftool
9320 frame $top.extdifff
9321 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9322 -padx 10
9323 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9324 -command choose_extdiff
9325 pack $top.extdifff.l $top.extdifff.b -side left
9326 grid x $top.extdifff $top.extdifft -sticky w
9328 label $top.cdisp -text [mc "Colors: press to choose"]
9329 grid $top.cdisp - -sticky w -pady 10
9330 label $top.bg -padx 40 -relief sunk -background $bgcolor
9331 button $top.bgbut -text [mc "Background"] -font optionfont \
9332 -command [list choosecolor bgcolor {} $top.bg background setbg]
9333 grid x $top.bgbut $top.bg -sticky w
9334 label $top.fg -padx 40 -relief sunk -background $fgcolor
9335 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9336 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9337 grid x $top.fgbut $top.fg -sticky w
9338 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9339 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9340 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9341 [list $ctext tag conf d0 -foreground]]
9342 grid x $top.diffoldbut $top.diffold -sticky w
9343 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9344 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9345 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9346 [list $ctext tag conf d1 -foreground]]
9347 grid x $top.diffnewbut $top.diffnew -sticky w
9348 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9349 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9350 -command [list choosecolor diffcolors 2 $top.hunksep \
9351 "diff hunk header" \
9352 [list $ctext tag conf hunksep -foreground]]
9353 grid x $top.hunksepbut $top.hunksep -sticky w
9354 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9355 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9356 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9357 grid x $top.selbgbut $top.selbgsep -sticky w
9359 label $top.cfont -text [mc "Fonts: press to choose"]
9360 grid $top.cfont - -sticky w -pady 10
9361 mkfontdisp mainfont $top [mc "Main font"]
9362 mkfontdisp textfont $top [mc "Diff display font"]
9363 mkfontdisp uifont $top [mc "User interface font"]
9365 frame $top.buts
9366 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9367 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9368 grid $top.buts.ok $top.buts.can
9369 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9370 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9371 grid $top.buts - - -pady 10 -sticky ew
9372 bind $top <Visibility> "focus $top.buts.ok"
9375 proc choose_extdiff {} {
9376 global extdifftool
9378 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9379 if {$prog ne {}} {
9380 set extdifftool $prog
9384 proc choosecolor {v vi w x cmd} {
9385 global $v
9387 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9388 -title [mc "Gitk: choose color for %s" $x]]
9389 if {$c eq {}} return
9390 $w conf -background $c
9391 lset $v $vi $c
9392 eval $cmd $c
9395 proc setselbg {c} {
9396 global bglist cflist
9397 foreach w $bglist {
9398 $w configure -selectbackground $c
9400 $cflist tag configure highlight \
9401 -background [$cflist cget -selectbackground]
9402 allcanvs itemconf secsel -fill $c
9405 proc setbg {c} {
9406 global bglist
9408 foreach w $bglist {
9409 $w conf -background $c
9413 proc setfg {c} {
9414 global fglist canv
9416 foreach w $fglist {
9417 $w conf -foreground $c
9419 allcanvs itemconf text -fill $c
9420 $canv itemconf circle -outline $c
9423 proc prefscan {} {
9424 global oldprefs prefstop
9426 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9427 limitdiffs tabstop} {
9428 global $v
9429 set $v $oldprefs($v)
9431 catch {destroy $prefstop}
9432 unset prefstop
9433 fontcan
9436 proc prefsok {} {
9437 global maxwidth maxgraphpct
9438 global oldprefs prefstop showneartags showlocalchanges
9439 global fontpref mainfont textfont uifont
9440 global limitdiffs treediffs
9442 catch {destroy $prefstop}
9443 unset prefstop
9444 fontcan
9445 set fontchanged 0
9446 if {$mainfont ne $fontpref(mainfont)} {
9447 set mainfont $fontpref(mainfont)
9448 parsefont mainfont $mainfont
9449 eval font configure mainfont [fontflags mainfont]
9450 eval font configure mainfontbold [fontflags mainfont 1]
9451 setcoords
9452 set fontchanged 1
9454 if {$textfont ne $fontpref(textfont)} {
9455 set textfont $fontpref(textfont)
9456 parsefont textfont $textfont
9457 eval font configure textfont [fontflags textfont]
9458 eval font configure textfontbold [fontflags textfont 1]
9460 if {$uifont ne $fontpref(uifont)} {
9461 set uifont $fontpref(uifont)
9462 parsefont uifont $uifont
9463 eval font configure uifont [fontflags uifont]
9465 settabs
9466 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9467 if {$showlocalchanges} {
9468 doshowlocalchanges
9469 } else {
9470 dohidelocalchanges
9473 if {$limitdiffs != $oldprefs(limitdiffs)} {
9474 # treediffs elements are limited by path
9475 catch {unset treediffs}
9477 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9478 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9479 redisplay
9480 } elseif {$showneartags != $oldprefs(showneartags) ||
9481 $limitdiffs != $oldprefs(limitdiffs)} {
9482 reselectline
9486 proc formatdate {d} {
9487 global datetimeformat
9488 if {$d ne {}} {
9489 set d [clock format $d -format $datetimeformat]
9491 return $d
9494 # This list of encoding names and aliases is distilled from
9495 # http://www.iana.org/assignments/character-sets.
9496 # Not all of them are supported by Tcl.
9497 set encoding_aliases {
9498 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9499 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9500 { ISO-10646-UTF-1 csISO10646UTF1 }
9501 { ISO_646.basic:1983 ref csISO646basic1983 }
9502 { INVARIANT csINVARIANT }
9503 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9504 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9505 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9506 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9507 { NATS-DANO iso-ir-9-1 csNATSDANO }
9508 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9509 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9510 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9511 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9512 { ISO-2022-KR csISO2022KR }
9513 { EUC-KR csEUCKR }
9514 { ISO-2022-JP csISO2022JP }
9515 { ISO-2022-JP-2 csISO2022JP2 }
9516 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9517 csISO13JISC6220jp }
9518 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9519 { IT iso-ir-15 ISO646-IT csISO15Italian }
9520 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9521 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9522 { greek7-old iso-ir-18 csISO18Greek7Old }
9523 { latin-greek iso-ir-19 csISO19LatinGreek }
9524 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
9525 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
9526 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
9527 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
9528 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
9529 { BS_viewdata iso-ir-47 csISO47BSViewdata }
9530 { INIS iso-ir-49 csISO49INIS }
9531 { INIS-8 iso-ir-50 csISO50INIS8 }
9532 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
9533 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
9534 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
9535 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
9536 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
9537 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
9538 csISO60Norwegian1 }
9539 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
9540 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
9541 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
9542 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
9543 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
9544 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
9545 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
9546 { greek7 iso-ir-88 csISO88Greek7 }
9547 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
9548 { iso-ir-90 csISO90 }
9549 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
9550 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
9551 csISO92JISC62991984b }
9552 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
9553 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
9554 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
9555 csISO95JIS62291984handadd }
9556 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
9557 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
9558 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
9559 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
9560 CP819 csISOLatin1 }
9561 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
9562 { T.61-7bit iso-ir-102 csISO102T617bit }
9563 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
9564 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
9565 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
9566 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
9567 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
9568 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
9569 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
9570 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
9571 arabic csISOLatinArabic }
9572 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
9573 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
9574 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
9575 greek greek8 csISOLatinGreek }
9576 { T.101-G2 iso-ir-128 csISO128T101G2 }
9577 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
9578 csISOLatinHebrew }
9579 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
9580 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
9581 { CSN_369103 iso-ir-139 csISO139CSN369103 }
9582 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
9583 { ISO_6937-2-add iso-ir-142 csISOTextComm }
9584 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
9585 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
9586 csISOLatinCyrillic }
9587 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
9588 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
9589 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
9590 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
9591 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
9592 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
9593 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
9594 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
9595 { ISO_10367-box iso-ir-155 csISO10367Box }
9596 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
9597 { latin-lap lap iso-ir-158 csISO158Lap }
9598 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
9599 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
9600 { us-dk csUSDK }
9601 { dk-us csDKUS }
9602 { JIS_X0201 X0201 csHalfWidthKatakana }
9603 { KSC5636 ISO646-KR csKSC5636 }
9604 { ISO-10646-UCS-2 csUnicode }
9605 { ISO-10646-UCS-4 csUCS4 }
9606 { DEC-MCS dec csDECMCS }
9607 { hp-roman8 roman8 r8 csHPRoman8 }
9608 { macintosh mac csMacintosh }
9609 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
9610 csIBM037 }
9611 { IBM038 EBCDIC-INT cp038 csIBM038 }
9612 { IBM273 CP273 csIBM273 }
9613 { IBM274 EBCDIC-BE CP274 csIBM274 }
9614 { IBM275 EBCDIC-BR cp275 csIBM275 }
9615 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
9616 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
9617 { IBM280 CP280 ebcdic-cp-it csIBM280 }
9618 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
9619 { IBM284 CP284 ebcdic-cp-es csIBM284 }
9620 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
9621 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
9622 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
9623 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
9624 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
9625 { IBM424 cp424 ebcdic-cp-he csIBM424 }
9626 { IBM437 cp437 437 csPC8CodePage437 }
9627 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
9628 { IBM775 cp775 csPC775Baltic }
9629 { IBM850 cp850 850 csPC850Multilingual }
9630 { IBM851 cp851 851 csIBM851 }
9631 { IBM852 cp852 852 csPCp852 }
9632 { IBM855 cp855 855 csIBM855 }
9633 { IBM857 cp857 857 csIBM857 }
9634 { IBM860 cp860 860 csIBM860 }
9635 { IBM861 cp861 861 cp-is csIBM861 }
9636 { IBM862 cp862 862 csPC862LatinHebrew }
9637 { IBM863 cp863 863 csIBM863 }
9638 { IBM864 cp864 csIBM864 }
9639 { IBM865 cp865 865 csIBM865 }
9640 { IBM866 cp866 866 csIBM866 }
9641 { IBM868 CP868 cp-ar csIBM868 }
9642 { IBM869 cp869 869 cp-gr csIBM869 }
9643 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
9644 { IBM871 CP871 ebcdic-cp-is csIBM871 }
9645 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
9646 { IBM891 cp891 csIBM891 }
9647 { IBM903 cp903 csIBM903 }
9648 { IBM904 cp904 904 csIBBM904 }
9649 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
9650 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
9651 { IBM1026 CP1026 csIBM1026 }
9652 { EBCDIC-AT-DE csIBMEBCDICATDE }
9653 { EBCDIC-AT-DE-A csEBCDICATDEA }
9654 { EBCDIC-CA-FR csEBCDICCAFR }
9655 { EBCDIC-DK-NO csEBCDICDKNO }
9656 { EBCDIC-DK-NO-A csEBCDICDKNOA }
9657 { EBCDIC-FI-SE csEBCDICFISE }
9658 { EBCDIC-FI-SE-A csEBCDICFISEA }
9659 { EBCDIC-FR csEBCDICFR }
9660 { EBCDIC-IT csEBCDICIT }
9661 { EBCDIC-PT csEBCDICPT }
9662 { EBCDIC-ES csEBCDICES }
9663 { EBCDIC-ES-A csEBCDICESA }
9664 { EBCDIC-ES-S csEBCDICESS }
9665 { EBCDIC-UK csEBCDICUK }
9666 { EBCDIC-US csEBCDICUS }
9667 { UNKNOWN-8BIT csUnknown8BiT }
9668 { MNEMONIC csMnemonic }
9669 { MNEM csMnem }
9670 { VISCII csVISCII }
9671 { VIQR csVIQR }
9672 { KOI8-R csKOI8R }
9673 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
9674 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
9675 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
9676 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
9677 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
9678 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
9679 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
9680 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
9681 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
9682 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
9683 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
9684 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
9685 { IBM1047 IBM-1047 }
9686 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
9687 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
9688 { UNICODE-1-1 csUnicode11 }
9689 { CESU-8 csCESU-8 }
9690 { BOCU-1 csBOCU-1 }
9691 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
9692 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
9693 l8 }
9694 { ISO-8859-15 ISO_8859-15 Latin-9 }
9695 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
9696 { GBK CP936 MS936 windows-936 }
9697 { JIS_Encoding csJISEncoding }
9698 { Shift_JIS MS_Kanji csShiftJIS }
9699 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
9700 EUC-JP }
9701 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
9702 { ISO-10646-UCS-Basic csUnicodeASCII }
9703 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
9704 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
9705 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
9706 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
9707 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
9708 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
9709 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
9710 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
9711 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
9712 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
9713 { Adobe-Standard-Encoding csAdobeStandardEncoding }
9714 { Ventura-US csVenturaUS }
9715 { Ventura-International csVenturaInternational }
9716 { PC8-Danish-Norwegian csPC8DanishNorwegian }
9717 { PC8-Turkish csPC8Turkish }
9718 { IBM-Symbols csIBMSymbols }
9719 { IBM-Thai csIBMThai }
9720 { HP-Legal csHPLegal }
9721 { HP-Pi-font csHPPiFont }
9722 { HP-Math8 csHPMath8 }
9723 { Adobe-Symbol-Encoding csHPPSMath }
9724 { HP-DeskTop csHPDesktop }
9725 { Ventura-Math csVenturaMath }
9726 { Microsoft-Publishing csMicrosoftPublishing }
9727 { Windows-31J csWindows31J }
9728 { GB2312 csGB2312 }
9729 { Big5 csBig5 }
9732 proc tcl_encoding {enc} {
9733 global encoding_aliases
9734 set names [encoding names]
9735 set lcnames [string tolower $names]
9736 set enc [string tolower $enc]
9737 set i [lsearch -exact $lcnames $enc]
9738 if {$i < 0} {
9739 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
9740 if {[regsub {^iso[-_]} $enc iso encx]} {
9741 set i [lsearch -exact $lcnames $encx]
9744 if {$i < 0} {
9745 foreach l $encoding_aliases {
9746 set ll [string tolower $l]
9747 if {[lsearch -exact $ll $enc] < 0} continue
9748 # look through the aliases for one that tcl knows about
9749 foreach e $ll {
9750 set i [lsearch -exact $lcnames $e]
9751 if {$i < 0} {
9752 if {[regsub {^iso[-_]} $e iso ex]} {
9753 set i [lsearch -exact $lcnames $ex]
9756 if {$i >= 0} break
9758 break
9761 if {$i >= 0} {
9762 return [lindex $names $i]
9764 return {}
9767 # First check that Tcl/Tk is recent enough
9768 if {[catch {package require Tk 8.4} err]} {
9769 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
9770 Gitk requires at least Tcl/Tk 8.4."]
9771 exit 1
9774 # defaults...
9775 set wrcomcmd "git diff-tree --stdin -p --pretty"
9777 set gitencoding {}
9778 catch {
9779 set gitencoding [exec git config --get i18n.commitencoding]
9781 if {$gitencoding == ""} {
9782 set gitencoding "utf-8"
9784 set tclencoding [tcl_encoding $gitencoding]
9785 if {$tclencoding == {}} {
9786 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
9789 set mainfont {Helvetica 9}
9790 set textfont {Courier 9}
9791 set uifont {Helvetica 9 bold}
9792 set tabstop 8
9793 set findmergefiles 0
9794 set maxgraphpct 50
9795 set maxwidth 16
9796 set revlistorder 0
9797 set fastdate 0
9798 set uparrowlen 5
9799 set downarrowlen 5
9800 set mingaplen 100
9801 set cmitmode "patch"
9802 set wrapcomment "none"
9803 set showneartags 1
9804 set maxrefs 20
9805 set maxlinelen 200
9806 set showlocalchanges 1
9807 set limitdiffs 1
9808 set datetimeformat "%Y-%m-%d %H:%M:%S"
9809 set autoselect 1
9811 set extdifftool "meld"
9813 set colors {green red blue magenta darkgrey brown orange}
9814 set bgcolor white
9815 set fgcolor black
9816 set diffcolors {red "#00a000" blue}
9817 set diffcontext 3
9818 set ignorespace 0
9819 set selectbgcolor gray85
9821 set circlecolors {white blue gray blue blue}
9823 ## For msgcat loading, first locate the installation location.
9824 if { [info exists ::env(GITK_MSGSDIR)] } {
9825 ## Msgsdir was manually set in the environment.
9826 set gitk_msgsdir $::env(GITK_MSGSDIR)
9827 } else {
9828 ## Let's guess the prefix from argv0.
9829 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
9830 set gitk_libdir [file join $gitk_prefix share gitk lib]
9831 set gitk_msgsdir [file join $gitk_libdir msgs]
9832 unset gitk_prefix
9835 ## Internationalization (i18n) through msgcat and gettext. See
9836 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
9837 package require msgcat
9838 namespace import ::msgcat::mc
9839 ## And eventually load the actual message catalog
9840 ::msgcat::mcload $gitk_msgsdir
9842 catch {source ~/.gitk}
9844 font create optionfont -family sans-serif -size -12
9846 parsefont mainfont $mainfont
9847 eval font create mainfont [fontflags mainfont]
9848 eval font create mainfontbold [fontflags mainfont 1]
9850 parsefont textfont $textfont
9851 eval font create textfont [fontflags textfont]
9852 eval font create textfontbold [fontflags textfont 1]
9854 parsefont uifont $uifont
9855 eval font create uifont [fontflags uifont]
9857 setoptions
9859 # check that we can find a .git directory somewhere...
9860 if {[catch {set gitdir [gitdir]}]} {
9861 show_error {} . [mc "Cannot find a git repository here."]
9862 exit 1
9864 if {![file isdirectory $gitdir]} {
9865 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
9866 exit 1
9869 set revtreeargs {}
9870 set cmdline_files {}
9871 set i 0
9872 set revtreeargscmd {}
9873 foreach arg $argv {
9874 switch -glob -- $arg {
9875 "" { }
9876 "--" {
9877 set cmdline_files [lrange $argv [expr {$i + 1}] end]
9878 break
9880 "--argscmd=*" {
9881 set revtreeargscmd [string range $arg 10 end]
9883 default {
9884 lappend revtreeargs $arg
9887 incr i
9890 if {$i >= [llength $argv] && $revtreeargs ne {}} {
9891 # no -- on command line, but some arguments (other than --argscmd)
9892 if {[catch {
9893 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
9894 set cmdline_files [split $f "\n"]
9895 set n [llength $cmdline_files]
9896 set revtreeargs [lrange $revtreeargs 0 end-$n]
9897 # Unfortunately git rev-parse doesn't produce an error when
9898 # something is both a revision and a filename. To be consistent
9899 # with git log and git rev-list, check revtreeargs for filenames.
9900 foreach arg $revtreeargs {
9901 if {[file exists $arg]} {
9902 show_error {} . [mc "Ambiguous argument '%s': both revision\
9903 and filename" $arg]
9904 exit 1
9907 } err]} {
9908 # unfortunately we get both stdout and stderr in $err,
9909 # so look for "fatal:".
9910 set i [string first "fatal:" $err]
9911 if {$i > 0} {
9912 set err [string range $err [expr {$i + 6}] end]
9914 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
9915 exit 1
9919 set nullid "0000000000000000000000000000000000000000"
9920 set nullid2 "0000000000000000000000000000000000000001"
9921 set nullfile "/dev/null"
9923 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
9925 set runq {}
9926 set history {}
9927 set historyindex 0
9928 set fh_serial 0
9929 set nhl_names {}
9930 set highlight_paths {}
9931 set findpattern {}
9932 set searchdirn -forwards
9933 set boldrows {}
9934 set boldnamerows {}
9935 set diffelide {0 0}
9936 set markingmatches 0
9937 set linkentercount 0
9938 set need_redisplay 0
9939 set nrows_drawn 0
9940 set firsttabstop 0
9942 set nextviewnum 1
9943 set curview 0
9944 set selectedview 0
9945 set selectedhlview [mc "None"]
9946 set highlight_related [mc "None"]
9947 set highlight_files {}
9948 set viewfiles(0) {}
9949 set viewperm(0) 0
9950 set viewargs(0) {}
9951 set viewargscmd(0) {}
9953 set selectedline {}
9954 set numcommits 0
9955 set loginstance 0
9956 set cmdlineok 0
9957 set stopped 0
9958 set stuffsaved 0
9959 set patchnum 0
9960 set lserial 0
9961 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
9962 setcoords
9963 makewindow
9964 # wait for the window to become visible
9965 tkwait visibility .
9966 wm title . "[file tail $argv0]: [file tail [pwd]]"
9967 readrefs
9969 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
9970 # create a view for the files/dirs specified on the command line
9971 set curview 1
9972 set selectedview 1
9973 set nextviewnum 2
9974 set viewname(1) [mc "Command line"]
9975 set viewfiles(1) $cmdline_files
9976 set viewargs(1) $revtreeargs
9977 set viewargscmd(1) $revtreeargscmd
9978 set viewperm(1) 0
9979 set vdatemode(1) 0
9980 addviewmenu 1
9981 .bar.view entryconf [mc "Edit view..."] -state normal
9982 .bar.view entryconf [mc "Delete view"] -state normal
9985 if {[info exists permviews]} {
9986 foreach v $permviews {
9987 set n $nextviewnum
9988 incr nextviewnum
9989 set viewname($n) [lindex $v 0]
9990 set viewfiles($n) [lindex $v 1]
9991 set viewargs($n) [lindex $v 2]
9992 set viewargscmd($n) [lindex $v 3]
9993 set viewperm($n) 1
9994 addviewmenu $n
9997 getcommits {}