gitk: Add a menu item to show where a given line comes from
[git/mingw.git] / gitk
blob7b02efb4fafd2879a182ad13b3830160d62c2fee
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 "[mc "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
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 "[mc "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 interestedin $mainheadid dodiffindex
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
381 return 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
388 catch {
389 set pid [pid $fd]
391 if {$::tcl_platform(platform) eq {windows}} {
392 exec kill -f $pid
393 } else {
394 exec kill $pid
397 catch {close $fd}
398 nukefile $fd
399 unset commfd($inst)
400 unset leftover($inst)
403 proc stop_backends {} {
404 global commfd
406 foreach inst [array names commfd] {
407 stop_instance $inst
411 proc stop_rev_list {view} {
412 global viewinstances
414 foreach inst $viewinstances($view) {
415 stop_instance $inst
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid selectheadid
423 if {$selid ne {}} {
424 set pending_select $selid
425 } elseif {$selectheadid ne {}} {
426 set pending_select $selectheadid
427 } else {
428 set pending_select $mainheadid
432 proc getcommits {selid} {
433 global canv curview need_redisplay viewactive
435 initlayout
436 if {[start_rev_list $curview]} {
437 reset_pending_select $selid
438 show_status [mc "Reading commits..."]
439 set need_redisplay 1
440 } else {
441 show_status [mc "No commits selected"]
445 proc updatecommits {} {
446 global curview vcanopt vorigargs vfilelimit viewinstances
447 global viewactive viewcomplete tclencoding
448 global startmsecs showneartags showlocalchanges
449 global mainheadid pending_select
450 global isworktree
451 global varcid vposids vnegids vflags vrevs
453 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
454 set oldmainid $mainheadid
455 rereadrefs
456 if {$showlocalchanges} {
457 if {$mainheadid ne $oldmainid} {
458 dohidelocalchanges
460 if {[commitinview $mainheadid $curview]} {
461 dodiffindex
464 set view $curview
465 if {$vcanopt($view)} {
466 set oldpos $vposids($view)
467 set oldneg $vnegids($view)
468 set revs [parseviewrevs $view $vrevs($view)]
469 if {$revs eq {}} {
470 return
472 # note: getting the delta when negative refs change is hard,
473 # and could require multiple git log invocations, so in that
474 # case we ask git log for all the commits (not just the delta)
475 if {$oldneg eq $vnegids($view)} {
476 set newrevs {}
477 set npos 0
478 # take out positive refs that we asked for before or
479 # that we have already seen
480 foreach rev $revs {
481 if {[string length $rev] == 40} {
482 if {[lsearch -exact $oldpos $rev] < 0
483 && ![info exists varcid($view,$rev)]} {
484 lappend newrevs $rev
485 incr npos
487 } else {
488 lappend $newrevs $rev
491 if {$npos == 0} return
492 set revs $newrevs
493 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
495 set args [concat $vflags($view) $revs --not $oldpos]
496 } else {
497 set args $vorigargs($view)
499 if {[catch {
500 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
501 --boundary $args "--" $vfilelimit($view)] r]
502 } err]} {
503 error_popup "[mc "Error executing git log:"] $err"
504 return
506 if {$viewactive($view) == 0} {
507 set startmsecs [clock clicks -milliseconds]
509 set i [reg_instance $fd]
510 lappend viewinstances($view) $i
511 fconfigure $fd -blocking 0 -translation lf -eofchar {}
512 if {$tclencoding != {}} {
513 fconfigure $fd -encoding $tclencoding
515 filerun $fd [list getcommitlines $fd $i $view 1]
516 incr viewactive($view)
517 set viewcomplete($view) 0
518 reset_pending_select {}
519 nowbusy $view "Reading"
520 if {$showneartags} {
521 getallcommits
525 proc reloadcommits {} {
526 global curview viewcomplete selectedline currentid thickerline
527 global showneartags treediffs commitinterest cached_commitrow
528 global targetid
530 set selid {}
531 if {$selectedline ne {}} {
532 set selid $currentid
535 if {!$viewcomplete($curview)} {
536 stop_rev_list $curview
538 resetvarcs $curview
539 set selectedline {}
540 catch {unset currentid}
541 catch {unset thickerline}
542 catch {unset treediffs}
543 readrefs
544 changedrefs
545 if {$showneartags} {
546 getallcommits
548 clear_display
549 catch {unset commitinterest}
550 catch {unset cached_commitrow}
551 catch {unset targetid}
552 setcanvscroll
553 getcommits $selid
554 return 0
557 # This makes a string representation of a positive integer which
558 # sorts as a string in numerical order
559 proc strrep {n} {
560 if {$n < 16} {
561 return [format "%x" $n]
562 } elseif {$n < 256} {
563 return [format "x%.2x" $n]
564 } elseif {$n < 65536} {
565 return [format "y%.4x" $n]
567 return [format "z%.8x" $n]
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
573 proc varcinit {view} {
574 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575 global vtokmod varcmod vrowmod varcix vlastins
577 set varcstart($view) {{}}
578 set vupptr($view) {0}
579 set vdownptr($view) {0}
580 set vleftptr($view) {0}
581 set vbackptr($view) {0}
582 set varctok($view) {{}}
583 set varcrow($view) {{}}
584 set vtokmod($view) {}
585 set varcmod($view) 0
586 set vrowmod($view) 0
587 set varcix($view) {{}}
588 set vlastins($view) {0}
591 proc resetvarcs {view} {
592 global varcid varccommits parents children vseedcount ordertok
594 foreach vid [array names varcid $view,*] {
595 unset varcid($vid)
596 unset children($vid)
597 unset parents($vid)
599 # some commits might have children but haven't been seen yet
600 foreach vid [array names children $view,*] {
601 unset children($vid)
603 foreach va [array names varccommits $view,*] {
604 unset varccommits($va)
606 foreach vd [array names vseedcount $view,*] {
607 unset vseedcount($vd)
609 catch {unset ordertok}
612 # returns a list of the commits with no children
613 proc seeds {v} {
614 global vdownptr vleftptr varcstart
616 set ret {}
617 set a [lindex $vdownptr($v) 0]
618 while {$a != 0} {
619 lappend ret [lindex $varcstart($v) $a]
620 set a [lindex $vleftptr($v) $a]
622 return $ret
625 proc newvarc {view id} {
626 global varcid varctok parents children vdatemode
627 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
628 global commitdata commitinfo vseedcount varccommits vlastins
630 set a [llength $varctok($view)]
631 set vid $view,$id
632 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
633 if {![info exists commitinfo($id)]} {
634 parsecommit $id $commitdata($id) 1
636 set cdate [lindex $commitinfo($id) 4]
637 if {![string is integer -strict $cdate]} {
638 set cdate 0
640 if {![info exists vseedcount($view,$cdate)]} {
641 set vseedcount($view,$cdate) -1
643 set c [incr vseedcount($view,$cdate)]
644 set cdate [expr {$cdate ^ 0xffffffff}]
645 set tok "s[strrep $cdate][strrep $c]"
646 } else {
647 set tok {}
649 set ka 0
650 if {[llength $children($vid)] > 0} {
651 set kid [lindex $children($vid) end]
652 set k $varcid($view,$kid)
653 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
654 set ki $kid
655 set ka $k
656 set tok [lindex $varctok($view) $k]
659 if {$ka != 0} {
660 set i [lsearch -exact $parents($view,$ki) $id]
661 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
662 append tok [strrep $j]
664 set c [lindex $vlastins($view) $ka]
665 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
666 set c $ka
667 set b [lindex $vdownptr($view) $ka]
668 } else {
669 set b [lindex $vleftptr($view) $c]
671 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
672 set c $b
673 set b [lindex $vleftptr($view) $c]
675 if {$c == $ka} {
676 lset vdownptr($view) $ka $a
677 lappend vbackptr($view) 0
678 } else {
679 lset vleftptr($view) $c $a
680 lappend vbackptr($view) $c
682 lset vlastins($view) $ka $a
683 lappend vupptr($view) $ka
684 lappend vleftptr($view) $b
685 if {$b != 0} {
686 lset vbackptr($view) $b $a
688 lappend varctok($view) $tok
689 lappend varcstart($view) $id
690 lappend vdownptr($view) 0
691 lappend varcrow($view) {}
692 lappend varcix($view) {}
693 set varccommits($view,$a) {}
694 lappend vlastins($view) 0
695 return $a
698 proc splitvarc {p v} {
699 global varcid varcstart varccommits varctok
700 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
702 set oa $varcid($v,$p)
703 set ac $varccommits($v,$oa)
704 set i [lsearch -exact $varccommits($v,$oa) $p]
705 if {$i <= 0} return
706 set na [llength $varctok($v)]
707 # "%" sorts before "0"...
708 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
709 lappend varctok($v) $tok
710 lappend varcrow($v) {}
711 lappend varcix($v) {}
712 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
713 set varccommits($v,$na) [lrange $ac $i end]
714 lappend varcstart($v) $p
715 foreach id $varccommits($v,$na) {
716 set varcid($v,$id) $na
718 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
719 lappend vlastins($v) [lindex $vlastins($v) $oa]
720 lset vdownptr($v) $oa $na
721 lset vlastins($v) $oa 0
722 lappend vupptr($v) $oa
723 lappend vleftptr($v) 0
724 lappend vbackptr($v) 0
725 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
726 lset vupptr($v) $b $na
730 proc renumbervarc {a v} {
731 global parents children varctok varcstart varccommits
732 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
734 set t1 [clock clicks -milliseconds]
735 set todo {}
736 set isrelated($a) 1
737 set kidchanged($a) 1
738 set ntot 0
739 while {$a != 0} {
740 if {[info exists isrelated($a)]} {
741 lappend todo $a
742 set id [lindex $varccommits($v,$a) end]
743 foreach p $parents($v,$id) {
744 if {[info exists varcid($v,$p)]} {
745 set isrelated($varcid($v,$p)) 1
749 incr ntot
750 set b [lindex $vdownptr($v) $a]
751 if {$b == 0} {
752 while {$a != 0} {
753 set b [lindex $vleftptr($v) $a]
754 if {$b != 0} break
755 set a [lindex $vupptr($v) $a]
758 set a $b
760 foreach a $todo {
761 if {![info exists kidchanged($a)]} continue
762 set id [lindex $varcstart($v) $a]
763 if {[llength $children($v,$id)] > 1} {
764 set children($v,$id) [lsort -command [list vtokcmp $v] \
765 $children($v,$id)]
767 set oldtok [lindex $varctok($v) $a]
768 if {!$vdatemode($v)} {
769 set tok {}
770 } else {
771 set tok $oldtok
773 set ka 0
774 set kid [last_real_child $v,$id]
775 if {$kid ne {}} {
776 set k $varcid($v,$kid)
777 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
778 set ki $kid
779 set ka $k
780 set tok [lindex $varctok($v) $k]
783 if {$ka != 0} {
784 set i [lsearch -exact $parents($v,$ki) $id]
785 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
786 append tok [strrep $j]
788 if {$tok eq $oldtok} {
789 continue
791 set id [lindex $varccommits($v,$a) end]
792 foreach p $parents($v,$id) {
793 if {[info exists varcid($v,$p)]} {
794 set kidchanged($varcid($v,$p)) 1
795 } else {
796 set sortkids($p) 1
799 lset varctok($v) $a $tok
800 set b [lindex $vupptr($v) $a]
801 if {$b != $ka} {
802 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
803 modify_arc $v $ka
805 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
806 modify_arc $v $b
808 set c [lindex $vbackptr($v) $a]
809 set d [lindex $vleftptr($v) $a]
810 if {$c == 0} {
811 lset vdownptr($v) $b $d
812 } else {
813 lset vleftptr($v) $c $d
815 if {$d != 0} {
816 lset vbackptr($v) $d $c
818 if {[lindex $vlastins($v) $b] == $a} {
819 lset vlastins($v) $b $c
821 lset vupptr($v) $a $ka
822 set c [lindex $vlastins($v) $ka]
823 if {$c == 0 || \
824 [string compare $tok [lindex $varctok($v) $c]] < 0} {
825 set c $ka
826 set b [lindex $vdownptr($v) $ka]
827 } else {
828 set b [lindex $vleftptr($v) $c]
830 while {$b != 0 && \
831 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
832 set c $b
833 set b [lindex $vleftptr($v) $c]
835 if {$c == $ka} {
836 lset vdownptr($v) $ka $a
837 lset vbackptr($v) $a 0
838 } else {
839 lset vleftptr($v) $c $a
840 lset vbackptr($v) $a $c
842 lset vleftptr($v) $a $b
843 if {$b != 0} {
844 lset vbackptr($v) $b $a
846 lset vlastins($v) $ka $a
849 foreach id [array names sortkids] {
850 if {[llength $children($v,$id)] > 1} {
851 set children($v,$id) [lsort -command [list vtokcmp $v] \
852 $children($v,$id)]
855 set t2 [clock clicks -milliseconds]
856 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
859 # Fix up the graph after we have found out that in view $v,
860 # $p (a commit that we have already seen) is actually the parent
861 # of the last commit in arc $a.
862 proc fix_reversal {p a v} {
863 global varcid varcstart varctok vupptr
865 set pa $varcid($v,$p)
866 if {$p ne [lindex $varcstart($v) $pa]} {
867 splitvarc $p $v
868 set pa $varcid($v,$p)
870 # seeds always need to be renumbered
871 if {[lindex $vupptr($v) $pa] == 0 ||
872 [string compare [lindex $varctok($v) $a] \
873 [lindex $varctok($v) $pa]] > 0} {
874 renumbervarc $pa $v
878 proc insertrow {id p v} {
879 global cmitlisted children parents varcid varctok vtokmod
880 global varccommits ordertok commitidx numcommits curview
881 global targetid targetrow
883 readcommit $id
884 set vid $v,$id
885 set cmitlisted($vid) 1
886 set children($vid) {}
887 set parents($vid) [list $p]
888 set a [newvarc $v $id]
889 set varcid($vid) $a
890 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
891 modify_arc $v $a
893 lappend varccommits($v,$a) $id
894 set vp $v,$p
895 if {[llength [lappend children($vp) $id]] > 1} {
896 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
897 catch {unset ordertok}
899 fix_reversal $p $a $v
900 incr commitidx($v)
901 if {$v == $curview} {
902 set numcommits $commitidx($v)
903 setcanvscroll
904 if {[info exists targetid]} {
905 if {![comes_before $targetid $p]} {
906 incr targetrow
912 proc insertfakerow {id p} {
913 global varcid varccommits parents children cmitlisted
914 global commitidx varctok vtokmod targetid targetrow curview numcommits
916 set v $curview
917 set a $varcid($v,$p)
918 set i [lsearch -exact $varccommits($v,$a) $p]
919 if {$i < 0} {
920 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
921 return
923 set children($v,$id) {}
924 set parents($v,$id) [list $p]
925 set varcid($v,$id) $a
926 lappend children($v,$p) $id
927 set cmitlisted($v,$id) 1
928 set numcommits [incr commitidx($v)]
929 # note we deliberately don't update varcstart($v) even if $i == 0
930 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
931 modify_arc $v $a $i
932 if {[info exists targetid]} {
933 if {![comes_before $targetid $p]} {
934 incr targetrow
937 setcanvscroll
938 drawvisible
941 proc removefakerow {id} {
942 global varcid varccommits parents children commitidx
943 global varctok vtokmod cmitlisted currentid selectedline
944 global targetid curview numcommits
946 set v $curview
947 if {[llength $parents($v,$id)] != 1} {
948 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
949 return
951 set p [lindex $parents($v,$id) 0]
952 set a $varcid($v,$id)
953 set i [lsearch -exact $varccommits($v,$a) $id]
954 if {$i < 0} {
955 puts "oops: removefakerow can't find [shortids $id] on arc $a"
956 return
958 unset varcid($v,$id)
959 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
960 unset parents($v,$id)
961 unset children($v,$id)
962 unset cmitlisted($v,$id)
963 set numcommits [incr commitidx($v) -1]
964 set j [lsearch -exact $children($v,$p) $id]
965 if {$j >= 0} {
966 set children($v,$p) [lreplace $children($v,$p) $j $j]
968 modify_arc $v $a $i
969 if {[info exist currentid] && $id eq $currentid} {
970 unset currentid
971 set selectedline {}
973 if {[info exists targetid] && $targetid eq $id} {
974 set targetid $p
976 setcanvscroll
977 drawvisible
980 proc first_real_child {vp} {
981 global children nullid nullid2
983 foreach id $children($vp) {
984 if {$id ne $nullid && $id ne $nullid2} {
985 return $id
988 return {}
991 proc last_real_child {vp} {
992 global children nullid nullid2
994 set kids $children($vp)
995 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
996 set id [lindex $kids $i]
997 if {$id ne $nullid && $id ne $nullid2} {
998 return $id
1001 return {}
1004 proc vtokcmp {v a b} {
1005 global varctok varcid
1007 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008 [lindex $varctok($v) $varcid($v,$b)]]
1011 # This assumes that if lim is not given, the caller has checked that
1012 # arc a's token is less than $vtokmod($v)
1013 proc modify_arc {v a {lim {}}} {
1014 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1016 if {$lim ne {}} {
1017 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1018 if {$c > 0} return
1019 if {$c == 0} {
1020 set r [lindex $varcrow($v) $a]
1021 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1024 set vtokmod($v) [lindex $varctok($v) $a]
1025 set varcmod($v) $a
1026 if {$v == $curview} {
1027 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1028 set a [lindex $vupptr($v) $a]
1029 set lim {}
1031 set r 0
1032 if {$a != 0} {
1033 if {$lim eq {}} {
1034 set lim [llength $varccommits($v,$a)]
1036 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1038 set vrowmod($v) $r
1039 undolayout $r
1043 proc update_arcrows {v} {
1044 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1045 global varcid vrownum varcorder varcix varccommits
1046 global vupptr vdownptr vleftptr varctok
1047 global displayorder parentlist curview cached_commitrow
1049 if {$vrowmod($v) == $commitidx($v)} return
1050 if {$v == $curview} {
1051 if {[llength $displayorder] > $vrowmod($v)} {
1052 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1053 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1055 catch {unset cached_commitrow}
1057 set narctot [expr {[llength $varctok($v)] - 1}]
1058 set a $varcmod($v)
1059 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1060 # go up the tree until we find something that has a row number,
1061 # or we get to a seed
1062 set a [lindex $vupptr($v) $a]
1064 if {$a == 0} {
1065 set a [lindex $vdownptr($v) 0]
1066 if {$a == 0} return
1067 set vrownum($v) {0}
1068 set varcorder($v) [list $a]
1069 lset varcix($v) $a 0
1070 lset varcrow($v) $a 0
1071 set arcn 0
1072 set row 0
1073 } else {
1074 set arcn [lindex $varcix($v) $a]
1075 if {[llength $vrownum($v)] > $arcn + 1} {
1076 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1077 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1079 set row [lindex $varcrow($v) $a]
1081 while {1} {
1082 set p $a
1083 incr row [llength $varccommits($v,$a)]
1084 # go down if possible
1085 set b [lindex $vdownptr($v) $a]
1086 if {$b == 0} {
1087 # if not, go left, or go up until we can go left
1088 while {$a != 0} {
1089 set b [lindex $vleftptr($v) $a]
1090 if {$b != 0} break
1091 set a [lindex $vupptr($v) $a]
1093 if {$a == 0} break
1095 set a $b
1096 incr arcn
1097 lappend vrownum($v) $row
1098 lappend varcorder($v) $a
1099 lset varcix($v) $a $arcn
1100 lset varcrow($v) $a $row
1102 set vtokmod($v) [lindex $varctok($v) $p]
1103 set varcmod($v) $p
1104 set vrowmod($v) $row
1105 if {[info exists currentid]} {
1106 set selectedline [rowofcommit $currentid]
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1112 global varcid
1114 return [info exists varcid($v,$id)]
1117 # Return the row number for commit $id in the current view
1118 proc rowofcommit {id} {
1119 global varcid varccommits varcrow curview cached_commitrow
1120 global varctok vtokmod
1122 set v $curview
1123 if {![info exists varcid($v,$id)]} {
1124 puts "oops rowofcommit no arc for [shortids $id]"
1125 return {}
1127 set a $varcid($v,$id)
1128 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1129 update_arcrows $v
1131 if {[info exists cached_commitrow($id)]} {
1132 return $cached_commitrow($id)
1134 set i [lsearch -exact $varccommits($v,$a) $id]
1135 if {$i < 0} {
1136 puts "oops didn't find commit [shortids $id] in arc $a"
1137 return {}
1139 incr i [lindex $varcrow($v) $a]
1140 set cached_commitrow($id) $i
1141 return $i
1144 # Returns 1 if a is on an earlier row than b, otherwise 0
1145 proc comes_before {a b} {
1146 global varcid varctok curview
1148 set v $curview
1149 if {$a eq $b || ![info exists varcid($v,$a)] || \
1150 ![info exists varcid($v,$b)]} {
1151 return 0
1153 if {$varcid($v,$a) != $varcid($v,$b)} {
1154 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1155 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1157 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1160 proc bsearch {l elt} {
1161 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1162 return 0
1164 set lo 0
1165 set hi [llength $l]
1166 while {$hi - $lo > 1} {
1167 set mid [expr {int(($lo + $hi) / 2)}]
1168 set t [lindex $l $mid]
1169 if {$elt < $t} {
1170 set hi $mid
1171 } elseif {$elt > $t} {
1172 set lo $mid
1173 } else {
1174 return $mid
1177 return $lo
1180 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1181 proc make_disporder {start end} {
1182 global vrownum curview commitidx displayorder parentlist
1183 global varccommits varcorder parents vrowmod varcrow
1184 global d_valid_start d_valid_end
1186 if {$end > $vrowmod($curview)} {
1187 update_arcrows $curview
1189 set ai [bsearch $vrownum($curview) $start]
1190 set start [lindex $vrownum($curview) $ai]
1191 set narc [llength $vrownum($curview)]
1192 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1193 set a [lindex $varcorder($curview) $ai]
1194 set l [llength $displayorder]
1195 set al [llength $varccommits($curview,$a)]
1196 if {$l < $r + $al} {
1197 if {$l < $r} {
1198 set pad [ntimes [expr {$r - $l}] {}]
1199 set displayorder [concat $displayorder $pad]
1200 set parentlist [concat $parentlist $pad]
1201 } elseif {$l > $r} {
1202 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1203 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1205 foreach id $varccommits($curview,$a) {
1206 lappend displayorder $id
1207 lappend parentlist $parents($curview,$id)
1209 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1210 set i $r
1211 foreach id $varccommits($curview,$a) {
1212 lset displayorder $i $id
1213 lset parentlist $i $parents($curview,$id)
1214 incr i
1217 incr r $al
1221 proc commitonrow {row} {
1222 global displayorder
1224 set id [lindex $displayorder $row]
1225 if {$id eq {}} {
1226 make_disporder $row [expr {$row + 1}]
1227 set id [lindex $displayorder $row]
1229 return $id
1232 proc closevarcs {v} {
1233 global varctok varccommits varcid parents children
1234 global cmitlisted commitidx vtokmod
1236 set missing_parents 0
1237 set scripts {}
1238 set narcs [llength $varctok($v)]
1239 for {set a 1} {$a < $narcs} {incr a} {
1240 set id [lindex $varccommits($v,$a) end]
1241 foreach p $parents($v,$id) {
1242 if {[info exists varcid($v,$p)]} continue
1243 # add p as a new commit
1244 incr missing_parents
1245 set cmitlisted($v,$p) 0
1246 set parents($v,$p) {}
1247 if {[llength $children($v,$p)] == 1 &&
1248 [llength $parents($v,$id)] == 1} {
1249 set b $a
1250 } else {
1251 set b [newvarc $v $p]
1253 set varcid($v,$p) $b
1254 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1255 modify_arc $v $b
1257 lappend varccommits($v,$b) $p
1258 incr commitidx($v)
1259 set scripts [check_interest $p $scripts]
1262 if {$missing_parents > 0} {
1263 foreach s $scripts {
1264 eval $s
1269 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1270 # Assumes we already have an arc for $rwid.
1271 proc rewrite_commit {v id rwid} {
1272 global children parents varcid varctok vtokmod varccommits
1274 foreach ch $children($v,$id) {
1275 # make $rwid be $ch's parent in place of $id
1276 set i [lsearch -exact $parents($v,$ch) $id]
1277 if {$i < 0} {
1278 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1280 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1281 # add $ch to $rwid's children and sort the list if necessary
1282 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1283 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1284 $children($v,$rwid)]
1286 # fix the graph after joining $id to $rwid
1287 set a $varcid($v,$ch)
1288 fix_reversal $rwid $a $v
1289 # parentlist is wrong for the last element of arc $a
1290 # even if displayorder is right, hence the 3rd arg here
1291 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1295 # Mechanism for registering a command to be executed when we come
1296 # across a particular commit. To handle the case when only the
1297 # prefix of the commit is known, the commitinterest array is now
1298 # indexed by the first 4 characters of the ID. Each element is a
1299 # list of id, cmd pairs.
1300 proc interestedin {id cmd} {
1301 global commitinterest
1303 lappend commitinterest([string range $id 0 3]) $id $cmd
1306 proc check_interest {id scripts} {
1307 global commitinterest
1309 set prefix [string range $id 0 3]
1310 if {[info exists commitinterest($prefix)]} {
1311 set newlist {}
1312 foreach {i script} $commitinterest($prefix) {
1313 if {[string match "$i*" $id]} {
1314 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1315 } else {
1316 lappend newlist $i $script
1319 if {$newlist ne {}} {
1320 set commitinterest($prefix) $newlist
1321 } else {
1322 unset commitinterest($prefix)
1325 return $scripts
1328 proc getcommitlines {fd inst view updating} {
1329 global cmitlisted leftover
1330 global commitidx commitdata vdatemode
1331 global parents children curview hlview
1332 global idpending ordertok
1333 global varccommits varcid varctok vtokmod vfilelimit
1335 set stuff [read $fd 500000]
1336 # git log doesn't terminate the last commit with a null...
1337 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1338 set stuff "\0"
1340 if {$stuff == {}} {
1341 if {![eof $fd]} {
1342 return 1
1344 global commfd viewcomplete viewactive viewname
1345 global viewinstances
1346 unset commfd($inst)
1347 set i [lsearch -exact $viewinstances($view) $inst]
1348 if {$i >= 0} {
1349 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1351 # set it blocking so we wait for the process to terminate
1352 fconfigure $fd -blocking 1
1353 if {[catch {close $fd} err]} {
1354 set fv {}
1355 if {$view != $curview} {
1356 set fv " for the \"$viewname($view)\" view"
1358 if {[string range $err 0 4] == "usage"} {
1359 set err "Gitk: error reading commits$fv:\
1360 bad arguments to git log."
1361 if {$viewname($view) eq "Command line"} {
1362 append err \
1363 " (Note: arguments to gitk are passed to git log\
1364 to allow selection of commits to be displayed.)"
1366 } else {
1367 set err "Error reading commits$fv: $err"
1369 error_popup $err
1371 if {[incr viewactive($view) -1] <= 0} {
1372 set viewcomplete($view) 1
1373 # Check if we have seen any ids listed as parents that haven't
1374 # appeared in the list
1375 closevarcs $view
1376 notbusy $view
1378 if {$view == $curview} {
1379 run chewcommits
1381 return 0
1383 set start 0
1384 set gotsome 0
1385 set scripts {}
1386 while 1 {
1387 set i [string first "\0" $stuff $start]
1388 if {$i < 0} {
1389 append leftover($inst) [string range $stuff $start end]
1390 break
1392 if {$start == 0} {
1393 set cmit $leftover($inst)
1394 append cmit [string range $stuff 0 [expr {$i - 1}]]
1395 set leftover($inst) {}
1396 } else {
1397 set cmit [string range $stuff $start [expr {$i - 1}]]
1399 set start [expr {$i + 1}]
1400 set j [string first "\n" $cmit]
1401 set ok 0
1402 set listed 1
1403 if {$j >= 0 && [string match "commit *" $cmit]} {
1404 set ids [string range $cmit 7 [expr {$j - 1}]]
1405 if {[string match {[-^<>]*} $ids]} {
1406 switch -- [string index $ids 0] {
1407 "-" {set listed 0}
1408 "^" {set listed 2}
1409 "<" {set listed 3}
1410 ">" {set listed 4}
1412 set ids [string range $ids 1 end]
1414 set ok 1
1415 foreach id $ids {
1416 if {[string length $id] != 40} {
1417 set ok 0
1418 break
1422 if {!$ok} {
1423 set shortcmit $cmit
1424 if {[string length $shortcmit] > 80} {
1425 set shortcmit "[string range $shortcmit 0 80]..."
1427 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1428 exit 1
1430 set id [lindex $ids 0]
1431 set vid $view,$id
1433 if {!$listed && $updating && ![info exists varcid($vid)] &&
1434 $vfilelimit($view) ne {}} {
1435 # git log doesn't rewrite parents for unlisted commits
1436 # when doing path limiting, so work around that here
1437 # by working out the rewritten parent with git rev-list
1438 # and if we already know about it, using the rewritten
1439 # parent as a substitute parent for $id's children.
1440 if {![catch {
1441 set rwid [exec git rev-list --first-parent --max-count=1 \
1442 $id -- $vfilelimit($view)]
1443 }]} {
1444 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1445 # use $rwid in place of $id
1446 rewrite_commit $view $id $rwid
1447 continue
1452 set a 0
1453 if {[info exists varcid($vid)]} {
1454 if {$cmitlisted($vid) || !$listed} continue
1455 set a $varcid($vid)
1457 if {$listed} {
1458 set olds [lrange $ids 1 end]
1459 } else {
1460 set olds {}
1462 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1463 set cmitlisted($vid) $listed
1464 set parents($vid) $olds
1465 if {![info exists children($vid)]} {
1466 set children($vid) {}
1467 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1468 set k [lindex $children($vid) 0]
1469 if {[llength $parents($view,$k)] == 1 &&
1470 (!$vdatemode($view) ||
1471 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1472 set a $varcid($view,$k)
1475 if {$a == 0} {
1476 # new arc
1477 set a [newvarc $view $id]
1479 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1480 modify_arc $view $a
1482 if {![info exists varcid($vid)]} {
1483 set varcid($vid) $a
1484 lappend varccommits($view,$a) $id
1485 incr commitidx($view)
1488 set i 0
1489 foreach p $olds {
1490 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1491 set vp $view,$p
1492 if {[llength [lappend children($vp) $id]] > 1 &&
1493 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1494 set children($vp) [lsort -command [list vtokcmp $view] \
1495 $children($vp)]
1496 catch {unset ordertok}
1498 if {[info exists varcid($view,$p)]} {
1499 fix_reversal $p $a $view
1502 incr i
1505 set scripts [check_interest $id $scripts]
1506 set gotsome 1
1508 if {$gotsome} {
1509 global numcommits hlview
1511 if {$view == $curview} {
1512 set numcommits $commitidx($view)
1513 run chewcommits
1515 if {[info exists hlview] && $view == $hlview} {
1516 # we never actually get here...
1517 run vhighlightmore
1519 foreach s $scripts {
1520 eval $s
1523 return 2
1526 proc chewcommits {} {
1527 global curview hlview viewcomplete
1528 global pending_select
1530 layoutmore
1531 if {$viewcomplete($curview)} {
1532 global commitidx varctok
1533 global numcommits startmsecs
1535 if {[info exists pending_select]} {
1536 update
1537 reset_pending_select {}
1539 if {[commitinview $pending_select $curview]} {
1540 selectline [rowofcommit $pending_select] 1
1541 } else {
1542 set row [first_real_row]
1543 selectline $row 1
1546 if {$commitidx($curview) > 0} {
1547 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1548 #puts "overall $ms ms for $numcommits commits"
1549 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1550 } else {
1551 show_status [mc "No commits selected"]
1553 notbusy layout
1555 return 0
1558 proc readcommit {id} {
1559 if {[catch {set contents [exec git cat-file commit $id]}]} return
1560 parsecommit $id $contents 0
1563 proc parsecommit {id contents listed} {
1564 global commitinfo cdate
1566 set inhdr 1
1567 set comment {}
1568 set headline {}
1569 set auname {}
1570 set audate {}
1571 set comname {}
1572 set comdate {}
1573 set hdrend [string first "\n\n" $contents]
1574 if {$hdrend < 0} {
1575 # should never happen...
1576 set hdrend [string length $contents]
1578 set header [string range $contents 0 [expr {$hdrend - 1}]]
1579 set comment [string range $contents [expr {$hdrend + 2}] end]
1580 foreach line [split $header "\n"] {
1581 set tag [lindex $line 0]
1582 if {$tag == "author"} {
1583 set audate [lindex $line end-1]
1584 set auname [lrange $line 1 end-2]
1585 } elseif {$tag == "committer"} {
1586 set comdate [lindex $line end-1]
1587 set comname [lrange $line 1 end-2]
1590 set headline {}
1591 # take the first non-blank line of the comment as the headline
1592 set headline [string trimleft $comment]
1593 set i [string first "\n" $headline]
1594 if {$i >= 0} {
1595 set headline [string range $headline 0 $i]
1597 set headline [string trimright $headline]
1598 set i [string first "\r" $headline]
1599 if {$i >= 0} {
1600 set headline [string trimright [string range $headline 0 $i]]
1602 if {!$listed} {
1603 # git log indents the comment by 4 spaces;
1604 # if we got this via git cat-file, add the indentation
1605 set newcomment {}
1606 foreach line [split $comment "\n"] {
1607 append newcomment " "
1608 append newcomment $line
1609 append newcomment "\n"
1611 set comment $newcomment
1613 if {$comdate != {}} {
1614 set cdate($id) $comdate
1616 set commitinfo($id) [list $headline $auname $audate \
1617 $comname $comdate $comment]
1620 proc getcommit {id} {
1621 global commitdata commitinfo
1623 if {[info exists commitdata($id)]} {
1624 parsecommit $id $commitdata($id) 1
1625 } else {
1626 readcommit $id
1627 if {![info exists commitinfo($id)]} {
1628 set commitinfo($id) [list [mc "No commit information available"]]
1631 return 1
1634 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1635 # and are present in the current view.
1636 # This is fairly slow...
1637 proc longid {prefix} {
1638 global varcid curview
1640 set ids {}
1641 foreach match [array names varcid "$curview,$prefix*"] {
1642 lappend ids [lindex [split $match ","] 1]
1644 return $ids
1647 proc readrefs {} {
1648 global tagids idtags headids idheads tagobjid
1649 global otherrefids idotherrefs mainhead mainheadid
1650 global selecthead selectheadid
1652 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1653 catch {unset $v}
1655 set refd [open [list | git show-ref -d] r]
1656 while {[gets $refd line] >= 0} {
1657 if {[string index $line 40] ne " "} continue
1658 set id [string range $line 0 39]
1659 set ref [string range $line 41 end]
1660 if {![string match "refs/*" $ref]} continue
1661 set name [string range $ref 5 end]
1662 if {[string match "remotes/*" $name]} {
1663 if {![string match "*/HEAD" $name]} {
1664 set headids($name) $id
1665 lappend idheads($id) $name
1667 } elseif {[string match "heads/*" $name]} {
1668 set name [string range $name 6 end]
1669 set headids($name) $id
1670 lappend idheads($id) $name
1671 } elseif {[string match "tags/*" $name]} {
1672 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1673 # which is what we want since the former is the commit ID
1674 set name [string range $name 5 end]
1675 if {[string match "*^{}" $name]} {
1676 set name [string range $name 0 end-3]
1677 } else {
1678 set tagobjid($name) $id
1680 set tagids($name) $id
1681 lappend idtags($id) $name
1682 } else {
1683 set otherrefids($name) $id
1684 lappend idotherrefs($id) $name
1687 catch {close $refd}
1688 set mainhead {}
1689 set mainheadid {}
1690 catch {
1691 set mainheadid [exec git rev-parse HEAD]
1692 set thehead [exec git symbolic-ref HEAD]
1693 if {[string match "refs/heads/*" $thehead]} {
1694 set mainhead [string range $thehead 11 end]
1697 set selectheadid {}
1698 if {$selecthead ne {}} {
1699 catch {
1700 set selectheadid [exec git rev-parse --verify $selecthead]
1705 # skip over fake commits
1706 proc first_real_row {} {
1707 global nullid nullid2 numcommits
1709 for {set row 0} {$row < $numcommits} {incr row} {
1710 set id [commitonrow $row]
1711 if {$id ne $nullid && $id ne $nullid2} {
1712 break
1715 return $row
1718 # update things for a head moved to a child of its previous location
1719 proc movehead {id name} {
1720 global headids idheads
1722 removehead $headids($name) $name
1723 set headids($name) $id
1724 lappend idheads($id) $name
1727 # update things when a head has been removed
1728 proc removehead {id name} {
1729 global headids idheads
1731 if {$idheads($id) eq $name} {
1732 unset idheads($id)
1733 } else {
1734 set i [lsearch -exact $idheads($id) $name]
1735 if {$i >= 0} {
1736 set idheads($id) [lreplace $idheads($id) $i $i]
1739 unset headids($name)
1742 proc show_error {w top msg} {
1743 message $w.m -text $msg -justify center -aspect 400
1744 pack $w.m -side top -fill x -padx 20 -pady 20
1745 button $w.ok -text [mc OK] -command "destroy $top"
1746 pack $w.ok -side bottom -fill x
1747 bind $top <Visibility> "grab $top; focus $top"
1748 bind $top <Key-Return> "destroy $top"
1749 tkwait window $top
1752 proc error_popup msg {
1753 set w .error
1754 toplevel $w
1755 wm transient $w .
1756 show_error $w $w $msg
1759 proc confirm_popup msg {
1760 global confirm_ok
1761 set confirm_ok 0
1762 set w .confirm
1763 toplevel $w
1764 wm transient $w .
1765 message $w.m -text $msg -justify center -aspect 400
1766 pack $w.m -side top -fill x -padx 20 -pady 20
1767 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1768 pack $w.ok -side left -fill x
1769 button $w.cancel -text [mc Cancel] -command "destroy $w"
1770 pack $w.cancel -side right -fill x
1771 bind $w <Visibility> "grab $w; focus $w"
1772 tkwait window $w
1773 return $confirm_ok
1776 proc setoptions {} {
1777 option add *Panedwindow.showHandle 1 startupFile
1778 option add *Panedwindow.sashRelief raised startupFile
1779 option add *Button.font uifont startupFile
1780 option add *Checkbutton.font uifont startupFile
1781 option add *Radiobutton.font uifont startupFile
1782 option add *Menu.font uifont startupFile
1783 option add *Menubutton.font uifont startupFile
1784 option add *Label.font uifont startupFile
1785 option add *Message.font uifont startupFile
1786 option add *Entry.font uifont startupFile
1789 # Make a menu and submenus.
1790 # m is the window name for the menu, items is the list of menu items to add.
1791 # Each item is a list {mc label type description options...}
1792 # mc is ignored; it's so we can put mc there to alert xgettext
1793 # label is the string that appears in the menu
1794 # type is cascade, command or radiobutton (should add checkbutton)
1795 # description depends on type; it's the sublist for cascade, the
1796 # command to invoke for command, or {variable value} for radiobutton
1797 proc makemenu {m items} {
1798 menu $m
1799 foreach i $items {
1800 set name [mc [lindex $i 1]]
1801 set type [lindex $i 2]
1802 set thing [lindex $i 3]
1803 set params [list $type]
1804 if {$name ne {}} {
1805 set u [string first "&" [string map {&& x} $name]]
1806 lappend params -label [string map {&& & & {}} $name]
1807 if {$u >= 0} {
1808 lappend params -underline $u
1811 switch -- $type {
1812 "cascade" {
1813 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1814 lappend params -menu $m.$submenu
1816 "command" {
1817 lappend params -command $thing
1819 "radiobutton" {
1820 lappend params -variable [lindex $thing 0] \
1821 -value [lindex $thing 1]
1824 eval $m add $params [lrange $i 4 end]
1825 if {$type eq "cascade"} {
1826 makemenu $m.$submenu $thing
1831 # translate string and remove ampersands
1832 proc mca {str} {
1833 return [string map {&& & & {}} [mc $str]]
1836 proc makewindow {} {
1837 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1838 global tabstop
1839 global findtype findtypemenu findloc findstring fstring geometry
1840 global entries sha1entry sha1string sha1but
1841 global diffcontextstring diffcontext
1842 global ignorespace
1843 global maincursor textcursor curtextcursor
1844 global rowctxmenu fakerowmenu mergemax wrapcomment
1845 global highlight_files gdttype
1846 global searchstring sstring
1847 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1848 global headctxmenu progresscanv progressitem progresscoords statusw
1849 global fprogitem fprogcoord lastprogupdate progupdatepending
1850 global rprogitem rprogcoord rownumsel numcommits
1851 global have_tk85
1853 # The "mc" arguments here are purely so that xgettext
1854 # sees the following string as needing to be translated
1855 makemenu .bar {
1856 {mc "File" cascade {
1857 {mc "Update" command updatecommits -accelerator F5}
1858 {mc "Reload" command reloadcommits}
1859 {mc "Reread references" command rereadrefs}
1860 {mc "List references" command showrefs}
1861 {mc "Quit" command doquit}
1863 {mc "Edit" cascade {
1864 {mc "Preferences" command doprefs}
1866 {mc "View" cascade {
1867 {mc "New view..." command {newview 0}}
1868 {mc "Edit view..." command editview -state disabled}
1869 {mc "Delete view" command delview -state disabled}
1870 {xx "" separator}
1871 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1873 {mc "Help" cascade {
1874 {mc "About gitk" command about}
1875 {mc "Key bindings" command keys}
1878 . configure -menu .bar
1880 # the gui has upper and lower half, parts of a paned window.
1881 panedwindow .ctop -orient vertical
1883 # possibly use assumed geometry
1884 if {![info exists geometry(pwsash0)]} {
1885 set geometry(topheight) [expr {15 * $linespc}]
1886 set geometry(topwidth) [expr {80 * $charspc}]
1887 set geometry(botheight) [expr {15 * $linespc}]
1888 set geometry(botwidth) [expr {50 * $charspc}]
1889 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1890 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1893 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1894 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1895 frame .tf.histframe
1896 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1898 # create three canvases
1899 set cscroll .tf.histframe.csb
1900 set canv .tf.histframe.pwclist.canv
1901 canvas $canv \
1902 -selectbackground $selectbgcolor \
1903 -background $bgcolor -bd 0 \
1904 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1905 .tf.histframe.pwclist add $canv
1906 set canv2 .tf.histframe.pwclist.canv2
1907 canvas $canv2 \
1908 -selectbackground $selectbgcolor \
1909 -background $bgcolor -bd 0 -yscrollincr $linespc
1910 .tf.histframe.pwclist add $canv2
1911 set canv3 .tf.histframe.pwclist.canv3
1912 canvas $canv3 \
1913 -selectbackground $selectbgcolor \
1914 -background $bgcolor -bd 0 -yscrollincr $linespc
1915 .tf.histframe.pwclist add $canv3
1916 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1917 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1919 # a scroll bar to rule them
1920 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1921 pack $cscroll -side right -fill y
1922 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1923 lappend bglist $canv $canv2 $canv3
1924 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1926 # we have two button bars at bottom of top frame. Bar 1
1927 frame .tf.bar
1928 frame .tf.lbar -height 15
1930 set sha1entry .tf.bar.sha1
1931 set entries $sha1entry
1932 set sha1but .tf.bar.sha1label
1933 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1934 -command gotocommit -width 8
1935 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1936 pack .tf.bar.sha1label -side left
1937 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1938 trace add variable sha1string write sha1change
1939 pack $sha1entry -side left -pady 2
1941 image create bitmap bm-left -data {
1942 #define left_width 16
1943 #define left_height 16
1944 static unsigned char left_bits[] = {
1945 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1946 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1947 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1949 image create bitmap bm-right -data {
1950 #define right_width 16
1951 #define right_height 16
1952 static unsigned char right_bits[] = {
1953 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1954 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1955 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1957 button .tf.bar.leftbut -image bm-left -command goback \
1958 -state disabled -width 26
1959 pack .tf.bar.leftbut -side left -fill y
1960 button .tf.bar.rightbut -image bm-right -command goforw \
1961 -state disabled -width 26
1962 pack .tf.bar.rightbut -side left -fill y
1964 label .tf.bar.rowlabel -text [mc "Row"]
1965 set rownumsel {}
1966 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1967 -relief sunken -anchor e
1968 label .tf.bar.rowlabel2 -text "/"
1969 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1970 -relief sunken -anchor e
1971 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1972 -side left
1973 global selectedline
1974 trace add variable selectedline write selectedline_change
1976 # Status label and progress bar
1977 set statusw .tf.bar.status
1978 label $statusw -width 15 -relief sunken
1979 pack $statusw -side left -padx 5
1980 set h [expr {[font metrics uifont -linespace] + 2}]
1981 set progresscanv .tf.bar.progress
1982 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1983 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1984 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1985 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1986 pack $progresscanv -side right -expand 1 -fill x
1987 set progresscoords {0 0}
1988 set fprogcoord 0
1989 set rprogcoord 0
1990 bind $progresscanv <Configure> adjustprogress
1991 set lastprogupdate [clock clicks -milliseconds]
1992 set progupdatepending 0
1994 # build up the bottom bar of upper window
1995 label .tf.lbar.flabel -text "[mc "Find"] "
1996 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
1997 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
1998 label .tf.lbar.flab2 -text " [mc "commit"] "
1999 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2000 -side left -fill y
2001 set gdttype [mc "containing:"]
2002 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2003 [mc "containing:"] \
2004 [mc "touching paths:"] \
2005 [mc "adding/removing string:"]]
2006 trace add variable gdttype write gdttype_change
2007 pack .tf.lbar.gdttype -side left -fill y
2009 set findstring {}
2010 set fstring .tf.lbar.findstring
2011 lappend entries $fstring
2012 entry $fstring -width 30 -font textfont -textvariable findstring
2013 trace add variable findstring write find_change
2014 set findtype [mc "Exact"]
2015 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2016 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2017 trace add variable findtype write findcom_change
2018 set findloc [mc "All fields"]
2019 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2020 [mc "Comments"] [mc "Author"] [mc "Committer"]
2021 trace add variable findloc write find_change
2022 pack .tf.lbar.findloc -side right
2023 pack .tf.lbar.findtype -side right
2024 pack $fstring -side left -expand 1 -fill x
2026 # Finish putting the upper half of the viewer together
2027 pack .tf.lbar -in .tf -side bottom -fill x
2028 pack .tf.bar -in .tf -side bottom -fill x
2029 pack .tf.histframe -fill both -side top -expand 1
2030 .ctop add .tf
2031 .ctop paneconfigure .tf -height $geometry(topheight)
2032 .ctop paneconfigure .tf -width $geometry(topwidth)
2034 # now build up the bottom
2035 panedwindow .pwbottom -orient horizontal
2037 # lower left, a text box over search bar, scroll bar to the right
2038 # if we know window height, then that will set the lower text height, otherwise
2039 # we set lower text height which will drive window height
2040 if {[info exists geometry(main)]} {
2041 frame .bleft -width $geometry(botwidth)
2042 } else {
2043 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2045 frame .bleft.top
2046 frame .bleft.mid
2047 frame .bleft.bottom
2049 button .bleft.top.search -text [mc "Search"] -command dosearch
2050 pack .bleft.top.search -side left -padx 5
2051 set sstring .bleft.top.sstring
2052 entry $sstring -width 20 -font textfont -textvariable searchstring
2053 lappend entries $sstring
2054 trace add variable searchstring write incrsearch
2055 pack $sstring -side left -expand 1 -fill x
2056 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2057 -command changediffdisp -variable diffelide -value {0 0}
2058 radiobutton .bleft.mid.old -text [mc "Old version"] \
2059 -command changediffdisp -variable diffelide -value {0 1}
2060 radiobutton .bleft.mid.new -text [mc "New version"] \
2061 -command changediffdisp -variable diffelide -value {1 0}
2062 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2063 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2064 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2065 -from 1 -increment 1 -to 10000000 \
2066 -validate all -validatecommand "diffcontextvalidate %P" \
2067 -textvariable diffcontextstring
2068 .bleft.mid.diffcontext set $diffcontext
2069 trace add variable diffcontextstring write diffcontextchange
2070 lappend entries .bleft.mid.diffcontext
2071 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2072 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2073 -command changeignorespace -variable ignorespace
2074 pack .bleft.mid.ignspace -side left -padx 5
2075 set ctext .bleft.bottom.ctext
2076 text $ctext -background $bgcolor -foreground $fgcolor \
2077 -state disabled -font textfont \
2078 -yscrollcommand scrolltext -wrap none \
2079 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2080 if {$have_tk85} {
2081 $ctext conf -tabstyle wordprocessor
2083 scrollbar .bleft.bottom.sb -command "$ctext yview"
2084 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2085 -width 10
2086 pack .bleft.top -side top -fill x
2087 pack .bleft.mid -side top -fill x
2088 grid $ctext .bleft.bottom.sb -sticky nsew
2089 grid .bleft.bottom.sbhorizontal -sticky ew
2090 grid columnconfigure .bleft.bottom 0 -weight 1
2091 grid rowconfigure .bleft.bottom 0 -weight 1
2092 grid rowconfigure .bleft.bottom 1 -weight 0
2093 pack .bleft.bottom -side top -fill both -expand 1
2094 lappend bglist $ctext
2095 lappend fglist $ctext
2097 $ctext tag conf comment -wrap $wrapcomment
2098 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2099 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2100 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2101 $ctext tag conf d1 -fore [lindex $diffcolors 1]
2102 $ctext tag conf m0 -fore red
2103 $ctext tag conf m1 -fore blue
2104 $ctext tag conf m2 -fore green
2105 $ctext tag conf m3 -fore purple
2106 $ctext tag conf m4 -fore brown
2107 $ctext tag conf m5 -fore "#009090"
2108 $ctext tag conf m6 -fore magenta
2109 $ctext tag conf m7 -fore "#808000"
2110 $ctext tag conf m8 -fore "#009000"
2111 $ctext tag conf m9 -fore "#ff0080"
2112 $ctext tag conf m10 -fore cyan
2113 $ctext tag conf m11 -fore "#b07070"
2114 $ctext tag conf m12 -fore "#70b0f0"
2115 $ctext tag conf m13 -fore "#70f0b0"
2116 $ctext tag conf m14 -fore "#f0b070"
2117 $ctext tag conf m15 -fore "#ff70b0"
2118 $ctext tag conf mmax -fore darkgrey
2119 set mergemax 16
2120 $ctext tag conf mresult -font textfontbold
2121 $ctext tag conf msep -font textfontbold
2122 $ctext tag conf found -back yellow
2124 .pwbottom add .bleft
2125 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2127 # lower right
2128 frame .bright
2129 frame .bright.mode
2130 radiobutton .bright.mode.patch -text [mc "Patch"] \
2131 -command reselectline -variable cmitmode -value "patch"
2132 radiobutton .bright.mode.tree -text [mc "Tree"] \
2133 -command reselectline -variable cmitmode -value "tree"
2134 grid .bright.mode.patch .bright.mode.tree -sticky ew
2135 pack .bright.mode -side top -fill x
2136 set cflist .bright.cfiles
2137 set indent [font measure mainfont "nn"]
2138 text $cflist \
2139 -selectbackground $selectbgcolor \
2140 -background $bgcolor -foreground $fgcolor \
2141 -font mainfont \
2142 -tabs [list $indent [expr {2 * $indent}]] \
2143 -yscrollcommand ".bright.sb set" \
2144 -cursor [. cget -cursor] \
2145 -spacing1 1 -spacing3 1
2146 lappend bglist $cflist
2147 lappend fglist $cflist
2148 scrollbar .bright.sb -command "$cflist yview"
2149 pack .bright.sb -side right -fill y
2150 pack $cflist -side left -fill both -expand 1
2151 $cflist tag configure highlight \
2152 -background [$cflist cget -selectbackground]
2153 $cflist tag configure bold -font mainfontbold
2155 .pwbottom add .bright
2156 .ctop add .pwbottom
2158 # restore window width & height if known
2159 if {[info exists geometry(main)]} {
2160 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2161 if {$w > [winfo screenwidth .]} {
2162 set w [winfo screenwidth .]
2164 if {$h > [winfo screenheight .]} {
2165 set h [winfo screenheight .]
2167 wm geometry . "${w}x$h"
2171 if {[tk windowingsystem] eq {aqua}} {
2172 set M1B M1
2173 } else {
2174 set M1B Control
2177 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2178 pack .ctop -fill both -expand 1
2179 bindall <1> {selcanvline %W %x %y}
2180 #bindall <B1-Motion> {selcanvline %W %x %y}
2181 if {[tk windowingsystem] == "win32"} {
2182 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2183 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2184 } else {
2185 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2186 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2187 if {[tk windowingsystem] eq "aqua"} {
2188 bindall <MouseWheel> {
2189 set delta [expr {- (%D)}]
2190 allcanvs yview scroll $delta units
2194 bindall <2> "canvscan mark %W %x %y"
2195 bindall <B2-Motion> "canvscan dragto %W %x %y"
2196 bindkey <Home> selfirstline
2197 bindkey <End> sellastline
2198 bind . <Key-Up> "selnextline -1"
2199 bind . <Key-Down> "selnextline 1"
2200 bind . <Shift-Key-Up> "dofind -1 0"
2201 bind . <Shift-Key-Down> "dofind 1 0"
2202 bindkey <Key-Right> "goforw"
2203 bindkey <Key-Left> "goback"
2204 bind . <Key-Prior> "selnextpage -1"
2205 bind . <Key-Next> "selnextpage 1"
2206 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2207 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2208 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2209 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2210 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2211 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2212 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2213 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2214 bindkey <Key-space> "$ctext yview scroll 1 pages"
2215 bindkey p "selnextline -1"
2216 bindkey n "selnextline 1"
2217 bindkey z "goback"
2218 bindkey x "goforw"
2219 bindkey i "selnextline -1"
2220 bindkey k "selnextline 1"
2221 bindkey j "goback"
2222 bindkey l "goforw"
2223 bindkey b prevfile
2224 bindkey d "$ctext yview scroll 18 units"
2225 bindkey u "$ctext yview scroll -18 units"
2226 bindkey / {dofind 1 1}
2227 bindkey <Key-Return> {dofind 1 1}
2228 bindkey ? {dofind -1 1}
2229 bindkey f nextfile
2230 bindkey <F5> updatecommits
2231 bind . <$M1B-q> doquit
2232 bind . <$M1B-f> {dofind 1 1}
2233 bind . <$M1B-g> {dofind 1 0}
2234 bind . <$M1B-r> dosearchback
2235 bind . <$M1B-s> dosearch
2236 bind . <$M1B-equal> {incrfont 1}
2237 bind . <$M1B-plus> {incrfont 1}
2238 bind . <$M1B-KP_Add> {incrfont 1}
2239 bind . <$M1B-minus> {incrfont -1}
2240 bind . <$M1B-KP_Subtract> {incrfont -1}
2241 wm protocol . WM_DELETE_WINDOW doquit
2242 bind . <Destroy> {stop_backends}
2243 bind . <Button-1> "click %W"
2244 bind $fstring <Key-Return> {dofind 1 1}
2245 bind $sha1entry <Key-Return> {gotocommit; break}
2246 bind $sha1entry <<PasteSelection>> clearsha1
2247 bind $cflist <1> {sel_flist %W %x %y; break}
2248 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2249 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2250 global ctxbut
2251 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2252 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2254 set maincursor [. cget -cursor]
2255 set textcursor [$ctext cget -cursor]
2256 set curtextcursor $textcursor
2258 set rowctxmenu .rowctxmenu
2259 makemenu $rowctxmenu {
2260 {mc "Diff this -> selected" command {diffvssel 0}}
2261 {mc "Diff selected -> this" command {diffvssel 1}}
2262 {mc "Make patch" command mkpatch}
2263 {mc "Create tag" command mktag}
2264 {mc "Write commit to file" command writecommit}
2265 {mc "Create new branch" command mkbranch}
2266 {mc "Cherry-pick this commit" command cherrypick}
2267 {mc "Reset HEAD branch to here" command resethead}
2269 $rowctxmenu configure -tearoff 0
2271 set fakerowmenu .fakerowmenu
2272 makemenu $fakerowmenu {
2273 {mc "Diff this -> selected" command {diffvssel 0}}
2274 {mc "Diff selected -> this" command {diffvssel 1}}
2275 {mc "Make patch" command mkpatch}
2277 $fakerowmenu configure -tearoff 0
2279 set headctxmenu .headctxmenu
2280 makemenu $headctxmenu {
2281 {mc "Check out this branch" command cobranch}
2282 {mc "Remove this branch" command rmbranch}
2284 $headctxmenu configure -tearoff 0
2286 global flist_menu
2287 set flist_menu .flistctxmenu
2288 makemenu $flist_menu {
2289 {mc "Highlight this too" command {flist_hl 0}}
2290 {mc "Highlight this only" command {flist_hl 1}}
2291 {mc "External diff" command {external_diff}}
2292 {mc "Blame parent commit" command {external_blame 1}}
2294 $flist_menu configure -tearoff 0
2296 global diff_menu
2297 set diff_menu .diffctxmenu
2298 makemenu $diff_menu {
2299 {mc "Show origin of this line" command show_line_source}
2300 {mc "Run git gui blame on this line" command {external_blame_diff}}
2302 $diff_menu configure -tearoff 0
2305 # Windows sends all mouse wheel events to the current focused window, not
2306 # the one where the mouse hovers, so bind those events here and redirect
2307 # to the correct window
2308 proc windows_mousewheel_redirector {W X Y D} {
2309 global canv canv2 canv3
2310 set w [winfo containing -displayof $W $X $Y]
2311 if {$w ne ""} {
2312 set u [expr {$D < 0 ? 5 : -5}]
2313 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2314 allcanvs yview scroll $u units
2315 } else {
2316 catch {
2317 $w yview scroll $u units
2323 # Update row number label when selectedline changes
2324 proc selectedline_change {n1 n2 op} {
2325 global selectedline rownumsel
2327 if {$selectedline eq {}} {
2328 set rownumsel {}
2329 } else {
2330 set rownumsel [expr {$selectedline + 1}]
2334 # mouse-2 makes all windows scan vertically, but only the one
2335 # the cursor is in scans horizontally
2336 proc canvscan {op w x y} {
2337 global canv canv2 canv3
2338 foreach c [list $canv $canv2 $canv3] {
2339 if {$c == $w} {
2340 $c scan $op $x $y
2341 } else {
2342 $c scan $op 0 $y
2347 proc scrollcanv {cscroll f0 f1} {
2348 $cscroll set $f0 $f1
2349 drawvisible
2350 flushhighlights
2353 # when we make a key binding for the toplevel, make sure
2354 # it doesn't get triggered when that key is pressed in the
2355 # find string entry widget.
2356 proc bindkey {ev script} {
2357 global entries
2358 bind . $ev $script
2359 set escript [bind Entry $ev]
2360 if {$escript == {}} {
2361 set escript [bind Entry <Key>]
2363 foreach e $entries {
2364 bind $e $ev "$escript; break"
2368 # set the focus back to the toplevel for any click outside
2369 # the entry widgets
2370 proc click {w} {
2371 global ctext entries
2372 foreach e [concat $entries $ctext] {
2373 if {$w == $e} return
2375 focus .
2378 # Adjust the progress bar for a change in requested extent or canvas size
2379 proc adjustprogress {} {
2380 global progresscanv progressitem progresscoords
2381 global fprogitem fprogcoord lastprogupdate progupdatepending
2382 global rprogitem rprogcoord
2384 set w [expr {[winfo width $progresscanv] - 4}]
2385 set x0 [expr {$w * [lindex $progresscoords 0]}]
2386 set x1 [expr {$w * [lindex $progresscoords 1]}]
2387 set h [winfo height $progresscanv]
2388 $progresscanv coords $progressitem $x0 0 $x1 $h
2389 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2390 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2391 set now [clock clicks -milliseconds]
2392 if {$now >= $lastprogupdate + 100} {
2393 set progupdatepending 0
2394 update
2395 } elseif {!$progupdatepending} {
2396 set progupdatepending 1
2397 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2401 proc doprogupdate {} {
2402 global lastprogupdate progupdatepending
2404 if {$progupdatepending} {
2405 set progupdatepending 0
2406 set lastprogupdate [clock clicks -milliseconds]
2407 update
2411 proc savestuff {w} {
2412 global canv canv2 canv3 mainfont textfont uifont tabstop
2413 global stuffsaved findmergefiles maxgraphpct
2414 global maxwidth showneartags showlocalchanges
2415 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2416 global cmitmode wrapcomment datetimeformat limitdiffs
2417 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2418 global autoselect extdifftool perfile_attrs
2420 if {$stuffsaved} return
2421 if {![winfo viewable .]} return
2422 catch {
2423 set f [open "~/.gitk-new" w]
2424 puts $f [list set mainfont $mainfont]
2425 puts $f [list set textfont $textfont]
2426 puts $f [list set uifont $uifont]
2427 puts $f [list set tabstop $tabstop]
2428 puts $f [list set findmergefiles $findmergefiles]
2429 puts $f [list set maxgraphpct $maxgraphpct]
2430 puts $f [list set maxwidth $maxwidth]
2431 puts $f [list set cmitmode $cmitmode]
2432 puts $f [list set wrapcomment $wrapcomment]
2433 puts $f [list set autoselect $autoselect]
2434 puts $f [list set showneartags $showneartags]
2435 puts $f [list set showlocalchanges $showlocalchanges]
2436 puts $f [list set datetimeformat $datetimeformat]
2437 puts $f [list set limitdiffs $limitdiffs]
2438 puts $f [list set bgcolor $bgcolor]
2439 puts $f [list set fgcolor $fgcolor]
2440 puts $f [list set colors $colors]
2441 puts $f [list set diffcolors $diffcolors]
2442 puts $f [list set diffcontext $diffcontext]
2443 puts $f [list set selectbgcolor $selectbgcolor]
2444 puts $f [list set extdifftool $extdifftool]
2445 puts $f [list set perfile_attrs $perfile_attrs]
2447 puts $f "set geometry(main) [wm geometry .]"
2448 puts $f "set geometry(topwidth) [winfo width .tf]"
2449 puts $f "set geometry(topheight) [winfo height .tf]"
2450 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2451 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2452 puts $f "set geometry(botwidth) [winfo width .bleft]"
2453 puts $f "set geometry(botheight) [winfo height .bleft]"
2455 puts -nonewline $f "set permviews {"
2456 for {set v 0} {$v < $nextviewnum} {incr v} {
2457 if {$viewperm($v)} {
2458 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2461 puts $f "}"
2462 close $f
2463 file rename -force "~/.gitk-new" "~/.gitk"
2465 set stuffsaved 1
2468 proc resizeclistpanes {win w} {
2469 global oldwidth
2470 if {[info exists oldwidth($win)]} {
2471 set s0 [$win sash coord 0]
2472 set s1 [$win sash coord 1]
2473 if {$w < 60} {
2474 set sash0 [expr {int($w/2 - 2)}]
2475 set sash1 [expr {int($w*5/6 - 2)}]
2476 } else {
2477 set factor [expr {1.0 * $w / $oldwidth($win)}]
2478 set sash0 [expr {int($factor * [lindex $s0 0])}]
2479 set sash1 [expr {int($factor * [lindex $s1 0])}]
2480 if {$sash0 < 30} {
2481 set sash0 30
2483 if {$sash1 < $sash0 + 20} {
2484 set sash1 [expr {$sash0 + 20}]
2486 if {$sash1 > $w - 10} {
2487 set sash1 [expr {$w - 10}]
2488 if {$sash0 > $sash1 - 20} {
2489 set sash0 [expr {$sash1 - 20}]
2493 $win sash place 0 $sash0 [lindex $s0 1]
2494 $win sash place 1 $sash1 [lindex $s1 1]
2496 set oldwidth($win) $w
2499 proc resizecdetpanes {win w} {
2500 global oldwidth
2501 if {[info exists oldwidth($win)]} {
2502 set s0 [$win sash coord 0]
2503 if {$w < 60} {
2504 set sash0 [expr {int($w*3/4 - 2)}]
2505 } else {
2506 set factor [expr {1.0 * $w / $oldwidth($win)}]
2507 set sash0 [expr {int($factor * [lindex $s0 0])}]
2508 if {$sash0 < 45} {
2509 set sash0 45
2511 if {$sash0 > $w - 15} {
2512 set sash0 [expr {$w - 15}]
2515 $win sash place 0 $sash0 [lindex $s0 1]
2517 set oldwidth($win) $w
2520 proc allcanvs args {
2521 global canv canv2 canv3
2522 eval $canv $args
2523 eval $canv2 $args
2524 eval $canv3 $args
2527 proc bindall {event action} {
2528 global canv canv2 canv3
2529 bind $canv $event $action
2530 bind $canv2 $event $action
2531 bind $canv3 $event $action
2534 proc about {} {
2535 global uifont
2536 set w .about
2537 if {[winfo exists $w]} {
2538 raise $w
2539 return
2541 toplevel $w
2542 wm title $w [mc "About gitk"]
2543 message $w.m -text [mc "
2544 Gitk - a commit viewer for git
2546 Copyright © 2005-2008 Paul Mackerras
2548 Use and redistribute under the terms of the GNU General Public License"] \
2549 -justify center -aspect 400 -border 2 -bg white -relief groove
2550 pack $w.m -side top -fill x -padx 2 -pady 2
2551 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2552 pack $w.ok -side bottom
2553 bind $w <Visibility> "focus $w.ok"
2554 bind $w <Key-Escape> "destroy $w"
2555 bind $w <Key-Return> "destroy $w"
2558 proc keys {} {
2559 set w .keys
2560 if {[winfo exists $w]} {
2561 raise $w
2562 return
2564 if {[tk windowingsystem] eq {aqua}} {
2565 set M1T Cmd
2566 } else {
2567 set M1T Ctrl
2569 toplevel $w
2570 wm title $w [mc "Gitk key bindings"]
2571 message $w.m -text "
2572 [mc "Gitk key bindings:"]
2574 [mc "<%s-Q> Quit" $M1T]
2575 [mc "<Home> Move to first commit"]
2576 [mc "<End> Move to last commit"]
2577 [mc "<Up>, p, i Move up one commit"]
2578 [mc "<Down>, n, k Move down one commit"]
2579 [mc "<Left>, z, j Go back in history list"]
2580 [mc "<Right>, x, l Go forward in history list"]
2581 [mc "<PageUp> Move up one page in commit list"]
2582 [mc "<PageDown> Move down one page in commit list"]
2583 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2584 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2585 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2586 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2587 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2588 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2589 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2590 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2591 [mc "<Delete>, b Scroll diff view up one page"]
2592 [mc "<Backspace> Scroll diff view up one page"]
2593 [mc "<Space> Scroll diff view down one page"]
2594 [mc "u Scroll diff view up 18 lines"]
2595 [mc "d Scroll diff view down 18 lines"]
2596 [mc "<%s-F> Find" $M1T]
2597 [mc "<%s-G> Move to next find hit" $M1T]
2598 [mc "<Return> Move to next find hit"]
2599 [mc "/ Move to next find hit, or redo find"]
2600 [mc "? Move to previous find hit"]
2601 [mc "f Scroll diff view to next file"]
2602 [mc "<%s-S> Search for next hit in diff view" $M1T]
2603 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2604 [mc "<%s-KP+> Increase font size" $M1T]
2605 [mc "<%s-plus> Increase font size" $M1T]
2606 [mc "<%s-KP-> Decrease font size" $M1T]
2607 [mc "<%s-minus> Decrease font size" $M1T]
2608 [mc "<F5> Update"]
2610 -justify left -bg white -border 2 -relief groove
2611 pack $w.m -side top -fill both -padx 2 -pady 2
2612 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2613 pack $w.ok -side bottom
2614 bind $w <Visibility> "focus $w.ok"
2615 bind $w <Key-Escape> "destroy $w"
2616 bind $w <Key-Return> "destroy $w"
2619 # Procedures for manipulating the file list window at the
2620 # bottom right of the overall window.
2622 proc treeview {w l openlevs} {
2623 global treecontents treediropen treeheight treeparent treeindex
2625 set ix 0
2626 set treeindex() 0
2627 set lev 0
2628 set prefix {}
2629 set prefixend -1
2630 set prefendstack {}
2631 set htstack {}
2632 set ht 0
2633 set treecontents() {}
2634 $w conf -state normal
2635 foreach f $l {
2636 while {[string range $f 0 $prefixend] ne $prefix} {
2637 if {$lev <= $openlevs} {
2638 $w mark set e:$treeindex($prefix) "end -1c"
2639 $w mark gravity e:$treeindex($prefix) left
2641 set treeheight($prefix) $ht
2642 incr ht [lindex $htstack end]
2643 set htstack [lreplace $htstack end end]
2644 set prefixend [lindex $prefendstack end]
2645 set prefendstack [lreplace $prefendstack end end]
2646 set prefix [string range $prefix 0 $prefixend]
2647 incr lev -1
2649 set tail [string range $f [expr {$prefixend+1}] end]
2650 while {[set slash [string first "/" $tail]] >= 0} {
2651 lappend htstack $ht
2652 set ht 0
2653 lappend prefendstack $prefixend
2654 incr prefixend [expr {$slash + 1}]
2655 set d [string range $tail 0 $slash]
2656 lappend treecontents($prefix) $d
2657 set oldprefix $prefix
2658 append prefix $d
2659 set treecontents($prefix) {}
2660 set treeindex($prefix) [incr ix]
2661 set treeparent($prefix) $oldprefix
2662 set tail [string range $tail [expr {$slash+1}] end]
2663 if {$lev <= $openlevs} {
2664 set ht 1
2665 set treediropen($prefix) [expr {$lev < $openlevs}]
2666 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2667 $w mark set d:$ix "end -1c"
2668 $w mark gravity d:$ix left
2669 set str "\n"
2670 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2671 $w insert end $str
2672 $w image create end -align center -image $bm -padx 1 \
2673 -name a:$ix
2674 $w insert end $d [highlight_tag $prefix]
2675 $w mark set s:$ix "end -1c"
2676 $w mark gravity s:$ix left
2678 incr lev
2680 if {$tail ne {}} {
2681 if {$lev <= $openlevs} {
2682 incr ht
2683 set str "\n"
2684 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2685 $w insert end $str
2686 $w insert end $tail [highlight_tag $f]
2688 lappend treecontents($prefix) $tail
2691 while {$htstack ne {}} {
2692 set treeheight($prefix) $ht
2693 incr ht [lindex $htstack end]
2694 set htstack [lreplace $htstack end end]
2695 set prefixend [lindex $prefendstack end]
2696 set prefendstack [lreplace $prefendstack end end]
2697 set prefix [string range $prefix 0 $prefixend]
2699 $w conf -state disabled
2702 proc linetoelt {l} {
2703 global treeheight treecontents
2705 set y 2
2706 set prefix {}
2707 while {1} {
2708 foreach e $treecontents($prefix) {
2709 if {$y == $l} {
2710 return "$prefix$e"
2712 set n 1
2713 if {[string index $e end] eq "/"} {
2714 set n $treeheight($prefix$e)
2715 if {$y + $n > $l} {
2716 append prefix $e
2717 incr y
2718 break
2721 incr y $n
2726 proc highlight_tree {y prefix} {
2727 global treeheight treecontents cflist
2729 foreach e $treecontents($prefix) {
2730 set path $prefix$e
2731 if {[highlight_tag $path] ne {}} {
2732 $cflist tag add bold $y.0 "$y.0 lineend"
2734 incr y
2735 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2736 set y [highlight_tree $y $path]
2739 return $y
2742 proc treeclosedir {w dir} {
2743 global treediropen treeheight treeparent treeindex
2745 set ix $treeindex($dir)
2746 $w conf -state normal
2747 $w delete s:$ix e:$ix
2748 set treediropen($dir) 0
2749 $w image configure a:$ix -image tri-rt
2750 $w conf -state disabled
2751 set n [expr {1 - $treeheight($dir)}]
2752 while {$dir ne {}} {
2753 incr treeheight($dir) $n
2754 set dir $treeparent($dir)
2758 proc treeopendir {w dir} {
2759 global treediropen treeheight treeparent treecontents treeindex
2761 set ix $treeindex($dir)
2762 $w conf -state normal
2763 $w image configure a:$ix -image tri-dn
2764 $w mark set e:$ix s:$ix
2765 $w mark gravity e:$ix right
2766 set lev 0
2767 set str "\n"
2768 set n [llength $treecontents($dir)]
2769 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2770 incr lev
2771 append str "\t"
2772 incr treeheight($x) $n
2774 foreach e $treecontents($dir) {
2775 set de $dir$e
2776 if {[string index $e end] eq "/"} {
2777 set iy $treeindex($de)
2778 $w mark set d:$iy e:$ix
2779 $w mark gravity d:$iy left
2780 $w insert e:$ix $str
2781 set treediropen($de) 0
2782 $w image create e:$ix -align center -image tri-rt -padx 1 \
2783 -name a:$iy
2784 $w insert e:$ix $e [highlight_tag $de]
2785 $w mark set s:$iy e:$ix
2786 $w mark gravity s:$iy left
2787 set treeheight($de) 1
2788 } else {
2789 $w insert e:$ix $str
2790 $w insert e:$ix $e [highlight_tag $de]
2793 $w mark gravity e:$ix right
2794 $w conf -state disabled
2795 set treediropen($dir) 1
2796 set top [lindex [split [$w index @0,0] .] 0]
2797 set ht [$w cget -height]
2798 set l [lindex [split [$w index s:$ix] .] 0]
2799 if {$l < $top} {
2800 $w yview $l.0
2801 } elseif {$l + $n + 1 > $top + $ht} {
2802 set top [expr {$l + $n + 2 - $ht}]
2803 if {$l < $top} {
2804 set top $l
2806 $w yview $top.0
2810 proc treeclick {w x y} {
2811 global treediropen cmitmode ctext cflist cflist_top
2813 if {$cmitmode ne "tree"} return
2814 if {![info exists cflist_top]} return
2815 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2816 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2817 $cflist tag add highlight $l.0 "$l.0 lineend"
2818 set cflist_top $l
2819 if {$l == 1} {
2820 $ctext yview 1.0
2821 return
2823 set e [linetoelt $l]
2824 if {[string index $e end] ne "/"} {
2825 showfile $e
2826 } elseif {$treediropen($e)} {
2827 treeclosedir $w $e
2828 } else {
2829 treeopendir $w $e
2833 proc setfilelist {id} {
2834 global treefilelist cflist jump_to_here
2836 treeview $cflist $treefilelist($id) 0
2837 if {$jump_to_here ne {}} {
2838 set f [lindex $jump_to_here 0]
2839 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2840 showfile $f
2845 image create bitmap tri-rt -background black -foreground blue -data {
2846 #define tri-rt_width 13
2847 #define tri-rt_height 13
2848 static unsigned char tri-rt_bits[] = {
2849 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2850 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2851 0x00, 0x00};
2852 } -maskdata {
2853 #define tri-rt-mask_width 13
2854 #define tri-rt-mask_height 13
2855 static unsigned char tri-rt-mask_bits[] = {
2856 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2857 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2858 0x08, 0x00};
2860 image create bitmap tri-dn -background black -foreground blue -data {
2861 #define tri-dn_width 13
2862 #define tri-dn_height 13
2863 static unsigned char tri-dn_bits[] = {
2864 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2865 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2866 0x00, 0x00};
2867 } -maskdata {
2868 #define tri-dn-mask_width 13
2869 #define tri-dn-mask_height 13
2870 static unsigned char tri-dn-mask_bits[] = {
2871 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2872 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2873 0x00, 0x00};
2876 image create bitmap reficon-T -background black -foreground yellow -data {
2877 #define tagicon_width 13
2878 #define tagicon_height 9
2879 static unsigned char tagicon_bits[] = {
2880 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2881 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2882 } -maskdata {
2883 #define tagicon-mask_width 13
2884 #define tagicon-mask_height 9
2885 static unsigned char tagicon-mask_bits[] = {
2886 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2887 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2889 set rectdata {
2890 #define headicon_width 13
2891 #define headicon_height 9
2892 static unsigned char headicon_bits[] = {
2893 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2894 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2896 set rectmask {
2897 #define headicon-mask_width 13
2898 #define headicon-mask_height 9
2899 static unsigned char headicon-mask_bits[] = {
2900 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2901 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2903 image create bitmap reficon-H -background black -foreground green \
2904 -data $rectdata -maskdata $rectmask
2905 image create bitmap reficon-o -background black -foreground "#ddddff" \
2906 -data $rectdata -maskdata $rectmask
2908 proc init_flist {first} {
2909 global cflist cflist_top difffilestart
2911 $cflist conf -state normal
2912 $cflist delete 0.0 end
2913 if {$first ne {}} {
2914 $cflist insert end $first
2915 set cflist_top 1
2916 $cflist tag add highlight 1.0 "1.0 lineend"
2917 } else {
2918 catch {unset cflist_top}
2920 $cflist conf -state disabled
2921 set difffilestart {}
2924 proc highlight_tag {f} {
2925 global highlight_paths
2927 foreach p $highlight_paths {
2928 if {[string match $p $f]} {
2929 return "bold"
2932 return {}
2935 proc highlight_filelist {} {
2936 global cmitmode cflist
2938 $cflist conf -state normal
2939 if {$cmitmode ne "tree"} {
2940 set end [lindex [split [$cflist index end] .] 0]
2941 for {set l 2} {$l < $end} {incr l} {
2942 set line [$cflist get $l.0 "$l.0 lineend"]
2943 if {[highlight_tag $line] ne {}} {
2944 $cflist tag add bold $l.0 "$l.0 lineend"
2947 } else {
2948 highlight_tree 2 {}
2950 $cflist conf -state disabled
2953 proc unhighlight_filelist {} {
2954 global cflist
2956 $cflist conf -state normal
2957 $cflist tag remove bold 1.0 end
2958 $cflist conf -state disabled
2961 proc add_flist {fl} {
2962 global cflist
2964 $cflist conf -state normal
2965 foreach f $fl {
2966 $cflist insert end "\n"
2967 $cflist insert end $f [highlight_tag $f]
2969 $cflist conf -state disabled
2972 proc sel_flist {w x y} {
2973 global ctext difffilestart cflist cflist_top cmitmode
2975 if {$cmitmode eq "tree"} return
2976 if {![info exists cflist_top]} return
2977 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2978 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2979 $cflist tag add highlight $l.0 "$l.0 lineend"
2980 set cflist_top $l
2981 if {$l == 1} {
2982 $ctext yview 1.0
2983 } else {
2984 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2988 proc pop_flist_menu {w X Y x y} {
2989 global ctext cflist cmitmode flist_menu flist_menu_file
2990 global treediffs diffids
2992 stopfinding
2993 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2994 if {$l <= 1} return
2995 if {$cmitmode eq "tree"} {
2996 set e [linetoelt $l]
2997 if {[string index $e end] eq "/"} return
2998 } else {
2999 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3001 set flist_menu_file $e
3002 set xdiffstate "normal"
3003 if {$cmitmode eq "tree"} {
3004 set xdiffstate "disabled"
3006 # Disable "External diff" item in tree mode
3007 $flist_menu entryconf 2 -state $xdiffstate
3008 tk_popup $flist_menu $X $Y
3011 proc find_ctext_fileinfo {line} {
3012 global ctext_file_names ctext_file_lines
3014 set ok [bsearch $ctext_file_lines $line]
3015 set tline [lindex $ctext_file_lines $ok]
3017 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3018 return {}
3019 } else {
3020 return [list [lindex $ctext_file_names $ok] $tline]
3024 proc pop_diff_menu {w X Y x y} {
3025 global ctext diff_menu flist_menu_file
3026 global diff_menu_txtpos diff_menu_line
3027 global diff_menu_filebase
3029 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3030 set diff_menu_line [lindex $diff_menu_txtpos 0]
3031 # don't pop up the menu on hunk-separator or file-separator lines
3032 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3033 return
3035 stopfinding
3036 set f [find_ctext_fileinfo $diff_menu_line]
3037 if {$f eq {}} return
3038 set flist_menu_file [lindex $f 0]
3039 set diff_menu_filebase [lindex $f 1]
3040 tk_popup $diff_menu $X $Y
3043 proc flist_hl {only} {
3044 global flist_menu_file findstring gdttype
3046 set x [shellquote $flist_menu_file]
3047 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3048 set findstring $x
3049 } else {
3050 append findstring " " $x
3052 set gdttype [mc "touching paths:"]
3055 proc save_file_from_commit {filename output what} {
3056 global nullfile
3058 if {[catch {exec git show $filename -- > $output} err]} {
3059 if {[string match "fatal: bad revision *" $err]} {
3060 return $nullfile
3062 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3063 return {}
3065 return $output
3068 proc external_diff_get_one_file {diffid filename diffdir} {
3069 global nullid nullid2 nullfile
3070 global gitdir
3072 if {$diffid == $nullid} {
3073 set difffile [file join [file dirname $gitdir] $filename]
3074 if {[file exists $difffile]} {
3075 return $difffile
3077 return $nullfile
3079 if {$diffid == $nullid2} {
3080 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3081 return [save_file_from_commit :$filename $difffile index]
3083 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3084 return [save_file_from_commit $diffid:$filename $difffile \
3085 "revision $diffid"]
3088 proc external_diff {} {
3089 global gitktmpdir nullid nullid2
3090 global flist_menu_file
3091 global diffids
3092 global diffnum
3093 global gitdir extdifftool
3095 if {[llength $diffids] == 1} {
3096 # no reference commit given
3097 set diffidto [lindex $diffids 0]
3098 if {$diffidto eq $nullid} {
3099 # diffing working copy with index
3100 set diffidfrom $nullid2
3101 } elseif {$diffidto eq $nullid2} {
3102 # diffing index with HEAD
3103 set diffidfrom "HEAD"
3104 } else {
3105 # use first parent commit
3106 global parentlist selectedline
3107 set diffidfrom [lindex $parentlist $selectedline 0]
3109 } else {
3110 set diffidfrom [lindex $diffids 0]
3111 set diffidto [lindex $diffids 1]
3114 # make sure that several diffs wont collide
3115 if {![info exists gitktmpdir]} {
3116 set gitktmpdir [file join [file dirname $gitdir] \
3117 [format ".gitk-tmp.%s" [pid]]]
3118 if {[catch {file mkdir $gitktmpdir} err]} {
3119 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3120 unset gitktmpdir
3121 return
3123 set diffnum 0
3125 incr diffnum
3126 set diffdir [file join $gitktmpdir $diffnum]
3127 if {[catch {file mkdir $diffdir} err]} {
3128 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3129 return
3132 # gather files to diff
3133 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3134 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3136 if {$difffromfile ne {} && $difftofile ne {}} {
3137 set cmd [concat | [shellsplit $extdifftool] \
3138 [list $difffromfile $difftofile]]
3139 if {[catch {set fl [open $cmd r]} err]} {
3140 file delete -force $diffdir
3141 error_popup "$extdifftool: [mc "command failed:"] $err"
3142 } else {
3143 fconfigure $fl -blocking 0
3144 filerun $fl [list delete_at_eof $fl $diffdir]
3149 proc find_hunk_blamespec {base line} {
3150 global ctext
3152 # Find and parse the hunk header
3153 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3154 if {$s_lix eq {}} return
3156 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3157 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3158 s_line old_specs osz osz1 new_line nsz]} {
3159 return
3162 # base lines for the parents
3163 set base_lines [list $new_line]
3164 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3165 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3166 old_spec old_line osz]} {
3167 return
3169 lappend base_lines $old_line
3172 # Now scan the lines to determine offset within the hunk
3173 set max_parent [expr {[llength $base_lines]-2}]
3174 set dline 0
3175 set s_lno [lindex [split $s_lix "."] 0]
3177 # Determine if the line is removed
3178 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3179 if {[string match {[-+ ]*} $chunk]} {
3180 set removed_idx [string first "-" $chunk]
3181 # Choose a parent index
3182 if {$removed_idx >= 0} {
3183 set parent $removed_idx
3184 } else {
3185 set unchanged_idx [string first " " $chunk]
3186 if {$unchanged_idx >= 0} {
3187 set parent $unchanged_idx
3188 } else {
3189 # blame the current commit
3190 set parent -1
3193 # then count other lines that belong to it
3194 for {set i $line} {[incr i -1] > $s_lno} {} {
3195 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3196 # Determine if the line is removed
3197 set removed_idx [string first "-" $chunk]
3198 if {$parent >= 0} {
3199 set code [string index $chunk $parent]
3200 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3201 incr dline
3203 } else {
3204 if {$removed_idx < 0} {
3205 incr dline
3209 incr parent
3210 } else {
3211 set parent 0
3214 incr dline [lindex $base_lines $parent]
3215 return [list $parent $dline]
3218 proc external_blame_diff {} {
3219 global currentid diffmergeid cmitmode
3220 global diff_menu_txtpos diff_menu_line
3221 global diff_menu_filebase flist_menu_file
3223 if {$cmitmode eq "tree"} {
3224 set parent_idx 0
3225 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3226 } else {
3227 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3228 if {$hinfo ne {}} {
3229 set parent_idx [lindex $hinfo 0]
3230 set line [lindex $hinfo 1]
3231 } else {
3232 set parent_idx 0
3233 set line 0
3237 external_blame $parent_idx $line
3240 proc external_blame {parent_idx {line {}}} {
3241 global flist_menu_file
3242 global nullid nullid2
3243 global parentlist selectedline currentid
3245 if {$parent_idx > 0} {
3246 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3247 } else {
3248 set base_commit $currentid
3251 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3252 error_popup [mc "No such commit"]
3253 return
3256 set cmdline [list git gui blame]
3257 if {$line ne {} && $line > 1} {
3258 lappend cmdline "--line=$line"
3260 lappend cmdline $base_commit $flist_menu_file
3261 if {[catch {eval exec $cmdline &} err]} {
3262 error_popup "[mc "git gui blame: command failed:"] $err"
3266 proc show_line_source {} {
3267 global cmitmode currentid parents curview blamestuff blameinst
3268 global diff_menu_line diff_menu_filebase flist_menu_file
3270 if {$cmitmode eq "tree"} {
3271 set id $currentid
3272 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3273 } else {
3274 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3275 if {$h eq {}} return
3276 set pi [lindex $h 0]
3277 if {$pi == 0} {
3278 mark_ctext_line $diff_menu_line
3279 return
3281 set id [lindex $parents($curview,$currentid) [expr {$pi - 1}]]
3282 set line [lindex $h 1]
3284 if {[catch {
3285 set f [open [list | git blame -p -L$line,+1 $id -- $flist_menu_file] r]
3286 } err]} {
3287 error_popup [mc "Couldn't start git blame: %s" $err]
3288 return
3290 fconfigure $f -blocking 0
3291 set i [reg_instance $f]
3292 set blamestuff($i) {}
3293 set blameinst $i
3294 filerun $f [list read_line_source $f $i]
3297 proc stopblaming {} {
3298 global blameinst
3300 if {[info exists blameinst]} {
3301 stop_instance $blameinst
3302 unset blameinst
3306 proc read_line_source {fd inst} {
3307 global blamestuff curview commfd blameinst
3309 while {[gets $fd line] >= 0} {
3310 lappend blamestuff($inst) $line
3312 if {![eof $fd]} {
3313 return 1
3315 unset commfd($inst)
3316 unset blameinst
3317 fconfigure $fd -blocking 1
3318 if {[catch {close $fd} err]} {
3319 error_popup [mc "Error running git blame: %s" $err]
3320 return 0
3323 set fname {}
3324 set line [split [lindex $blamestuff($inst) 0] " "]
3325 set id [lindex $line 0]
3326 set lnum [lindex $line 1]
3327 if {[string length $id] == 40 && [string is xdigit $id] &&
3328 [string is digit -strict $lnum]} {
3329 # look for "filename" line
3330 foreach l $blamestuff($inst) {
3331 if {[string match "filename *" $l]} {
3332 set fname [string range $l 9 end]
3333 break
3337 if {$fname ne {}} {
3338 # all looks good, select it
3339 if {[commitinview $id $curview]} {
3340 selectline [rowofcommit $id] 1 [list $fname $lnum]
3341 } else {
3342 error_popup [mc "That line comes from commit %s, \
3343 which is not in this view" [shortids $id]]
3345 } else {
3346 puts "oops couldn't parse git blame output"
3348 return 0
3351 # delete $dir when we see eof on $f (presumably because the child has exited)
3352 proc delete_at_eof {f dir} {
3353 while {[gets $f line] >= 0} {}
3354 if {[eof $f]} {
3355 if {[catch {close $f} err]} {
3356 error_popup "[mc "External diff viewer failed:"] $err"
3358 file delete -force $dir
3359 return 0
3361 return 1
3364 # Functions for adding and removing shell-type quoting
3366 proc shellquote {str} {
3367 if {![string match "*\['\"\\ \t]*" $str]} {
3368 return $str
3370 if {![string match "*\['\"\\]*" $str]} {
3371 return "\"$str\""
3373 if {![string match "*'*" $str]} {
3374 return "'$str'"
3376 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3379 proc shellarglist {l} {
3380 set str {}
3381 foreach a $l {
3382 if {$str ne {}} {
3383 append str " "
3385 append str [shellquote $a]
3387 return $str
3390 proc shelldequote {str} {
3391 set ret {}
3392 set used -1
3393 while {1} {
3394 incr used
3395 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3396 append ret [string range $str $used end]
3397 set used [string length $str]
3398 break
3400 set first [lindex $first 0]
3401 set ch [string index $str $first]
3402 if {$first > $used} {
3403 append ret [string range $str $used [expr {$first - 1}]]
3404 set used $first
3406 if {$ch eq " " || $ch eq "\t"} break
3407 incr used
3408 if {$ch eq "'"} {
3409 set first [string first "'" $str $used]
3410 if {$first < 0} {
3411 error "unmatched single-quote"
3413 append ret [string range $str $used [expr {$first - 1}]]
3414 set used $first
3415 continue
3417 if {$ch eq "\\"} {
3418 if {$used >= [string length $str]} {
3419 error "trailing backslash"
3421 append ret [string index $str $used]
3422 continue
3424 # here ch == "\""
3425 while {1} {
3426 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3427 error "unmatched double-quote"
3429 set first [lindex $first 0]
3430 set ch [string index $str $first]
3431 if {$first > $used} {
3432 append ret [string range $str $used [expr {$first - 1}]]
3433 set used $first
3435 if {$ch eq "\""} break
3436 incr used
3437 append ret [string index $str $used]
3438 incr used
3441 return [list $used $ret]
3444 proc shellsplit {str} {
3445 set l {}
3446 while {1} {
3447 set str [string trimleft $str]
3448 if {$str eq {}} break
3449 set dq [shelldequote $str]
3450 set n [lindex $dq 0]
3451 set word [lindex $dq 1]
3452 set str [string range $str $n end]
3453 lappend l $word
3455 return $l
3458 # Code to implement multiple views
3460 proc newview {ishighlight} {
3461 global nextviewnum newviewname newviewperm newishighlight
3462 global newviewargs revtreeargs viewargscmd newviewargscmd curview
3464 set newishighlight $ishighlight
3465 set top .gitkview
3466 if {[winfo exists $top]} {
3467 raise $top
3468 return
3470 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3471 set newviewperm($nextviewnum) 0
3472 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
3473 set newviewargscmd($nextviewnum) $viewargscmd($curview)
3474 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3477 proc editview {} {
3478 global curview
3479 global viewname viewperm newviewname newviewperm
3480 global viewargs newviewargs viewargscmd newviewargscmd
3482 set top .gitkvedit-$curview
3483 if {[winfo exists $top]} {
3484 raise $top
3485 return
3487 set newviewname($curview) $viewname($curview)
3488 set newviewperm($curview) $viewperm($curview)
3489 set newviewargs($curview) [shellarglist $viewargs($curview)]
3490 set newviewargscmd($curview) $viewargscmd($curview)
3491 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3494 proc vieweditor {top n title} {
3495 global newviewname newviewperm viewfiles bgcolor
3497 toplevel $top
3498 wm title $top $title
3499 label $top.nl -text [mc "Name"]
3500 entry $top.name -width 20 -textvariable newviewname($n)
3501 grid $top.nl $top.name -sticky w -pady 5
3502 checkbutton $top.perm -text [mc "Remember this view"] \
3503 -variable newviewperm($n)
3504 grid $top.perm - -pady 5 -sticky w
3505 message $top.al -aspect 1000 \
3506 -text [mc "Commits to include (arguments to git log):"]
3507 grid $top.al - -sticky w -pady 5
3508 entry $top.args -width 50 -textvariable newviewargs($n) \
3509 -background $bgcolor
3510 grid $top.args - -sticky ew -padx 5
3512 message $top.ac -aspect 1000 \
3513 -text [mc "Command to generate more commits to include:"]
3514 grid $top.ac - -sticky w -pady 5
3515 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
3516 -background white
3517 grid $top.argscmd - -sticky ew -padx 5
3519 message $top.l -aspect 1000 \
3520 -text [mc "Enter files and directories to include, one per line:"]
3521 grid $top.l - -sticky w
3522 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
3523 if {[info exists viewfiles($n)]} {
3524 foreach f $viewfiles($n) {
3525 $top.t insert end $f
3526 $top.t insert end "\n"
3528 $top.t delete {end - 1c} end
3529 $top.t mark set insert 0.0
3531 grid $top.t - -sticky ew -padx 5
3532 frame $top.buts
3533 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3534 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3535 grid $top.buts.ok $top.buts.can
3536 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3537 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3538 grid $top.buts - -pady 10 -sticky ew
3539 focus $top.t
3542 proc doviewmenu {m first cmd op argv} {
3543 set nmenu [$m index end]
3544 for {set i $first} {$i <= $nmenu} {incr i} {
3545 if {[$m entrycget $i -command] eq $cmd} {
3546 eval $m $op $i $argv
3547 break
3552 proc allviewmenus {n op args} {
3553 # global viewhlmenu
3555 doviewmenu .bar.view 5 [list showview $n] $op $args
3556 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3559 proc newviewok {top n} {
3560 global nextviewnum newviewperm newviewname newishighlight
3561 global viewname viewfiles viewperm selectedview curview
3562 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
3564 if {[catch {
3565 set newargs [shellsplit $newviewargs($n)]
3566 } err]} {
3567 error_popup "[mc "Error in commit selection arguments:"] $err"
3568 wm raise $top
3569 focus $top
3570 return
3572 set files {}
3573 foreach f [split [$top.t get 0.0 end] "\n"] {
3574 set ft [string trim $f]
3575 if {$ft ne {}} {
3576 lappend files $ft
3579 if {![info exists viewfiles($n)]} {
3580 # creating a new view
3581 incr nextviewnum
3582 set viewname($n) $newviewname($n)
3583 set viewperm($n) $newviewperm($n)
3584 set viewfiles($n) $files
3585 set viewargs($n) $newargs
3586 set viewargscmd($n) $newviewargscmd($n)
3587 addviewmenu $n
3588 if {!$newishighlight} {
3589 run showview $n
3590 } else {
3591 run addvhighlight $n
3593 } else {
3594 # editing an existing view
3595 set viewperm($n) $newviewperm($n)
3596 if {$newviewname($n) ne $viewname($n)} {
3597 set viewname($n) $newviewname($n)
3598 doviewmenu .bar.view 5 [list showview $n] \
3599 entryconf [list -label $viewname($n)]
3600 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3601 # entryconf [list -label $viewname($n) -value $viewname($n)]
3603 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3604 $newviewargscmd($n) ne $viewargscmd($n)} {
3605 set viewfiles($n) $files
3606 set viewargs($n) $newargs
3607 set viewargscmd($n) $newviewargscmd($n)
3608 if {$curview == $n} {
3609 run reloadcommits
3613 catch {destroy $top}
3616 proc delview {} {
3617 global curview viewperm hlview selectedhlview
3619 if {$curview == 0} return
3620 if {[info exists hlview] && $hlview == $curview} {
3621 set selectedhlview [mc "None"]
3622 unset hlview
3624 allviewmenus $curview delete
3625 set viewperm($curview) 0
3626 showview 0
3629 proc addviewmenu {n} {
3630 global viewname viewhlmenu
3632 .bar.view add radiobutton -label $viewname($n) \
3633 -command [list showview $n] -variable selectedview -value $n
3634 #$viewhlmenu add radiobutton -label $viewname($n) \
3635 # -command [list addvhighlight $n] -variable selectedhlview
3638 proc showview {n} {
3639 global curview cached_commitrow ordertok
3640 global displayorder parentlist rowidlist rowisopt rowfinal
3641 global colormap rowtextx nextcolor canvxmax
3642 global numcommits viewcomplete
3643 global selectedline currentid canv canvy0
3644 global treediffs
3645 global pending_select mainheadid
3646 global commitidx
3647 global selectedview
3648 global hlview selectedhlview commitinterest
3650 if {$n == $curview} return
3651 set selid {}
3652 set ymax [lindex [$canv cget -scrollregion] 3]
3653 set span [$canv yview]
3654 set ytop [expr {[lindex $span 0] * $ymax}]
3655 set ybot [expr {[lindex $span 1] * $ymax}]
3656 set yscreen [expr {($ybot - $ytop) / 2}]
3657 if {$selectedline ne {}} {
3658 set selid $currentid
3659 set y [yc $selectedline]
3660 if {$ytop < $y && $y < $ybot} {
3661 set yscreen [expr {$y - $ytop}]
3663 } elseif {[info exists pending_select]} {
3664 set selid $pending_select
3665 unset pending_select
3667 unselectline
3668 normalline
3669 catch {unset treediffs}
3670 clear_display
3671 if {[info exists hlview] && $hlview == $n} {
3672 unset hlview
3673 set selectedhlview [mc "None"]
3675 catch {unset commitinterest}
3676 catch {unset cached_commitrow}
3677 catch {unset ordertok}
3679 set curview $n
3680 set selectedview $n
3681 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3682 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3684 run refill_reflist
3685 if {![info exists viewcomplete($n)]} {
3686 getcommits $selid
3687 return
3690 set displayorder {}
3691 set parentlist {}
3692 set rowidlist {}
3693 set rowisopt {}
3694 set rowfinal {}
3695 set numcommits $commitidx($n)
3697 catch {unset colormap}
3698 catch {unset rowtextx}
3699 set nextcolor 0
3700 set canvxmax [$canv cget -width]
3701 set curview $n
3702 set row 0
3703 setcanvscroll
3704 set yf 0
3705 set row {}
3706 if {$selid ne {} && [commitinview $selid $n]} {
3707 set row [rowofcommit $selid]
3708 # try to get the selected row in the same position on the screen
3709 set ymax [lindex [$canv cget -scrollregion] 3]
3710 set ytop [expr {[yc $row] - $yscreen}]
3711 if {$ytop < 0} {
3712 set ytop 0
3714 set yf [expr {$ytop * 1.0 / $ymax}]
3716 allcanvs yview moveto $yf
3717 drawvisible
3718 if {$row ne {}} {
3719 selectline $row 0
3720 } elseif {!$viewcomplete($n)} {
3721 reset_pending_select $selid
3722 } else {
3723 reset_pending_select {}
3725 if {[commitinview $pending_select $curview]} {
3726 selectline [rowofcommit $pending_select] 1
3727 } else {
3728 set row [first_real_row]
3729 if {$row < $numcommits} {
3730 selectline $row 0
3734 if {!$viewcomplete($n)} {
3735 if {$numcommits == 0} {
3736 show_status [mc "Reading commits..."]
3738 } elseif {$numcommits == 0} {
3739 show_status [mc "No commits selected"]
3743 # Stuff relating to the highlighting facility
3745 proc ishighlighted {id} {
3746 global vhighlights fhighlights nhighlights rhighlights
3748 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3749 return $nhighlights($id)
3751 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3752 return $vhighlights($id)
3754 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3755 return $fhighlights($id)
3757 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3758 return $rhighlights($id)
3760 return 0
3763 proc bolden {row font} {
3764 global canv linehtag selectedline boldrows
3766 lappend boldrows $row
3767 $canv itemconf $linehtag($row) -font $font
3768 if {$row == $selectedline} {
3769 $canv delete secsel
3770 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3771 -outline {{}} -tags secsel \
3772 -fill [$canv cget -selectbackground]]
3773 $canv lower $t
3777 proc bolden_name {row font} {
3778 global canv2 linentag selectedline boldnamerows
3780 lappend boldnamerows $row
3781 $canv2 itemconf $linentag($row) -font $font
3782 if {$row == $selectedline} {
3783 $canv2 delete secsel
3784 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3785 -outline {{}} -tags secsel \
3786 -fill [$canv2 cget -selectbackground]]
3787 $canv2 lower $t
3791 proc unbolden {} {
3792 global boldrows
3794 set stillbold {}
3795 foreach row $boldrows {
3796 if {![ishighlighted [commitonrow $row]]} {
3797 bolden $row mainfont
3798 } else {
3799 lappend stillbold $row
3802 set boldrows $stillbold
3805 proc addvhighlight {n} {
3806 global hlview viewcomplete curview vhl_done commitidx
3808 if {[info exists hlview]} {
3809 delvhighlight
3811 set hlview $n
3812 if {$n != $curview && ![info exists viewcomplete($n)]} {
3813 start_rev_list $n
3815 set vhl_done $commitidx($hlview)
3816 if {$vhl_done > 0} {
3817 drawvisible
3821 proc delvhighlight {} {
3822 global hlview vhighlights
3824 if {![info exists hlview]} return
3825 unset hlview
3826 catch {unset vhighlights}
3827 unbolden
3830 proc vhighlightmore {} {
3831 global hlview vhl_done commitidx vhighlights curview
3833 set max $commitidx($hlview)
3834 set vr [visiblerows]
3835 set r0 [lindex $vr 0]
3836 set r1 [lindex $vr 1]
3837 for {set i $vhl_done} {$i < $max} {incr i} {
3838 set id [commitonrow $i $hlview]
3839 if {[commitinview $id $curview]} {
3840 set row [rowofcommit $id]
3841 if {$r0 <= $row && $row <= $r1} {
3842 if {![highlighted $row]} {
3843 bolden $row mainfontbold
3845 set vhighlights($id) 1
3849 set vhl_done $max
3850 return 0
3853 proc askvhighlight {row id} {
3854 global hlview vhighlights iddrawn
3856 if {[commitinview $id $hlview]} {
3857 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3858 bolden $row mainfontbold
3860 set vhighlights($id) 1
3861 } else {
3862 set vhighlights($id) 0
3866 proc hfiles_change {} {
3867 global highlight_files filehighlight fhighlights fh_serial
3868 global highlight_paths gdttype
3870 if {[info exists filehighlight]} {
3871 # delete previous highlights
3872 catch {close $filehighlight}
3873 unset filehighlight
3874 catch {unset fhighlights}
3875 unbolden
3876 unhighlight_filelist
3878 set highlight_paths {}
3879 after cancel do_file_hl $fh_serial
3880 incr fh_serial
3881 if {$highlight_files ne {}} {
3882 after 300 do_file_hl $fh_serial
3886 proc gdttype_change {name ix op} {
3887 global gdttype highlight_files findstring findpattern
3889 stopfinding
3890 if {$findstring ne {}} {
3891 if {$gdttype eq [mc "containing:"]} {
3892 if {$highlight_files ne {}} {
3893 set highlight_files {}
3894 hfiles_change
3896 findcom_change
3897 } else {
3898 if {$findpattern ne {}} {
3899 set findpattern {}
3900 findcom_change
3902 set highlight_files $findstring
3903 hfiles_change
3905 drawvisible
3907 # enable/disable findtype/findloc menus too
3910 proc find_change {name ix op} {
3911 global gdttype findstring highlight_files
3913 stopfinding
3914 if {$gdttype eq [mc "containing:"]} {
3915 findcom_change
3916 } else {
3917 if {$highlight_files ne $findstring} {
3918 set highlight_files $findstring
3919 hfiles_change
3922 drawvisible
3925 proc findcom_change args {
3926 global nhighlights boldnamerows
3927 global findpattern findtype findstring gdttype
3929 stopfinding
3930 # delete previous highlights, if any
3931 foreach row $boldnamerows {
3932 bolden_name $row mainfont
3934 set boldnamerows {}
3935 catch {unset nhighlights}
3936 unbolden
3937 unmarkmatches
3938 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
3939 set findpattern {}
3940 } elseif {$findtype eq [mc "Regexp"]} {
3941 set findpattern $findstring
3942 } else {
3943 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
3944 $findstring]
3945 set findpattern "*$e*"
3949 proc makepatterns {l} {
3950 set ret {}
3951 foreach e $l {
3952 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
3953 if {[string index $ee end] eq "/"} {
3954 lappend ret "$ee*"
3955 } else {
3956 lappend ret $ee
3957 lappend ret "$ee/*"
3960 return $ret
3963 proc do_file_hl {serial} {
3964 global highlight_files filehighlight highlight_paths gdttype fhl_list
3966 if {$gdttype eq [mc "touching paths:"]} {
3967 if {[catch {set paths [shellsplit $highlight_files]}]} return
3968 set highlight_paths [makepatterns $paths]
3969 highlight_filelist
3970 set gdtargs [concat -- $paths]
3971 } elseif {$gdttype eq [mc "adding/removing string:"]} {
3972 set gdtargs [list "-S$highlight_files"]
3973 } else {
3974 # must be "containing:", i.e. we're searching commit info
3975 return
3977 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
3978 set filehighlight [open $cmd r+]
3979 fconfigure $filehighlight -blocking 0
3980 filerun $filehighlight readfhighlight
3981 set fhl_list {}
3982 drawvisible
3983 flushhighlights
3986 proc flushhighlights {} {
3987 global filehighlight fhl_list
3989 if {[info exists filehighlight]} {
3990 lappend fhl_list {}
3991 puts $filehighlight ""
3992 flush $filehighlight
3996 proc askfilehighlight {row id} {
3997 global filehighlight fhighlights fhl_list
3999 lappend fhl_list $id
4000 set fhighlights($id) -1
4001 puts $filehighlight $id
4004 proc readfhighlight {} {
4005 global filehighlight fhighlights curview iddrawn
4006 global fhl_list find_dirn
4008 if {![info exists filehighlight]} {
4009 return 0
4011 set nr 0
4012 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4013 set line [string trim $line]
4014 set i [lsearch -exact $fhl_list $line]
4015 if {$i < 0} continue
4016 for {set j 0} {$j < $i} {incr j} {
4017 set id [lindex $fhl_list $j]
4018 set fhighlights($id) 0
4020 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4021 if {$line eq {}} continue
4022 if {![commitinview $line $curview]} continue
4023 set row [rowofcommit $line]
4024 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4025 bolden $row mainfontbold
4027 set fhighlights($line) 1
4029 if {[eof $filehighlight]} {
4030 # strange...
4031 puts "oops, git diff-tree died"
4032 catch {close $filehighlight}
4033 unset filehighlight
4034 return 0
4036 if {[info exists find_dirn]} {
4037 run findmore
4039 return 1
4042 proc doesmatch {f} {
4043 global findtype findpattern
4045 if {$findtype eq [mc "Regexp"]} {
4046 return [regexp $findpattern $f]
4047 } elseif {$findtype eq [mc "IgnCase"]} {
4048 return [string match -nocase $findpattern $f]
4049 } else {
4050 return [string match $findpattern $f]
4054 proc askfindhighlight {row id} {
4055 global nhighlights commitinfo iddrawn
4056 global findloc
4057 global markingmatches
4059 if {![info exists commitinfo($id)]} {
4060 getcommit $id
4062 set info $commitinfo($id)
4063 set isbold 0
4064 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4065 foreach f $info ty $fldtypes {
4066 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4067 [doesmatch $f]} {
4068 if {$ty eq [mc "Author"]} {
4069 set isbold 2
4070 break
4072 set isbold 1
4075 if {$isbold && [info exists iddrawn($id)]} {
4076 if {![ishighlighted $id]} {
4077 bolden $row mainfontbold
4078 if {$isbold > 1} {
4079 bolden_name $row mainfontbold
4082 if {$markingmatches} {
4083 markrowmatches $row $id
4086 set nhighlights($id) $isbold
4089 proc markrowmatches {row id} {
4090 global canv canv2 linehtag linentag commitinfo findloc
4092 set headline [lindex $commitinfo($id) 0]
4093 set author [lindex $commitinfo($id) 1]
4094 $canv delete match$row
4095 $canv2 delete match$row
4096 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4097 set m [findmatches $headline]
4098 if {$m ne {}} {
4099 markmatches $canv $row $headline $linehtag($row) $m \
4100 [$canv itemcget $linehtag($row) -font] $row
4103 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4104 set m [findmatches $author]
4105 if {$m ne {}} {
4106 markmatches $canv2 $row $author $linentag($row) $m \
4107 [$canv2 itemcget $linentag($row) -font] $row
4112 proc vrel_change {name ix op} {
4113 global highlight_related
4115 rhighlight_none
4116 if {$highlight_related ne [mc "None"]} {
4117 run drawvisible
4121 # prepare for testing whether commits are descendents or ancestors of a
4122 proc rhighlight_sel {a} {
4123 global descendent desc_todo ancestor anc_todo
4124 global highlight_related
4126 catch {unset descendent}
4127 set desc_todo [list $a]
4128 catch {unset ancestor}
4129 set anc_todo [list $a]
4130 if {$highlight_related ne [mc "None"]} {
4131 rhighlight_none
4132 run drawvisible
4136 proc rhighlight_none {} {
4137 global rhighlights
4139 catch {unset rhighlights}
4140 unbolden
4143 proc is_descendent {a} {
4144 global curview children descendent desc_todo
4146 set v $curview
4147 set la [rowofcommit $a]
4148 set todo $desc_todo
4149 set leftover {}
4150 set done 0
4151 for {set i 0} {$i < [llength $todo]} {incr i} {
4152 set do [lindex $todo $i]
4153 if {[rowofcommit $do] < $la} {
4154 lappend leftover $do
4155 continue
4157 foreach nk $children($v,$do) {
4158 if {![info exists descendent($nk)]} {
4159 set descendent($nk) 1
4160 lappend todo $nk
4161 if {$nk eq $a} {
4162 set done 1
4166 if {$done} {
4167 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4168 return
4171 set descendent($a) 0
4172 set desc_todo $leftover
4175 proc is_ancestor {a} {
4176 global curview parents ancestor anc_todo
4178 set v $curview
4179 set la [rowofcommit $a]
4180 set todo $anc_todo
4181 set leftover {}
4182 set done 0
4183 for {set i 0} {$i < [llength $todo]} {incr i} {
4184 set do [lindex $todo $i]
4185 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4186 lappend leftover $do
4187 continue
4189 foreach np $parents($v,$do) {
4190 if {![info exists ancestor($np)]} {
4191 set ancestor($np) 1
4192 lappend todo $np
4193 if {$np eq $a} {
4194 set done 1
4198 if {$done} {
4199 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4200 return
4203 set ancestor($a) 0
4204 set anc_todo $leftover
4207 proc askrelhighlight {row id} {
4208 global descendent highlight_related iddrawn rhighlights
4209 global selectedline ancestor
4211 if {$selectedline eq {}} return
4212 set isbold 0
4213 if {$highlight_related eq [mc "Descendant"] ||
4214 $highlight_related eq [mc "Not descendant"]} {
4215 if {![info exists descendent($id)]} {
4216 is_descendent $id
4218 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4219 set isbold 1
4221 } elseif {$highlight_related eq [mc "Ancestor"] ||
4222 $highlight_related eq [mc "Not ancestor"]} {
4223 if {![info exists ancestor($id)]} {
4224 is_ancestor $id
4226 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4227 set isbold 1
4230 if {[info exists iddrawn($id)]} {
4231 if {$isbold && ![ishighlighted $id]} {
4232 bolden $row mainfontbold
4235 set rhighlights($id) $isbold
4238 # Graph layout functions
4240 proc shortids {ids} {
4241 set res {}
4242 foreach id $ids {
4243 if {[llength $id] > 1} {
4244 lappend res [shortids $id]
4245 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4246 lappend res [string range $id 0 7]
4247 } else {
4248 lappend res $id
4251 return $res
4254 proc ntimes {n o} {
4255 set ret {}
4256 set o [list $o]
4257 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4258 if {($n & $mask) != 0} {
4259 set ret [concat $ret $o]
4261 set o [concat $o $o]
4263 return $ret
4266 proc ordertoken {id} {
4267 global ordertok curview varcid varcstart varctok curview parents children
4268 global nullid nullid2
4270 if {[info exists ordertok($id)]} {
4271 return $ordertok($id)
4273 set origid $id
4274 set todo {}
4275 while {1} {
4276 if {[info exists varcid($curview,$id)]} {
4277 set a $varcid($curview,$id)
4278 set p [lindex $varcstart($curview) $a]
4279 } else {
4280 set p [lindex $children($curview,$id) 0]
4282 if {[info exists ordertok($p)]} {
4283 set tok $ordertok($p)
4284 break
4286 set id [first_real_child $curview,$p]
4287 if {$id eq {}} {
4288 # it's a root
4289 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4290 break
4292 if {[llength $parents($curview,$id)] == 1} {
4293 lappend todo [list $p {}]
4294 } else {
4295 set j [lsearch -exact $parents($curview,$id) $p]
4296 if {$j < 0} {
4297 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4299 lappend todo [list $p [strrep $j]]
4302 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4303 set p [lindex $todo $i 0]
4304 append tok [lindex $todo $i 1]
4305 set ordertok($p) $tok
4307 set ordertok($origid) $tok
4308 return $tok
4311 # Work out where id should go in idlist so that order-token
4312 # values increase from left to right
4313 proc idcol {idlist id {i 0}} {
4314 set t [ordertoken $id]
4315 if {$i < 0} {
4316 set i 0
4318 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4319 if {$i > [llength $idlist]} {
4320 set i [llength $idlist]
4322 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4323 incr i
4324 } else {
4325 if {$t > [ordertoken [lindex $idlist $i]]} {
4326 while {[incr i] < [llength $idlist] &&
4327 $t >= [ordertoken [lindex $idlist $i]]} {}
4330 return $i
4333 proc initlayout {} {
4334 global rowidlist rowisopt rowfinal displayorder parentlist
4335 global numcommits canvxmax canv
4336 global nextcolor
4337 global colormap rowtextx
4339 set numcommits 0
4340 set displayorder {}
4341 set parentlist {}
4342 set nextcolor 0
4343 set rowidlist {}
4344 set rowisopt {}
4345 set rowfinal {}
4346 set canvxmax [$canv cget -width]
4347 catch {unset colormap}
4348 catch {unset rowtextx}
4349 setcanvscroll
4352 proc setcanvscroll {} {
4353 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4354 global lastscrollset lastscrollrows
4356 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4357 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4358 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4359 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4360 set lastscrollset [clock clicks -milliseconds]
4361 set lastscrollrows $numcommits
4364 proc visiblerows {} {
4365 global canv numcommits linespc
4367 set ymax [lindex [$canv cget -scrollregion] 3]
4368 if {$ymax eq {} || $ymax == 0} return
4369 set f [$canv yview]
4370 set y0 [expr {int([lindex $f 0] * $ymax)}]
4371 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4372 if {$r0 < 0} {
4373 set r0 0
4375 set y1 [expr {int([lindex $f 1] * $ymax)}]
4376 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4377 if {$r1 >= $numcommits} {
4378 set r1 [expr {$numcommits - 1}]
4380 return [list $r0 $r1]
4383 proc layoutmore {} {
4384 global commitidx viewcomplete curview
4385 global numcommits pending_select curview
4386 global lastscrollset lastscrollrows
4388 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4389 [clock clicks -milliseconds] - $lastscrollset > 500} {
4390 setcanvscroll
4392 if {[info exists pending_select] &&
4393 [commitinview $pending_select $curview]} {
4394 update
4395 selectline [rowofcommit $pending_select] 1
4397 drawvisible
4400 proc doshowlocalchanges {} {
4401 global curview mainheadid
4403 if {$mainheadid eq {}} return
4404 if {[commitinview $mainheadid $curview]} {
4405 dodiffindex
4406 } else {
4407 interestedin $mainheadid dodiffindex
4411 proc dohidelocalchanges {} {
4412 global nullid nullid2 lserial curview
4414 if {[commitinview $nullid $curview]} {
4415 removefakerow $nullid
4417 if {[commitinview $nullid2 $curview]} {
4418 removefakerow $nullid2
4420 incr lserial
4423 # spawn off a process to do git diff-index --cached HEAD
4424 proc dodiffindex {} {
4425 global lserial showlocalchanges
4426 global isworktree
4428 if {!$showlocalchanges || !$isworktree} return
4429 incr lserial
4430 set fd [open "|git diff-index --cached HEAD" r]
4431 fconfigure $fd -blocking 0
4432 set i [reg_instance $fd]
4433 filerun $fd [list readdiffindex $fd $lserial $i]
4436 proc readdiffindex {fd serial inst} {
4437 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4439 set isdiff 1
4440 if {[gets $fd line] < 0} {
4441 if {![eof $fd]} {
4442 return 1
4444 set isdiff 0
4446 # we only need to see one line and we don't really care what it says...
4447 stop_instance $inst
4449 if {$serial != $lserial} {
4450 return 0
4453 # now see if there are any local changes not checked in to the index
4454 set fd [open "|git diff-files" r]
4455 fconfigure $fd -blocking 0
4456 set i [reg_instance $fd]
4457 filerun $fd [list readdifffiles $fd $serial $i]
4459 if {$isdiff && ![commitinview $nullid2 $curview]} {
4460 # add the line for the changes in the index to the graph
4461 set hl [mc "Local changes checked in to index but not committed"]
4462 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4463 set commitdata($nullid2) "\n $hl\n"
4464 if {[commitinview $nullid $curview]} {
4465 removefakerow $nullid
4467 insertfakerow $nullid2 $mainheadid
4468 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4469 removefakerow $nullid2
4471 return 0
4474 proc readdifffiles {fd serial inst} {
4475 global mainheadid nullid nullid2 curview
4476 global commitinfo commitdata lserial
4478 set isdiff 1
4479 if {[gets $fd line] < 0} {
4480 if {![eof $fd]} {
4481 return 1
4483 set isdiff 0
4485 # we only need to see one line and we don't really care what it says...
4486 stop_instance $inst
4488 if {$serial != $lserial} {
4489 return 0
4492 if {$isdiff && ![commitinview $nullid $curview]} {
4493 # add the line for the local diff to the graph
4494 set hl [mc "Local uncommitted changes, not checked in to index"]
4495 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4496 set commitdata($nullid) "\n $hl\n"
4497 if {[commitinview $nullid2 $curview]} {
4498 set p $nullid2
4499 } else {
4500 set p $mainheadid
4502 insertfakerow $nullid $p
4503 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4504 removefakerow $nullid
4506 return 0
4509 proc nextuse {id row} {
4510 global curview children
4512 if {[info exists children($curview,$id)]} {
4513 foreach kid $children($curview,$id) {
4514 if {![commitinview $kid $curview]} {
4515 return -1
4517 if {[rowofcommit $kid] > $row} {
4518 return [rowofcommit $kid]
4522 if {[commitinview $id $curview]} {
4523 return [rowofcommit $id]
4525 return -1
4528 proc prevuse {id row} {
4529 global curview children
4531 set ret -1
4532 if {[info exists children($curview,$id)]} {
4533 foreach kid $children($curview,$id) {
4534 if {![commitinview $kid $curview]} break
4535 if {[rowofcommit $kid] < $row} {
4536 set ret [rowofcommit $kid]
4540 return $ret
4543 proc make_idlist {row} {
4544 global displayorder parentlist uparrowlen downarrowlen mingaplen
4545 global commitidx curview children
4547 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4548 if {$r < 0} {
4549 set r 0
4551 set ra [expr {$row - $downarrowlen}]
4552 if {$ra < 0} {
4553 set ra 0
4555 set rb [expr {$row + $uparrowlen}]
4556 if {$rb > $commitidx($curview)} {
4557 set rb $commitidx($curview)
4559 make_disporder $r [expr {$rb + 1}]
4560 set ids {}
4561 for {} {$r < $ra} {incr r} {
4562 set nextid [lindex $displayorder [expr {$r + 1}]]
4563 foreach p [lindex $parentlist $r] {
4564 if {$p eq $nextid} continue
4565 set rn [nextuse $p $r]
4566 if {$rn >= $row &&
4567 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4568 lappend ids [list [ordertoken $p] $p]
4572 for {} {$r < $row} {incr r} {
4573 set nextid [lindex $displayorder [expr {$r + 1}]]
4574 foreach p [lindex $parentlist $r] {
4575 if {$p eq $nextid} continue
4576 set rn [nextuse $p $r]
4577 if {$rn < 0 || $rn >= $row} {
4578 lappend ids [list [ordertoken $p] $p]
4582 set id [lindex $displayorder $row]
4583 lappend ids [list [ordertoken $id] $id]
4584 while {$r < $rb} {
4585 foreach p [lindex $parentlist $r] {
4586 set firstkid [lindex $children($curview,$p) 0]
4587 if {[rowofcommit $firstkid] < $row} {
4588 lappend ids [list [ordertoken $p] $p]
4591 incr r
4592 set id [lindex $displayorder $r]
4593 if {$id ne {}} {
4594 set firstkid [lindex $children($curview,$id) 0]
4595 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4596 lappend ids [list [ordertoken $id] $id]
4600 set idlist {}
4601 foreach idx [lsort -unique $ids] {
4602 lappend idlist [lindex $idx 1]
4604 return $idlist
4607 proc rowsequal {a b} {
4608 while {[set i [lsearch -exact $a {}]] >= 0} {
4609 set a [lreplace $a $i $i]
4611 while {[set i [lsearch -exact $b {}]] >= 0} {
4612 set b [lreplace $b $i $i]
4614 return [expr {$a eq $b}]
4617 proc makeupline {id row rend col} {
4618 global rowidlist uparrowlen downarrowlen mingaplen
4620 for {set r $rend} {1} {set r $rstart} {
4621 set rstart [prevuse $id $r]
4622 if {$rstart < 0} return
4623 if {$rstart < $row} break
4625 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4626 set rstart [expr {$rend - $uparrowlen - 1}]
4628 for {set r $rstart} {[incr r] <= $row} {} {
4629 set idlist [lindex $rowidlist $r]
4630 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4631 set col [idcol $idlist $id $col]
4632 lset rowidlist $r [linsert $idlist $col $id]
4633 changedrow $r
4638 proc layoutrows {row endrow} {
4639 global rowidlist rowisopt rowfinal displayorder
4640 global uparrowlen downarrowlen maxwidth mingaplen
4641 global children parentlist
4642 global commitidx viewcomplete curview
4644 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4645 set idlist {}
4646 if {$row > 0} {
4647 set rm1 [expr {$row - 1}]
4648 foreach id [lindex $rowidlist $rm1] {
4649 if {$id ne {}} {
4650 lappend idlist $id
4653 set final [lindex $rowfinal $rm1]
4655 for {} {$row < $endrow} {incr row} {
4656 set rm1 [expr {$row - 1}]
4657 if {$rm1 < 0 || $idlist eq {}} {
4658 set idlist [make_idlist $row]
4659 set final 1
4660 } else {
4661 set id [lindex $displayorder $rm1]
4662 set col [lsearch -exact $idlist $id]
4663 set idlist [lreplace $idlist $col $col]
4664 foreach p [lindex $parentlist $rm1] {
4665 if {[lsearch -exact $idlist $p] < 0} {
4666 set col [idcol $idlist $p $col]
4667 set idlist [linsert $idlist $col $p]
4668 # if not the first child, we have to insert a line going up
4669 if {$id ne [lindex $children($curview,$p) 0]} {
4670 makeupline $p $rm1 $row $col
4674 set id [lindex $displayorder $row]
4675 if {$row > $downarrowlen} {
4676 set termrow [expr {$row - $downarrowlen - 1}]
4677 foreach p [lindex $parentlist $termrow] {
4678 set i [lsearch -exact $idlist $p]
4679 if {$i < 0} continue
4680 set nr [nextuse $p $termrow]
4681 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4682 set idlist [lreplace $idlist $i $i]
4686 set col [lsearch -exact $idlist $id]
4687 if {$col < 0} {
4688 set col [idcol $idlist $id]
4689 set idlist [linsert $idlist $col $id]
4690 if {$children($curview,$id) ne {}} {
4691 makeupline $id $rm1 $row $col
4694 set r [expr {$row + $uparrowlen - 1}]
4695 if {$r < $commitidx($curview)} {
4696 set x $col
4697 foreach p [lindex $parentlist $r] {
4698 if {[lsearch -exact $idlist $p] >= 0} continue
4699 set fk [lindex $children($curview,$p) 0]
4700 if {[rowofcommit $fk] < $row} {
4701 set x [idcol $idlist $p $x]
4702 set idlist [linsert $idlist $x $p]
4705 if {[incr r] < $commitidx($curview)} {
4706 set p [lindex $displayorder $r]
4707 if {[lsearch -exact $idlist $p] < 0} {
4708 set fk [lindex $children($curview,$p) 0]
4709 if {$fk ne {} && [rowofcommit $fk] < $row} {
4710 set x [idcol $idlist $p $x]
4711 set idlist [linsert $idlist $x $p]
4717 if {$final && !$viewcomplete($curview) &&
4718 $row + $uparrowlen + $mingaplen + $downarrowlen
4719 >= $commitidx($curview)} {
4720 set final 0
4722 set l [llength $rowidlist]
4723 if {$row == $l} {
4724 lappend rowidlist $idlist
4725 lappend rowisopt 0
4726 lappend rowfinal $final
4727 } elseif {$row < $l} {
4728 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4729 lset rowidlist $row $idlist
4730 changedrow $row
4732 lset rowfinal $row $final
4733 } else {
4734 set pad [ntimes [expr {$row - $l}] {}]
4735 set rowidlist [concat $rowidlist $pad]
4736 lappend rowidlist $idlist
4737 set rowfinal [concat $rowfinal $pad]
4738 lappend rowfinal $final
4739 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4742 return $row
4745 proc changedrow {row} {
4746 global displayorder iddrawn rowisopt need_redisplay
4748 set l [llength $rowisopt]
4749 if {$row < $l} {
4750 lset rowisopt $row 0
4751 if {$row + 1 < $l} {
4752 lset rowisopt [expr {$row + 1}] 0
4753 if {$row + 2 < $l} {
4754 lset rowisopt [expr {$row + 2}] 0
4758 set id [lindex $displayorder $row]
4759 if {[info exists iddrawn($id)]} {
4760 set need_redisplay 1
4764 proc insert_pad {row col npad} {
4765 global rowidlist
4767 set pad [ntimes $npad {}]
4768 set idlist [lindex $rowidlist $row]
4769 set bef [lrange $idlist 0 [expr {$col - 1}]]
4770 set aft [lrange $idlist $col end]
4771 set i [lsearch -exact $aft {}]
4772 if {$i > 0} {
4773 set aft [lreplace $aft $i $i]
4775 lset rowidlist $row [concat $bef $pad $aft]
4776 changedrow $row
4779 proc optimize_rows {row col endrow} {
4780 global rowidlist rowisopt displayorder curview children
4782 if {$row < 1} {
4783 set row 1
4785 for {} {$row < $endrow} {incr row; set col 0} {
4786 if {[lindex $rowisopt $row]} continue
4787 set haspad 0
4788 set y0 [expr {$row - 1}]
4789 set ym [expr {$row - 2}]
4790 set idlist [lindex $rowidlist $row]
4791 set previdlist [lindex $rowidlist $y0]
4792 if {$idlist eq {} || $previdlist eq {}} continue
4793 if {$ym >= 0} {
4794 set pprevidlist [lindex $rowidlist $ym]
4795 if {$pprevidlist eq {}} continue
4796 } else {
4797 set pprevidlist {}
4799 set x0 -1
4800 set xm -1
4801 for {} {$col < [llength $idlist]} {incr col} {
4802 set id [lindex $idlist $col]
4803 if {[lindex $previdlist $col] eq $id} continue
4804 if {$id eq {}} {
4805 set haspad 1
4806 continue
4808 set x0 [lsearch -exact $previdlist $id]
4809 if {$x0 < 0} continue
4810 set z [expr {$x0 - $col}]
4811 set isarrow 0
4812 set z0 {}
4813 if {$ym >= 0} {
4814 set xm [lsearch -exact $pprevidlist $id]
4815 if {$xm >= 0} {
4816 set z0 [expr {$xm - $x0}]
4819 if {$z0 eq {}} {
4820 # if row y0 is the first child of $id then it's not an arrow
4821 if {[lindex $children($curview,$id) 0] ne
4822 [lindex $displayorder $y0]} {
4823 set isarrow 1
4826 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4827 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4828 set isarrow 1
4830 # Looking at lines from this row to the previous row,
4831 # make them go straight up if they end in an arrow on
4832 # the previous row; otherwise make them go straight up
4833 # or at 45 degrees.
4834 if {$z < -1 || ($z < 0 && $isarrow)} {
4835 # Line currently goes left too much;
4836 # insert pads in the previous row, then optimize it
4837 set npad [expr {-1 - $z + $isarrow}]
4838 insert_pad $y0 $x0 $npad
4839 if {$y0 > 0} {
4840 optimize_rows $y0 $x0 $row
4842 set previdlist [lindex $rowidlist $y0]
4843 set x0 [lsearch -exact $previdlist $id]
4844 set z [expr {$x0 - $col}]
4845 if {$z0 ne {}} {
4846 set pprevidlist [lindex $rowidlist $ym]
4847 set xm [lsearch -exact $pprevidlist $id]
4848 set z0 [expr {$xm - $x0}]
4850 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4851 # Line currently goes right too much;
4852 # insert pads in this line
4853 set npad [expr {$z - 1 + $isarrow}]
4854 insert_pad $row $col $npad
4855 set idlist [lindex $rowidlist $row]
4856 incr col $npad
4857 set z [expr {$x0 - $col}]
4858 set haspad 1
4860 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4861 # this line links to its first child on row $row-2
4862 set id [lindex $displayorder $ym]
4863 set xc [lsearch -exact $pprevidlist $id]
4864 if {$xc >= 0} {
4865 set z0 [expr {$xc - $x0}]
4868 # avoid lines jigging left then immediately right
4869 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4870 insert_pad $y0 $x0 1
4871 incr x0
4872 optimize_rows $y0 $x0 $row
4873 set previdlist [lindex $rowidlist $y0]
4876 if {!$haspad} {
4877 # Find the first column that doesn't have a line going right
4878 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
4879 set id [lindex $idlist $col]
4880 if {$id eq {}} break
4881 set x0 [lsearch -exact $previdlist $id]
4882 if {$x0 < 0} {
4883 # check if this is the link to the first child
4884 set kid [lindex $displayorder $y0]
4885 if {[lindex $children($curview,$id) 0] eq $kid} {
4886 # it is, work out offset to child
4887 set x0 [lsearch -exact $previdlist $kid]
4890 if {$x0 <= $col} break
4892 # Insert a pad at that column as long as it has a line and
4893 # isn't the last column
4894 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
4895 set idlist [linsert $idlist $col {}]
4896 lset rowidlist $row $idlist
4897 changedrow $row
4903 proc xc {row col} {
4904 global canvx0 linespc
4905 return [expr {$canvx0 + $col * $linespc}]
4908 proc yc {row} {
4909 global canvy0 linespc
4910 return [expr {$canvy0 + $row * $linespc}]
4913 proc linewidth {id} {
4914 global thickerline lthickness
4916 set wid $lthickness
4917 if {[info exists thickerline] && $id eq $thickerline} {
4918 set wid [expr {2 * $lthickness}]
4920 return $wid
4923 proc rowranges {id} {
4924 global curview children uparrowlen downarrowlen
4925 global rowidlist
4927 set kids $children($curview,$id)
4928 if {$kids eq {}} {
4929 return {}
4931 set ret {}
4932 lappend kids $id
4933 foreach child $kids {
4934 if {![commitinview $child $curview]} break
4935 set row [rowofcommit $child]
4936 if {![info exists prev]} {
4937 lappend ret [expr {$row + 1}]
4938 } else {
4939 if {$row <= $prevrow} {
4940 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
4942 # see if the line extends the whole way from prevrow to row
4943 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
4944 [lsearch -exact [lindex $rowidlist \
4945 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
4946 # it doesn't, see where it ends
4947 set r [expr {$prevrow + $downarrowlen}]
4948 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4949 while {[incr r -1] > $prevrow &&
4950 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4951 } else {
4952 while {[incr r] <= $row &&
4953 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4954 incr r -1
4956 lappend ret $r
4957 # see where it starts up again
4958 set r [expr {$row - $uparrowlen}]
4959 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
4960 while {[incr r] < $row &&
4961 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
4962 } else {
4963 while {[incr r -1] >= $prevrow &&
4964 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
4965 incr r
4967 lappend ret $r
4970 if {$child eq $id} {
4971 lappend ret $row
4973 set prev $child
4974 set prevrow $row
4976 return $ret
4979 proc drawlineseg {id row endrow arrowlow} {
4980 global rowidlist displayorder iddrawn linesegs
4981 global canv colormap linespc curview maxlinelen parentlist
4983 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
4984 set le [expr {$row + 1}]
4985 set arrowhigh 1
4986 while {1} {
4987 set c [lsearch -exact [lindex $rowidlist $le] $id]
4988 if {$c < 0} {
4989 incr le -1
4990 break
4992 lappend cols $c
4993 set x [lindex $displayorder $le]
4994 if {$x eq $id} {
4995 set arrowhigh 0
4996 break
4998 if {[info exists iddrawn($x)] || $le == $endrow} {
4999 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5000 if {$c >= 0} {
5001 lappend cols $c
5002 set arrowhigh 0
5004 break
5006 incr le
5008 if {$le <= $row} {
5009 return $row
5012 set lines {}
5013 set i 0
5014 set joinhigh 0
5015 if {[info exists linesegs($id)]} {
5016 set lines $linesegs($id)
5017 foreach li $lines {
5018 set r0 [lindex $li 0]
5019 if {$r0 > $row} {
5020 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5021 set joinhigh 1
5023 break
5025 incr i
5028 set joinlow 0
5029 if {$i > 0} {
5030 set li [lindex $lines [expr {$i-1}]]
5031 set r1 [lindex $li 1]
5032 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5033 set joinlow 1
5037 set x [lindex $cols [expr {$le - $row}]]
5038 set xp [lindex $cols [expr {$le - 1 - $row}]]
5039 set dir [expr {$xp - $x}]
5040 if {$joinhigh} {
5041 set ith [lindex $lines $i 2]
5042 set coords [$canv coords $ith]
5043 set ah [$canv itemcget $ith -arrow]
5044 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5045 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5046 if {$x2 ne {} && $x - $x2 == $dir} {
5047 set coords [lrange $coords 0 end-2]
5049 } else {
5050 set coords [list [xc $le $x] [yc $le]]
5052 if {$joinlow} {
5053 set itl [lindex $lines [expr {$i-1}] 2]
5054 set al [$canv itemcget $itl -arrow]
5055 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5056 } elseif {$arrowlow} {
5057 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5058 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5059 set arrowlow 0
5062 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5063 for {set y $le} {[incr y -1] > $row} {} {
5064 set x $xp
5065 set xp [lindex $cols [expr {$y - 1 - $row}]]
5066 set ndir [expr {$xp - $x}]
5067 if {$dir != $ndir || $xp < 0} {
5068 lappend coords [xc $y $x] [yc $y]
5070 set dir $ndir
5072 if {!$joinlow} {
5073 if {$xp < 0} {
5074 # join parent line to first child
5075 set ch [lindex $displayorder $row]
5076 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5077 if {$xc < 0} {
5078 puts "oops: drawlineseg: child $ch not on row $row"
5079 } elseif {$xc != $x} {
5080 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5081 set d [expr {int(0.5 * $linespc)}]
5082 set x1 [xc $row $x]
5083 if {$xc < $x} {
5084 set x2 [expr {$x1 - $d}]
5085 } else {
5086 set x2 [expr {$x1 + $d}]
5088 set y2 [yc $row]
5089 set y1 [expr {$y2 + $d}]
5090 lappend coords $x1 $y1 $x2 $y2
5091 } elseif {$xc < $x - 1} {
5092 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5093 } elseif {$xc > $x + 1} {
5094 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5096 set x $xc
5098 lappend coords [xc $row $x] [yc $row]
5099 } else {
5100 set xn [xc $row $xp]
5101 set yn [yc $row]
5102 lappend coords $xn $yn
5104 if {!$joinhigh} {
5105 assigncolor $id
5106 set t [$canv create line $coords -width [linewidth $id] \
5107 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5108 $canv lower $t
5109 bindline $t $id
5110 set lines [linsert $lines $i [list $row $le $t]]
5111 } else {
5112 $canv coords $ith $coords
5113 if {$arrow ne $ah} {
5114 $canv itemconf $ith -arrow $arrow
5116 lset lines $i 0 $row
5118 } else {
5119 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5120 set ndir [expr {$xo - $xp}]
5121 set clow [$canv coords $itl]
5122 if {$dir == $ndir} {
5123 set clow [lrange $clow 2 end]
5125 set coords [concat $coords $clow]
5126 if {!$joinhigh} {
5127 lset lines [expr {$i-1}] 1 $le
5128 } else {
5129 # coalesce two pieces
5130 $canv delete $ith
5131 set b [lindex $lines [expr {$i-1}] 0]
5132 set e [lindex $lines $i 1]
5133 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5135 $canv coords $itl $coords
5136 if {$arrow ne $al} {
5137 $canv itemconf $itl -arrow $arrow
5141 set linesegs($id) $lines
5142 return $le
5145 proc drawparentlinks {id row} {
5146 global rowidlist canv colormap curview parentlist
5147 global idpos linespc
5149 set rowids [lindex $rowidlist $row]
5150 set col [lsearch -exact $rowids $id]
5151 if {$col < 0} return
5152 set olds [lindex $parentlist $row]
5153 set row2 [expr {$row + 1}]
5154 set x [xc $row $col]
5155 set y [yc $row]
5156 set y2 [yc $row2]
5157 set d [expr {int(0.5 * $linespc)}]
5158 set ymid [expr {$y + $d}]
5159 set ids [lindex $rowidlist $row2]
5160 # rmx = right-most X coord used
5161 set rmx 0
5162 foreach p $olds {
5163 set i [lsearch -exact $ids $p]
5164 if {$i < 0} {
5165 puts "oops, parent $p of $id not in list"
5166 continue
5168 set x2 [xc $row2 $i]
5169 if {$x2 > $rmx} {
5170 set rmx $x2
5172 set j [lsearch -exact $rowids $p]
5173 if {$j < 0} {
5174 # drawlineseg will do this one for us
5175 continue
5177 assigncolor $p
5178 # should handle duplicated parents here...
5179 set coords [list $x $y]
5180 if {$i != $col} {
5181 # if attaching to a vertical segment, draw a smaller
5182 # slant for visual distinctness
5183 if {$i == $j} {
5184 if {$i < $col} {
5185 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5186 } else {
5187 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5189 } elseif {$i < $col && $i < $j} {
5190 # segment slants towards us already
5191 lappend coords [xc $row $j] $y
5192 } else {
5193 if {$i < $col - 1} {
5194 lappend coords [expr {$x2 + $linespc}] $y
5195 } elseif {$i > $col + 1} {
5196 lappend coords [expr {$x2 - $linespc}] $y
5198 lappend coords $x2 $y2
5200 } else {
5201 lappend coords $x2 $y2
5203 set t [$canv create line $coords -width [linewidth $p] \
5204 -fill $colormap($p) -tags lines.$p]
5205 $canv lower $t
5206 bindline $t $p
5208 if {$rmx > [lindex $idpos($id) 1]} {
5209 lset idpos($id) 1 $rmx
5210 redrawtags $id
5214 proc drawlines {id} {
5215 global canv
5217 $canv itemconf lines.$id -width [linewidth $id]
5220 proc drawcmittext {id row col} {
5221 global linespc canv canv2 canv3 fgcolor curview
5222 global cmitlisted commitinfo rowidlist parentlist
5223 global rowtextx idpos idtags idheads idotherrefs
5224 global linehtag linentag linedtag selectedline
5225 global canvxmax boldrows boldnamerows fgcolor
5226 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5228 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5229 set listed $cmitlisted($curview,$id)
5230 if {$id eq $nullid} {
5231 set ofill red
5232 } elseif {$id eq $nullid2} {
5233 set ofill green
5234 } elseif {$id eq $mainheadid} {
5235 set ofill yellow
5236 } else {
5237 set ofill [lindex $circlecolors $listed]
5239 set x [xc $row $col]
5240 set y [yc $row]
5241 set orad [expr {$linespc / 3}]
5242 if {$listed <= 2} {
5243 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5244 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5245 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5246 } elseif {$listed == 3} {
5247 # triangle pointing left for left-side commits
5248 set t [$canv create polygon \
5249 [expr {$x - $orad}] $y \
5250 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5251 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5252 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5253 } else {
5254 # triangle pointing right for right-side commits
5255 set t [$canv create polygon \
5256 [expr {$x + $orad - 1}] $y \
5257 [expr {$x - $orad}] [expr {$y - $orad}] \
5258 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5259 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5261 set circleitem($row) $t
5262 $canv raise $t
5263 $canv bind $t <1> {selcanvline {} %x %y}
5264 set rmx [llength [lindex $rowidlist $row]]
5265 set olds [lindex $parentlist $row]
5266 if {$olds ne {}} {
5267 set nextids [lindex $rowidlist [expr {$row + 1}]]
5268 foreach p $olds {
5269 set i [lsearch -exact $nextids $p]
5270 if {$i > $rmx} {
5271 set rmx $i
5275 set xt [xc $row $rmx]
5276 set rowtextx($row) $xt
5277 set idpos($id) [list $x $xt $y]
5278 if {[info exists idtags($id)] || [info exists idheads($id)]
5279 || [info exists idotherrefs($id)]} {
5280 set xt [drawtags $id $x $xt $y]
5282 set headline [lindex $commitinfo($id) 0]
5283 set name [lindex $commitinfo($id) 1]
5284 set date [lindex $commitinfo($id) 2]
5285 set date [formatdate $date]
5286 set font mainfont
5287 set nfont mainfont
5288 set isbold [ishighlighted $id]
5289 if {$isbold > 0} {
5290 lappend boldrows $row
5291 set font mainfontbold
5292 if {$isbold > 1} {
5293 lappend boldnamerows $row
5294 set nfont mainfontbold
5297 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5298 -text $headline -font $font -tags text]
5299 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5300 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5301 -text $name -font $nfont -tags text]
5302 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5303 -text $date -font mainfont -tags text]
5304 if {$selectedline == $row} {
5305 make_secsel $row
5307 set xr [expr {$xt + [font measure $font $headline]}]
5308 if {$xr > $canvxmax} {
5309 set canvxmax $xr
5310 setcanvscroll
5314 proc drawcmitrow {row} {
5315 global displayorder rowidlist nrows_drawn
5316 global iddrawn markingmatches
5317 global commitinfo numcommits
5318 global filehighlight fhighlights findpattern nhighlights
5319 global hlview vhighlights
5320 global highlight_related rhighlights
5322 if {$row >= $numcommits} return
5324 set id [lindex $displayorder $row]
5325 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5326 askvhighlight $row $id
5328 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5329 askfilehighlight $row $id
5331 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5332 askfindhighlight $row $id
5334 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5335 askrelhighlight $row $id
5337 if {![info exists iddrawn($id)]} {
5338 set col [lsearch -exact [lindex $rowidlist $row] $id]
5339 if {$col < 0} {
5340 puts "oops, row $row id $id not in list"
5341 return
5343 if {![info exists commitinfo($id)]} {
5344 getcommit $id
5346 assigncolor $id
5347 drawcmittext $id $row $col
5348 set iddrawn($id) 1
5349 incr nrows_drawn
5351 if {$markingmatches} {
5352 markrowmatches $row $id
5356 proc drawcommits {row {endrow {}}} {
5357 global numcommits iddrawn displayorder curview need_redisplay
5358 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5360 if {$row < 0} {
5361 set row 0
5363 if {$endrow eq {}} {
5364 set endrow $row
5366 if {$endrow >= $numcommits} {
5367 set endrow [expr {$numcommits - 1}]
5370 set rl1 [expr {$row - $downarrowlen - 3}]
5371 if {$rl1 < 0} {
5372 set rl1 0
5374 set ro1 [expr {$row - 3}]
5375 if {$ro1 < 0} {
5376 set ro1 0
5378 set r2 [expr {$endrow + $uparrowlen + 3}]
5379 if {$r2 > $numcommits} {
5380 set r2 $numcommits
5382 for {set r $rl1} {$r < $r2} {incr r} {
5383 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5384 if {$rl1 < $r} {
5385 layoutrows $rl1 $r
5387 set rl1 [expr {$r + 1}]
5390 if {$rl1 < $r} {
5391 layoutrows $rl1 $r
5393 optimize_rows $ro1 0 $r2
5394 if {$need_redisplay || $nrows_drawn > 2000} {
5395 clear_display
5396 drawvisible
5399 # make the lines join to already-drawn rows either side
5400 set r [expr {$row - 1}]
5401 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5402 set r $row
5404 set er [expr {$endrow + 1}]
5405 if {$er >= $numcommits ||
5406 ![info exists iddrawn([lindex $displayorder $er])]} {
5407 set er $endrow
5409 for {} {$r <= $er} {incr r} {
5410 set id [lindex $displayorder $r]
5411 set wasdrawn [info exists iddrawn($id)]
5412 drawcmitrow $r
5413 if {$r == $er} break
5414 set nextid [lindex $displayorder [expr {$r + 1}]]
5415 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5416 drawparentlinks $id $r
5418 set rowids [lindex $rowidlist $r]
5419 foreach lid $rowids {
5420 if {$lid eq {}} continue
5421 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5422 if {$lid eq $id} {
5423 # see if this is the first child of any of its parents
5424 foreach p [lindex $parentlist $r] {
5425 if {[lsearch -exact $rowids $p] < 0} {
5426 # make this line extend up to the child
5427 set lineend($p) [drawlineseg $p $r $er 0]
5430 } else {
5431 set lineend($lid) [drawlineseg $lid $r $er 1]
5437 proc undolayout {row} {
5438 global uparrowlen mingaplen downarrowlen
5439 global rowidlist rowisopt rowfinal need_redisplay
5441 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5442 if {$r < 0} {
5443 set r 0
5445 if {[llength $rowidlist] > $r} {
5446 incr r -1
5447 set rowidlist [lrange $rowidlist 0 $r]
5448 set rowfinal [lrange $rowfinal 0 $r]
5449 set rowisopt [lrange $rowisopt 0 $r]
5450 set need_redisplay 1
5451 run drawvisible
5455 proc drawvisible {} {
5456 global canv linespc curview vrowmod selectedline targetrow targetid
5457 global need_redisplay cscroll numcommits
5459 set fs [$canv yview]
5460 set ymax [lindex [$canv cget -scrollregion] 3]
5461 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5462 set f0 [lindex $fs 0]
5463 set f1 [lindex $fs 1]
5464 set y0 [expr {int($f0 * $ymax)}]
5465 set y1 [expr {int($f1 * $ymax)}]
5467 if {[info exists targetid]} {
5468 if {[commitinview $targetid $curview]} {
5469 set r [rowofcommit $targetid]
5470 if {$r != $targetrow} {
5471 # Fix up the scrollregion and change the scrolling position
5472 # now that our target row has moved.
5473 set diff [expr {($r - $targetrow) * $linespc}]
5474 set targetrow $r
5475 setcanvscroll
5476 set ymax [lindex [$canv cget -scrollregion] 3]
5477 incr y0 $diff
5478 incr y1 $diff
5479 set f0 [expr {$y0 / $ymax}]
5480 set f1 [expr {$y1 / $ymax}]
5481 allcanvs yview moveto $f0
5482 $cscroll set $f0 $f1
5483 set need_redisplay 1
5485 } else {
5486 unset targetid
5490 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5491 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5492 if {$endrow >= $vrowmod($curview)} {
5493 update_arcrows $curview
5495 if {$selectedline ne {} &&
5496 $row <= $selectedline && $selectedline <= $endrow} {
5497 set targetrow $selectedline
5498 } elseif {[info exists targetid]} {
5499 set targetrow [expr {int(($row + $endrow) / 2)}]
5501 if {[info exists targetrow]} {
5502 if {$targetrow >= $numcommits} {
5503 set targetrow [expr {$numcommits - 1}]
5505 set targetid [commitonrow $targetrow]
5507 drawcommits $row $endrow
5510 proc clear_display {} {
5511 global iddrawn linesegs need_redisplay nrows_drawn
5512 global vhighlights fhighlights nhighlights rhighlights
5513 global linehtag linentag linedtag boldrows boldnamerows
5515 allcanvs delete all
5516 catch {unset iddrawn}
5517 catch {unset linesegs}
5518 catch {unset linehtag}
5519 catch {unset linentag}
5520 catch {unset linedtag}
5521 set boldrows {}
5522 set boldnamerows {}
5523 catch {unset vhighlights}
5524 catch {unset fhighlights}
5525 catch {unset nhighlights}
5526 catch {unset rhighlights}
5527 set need_redisplay 0
5528 set nrows_drawn 0
5531 proc findcrossings {id} {
5532 global rowidlist parentlist numcommits displayorder
5534 set cross {}
5535 set ccross {}
5536 foreach {s e} [rowranges $id] {
5537 if {$e >= $numcommits} {
5538 set e [expr {$numcommits - 1}]
5540 if {$e <= $s} continue
5541 for {set row $e} {[incr row -1] >= $s} {} {
5542 set x [lsearch -exact [lindex $rowidlist $row] $id]
5543 if {$x < 0} break
5544 set olds [lindex $parentlist $row]
5545 set kid [lindex $displayorder $row]
5546 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5547 if {$kidx < 0} continue
5548 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5549 foreach p $olds {
5550 set px [lsearch -exact $nextrow $p]
5551 if {$px < 0} continue
5552 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5553 if {[lsearch -exact $ccross $p] >= 0} continue
5554 if {$x == $px + ($kidx < $px? -1: 1)} {
5555 lappend ccross $p
5556 } elseif {[lsearch -exact $cross $p] < 0} {
5557 lappend cross $p
5563 return [concat $ccross {{}} $cross]
5566 proc assigncolor {id} {
5567 global colormap colors nextcolor
5568 global parents children children curview
5570 if {[info exists colormap($id)]} return
5571 set ncolors [llength $colors]
5572 if {[info exists children($curview,$id)]} {
5573 set kids $children($curview,$id)
5574 } else {
5575 set kids {}
5577 if {[llength $kids] == 1} {
5578 set child [lindex $kids 0]
5579 if {[info exists colormap($child)]
5580 && [llength $parents($curview,$child)] == 1} {
5581 set colormap($id) $colormap($child)
5582 return
5585 set badcolors {}
5586 set origbad {}
5587 foreach x [findcrossings $id] {
5588 if {$x eq {}} {
5589 # delimiter between corner crossings and other crossings
5590 if {[llength $badcolors] >= $ncolors - 1} break
5591 set origbad $badcolors
5593 if {[info exists colormap($x)]
5594 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5595 lappend badcolors $colormap($x)
5598 if {[llength $badcolors] >= $ncolors} {
5599 set badcolors $origbad
5601 set origbad $badcolors
5602 if {[llength $badcolors] < $ncolors - 1} {
5603 foreach child $kids {
5604 if {[info exists colormap($child)]
5605 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5606 lappend badcolors $colormap($child)
5608 foreach p $parents($curview,$child) {
5609 if {[info exists colormap($p)]
5610 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5611 lappend badcolors $colormap($p)
5615 if {[llength $badcolors] >= $ncolors} {
5616 set badcolors $origbad
5619 for {set i 0} {$i <= $ncolors} {incr i} {
5620 set c [lindex $colors $nextcolor]
5621 if {[incr nextcolor] >= $ncolors} {
5622 set nextcolor 0
5624 if {[lsearch -exact $badcolors $c]} break
5626 set colormap($id) $c
5629 proc bindline {t id} {
5630 global canv
5632 $canv bind $t <Enter> "lineenter %x %y $id"
5633 $canv bind $t <Motion> "linemotion %x %y $id"
5634 $canv bind $t <Leave> "lineleave $id"
5635 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5638 proc drawtags {id x xt y1} {
5639 global idtags idheads idotherrefs mainhead
5640 global linespc lthickness
5641 global canv rowtextx curview fgcolor bgcolor ctxbut
5643 set marks {}
5644 set ntags 0
5645 set nheads 0
5646 if {[info exists idtags($id)]} {
5647 set marks $idtags($id)
5648 set ntags [llength $marks]
5650 if {[info exists idheads($id)]} {
5651 set marks [concat $marks $idheads($id)]
5652 set nheads [llength $idheads($id)]
5654 if {[info exists idotherrefs($id)]} {
5655 set marks [concat $marks $idotherrefs($id)]
5657 if {$marks eq {}} {
5658 return $xt
5661 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5662 set yt [expr {$y1 - 0.5 * $linespc}]
5663 set yb [expr {$yt + $linespc - 1}]
5664 set xvals {}
5665 set wvals {}
5666 set i -1
5667 foreach tag $marks {
5668 incr i
5669 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5670 set wid [font measure mainfontbold $tag]
5671 } else {
5672 set wid [font measure mainfont $tag]
5674 lappend xvals $xt
5675 lappend wvals $wid
5676 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5678 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5679 -width $lthickness -fill black -tags tag.$id]
5680 $canv lower $t
5681 foreach tag $marks x $xvals wid $wvals {
5682 set xl [expr {$x + $delta}]
5683 set xr [expr {$x + $delta + $wid + $lthickness}]
5684 set font mainfont
5685 if {[incr ntags -1] >= 0} {
5686 # draw a tag
5687 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5688 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5689 -width 1 -outline black -fill yellow -tags tag.$id]
5690 $canv bind $t <1> [list showtag $tag 1]
5691 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5692 } else {
5693 # draw a head or other ref
5694 if {[incr nheads -1] >= 0} {
5695 set col green
5696 if {$tag eq $mainhead} {
5697 set font mainfontbold
5699 } else {
5700 set col "#ddddff"
5702 set xl [expr {$xl - $delta/2}]
5703 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5704 -width 1 -outline black -fill $col -tags tag.$id
5705 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5706 set rwid [font measure mainfont $remoteprefix]
5707 set xi [expr {$x + 1}]
5708 set yti [expr {$yt + 1}]
5709 set xri [expr {$x + $rwid}]
5710 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5711 -width 0 -fill "#ffddaa" -tags tag.$id
5714 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5715 -font $font -tags [list tag.$id text]]
5716 if {$ntags >= 0} {
5717 $canv bind $t <1> [list showtag $tag 1]
5718 } elseif {$nheads >= 0} {
5719 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5722 return $xt
5725 proc xcoord {i level ln} {
5726 global canvx0 xspc1 xspc2
5728 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5729 if {$i > 0 && $i == $level} {
5730 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5731 } elseif {$i > $level} {
5732 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5734 return $x
5737 proc show_status {msg} {
5738 global canv fgcolor
5740 clear_display
5741 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5742 -tags text -fill $fgcolor
5745 # Don't change the text pane cursor if it is currently the hand cursor,
5746 # showing that we are over a sha1 ID link.
5747 proc settextcursor {c} {
5748 global ctext curtextcursor
5750 if {[$ctext cget -cursor] == $curtextcursor} {
5751 $ctext config -cursor $c
5753 set curtextcursor $c
5756 proc nowbusy {what {name {}}} {
5757 global isbusy busyname statusw
5759 if {[array names isbusy] eq {}} {
5760 . config -cursor watch
5761 settextcursor watch
5763 set isbusy($what) 1
5764 set busyname($what) $name
5765 if {$name ne {}} {
5766 $statusw conf -text $name
5770 proc notbusy {what} {
5771 global isbusy maincursor textcursor busyname statusw
5773 catch {
5774 unset isbusy($what)
5775 if {$busyname($what) ne {} &&
5776 [$statusw cget -text] eq $busyname($what)} {
5777 $statusw conf -text {}
5780 if {[array names isbusy] eq {}} {
5781 . config -cursor $maincursor
5782 settextcursor $textcursor
5786 proc findmatches {f} {
5787 global findtype findstring
5788 if {$findtype == [mc "Regexp"]} {
5789 set matches [regexp -indices -all -inline $findstring $f]
5790 } else {
5791 set fs $findstring
5792 if {$findtype == [mc "IgnCase"]} {
5793 set f [string tolower $f]
5794 set fs [string tolower $fs]
5796 set matches {}
5797 set i 0
5798 set l [string length $fs]
5799 while {[set j [string first $fs $f $i]] >= 0} {
5800 lappend matches [list $j [expr {$j+$l-1}]]
5801 set i [expr {$j + $l}]
5804 return $matches
5807 proc dofind {{dirn 1} {wrap 1}} {
5808 global findstring findstartline findcurline selectedline numcommits
5809 global gdttype filehighlight fh_serial find_dirn findallowwrap
5811 if {[info exists find_dirn]} {
5812 if {$find_dirn == $dirn} return
5813 stopfinding
5815 focus .
5816 if {$findstring eq {} || $numcommits == 0} return
5817 if {$selectedline eq {}} {
5818 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5819 } else {
5820 set findstartline $selectedline
5822 set findcurline $findstartline
5823 nowbusy finding [mc "Searching"]
5824 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5825 after cancel do_file_hl $fh_serial
5826 do_file_hl $fh_serial
5828 set find_dirn $dirn
5829 set findallowwrap $wrap
5830 run findmore
5833 proc stopfinding {} {
5834 global find_dirn findcurline fprogcoord
5836 if {[info exists find_dirn]} {
5837 unset find_dirn
5838 unset findcurline
5839 notbusy finding
5840 set fprogcoord 0
5841 adjustprogress
5843 stopblaming
5846 proc findmore {} {
5847 global commitdata commitinfo numcommits findpattern findloc
5848 global findstartline findcurline findallowwrap
5849 global find_dirn gdttype fhighlights fprogcoord
5850 global curview varcorder vrownum varccommits vrowmod
5852 if {![info exists find_dirn]} {
5853 return 0
5855 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5856 set l $findcurline
5857 set moretodo 0
5858 if {$find_dirn > 0} {
5859 incr l
5860 if {$l >= $numcommits} {
5861 set l 0
5863 if {$l <= $findstartline} {
5864 set lim [expr {$findstartline + 1}]
5865 } else {
5866 set lim $numcommits
5867 set moretodo $findallowwrap
5869 } else {
5870 if {$l == 0} {
5871 set l $numcommits
5873 incr l -1
5874 if {$l >= $findstartline} {
5875 set lim [expr {$findstartline - 1}]
5876 } else {
5877 set lim -1
5878 set moretodo $findallowwrap
5881 set n [expr {($lim - $l) * $find_dirn}]
5882 if {$n > 500} {
5883 set n 500
5884 set moretodo 1
5886 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
5887 update_arcrows $curview
5889 set found 0
5890 set domore 1
5891 set ai [bsearch $vrownum($curview) $l]
5892 set a [lindex $varcorder($curview) $ai]
5893 set arow [lindex $vrownum($curview) $ai]
5894 set ids [lindex $varccommits($curview,$a)]
5895 set arowend [expr {$arow + [llength $ids]}]
5896 if {$gdttype eq [mc "containing:"]} {
5897 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5898 if {$l < $arow || $l >= $arowend} {
5899 incr ai $find_dirn
5900 set a [lindex $varcorder($curview) $ai]
5901 set arow [lindex $vrownum($curview) $ai]
5902 set ids [lindex $varccommits($curview,$a)]
5903 set arowend [expr {$arow + [llength $ids]}]
5905 set id [lindex $ids [expr {$l - $arow}]]
5906 # shouldn't happen unless git log doesn't give all the commits...
5907 if {![info exists commitdata($id)] ||
5908 ![doesmatch $commitdata($id)]} {
5909 continue
5911 if {![info exists commitinfo($id)]} {
5912 getcommit $id
5914 set info $commitinfo($id)
5915 foreach f $info ty $fldtypes {
5916 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
5917 [doesmatch $f]} {
5918 set found 1
5919 break
5922 if {$found} break
5924 } else {
5925 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
5926 if {$l < $arow || $l >= $arowend} {
5927 incr ai $find_dirn
5928 set a [lindex $varcorder($curview) $ai]
5929 set arow [lindex $vrownum($curview) $ai]
5930 set ids [lindex $varccommits($curview,$a)]
5931 set arowend [expr {$arow + [llength $ids]}]
5933 set id [lindex $ids [expr {$l - $arow}]]
5934 if {![info exists fhighlights($id)]} {
5935 # this sets fhighlights($id) to -1
5936 askfilehighlight $l $id
5938 if {$fhighlights($id) > 0} {
5939 set found $domore
5940 break
5942 if {$fhighlights($id) < 0} {
5943 if {$domore} {
5944 set domore 0
5945 set findcurline [expr {$l - $find_dirn}]
5950 if {$found || ($domore && !$moretodo)} {
5951 unset findcurline
5952 unset find_dirn
5953 notbusy finding
5954 set fprogcoord 0
5955 adjustprogress
5956 if {$found} {
5957 findselectline $l
5958 } else {
5959 bell
5961 return 0
5963 if {!$domore} {
5964 flushhighlights
5965 } else {
5966 set findcurline [expr {$l - $find_dirn}]
5968 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
5969 if {$n < 0} {
5970 incr n $numcommits
5972 set fprogcoord [expr {$n * 1.0 / $numcommits}]
5973 adjustprogress
5974 return $domore
5977 proc findselectline {l} {
5978 global findloc commentend ctext findcurline markingmatches gdttype
5980 set markingmatches 1
5981 set findcurline $l
5982 selectline $l 1
5983 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
5984 # highlight the matches in the comments
5985 set f [$ctext get 1.0 $commentend]
5986 set matches [findmatches $f]
5987 foreach match $matches {
5988 set start [lindex $match 0]
5989 set end [expr {[lindex $match 1] + 1}]
5990 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
5993 drawvisible
5996 # mark the bits of a headline or author that match a find string
5997 proc markmatches {canv l str tag matches font row} {
5998 global selectedline
6000 set bbox [$canv bbox $tag]
6001 set x0 [lindex $bbox 0]
6002 set y0 [lindex $bbox 1]
6003 set y1 [lindex $bbox 3]
6004 foreach match $matches {
6005 set start [lindex $match 0]
6006 set end [lindex $match 1]
6007 if {$start > $end} continue
6008 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6009 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6010 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6011 [expr {$x0+$xlen+2}] $y1 \
6012 -outline {} -tags [list match$l matches] -fill yellow]
6013 $canv lower $t
6014 if {$row == $selectedline} {
6015 $canv raise $t secsel
6020 proc unmarkmatches {} {
6021 global markingmatches
6023 allcanvs delete matches
6024 set markingmatches 0
6025 stopfinding
6028 proc selcanvline {w x y} {
6029 global canv canvy0 ctext linespc
6030 global rowtextx
6031 set ymax [lindex [$canv cget -scrollregion] 3]
6032 if {$ymax == {}} return
6033 set yfrac [lindex [$canv yview] 0]
6034 set y [expr {$y + $yfrac * $ymax}]
6035 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6036 if {$l < 0} {
6037 set l 0
6039 if {$w eq $canv} {
6040 set xmax [lindex [$canv cget -scrollregion] 2]
6041 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6042 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6044 unmarkmatches
6045 selectline $l 1
6048 proc commit_descriptor {p} {
6049 global commitinfo
6050 if {![info exists commitinfo($p)]} {
6051 getcommit $p
6053 set l "..."
6054 if {[llength $commitinfo($p)] > 1} {
6055 set l [lindex $commitinfo($p) 0]
6057 return "$p ($l)\n"
6060 # append some text to the ctext widget, and make any SHA1 ID
6061 # that we know about be a clickable link.
6062 proc appendwithlinks {text tags} {
6063 global ctext linknum curview
6065 set start [$ctext index "end - 1c"]
6066 $ctext insert end $text $tags
6067 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6068 foreach l $links {
6069 set s [lindex $l 0]
6070 set e [lindex $l 1]
6071 set linkid [string range $text $s $e]
6072 incr e
6073 $ctext tag delete link$linknum
6074 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6075 setlink $linkid link$linknum
6076 incr linknum
6080 proc setlink {id lk} {
6081 global curview ctext pendinglinks
6083 set known 0
6084 if {[string length $id] < 40} {
6085 set matches [longid $id]
6086 if {[llength $matches] > 0} {
6087 if {[llength $matches] > 1} return
6088 set known 1
6089 set id [lindex $matches 0]
6091 } else {
6092 set known [commitinview $id $curview]
6094 if {$known} {
6095 $ctext tag conf $lk -foreground blue -underline 1
6096 $ctext tag bind $lk <1> [list selbyid $id]
6097 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6098 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6099 } else {
6100 lappend pendinglinks($id) $lk
6101 interestedin $id {makelink %P}
6105 proc makelink {id} {
6106 global pendinglinks
6108 if {![info exists pendinglinks($id)]} return
6109 foreach lk $pendinglinks($id) {
6110 setlink $id $lk
6112 unset pendinglinks($id)
6115 proc linkcursor {w inc} {
6116 global linkentercount curtextcursor
6118 if {[incr linkentercount $inc] > 0} {
6119 $w configure -cursor hand2
6120 } else {
6121 $w configure -cursor $curtextcursor
6122 if {$linkentercount < 0} {
6123 set linkentercount 0
6128 proc viewnextline {dir} {
6129 global canv linespc
6131 $canv delete hover
6132 set ymax [lindex [$canv cget -scrollregion] 3]
6133 set wnow [$canv yview]
6134 set wtop [expr {[lindex $wnow 0] * $ymax}]
6135 set newtop [expr {$wtop + $dir * $linespc}]
6136 if {$newtop < 0} {
6137 set newtop 0
6138 } elseif {$newtop > $ymax} {
6139 set newtop $ymax
6141 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6144 # add a list of tag or branch names at position pos
6145 # returns the number of names inserted
6146 proc appendrefs {pos ids var} {
6147 global ctext linknum curview $var maxrefs
6149 if {[catch {$ctext index $pos}]} {
6150 return 0
6152 $ctext conf -state normal
6153 $ctext delete $pos "$pos lineend"
6154 set tags {}
6155 foreach id $ids {
6156 foreach tag [set $var\($id\)] {
6157 lappend tags [list $tag $id]
6160 if {[llength $tags] > $maxrefs} {
6161 $ctext insert $pos "many ([llength $tags])"
6162 } else {
6163 set tags [lsort -index 0 -decreasing $tags]
6164 set sep {}
6165 foreach ti $tags {
6166 set id [lindex $ti 1]
6167 set lk link$linknum
6168 incr linknum
6169 $ctext tag delete $lk
6170 $ctext insert $pos $sep
6171 $ctext insert $pos [lindex $ti 0] $lk
6172 setlink $id $lk
6173 set sep ", "
6176 $ctext conf -state disabled
6177 return [llength $tags]
6180 # called when we have finished computing the nearby tags
6181 proc dispneartags {delay} {
6182 global selectedline currentid showneartags tagphase
6184 if {$selectedline eq {} || !$showneartags} return
6185 after cancel dispnexttag
6186 if {$delay} {
6187 after 200 dispnexttag
6188 set tagphase -1
6189 } else {
6190 after idle dispnexttag
6191 set tagphase 0
6195 proc dispnexttag {} {
6196 global selectedline currentid showneartags tagphase ctext
6198 if {$selectedline eq {} || !$showneartags} return
6199 switch -- $tagphase {
6201 set dtags [desctags $currentid]
6202 if {$dtags ne {}} {
6203 appendrefs precedes $dtags idtags
6207 set atags [anctags $currentid]
6208 if {$atags ne {}} {
6209 appendrefs follows $atags idtags
6213 set dheads [descheads $currentid]
6214 if {$dheads ne {}} {
6215 if {[appendrefs branch $dheads idheads] > 1
6216 && [$ctext get "branch -3c"] eq "h"} {
6217 # turn "Branch" into "Branches"
6218 $ctext conf -state normal
6219 $ctext insert "branch -2c" "es"
6220 $ctext conf -state disabled
6225 if {[incr tagphase] <= 2} {
6226 after idle dispnexttag
6230 proc make_secsel {l} {
6231 global linehtag linentag linedtag canv canv2 canv3
6233 if {![info exists linehtag($l)]} return
6234 $canv delete secsel
6235 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6236 -tags secsel -fill [$canv cget -selectbackground]]
6237 $canv lower $t
6238 $canv2 delete secsel
6239 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6240 -tags secsel -fill [$canv2 cget -selectbackground]]
6241 $canv2 lower $t
6242 $canv3 delete secsel
6243 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6244 -tags secsel -fill [$canv3 cget -selectbackground]]
6245 $canv3 lower $t
6248 proc selectline {l isnew {desired_loc {}}} {
6249 global canv ctext commitinfo selectedline
6250 global canvy0 linespc parents children curview
6251 global currentid sha1entry
6252 global commentend idtags linknum
6253 global mergemax numcommits pending_select
6254 global cmitmode showneartags allcommits
6255 global targetrow targetid lastscrollrows
6256 global autoselect jump_to_here
6258 catch {unset pending_select}
6259 $canv delete hover
6260 normalline
6261 unsel_reflist
6262 stopfinding
6263 if {$l < 0 || $l >= $numcommits} return
6264 set id [commitonrow $l]
6265 set targetid $id
6266 set targetrow $l
6267 set selectedline $l
6268 set currentid $id
6269 if {$lastscrollrows < $numcommits} {
6270 setcanvscroll
6273 set y [expr {$canvy0 + $l * $linespc}]
6274 set ymax [lindex [$canv cget -scrollregion] 3]
6275 set ytop [expr {$y - $linespc - 1}]
6276 set ybot [expr {$y + $linespc + 1}]
6277 set wnow [$canv yview]
6278 set wtop [expr {[lindex $wnow 0] * $ymax}]
6279 set wbot [expr {[lindex $wnow 1] * $ymax}]
6280 set wh [expr {$wbot - $wtop}]
6281 set newtop $wtop
6282 if {$ytop < $wtop} {
6283 if {$ybot < $wtop} {
6284 set newtop [expr {$y - $wh / 2.0}]
6285 } else {
6286 set newtop $ytop
6287 if {$newtop > $wtop - $linespc} {
6288 set newtop [expr {$wtop - $linespc}]
6291 } elseif {$ybot > $wbot} {
6292 if {$ytop > $wbot} {
6293 set newtop [expr {$y - $wh / 2.0}]
6294 } else {
6295 set newtop [expr {$ybot - $wh}]
6296 if {$newtop < $wtop + $linespc} {
6297 set newtop [expr {$wtop + $linespc}]
6301 if {$newtop != $wtop} {
6302 if {$newtop < 0} {
6303 set newtop 0
6305 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6306 drawvisible
6309 make_secsel $l
6311 if {$isnew} {
6312 addtohistory [list selbyid $id]
6315 $sha1entry delete 0 end
6316 $sha1entry insert 0 $id
6317 if {$autoselect} {
6318 $sha1entry selection from 0
6319 $sha1entry selection to end
6321 rhighlight_sel $id
6323 $ctext conf -state normal
6324 clear_ctext
6325 set linknum 0
6326 if {![info exists commitinfo($id)]} {
6327 getcommit $id
6329 set info $commitinfo($id)
6330 set date [formatdate [lindex $info 2]]
6331 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6332 set date [formatdate [lindex $info 4]]
6333 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6334 if {[info exists idtags($id)]} {
6335 $ctext insert end [mc "Tags:"]
6336 foreach tag $idtags($id) {
6337 $ctext insert end " $tag"
6339 $ctext insert end "\n"
6342 set headers {}
6343 set olds $parents($curview,$id)
6344 if {[llength $olds] > 1} {
6345 set np 0
6346 foreach p $olds {
6347 if {$np >= $mergemax} {
6348 set tag mmax
6349 } else {
6350 set tag m$np
6352 $ctext insert end "[mc "Parent"]: " $tag
6353 appendwithlinks [commit_descriptor $p] {}
6354 incr np
6356 } else {
6357 foreach p $olds {
6358 append headers "[mc "Parent"]: [commit_descriptor $p]"
6362 foreach c $children($curview,$id) {
6363 append headers "[mc "Child"]: [commit_descriptor $c]"
6366 # make anything that looks like a SHA1 ID be a clickable link
6367 appendwithlinks $headers {}
6368 if {$showneartags} {
6369 if {![info exists allcommits]} {
6370 getallcommits
6372 $ctext insert end "[mc "Branch"]: "
6373 $ctext mark set branch "end -1c"
6374 $ctext mark gravity branch left
6375 $ctext insert end "\n[mc "Follows"]: "
6376 $ctext mark set follows "end -1c"
6377 $ctext mark gravity follows left
6378 $ctext insert end "\n[mc "Precedes"]: "
6379 $ctext mark set precedes "end -1c"
6380 $ctext mark gravity precedes left
6381 $ctext insert end "\n"
6382 dispneartags 1
6384 $ctext insert end "\n"
6385 set comment [lindex $info 5]
6386 if {[string first "\r" $comment] >= 0} {
6387 set comment [string map {"\r" "\n "} $comment]
6389 appendwithlinks $comment {comment}
6391 $ctext tag remove found 1.0 end
6392 $ctext conf -state disabled
6393 set commentend [$ctext index "end - 1c"]
6395 set jump_to_here $desired_loc
6396 init_flist [mc "Comments"]
6397 if {$cmitmode eq "tree"} {
6398 gettree $id
6399 } elseif {[llength $olds] <= 1} {
6400 startdiff $id
6401 } else {
6402 mergediff $id
6406 proc selfirstline {} {
6407 unmarkmatches
6408 selectline 0 1
6411 proc sellastline {} {
6412 global numcommits
6413 unmarkmatches
6414 set l [expr {$numcommits - 1}]
6415 selectline $l 1
6418 proc selnextline {dir} {
6419 global selectedline
6420 focus .
6421 if {$selectedline eq {}} return
6422 set l [expr {$selectedline + $dir}]
6423 unmarkmatches
6424 selectline $l 1
6427 proc selnextpage {dir} {
6428 global canv linespc selectedline numcommits
6430 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6431 if {$lpp < 1} {
6432 set lpp 1
6434 allcanvs yview scroll [expr {$dir * $lpp}] units
6435 drawvisible
6436 if {$selectedline eq {}} return
6437 set l [expr {$selectedline + $dir * $lpp}]
6438 if {$l < 0} {
6439 set l 0
6440 } elseif {$l >= $numcommits} {
6441 set l [expr $numcommits - 1]
6443 unmarkmatches
6444 selectline $l 1
6447 proc unselectline {} {
6448 global selectedline currentid
6450 set selectedline {}
6451 catch {unset currentid}
6452 allcanvs delete secsel
6453 rhighlight_none
6456 proc reselectline {} {
6457 global selectedline
6459 if {$selectedline ne {}} {
6460 selectline $selectedline 0
6464 proc addtohistory {cmd} {
6465 global history historyindex curview
6467 set elt [list $curview $cmd]
6468 if {$historyindex > 0
6469 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6470 return
6473 if {$historyindex < [llength $history]} {
6474 set history [lreplace $history $historyindex end $elt]
6475 } else {
6476 lappend history $elt
6478 incr historyindex
6479 if {$historyindex > 1} {
6480 .tf.bar.leftbut conf -state normal
6481 } else {
6482 .tf.bar.leftbut conf -state disabled
6484 .tf.bar.rightbut conf -state disabled
6487 proc godo {elt} {
6488 global curview
6490 set view [lindex $elt 0]
6491 set cmd [lindex $elt 1]
6492 if {$curview != $view} {
6493 showview $view
6495 eval $cmd
6498 proc goback {} {
6499 global history historyindex
6500 focus .
6502 if {$historyindex > 1} {
6503 incr historyindex -1
6504 godo [lindex $history [expr {$historyindex - 1}]]
6505 .tf.bar.rightbut conf -state normal
6507 if {$historyindex <= 1} {
6508 .tf.bar.leftbut conf -state disabled
6512 proc goforw {} {
6513 global history historyindex
6514 focus .
6516 if {$historyindex < [llength $history]} {
6517 set cmd [lindex $history $historyindex]
6518 incr historyindex
6519 godo $cmd
6520 .tf.bar.leftbut conf -state normal
6522 if {$historyindex >= [llength $history]} {
6523 .tf.bar.rightbut conf -state disabled
6527 proc gettree {id} {
6528 global treefilelist treeidlist diffids diffmergeid treepending
6529 global nullid nullid2
6531 set diffids $id
6532 catch {unset diffmergeid}
6533 if {![info exists treefilelist($id)]} {
6534 if {![info exists treepending]} {
6535 if {$id eq $nullid} {
6536 set cmd [list | git ls-files]
6537 } elseif {$id eq $nullid2} {
6538 set cmd [list | git ls-files --stage -t]
6539 } else {
6540 set cmd [list | git ls-tree -r $id]
6542 if {[catch {set gtf [open $cmd r]}]} {
6543 return
6545 set treepending $id
6546 set treefilelist($id) {}
6547 set treeidlist($id) {}
6548 fconfigure $gtf -blocking 0 -encoding binary
6549 filerun $gtf [list gettreeline $gtf $id]
6551 } else {
6552 setfilelist $id
6556 proc gettreeline {gtf id} {
6557 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6559 set nl 0
6560 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6561 if {$diffids eq $nullid} {
6562 set fname $line
6563 } else {
6564 set i [string first "\t" $line]
6565 if {$i < 0} continue
6566 set fname [string range $line [expr {$i+1}] end]
6567 set line [string range $line 0 [expr {$i-1}]]
6568 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6569 set sha1 [lindex $line 2]
6570 lappend treeidlist($id) $sha1
6572 if {[string index $fname 0] eq "\""} {
6573 set fname [lindex $fname 0]
6575 set fname [encoding convertfrom $fname]
6576 lappend treefilelist($id) $fname
6578 if {![eof $gtf]} {
6579 return [expr {$nl >= 1000? 2: 1}]
6581 close $gtf
6582 unset treepending
6583 if {$cmitmode ne "tree"} {
6584 if {![info exists diffmergeid]} {
6585 gettreediffs $diffids
6587 } elseif {$id ne $diffids} {
6588 gettree $diffids
6589 } else {
6590 setfilelist $id
6592 return 0
6595 proc showfile {f} {
6596 global treefilelist treeidlist diffids nullid nullid2
6597 global ctext_file_names ctext_file_lines
6598 global ctext commentend
6600 set i [lsearch -exact $treefilelist($diffids) $f]
6601 if {$i < 0} {
6602 puts "oops, $f not in list for id $diffids"
6603 return
6605 if {$diffids eq $nullid} {
6606 if {[catch {set bf [open $f r]} err]} {
6607 puts "oops, can't read $f: $err"
6608 return
6610 } else {
6611 set blob [lindex $treeidlist($diffids) $i]
6612 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6613 puts "oops, error reading blob $blob: $err"
6614 return
6617 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6618 filerun $bf [list getblobline $bf $diffids]
6619 $ctext config -state normal
6620 clear_ctext $commentend
6621 lappend ctext_file_names $f
6622 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6623 $ctext insert end "\n"
6624 $ctext insert end "$f\n" filesep
6625 $ctext config -state disabled
6626 $ctext yview $commentend
6627 settabs 0
6630 proc getblobline {bf id} {
6631 global diffids cmitmode ctext
6633 if {$id ne $diffids || $cmitmode ne "tree"} {
6634 catch {close $bf}
6635 return 0
6637 $ctext config -state normal
6638 set nl 0
6639 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6640 $ctext insert end "$line\n"
6642 if {[eof $bf]} {
6643 global jump_to_here ctext_file_names commentend
6645 # delete last newline
6646 $ctext delete "end - 2c" "end - 1c"
6647 close $bf
6648 if {$jump_to_here ne {} &&
6649 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6650 set lnum [expr {[lindex $jump_to_here 1] +
6651 [lindex [split $commentend .] 0]}]
6652 mark_ctext_line $lnum
6654 return 0
6656 $ctext config -state disabled
6657 return [expr {$nl >= 1000? 2: 1}]
6660 proc mark_ctext_line {lnum} {
6661 global ctext
6663 $ctext tag delete omark
6664 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6665 $ctext tag conf omark -background "#e0e0ff"
6666 $ctext see $lnum.0
6669 proc mergediff {id} {
6670 global diffmergeid mdifffd
6671 global diffids treediffs
6672 global parents
6673 global diffcontext
6674 global diffencoding
6675 global limitdiffs vfilelimit curview
6676 global targetline
6678 set diffmergeid $id
6679 set diffids $id
6680 set treediffs($id) {}
6681 set targetline {}
6682 # this doesn't seem to actually affect anything...
6683 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6684 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6685 set cmd [concat $cmd -- $vfilelimit($curview)]
6687 if {[catch {set mdf [open $cmd r]} err]} {
6688 error_popup "[mc "Error getting merge diffs:"] $err"
6689 return
6691 fconfigure $mdf -blocking 0 -encoding binary
6692 set mdifffd($id) $mdf
6693 set np [llength $parents($curview,$id)]
6694 set diffencoding [get_path_encoding {}]
6695 settabs $np
6696 filerun $mdf [list getmergediffline $mdf $id $np]
6699 proc getmergediffline {mdf id np} {
6700 global diffmergeid ctext cflist mergemax
6701 global difffilestart mdifffd treediffs
6702 global ctext_file_names ctext_file_lines
6703 global diffencoding jump_to_here targetline diffline
6705 $ctext conf -state normal
6706 set nr 0
6707 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6708 if {![info exists diffmergeid] || $id != $diffmergeid
6709 || $mdf != $mdifffd($id)} {
6710 close $mdf
6711 return 0
6713 if {[regexp {^diff --cc (.*)} $line match fname]} {
6714 # start of a new file
6715 set fname [encoding convertfrom $fname]
6716 $ctext insert end "\n"
6717 set here [$ctext index "end - 1c"]
6718 lappend difffilestart $here
6719 lappend treediffs($id) $fname
6720 add_flist [list $fname]
6721 lappend ctext_file_names $fname
6722 lappend ctext_file_lines [lindex [split $here "."] 0]
6723 set diffencoding [get_path_encoding $fname]
6724 set l [expr {(78 - [string length $fname]) / 2}]
6725 set pad [string range "----------------------------------------" 1 $l]
6726 $ctext insert end "$pad $fname $pad\n" filesep
6727 set targetline {}
6728 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
6729 set targetline [lindex $jump_to_here 1]
6731 set diffline 0
6732 } elseif {[regexp {^@@} $line]} {
6733 set line [encoding convertfrom $diffencoding $line]
6734 $ctext insert end "$line\n" hunksep
6735 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
6736 set diffline $nl
6738 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6739 # do nothing
6740 } else {
6741 set line [encoding convertfrom $diffencoding $line]
6742 # parse the prefix - one ' ', '-' or '+' for each parent
6743 set spaces {}
6744 set minuses {}
6745 set pluses {}
6746 set isbad 0
6747 for {set j 0} {$j < $np} {incr j} {
6748 set c [string range $line $j $j]
6749 if {$c == " "} {
6750 lappend spaces $j
6751 } elseif {$c == "-"} {
6752 lappend minuses $j
6753 } elseif {$c == "+"} {
6754 lappend pluses $j
6755 } else {
6756 set isbad 1
6757 break
6760 set tags {}
6761 set num {}
6762 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6763 # line doesn't appear in result, parents in $minuses have the line
6764 set num [lindex $minuses 0]
6765 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6766 # line appears in result, parents in $pluses don't have the line
6767 lappend tags mresult
6768 set num [lindex $spaces 0]
6770 if {$num ne {}} {
6771 if {$num >= $mergemax} {
6772 set num "max"
6774 lappend tags m$num
6776 $ctext insert end "$line\n" $tags
6777 if {$targetline ne {} && $minuses eq {}} {
6778 if {$diffline == $targetline} {
6779 set here [$ctext index "end - 1 line"]
6780 mark_ctext_line [lindex [split $here .] 0]
6781 set targetline {}
6782 } else {
6783 incr diffline
6788 $ctext conf -state disabled
6789 if {[eof $mdf]} {
6790 close $mdf
6791 return 0
6793 return [expr {$nr >= 1000? 2: 1}]
6796 proc startdiff {ids} {
6797 global treediffs diffids treepending diffmergeid nullid nullid2
6799 settabs 1
6800 set diffids $ids
6801 catch {unset diffmergeid}
6802 if {![info exists treediffs($ids)] ||
6803 [lsearch -exact $ids $nullid] >= 0 ||
6804 [lsearch -exact $ids $nullid2] >= 0} {
6805 if {![info exists treepending]} {
6806 gettreediffs $ids
6808 } else {
6809 addtocflist $ids
6813 proc path_filter {filter name} {
6814 foreach p $filter {
6815 set l [string length $p]
6816 if {[string index $p end] eq "/"} {
6817 if {[string compare -length $l $p $name] == 0} {
6818 return 1
6820 } else {
6821 if {[string compare -length $l $p $name] == 0 &&
6822 ([string length $name] == $l ||
6823 [string index $name $l] eq "/")} {
6824 return 1
6828 return 0
6831 proc addtocflist {ids} {
6832 global treediffs
6834 add_flist $treediffs($ids)
6835 getblobdiffs $ids
6838 proc diffcmd {ids flags} {
6839 global nullid nullid2
6841 set i [lsearch -exact $ids $nullid]
6842 set j [lsearch -exact $ids $nullid2]
6843 if {$i >= 0} {
6844 if {[llength $ids] > 1 && $j < 0} {
6845 # comparing working directory with some specific revision
6846 set cmd [concat | git diff-index $flags]
6847 if {$i == 0} {
6848 lappend cmd -R [lindex $ids 1]
6849 } else {
6850 lappend cmd [lindex $ids 0]
6852 } else {
6853 # comparing working directory with index
6854 set cmd [concat | git diff-files $flags]
6855 if {$j == 1} {
6856 lappend cmd -R
6859 } elseif {$j >= 0} {
6860 set cmd [concat | git diff-index --cached $flags]
6861 if {[llength $ids] > 1} {
6862 # comparing index with specific revision
6863 if {$i == 0} {
6864 lappend cmd -R [lindex $ids 1]
6865 } else {
6866 lappend cmd [lindex $ids 0]
6868 } else {
6869 # comparing index with HEAD
6870 lappend cmd HEAD
6872 } else {
6873 set cmd [concat | git diff-tree -r $flags $ids]
6875 return $cmd
6878 proc gettreediffs {ids} {
6879 global treediff treepending
6881 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
6883 set treepending $ids
6884 set treediff {}
6885 fconfigure $gdtf -blocking 0 -encoding binary
6886 filerun $gdtf [list gettreediffline $gdtf $ids]
6889 proc gettreediffline {gdtf ids} {
6890 global treediff treediffs treepending diffids diffmergeid
6891 global cmitmode vfilelimit curview limitdiffs perfile_attrs
6893 set nr 0
6894 set sublist {}
6895 set max 1000
6896 if {$perfile_attrs} {
6897 # cache_gitattr is slow, and even slower on win32 where we
6898 # have to invoke it for only about 30 paths at a time
6899 set max 500
6900 if {[tk windowingsystem] == "win32"} {
6901 set max 120
6904 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
6905 set i [string first "\t" $line]
6906 if {$i >= 0} {
6907 set file [string range $line [expr {$i+1}] end]
6908 if {[string index $file 0] eq "\""} {
6909 set file [lindex $file 0]
6911 set file [encoding convertfrom $file]
6912 lappend treediff $file
6913 lappend sublist $file
6916 if {$perfile_attrs} {
6917 cache_gitattr encoding $sublist
6919 if {![eof $gdtf]} {
6920 return [expr {$nr >= $max? 2: 1}]
6922 close $gdtf
6923 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6924 set flist {}
6925 foreach f $treediff {
6926 if {[path_filter $vfilelimit($curview) $f]} {
6927 lappend flist $f
6930 set treediffs($ids) $flist
6931 } else {
6932 set treediffs($ids) $treediff
6934 unset treepending
6935 if {$cmitmode eq "tree"} {
6936 gettree $diffids
6937 } elseif {$ids != $diffids} {
6938 if {![info exists diffmergeid]} {
6939 gettreediffs $diffids
6941 } else {
6942 addtocflist $ids
6944 return 0
6947 # empty string or positive integer
6948 proc diffcontextvalidate {v} {
6949 return [regexp {^(|[1-9][0-9]*)$} $v]
6952 proc diffcontextchange {n1 n2 op} {
6953 global diffcontextstring diffcontext
6955 if {[string is integer -strict $diffcontextstring]} {
6956 if {$diffcontextstring > 0} {
6957 set diffcontext $diffcontextstring
6958 reselectline
6963 proc changeignorespace {} {
6964 reselectline
6967 proc getblobdiffs {ids} {
6968 global blobdifffd diffids env
6969 global diffinhdr treediffs
6970 global diffcontext
6971 global ignorespace
6972 global limitdiffs vfilelimit curview
6973 global diffencoding targetline
6975 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
6976 if {$ignorespace} {
6977 append cmd " -w"
6979 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6980 set cmd [concat $cmd -- $vfilelimit($curview)]
6982 if {[catch {set bdf [open $cmd r]} err]} {
6983 puts "error getting diffs: $err"
6984 return
6986 set targetline {}
6987 set diffinhdr 0
6988 set diffencoding [get_path_encoding {}]
6989 fconfigure $bdf -blocking 0 -encoding binary
6990 set blobdifffd($ids) $bdf
6991 filerun $bdf [list getblobdiffline $bdf $diffids]
6994 proc setinlist {var i val} {
6995 global $var
6997 while {[llength [set $var]] < $i} {
6998 lappend $var {}
7000 if {[llength [set $var]] == $i} {
7001 lappend $var $val
7002 } else {
7003 lset $var $i $val
7007 proc makediffhdr {fname ids} {
7008 global ctext curdiffstart treediffs
7009 global ctext_file_names jump_to_here targetline diffline
7011 set i [lsearch -exact $treediffs($ids) $fname]
7012 if {$i >= 0} {
7013 setinlist difffilestart $i $curdiffstart
7015 set ctext_file_names [lreplace $ctext_file_names end end $fname]
7016 set l [expr {(78 - [string length $fname]) / 2}]
7017 set pad [string range "----------------------------------------" 1 $l]
7018 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7019 set targetline {}
7020 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7021 set targetline [lindex $jump_to_here 1]
7023 set diffline 0
7026 proc getblobdiffline {bdf ids} {
7027 global diffids blobdifffd ctext curdiffstart
7028 global diffnexthead diffnextnote difffilestart
7029 global ctext_file_names ctext_file_lines
7030 global diffinhdr treediffs
7031 global diffencoding jump_to_here targetline diffline
7033 set nr 0
7034 $ctext conf -state normal
7035 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7036 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7037 close $bdf
7038 return 0
7040 if {![string compare -length 11 "diff --git " $line]} {
7041 # trim off "diff --git "
7042 set line [string range $line 11 end]
7043 set diffinhdr 1
7044 # start of a new file
7045 $ctext insert end "\n"
7046 set curdiffstart [$ctext index "end - 1c"]
7047 lappend ctext_file_names ""
7048 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7049 $ctext insert end "\n" filesep
7050 # If the name hasn't changed the length will be odd,
7051 # the middle char will be a space, and the two bits either
7052 # side will be a/name and b/name, or "a/name" and "b/name".
7053 # If the name has changed we'll get "rename from" and
7054 # "rename to" or "copy from" and "copy to" lines following this,
7055 # and we'll use them to get the filenames.
7056 # This complexity is necessary because spaces in the filename(s)
7057 # don't get escaped.
7058 set l [string length $line]
7059 set i [expr {$l / 2}]
7060 if {!(($l & 1) && [string index $line $i] eq " " &&
7061 [string range $line 2 [expr {$i - 1}]] eq \
7062 [string range $line [expr {$i + 3}] end])} {
7063 continue
7065 # unescape if quoted and chop off the a/ from the front
7066 if {[string index $line 0] eq "\""} {
7067 set fname [string range [lindex $line 0] 2 end]
7068 } else {
7069 set fname [string range $line 2 [expr {$i - 1}]]
7071 set fname [encoding convertfrom $fname]
7072 set diffencoding [get_path_encoding $fname]
7073 makediffhdr $fname $ids
7075 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
7076 $line match f1l f1c f2l f2c rest]} {
7077 set line [encoding convertfrom $diffencoding $line]
7078 $ctext insert end "$line\n" hunksep
7079 set diffinhdr 0
7080 set diffline $f2l
7082 } elseif {$diffinhdr} {
7083 if {![string compare -length 12 "rename from " $line]} {
7084 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7085 if {[string index $fname 0] eq "\""} {
7086 set fname [lindex $fname 0]
7088 set fname [encoding convertfrom $fname]
7089 set i [lsearch -exact $treediffs($ids) $fname]
7090 if {$i >= 0} {
7091 setinlist difffilestart $i $curdiffstart
7093 } elseif {![string compare -length 10 $line "rename to "] ||
7094 ![string compare -length 8 $line "copy to "]} {
7095 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7096 if {[string index $fname 0] eq "\""} {
7097 set fname [lindex $fname 0]
7099 set fname [encoding convertfrom $fname]
7100 set diffencoding [get_path_encoding $fname]
7101 makediffhdr $fname $ids
7102 } elseif {[string compare -length 3 $line "---"] == 0} {
7103 # do nothing
7104 continue
7105 } elseif {[string compare -length 3 $line "+++"] == 0} {
7106 set diffinhdr 0
7107 continue
7109 $ctext insert end "$line\n" filesep
7111 } else {
7112 set line [encoding convertfrom $diffencoding $line]
7113 set x [string range $line 0 0]
7114 set here [$ctext index "end - 1 chars"]
7115 if {$x == "-" || $x == "+"} {
7116 set tag [expr {$x == "+"}]
7117 $ctext insert end "$line\n" d$tag
7118 } elseif {$x == " "} {
7119 $ctext insert end "$line\n"
7120 } else {
7121 # "\ No newline at end of file",
7122 # or something else we don't recognize
7123 $ctext insert end "$line\n" hunksep
7125 if {$targetline ne {} && ($x eq " " || $x eq "+")} {
7126 if {$diffline == $targetline} {
7127 mark_ctext_line [lindex [split $here .] 0]
7128 set targetline {}
7129 } else {
7130 incr diffline
7135 $ctext conf -state disabled
7136 if {[eof $bdf]} {
7137 close $bdf
7138 return 0
7140 return [expr {$nr >= 1000? 2: 1}]
7143 proc changediffdisp {} {
7144 global ctext diffelide
7146 $ctext tag conf d0 -elide [lindex $diffelide 0]
7147 $ctext tag conf d1 -elide [lindex $diffelide 1]
7150 proc highlightfile {loc cline} {
7151 global ctext cflist cflist_top
7153 $ctext yview $loc
7154 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7155 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7156 $cflist see $cline.0
7157 set cflist_top $cline
7160 proc prevfile {} {
7161 global difffilestart ctext cmitmode
7163 if {$cmitmode eq "tree"} return
7164 set prev 0.0
7165 set prevline 1
7166 set here [$ctext index @0,0]
7167 foreach loc $difffilestart {
7168 if {[$ctext compare $loc >= $here]} {
7169 highlightfile $prev $prevline
7170 return
7172 set prev $loc
7173 incr prevline
7175 highlightfile $prev $prevline
7178 proc nextfile {} {
7179 global difffilestart ctext cmitmode
7181 if {$cmitmode eq "tree"} return
7182 set here [$ctext index @0,0]
7183 set line 1
7184 foreach loc $difffilestart {
7185 incr line
7186 if {[$ctext compare $loc > $here]} {
7187 highlightfile $loc $line
7188 return
7193 proc clear_ctext {{first 1.0}} {
7194 global ctext smarktop smarkbot
7195 global ctext_file_names ctext_file_lines
7196 global pendinglinks
7198 set l [lindex [split $first .] 0]
7199 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7200 set smarktop $l
7202 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7203 set smarkbot $l
7205 $ctext delete $first end
7206 if {$first eq "1.0"} {
7207 catch {unset pendinglinks}
7209 set ctext_file_names {}
7210 set ctext_file_lines {}
7213 proc settabs {{firstab {}}} {
7214 global firsttabstop tabstop ctext have_tk85
7216 if {$firstab ne {} && $have_tk85} {
7217 set firsttabstop $firstab
7219 set w [font measure textfont "0"]
7220 if {$firsttabstop != 0} {
7221 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7222 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7223 } elseif {$have_tk85 || $tabstop != 8} {
7224 $ctext conf -tabs [expr {$tabstop * $w}]
7225 } else {
7226 $ctext conf -tabs {}
7230 proc incrsearch {name ix op} {
7231 global ctext searchstring searchdirn
7233 $ctext tag remove found 1.0 end
7234 if {[catch {$ctext index anchor}]} {
7235 # no anchor set, use start of selection, or of visible area
7236 set sel [$ctext tag ranges sel]
7237 if {$sel ne {}} {
7238 $ctext mark set anchor [lindex $sel 0]
7239 } elseif {$searchdirn eq "-forwards"} {
7240 $ctext mark set anchor @0,0
7241 } else {
7242 $ctext mark set anchor @0,[winfo height $ctext]
7245 if {$searchstring ne {}} {
7246 set here [$ctext search $searchdirn -- $searchstring anchor]
7247 if {$here ne {}} {
7248 $ctext see $here
7250 searchmarkvisible 1
7254 proc dosearch {} {
7255 global sstring ctext searchstring searchdirn
7257 focus $sstring
7258 $sstring icursor end
7259 set searchdirn -forwards
7260 if {$searchstring ne {}} {
7261 set sel [$ctext tag ranges sel]
7262 if {$sel ne {}} {
7263 set start "[lindex $sel 0] + 1c"
7264 } elseif {[catch {set start [$ctext index anchor]}]} {
7265 set start "@0,0"
7267 set match [$ctext search -count mlen -- $searchstring $start]
7268 $ctext tag remove sel 1.0 end
7269 if {$match eq {}} {
7270 bell
7271 return
7273 $ctext see $match
7274 set mend "$match + $mlen c"
7275 $ctext tag add sel $match $mend
7276 $ctext mark unset anchor
7280 proc dosearchback {} {
7281 global sstring ctext searchstring searchdirn
7283 focus $sstring
7284 $sstring icursor end
7285 set searchdirn -backwards
7286 if {$searchstring ne {}} {
7287 set sel [$ctext tag ranges sel]
7288 if {$sel ne {}} {
7289 set start [lindex $sel 0]
7290 } elseif {[catch {set start [$ctext index anchor]}]} {
7291 set start @0,[winfo height $ctext]
7293 set match [$ctext search -backwards -count ml -- $searchstring $start]
7294 $ctext tag remove sel 1.0 end
7295 if {$match eq {}} {
7296 bell
7297 return
7299 $ctext see $match
7300 set mend "$match + $ml c"
7301 $ctext tag add sel $match $mend
7302 $ctext mark unset anchor
7306 proc searchmark {first last} {
7307 global ctext searchstring
7309 set mend $first.0
7310 while {1} {
7311 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7312 if {$match eq {}} break
7313 set mend "$match + $mlen c"
7314 $ctext tag add found $match $mend
7318 proc searchmarkvisible {doall} {
7319 global ctext smarktop smarkbot
7321 set topline [lindex [split [$ctext index @0,0] .] 0]
7322 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7323 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7324 # no overlap with previous
7325 searchmark $topline $botline
7326 set smarktop $topline
7327 set smarkbot $botline
7328 } else {
7329 if {$topline < $smarktop} {
7330 searchmark $topline [expr {$smarktop-1}]
7331 set smarktop $topline
7333 if {$botline > $smarkbot} {
7334 searchmark [expr {$smarkbot+1}] $botline
7335 set smarkbot $botline
7340 proc scrolltext {f0 f1} {
7341 global searchstring
7343 .bleft.bottom.sb set $f0 $f1
7344 if {$searchstring ne {}} {
7345 searchmarkvisible 0
7349 proc setcoords {} {
7350 global linespc charspc canvx0 canvy0
7351 global xspc1 xspc2 lthickness
7353 set linespc [font metrics mainfont -linespace]
7354 set charspc [font measure mainfont "m"]
7355 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7356 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7357 set lthickness [expr {int($linespc / 9) + 1}]
7358 set xspc1(0) $linespc
7359 set xspc2 $linespc
7362 proc redisplay {} {
7363 global canv
7364 global selectedline
7366 set ymax [lindex [$canv cget -scrollregion] 3]
7367 if {$ymax eq {} || $ymax == 0} return
7368 set span [$canv yview]
7369 clear_display
7370 setcanvscroll
7371 allcanvs yview moveto [lindex $span 0]
7372 drawvisible
7373 if {$selectedline ne {}} {
7374 selectline $selectedline 0
7375 allcanvs yview moveto [lindex $span 0]
7379 proc parsefont {f n} {
7380 global fontattr
7382 set fontattr($f,family) [lindex $n 0]
7383 set s [lindex $n 1]
7384 if {$s eq {} || $s == 0} {
7385 set s 10
7386 } elseif {$s < 0} {
7387 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7389 set fontattr($f,size) $s
7390 set fontattr($f,weight) normal
7391 set fontattr($f,slant) roman
7392 foreach style [lrange $n 2 end] {
7393 switch -- $style {
7394 "normal" -
7395 "bold" {set fontattr($f,weight) $style}
7396 "roman" -
7397 "italic" {set fontattr($f,slant) $style}
7402 proc fontflags {f {isbold 0}} {
7403 global fontattr
7405 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7406 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7407 -slant $fontattr($f,slant)]
7410 proc fontname {f} {
7411 global fontattr
7413 set n [list $fontattr($f,family) $fontattr($f,size)]
7414 if {$fontattr($f,weight) eq "bold"} {
7415 lappend n "bold"
7417 if {$fontattr($f,slant) eq "italic"} {
7418 lappend n "italic"
7420 return $n
7423 proc incrfont {inc} {
7424 global mainfont textfont ctext canv cflist showrefstop
7425 global stopped entries fontattr
7427 unmarkmatches
7428 set s $fontattr(mainfont,size)
7429 incr s $inc
7430 if {$s < 1} {
7431 set s 1
7433 set fontattr(mainfont,size) $s
7434 font config mainfont -size $s
7435 font config mainfontbold -size $s
7436 set mainfont [fontname mainfont]
7437 set s $fontattr(textfont,size)
7438 incr s $inc
7439 if {$s < 1} {
7440 set s 1
7442 set fontattr(textfont,size) $s
7443 font config textfont -size $s
7444 font config textfontbold -size $s
7445 set textfont [fontname textfont]
7446 setcoords
7447 settabs
7448 redisplay
7451 proc clearsha1 {} {
7452 global sha1entry sha1string
7453 if {[string length $sha1string] == 40} {
7454 $sha1entry delete 0 end
7458 proc sha1change {n1 n2 op} {
7459 global sha1string currentid sha1but
7460 if {$sha1string == {}
7461 || ([info exists currentid] && $sha1string == $currentid)} {
7462 set state disabled
7463 } else {
7464 set state normal
7466 if {[$sha1but cget -state] == $state} return
7467 if {$state == "normal"} {
7468 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7469 } else {
7470 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7474 proc gotocommit {} {
7475 global sha1string tagids headids curview varcid
7477 if {$sha1string == {}
7478 || ([info exists currentid] && $sha1string == $currentid)} return
7479 if {[info exists tagids($sha1string)]} {
7480 set id $tagids($sha1string)
7481 } elseif {[info exists headids($sha1string)]} {
7482 set id $headids($sha1string)
7483 } else {
7484 set id [string tolower $sha1string]
7485 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7486 set matches [longid $id]
7487 if {$matches ne {}} {
7488 if {[llength $matches] > 1} {
7489 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7490 return
7492 set id [lindex $matches 0]
7496 if {[commitinview $id $curview]} {
7497 selectline [rowofcommit $id] 1
7498 return
7500 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7501 set msg [mc "SHA1 id %s is not known" $sha1string]
7502 } else {
7503 set msg [mc "Tag/Head %s is not known" $sha1string]
7505 error_popup $msg
7508 proc lineenter {x y id} {
7509 global hoverx hovery hoverid hovertimer
7510 global commitinfo canv
7512 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7513 set hoverx $x
7514 set hovery $y
7515 set hoverid $id
7516 if {[info exists hovertimer]} {
7517 after cancel $hovertimer
7519 set hovertimer [after 500 linehover]
7520 $canv delete hover
7523 proc linemotion {x y id} {
7524 global hoverx hovery hoverid hovertimer
7526 if {[info exists hoverid] && $id == $hoverid} {
7527 set hoverx $x
7528 set hovery $y
7529 if {[info exists hovertimer]} {
7530 after cancel $hovertimer
7532 set hovertimer [after 500 linehover]
7536 proc lineleave {id} {
7537 global hoverid hovertimer canv
7539 if {[info exists hoverid] && $id == $hoverid} {
7540 $canv delete hover
7541 if {[info exists hovertimer]} {
7542 after cancel $hovertimer
7543 unset hovertimer
7545 unset hoverid
7549 proc linehover {} {
7550 global hoverx hovery hoverid hovertimer
7551 global canv linespc lthickness
7552 global commitinfo
7554 set text [lindex $commitinfo($hoverid) 0]
7555 set ymax [lindex [$canv cget -scrollregion] 3]
7556 if {$ymax == {}} return
7557 set yfrac [lindex [$canv yview] 0]
7558 set x [expr {$hoverx + 2 * $linespc}]
7559 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7560 set x0 [expr {$x - 2 * $lthickness}]
7561 set y0 [expr {$y - 2 * $lthickness}]
7562 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7563 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7564 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7565 -fill \#ffff80 -outline black -width 1 -tags hover]
7566 $canv raise $t
7567 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7568 -font mainfont]
7569 $canv raise $t
7572 proc clickisonarrow {id y} {
7573 global lthickness
7575 set ranges [rowranges $id]
7576 set thresh [expr {2 * $lthickness + 6}]
7577 set n [expr {[llength $ranges] - 1}]
7578 for {set i 1} {$i < $n} {incr i} {
7579 set row [lindex $ranges $i]
7580 if {abs([yc $row] - $y) < $thresh} {
7581 return $i
7584 return {}
7587 proc arrowjump {id n y} {
7588 global canv
7590 # 1 <-> 2, 3 <-> 4, etc...
7591 set n [expr {(($n - 1) ^ 1) + 1}]
7592 set row [lindex [rowranges $id] $n]
7593 set yt [yc $row]
7594 set ymax [lindex [$canv cget -scrollregion] 3]
7595 if {$ymax eq {} || $ymax <= 0} return
7596 set view [$canv yview]
7597 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7598 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7599 if {$yfrac < 0} {
7600 set yfrac 0
7602 allcanvs yview moveto $yfrac
7605 proc lineclick {x y id isnew} {
7606 global ctext commitinfo children canv thickerline curview
7608 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7609 unmarkmatches
7610 unselectline
7611 normalline
7612 $canv delete hover
7613 # draw this line thicker than normal
7614 set thickerline $id
7615 drawlines $id
7616 if {$isnew} {
7617 set ymax [lindex [$canv cget -scrollregion] 3]
7618 if {$ymax eq {}} return
7619 set yfrac [lindex [$canv yview] 0]
7620 set y [expr {$y + $yfrac * $ymax}]
7622 set dirn [clickisonarrow $id $y]
7623 if {$dirn ne {}} {
7624 arrowjump $id $dirn $y
7625 return
7628 if {$isnew} {
7629 addtohistory [list lineclick $x $y $id 0]
7631 # fill the details pane with info about this line
7632 $ctext conf -state normal
7633 clear_ctext
7634 settabs 0
7635 $ctext insert end "[mc "Parent"]:\t"
7636 $ctext insert end $id link0
7637 setlink $id link0
7638 set info $commitinfo($id)
7639 $ctext insert end "\n\t[lindex $info 0]\n"
7640 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7641 set date [formatdate [lindex $info 2]]
7642 $ctext insert end "\t[mc "Date"]:\t$date\n"
7643 set kids $children($curview,$id)
7644 if {$kids ne {}} {
7645 $ctext insert end "\n[mc "Children"]:"
7646 set i 0
7647 foreach child $kids {
7648 incr i
7649 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7650 set info $commitinfo($child)
7651 $ctext insert end "\n\t"
7652 $ctext insert end $child link$i
7653 setlink $child link$i
7654 $ctext insert end "\n\t[lindex $info 0]"
7655 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7656 set date [formatdate [lindex $info 2]]
7657 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7660 $ctext conf -state disabled
7661 init_flist {}
7664 proc normalline {} {
7665 global thickerline
7666 if {[info exists thickerline]} {
7667 set id $thickerline
7668 unset thickerline
7669 drawlines $id
7673 proc selbyid {id} {
7674 global curview
7675 if {[commitinview $id $curview]} {
7676 selectline [rowofcommit $id] 1
7680 proc mstime {} {
7681 global startmstime
7682 if {![info exists startmstime]} {
7683 set startmstime [clock clicks -milliseconds]
7685 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7688 proc rowmenu {x y id} {
7689 global rowctxmenu selectedline rowmenuid curview
7690 global nullid nullid2 fakerowmenu mainhead
7692 stopfinding
7693 set rowmenuid $id
7694 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7695 set state disabled
7696 } else {
7697 set state normal
7699 if {$id ne $nullid && $id ne $nullid2} {
7700 set menu $rowctxmenu
7701 if {$mainhead ne {}} {
7702 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7703 } else {
7704 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7706 } else {
7707 set menu $fakerowmenu
7709 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7710 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7711 $menu entryconfigure [mca "Make patch"] -state $state
7712 tk_popup $menu $x $y
7715 proc diffvssel {dirn} {
7716 global rowmenuid selectedline
7718 if {$selectedline eq {}} return
7719 if {$dirn} {
7720 set oldid [commitonrow $selectedline]
7721 set newid $rowmenuid
7722 } else {
7723 set oldid $rowmenuid
7724 set newid [commitonrow $selectedline]
7726 addtohistory [list doseldiff $oldid $newid]
7727 doseldiff $oldid $newid
7730 proc doseldiff {oldid newid} {
7731 global ctext
7732 global commitinfo
7734 $ctext conf -state normal
7735 clear_ctext
7736 init_flist [mc "Top"]
7737 $ctext insert end "[mc "From"] "
7738 $ctext insert end $oldid link0
7739 setlink $oldid link0
7740 $ctext insert end "\n "
7741 $ctext insert end [lindex $commitinfo($oldid) 0]
7742 $ctext insert end "\n\n[mc "To"] "
7743 $ctext insert end $newid link1
7744 setlink $newid link1
7745 $ctext insert end "\n "
7746 $ctext insert end [lindex $commitinfo($newid) 0]
7747 $ctext insert end "\n"
7748 $ctext conf -state disabled
7749 $ctext tag remove found 1.0 end
7750 startdiff [list $oldid $newid]
7753 proc mkpatch {} {
7754 global rowmenuid currentid commitinfo patchtop patchnum
7756 if {![info exists currentid]} return
7757 set oldid $currentid
7758 set oldhead [lindex $commitinfo($oldid) 0]
7759 set newid $rowmenuid
7760 set newhead [lindex $commitinfo($newid) 0]
7761 set top .patch
7762 set patchtop $top
7763 catch {destroy $top}
7764 toplevel $top
7765 label $top.title -text [mc "Generate patch"]
7766 grid $top.title - -pady 10
7767 label $top.from -text [mc "From:"]
7768 entry $top.fromsha1 -width 40 -relief flat
7769 $top.fromsha1 insert 0 $oldid
7770 $top.fromsha1 conf -state readonly
7771 grid $top.from $top.fromsha1 -sticky w
7772 entry $top.fromhead -width 60 -relief flat
7773 $top.fromhead insert 0 $oldhead
7774 $top.fromhead conf -state readonly
7775 grid x $top.fromhead -sticky w
7776 label $top.to -text [mc "To:"]
7777 entry $top.tosha1 -width 40 -relief flat
7778 $top.tosha1 insert 0 $newid
7779 $top.tosha1 conf -state readonly
7780 grid $top.to $top.tosha1 -sticky w
7781 entry $top.tohead -width 60 -relief flat
7782 $top.tohead insert 0 $newhead
7783 $top.tohead conf -state readonly
7784 grid x $top.tohead -sticky w
7785 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7786 grid $top.rev x -pady 10
7787 label $top.flab -text [mc "Output file:"]
7788 entry $top.fname -width 60
7789 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7790 incr patchnum
7791 grid $top.flab $top.fname -sticky w
7792 frame $top.buts
7793 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7794 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7795 grid $top.buts.gen $top.buts.can
7796 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7797 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7798 grid $top.buts - -pady 10 -sticky ew
7799 focus $top.fname
7802 proc mkpatchrev {} {
7803 global patchtop
7805 set oldid [$patchtop.fromsha1 get]
7806 set oldhead [$patchtop.fromhead get]
7807 set newid [$patchtop.tosha1 get]
7808 set newhead [$patchtop.tohead get]
7809 foreach e [list fromsha1 fromhead tosha1 tohead] \
7810 v [list $newid $newhead $oldid $oldhead] {
7811 $patchtop.$e conf -state normal
7812 $patchtop.$e delete 0 end
7813 $patchtop.$e insert 0 $v
7814 $patchtop.$e conf -state readonly
7818 proc mkpatchgo {} {
7819 global patchtop nullid nullid2
7821 set oldid [$patchtop.fromsha1 get]
7822 set newid [$patchtop.tosha1 get]
7823 set fname [$patchtop.fname get]
7824 set cmd [diffcmd [list $oldid $newid] -p]
7825 # trim off the initial "|"
7826 set cmd [lrange $cmd 1 end]
7827 lappend cmd >$fname &
7828 if {[catch {eval exec $cmd} err]} {
7829 error_popup "[mc "Error creating patch:"] $err"
7831 catch {destroy $patchtop}
7832 unset patchtop
7835 proc mkpatchcan {} {
7836 global patchtop
7838 catch {destroy $patchtop}
7839 unset patchtop
7842 proc mktag {} {
7843 global rowmenuid mktagtop commitinfo
7845 set top .maketag
7846 set mktagtop $top
7847 catch {destroy $top}
7848 toplevel $top
7849 label $top.title -text [mc "Create tag"]
7850 grid $top.title - -pady 10
7851 label $top.id -text [mc "ID:"]
7852 entry $top.sha1 -width 40 -relief flat
7853 $top.sha1 insert 0 $rowmenuid
7854 $top.sha1 conf -state readonly
7855 grid $top.id $top.sha1 -sticky w
7856 entry $top.head -width 60 -relief flat
7857 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7858 $top.head conf -state readonly
7859 grid x $top.head -sticky w
7860 label $top.tlab -text [mc "Tag name:"]
7861 entry $top.tag -width 60
7862 grid $top.tlab $top.tag -sticky w
7863 frame $top.buts
7864 button $top.buts.gen -text [mc "Create"] -command mktaggo
7865 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7866 grid $top.buts.gen $top.buts.can
7867 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7868 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7869 grid $top.buts - -pady 10 -sticky ew
7870 focus $top.tag
7873 proc domktag {} {
7874 global mktagtop env tagids idtags
7876 set id [$mktagtop.sha1 get]
7877 set tag [$mktagtop.tag get]
7878 if {$tag == {}} {
7879 error_popup [mc "No tag name specified"]
7880 return
7882 if {[info exists tagids($tag)]} {
7883 error_popup [mc "Tag \"%s\" already exists" $tag]
7884 return
7886 if {[catch {
7887 exec git tag $tag $id
7888 } err]} {
7889 error_popup "[mc "Error creating tag:"] $err"
7890 return
7893 set tagids($tag) $id
7894 lappend idtags($id) $tag
7895 redrawtags $id
7896 addedtag $id
7897 dispneartags 0
7898 run refill_reflist
7901 proc redrawtags {id} {
7902 global canv linehtag idpos currentid curview cmitlisted
7903 global canvxmax iddrawn circleitem mainheadid circlecolors
7905 if {![commitinview $id $curview]} return
7906 if {![info exists iddrawn($id)]} return
7907 set row [rowofcommit $id]
7908 if {$id eq $mainheadid} {
7909 set ofill yellow
7910 } else {
7911 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
7913 $canv itemconf $circleitem($row) -fill $ofill
7914 $canv delete tag.$id
7915 set xt [eval drawtags $id $idpos($id)]
7916 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
7917 set text [$canv itemcget $linehtag($row) -text]
7918 set font [$canv itemcget $linehtag($row) -font]
7919 set xr [expr {$xt + [font measure $font $text]}]
7920 if {$xr > $canvxmax} {
7921 set canvxmax $xr
7922 setcanvscroll
7924 if {[info exists currentid] && $currentid == $id} {
7925 make_secsel $row
7929 proc mktagcan {} {
7930 global mktagtop
7932 catch {destroy $mktagtop}
7933 unset mktagtop
7936 proc mktaggo {} {
7937 domktag
7938 mktagcan
7941 proc writecommit {} {
7942 global rowmenuid wrcomtop commitinfo wrcomcmd
7944 set top .writecommit
7945 set wrcomtop $top
7946 catch {destroy $top}
7947 toplevel $top
7948 label $top.title -text [mc "Write commit to file"]
7949 grid $top.title - -pady 10
7950 label $top.id -text [mc "ID:"]
7951 entry $top.sha1 -width 40 -relief flat
7952 $top.sha1 insert 0 $rowmenuid
7953 $top.sha1 conf -state readonly
7954 grid $top.id $top.sha1 -sticky w
7955 entry $top.head -width 60 -relief flat
7956 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7957 $top.head conf -state readonly
7958 grid x $top.head -sticky w
7959 label $top.clab -text [mc "Command:"]
7960 entry $top.cmd -width 60 -textvariable wrcomcmd
7961 grid $top.clab $top.cmd -sticky w -pady 10
7962 label $top.flab -text [mc "Output file:"]
7963 entry $top.fname -width 60
7964 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
7965 grid $top.flab $top.fname -sticky w
7966 frame $top.buts
7967 button $top.buts.gen -text [mc "Write"] -command wrcomgo
7968 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
7969 grid $top.buts.gen $top.buts.can
7970 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7971 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7972 grid $top.buts - -pady 10 -sticky ew
7973 focus $top.fname
7976 proc wrcomgo {} {
7977 global wrcomtop
7979 set id [$wrcomtop.sha1 get]
7980 set cmd "echo $id | [$wrcomtop.cmd get]"
7981 set fname [$wrcomtop.fname get]
7982 if {[catch {exec sh -c $cmd >$fname &} err]} {
7983 error_popup "[mc "Error writing commit:"] $err"
7985 catch {destroy $wrcomtop}
7986 unset wrcomtop
7989 proc wrcomcan {} {
7990 global wrcomtop
7992 catch {destroy $wrcomtop}
7993 unset wrcomtop
7996 proc mkbranch {} {
7997 global rowmenuid mkbrtop
7999 set top .makebranch
8000 catch {destroy $top}
8001 toplevel $top
8002 label $top.title -text [mc "Create new branch"]
8003 grid $top.title - -pady 10
8004 label $top.id -text [mc "ID:"]
8005 entry $top.sha1 -width 40 -relief flat
8006 $top.sha1 insert 0 $rowmenuid
8007 $top.sha1 conf -state readonly
8008 grid $top.id $top.sha1 -sticky w
8009 label $top.nlab -text [mc "Name:"]
8010 entry $top.name -width 40
8011 bind $top.name <Key-Return> "[list mkbrgo $top]"
8012 grid $top.nlab $top.name -sticky w
8013 frame $top.buts
8014 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8015 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8016 grid $top.buts.go $top.buts.can
8017 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8018 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8019 grid $top.buts - -pady 10 -sticky ew
8020 focus $top.name
8023 proc mkbrgo {top} {
8024 global headids idheads
8026 set name [$top.name get]
8027 set id [$top.sha1 get]
8028 set cmdargs {}
8029 set old_id {}
8030 if {$name eq {}} {
8031 error_popup [mc "Please specify a name for the new branch"]
8032 return
8034 if {[info exists headids($name)]} {
8035 if {![confirm_popup [mc \
8036 "Branch '%s' already exists. Overwrite?" $name]]} {
8037 return
8039 set old_id $headids($name)
8040 lappend cmdargs -f
8042 catch {destroy $top}
8043 lappend cmdargs $name $id
8044 nowbusy newbranch
8045 update
8046 if {[catch {
8047 eval exec git branch $cmdargs
8048 } err]} {
8049 notbusy newbranch
8050 error_popup $err
8051 } else {
8052 notbusy newbranch
8053 if {$old_id ne {}} {
8054 movehead $id $name
8055 movedhead $id $name
8056 redrawtags $old_id
8057 redrawtags $id
8058 } else {
8059 set headids($name) $id
8060 lappend idheads($id) $name
8061 addedhead $id $name
8062 redrawtags $id
8064 dispneartags 0
8065 run refill_reflist
8069 proc cherrypick {} {
8070 global rowmenuid curview
8071 global mainhead mainheadid
8073 set oldhead [exec git rev-parse HEAD]
8074 set dheads [descheads $rowmenuid]
8075 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8076 set ok [confirm_popup [mc "Commit %s is already\
8077 included in branch %s -- really re-apply it?" \
8078 [string range $rowmenuid 0 7] $mainhead]]
8079 if {!$ok} return
8081 nowbusy cherrypick [mc "Cherry-picking"]
8082 update
8083 # Unfortunately git-cherry-pick writes stuff to stderr even when
8084 # no error occurs, and exec takes that as an indication of error...
8085 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8086 notbusy cherrypick
8087 error_popup $err
8088 return
8090 set newhead [exec git rev-parse HEAD]
8091 if {$newhead eq $oldhead} {
8092 notbusy cherrypick
8093 error_popup [mc "No changes committed"]
8094 return
8096 addnewchild $newhead $oldhead
8097 if {[commitinview $oldhead $curview]} {
8098 insertrow $newhead $oldhead $curview
8099 if {$mainhead ne {}} {
8100 movehead $newhead $mainhead
8101 movedhead $newhead $mainhead
8103 set mainheadid $newhead
8104 redrawtags $oldhead
8105 redrawtags $newhead
8106 selbyid $newhead
8108 notbusy cherrypick
8111 proc resethead {} {
8112 global mainhead rowmenuid confirm_ok resettype
8114 set confirm_ok 0
8115 set w ".confirmreset"
8116 toplevel $w
8117 wm transient $w .
8118 wm title $w [mc "Confirm reset"]
8119 message $w.m -text \
8120 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8121 -justify center -aspect 1000
8122 pack $w.m -side top -fill x -padx 20 -pady 20
8123 frame $w.f -relief sunken -border 2
8124 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8125 grid $w.f.rt -sticky w
8126 set resettype mixed
8127 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8128 -text [mc "Soft: Leave working tree and index untouched"]
8129 grid $w.f.soft -sticky w
8130 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8131 -text [mc "Mixed: Leave working tree untouched, reset index"]
8132 grid $w.f.mixed -sticky w
8133 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8134 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8135 grid $w.f.hard -sticky w
8136 pack $w.f -side top -fill x
8137 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8138 pack $w.ok -side left -fill x -padx 20 -pady 20
8139 button $w.cancel -text [mc Cancel] -command "destroy $w"
8140 pack $w.cancel -side right -fill x -padx 20 -pady 20
8141 bind $w <Visibility> "grab $w; focus $w"
8142 tkwait window $w
8143 if {!$confirm_ok} return
8144 if {[catch {set fd [open \
8145 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8146 error_popup $err
8147 } else {
8148 dohidelocalchanges
8149 filerun $fd [list readresetstat $fd]
8150 nowbusy reset [mc "Resetting"]
8151 selbyid $rowmenuid
8155 proc readresetstat {fd} {
8156 global mainhead mainheadid showlocalchanges rprogcoord
8158 if {[gets $fd line] >= 0} {
8159 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8160 set rprogcoord [expr {1.0 * $m / $n}]
8161 adjustprogress
8163 return 1
8165 set rprogcoord 0
8166 adjustprogress
8167 notbusy reset
8168 if {[catch {close $fd} err]} {
8169 error_popup $err
8171 set oldhead $mainheadid
8172 set newhead [exec git rev-parse HEAD]
8173 if {$newhead ne $oldhead} {
8174 movehead $newhead $mainhead
8175 movedhead $newhead $mainhead
8176 set mainheadid $newhead
8177 redrawtags $oldhead
8178 redrawtags $newhead
8180 if {$showlocalchanges} {
8181 doshowlocalchanges
8183 return 0
8186 # context menu for a head
8187 proc headmenu {x y id head} {
8188 global headmenuid headmenuhead headctxmenu mainhead
8190 stopfinding
8191 set headmenuid $id
8192 set headmenuhead $head
8193 set state normal
8194 if {$head eq $mainhead} {
8195 set state disabled
8197 $headctxmenu entryconfigure 0 -state $state
8198 $headctxmenu entryconfigure 1 -state $state
8199 tk_popup $headctxmenu $x $y
8202 proc cobranch {} {
8203 global headmenuid headmenuhead headids
8204 global showlocalchanges mainheadid
8206 # check the tree is clean first??
8207 nowbusy checkout [mc "Checking out"]
8208 update
8209 dohidelocalchanges
8210 if {[catch {
8211 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8212 } err]} {
8213 notbusy checkout
8214 error_popup $err
8215 if {$showlocalchanges} {
8216 dodiffindex
8218 } else {
8219 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8223 proc readcheckoutstat {fd newhead newheadid} {
8224 global mainhead mainheadid headids showlocalchanges progresscoords
8226 if {[gets $fd line] >= 0} {
8227 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8228 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8229 adjustprogress
8231 return 1
8233 set progresscoords {0 0}
8234 adjustprogress
8235 notbusy checkout
8236 if {[catch {close $fd} err]} {
8237 error_popup $err
8239 set oldmainid $mainheadid
8240 set mainhead $newhead
8241 set mainheadid $newheadid
8242 redrawtags $oldmainid
8243 redrawtags $newheadid
8244 selbyid $newheadid
8245 if {$showlocalchanges} {
8246 dodiffindex
8250 proc rmbranch {} {
8251 global headmenuid headmenuhead mainhead
8252 global idheads
8254 set head $headmenuhead
8255 set id $headmenuid
8256 # this check shouldn't be needed any more...
8257 if {$head eq $mainhead} {
8258 error_popup [mc "Cannot delete the currently checked-out branch"]
8259 return
8261 set dheads [descheads $id]
8262 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8263 # the stuff on this branch isn't on any other branch
8264 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8265 branch.\nReally delete branch %s?" $head $head]]} return
8267 nowbusy rmbranch
8268 update
8269 if {[catch {exec git branch -D $head} err]} {
8270 notbusy rmbranch
8271 error_popup $err
8272 return
8274 removehead $id $head
8275 removedhead $id $head
8276 redrawtags $id
8277 notbusy rmbranch
8278 dispneartags 0
8279 run refill_reflist
8282 # Display a list of tags and heads
8283 proc showrefs {} {
8284 global showrefstop bgcolor fgcolor selectbgcolor
8285 global bglist fglist reflistfilter reflist maincursor
8287 set top .showrefs
8288 set showrefstop $top
8289 if {[winfo exists $top]} {
8290 raise $top
8291 refill_reflist
8292 return
8294 toplevel $top
8295 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8296 text $top.list -background $bgcolor -foreground $fgcolor \
8297 -selectbackground $selectbgcolor -font mainfont \
8298 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8299 -width 30 -height 20 -cursor $maincursor \
8300 -spacing1 1 -spacing3 1 -state disabled
8301 $top.list tag configure highlight -background $selectbgcolor
8302 lappend bglist $top.list
8303 lappend fglist $top.list
8304 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8305 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8306 grid $top.list $top.ysb -sticky nsew
8307 grid $top.xsb x -sticky ew
8308 frame $top.f
8309 label $top.f.l -text "[mc "Filter"]: "
8310 entry $top.f.e -width 20 -textvariable reflistfilter
8311 set reflistfilter "*"
8312 trace add variable reflistfilter write reflistfilter_change
8313 pack $top.f.e -side right -fill x -expand 1
8314 pack $top.f.l -side left
8315 grid $top.f - -sticky ew -pady 2
8316 button $top.close -command [list destroy $top] -text [mc "Close"]
8317 grid $top.close -
8318 grid columnconfigure $top 0 -weight 1
8319 grid rowconfigure $top 0 -weight 1
8320 bind $top.list <1> {break}
8321 bind $top.list <B1-Motion> {break}
8322 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8323 set reflist {}
8324 refill_reflist
8327 proc sel_reflist {w x y} {
8328 global showrefstop reflist headids tagids otherrefids
8330 if {![winfo exists $showrefstop]} return
8331 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8332 set ref [lindex $reflist [expr {$l-1}]]
8333 set n [lindex $ref 0]
8334 switch -- [lindex $ref 1] {
8335 "H" {selbyid $headids($n)}
8336 "T" {selbyid $tagids($n)}
8337 "o" {selbyid $otherrefids($n)}
8339 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8342 proc unsel_reflist {} {
8343 global showrefstop
8345 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8346 $showrefstop.list tag remove highlight 0.0 end
8349 proc reflistfilter_change {n1 n2 op} {
8350 global reflistfilter
8352 after cancel refill_reflist
8353 after 200 refill_reflist
8356 proc refill_reflist {} {
8357 global reflist reflistfilter showrefstop headids tagids otherrefids
8358 global curview
8360 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8361 set refs {}
8362 foreach n [array names headids] {
8363 if {[string match $reflistfilter $n]} {
8364 if {[commitinview $headids($n) $curview]} {
8365 lappend refs [list $n H]
8366 } else {
8367 interestedin $headids($n) {run refill_reflist}
8371 foreach n [array names tagids] {
8372 if {[string match $reflistfilter $n]} {
8373 if {[commitinview $tagids($n) $curview]} {
8374 lappend refs [list $n T]
8375 } else {
8376 interestedin $tagids($n) {run refill_reflist}
8380 foreach n [array names otherrefids] {
8381 if {[string match $reflistfilter $n]} {
8382 if {[commitinview $otherrefids($n) $curview]} {
8383 lappend refs [list $n o]
8384 } else {
8385 interestedin $otherrefids($n) {run refill_reflist}
8389 set refs [lsort -index 0 $refs]
8390 if {$refs eq $reflist} return
8392 # Update the contents of $showrefstop.list according to the
8393 # differences between $reflist (old) and $refs (new)
8394 $showrefstop.list conf -state normal
8395 $showrefstop.list insert end "\n"
8396 set i 0
8397 set j 0
8398 while {$i < [llength $reflist] || $j < [llength $refs]} {
8399 if {$i < [llength $reflist]} {
8400 if {$j < [llength $refs]} {
8401 set cmp [string compare [lindex $reflist $i 0] \
8402 [lindex $refs $j 0]]
8403 if {$cmp == 0} {
8404 set cmp [string compare [lindex $reflist $i 1] \
8405 [lindex $refs $j 1]]
8407 } else {
8408 set cmp -1
8410 } else {
8411 set cmp 1
8413 switch -- $cmp {
8414 -1 {
8415 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8416 incr i
8419 incr i
8420 incr j
8423 set l [expr {$j + 1}]
8424 $showrefstop.list image create $l.0 -align baseline \
8425 -image reficon-[lindex $refs $j 1] -padx 2
8426 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8427 incr j
8431 set reflist $refs
8432 # delete last newline
8433 $showrefstop.list delete end-2c end-1c
8434 $showrefstop.list conf -state disabled
8437 # Stuff for finding nearby tags
8438 proc getallcommits {} {
8439 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8440 global idheads idtags idotherrefs allparents tagobjid
8442 if {![info exists allcommits]} {
8443 set nextarc 0
8444 set allcommits 0
8445 set seeds {}
8446 set allcwait 0
8447 set cachedarcs 0
8448 set allccache [file join [gitdir] "gitk.cache"]
8449 if {![catch {
8450 set f [open $allccache r]
8451 set allcwait 1
8452 getcache $f
8453 }]} return
8456 if {$allcwait} {
8457 return
8459 set cmd [list | git rev-list --parents]
8460 set allcupdate [expr {$seeds ne {}}]
8461 if {!$allcupdate} {
8462 set ids "--all"
8463 } else {
8464 set refs [concat [array names idheads] [array names idtags] \
8465 [array names idotherrefs]]
8466 set ids {}
8467 set tagobjs {}
8468 foreach name [array names tagobjid] {
8469 lappend tagobjs $tagobjid($name)
8471 foreach id [lsort -unique $refs] {
8472 if {![info exists allparents($id)] &&
8473 [lsearch -exact $tagobjs $id] < 0} {
8474 lappend ids $id
8477 if {$ids ne {}} {
8478 foreach id $seeds {
8479 lappend ids "^$id"
8483 if {$ids ne {}} {
8484 set fd [open [concat $cmd $ids] r]
8485 fconfigure $fd -blocking 0
8486 incr allcommits
8487 nowbusy allcommits
8488 filerun $fd [list getallclines $fd]
8489 } else {
8490 dispneartags 0
8494 # Since most commits have 1 parent and 1 child, we group strings of
8495 # such commits into "arcs" joining branch/merge points (BMPs), which
8496 # are commits that either don't have 1 parent or don't have 1 child.
8498 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8499 # arcout(id) - outgoing arcs for BMP
8500 # arcids(a) - list of IDs on arc including end but not start
8501 # arcstart(a) - BMP ID at start of arc
8502 # arcend(a) - BMP ID at end of arc
8503 # growing(a) - arc a is still growing
8504 # arctags(a) - IDs out of arcids (excluding end) that have tags
8505 # archeads(a) - IDs out of arcids (excluding end) that have heads
8506 # The start of an arc is at the descendent end, so "incoming" means
8507 # coming from descendents, and "outgoing" means going towards ancestors.
8509 proc getallclines {fd} {
8510 global allparents allchildren idtags idheads nextarc
8511 global arcnos arcids arctags arcout arcend arcstart archeads growing
8512 global seeds allcommits cachedarcs allcupdate
8514 set nid 0
8515 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8516 set id [lindex $line 0]
8517 if {[info exists allparents($id)]} {
8518 # seen it already
8519 continue
8521 set cachedarcs 0
8522 set olds [lrange $line 1 end]
8523 set allparents($id) $olds
8524 if {![info exists allchildren($id)]} {
8525 set allchildren($id) {}
8526 set arcnos($id) {}
8527 lappend seeds $id
8528 } else {
8529 set a $arcnos($id)
8530 if {[llength $olds] == 1 && [llength $a] == 1} {
8531 lappend arcids($a) $id
8532 if {[info exists idtags($id)]} {
8533 lappend arctags($a) $id
8535 if {[info exists idheads($id)]} {
8536 lappend archeads($a) $id
8538 if {[info exists allparents($olds)]} {
8539 # seen parent already
8540 if {![info exists arcout($olds)]} {
8541 splitarc $olds
8543 lappend arcids($a) $olds
8544 set arcend($a) $olds
8545 unset growing($a)
8547 lappend allchildren($olds) $id
8548 lappend arcnos($olds) $a
8549 continue
8552 foreach a $arcnos($id) {
8553 lappend arcids($a) $id
8554 set arcend($a) $id
8555 unset growing($a)
8558 set ao {}
8559 foreach p $olds {
8560 lappend allchildren($p) $id
8561 set a [incr nextarc]
8562 set arcstart($a) $id
8563 set archeads($a) {}
8564 set arctags($a) {}
8565 set archeads($a) {}
8566 set arcids($a) {}
8567 lappend ao $a
8568 set growing($a) 1
8569 if {[info exists allparents($p)]} {
8570 # seen it already, may need to make a new branch
8571 if {![info exists arcout($p)]} {
8572 splitarc $p
8574 lappend arcids($a) $p
8575 set arcend($a) $p
8576 unset growing($a)
8578 lappend arcnos($p) $a
8580 set arcout($id) $ao
8582 if {$nid > 0} {
8583 global cached_dheads cached_dtags cached_atags
8584 catch {unset cached_dheads}
8585 catch {unset cached_dtags}
8586 catch {unset cached_atags}
8588 if {![eof $fd]} {
8589 return [expr {$nid >= 1000? 2: 1}]
8591 set cacheok 1
8592 if {[catch {
8593 fconfigure $fd -blocking 1
8594 close $fd
8595 } err]} {
8596 # got an error reading the list of commits
8597 # if we were updating, try rereading the whole thing again
8598 if {$allcupdate} {
8599 incr allcommits -1
8600 dropcache $err
8601 return
8603 error_popup "[mc "Error reading commit topology information;\
8604 branch and preceding/following tag information\
8605 will be incomplete."]\n($err)"
8606 set cacheok 0
8608 if {[incr allcommits -1] == 0} {
8609 notbusy allcommits
8610 if {$cacheok} {
8611 run savecache
8614 dispneartags 0
8615 return 0
8618 proc recalcarc {a} {
8619 global arctags archeads arcids idtags idheads
8621 set at {}
8622 set ah {}
8623 foreach id [lrange $arcids($a) 0 end-1] {
8624 if {[info exists idtags($id)]} {
8625 lappend at $id
8627 if {[info exists idheads($id)]} {
8628 lappend ah $id
8631 set arctags($a) $at
8632 set archeads($a) $ah
8635 proc splitarc {p} {
8636 global arcnos arcids nextarc arctags archeads idtags idheads
8637 global arcstart arcend arcout allparents growing
8639 set a $arcnos($p)
8640 if {[llength $a] != 1} {
8641 puts "oops splitarc called but [llength $a] arcs already"
8642 return
8644 set a [lindex $a 0]
8645 set i [lsearch -exact $arcids($a) $p]
8646 if {$i < 0} {
8647 puts "oops splitarc $p not in arc $a"
8648 return
8650 set na [incr nextarc]
8651 if {[info exists arcend($a)]} {
8652 set arcend($na) $arcend($a)
8653 } else {
8654 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8655 set j [lsearch -exact $arcnos($l) $a]
8656 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8658 set tail [lrange $arcids($a) [expr {$i+1}] end]
8659 set arcids($a) [lrange $arcids($a) 0 $i]
8660 set arcend($a) $p
8661 set arcstart($na) $p
8662 set arcout($p) $na
8663 set arcids($na) $tail
8664 if {[info exists growing($a)]} {
8665 set growing($na) 1
8666 unset growing($a)
8669 foreach id $tail {
8670 if {[llength $arcnos($id)] == 1} {
8671 set arcnos($id) $na
8672 } else {
8673 set j [lsearch -exact $arcnos($id) $a]
8674 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8678 # reconstruct tags and heads lists
8679 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8680 recalcarc $a
8681 recalcarc $na
8682 } else {
8683 set arctags($na) {}
8684 set archeads($na) {}
8688 # Update things for a new commit added that is a child of one
8689 # existing commit. Used when cherry-picking.
8690 proc addnewchild {id p} {
8691 global allparents allchildren idtags nextarc
8692 global arcnos arcids arctags arcout arcend arcstart archeads growing
8693 global seeds allcommits
8695 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8696 set allparents($id) [list $p]
8697 set allchildren($id) {}
8698 set arcnos($id) {}
8699 lappend seeds $id
8700 lappend allchildren($p) $id
8701 set a [incr nextarc]
8702 set arcstart($a) $id
8703 set archeads($a) {}
8704 set arctags($a) {}
8705 set arcids($a) [list $p]
8706 set arcend($a) $p
8707 if {![info exists arcout($p)]} {
8708 splitarc $p
8710 lappend arcnos($p) $a
8711 set arcout($id) [list $a]
8714 # This implements a cache for the topology information.
8715 # The cache saves, for each arc, the start and end of the arc,
8716 # the ids on the arc, and the outgoing arcs from the end.
8717 proc readcache {f} {
8718 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8719 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8720 global allcwait
8722 set a $nextarc
8723 set lim $cachedarcs
8724 if {$lim - $a > 500} {
8725 set lim [expr {$a + 500}]
8727 if {[catch {
8728 if {$a == $lim} {
8729 # finish reading the cache and setting up arctags, etc.
8730 set line [gets $f]
8731 if {$line ne "1"} {error "bad final version"}
8732 close $f
8733 foreach id [array names idtags] {
8734 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8735 [llength $allparents($id)] == 1} {
8736 set a [lindex $arcnos($id) 0]
8737 if {$arctags($a) eq {}} {
8738 recalcarc $a
8742 foreach id [array names idheads] {
8743 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8744 [llength $allparents($id)] == 1} {
8745 set a [lindex $arcnos($id) 0]
8746 if {$archeads($a) eq {}} {
8747 recalcarc $a
8751 foreach id [lsort -unique $possible_seeds] {
8752 if {$arcnos($id) eq {}} {
8753 lappend seeds $id
8756 set allcwait 0
8757 } else {
8758 while {[incr a] <= $lim} {
8759 set line [gets $f]
8760 if {[llength $line] != 3} {error "bad line"}
8761 set s [lindex $line 0]
8762 set arcstart($a) $s
8763 lappend arcout($s) $a
8764 if {![info exists arcnos($s)]} {
8765 lappend possible_seeds $s
8766 set arcnos($s) {}
8768 set e [lindex $line 1]
8769 if {$e eq {}} {
8770 set growing($a) 1
8771 } else {
8772 set arcend($a) $e
8773 if {![info exists arcout($e)]} {
8774 set arcout($e) {}
8777 set arcids($a) [lindex $line 2]
8778 foreach id $arcids($a) {
8779 lappend allparents($s) $id
8780 set s $id
8781 lappend arcnos($id) $a
8783 if {![info exists allparents($s)]} {
8784 set allparents($s) {}
8786 set arctags($a) {}
8787 set archeads($a) {}
8789 set nextarc [expr {$a - 1}]
8791 } err]} {
8792 dropcache $err
8793 return 0
8795 if {!$allcwait} {
8796 getallcommits
8798 return $allcwait
8801 proc getcache {f} {
8802 global nextarc cachedarcs possible_seeds
8804 if {[catch {
8805 set line [gets $f]
8806 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8807 # make sure it's an integer
8808 set cachedarcs [expr {int([lindex $line 1])}]
8809 if {$cachedarcs < 0} {error "bad number of arcs"}
8810 set nextarc 0
8811 set possible_seeds {}
8812 run readcache $f
8813 } err]} {
8814 dropcache $err
8816 return 0
8819 proc dropcache {err} {
8820 global allcwait nextarc cachedarcs seeds
8822 #puts "dropping cache ($err)"
8823 foreach v {arcnos arcout arcids arcstart arcend growing \
8824 arctags archeads allparents allchildren} {
8825 global $v
8826 catch {unset $v}
8828 set allcwait 0
8829 set nextarc 0
8830 set cachedarcs 0
8831 set seeds {}
8832 getallcommits
8835 proc writecache {f} {
8836 global cachearc cachedarcs allccache
8837 global arcstart arcend arcnos arcids arcout
8839 set a $cachearc
8840 set lim $cachedarcs
8841 if {$lim - $a > 1000} {
8842 set lim [expr {$a + 1000}]
8844 if {[catch {
8845 while {[incr a] <= $lim} {
8846 if {[info exists arcend($a)]} {
8847 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
8848 } else {
8849 puts $f [list $arcstart($a) {} $arcids($a)]
8852 } err]} {
8853 catch {close $f}
8854 catch {file delete $allccache}
8855 #puts "writing cache failed ($err)"
8856 return 0
8858 set cachearc [expr {$a - 1}]
8859 if {$a > $cachedarcs} {
8860 puts $f "1"
8861 close $f
8862 return 0
8864 return 1
8867 proc savecache {} {
8868 global nextarc cachedarcs cachearc allccache
8870 if {$nextarc == $cachedarcs} return
8871 set cachearc 0
8872 set cachedarcs $nextarc
8873 catch {
8874 set f [open $allccache w]
8875 puts $f [list 1 $cachedarcs]
8876 run writecache $f
8880 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
8881 # or 0 if neither is true.
8882 proc anc_or_desc {a b} {
8883 global arcout arcstart arcend arcnos cached_isanc
8885 if {$arcnos($a) eq $arcnos($b)} {
8886 # Both are on the same arc(s); either both are the same BMP,
8887 # or if one is not a BMP, the other is also not a BMP or is
8888 # the BMP at end of the arc (and it only has 1 incoming arc).
8889 # Or both can be BMPs with no incoming arcs.
8890 if {$a eq $b || $arcnos($a) eq {}} {
8891 return 0
8893 # assert {[llength $arcnos($a)] == 1}
8894 set arc [lindex $arcnos($a) 0]
8895 set i [lsearch -exact $arcids($arc) $a]
8896 set j [lsearch -exact $arcids($arc) $b]
8897 if {$i < 0 || $i > $j} {
8898 return 1
8899 } else {
8900 return -1
8904 if {![info exists arcout($a)]} {
8905 set arc [lindex $arcnos($a) 0]
8906 if {[info exists arcend($arc)]} {
8907 set aend $arcend($arc)
8908 } else {
8909 set aend {}
8911 set a $arcstart($arc)
8912 } else {
8913 set aend $a
8915 if {![info exists arcout($b)]} {
8916 set arc [lindex $arcnos($b) 0]
8917 if {[info exists arcend($arc)]} {
8918 set bend $arcend($arc)
8919 } else {
8920 set bend {}
8922 set b $arcstart($arc)
8923 } else {
8924 set bend $b
8926 if {$a eq $bend} {
8927 return 1
8929 if {$b eq $aend} {
8930 return -1
8932 if {[info exists cached_isanc($a,$bend)]} {
8933 if {$cached_isanc($a,$bend)} {
8934 return 1
8937 if {[info exists cached_isanc($b,$aend)]} {
8938 if {$cached_isanc($b,$aend)} {
8939 return -1
8941 if {[info exists cached_isanc($a,$bend)]} {
8942 return 0
8946 set todo [list $a $b]
8947 set anc($a) a
8948 set anc($b) b
8949 for {set i 0} {$i < [llength $todo]} {incr i} {
8950 set x [lindex $todo $i]
8951 if {$anc($x) eq {}} {
8952 continue
8954 foreach arc $arcnos($x) {
8955 set xd $arcstart($arc)
8956 if {$xd eq $bend} {
8957 set cached_isanc($a,$bend) 1
8958 set cached_isanc($b,$aend) 0
8959 return 1
8960 } elseif {$xd eq $aend} {
8961 set cached_isanc($b,$aend) 1
8962 set cached_isanc($a,$bend) 0
8963 return -1
8965 if {![info exists anc($xd)]} {
8966 set anc($xd) $anc($x)
8967 lappend todo $xd
8968 } elseif {$anc($xd) ne $anc($x)} {
8969 set anc($xd) {}
8973 set cached_isanc($a,$bend) 0
8974 set cached_isanc($b,$aend) 0
8975 return 0
8978 # This identifies whether $desc has an ancestor that is
8979 # a growing tip of the graph and which is not an ancestor of $anc
8980 # and returns 0 if so and 1 if not.
8981 # If we subsequently discover a tag on such a growing tip, and that
8982 # turns out to be a descendent of $anc (which it could, since we
8983 # don't necessarily see children before parents), then $desc
8984 # isn't a good choice to display as a descendent tag of
8985 # $anc (since it is the descendent of another tag which is
8986 # a descendent of $anc). Similarly, $anc isn't a good choice to
8987 # display as a ancestor tag of $desc.
8989 proc is_certain {desc anc} {
8990 global arcnos arcout arcstart arcend growing problems
8992 set certain {}
8993 if {[llength $arcnos($anc)] == 1} {
8994 # tags on the same arc are certain
8995 if {$arcnos($desc) eq $arcnos($anc)} {
8996 return 1
8998 if {![info exists arcout($anc)]} {
8999 # if $anc is partway along an arc, use the start of the arc instead
9000 set a [lindex $arcnos($anc) 0]
9001 set anc $arcstart($a)
9004 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9005 set x $desc
9006 } else {
9007 set a [lindex $arcnos($desc) 0]
9008 set x $arcend($a)
9010 if {$x == $anc} {
9011 return 1
9013 set anclist [list $x]
9014 set dl($x) 1
9015 set nnh 1
9016 set ngrowanc 0
9017 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9018 set x [lindex $anclist $i]
9019 if {$dl($x)} {
9020 incr nnh -1
9022 set done($x) 1
9023 foreach a $arcout($x) {
9024 if {[info exists growing($a)]} {
9025 if {![info exists growanc($x)] && $dl($x)} {
9026 set growanc($x) 1
9027 incr ngrowanc
9029 } else {
9030 set y $arcend($a)
9031 if {[info exists dl($y)]} {
9032 if {$dl($y)} {
9033 if {!$dl($x)} {
9034 set dl($y) 0
9035 if {![info exists done($y)]} {
9036 incr nnh -1
9038 if {[info exists growanc($x)]} {
9039 incr ngrowanc -1
9041 set xl [list $y]
9042 for {set k 0} {$k < [llength $xl]} {incr k} {
9043 set z [lindex $xl $k]
9044 foreach c $arcout($z) {
9045 if {[info exists arcend($c)]} {
9046 set v $arcend($c)
9047 if {[info exists dl($v)] && $dl($v)} {
9048 set dl($v) 0
9049 if {![info exists done($v)]} {
9050 incr nnh -1
9052 if {[info exists growanc($v)]} {
9053 incr ngrowanc -1
9055 lappend xl $v
9062 } elseif {$y eq $anc || !$dl($x)} {
9063 set dl($y) 0
9064 lappend anclist $y
9065 } else {
9066 set dl($y) 1
9067 lappend anclist $y
9068 incr nnh
9073 foreach x [array names growanc] {
9074 if {$dl($x)} {
9075 return 0
9077 return 0
9079 return 1
9082 proc validate_arctags {a} {
9083 global arctags idtags
9085 set i -1
9086 set na $arctags($a)
9087 foreach id $arctags($a) {
9088 incr i
9089 if {![info exists idtags($id)]} {
9090 set na [lreplace $na $i $i]
9091 incr i -1
9094 set arctags($a) $na
9097 proc validate_archeads {a} {
9098 global archeads idheads
9100 set i -1
9101 set na $archeads($a)
9102 foreach id $archeads($a) {
9103 incr i
9104 if {![info exists idheads($id)]} {
9105 set na [lreplace $na $i $i]
9106 incr i -1
9109 set archeads($a) $na
9112 # Return the list of IDs that have tags that are descendents of id,
9113 # ignoring IDs that are descendents of IDs already reported.
9114 proc desctags {id} {
9115 global arcnos arcstart arcids arctags idtags allparents
9116 global growing cached_dtags
9118 if {![info exists allparents($id)]} {
9119 return {}
9121 set t1 [clock clicks -milliseconds]
9122 set argid $id
9123 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9124 # part-way along an arc; check that arc first
9125 set a [lindex $arcnos($id) 0]
9126 if {$arctags($a) ne {}} {
9127 validate_arctags $a
9128 set i [lsearch -exact $arcids($a) $id]
9129 set tid {}
9130 foreach t $arctags($a) {
9131 set j [lsearch -exact $arcids($a) $t]
9132 if {$j >= $i} break
9133 set tid $t
9135 if {$tid ne {}} {
9136 return $tid
9139 set id $arcstart($a)
9140 if {[info exists idtags($id)]} {
9141 return $id
9144 if {[info exists cached_dtags($id)]} {
9145 return $cached_dtags($id)
9148 set origid $id
9149 set todo [list $id]
9150 set queued($id) 1
9151 set nc 1
9152 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9153 set id [lindex $todo $i]
9154 set done($id) 1
9155 set ta [info exists hastaggedancestor($id)]
9156 if {!$ta} {
9157 incr nc -1
9159 # ignore tags on starting node
9160 if {!$ta && $i > 0} {
9161 if {[info exists idtags($id)]} {
9162 set tagloc($id) $id
9163 set ta 1
9164 } elseif {[info exists cached_dtags($id)]} {
9165 set tagloc($id) $cached_dtags($id)
9166 set ta 1
9169 foreach a $arcnos($id) {
9170 set d $arcstart($a)
9171 if {!$ta && $arctags($a) ne {}} {
9172 validate_arctags $a
9173 if {$arctags($a) ne {}} {
9174 lappend tagloc($id) [lindex $arctags($a) end]
9177 if {$ta || $arctags($a) ne {}} {
9178 set tomark [list $d]
9179 for {set j 0} {$j < [llength $tomark]} {incr j} {
9180 set dd [lindex $tomark $j]
9181 if {![info exists hastaggedancestor($dd)]} {
9182 if {[info exists done($dd)]} {
9183 foreach b $arcnos($dd) {
9184 lappend tomark $arcstart($b)
9186 if {[info exists tagloc($dd)]} {
9187 unset tagloc($dd)
9189 } elseif {[info exists queued($dd)]} {
9190 incr nc -1
9192 set hastaggedancestor($dd) 1
9196 if {![info exists queued($d)]} {
9197 lappend todo $d
9198 set queued($d) 1
9199 if {![info exists hastaggedancestor($d)]} {
9200 incr nc
9205 set tags {}
9206 foreach id [array names tagloc] {
9207 if {![info exists hastaggedancestor($id)]} {
9208 foreach t $tagloc($id) {
9209 if {[lsearch -exact $tags $t] < 0} {
9210 lappend tags $t
9215 set t2 [clock clicks -milliseconds]
9216 set loopix $i
9218 # remove tags that are descendents of other tags
9219 for {set i 0} {$i < [llength $tags]} {incr i} {
9220 set a [lindex $tags $i]
9221 for {set j 0} {$j < $i} {incr j} {
9222 set b [lindex $tags $j]
9223 set r [anc_or_desc $a $b]
9224 if {$r == 1} {
9225 set tags [lreplace $tags $j $j]
9226 incr j -1
9227 incr i -1
9228 } elseif {$r == -1} {
9229 set tags [lreplace $tags $i $i]
9230 incr i -1
9231 break
9236 if {[array names growing] ne {}} {
9237 # graph isn't finished, need to check if any tag could get
9238 # eclipsed by another tag coming later. Simply ignore any
9239 # tags that could later get eclipsed.
9240 set ctags {}
9241 foreach t $tags {
9242 if {[is_certain $t $origid]} {
9243 lappend ctags $t
9246 if {$tags eq $ctags} {
9247 set cached_dtags($origid) $tags
9248 } else {
9249 set tags $ctags
9251 } else {
9252 set cached_dtags($origid) $tags
9254 set t3 [clock clicks -milliseconds]
9255 if {0 && $t3 - $t1 >= 100} {
9256 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9257 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9259 return $tags
9262 proc anctags {id} {
9263 global arcnos arcids arcout arcend arctags idtags allparents
9264 global growing cached_atags
9266 if {![info exists allparents($id)]} {
9267 return {}
9269 set t1 [clock clicks -milliseconds]
9270 set argid $id
9271 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9272 # part-way along an arc; check that arc first
9273 set a [lindex $arcnos($id) 0]
9274 if {$arctags($a) ne {}} {
9275 validate_arctags $a
9276 set i [lsearch -exact $arcids($a) $id]
9277 foreach t $arctags($a) {
9278 set j [lsearch -exact $arcids($a) $t]
9279 if {$j > $i} {
9280 return $t
9284 if {![info exists arcend($a)]} {
9285 return {}
9287 set id $arcend($a)
9288 if {[info exists idtags($id)]} {
9289 return $id
9292 if {[info exists cached_atags($id)]} {
9293 return $cached_atags($id)
9296 set origid $id
9297 set todo [list $id]
9298 set queued($id) 1
9299 set taglist {}
9300 set nc 1
9301 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9302 set id [lindex $todo $i]
9303 set done($id) 1
9304 set td [info exists hastaggeddescendent($id)]
9305 if {!$td} {
9306 incr nc -1
9308 # ignore tags on starting node
9309 if {!$td && $i > 0} {
9310 if {[info exists idtags($id)]} {
9311 set tagloc($id) $id
9312 set td 1
9313 } elseif {[info exists cached_atags($id)]} {
9314 set tagloc($id) $cached_atags($id)
9315 set td 1
9318 foreach a $arcout($id) {
9319 if {!$td && $arctags($a) ne {}} {
9320 validate_arctags $a
9321 if {$arctags($a) ne {}} {
9322 lappend tagloc($id) [lindex $arctags($a) 0]
9325 if {![info exists arcend($a)]} continue
9326 set d $arcend($a)
9327 if {$td || $arctags($a) ne {}} {
9328 set tomark [list $d]
9329 for {set j 0} {$j < [llength $tomark]} {incr j} {
9330 set dd [lindex $tomark $j]
9331 if {![info exists hastaggeddescendent($dd)]} {
9332 if {[info exists done($dd)]} {
9333 foreach b $arcout($dd) {
9334 if {[info exists arcend($b)]} {
9335 lappend tomark $arcend($b)
9338 if {[info exists tagloc($dd)]} {
9339 unset tagloc($dd)
9341 } elseif {[info exists queued($dd)]} {
9342 incr nc -1
9344 set hastaggeddescendent($dd) 1
9348 if {![info exists queued($d)]} {
9349 lappend todo $d
9350 set queued($d) 1
9351 if {![info exists hastaggeddescendent($d)]} {
9352 incr nc
9357 set t2 [clock clicks -milliseconds]
9358 set loopix $i
9359 set tags {}
9360 foreach id [array names tagloc] {
9361 if {![info exists hastaggeddescendent($id)]} {
9362 foreach t $tagloc($id) {
9363 if {[lsearch -exact $tags $t] < 0} {
9364 lappend tags $t
9370 # remove tags that are ancestors of other tags
9371 for {set i 0} {$i < [llength $tags]} {incr i} {
9372 set a [lindex $tags $i]
9373 for {set j 0} {$j < $i} {incr j} {
9374 set b [lindex $tags $j]
9375 set r [anc_or_desc $a $b]
9376 if {$r == -1} {
9377 set tags [lreplace $tags $j $j]
9378 incr j -1
9379 incr i -1
9380 } elseif {$r == 1} {
9381 set tags [lreplace $tags $i $i]
9382 incr i -1
9383 break
9388 if {[array names growing] ne {}} {
9389 # graph isn't finished, need to check if any tag could get
9390 # eclipsed by another tag coming later. Simply ignore any
9391 # tags that could later get eclipsed.
9392 set ctags {}
9393 foreach t $tags {
9394 if {[is_certain $origid $t]} {
9395 lappend ctags $t
9398 if {$tags eq $ctags} {
9399 set cached_atags($origid) $tags
9400 } else {
9401 set tags $ctags
9403 } else {
9404 set cached_atags($origid) $tags
9406 set t3 [clock clicks -milliseconds]
9407 if {0 && $t3 - $t1 >= 100} {
9408 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9409 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9411 return $tags
9414 # Return the list of IDs that have heads that are descendents of id,
9415 # including id itself if it has a head.
9416 proc descheads {id} {
9417 global arcnos arcstart arcids archeads idheads cached_dheads
9418 global allparents
9420 if {![info exists allparents($id)]} {
9421 return {}
9423 set aret {}
9424 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9425 # part-way along an arc; check it first
9426 set a [lindex $arcnos($id) 0]
9427 if {$archeads($a) ne {}} {
9428 validate_archeads $a
9429 set i [lsearch -exact $arcids($a) $id]
9430 foreach t $archeads($a) {
9431 set j [lsearch -exact $arcids($a) $t]
9432 if {$j > $i} break
9433 lappend aret $t
9436 set id $arcstart($a)
9438 set origid $id
9439 set todo [list $id]
9440 set seen($id) 1
9441 set ret {}
9442 for {set i 0} {$i < [llength $todo]} {incr i} {
9443 set id [lindex $todo $i]
9444 if {[info exists cached_dheads($id)]} {
9445 set ret [concat $ret $cached_dheads($id)]
9446 } else {
9447 if {[info exists idheads($id)]} {
9448 lappend ret $id
9450 foreach a $arcnos($id) {
9451 if {$archeads($a) ne {}} {
9452 validate_archeads $a
9453 if {$archeads($a) ne {}} {
9454 set ret [concat $ret $archeads($a)]
9457 set d $arcstart($a)
9458 if {![info exists seen($d)]} {
9459 lappend todo $d
9460 set seen($d) 1
9465 set ret [lsort -unique $ret]
9466 set cached_dheads($origid) $ret
9467 return [concat $ret $aret]
9470 proc addedtag {id} {
9471 global arcnos arcout cached_dtags cached_atags
9473 if {![info exists arcnos($id)]} return
9474 if {![info exists arcout($id)]} {
9475 recalcarc [lindex $arcnos($id) 0]
9477 catch {unset cached_dtags}
9478 catch {unset cached_atags}
9481 proc addedhead {hid head} {
9482 global arcnos arcout cached_dheads
9484 if {![info exists arcnos($hid)]} return
9485 if {![info exists arcout($hid)]} {
9486 recalcarc [lindex $arcnos($hid) 0]
9488 catch {unset cached_dheads}
9491 proc removedhead {hid head} {
9492 global cached_dheads
9494 catch {unset cached_dheads}
9497 proc movedhead {hid head} {
9498 global arcnos arcout cached_dheads
9500 if {![info exists arcnos($hid)]} return
9501 if {![info exists arcout($hid)]} {
9502 recalcarc [lindex $arcnos($hid) 0]
9504 catch {unset cached_dheads}
9507 proc changedrefs {} {
9508 global cached_dheads cached_dtags cached_atags
9509 global arctags archeads arcnos arcout idheads idtags
9511 foreach id [concat [array names idheads] [array names idtags]] {
9512 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9513 set a [lindex $arcnos($id) 0]
9514 if {![info exists donearc($a)]} {
9515 recalcarc $a
9516 set donearc($a) 1
9520 catch {unset cached_dtags}
9521 catch {unset cached_atags}
9522 catch {unset cached_dheads}
9525 proc rereadrefs {} {
9526 global idtags idheads idotherrefs mainheadid
9528 set refids [concat [array names idtags] \
9529 [array names idheads] [array names idotherrefs]]
9530 foreach id $refids {
9531 if {![info exists ref($id)]} {
9532 set ref($id) [listrefs $id]
9535 set oldmainhead $mainheadid
9536 readrefs
9537 changedrefs
9538 set refids [lsort -unique [concat $refids [array names idtags] \
9539 [array names idheads] [array names idotherrefs]]]
9540 foreach id $refids {
9541 set v [listrefs $id]
9542 if {![info exists ref($id)] || $ref($id) != $v} {
9543 redrawtags $id
9546 if {$oldmainhead ne $mainheadid} {
9547 redrawtags $oldmainhead
9548 redrawtags $mainheadid
9550 run refill_reflist
9553 proc listrefs {id} {
9554 global idtags idheads idotherrefs
9556 set x {}
9557 if {[info exists idtags($id)]} {
9558 set x $idtags($id)
9560 set y {}
9561 if {[info exists idheads($id)]} {
9562 set y $idheads($id)
9564 set z {}
9565 if {[info exists idotherrefs($id)]} {
9566 set z $idotherrefs($id)
9568 return [list $x $y $z]
9571 proc showtag {tag isnew} {
9572 global ctext tagcontents tagids linknum tagobjid
9574 if {$isnew} {
9575 addtohistory [list showtag $tag 0]
9577 $ctext conf -state normal
9578 clear_ctext
9579 settabs 0
9580 set linknum 0
9581 if {![info exists tagcontents($tag)]} {
9582 catch {
9583 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9586 if {[info exists tagcontents($tag)]} {
9587 set text $tagcontents($tag)
9588 } else {
9589 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9591 appendwithlinks $text {}
9592 $ctext conf -state disabled
9593 init_flist {}
9596 proc doquit {} {
9597 global stopped
9598 global gitktmpdir
9600 set stopped 100
9601 savestuff .
9602 destroy .
9604 if {[info exists gitktmpdir]} {
9605 catch {file delete -force $gitktmpdir}
9609 proc mkfontdisp {font top which} {
9610 global fontattr fontpref $font
9612 set fontpref($font) [set $font]
9613 button $top.${font}but -text $which -font optionfont \
9614 -command [list choosefont $font $which]
9615 label $top.$font -relief flat -font $font \
9616 -text $fontattr($font,family) -justify left
9617 grid x $top.${font}but $top.$font -sticky w
9620 proc choosefont {font which} {
9621 global fontparam fontlist fonttop fontattr
9623 set fontparam(which) $which
9624 set fontparam(font) $font
9625 set fontparam(family) [font actual $font -family]
9626 set fontparam(size) $fontattr($font,size)
9627 set fontparam(weight) $fontattr($font,weight)
9628 set fontparam(slant) $fontattr($font,slant)
9629 set top .gitkfont
9630 set fonttop $top
9631 if {![winfo exists $top]} {
9632 font create sample
9633 eval font config sample [font actual $font]
9634 toplevel $top
9635 wm title $top [mc "Gitk font chooser"]
9636 label $top.l -textvariable fontparam(which)
9637 pack $top.l -side top
9638 set fontlist [lsort [font families]]
9639 frame $top.f
9640 listbox $top.f.fam -listvariable fontlist \
9641 -yscrollcommand [list $top.f.sb set]
9642 bind $top.f.fam <<ListboxSelect>> selfontfam
9643 scrollbar $top.f.sb -command [list $top.f.fam yview]
9644 pack $top.f.sb -side right -fill y
9645 pack $top.f.fam -side left -fill both -expand 1
9646 pack $top.f -side top -fill both -expand 1
9647 frame $top.g
9648 spinbox $top.g.size -from 4 -to 40 -width 4 \
9649 -textvariable fontparam(size) \
9650 -validatecommand {string is integer -strict %s}
9651 checkbutton $top.g.bold -padx 5 \
9652 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9653 -variable fontparam(weight) -onvalue bold -offvalue normal
9654 checkbutton $top.g.ital -padx 5 \
9655 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9656 -variable fontparam(slant) -onvalue italic -offvalue roman
9657 pack $top.g.size $top.g.bold $top.g.ital -side left
9658 pack $top.g -side top
9659 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9660 -background white
9661 $top.c create text 100 25 -anchor center -text $which -font sample \
9662 -fill black -tags text
9663 bind $top.c <Configure> [list centertext $top.c]
9664 pack $top.c -side top -fill x
9665 frame $top.buts
9666 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9667 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9668 grid $top.buts.ok $top.buts.can
9669 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9670 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9671 pack $top.buts -side bottom -fill x
9672 trace add variable fontparam write chg_fontparam
9673 } else {
9674 raise $top
9675 $top.c itemconf text -text $which
9677 set i [lsearch -exact $fontlist $fontparam(family)]
9678 if {$i >= 0} {
9679 $top.f.fam selection set $i
9680 $top.f.fam see $i
9684 proc centertext {w} {
9685 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9688 proc fontok {} {
9689 global fontparam fontpref prefstop
9691 set f $fontparam(font)
9692 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9693 if {$fontparam(weight) eq "bold"} {
9694 lappend fontpref($f) "bold"
9696 if {$fontparam(slant) eq "italic"} {
9697 lappend fontpref($f) "italic"
9699 set w $prefstop.$f
9700 $w conf -text $fontparam(family) -font $fontpref($f)
9702 fontcan
9705 proc fontcan {} {
9706 global fonttop fontparam
9708 if {[info exists fonttop]} {
9709 catch {destroy $fonttop}
9710 catch {font delete sample}
9711 unset fonttop
9712 unset fontparam
9716 proc selfontfam {} {
9717 global fonttop fontparam
9719 set i [$fonttop.f.fam curselection]
9720 if {$i ne {}} {
9721 set fontparam(family) [$fonttop.f.fam get $i]
9725 proc chg_fontparam {v sub op} {
9726 global fontparam
9728 font config sample -$sub $fontparam($sub)
9731 proc doprefs {} {
9732 global maxwidth maxgraphpct
9733 global oldprefs prefstop showneartags showlocalchanges
9734 global bgcolor fgcolor ctext diffcolors selectbgcolor
9735 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9737 set top .gitkprefs
9738 set prefstop $top
9739 if {[winfo exists $top]} {
9740 raise $top
9741 return
9743 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9744 limitdiffs tabstop perfile_attrs} {
9745 set oldprefs($v) [set $v]
9747 toplevel $top
9748 wm title $top [mc "Gitk preferences"]
9749 label $top.ldisp -text [mc "Commit list display options"]
9750 grid $top.ldisp - -sticky w -pady 10
9751 label $top.spacer -text " "
9752 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9753 -font optionfont
9754 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9755 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9756 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9757 -font optionfont
9758 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9759 grid x $top.maxpctl $top.maxpct -sticky w
9760 frame $top.showlocal
9761 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9762 checkbutton $top.showlocal.b -variable showlocalchanges
9763 pack $top.showlocal.b $top.showlocal.l -side left
9764 grid x $top.showlocal -sticky w
9765 frame $top.autoselect
9766 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9767 checkbutton $top.autoselect.b -variable autoselect
9768 pack $top.autoselect.b $top.autoselect.l -side left
9769 grid x $top.autoselect -sticky w
9771 label $top.ddisp -text [mc "Diff display options"]
9772 grid $top.ddisp - -sticky w -pady 10
9773 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9774 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9775 grid x $top.tabstopl $top.tabstop -sticky w
9776 frame $top.ntag
9777 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9778 checkbutton $top.ntag.b -variable showneartags
9779 pack $top.ntag.b $top.ntag.l -side left
9780 grid x $top.ntag -sticky w
9781 frame $top.ldiff
9782 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9783 checkbutton $top.ldiff.b -variable limitdiffs
9784 pack $top.ldiff.b $top.ldiff.l -side left
9785 grid x $top.ldiff -sticky w
9786 frame $top.lattr
9787 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
9788 checkbutton $top.lattr.b -variable perfile_attrs
9789 pack $top.lattr.b $top.lattr.l -side left
9790 grid x $top.lattr -sticky w
9792 entry $top.extdifft -textvariable extdifftool
9793 frame $top.extdifff
9794 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9795 -padx 10
9796 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9797 -command choose_extdiff
9798 pack $top.extdifff.l $top.extdifff.b -side left
9799 grid x $top.extdifff $top.extdifft -sticky w
9801 label $top.cdisp -text [mc "Colors: press to choose"]
9802 grid $top.cdisp - -sticky w -pady 10
9803 label $top.bg -padx 40 -relief sunk -background $bgcolor
9804 button $top.bgbut -text [mc "Background"] -font optionfont \
9805 -command [list choosecolor bgcolor {} $top.bg background setbg]
9806 grid x $top.bgbut $top.bg -sticky w
9807 label $top.fg -padx 40 -relief sunk -background $fgcolor
9808 button $top.fgbut -text [mc "Foreground"] -font optionfont \
9809 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
9810 grid x $top.fgbut $top.fg -sticky w
9811 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
9812 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
9813 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
9814 [list $ctext tag conf d0 -foreground]]
9815 grid x $top.diffoldbut $top.diffold -sticky w
9816 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
9817 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
9818 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
9819 [list $ctext tag conf d1 -foreground]]
9820 grid x $top.diffnewbut $top.diffnew -sticky w
9821 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
9822 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
9823 -command [list choosecolor diffcolors 2 $top.hunksep \
9824 "diff hunk header" \
9825 [list $ctext tag conf hunksep -foreground]]
9826 grid x $top.hunksepbut $top.hunksep -sticky w
9827 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
9828 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
9829 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
9830 grid x $top.selbgbut $top.selbgsep -sticky w
9832 label $top.cfont -text [mc "Fonts: press to choose"]
9833 grid $top.cfont - -sticky w -pady 10
9834 mkfontdisp mainfont $top [mc "Main font"]
9835 mkfontdisp textfont $top [mc "Diff display font"]
9836 mkfontdisp uifont $top [mc "User interface font"]
9838 frame $top.buts
9839 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
9840 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
9841 grid $top.buts.ok $top.buts.can
9842 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9843 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9844 grid $top.buts - - -pady 10 -sticky ew
9845 bind $top <Visibility> "focus $top.buts.ok"
9848 proc choose_extdiff {} {
9849 global extdifftool
9851 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
9852 if {$prog ne {}} {
9853 set extdifftool $prog
9857 proc choosecolor {v vi w x cmd} {
9858 global $v
9860 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
9861 -title [mc "Gitk: choose color for %s" $x]]
9862 if {$c eq {}} return
9863 $w conf -background $c
9864 lset $v $vi $c
9865 eval $cmd $c
9868 proc setselbg {c} {
9869 global bglist cflist
9870 foreach w $bglist {
9871 $w configure -selectbackground $c
9873 $cflist tag configure highlight \
9874 -background [$cflist cget -selectbackground]
9875 allcanvs itemconf secsel -fill $c
9878 proc setbg {c} {
9879 global bglist
9881 foreach w $bglist {
9882 $w conf -background $c
9886 proc setfg {c} {
9887 global fglist canv
9889 foreach w $fglist {
9890 $w conf -foreground $c
9892 allcanvs itemconf text -fill $c
9893 $canv itemconf circle -outline $c
9896 proc prefscan {} {
9897 global oldprefs prefstop
9899 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9900 limitdiffs tabstop perfile_attrs} {
9901 global $v
9902 set $v $oldprefs($v)
9904 catch {destroy $prefstop}
9905 unset prefstop
9906 fontcan
9909 proc prefsok {} {
9910 global maxwidth maxgraphpct
9911 global oldprefs prefstop showneartags showlocalchanges
9912 global fontpref mainfont textfont uifont
9913 global limitdiffs treediffs perfile_attrs
9915 catch {destroy $prefstop}
9916 unset prefstop
9917 fontcan
9918 set fontchanged 0
9919 if {$mainfont ne $fontpref(mainfont)} {
9920 set mainfont $fontpref(mainfont)
9921 parsefont mainfont $mainfont
9922 eval font configure mainfont [fontflags mainfont]
9923 eval font configure mainfontbold [fontflags mainfont 1]
9924 setcoords
9925 set fontchanged 1
9927 if {$textfont ne $fontpref(textfont)} {
9928 set textfont $fontpref(textfont)
9929 parsefont textfont $textfont
9930 eval font configure textfont [fontflags textfont]
9931 eval font configure textfontbold [fontflags textfont 1]
9933 if {$uifont ne $fontpref(uifont)} {
9934 set uifont $fontpref(uifont)
9935 parsefont uifont $uifont
9936 eval font configure uifont [fontflags uifont]
9938 settabs
9939 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
9940 if {$showlocalchanges} {
9941 doshowlocalchanges
9942 } else {
9943 dohidelocalchanges
9946 if {$limitdiffs != $oldprefs(limitdiffs) ||
9947 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
9948 # treediffs elements are limited by path;
9949 # won't have encodings cached if perfile_attrs was just turned on
9950 catch {unset treediffs}
9952 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
9953 || $maxgraphpct != $oldprefs(maxgraphpct)} {
9954 redisplay
9955 } elseif {$showneartags != $oldprefs(showneartags) ||
9956 $limitdiffs != $oldprefs(limitdiffs)} {
9957 reselectline
9961 proc formatdate {d} {
9962 global datetimeformat
9963 if {$d ne {}} {
9964 set d [clock format $d -format $datetimeformat]
9966 return $d
9969 # This list of encoding names and aliases is distilled from
9970 # http://www.iana.org/assignments/character-sets.
9971 # Not all of them are supported by Tcl.
9972 set encoding_aliases {
9973 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
9974 ISO646-US US-ASCII us IBM367 cp367 csASCII }
9975 { ISO-10646-UTF-1 csISO10646UTF1 }
9976 { ISO_646.basic:1983 ref csISO646basic1983 }
9977 { INVARIANT csINVARIANT }
9978 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
9979 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
9980 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
9981 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
9982 { NATS-DANO iso-ir-9-1 csNATSDANO }
9983 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
9984 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
9985 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
9986 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
9987 { ISO-2022-KR csISO2022KR }
9988 { EUC-KR csEUCKR }
9989 { ISO-2022-JP csISO2022JP }
9990 { ISO-2022-JP-2 csISO2022JP2 }
9991 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
9992 csISO13JISC6220jp }
9993 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
9994 { IT iso-ir-15 ISO646-IT csISO15Italian }
9995 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
9996 { ES iso-ir-17 ISO646-ES csISO17Spanish }
9997 { greek7-old iso-ir-18 csISO18Greek7Old }
9998 { latin-greek iso-ir-19 csISO19LatinGreek }
9999 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10000 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10001 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10002 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10003 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10004 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10005 { INIS iso-ir-49 csISO49INIS }
10006 { INIS-8 iso-ir-50 csISO50INIS8 }
10007 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10008 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10009 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10010 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10011 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10012 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10013 csISO60Norwegian1 }
10014 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10015 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10016 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10017 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10018 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10019 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10020 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10021 { greek7 iso-ir-88 csISO88Greek7 }
10022 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10023 { iso-ir-90 csISO90 }
10024 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10025 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10026 csISO92JISC62991984b }
10027 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10028 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10029 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10030 csISO95JIS62291984handadd }
10031 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10032 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10033 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10034 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10035 CP819 csISOLatin1 }
10036 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10037 { T.61-7bit iso-ir-102 csISO102T617bit }
10038 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10039 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10040 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10041 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10042 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10043 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10044 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10045 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10046 arabic csISOLatinArabic }
10047 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10048 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10049 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10050 greek greek8 csISOLatinGreek }
10051 { T.101-G2 iso-ir-128 csISO128T101G2 }
10052 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10053 csISOLatinHebrew }
10054 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10055 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10056 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10057 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10058 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10059 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10060 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10061 csISOLatinCyrillic }
10062 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10063 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10064 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10065 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10066 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10067 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10068 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10069 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10070 { ISO_10367-box iso-ir-155 csISO10367Box }
10071 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10072 { latin-lap lap iso-ir-158 csISO158Lap }
10073 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10074 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10075 { us-dk csUSDK }
10076 { dk-us csDKUS }
10077 { JIS_X0201 X0201 csHalfWidthKatakana }
10078 { KSC5636 ISO646-KR csKSC5636 }
10079 { ISO-10646-UCS-2 csUnicode }
10080 { ISO-10646-UCS-4 csUCS4 }
10081 { DEC-MCS dec csDECMCS }
10082 { hp-roman8 roman8 r8 csHPRoman8 }
10083 { macintosh mac csMacintosh }
10084 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10085 csIBM037 }
10086 { IBM038 EBCDIC-INT cp038 csIBM038 }
10087 { IBM273 CP273 csIBM273 }
10088 { IBM274 EBCDIC-BE CP274 csIBM274 }
10089 { IBM275 EBCDIC-BR cp275 csIBM275 }
10090 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10091 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10092 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10093 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10094 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10095 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10096 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10097 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10098 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10099 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10100 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10101 { IBM437 cp437 437 csPC8CodePage437 }
10102 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10103 { IBM775 cp775 csPC775Baltic }
10104 { IBM850 cp850 850 csPC850Multilingual }
10105 { IBM851 cp851 851 csIBM851 }
10106 { IBM852 cp852 852 csPCp852 }
10107 { IBM855 cp855 855 csIBM855 }
10108 { IBM857 cp857 857 csIBM857 }
10109 { IBM860 cp860 860 csIBM860 }
10110 { IBM861 cp861 861 cp-is csIBM861 }
10111 { IBM862 cp862 862 csPC862LatinHebrew }
10112 { IBM863 cp863 863 csIBM863 }
10113 { IBM864 cp864 csIBM864 }
10114 { IBM865 cp865 865 csIBM865 }
10115 { IBM866 cp866 866 csIBM866 }
10116 { IBM868 CP868 cp-ar csIBM868 }
10117 { IBM869 cp869 869 cp-gr csIBM869 }
10118 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10119 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10120 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10121 { IBM891 cp891 csIBM891 }
10122 { IBM903 cp903 csIBM903 }
10123 { IBM904 cp904 904 csIBBM904 }
10124 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10125 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10126 { IBM1026 CP1026 csIBM1026 }
10127 { EBCDIC-AT-DE csIBMEBCDICATDE }
10128 { EBCDIC-AT-DE-A csEBCDICATDEA }
10129 { EBCDIC-CA-FR csEBCDICCAFR }
10130 { EBCDIC-DK-NO csEBCDICDKNO }
10131 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10132 { EBCDIC-FI-SE csEBCDICFISE }
10133 { EBCDIC-FI-SE-A csEBCDICFISEA }
10134 { EBCDIC-FR csEBCDICFR }
10135 { EBCDIC-IT csEBCDICIT }
10136 { EBCDIC-PT csEBCDICPT }
10137 { EBCDIC-ES csEBCDICES }
10138 { EBCDIC-ES-A csEBCDICESA }
10139 { EBCDIC-ES-S csEBCDICESS }
10140 { EBCDIC-UK csEBCDICUK }
10141 { EBCDIC-US csEBCDICUS }
10142 { UNKNOWN-8BIT csUnknown8BiT }
10143 { MNEMONIC csMnemonic }
10144 { MNEM csMnem }
10145 { VISCII csVISCII }
10146 { VIQR csVIQR }
10147 { KOI8-R csKOI8R }
10148 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10149 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10150 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10151 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10152 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10153 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10154 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10155 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10156 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10157 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10158 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10159 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10160 { IBM1047 IBM-1047 }
10161 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10162 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10163 { UNICODE-1-1 csUnicode11 }
10164 { CESU-8 csCESU-8 }
10165 { BOCU-1 csBOCU-1 }
10166 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10167 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10168 l8 }
10169 { ISO-8859-15 ISO_8859-15 Latin-9 }
10170 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10171 { GBK CP936 MS936 windows-936 }
10172 { JIS_Encoding csJISEncoding }
10173 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10174 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10175 EUC-JP }
10176 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10177 { ISO-10646-UCS-Basic csUnicodeASCII }
10178 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10179 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10180 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10181 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10182 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10183 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10184 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10185 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10186 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10187 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10188 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10189 { Ventura-US csVenturaUS }
10190 { Ventura-International csVenturaInternational }
10191 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10192 { PC8-Turkish csPC8Turkish }
10193 { IBM-Symbols csIBMSymbols }
10194 { IBM-Thai csIBMThai }
10195 { HP-Legal csHPLegal }
10196 { HP-Pi-font csHPPiFont }
10197 { HP-Math8 csHPMath8 }
10198 { Adobe-Symbol-Encoding csHPPSMath }
10199 { HP-DeskTop csHPDesktop }
10200 { Ventura-Math csVenturaMath }
10201 { Microsoft-Publishing csMicrosoftPublishing }
10202 { Windows-31J csWindows31J }
10203 { GB2312 csGB2312 }
10204 { Big5 csBig5 }
10207 proc tcl_encoding {enc} {
10208 global encoding_aliases tcl_encoding_cache
10209 if {[info exists tcl_encoding_cache($enc)]} {
10210 return $tcl_encoding_cache($enc)
10212 set names [encoding names]
10213 set lcnames [string tolower $names]
10214 set enc [string tolower $enc]
10215 set i [lsearch -exact $lcnames $enc]
10216 if {$i < 0} {
10217 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10218 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10219 set i [lsearch -exact $lcnames $encx]
10222 if {$i < 0} {
10223 foreach l $encoding_aliases {
10224 set ll [string tolower $l]
10225 if {[lsearch -exact $ll $enc] < 0} continue
10226 # look through the aliases for one that tcl knows about
10227 foreach e $ll {
10228 set i [lsearch -exact $lcnames $e]
10229 if {$i < 0} {
10230 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10231 set i [lsearch -exact $lcnames $ex]
10234 if {$i >= 0} break
10236 break
10239 set tclenc {}
10240 if {$i >= 0} {
10241 set tclenc [lindex $names $i]
10243 set tcl_encoding_cache($enc) $tclenc
10244 return $tclenc
10247 proc gitattr {path attr default} {
10248 global path_attr_cache
10249 if {[info exists path_attr_cache($attr,$path)]} {
10250 set r $path_attr_cache($attr,$path)
10251 } else {
10252 set r "unspecified"
10253 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10254 regexp "(.*): encoding: (.*)" $line m f r
10256 set path_attr_cache($attr,$path) $r
10258 if {$r eq "unspecified"} {
10259 return $default
10261 return $r
10264 proc cache_gitattr {attr pathlist} {
10265 global path_attr_cache
10266 set newlist {}
10267 foreach path $pathlist {
10268 if {![info exists path_attr_cache($attr,$path)]} {
10269 lappend newlist $path
10272 set lim 1000
10273 if {[tk windowingsystem] == "win32"} {
10274 # windows has a 32k limit on the arguments to a command...
10275 set lim 30
10277 while {$newlist ne {}} {
10278 set head [lrange $newlist 0 [expr {$lim - 1}]]
10279 set newlist [lrange $newlist $lim end]
10280 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10281 foreach row [split $rlist "\n"] {
10282 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10283 if {[string index $path 0] eq "\""} {
10284 set path [encoding convertfrom [lindex $path 0]]
10286 set path_attr_cache($attr,$path) $value
10293 proc get_path_encoding {path} {
10294 global gui_encoding perfile_attrs
10295 set tcl_enc $gui_encoding
10296 if {$path ne {} && $perfile_attrs} {
10297 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10298 if {$enc2 ne {}} {
10299 set tcl_enc $enc2
10302 return $tcl_enc
10305 # First check that Tcl/Tk is recent enough
10306 if {[catch {package require Tk 8.4} err]} {
10307 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10308 Gitk requires at least Tcl/Tk 8.4."]
10309 exit 1
10312 # defaults...
10313 set wrcomcmd "git diff-tree --stdin -p --pretty"
10315 set gitencoding {}
10316 catch {
10317 set gitencoding [exec git config --get i18n.commitencoding]
10319 if {$gitencoding == ""} {
10320 set gitencoding "utf-8"
10322 set tclencoding [tcl_encoding $gitencoding]
10323 if {$tclencoding == {}} {
10324 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10327 set gui_encoding [encoding system]
10328 catch {
10329 set enc [exec git config --get gui.encoding]
10330 if {$enc ne {}} {
10331 set tclenc [tcl_encoding $enc]
10332 if {$tclenc ne {}} {
10333 set gui_encoding $tclenc
10334 } else {
10335 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10340 set mainfont {Helvetica 9}
10341 set textfont {Courier 9}
10342 set uifont {Helvetica 9 bold}
10343 set tabstop 8
10344 set findmergefiles 0
10345 set maxgraphpct 50
10346 set maxwidth 16
10347 set revlistorder 0
10348 set fastdate 0
10349 set uparrowlen 5
10350 set downarrowlen 5
10351 set mingaplen 100
10352 set cmitmode "patch"
10353 set wrapcomment "none"
10354 set showneartags 1
10355 set maxrefs 20
10356 set maxlinelen 200
10357 set showlocalchanges 1
10358 set limitdiffs 1
10359 set datetimeformat "%Y-%m-%d %H:%M:%S"
10360 set autoselect 1
10361 set perfile_attrs 0
10363 set extdifftool "meld"
10365 set colors {green red blue magenta darkgrey brown orange}
10366 set bgcolor white
10367 set fgcolor black
10368 set diffcolors {red "#00a000" blue}
10369 set diffcontext 3
10370 set ignorespace 0
10371 set selectbgcolor gray85
10373 set circlecolors {white blue gray blue blue}
10375 # button for popping up context menus
10376 if {[tk windowingsystem] eq "aqua"} {
10377 set ctxbut <Button-2>
10378 } else {
10379 set ctxbut <Button-3>
10382 ## For msgcat loading, first locate the installation location.
10383 if { [info exists ::env(GITK_MSGSDIR)] } {
10384 ## Msgsdir was manually set in the environment.
10385 set gitk_msgsdir $::env(GITK_MSGSDIR)
10386 } else {
10387 ## Let's guess the prefix from argv0.
10388 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10389 set gitk_libdir [file join $gitk_prefix share gitk lib]
10390 set gitk_msgsdir [file join $gitk_libdir msgs]
10391 unset gitk_prefix
10394 ## Internationalization (i18n) through msgcat and gettext. See
10395 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10396 package require msgcat
10397 namespace import ::msgcat::mc
10398 ## And eventually load the actual message catalog
10399 ::msgcat::mcload $gitk_msgsdir
10401 catch {source ~/.gitk}
10403 font create optionfont -family sans-serif -size -12
10405 parsefont mainfont $mainfont
10406 eval font create mainfont [fontflags mainfont]
10407 eval font create mainfontbold [fontflags mainfont 1]
10409 parsefont textfont $textfont
10410 eval font create textfont [fontflags textfont]
10411 eval font create textfontbold [fontflags textfont 1]
10413 parsefont uifont $uifont
10414 eval font create uifont [fontflags uifont]
10416 setoptions
10418 # check that we can find a .git directory somewhere...
10419 if {[catch {set gitdir [gitdir]}]} {
10420 show_error {} . [mc "Cannot find a git repository here."]
10421 exit 1
10423 if {![file isdirectory $gitdir]} {
10424 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10425 exit 1
10428 set selecthead {}
10429 set selectheadid {}
10431 set revtreeargs {}
10432 set cmdline_files {}
10433 set i 0
10434 set revtreeargscmd {}
10435 foreach arg $argv {
10436 switch -glob -- $arg {
10437 "" { }
10438 "--" {
10439 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10440 break
10442 "--select-commit=*" {
10443 set selecthead [string range $arg 16 end]
10445 "--argscmd=*" {
10446 set revtreeargscmd [string range $arg 10 end]
10448 default {
10449 lappend revtreeargs $arg
10452 incr i
10455 if {$selecthead eq "HEAD"} {
10456 set selecthead {}
10459 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10460 # no -- on command line, but some arguments (other than --argscmd)
10461 if {[catch {
10462 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10463 set cmdline_files [split $f "\n"]
10464 set n [llength $cmdline_files]
10465 set revtreeargs [lrange $revtreeargs 0 end-$n]
10466 # Unfortunately git rev-parse doesn't produce an error when
10467 # something is both a revision and a filename. To be consistent
10468 # with git log and git rev-list, check revtreeargs for filenames.
10469 foreach arg $revtreeargs {
10470 if {[file exists $arg]} {
10471 show_error {} . [mc "Ambiguous argument '%s': both revision\
10472 and filename" $arg]
10473 exit 1
10476 } err]} {
10477 # unfortunately we get both stdout and stderr in $err,
10478 # so look for "fatal:".
10479 set i [string first "fatal:" $err]
10480 if {$i > 0} {
10481 set err [string range $err [expr {$i + 6}] end]
10483 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10484 exit 1
10488 set nullid "0000000000000000000000000000000000000000"
10489 set nullid2 "0000000000000000000000000000000000000001"
10490 set nullfile "/dev/null"
10492 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10494 set runq {}
10495 set history {}
10496 set historyindex 0
10497 set fh_serial 0
10498 set nhl_names {}
10499 set highlight_paths {}
10500 set findpattern {}
10501 set searchdirn -forwards
10502 set boldrows {}
10503 set boldnamerows {}
10504 set diffelide {0 0}
10505 set markingmatches 0
10506 set linkentercount 0
10507 set need_redisplay 0
10508 set nrows_drawn 0
10509 set firsttabstop 0
10511 set nextviewnum 1
10512 set curview 0
10513 set selectedview 0
10514 set selectedhlview [mc "None"]
10515 set highlight_related [mc "None"]
10516 set highlight_files {}
10517 set viewfiles(0) {}
10518 set viewperm(0) 0
10519 set viewargs(0) {}
10520 set viewargscmd(0) {}
10522 set selectedline {}
10523 set numcommits 0
10524 set loginstance 0
10525 set cmdlineok 0
10526 set stopped 0
10527 set stuffsaved 0
10528 set patchnum 0
10529 set lserial 0
10530 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10531 setcoords
10532 makewindow
10533 # wait for the window to become visible
10534 tkwait visibility .
10535 wm title . "[file tail $argv0]: [file tail [pwd]]"
10536 readrefs
10538 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10539 # create a view for the files/dirs specified on the command line
10540 set curview 1
10541 set selectedview 1
10542 set nextviewnum 2
10543 set viewname(1) [mc "Command line"]
10544 set viewfiles(1) $cmdline_files
10545 set viewargs(1) $revtreeargs
10546 set viewargscmd(1) $revtreeargscmd
10547 set viewperm(1) 0
10548 set vdatemode(1) 0
10549 addviewmenu 1
10550 .bar.view entryconf [mca "Edit view..."] -state normal
10551 .bar.view entryconf [mca "Delete view"] -state normal
10554 if {[info exists permviews]} {
10555 foreach v $permviews {
10556 set n $nextviewnum
10557 incr nextviewnum
10558 set viewname($n) [lindex $v 0]
10559 set viewfiles($n) [lindex $v 1]
10560 set viewargs($n) [lindex $v 2]
10561 set viewargscmd($n) [lindex $v 3]
10562 set viewperm($n) 1
10563 addviewmenu $n
10566 getcommits {}