gitk: Implement a user-friendly Edit View dialog
[git.git] / gitk
blob2048798bcf7c1207f7ea86ed5cfad2d34fd22f25
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 bind $top <Key-space> "destroy $top"
1750 bind $top <Key-Escape> "destroy $top"
1751 tkwait window $top
1754 proc error_popup {msg {owner .}} {
1755 set w .error
1756 toplevel $w
1757 wm transient $w $owner
1758 show_error $w $w $msg
1761 proc confirm_popup {msg {owner .}} {
1762 global confirm_ok
1763 set confirm_ok 0
1764 set w .confirm
1765 toplevel $w
1766 wm transient $w $owner
1767 message $w.m -text $msg -justify center -aspect 400
1768 pack $w.m -side top -fill x -padx 20 -pady 20
1769 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1770 pack $w.ok -side left -fill x
1771 button $w.cancel -text [mc Cancel] -command "destroy $w"
1772 pack $w.cancel -side right -fill x
1773 bind $w <Visibility> "grab $w; focus $w"
1774 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1775 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1776 bind $w <Key-Escape> "destroy $w"
1777 tkwait window $w
1778 return $confirm_ok
1781 proc setoptions {} {
1782 option add *Panedwindow.showHandle 1 startupFile
1783 option add *Panedwindow.sashRelief raised startupFile
1784 option add *Button.font uifont startupFile
1785 option add *Checkbutton.font uifont startupFile
1786 option add *Radiobutton.font uifont startupFile
1787 option add *Menu.font uifont startupFile
1788 option add *Menubutton.font uifont startupFile
1789 option add *Label.font uifont startupFile
1790 option add *Message.font uifont startupFile
1791 option add *Entry.font uifont startupFile
1794 # Make a menu and submenus.
1795 # m is the window name for the menu, items is the list of menu items to add.
1796 # Each item is a list {mc label type description options...}
1797 # mc is ignored; it's so we can put mc there to alert xgettext
1798 # label is the string that appears in the menu
1799 # type is cascade, command or radiobutton (should add checkbutton)
1800 # description depends on type; it's the sublist for cascade, the
1801 # command to invoke for command, or {variable value} for radiobutton
1802 proc makemenu {m items} {
1803 menu $m
1804 foreach i $items {
1805 set name [mc [lindex $i 1]]
1806 set type [lindex $i 2]
1807 set thing [lindex $i 3]
1808 set params [list $type]
1809 if {$name ne {}} {
1810 set u [string first "&" [string map {&& x} $name]]
1811 lappend params -label [string map {&& & & {}} $name]
1812 if {$u >= 0} {
1813 lappend params -underline $u
1816 switch -- $type {
1817 "cascade" {
1818 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1819 lappend params -menu $m.$submenu
1821 "command" {
1822 lappend params -command $thing
1824 "radiobutton" {
1825 lappend params -variable [lindex $thing 0] \
1826 -value [lindex $thing 1]
1829 eval $m add $params [lrange $i 4 end]
1830 if {$type eq "cascade"} {
1831 makemenu $m.$submenu $thing
1836 # translate string and remove ampersands
1837 proc mca {str} {
1838 return [string map {&& & & {}} [mc $str]]
1841 proc makewindow {} {
1842 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1843 global tabstop
1844 global findtype findtypemenu findloc findstring fstring geometry
1845 global entries sha1entry sha1string sha1but
1846 global diffcontextstring diffcontext
1847 global ignorespace
1848 global maincursor textcursor curtextcursor
1849 global rowctxmenu fakerowmenu mergemax wrapcomment
1850 global highlight_files gdttype
1851 global searchstring sstring
1852 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1853 global headctxmenu progresscanv progressitem progresscoords statusw
1854 global fprogitem fprogcoord lastprogupdate progupdatepending
1855 global rprogitem rprogcoord rownumsel numcommits
1856 global have_tk85
1858 # The "mc" arguments here are purely so that xgettext
1859 # sees the following string as needing to be translated
1860 makemenu .bar {
1861 {mc "File" cascade {
1862 {mc "Update" command updatecommits -accelerator F5}
1863 {mc "Reload" command reloadcommits}
1864 {mc "Reread references" command rereadrefs}
1865 {mc "List references" command showrefs}
1866 {mc "Quit" command doquit}
1868 {mc "Edit" cascade {
1869 {mc "Preferences" command doprefs}
1871 {mc "View" cascade {
1872 {mc "New view..." command {newview 0}}
1873 {mc "Edit view..." command editview -state disabled}
1874 {mc "Delete view" command delview -state disabled}
1875 {xx "" separator}
1876 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1878 {mc "Help" cascade {
1879 {mc "About gitk" command about}
1880 {mc "Key bindings" command keys}
1883 . configure -menu .bar
1885 # the gui has upper and lower half, parts of a paned window.
1886 panedwindow .ctop -orient vertical
1888 # possibly use assumed geometry
1889 if {![info exists geometry(pwsash0)]} {
1890 set geometry(topheight) [expr {15 * $linespc}]
1891 set geometry(topwidth) [expr {80 * $charspc}]
1892 set geometry(botheight) [expr {15 * $linespc}]
1893 set geometry(botwidth) [expr {50 * $charspc}]
1894 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1895 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1898 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1899 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1900 frame .tf.histframe
1901 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1903 # create three canvases
1904 set cscroll .tf.histframe.csb
1905 set canv .tf.histframe.pwclist.canv
1906 canvas $canv \
1907 -selectbackground $selectbgcolor \
1908 -background $bgcolor -bd 0 \
1909 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1910 .tf.histframe.pwclist add $canv
1911 set canv2 .tf.histframe.pwclist.canv2
1912 canvas $canv2 \
1913 -selectbackground $selectbgcolor \
1914 -background $bgcolor -bd 0 -yscrollincr $linespc
1915 .tf.histframe.pwclist add $canv2
1916 set canv3 .tf.histframe.pwclist.canv3
1917 canvas $canv3 \
1918 -selectbackground $selectbgcolor \
1919 -background $bgcolor -bd 0 -yscrollincr $linespc
1920 .tf.histframe.pwclist add $canv3
1921 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1922 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1924 # a scroll bar to rule them
1925 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1926 pack $cscroll -side right -fill y
1927 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1928 lappend bglist $canv $canv2 $canv3
1929 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1931 # we have two button bars at bottom of top frame. Bar 1
1932 frame .tf.bar
1933 frame .tf.lbar -height 15
1935 set sha1entry .tf.bar.sha1
1936 set entries $sha1entry
1937 set sha1but .tf.bar.sha1label
1938 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1939 -command gotocommit -width 8
1940 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1941 pack .tf.bar.sha1label -side left
1942 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1943 trace add variable sha1string write sha1change
1944 pack $sha1entry -side left -pady 2
1946 image create bitmap bm-left -data {
1947 #define left_width 16
1948 #define left_height 16
1949 static unsigned char left_bits[] = {
1950 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1951 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1952 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1954 image create bitmap bm-right -data {
1955 #define right_width 16
1956 #define right_height 16
1957 static unsigned char right_bits[] = {
1958 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
1959 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
1960 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
1962 button .tf.bar.leftbut -image bm-left -command goback \
1963 -state disabled -width 26
1964 pack .tf.bar.leftbut -side left -fill y
1965 button .tf.bar.rightbut -image bm-right -command goforw \
1966 -state disabled -width 26
1967 pack .tf.bar.rightbut -side left -fill y
1969 label .tf.bar.rowlabel -text [mc "Row"]
1970 set rownumsel {}
1971 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
1972 -relief sunken -anchor e
1973 label .tf.bar.rowlabel2 -text "/"
1974 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
1975 -relief sunken -anchor e
1976 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
1977 -side left
1978 global selectedline
1979 trace add variable selectedline write selectedline_change
1981 # Status label and progress bar
1982 set statusw .tf.bar.status
1983 label $statusw -width 15 -relief sunken
1984 pack $statusw -side left -padx 5
1985 set h [expr {[font metrics uifont -linespace] + 2}]
1986 set progresscanv .tf.bar.progress
1987 canvas $progresscanv -relief sunken -height $h -borderwidth 2
1988 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
1989 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
1990 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
1991 pack $progresscanv -side right -expand 1 -fill x
1992 set progresscoords {0 0}
1993 set fprogcoord 0
1994 set rprogcoord 0
1995 bind $progresscanv <Configure> adjustprogress
1996 set lastprogupdate [clock clicks -milliseconds]
1997 set progupdatepending 0
1999 # build up the bottom bar of upper window
2000 label .tf.lbar.flabel -text "[mc "Find"] "
2001 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2002 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2003 label .tf.lbar.flab2 -text " [mc "commit"] "
2004 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2005 -side left -fill y
2006 set gdttype [mc "containing:"]
2007 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2008 [mc "containing:"] \
2009 [mc "touching paths:"] \
2010 [mc "adding/removing string:"]]
2011 trace add variable gdttype write gdttype_change
2012 pack .tf.lbar.gdttype -side left -fill y
2014 set findstring {}
2015 set fstring .tf.lbar.findstring
2016 lappend entries $fstring
2017 entry $fstring -width 30 -font textfont -textvariable findstring
2018 trace add variable findstring write find_change
2019 set findtype [mc "Exact"]
2020 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2021 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2022 trace add variable findtype write findcom_change
2023 set findloc [mc "All fields"]
2024 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2025 [mc "Comments"] [mc "Author"] [mc "Committer"]
2026 trace add variable findloc write find_change
2027 pack .tf.lbar.findloc -side right
2028 pack .tf.lbar.findtype -side right
2029 pack $fstring -side left -expand 1 -fill x
2031 # Finish putting the upper half of the viewer together
2032 pack .tf.lbar -in .tf -side bottom -fill x
2033 pack .tf.bar -in .tf -side bottom -fill x
2034 pack .tf.histframe -fill both -side top -expand 1
2035 .ctop add .tf
2036 .ctop paneconfigure .tf -height $geometry(topheight)
2037 .ctop paneconfigure .tf -width $geometry(topwidth)
2039 # now build up the bottom
2040 panedwindow .pwbottom -orient horizontal
2042 # lower left, a text box over search bar, scroll bar to the right
2043 # if we know window height, then that will set the lower text height, otherwise
2044 # we set lower text height which will drive window height
2045 if {[info exists geometry(main)]} {
2046 frame .bleft -width $geometry(botwidth)
2047 } else {
2048 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2050 frame .bleft.top
2051 frame .bleft.mid
2052 frame .bleft.bottom
2054 button .bleft.top.search -text [mc "Search"] -command dosearch
2055 pack .bleft.top.search -side left -padx 5
2056 set sstring .bleft.top.sstring
2057 entry $sstring -width 20 -font textfont -textvariable searchstring
2058 lappend entries $sstring
2059 trace add variable searchstring write incrsearch
2060 pack $sstring -side left -expand 1 -fill x
2061 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2062 -command changediffdisp -variable diffelide -value {0 0}
2063 radiobutton .bleft.mid.old -text [mc "Old version"] \
2064 -command changediffdisp -variable diffelide -value {0 1}
2065 radiobutton .bleft.mid.new -text [mc "New version"] \
2066 -command changediffdisp -variable diffelide -value {1 0}
2067 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2068 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2069 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2070 -from 1 -increment 1 -to 10000000 \
2071 -validate all -validatecommand "diffcontextvalidate %P" \
2072 -textvariable diffcontextstring
2073 .bleft.mid.diffcontext set $diffcontext
2074 trace add variable diffcontextstring write diffcontextchange
2075 lappend entries .bleft.mid.diffcontext
2076 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2077 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2078 -command changeignorespace -variable ignorespace
2079 pack .bleft.mid.ignspace -side left -padx 5
2080 set ctext .bleft.bottom.ctext
2081 text $ctext -background $bgcolor -foreground $fgcolor \
2082 -state disabled -font textfont \
2083 -yscrollcommand scrolltext -wrap none \
2084 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2085 if {$have_tk85} {
2086 $ctext conf -tabstyle wordprocessor
2088 scrollbar .bleft.bottom.sb -command "$ctext yview"
2089 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2090 -width 10
2091 pack .bleft.top -side top -fill x
2092 pack .bleft.mid -side top -fill x
2093 grid $ctext .bleft.bottom.sb -sticky nsew
2094 grid .bleft.bottom.sbhorizontal -sticky ew
2095 grid columnconfigure .bleft.bottom 0 -weight 1
2096 grid rowconfigure .bleft.bottom 0 -weight 1
2097 grid rowconfigure .bleft.bottom 1 -weight 0
2098 pack .bleft.bottom -side top -fill both -expand 1
2099 lappend bglist $ctext
2100 lappend fglist $ctext
2102 $ctext tag conf comment -wrap $wrapcomment
2103 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2104 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2105 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2106 $ctext tag conf d1 -fore [lindex $diffcolors 1]
2107 $ctext tag conf m0 -fore red
2108 $ctext tag conf m1 -fore blue
2109 $ctext tag conf m2 -fore green
2110 $ctext tag conf m3 -fore purple
2111 $ctext tag conf m4 -fore brown
2112 $ctext tag conf m5 -fore "#009090"
2113 $ctext tag conf m6 -fore magenta
2114 $ctext tag conf m7 -fore "#808000"
2115 $ctext tag conf m8 -fore "#009000"
2116 $ctext tag conf m9 -fore "#ff0080"
2117 $ctext tag conf m10 -fore cyan
2118 $ctext tag conf m11 -fore "#b07070"
2119 $ctext tag conf m12 -fore "#70b0f0"
2120 $ctext tag conf m13 -fore "#70f0b0"
2121 $ctext tag conf m14 -fore "#f0b070"
2122 $ctext tag conf m15 -fore "#ff70b0"
2123 $ctext tag conf mmax -fore darkgrey
2124 set mergemax 16
2125 $ctext tag conf mresult -font textfontbold
2126 $ctext tag conf msep -font textfontbold
2127 $ctext tag conf found -back yellow
2129 .pwbottom add .bleft
2130 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2132 # lower right
2133 frame .bright
2134 frame .bright.mode
2135 radiobutton .bright.mode.patch -text [mc "Patch"] \
2136 -command reselectline -variable cmitmode -value "patch"
2137 radiobutton .bright.mode.tree -text [mc "Tree"] \
2138 -command reselectline -variable cmitmode -value "tree"
2139 grid .bright.mode.patch .bright.mode.tree -sticky ew
2140 pack .bright.mode -side top -fill x
2141 set cflist .bright.cfiles
2142 set indent [font measure mainfont "nn"]
2143 text $cflist \
2144 -selectbackground $selectbgcolor \
2145 -background $bgcolor -foreground $fgcolor \
2146 -font mainfont \
2147 -tabs [list $indent [expr {2 * $indent}]] \
2148 -yscrollcommand ".bright.sb set" \
2149 -cursor [. cget -cursor] \
2150 -spacing1 1 -spacing3 1
2151 lappend bglist $cflist
2152 lappend fglist $cflist
2153 scrollbar .bright.sb -command "$cflist yview"
2154 pack .bright.sb -side right -fill y
2155 pack $cflist -side left -fill both -expand 1
2156 $cflist tag configure highlight \
2157 -background [$cflist cget -selectbackground]
2158 $cflist tag configure bold -font mainfontbold
2160 .pwbottom add .bright
2161 .ctop add .pwbottom
2163 # restore window width & height if known
2164 if {[info exists geometry(main)]} {
2165 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2166 if {$w > [winfo screenwidth .]} {
2167 set w [winfo screenwidth .]
2169 if {$h > [winfo screenheight .]} {
2170 set h [winfo screenheight .]
2172 wm geometry . "${w}x$h"
2176 if {[tk windowingsystem] eq {aqua}} {
2177 set M1B M1
2178 } else {
2179 set M1B Control
2182 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2183 pack .ctop -fill both -expand 1
2184 bindall <1> {selcanvline %W %x %y}
2185 #bindall <B1-Motion> {selcanvline %W %x %y}
2186 if {[tk windowingsystem] == "win32"} {
2187 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2188 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2189 } else {
2190 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2191 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2192 if {[tk windowingsystem] eq "aqua"} {
2193 bindall <MouseWheel> {
2194 set delta [expr {- (%D)}]
2195 allcanvs yview scroll $delta units
2199 bindall <2> "canvscan mark %W %x %y"
2200 bindall <B2-Motion> "canvscan dragto %W %x %y"
2201 bindkey <Home> selfirstline
2202 bindkey <End> sellastline
2203 bind . <Key-Up> "selnextline -1"
2204 bind . <Key-Down> "selnextline 1"
2205 bind . <Shift-Key-Up> "dofind -1 0"
2206 bind . <Shift-Key-Down> "dofind 1 0"
2207 bindkey <Key-Right> "goforw"
2208 bindkey <Key-Left> "goback"
2209 bind . <Key-Prior> "selnextpage -1"
2210 bind . <Key-Next> "selnextpage 1"
2211 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2212 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2213 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2214 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2215 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2216 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2217 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2218 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2219 bindkey <Key-space> "$ctext yview scroll 1 pages"
2220 bindkey p "selnextline -1"
2221 bindkey n "selnextline 1"
2222 bindkey z "goback"
2223 bindkey x "goforw"
2224 bindkey i "selnextline -1"
2225 bindkey k "selnextline 1"
2226 bindkey j "goback"
2227 bindkey l "goforw"
2228 bindkey b prevfile
2229 bindkey d "$ctext yview scroll 18 units"
2230 bindkey u "$ctext yview scroll -18 units"
2231 bindkey / {dofind 1 1}
2232 bindkey <Key-Return> {dofind 1 1}
2233 bindkey ? {dofind -1 1}
2234 bindkey f nextfile
2235 bindkey <F5> updatecommits
2236 bind . <$M1B-q> doquit
2237 bind . <$M1B-f> {dofind 1 1}
2238 bind . <$M1B-g> {dofind 1 0}
2239 bind . <$M1B-r> dosearchback
2240 bind . <$M1B-s> dosearch
2241 bind . <$M1B-equal> {incrfont 1}
2242 bind . <$M1B-plus> {incrfont 1}
2243 bind . <$M1B-KP_Add> {incrfont 1}
2244 bind . <$M1B-minus> {incrfont -1}
2245 bind . <$M1B-KP_Subtract> {incrfont -1}
2246 wm protocol . WM_DELETE_WINDOW doquit
2247 bind . <Destroy> {stop_backends}
2248 bind . <Button-1> "click %W"
2249 bind $fstring <Key-Return> {dofind 1 1}
2250 bind $sha1entry <Key-Return> {gotocommit; break}
2251 bind $sha1entry <<PasteSelection>> clearsha1
2252 bind $cflist <1> {sel_flist %W %x %y; break}
2253 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2254 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2255 global ctxbut
2256 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2257 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2259 set maincursor [. cget -cursor]
2260 set textcursor [$ctext cget -cursor]
2261 set curtextcursor $textcursor
2263 set rowctxmenu .rowctxmenu
2264 makemenu $rowctxmenu {
2265 {mc "Diff this -> selected" command {diffvssel 0}}
2266 {mc "Diff selected -> this" command {diffvssel 1}}
2267 {mc "Make patch" command mkpatch}
2268 {mc "Create tag" command mktag}
2269 {mc "Write commit to file" command writecommit}
2270 {mc "Create new branch" command mkbranch}
2271 {mc "Cherry-pick this commit" command cherrypick}
2272 {mc "Reset HEAD branch to here" command resethead}
2274 $rowctxmenu configure -tearoff 0
2276 set fakerowmenu .fakerowmenu
2277 makemenu $fakerowmenu {
2278 {mc "Diff this -> selected" command {diffvssel 0}}
2279 {mc "Diff selected -> this" command {diffvssel 1}}
2280 {mc "Make patch" command mkpatch}
2282 $fakerowmenu configure -tearoff 0
2284 set headctxmenu .headctxmenu
2285 makemenu $headctxmenu {
2286 {mc "Check out this branch" command cobranch}
2287 {mc "Remove this branch" command rmbranch}
2289 $headctxmenu configure -tearoff 0
2291 global flist_menu
2292 set flist_menu .flistctxmenu
2293 makemenu $flist_menu {
2294 {mc "Highlight this too" command {flist_hl 0}}
2295 {mc "Highlight this only" command {flist_hl 1}}
2296 {mc "External diff" command {external_diff}}
2297 {mc "Blame parent commit" command {external_blame 1}}
2299 $flist_menu configure -tearoff 0
2301 global diff_menu
2302 set diff_menu .diffctxmenu
2303 makemenu $diff_menu {
2304 {mc "Show origin of this line" command show_line_source}
2305 {mc "Run git gui blame on this line" command {external_blame_diff}}
2307 $diff_menu configure -tearoff 0
2310 # Windows sends all mouse wheel events to the current focused window, not
2311 # the one where the mouse hovers, so bind those events here and redirect
2312 # to the correct window
2313 proc windows_mousewheel_redirector {W X Y D} {
2314 global canv canv2 canv3
2315 set w [winfo containing -displayof $W $X $Y]
2316 if {$w ne ""} {
2317 set u [expr {$D < 0 ? 5 : -5}]
2318 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2319 allcanvs yview scroll $u units
2320 } else {
2321 catch {
2322 $w yview scroll $u units
2328 # Update row number label when selectedline changes
2329 proc selectedline_change {n1 n2 op} {
2330 global selectedline rownumsel
2332 if {$selectedline eq {}} {
2333 set rownumsel {}
2334 } else {
2335 set rownumsel [expr {$selectedline + 1}]
2339 # mouse-2 makes all windows scan vertically, but only the one
2340 # the cursor is in scans horizontally
2341 proc canvscan {op w x y} {
2342 global canv canv2 canv3
2343 foreach c [list $canv $canv2 $canv3] {
2344 if {$c == $w} {
2345 $c scan $op $x $y
2346 } else {
2347 $c scan $op 0 $y
2352 proc scrollcanv {cscroll f0 f1} {
2353 $cscroll set $f0 $f1
2354 drawvisible
2355 flushhighlights
2358 # when we make a key binding for the toplevel, make sure
2359 # it doesn't get triggered when that key is pressed in the
2360 # find string entry widget.
2361 proc bindkey {ev script} {
2362 global entries
2363 bind . $ev $script
2364 set escript [bind Entry $ev]
2365 if {$escript == {}} {
2366 set escript [bind Entry <Key>]
2368 foreach e $entries {
2369 bind $e $ev "$escript; break"
2373 # set the focus back to the toplevel for any click outside
2374 # the entry widgets
2375 proc click {w} {
2376 global ctext entries
2377 foreach e [concat $entries $ctext] {
2378 if {$w == $e} return
2380 focus .
2383 # Adjust the progress bar for a change in requested extent or canvas size
2384 proc adjustprogress {} {
2385 global progresscanv progressitem progresscoords
2386 global fprogitem fprogcoord lastprogupdate progupdatepending
2387 global rprogitem rprogcoord
2389 set w [expr {[winfo width $progresscanv] - 4}]
2390 set x0 [expr {$w * [lindex $progresscoords 0]}]
2391 set x1 [expr {$w * [lindex $progresscoords 1]}]
2392 set h [winfo height $progresscanv]
2393 $progresscanv coords $progressitem $x0 0 $x1 $h
2394 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2395 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2396 set now [clock clicks -milliseconds]
2397 if {$now >= $lastprogupdate + 100} {
2398 set progupdatepending 0
2399 update
2400 } elseif {!$progupdatepending} {
2401 set progupdatepending 1
2402 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2406 proc doprogupdate {} {
2407 global lastprogupdate progupdatepending
2409 if {$progupdatepending} {
2410 set progupdatepending 0
2411 set lastprogupdate [clock clicks -milliseconds]
2412 update
2416 proc savestuff {w} {
2417 global canv canv2 canv3 mainfont textfont uifont tabstop
2418 global stuffsaved findmergefiles maxgraphpct
2419 global maxwidth showneartags showlocalchanges
2420 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2421 global cmitmode wrapcomment datetimeformat limitdiffs
2422 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2423 global autoselect extdifftool perfile_attrs markbgcolor
2425 if {$stuffsaved} return
2426 if {![winfo viewable .]} return
2427 catch {
2428 set f [open "~/.gitk-new" w]
2429 puts $f [list set mainfont $mainfont]
2430 puts $f [list set textfont $textfont]
2431 puts $f [list set uifont $uifont]
2432 puts $f [list set tabstop $tabstop]
2433 puts $f [list set findmergefiles $findmergefiles]
2434 puts $f [list set maxgraphpct $maxgraphpct]
2435 puts $f [list set maxwidth $maxwidth]
2436 puts $f [list set cmitmode $cmitmode]
2437 puts $f [list set wrapcomment $wrapcomment]
2438 puts $f [list set autoselect $autoselect]
2439 puts $f [list set showneartags $showneartags]
2440 puts $f [list set showlocalchanges $showlocalchanges]
2441 puts $f [list set datetimeformat $datetimeformat]
2442 puts $f [list set limitdiffs $limitdiffs]
2443 puts $f [list set bgcolor $bgcolor]
2444 puts $f [list set fgcolor $fgcolor]
2445 puts $f [list set colors $colors]
2446 puts $f [list set diffcolors $diffcolors]
2447 puts $f [list set markbgcolor $markbgcolor]
2448 puts $f [list set diffcontext $diffcontext]
2449 puts $f [list set selectbgcolor $selectbgcolor]
2450 puts $f [list set extdifftool $extdifftool]
2451 puts $f [list set perfile_attrs $perfile_attrs]
2453 puts $f "set geometry(main) [wm geometry .]"
2454 puts $f "set geometry(topwidth) [winfo width .tf]"
2455 puts $f "set geometry(topheight) [winfo height .tf]"
2456 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2457 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2458 puts $f "set geometry(botwidth) [winfo width .bleft]"
2459 puts $f "set geometry(botheight) [winfo height .bleft]"
2461 puts -nonewline $f "set permviews {"
2462 for {set v 0} {$v < $nextviewnum} {incr v} {
2463 if {$viewperm($v)} {
2464 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2467 puts $f "}"
2468 close $f
2469 file rename -force "~/.gitk-new" "~/.gitk"
2471 set stuffsaved 1
2474 proc resizeclistpanes {win w} {
2475 global oldwidth
2476 if {[info exists oldwidth($win)]} {
2477 set s0 [$win sash coord 0]
2478 set s1 [$win sash coord 1]
2479 if {$w < 60} {
2480 set sash0 [expr {int($w/2 - 2)}]
2481 set sash1 [expr {int($w*5/6 - 2)}]
2482 } else {
2483 set factor [expr {1.0 * $w / $oldwidth($win)}]
2484 set sash0 [expr {int($factor * [lindex $s0 0])}]
2485 set sash1 [expr {int($factor * [lindex $s1 0])}]
2486 if {$sash0 < 30} {
2487 set sash0 30
2489 if {$sash1 < $sash0 + 20} {
2490 set sash1 [expr {$sash0 + 20}]
2492 if {$sash1 > $w - 10} {
2493 set sash1 [expr {$w - 10}]
2494 if {$sash0 > $sash1 - 20} {
2495 set sash0 [expr {$sash1 - 20}]
2499 $win sash place 0 $sash0 [lindex $s0 1]
2500 $win sash place 1 $sash1 [lindex $s1 1]
2502 set oldwidth($win) $w
2505 proc resizecdetpanes {win w} {
2506 global oldwidth
2507 if {[info exists oldwidth($win)]} {
2508 set s0 [$win sash coord 0]
2509 if {$w < 60} {
2510 set sash0 [expr {int($w*3/4 - 2)}]
2511 } else {
2512 set factor [expr {1.0 * $w / $oldwidth($win)}]
2513 set sash0 [expr {int($factor * [lindex $s0 0])}]
2514 if {$sash0 < 45} {
2515 set sash0 45
2517 if {$sash0 > $w - 15} {
2518 set sash0 [expr {$w - 15}]
2521 $win sash place 0 $sash0 [lindex $s0 1]
2523 set oldwidth($win) $w
2526 proc allcanvs args {
2527 global canv canv2 canv3
2528 eval $canv $args
2529 eval $canv2 $args
2530 eval $canv3 $args
2533 proc bindall {event action} {
2534 global canv canv2 canv3
2535 bind $canv $event $action
2536 bind $canv2 $event $action
2537 bind $canv3 $event $action
2540 proc about {} {
2541 global uifont
2542 set w .about
2543 if {[winfo exists $w]} {
2544 raise $w
2545 return
2547 toplevel $w
2548 wm title $w [mc "About gitk"]
2549 wm transient $w .
2550 message $w.m -text [mc "
2551 Gitk - a commit viewer for git
2553 Copyright © 2005-2008 Paul Mackerras
2555 Use and redistribute under the terms of the GNU General Public License"] \
2556 -justify center -aspect 400 -border 2 -bg white -relief groove
2557 pack $w.m -side top -fill x -padx 2 -pady 2
2558 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2559 pack $w.ok -side bottom
2560 bind $w <Visibility> "focus $w.ok"
2561 bind $w <Key-Escape> "destroy $w"
2562 bind $w <Key-Return> "destroy $w"
2565 proc keys {} {
2566 set w .keys
2567 if {[winfo exists $w]} {
2568 raise $w
2569 return
2571 if {[tk windowingsystem] eq {aqua}} {
2572 set M1T Cmd
2573 } else {
2574 set M1T Ctrl
2576 toplevel $w
2577 wm title $w [mc "Gitk key bindings"]
2578 wm transient $w .
2579 message $w.m -text "
2580 [mc "Gitk key bindings:"]
2582 [mc "<%s-Q> Quit" $M1T]
2583 [mc "<Home> Move to first commit"]
2584 [mc "<End> Move to last commit"]
2585 [mc "<Up>, p, i Move up one commit"]
2586 [mc "<Down>, n, k Move down one commit"]
2587 [mc "<Left>, z, j Go back in history list"]
2588 [mc "<Right>, x, l Go forward in history list"]
2589 [mc "<PageUp> Move up one page in commit list"]
2590 [mc "<PageDown> Move down one page in commit list"]
2591 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2592 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2593 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2594 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2595 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2596 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2597 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2598 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2599 [mc "<Delete>, b Scroll diff view up one page"]
2600 [mc "<Backspace> Scroll diff view up one page"]
2601 [mc "<Space> Scroll diff view down one page"]
2602 [mc "u Scroll diff view up 18 lines"]
2603 [mc "d Scroll diff view down 18 lines"]
2604 [mc "<%s-F> Find" $M1T]
2605 [mc "<%s-G> Move to next find hit" $M1T]
2606 [mc "<Return> Move to next find hit"]
2607 [mc "/ Move to next find hit, or redo find"]
2608 [mc "? Move to previous find hit"]
2609 [mc "f Scroll diff view to next file"]
2610 [mc "<%s-S> Search for next hit in diff view" $M1T]
2611 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2612 [mc "<%s-KP+> Increase font size" $M1T]
2613 [mc "<%s-plus> Increase font size" $M1T]
2614 [mc "<%s-KP-> Decrease font size" $M1T]
2615 [mc "<%s-minus> Decrease font size" $M1T]
2616 [mc "<F5> Update"]
2618 -justify left -bg white -border 2 -relief groove
2619 pack $w.m -side top -fill both -padx 2 -pady 2
2620 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2621 bind $w <Key-Escape> [list destroy $w]
2622 pack $w.ok -side bottom
2623 bind $w <Visibility> "focus $w.ok"
2624 bind $w <Key-Escape> "destroy $w"
2625 bind $w <Key-Return> "destroy $w"
2628 # Procedures for manipulating the file list window at the
2629 # bottom right of the overall window.
2631 proc treeview {w l openlevs} {
2632 global treecontents treediropen treeheight treeparent treeindex
2634 set ix 0
2635 set treeindex() 0
2636 set lev 0
2637 set prefix {}
2638 set prefixend -1
2639 set prefendstack {}
2640 set htstack {}
2641 set ht 0
2642 set treecontents() {}
2643 $w conf -state normal
2644 foreach f $l {
2645 while {[string range $f 0 $prefixend] ne $prefix} {
2646 if {$lev <= $openlevs} {
2647 $w mark set e:$treeindex($prefix) "end -1c"
2648 $w mark gravity e:$treeindex($prefix) left
2650 set treeheight($prefix) $ht
2651 incr ht [lindex $htstack end]
2652 set htstack [lreplace $htstack end end]
2653 set prefixend [lindex $prefendstack end]
2654 set prefendstack [lreplace $prefendstack end end]
2655 set prefix [string range $prefix 0 $prefixend]
2656 incr lev -1
2658 set tail [string range $f [expr {$prefixend+1}] end]
2659 while {[set slash [string first "/" $tail]] >= 0} {
2660 lappend htstack $ht
2661 set ht 0
2662 lappend prefendstack $prefixend
2663 incr prefixend [expr {$slash + 1}]
2664 set d [string range $tail 0 $slash]
2665 lappend treecontents($prefix) $d
2666 set oldprefix $prefix
2667 append prefix $d
2668 set treecontents($prefix) {}
2669 set treeindex($prefix) [incr ix]
2670 set treeparent($prefix) $oldprefix
2671 set tail [string range $tail [expr {$slash+1}] end]
2672 if {$lev <= $openlevs} {
2673 set ht 1
2674 set treediropen($prefix) [expr {$lev < $openlevs}]
2675 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2676 $w mark set d:$ix "end -1c"
2677 $w mark gravity d:$ix left
2678 set str "\n"
2679 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2680 $w insert end $str
2681 $w image create end -align center -image $bm -padx 1 \
2682 -name a:$ix
2683 $w insert end $d [highlight_tag $prefix]
2684 $w mark set s:$ix "end -1c"
2685 $w mark gravity s:$ix left
2687 incr lev
2689 if {$tail ne {}} {
2690 if {$lev <= $openlevs} {
2691 incr ht
2692 set str "\n"
2693 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2694 $w insert end $str
2695 $w insert end $tail [highlight_tag $f]
2697 lappend treecontents($prefix) $tail
2700 while {$htstack ne {}} {
2701 set treeheight($prefix) $ht
2702 incr ht [lindex $htstack end]
2703 set htstack [lreplace $htstack end end]
2704 set prefixend [lindex $prefendstack end]
2705 set prefendstack [lreplace $prefendstack end end]
2706 set prefix [string range $prefix 0 $prefixend]
2708 $w conf -state disabled
2711 proc linetoelt {l} {
2712 global treeheight treecontents
2714 set y 2
2715 set prefix {}
2716 while {1} {
2717 foreach e $treecontents($prefix) {
2718 if {$y == $l} {
2719 return "$prefix$e"
2721 set n 1
2722 if {[string index $e end] eq "/"} {
2723 set n $treeheight($prefix$e)
2724 if {$y + $n > $l} {
2725 append prefix $e
2726 incr y
2727 break
2730 incr y $n
2735 proc highlight_tree {y prefix} {
2736 global treeheight treecontents cflist
2738 foreach e $treecontents($prefix) {
2739 set path $prefix$e
2740 if {[highlight_tag $path] ne {}} {
2741 $cflist tag add bold $y.0 "$y.0 lineend"
2743 incr y
2744 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2745 set y [highlight_tree $y $path]
2748 return $y
2751 proc treeclosedir {w dir} {
2752 global treediropen treeheight treeparent treeindex
2754 set ix $treeindex($dir)
2755 $w conf -state normal
2756 $w delete s:$ix e:$ix
2757 set treediropen($dir) 0
2758 $w image configure a:$ix -image tri-rt
2759 $w conf -state disabled
2760 set n [expr {1 - $treeheight($dir)}]
2761 while {$dir ne {}} {
2762 incr treeheight($dir) $n
2763 set dir $treeparent($dir)
2767 proc treeopendir {w dir} {
2768 global treediropen treeheight treeparent treecontents treeindex
2770 set ix $treeindex($dir)
2771 $w conf -state normal
2772 $w image configure a:$ix -image tri-dn
2773 $w mark set e:$ix s:$ix
2774 $w mark gravity e:$ix right
2775 set lev 0
2776 set str "\n"
2777 set n [llength $treecontents($dir)]
2778 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2779 incr lev
2780 append str "\t"
2781 incr treeheight($x) $n
2783 foreach e $treecontents($dir) {
2784 set de $dir$e
2785 if {[string index $e end] eq "/"} {
2786 set iy $treeindex($de)
2787 $w mark set d:$iy e:$ix
2788 $w mark gravity d:$iy left
2789 $w insert e:$ix $str
2790 set treediropen($de) 0
2791 $w image create e:$ix -align center -image tri-rt -padx 1 \
2792 -name a:$iy
2793 $w insert e:$ix $e [highlight_tag $de]
2794 $w mark set s:$iy e:$ix
2795 $w mark gravity s:$iy left
2796 set treeheight($de) 1
2797 } else {
2798 $w insert e:$ix $str
2799 $w insert e:$ix $e [highlight_tag $de]
2802 $w mark gravity e:$ix right
2803 $w conf -state disabled
2804 set treediropen($dir) 1
2805 set top [lindex [split [$w index @0,0] .] 0]
2806 set ht [$w cget -height]
2807 set l [lindex [split [$w index s:$ix] .] 0]
2808 if {$l < $top} {
2809 $w yview $l.0
2810 } elseif {$l + $n + 1 > $top + $ht} {
2811 set top [expr {$l + $n + 2 - $ht}]
2812 if {$l < $top} {
2813 set top $l
2815 $w yview $top.0
2819 proc treeclick {w x y} {
2820 global treediropen cmitmode ctext cflist cflist_top
2822 if {$cmitmode ne "tree"} return
2823 if {![info exists cflist_top]} return
2824 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2825 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2826 $cflist tag add highlight $l.0 "$l.0 lineend"
2827 set cflist_top $l
2828 if {$l == 1} {
2829 $ctext yview 1.0
2830 return
2832 set e [linetoelt $l]
2833 if {[string index $e end] ne "/"} {
2834 showfile $e
2835 } elseif {$treediropen($e)} {
2836 treeclosedir $w $e
2837 } else {
2838 treeopendir $w $e
2842 proc setfilelist {id} {
2843 global treefilelist cflist jump_to_here
2845 treeview $cflist $treefilelist($id) 0
2846 if {$jump_to_here ne {}} {
2847 set f [lindex $jump_to_here 0]
2848 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2849 showfile $f
2854 image create bitmap tri-rt -background black -foreground blue -data {
2855 #define tri-rt_width 13
2856 #define tri-rt_height 13
2857 static unsigned char tri-rt_bits[] = {
2858 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2859 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2860 0x00, 0x00};
2861 } -maskdata {
2862 #define tri-rt-mask_width 13
2863 #define tri-rt-mask_height 13
2864 static unsigned char tri-rt-mask_bits[] = {
2865 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2866 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2867 0x08, 0x00};
2869 image create bitmap tri-dn -background black -foreground blue -data {
2870 #define tri-dn_width 13
2871 #define tri-dn_height 13
2872 static unsigned char tri-dn_bits[] = {
2873 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2874 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2875 0x00, 0x00};
2876 } -maskdata {
2877 #define tri-dn-mask_width 13
2878 #define tri-dn-mask_height 13
2879 static unsigned char tri-dn-mask_bits[] = {
2880 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2881 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2882 0x00, 0x00};
2885 image create bitmap reficon-T -background black -foreground yellow -data {
2886 #define tagicon_width 13
2887 #define tagicon_height 9
2888 static unsigned char tagicon_bits[] = {
2889 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2890 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2891 } -maskdata {
2892 #define tagicon-mask_width 13
2893 #define tagicon-mask_height 9
2894 static unsigned char tagicon-mask_bits[] = {
2895 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2896 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2898 set rectdata {
2899 #define headicon_width 13
2900 #define headicon_height 9
2901 static unsigned char headicon_bits[] = {
2902 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2903 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2905 set rectmask {
2906 #define headicon-mask_width 13
2907 #define headicon-mask_height 9
2908 static unsigned char headicon-mask_bits[] = {
2909 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2910 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2912 image create bitmap reficon-H -background black -foreground green \
2913 -data $rectdata -maskdata $rectmask
2914 image create bitmap reficon-o -background black -foreground "#ddddff" \
2915 -data $rectdata -maskdata $rectmask
2917 proc init_flist {first} {
2918 global cflist cflist_top difffilestart
2920 $cflist conf -state normal
2921 $cflist delete 0.0 end
2922 if {$first ne {}} {
2923 $cflist insert end $first
2924 set cflist_top 1
2925 $cflist tag add highlight 1.0 "1.0 lineend"
2926 } else {
2927 catch {unset cflist_top}
2929 $cflist conf -state disabled
2930 set difffilestart {}
2933 proc highlight_tag {f} {
2934 global highlight_paths
2936 foreach p $highlight_paths {
2937 if {[string match $p $f]} {
2938 return "bold"
2941 return {}
2944 proc highlight_filelist {} {
2945 global cmitmode cflist
2947 $cflist conf -state normal
2948 if {$cmitmode ne "tree"} {
2949 set end [lindex [split [$cflist index end] .] 0]
2950 for {set l 2} {$l < $end} {incr l} {
2951 set line [$cflist get $l.0 "$l.0 lineend"]
2952 if {[highlight_tag $line] ne {}} {
2953 $cflist tag add bold $l.0 "$l.0 lineend"
2956 } else {
2957 highlight_tree 2 {}
2959 $cflist conf -state disabled
2962 proc unhighlight_filelist {} {
2963 global cflist
2965 $cflist conf -state normal
2966 $cflist tag remove bold 1.0 end
2967 $cflist conf -state disabled
2970 proc add_flist {fl} {
2971 global cflist
2973 $cflist conf -state normal
2974 foreach f $fl {
2975 $cflist insert end "\n"
2976 $cflist insert end $f [highlight_tag $f]
2978 $cflist conf -state disabled
2981 proc sel_flist {w x y} {
2982 global ctext difffilestart cflist cflist_top cmitmode
2984 if {$cmitmode eq "tree"} return
2985 if {![info exists cflist_top]} return
2986 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2987 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2988 $cflist tag add highlight $l.0 "$l.0 lineend"
2989 set cflist_top $l
2990 if {$l == 1} {
2991 $ctext yview 1.0
2992 } else {
2993 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
2997 proc pop_flist_menu {w X Y x y} {
2998 global ctext cflist cmitmode flist_menu flist_menu_file
2999 global treediffs diffids
3001 stopfinding
3002 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3003 if {$l <= 1} return
3004 if {$cmitmode eq "tree"} {
3005 set e [linetoelt $l]
3006 if {[string index $e end] eq "/"} return
3007 } else {
3008 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3010 set flist_menu_file $e
3011 set xdiffstate "normal"
3012 if {$cmitmode eq "tree"} {
3013 set xdiffstate "disabled"
3015 # Disable "External diff" item in tree mode
3016 $flist_menu entryconf 2 -state $xdiffstate
3017 tk_popup $flist_menu $X $Y
3020 proc find_ctext_fileinfo {line} {
3021 global ctext_file_names ctext_file_lines
3023 set ok [bsearch $ctext_file_lines $line]
3024 set tline [lindex $ctext_file_lines $ok]
3026 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3027 return {}
3028 } else {
3029 return [list [lindex $ctext_file_names $ok] $tline]
3033 proc pop_diff_menu {w X Y x y} {
3034 global ctext diff_menu flist_menu_file
3035 global diff_menu_txtpos diff_menu_line
3036 global diff_menu_filebase
3038 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3039 set diff_menu_line [lindex $diff_menu_txtpos 0]
3040 # don't pop up the menu on hunk-separator or file-separator lines
3041 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3042 return
3044 stopfinding
3045 set f [find_ctext_fileinfo $diff_menu_line]
3046 if {$f eq {}} return
3047 set flist_menu_file [lindex $f 0]
3048 set diff_menu_filebase [lindex $f 1]
3049 tk_popup $diff_menu $X $Y
3052 proc flist_hl {only} {
3053 global flist_menu_file findstring gdttype
3055 set x [shellquote $flist_menu_file]
3056 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3057 set findstring $x
3058 } else {
3059 append findstring " " $x
3061 set gdttype [mc "touching paths:"]
3064 proc save_file_from_commit {filename output what} {
3065 global nullfile
3067 if {[catch {exec git show $filename -- > $output} err]} {
3068 if {[string match "fatal: bad revision *" $err]} {
3069 return $nullfile
3071 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3072 return {}
3074 return $output
3077 proc external_diff_get_one_file {diffid filename diffdir} {
3078 global nullid nullid2 nullfile
3079 global gitdir
3081 if {$diffid == $nullid} {
3082 set difffile [file join [file dirname $gitdir] $filename]
3083 if {[file exists $difffile]} {
3084 return $difffile
3086 return $nullfile
3088 if {$diffid == $nullid2} {
3089 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3090 return [save_file_from_commit :$filename $difffile index]
3092 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3093 return [save_file_from_commit $diffid:$filename $difffile \
3094 "revision $diffid"]
3097 proc external_diff {} {
3098 global gitktmpdir nullid nullid2
3099 global flist_menu_file
3100 global diffids
3101 global diffnum
3102 global gitdir extdifftool
3104 if {[llength $diffids] == 1} {
3105 # no reference commit given
3106 set diffidto [lindex $diffids 0]
3107 if {$diffidto eq $nullid} {
3108 # diffing working copy with index
3109 set diffidfrom $nullid2
3110 } elseif {$diffidto eq $nullid2} {
3111 # diffing index with HEAD
3112 set diffidfrom "HEAD"
3113 } else {
3114 # use first parent commit
3115 global parentlist selectedline
3116 set diffidfrom [lindex $parentlist $selectedline 0]
3118 } else {
3119 set diffidfrom [lindex $diffids 0]
3120 set diffidto [lindex $diffids 1]
3123 # make sure that several diffs wont collide
3124 if {![info exists gitktmpdir]} {
3125 set gitktmpdir [file join [file dirname $gitdir] \
3126 [format ".gitk-tmp.%s" [pid]]]
3127 if {[catch {file mkdir $gitktmpdir} err]} {
3128 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3129 unset gitktmpdir
3130 return
3132 set diffnum 0
3134 incr diffnum
3135 set diffdir [file join $gitktmpdir $diffnum]
3136 if {[catch {file mkdir $diffdir} err]} {
3137 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3138 return
3141 # gather files to diff
3142 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3143 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3145 if {$difffromfile ne {} && $difftofile ne {}} {
3146 set cmd [concat | [shellsplit $extdifftool] \
3147 [list $difffromfile $difftofile]]
3148 if {[catch {set fl [open $cmd r]} err]} {
3149 file delete -force $diffdir
3150 error_popup "$extdifftool: [mc "command failed:"] $err"
3151 } else {
3152 fconfigure $fl -blocking 0
3153 filerun $fl [list delete_at_eof $fl $diffdir]
3158 proc find_hunk_blamespec {base line} {
3159 global ctext
3161 # Find and parse the hunk header
3162 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3163 if {$s_lix eq {}} return
3165 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3166 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3167 s_line old_specs osz osz1 new_line nsz]} {
3168 return
3171 # base lines for the parents
3172 set base_lines [list $new_line]
3173 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3174 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3175 old_spec old_line osz]} {
3176 return
3178 lappend base_lines $old_line
3181 # Now scan the lines to determine offset within the hunk
3182 set max_parent [expr {[llength $base_lines]-2}]
3183 set dline 0
3184 set s_lno [lindex [split $s_lix "."] 0]
3186 # Determine if the line is removed
3187 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3188 if {[string match {[-+ ]*} $chunk]} {
3189 set removed_idx [string first "-" $chunk]
3190 # Choose a parent index
3191 if {$removed_idx >= 0} {
3192 set parent $removed_idx
3193 } else {
3194 set unchanged_idx [string first " " $chunk]
3195 if {$unchanged_idx >= 0} {
3196 set parent $unchanged_idx
3197 } else {
3198 # blame the current commit
3199 set parent -1
3202 # then count other lines that belong to it
3203 for {set i $line} {[incr i -1] > $s_lno} {} {
3204 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3205 # Determine if the line is removed
3206 set removed_idx [string first "-" $chunk]
3207 if {$parent >= 0} {
3208 set code [string index $chunk $parent]
3209 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3210 incr dline
3212 } else {
3213 if {$removed_idx < 0} {
3214 incr dline
3218 incr parent
3219 } else {
3220 set parent 0
3223 incr dline [lindex $base_lines $parent]
3224 return [list $parent $dline]
3227 proc external_blame_diff {} {
3228 global currentid diffmergeid cmitmode
3229 global diff_menu_txtpos diff_menu_line
3230 global diff_menu_filebase flist_menu_file
3232 if {$cmitmode eq "tree"} {
3233 set parent_idx 0
3234 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3235 } else {
3236 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3237 if {$hinfo ne {}} {
3238 set parent_idx [lindex $hinfo 0]
3239 set line [lindex $hinfo 1]
3240 } else {
3241 set parent_idx 0
3242 set line 0
3246 external_blame $parent_idx $line
3249 proc external_blame {parent_idx {line {}}} {
3250 global flist_menu_file
3251 global nullid nullid2
3252 global parentlist selectedline currentid
3254 if {$parent_idx > 0} {
3255 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3256 } else {
3257 set base_commit $currentid
3260 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3261 error_popup [mc "No such commit"]
3262 return
3265 set cmdline [list git gui blame]
3266 if {$line ne {} && $line > 1} {
3267 lappend cmdline "--line=$line"
3269 lappend cmdline $base_commit $flist_menu_file
3270 if {[catch {eval exec $cmdline &} err]} {
3271 error_popup "[mc "git gui blame: command failed:"] $err"
3275 proc show_line_source {} {
3276 global cmitmode currentid parents curview blamestuff blameinst
3277 global diff_menu_line diff_menu_filebase flist_menu_file
3279 if {$cmitmode eq "tree"} {
3280 set id $currentid
3281 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3282 } else {
3283 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3284 if {$h eq {}} return
3285 set pi [lindex $h 0]
3286 if {$pi == 0} {
3287 mark_ctext_line $diff_menu_line
3288 return
3290 set id [lindex $parents($curview,$currentid) [expr {$pi - 1}]]
3291 set line [lindex $h 1]
3293 if {[catch {
3294 set f [open [list | git blame -p -L$line,+1 $id -- $flist_menu_file] r]
3295 } err]} {
3296 error_popup [mc "Couldn't start git blame: %s" $err]
3297 return
3299 fconfigure $f -blocking 0
3300 set i [reg_instance $f]
3301 set blamestuff($i) {}
3302 set blameinst $i
3303 filerun $f [list read_line_source $f $i]
3306 proc stopblaming {} {
3307 global blameinst
3309 if {[info exists blameinst]} {
3310 stop_instance $blameinst
3311 unset blameinst
3315 proc read_line_source {fd inst} {
3316 global blamestuff curview commfd blameinst
3318 while {[gets $fd line] >= 0} {
3319 lappend blamestuff($inst) $line
3321 if {![eof $fd]} {
3322 return 1
3324 unset commfd($inst)
3325 unset blameinst
3326 fconfigure $fd -blocking 1
3327 if {[catch {close $fd} err]} {
3328 error_popup [mc "Error running git blame: %s" $err]
3329 return 0
3332 set fname {}
3333 set line [split [lindex $blamestuff($inst) 0] " "]
3334 set id [lindex $line 0]
3335 set lnum [lindex $line 1]
3336 if {[string length $id] == 40 && [string is xdigit $id] &&
3337 [string is digit -strict $lnum]} {
3338 # look for "filename" line
3339 foreach l $blamestuff($inst) {
3340 if {[string match "filename *" $l]} {
3341 set fname [string range $l 9 end]
3342 break
3346 if {$fname ne {}} {
3347 # all looks good, select it
3348 if {[commitinview $id $curview]} {
3349 selectline [rowofcommit $id] 1 [list $fname $lnum]
3350 } else {
3351 error_popup [mc "That line comes from commit %s, \
3352 which is not in this view" [shortids $id]]
3354 } else {
3355 puts "oops couldn't parse git blame output"
3357 return 0
3360 # delete $dir when we see eof on $f (presumably because the child has exited)
3361 proc delete_at_eof {f dir} {
3362 while {[gets $f line] >= 0} {}
3363 if {[eof $f]} {
3364 if {[catch {close $f} err]} {
3365 error_popup "[mc "External diff viewer failed:"] $err"
3367 file delete -force $dir
3368 return 0
3370 return 1
3373 # Functions for adding and removing shell-type quoting
3375 proc shellquote {str} {
3376 if {![string match "*\['\"\\ \t]*" $str]} {
3377 return $str
3379 if {![string match "*\['\"\\]*" $str]} {
3380 return "\"$str\""
3382 if {![string match "*'*" $str]} {
3383 return "'$str'"
3385 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3388 proc shellarglist {l} {
3389 set str {}
3390 foreach a $l {
3391 if {$str ne {}} {
3392 append str " "
3394 append str [shellquote $a]
3396 return $str
3399 proc shelldequote {str} {
3400 set ret {}
3401 set used -1
3402 while {1} {
3403 incr used
3404 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3405 append ret [string range $str $used end]
3406 set used [string length $str]
3407 break
3409 set first [lindex $first 0]
3410 set ch [string index $str $first]
3411 if {$first > $used} {
3412 append ret [string range $str $used [expr {$first - 1}]]
3413 set used $first
3415 if {$ch eq " " || $ch eq "\t"} break
3416 incr used
3417 if {$ch eq "'"} {
3418 set first [string first "'" $str $used]
3419 if {$first < 0} {
3420 error "unmatched single-quote"
3422 append ret [string range $str $used [expr {$first - 1}]]
3423 set used $first
3424 continue
3426 if {$ch eq "\\"} {
3427 if {$used >= [string length $str]} {
3428 error "trailing backslash"
3430 append ret [string index $str $used]
3431 continue
3433 # here ch == "\""
3434 while {1} {
3435 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3436 error "unmatched double-quote"
3438 set first [lindex $first 0]
3439 set ch [string index $str $first]
3440 if {$first > $used} {
3441 append ret [string range $str $used [expr {$first - 1}]]
3442 set used $first
3444 if {$ch eq "\""} break
3445 incr used
3446 append ret [string index $str $used]
3447 incr used
3450 return [list $used $ret]
3453 proc shellsplit {str} {
3454 set l {}
3455 while {1} {
3456 set str [string trimleft $str]
3457 if {$str eq {}} break
3458 set dq [shelldequote $str]
3459 set n [lindex $dq 0]
3460 set word [lindex $dq 1]
3461 set str [string range $str $n end]
3462 lappend l $word
3464 return $l
3467 # Code to implement multiple views
3469 proc newview {ishighlight} {
3470 global nextviewnum newviewname newishighlight
3471 global revtreeargs viewargscmd newviewopts curview
3473 set newishighlight $ishighlight
3474 set top .gitkview
3475 if {[winfo exists $top]} {
3476 raise $top
3477 return
3479 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3480 set newviewopts($nextviewnum,perm) 0
3481 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3482 decode_view_opts $nextviewnum $revtreeargs
3483 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3486 set known_view_options {
3487 {perm b . {} {mc "Remember this view"}}
3488 {args t50= + {} {mc "Commits to include (arguments to git log):"}}
3489 {all b * "--all" {mc "Use all refs"}}
3490 {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}}
3491 {lright b . "--left-right" {mc "Mark branch sides"}}
3492 {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}}
3493 {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}}
3494 {limit t10 + "--max-count=*" {mc "Max count:"}}
3495 {skip t10 . "--skip=*" {mc "Skip:"}}
3496 {first b . "--first-parent" {mc "Limit to first parent"}}
3497 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3500 proc encode_view_opts {n} {
3501 global known_view_options newviewopts
3503 set rargs [list]
3504 foreach opt $known_view_options {
3505 set patterns [lindex $opt 3]
3506 if {$patterns eq {}} continue
3507 set pattern [lindex $patterns 0]
3509 set val $newviewopts($n,[lindex $opt 0])
3511 if {[lindex $opt 1] eq "b"} {
3512 if {$val} {
3513 lappend rargs $pattern
3515 } else {
3516 set val [string trim $val]
3517 if {$val ne {}} {
3518 set pfix [string range $pattern 0 end-1]
3519 lappend rargs $pfix$val
3523 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3526 proc decode_view_opts {n view_args} {
3527 global known_view_options newviewopts
3529 foreach opt $known_view_options {
3530 if {[lindex $opt 1] eq "b"} {
3531 set val 0
3532 } else {
3533 set val {}
3535 set newviewopts($n,[lindex $opt 0]) $val
3537 set oargs [list]
3538 foreach arg $view_args {
3539 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3540 && ![info exists found(limit)]} {
3541 set newviewopts($n,limit) $cnt
3542 set found(limit) 1
3543 continue
3545 catch { unset val }
3546 foreach opt $known_view_options {
3547 set id [lindex $opt 0]
3548 if {[info exists found($id)]} continue
3549 foreach pattern [lindex $opt 3] {
3550 if {![string match $pattern $arg]} continue
3551 if {[lindex $opt 1] ne "b"} {
3552 set size [string length $pattern]
3553 set val [string range $arg [expr {$size-1}] end]
3554 } else {
3555 set val 1
3557 set newviewopts($n,$id) $val
3558 set found($id) 1
3559 break
3561 if {[info exists val]} break
3563 if {[info exists val]} continue
3564 lappend oargs $arg
3566 set newviewopts($n,args) [shellarglist $oargs]
3569 proc editview {} {
3570 global curview
3571 global viewname viewperm newviewname newviewopts
3572 global viewargs viewargscmd
3574 set top .gitkvedit-$curview
3575 if {[winfo exists $top]} {
3576 raise $top
3577 return
3579 set newviewname($curview) $viewname($curview)
3580 set newviewopts($curview,perm) $viewperm($curview)
3581 set newviewopts($curview,cmd) $viewargscmd($curview)
3582 decode_view_opts $curview $viewargs($curview)
3583 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3586 proc vieweditor {top n title} {
3587 global newviewname newviewopts viewfiles bgcolor
3588 global known_view_options
3590 toplevel $top
3591 wm title $top $title
3592 wm transient $top .
3594 # View name
3595 frame $top.nfr
3596 label $top.nl -text [mc "Name"]
3597 entry $top.name -width 20 -textvariable newviewname($n)
3598 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3599 pack $top.nl -in $top.nfr -side left -padx {0 30}
3600 pack $top.name -in $top.nfr -side left
3602 # View options
3603 set cframe $top.nfr
3604 set cexpand 0
3605 set cnt 0
3606 foreach opt $known_view_options {
3607 set id [lindex $opt 0]
3608 set type [lindex $opt 1]
3609 set flags [lindex $opt 2]
3610 set title [eval [lindex $opt 4]]
3611 set lxpad 0
3613 if {$flags eq "+" || $flags eq "*"} {
3614 set cframe $top.fr$cnt
3615 incr cnt
3616 frame $cframe
3617 pack $cframe -in $top -fill x -pady 3 -padx 3
3618 set cexpand [expr {$flags eq "*"}]
3619 } else {
3620 set lxpad 5
3623 if {$type eq "b"} {
3624 checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3625 pack $cframe.c_$id -in $cframe -side left \
3626 -padx [list $lxpad 0] -expand $cexpand -anchor w
3627 } elseif {[regexp {^t(\d+)$} $type type sz]} {
3628 message $cframe.l_$id -aspect 1500 -text $title
3629 entry $cframe.e_$id -width $sz -background $bgcolor \
3630 -textvariable newviewopts($n,$id)
3631 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3632 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3633 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3634 message $cframe.l_$id -aspect 1500 -text $title
3635 entry $cframe.e_$id -width $sz -background $bgcolor \
3636 -textvariable newviewopts($n,$id)
3637 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3638 pack $cframe.e_$id -in $cframe -side top -fill x
3642 # Path list
3643 message $top.l -aspect 1500 \
3644 -text [mc "Enter files and directories to include, one per line:"]
3645 pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3646 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3647 if {[info exists viewfiles($n)]} {
3648 foreach f $viewfiles($n) {
3649 $top.t insert end $f
3650 $top.t insert end "\n"
3652 $top.t delete {end - 1c} end
3653 $top.t mark set insert 0.0
3655 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3656 frame $top.buts
3657 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3658 button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3659 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3660 bind $top <Control-Return> [list newviewok $top $n]
3661 bind $top <F5> [list newviewok $top $n 1]
3662 bind $top <Escape> [list destroy $top]
3663 grid $top.buts.ok $top.buts.apply $top.buts.can
3664 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3665 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3666 grid columnconfigure $top.buts 2 -weight 1 -uniform a
3667 pack $top.buts -in $top -side top -fill x
3668 focus $top.t
3671 proc doviewmenu {m first cmd op argv} {
3672 set nmenu [$m index end]
3673 for {set i $first} {$i <= $nmenu} {incr i} {
3674 if {[$m entrycget $i -command] eq $cmd} {
3675 eval $m $op $i $argv
3676 break
3681 proc allviewmenus {n op args} {
3682 # global viewhlmenu
3684 doviewmenu .bar.view 5 [list showview $n] $op $args
3685 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3688 proc newviewok {top n {apply 0}} {
3689 global nextviewnum newviewperm newviewname newishighlight
3690 global viewname viewfiles viewperm selectedview curview
3691 global viewargs viewargscmd newviewopts viewhlmenu
3693 if {[catch {
3694 set newargs [encode_view_opts $n]
3695 } err]} {
3696 error_popup "[mc "Error in commit selection arguments:"] $err" $top
3697 return
3699 set files {}
3700 foreach f [split [$top.t get 0.0 end] "\n"] {
3701 set ft [string trim $f]
3702 if {$ft ne {}} {
3703 lappend files $ft
3706 if {![info exists viewfiles($n)]} {
3707 # creating a new view
3708 incr nextviewnum
3709 set viewname($n) $newviewname($n)
3710 set viewperm($n) $newviewopts($n,perm)
3711 set viewfiles($n) $files
3712 set viewargs($n) $newargs
3713 set viewargscmd($n) $newviewopts($n,cmd)
3714 addviewmenu $n
3715 if {!$newishighlight} {
3716 run showview $n
3717 } else {
3718 run addvhighlight $n
3720 } else {
3721 # editing an existing view
3722 set viewperm($n) $newviewopts($n,perm)
3723 if {$newviewname($n) ne $viewname($n)} {
3724 set viewname($n) $newviewname($n)
3725 doviewmenu .bar.view 5 [list showview $n] \
3726 entryconf [list -label $viewname($n)]
3727 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3728 # entryconf [list -label $viewname($n) -value $viewname($n)]
3730 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3731 $newviewopts($n,cmd) ne $viewargscmd($n)} {
3732 set viewfiles($n) $files
3733 set viewargs($n) $newargs
3734 set viewargscmd($n) $newviewopts($n,cmd)
3735 if {$curview == $n} {
3736 run reloadcommits
3740 if {$apply} return
3741 catch {destroy $top}
3744 proc delview {} {
3745 global curview viewperm hlview selectedhlview
3747 if {$curview == 0} return
3748 if {[info exists hlview] && $hlview == $curview} {
3749 set selectedhlview [mc "None"]
3750 unset hlview
3752 allviewmenus $curview delete
3753 set viewperm($curview) 0
3754 showview 0
3757 proc addviewmenu {n} {
3758 global viewname viewhlmenu
3760 .bar.view add radiobutton -label $viewname($n) \
3761 -command [list showview $n] -variable selectedview -value $n
3762 #$viewhlmenu add radiobutton -label $viewname($n) \
3763 # -command [list addvhighlight $n] -variable selectedhlview
3766 proc showview {n} {
3767 global curview cached_commitrow ordertok
3768 global displayorder parentlist rowidlist rowisopt rowfinal
3769 global colormap rowtextx nextcolor canvxmax
3770 global numcommits viewcomplete
3771 global selectedline currentid canv canvy0
3772 global treediffs
3773 global pending_select mainheadid
3774 global commitidx
3775 global selectedview
3776 global hlview selectedhlview commitinterest
3778 if {$n == $curview} return
3779 set selid {}
3780 set ymax [lindex [$canv cget -scrollregion] 3]
3781 set span [$canv yview]
3782 set ytop [expr {[lindex $span 0] * $ymax}]
3783 set ybot [expr {[lindex $span 1] * $ymax}]
3784 set yscreen [expr {($ybot - $ytop) / 2}]
3785 if {$selectedline ne {}} {
3786 set selid $currentid
3787 set y [yc $selectedline]
3788 if {$ytop < $y && $y < $ybot} {
3789 set yscreen [expr {$y - $ytop}]
3791 } elseif {[info exists pending_select]} {
3792 set selid $pending_select
3793 unset pending_select
3795 unselectline
3796 normalline
3797 catch {unset treediffs}
3798 clear_display
3799 if {[info exists hlview] && $hlview == $n} {
3800 unset hlview
3801 set selectedhlview [mc "None"]
3803 catch {unset commitinterest}
3804 catch {unset cached_commitrow}
3805 catch {unset ordertok}
3807 set curview $n
3808 set selectedview $n
3809 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3810 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3812 run refill_reflist
3813 if {![info exists viewcomplete($n)]} {
3814 getcommits $selid
3815 return
3818 set displayorder {}
3819 set parentlist {}
3820 set rowidlist {}
3821 set rowisopt {}
3822 set rowfinal {}
3823 set numcommits $commitidx($n)
3825 catch {unset colormap}
3826 catch {unset rowtextx}
3827 set nextcolor 0
3828 set canvxmax [$canv cget -width]
3829 set curview $n
3830 set row 0
3831 setcanvscroll
3832 set yf 0
3833 set row {}
3834 if {$selid ne {} && [commitinview $selid $n]} {
3835 set row [rowofcommit $selid]
3836 # try to get the selected row in the same position on the screen
3837 set ymax [lindex [$canv cget -scrollregion] 3]
3838 set ytop [expr {[yc $row] - $yscreen}]
3839 if {$ytop < 0} {
3840 set ytop 0
3842 set yf [expr {$ytop * 1.0 / $ymax}]
3844 allcanvs yview moveto $yf
3845 drawvisible
3846 if {$row ne {}} {
3847 selectline $row 0
3848 } elseif {!$viewcomplete($n)} {
3849 reset_pending_select $selid
3850 } else {
3851 reset_pending_select {}
3853 if {[commitinview $pending_select $curview]} {
3854 selectline [rowofcommit $pending_select] 1
3855 } else {
3856 set row [first_real_row]
3857 if {$row < $numcommits} {
3858 selectline $row 0
3862 if {!$viewcomplete($n)} {
3863 if {$numcommits == 0} {
3864 show_status [mc "Reading commits..."]
3866 } elseif {$numcommits == 0} {
3867 show_status [mc "No commits selected"]
3871 # Stuff relating to the highlighting facility
3873 proc ishighlighted {id} {
3874 global vhighlights fhighlights nhighlights rhighlights
3876 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3877 return $nhighlights($id)
3879 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3880 return $vhighlights($id)
3882 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3883 return $fhighlights($id)
3885 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
3886 return $rhighlights($id)
3888 return 0
3891 proc bolden {row font} {
3892 global canv linehtag selectedline boldrows
3894 lappend boldrows $row
3895 $canv itemconf $linehtag($row) -font $font
3896 if {$row == $selectedline} {
3897 $canv delete secsel
3898 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
3899 -outline {{}} -tags secsel \
3900 -fill [$canv cget -selectbackground]]
3901 $canv lower $t
3905 proc bolden_name {row font} {
3906 global canv2 linentag selectedline boldnamerows
3908 lappend boldnamerows $row
3909 $canv2 itemconf $linentag($row) -font $font
3910 if {$row == $selectedline} {
3911 $canv2 delete secsel
3912 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
3913 -outline {{}} -tags secsel \
3914 -fill [$canv2 cget -selectbackground]]
3915 $canv2 lower $t
3919 proc unbolden {} {
3920 global boldrows
3922 set stillbold {}
3923 foreach row $boldrows {
3924 if {![ishighlighted [commitonrow $row]]} {
3925 bolden $row mainfont
3926 } else {
3927 lappend stillbold $row
3930 set boldrows $stillbold
3933 proc addvhighlight {n} {
3934 global hlview viewcomplete curview vhl_done commitidx
3936 if {[info exists hlview]} {
3937 delvhighlight
3939 set hlview $n
3940 if {$n != $curview && ![info exists viewcomplete($n)]} {
3941 start_rev_list $n
3943 set vhl_done $commitidx($hlview)
3944 if {$vhl_done > 0} {
3945 drawvisible
3949 proc delvhighlight {} {
3950 global hlview vhighlights
3952 if {![info exists hlview]} return
3953 unset hlview
3954 catch {unset vhighlights}
3955 unbolden
3958 proc vhighlightmore {} {
3959 global hlview vhl_done commitidx vhighlights curview
3961 set max $commitidx($hlview)
3962 set vr [visiblerows]
3963 set r0 [lindex $vr 0]
3964 set r1 [lindex $vr 1]
3965 for {set i $vhl_done} {$i < $max} {incr i} {
3966 set id [commitonrow $i $hlview]
3967 if {[commitinview $id $curview]} {
3968 set row [rowofcommit $id]
3969 if {$r0 <= $row && $row <= $r1} {
3970 if {![highlighted $row]} {
3971 bolden $row mainfontbold
3973 set vhighlights($id) 1
3977 set vhl_done $max
3978 return 0
3981 proc askvhighlight {row id} {
3982 global hlview vhighlights iddrawn
3984 if {[commitinview $id $hlview]} {
3985 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
3986 bolden $row mainfontbold
3988 set vhighlights($id) 1
3989 } else {
3990 set vhighlights($id) 0
3994 proc hfiles_change {} {
3995 global highlight_files filehighlight fhighlights fh_serial
3996 global highlight_paths gdttype
3998 if {[info exists filehighlight]} {
3999 # delete previous highlights
4000 catch {close $filehighlight}
4001 unset filehighlight
4002 catch {unset fhighlights}
4003 unbolden
4004 unhighlight_filelist
4006 set highlight_paths {}
4007 after cancel do_file_hl $fh_serial
4008 incr fh_serial
4009 if {$highlight_files ne {}} {
4010 after 300 do_file_hl $fh_serial
4014 proc gdttype_change {name ix op} {
4015 global gdttype highlight_files findstring findpattern
4017 stopfinding
4018 if {$findstring ne {}} {
4019 if {$gdttype eq [mc "containing:"]} {
4020 if {$highlight_files ne {}} {
4021 set highlight_files {}
4022 hfiles_change
4024 findcom_change
4025 } else {
4026 if {$findpattern ne {}} {
4027 set findpattern {}
4028 findcom_change
4030 set highlight_files $findstring
4031 hfiles_change
4033 drawvisible
4035 # enable/disable findtype/findloc menus too
4038 proc find_change {name ix op} {
4039 global gdttype findstring highlight_files
4041 stopfinding
4042 if {$gdttype eq [mc "containing:"]} {
4043 findcom_change
4044 } else {
4045 if {$highlight_files ne $findstring} {
4046 set highlight_files $findstring
4047 hfiles_change
4050 drawvisible
4053 proc findcom_change args {
4054 global nhighlights boldnamerows
4055 global findpattern findtype findstring gdttype
4057 stopfinding
4058 # delete previous highlights, if any
4059 foreach row $boldnamerows {
4060 bolden_name $row mainfont
4062 set boldnamerows {}
4063 catch {unset nhighlights}
4064 unbolden
4065 unmarkmatches
4066 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4067 set findpattern {}
4068 } elseif {$findtype eq [mc "Regexp"]} {
4069 set findpattern $findstring
4070 } else {
4071 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4072 $findstring]
4073 set findpattern "*$e*"
4077 proc makepatterns {l} {
4078 set ret {}
4079 foreach e $l {
4080 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4081 if {[string index $ee end] eq "/"} {
4082 lappend ret "$ee*"
4083 } else {
4084 lappend ret $ee
4085 lappend ret "$ee/*"
4088 return $ret
4091 proc do_file_hl {serial} {
4092 global highlight_files filehighlight highlight_paths gdttype fhl_list
4094 if {$gdttype eq [mc "touching paths:"]} {
4095 if {[catch {set paths [shellsplit $highlight_files]}]} return
4096 set highlight_paths [makepatterns $paths]
4097 highlight_filelist
4098 set gdtargs [concat -- $paths]
4099 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4100 set gdtargs [list "-S$highlight_files"]
4101 } else {
4102 # must be "containing:", i.e. we're searching commit info
4103 return
4105 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4106 set filehighlight [open $cmd r+]
4107 fconfigure $filehighlight -blocking 0
4108 filerun $filehighlight readfhighlight
4109 set fhl_list {}
4110 drawvisible
4111 flushhighlights
4114 proc flushhighlights {} {
4115 global filehighlight fhl_list
4117 if {[info exists filehighlight]} {
4118 lappend fhl_list {}
4119 puts $filehighlight ""
4120 flush $filehighlight
4124 proc askfilehighlight {row id} {
4125 global filehighlight fhighlights fhl_list
4127 lappend fhl_list $id
4128 set fhighlights($id) -1
4129 puts $filehighlight $id
4132 proc readfhighlight {} {
4133 global filehighlight fhighlights curview iddrawn
4134 global fhl_list find_dirn
4136 if {![info exists filehighlight]} {
4137 return 0
4139 set nr 0
4140 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4141 set line [string trim $line]
4142 set i [lsearch -exact $fhl_list $line]
4143 if {$i < 0} continue
4144 for {set j 0} {$j < $i} {incr j} {
4145 set id [lindex $fhl_list $j]
4146 set fhighlights($id) 0
4148 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4149 if {$line eq {}} continue
4150 if {![commitinview $line $curview]} continue
4151 set row [rowofcommit $line]
4152 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4153 bolden $row mainfontbold
4155 set fhighlights($line) 1
4157 if {[eof $filehighlight]} {
4158 # strange...
4159 puts "oops, git diff-tree died"
4160 catch {close $filehighlight}
4161 unset filehighlight
4162 return 0
4164 if {[info exists find_dirn]} {
4165 run findmore
4167 return 1
4170 proc doesmatch {f} {
4171 global findtype findpattern
4173 if {$findtype eq [mc "Regexp"]} {
4174 return [regexp $findpattern $f]
4175 } elseif {$findtype eq [mc "IgnCase"]} {
4176 return [string match -nocase $findpattern $f]
4177 } else {
4178 return [string match $findpattern $f]
4182 proc askfindhighlight {row id} {
4183 global nhighlights commitinfo iddrawn
4184 global findloc
4185 global markingmatches
4187 if {![info exists commitinfo($id)]} {
4188 getcommit $id
4190 set info $commitinfo($id)
4191 set isbold 0
4192 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4193 foreach f $info ty $fldtypes {
4194 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4195 [doesmatch $f]} {
4196 if {$ty eq [mc "Author"]} {
4197 set isbold 2
4198 break
4200 set isbold 1
4203 if {$isbold && [info exists iddrawn($id)]} {
4204 if {![ishighlighted $id]} {
4205 bolden $row mainfontbold
4206 if {$isbold > 1} {
4207 bolden_name $row mainfontbold
4210 if {$markingmatches} {
4211 markrowmatches $row $id
4214 set nhighlights($id) $isbold
4217 proc markrowmatches {row id} {
4218 global canv canv2 linehtag linentag commitinfo findloc
4220 set headline [lindex $commitinfo($id) 0]
4221 set author [lindex $commitinfo($id) 1]
4222 $canv delete match$row
4223 $canv2 delete match$row
4224 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4225 set m [findmatches $headline]
4226 if {$m ne {}} {
4227 markmatches $canv $row $headline $linehtag($row) $m \
4228 [$canv itemcget $linehtag($row) -font] $row
4231 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4232 set m [findmatches $author]
4233 if {$m ne {}} {
4234 markmatches $canv2 $row $author $linentag($row) $m \
4235 [$canv2 itemcget $linentag($row) -font] $row
4240 proc vrel_change {name ix op} {
4241 global highlight_related
4243 rhighlight_none
4244 if {$highlight_related ne [mc "None"]} {
4245 run drawvisible
4249 # prepare for testing whether commits are descendents or ancestors of a
4250 proc rhighlight_sel {a} {
4251 global descendent desc_todo ancestor anc_todo
4252 global highlight_related
4254 catch {unset descendent}
4255 set desc_todo [list $a]
4256 catch {unset ancestor}
4257 set anc_todo [list $a]
4258 if {$highlight_related ne [mc "None"]} {
4259 rhighlight_none
4260 run drawvisible
4264 proc rhighlight_none {} {
4265 global rhighlights
4267 catch {unset rhighlights}
4268 unbolden
4271 proc is_descendent {a} {
4272 global curview children descendent desc_todo
4274 set v $curview
4275 set la [rowofcommit $a]
4276 set todo $desc_todo
4277 set leftover {}
4278 set done 0
4279 for {set i 0} {$i < [llength $todo]} {incr i} {
4280 set do [lindex $todo $i]
4281 if {[rowofcommit $do] < $la} {
4282 lappend leftover $do
4283 continue
4285 foreach nk $children($v,$do) {
4286 if {![info exists descendent($nk)]} {
4287 set descendent($nk) 1
4288 lappend todo $nk
4289 if {$nk eq $a} {
4290 set done 1
4294 if {$done} {
4295 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4296 return
4299 set descendent($a) 0
4300 set desc_todo $leftover
4303 proc is_ancestor {a} {
4304 global curview parents ancestor anc_todo
4306 set v $curview
4307 set la [rowofcommit $a]
4308 set todo $anc_todo
4309 set leftover {}
4310 set done 0
4311 for {set i 0} {$i < [llength $todo]} {incr i} {
4312 set do [lindex $todo $i]
4313 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4314 lappend leftover $do
4315 continue
4317 foreach np $parents($v,$do) {
4318 if {![info exists ancestor($np)]} {
4319 set ancestor($np) 1
4320 lappend todo $np
4321 if {$np eq $a} {
4322 set done 1
4326 if {$done} {
4327 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4328 return
4331 set ancestor($a) 0
4332 set anc_todo $leftover
4335 proc askrelhighlight {row id} {
4336 global descendent highlight_related iddrawn rhighlights
4337 global selectedline ancestor
4339 if {$selectedline eq {}} return
4340 set isbold 0
4341 if {$highlight_related eq [mc "Descendant"] ||
4342 $highlight_related eq [mc "Not descendant"]} {
4343 if {![info exists descendent($id)]} {
4344 is_descendent $id
4346 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4347 set isbold 1
4349 } elseif {$highlight_related eq [mc "Ancestor"] ||
4350 $highlight_related eq [mc "Not ancestor"]} {
4351 if {![info exists ancestor($id)]} {
4352 is_ancestor $id
4354 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4355 set isbold 1
4358 if {[info exists iddrawn($id)]} {
4359 if {$isbold && ![ishighlighted $id]} {
4360 bolden $row mainfontbold
4363 set rhighlights($id) $isbold
4366 # Graph layout functions
4368 proc shortids {ids} {
4369 set res {}
4370 foreach id $ids {
4371 if {[llength $id] > 1} {
4372 lappend res [shortids $id]
4373 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4374 lappend res [string range $id 0 7]
4375 } else {
4376 lappend res $id
4379 return $res
4382 proc ntimes {n o} {
4383 set ret {}
4384 set o [list $o]
4385 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4386 if {($n & $mask) != 0} {
4387 set ret [concat $ret $o]
4389 set o [concat $o $o]
4391 return $ret
4394 proc ordertoken {id} {
4395 global ordertok curview varcid varcstart varctok curview parents children
4396 global nullid nullid2
4398 if {[info exists ordertok($id)]} {
4399 return $ordertok($id)
4401 set origid $id
4402 set todo {}
4403 while {1} {
4404 if {[info exists varcid($curview,$id)]} {
4405 set a $varcid($curview,$id)
4406 set p [lindex $varcstart($curview) $a]
4407 } else {
4408 set p [lindex $children($curview,$id) 0]
4410 if {[info exists ordertok($p)]} {
4411 set tok $ordertok($p)
4412 break
4414 set id [first_real_child $curview,$p]
4415 if {$id eq {}} {
4416 # it's a root
4417 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4418 break
4420 if {[llength $parents($curview,$id)] == 1} {
4421 lappend todo [list $p {}]
4422 } else {
4423 set j [lsearch -exact $parents($curview,$id) $p]
4424 if {$j < 0} {
4425 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4427 lappend todo [list $p [strrep $j]]
4430 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4431 set p [lindex $todo $i 0]
4432 append tok [lindex $todo $i 1]
4433 set ordertok($p) $tok
4435 set ordertok($origid) $tok
4436 return $tok
4439 # Work out where id should go in idlist so that order-token
4440 # values increase from left to right
4441 proc idcol {idlist id {i 0}} {
4442 set t [ordertoken $id]
4443 if {$i < 0} {
4444 set i 0
4446 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4447 if {$i > [llength $idlist]} {
4448 set i [llength $idlist]
4450 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4451 incr i
4452 } else {
4453 if {$t > [ordertoken [lindex $idlist $i]]} {
4454 while {[incr i] < [llength $idlist] &&
4455 $t >= [ordertoken [lindex $idlist $i]]} {}
4458 return $i
4461 proc initlayout {} {
4462 global rowidlist rowisopt rowfinal displayorder parentlist
4463 global numcommits canvxmax canv
4464 global nextcolor
4465 global colormap rowtextx
4467 set numcommits 0
4468 set displayorder {}
4469 set parentlist {}
4470 set nextcolor 0
4471 set rowidlist {}
4472 set rowisopt {}
4473 set rowfinal {}
4474 set canvxmax [$canv cget -width]
4475 catch {unset colormap}
4476 catch {unset rowtextx}
4477 setcanvscroll
4480 proc setcanvscroll {} {
4481 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4482 global lastscrollset lastscrollrows
4484 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4485 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4486 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4487 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4488 set lastscrollset [clock clicks -milliseconds]
4489 set lastscrollrows $numcommits
4492 proc visiblerows {} {
4493 global canv numcommits linespc
4495 set ymax [lindex [$canv cget -scrollregion] 3]
4496 if {$ymax eq {} || $ymax == 0} return
4497 set f [$canv yview]
4498 set y0 [expr {int([lindex $f 0] * $ymax)}]
4499 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4500 if {$r0 < 0} {
4501 set r0 0
4503 set y1 [expr {int([lindex $f 1] * $ymax)}]
4504 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4505 if {$r1 >= $numcommits} {
4506 set r1 [expr {$numcommits - 1}]
4508 return [list $r0 $r1]
4511 proc layoutmore {} {
4512 global commitidx viewcomplete curview
4513 global numcommits pending_select curview
4514 global lastscrollset lastscrollrows
4516 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4517 [clock clicks -milliseconds] - $lastscrollset > 500} {
4518 setcanvscroll
4520 if {[info exists pending_select] &&
4521 [commitinview $pending_select $curview]} {
4522 update
4523 selectline [rowofcommit $pending_select] 1
4525 drawvisible
4528 proc doshowlocalchanges {} {
4529 global curview mainheadid
4531 if {$mainheadid eq {}} return
4532 if {[commitinview $mainheadid $curview]} {
4533 dodiffindex
4534 } else {
4535 interestedin $mainheadid dodiffindex
4539 proc dohidelocalchanges {} {
4540 global nullid nullid2 lserial curview
4542 if {[commitinview $nullid $curview]} {
4543 removefakerow $nullid
4545 if {[commitinview $nullid2 $curview]} {
4546 removefakerow $nullid2
4548 incr lserial
4551 # spawn off a process to do git diff-index --cached HEAD
4552 proc dodiffindex {} {
4553 global lserial showlocalchanges
4554 global isworktree
4556 if {!$showlocalchanges || !$isworktree} return
4557 incr lserial
4558 set fd [open "|git diff-index --cached HEAD" r]
4559 fconfigure $fd -blocking 0
4560 set i [reg_instance $fd]
4561 filerun $fd [list readdiffindex $fd $lserial $i]
4564 proc readdiffindex {fd serial inst} {
4565 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4567 set isdiff 1
4568 if {[gets $fd line] < 0} {
4569 if {![eof $fd]} {
4570 return 1
4572 set isdiff 0
4574 # we only need to see one line and we don't really care what it says...
4575 stop_instance $inst
4577 if {$serial != $lserial} {
4578 return 0
4581 # now see if there are any local changes not checked in to the index
4582 set fd [open "|git diff-files" r]
4583 fconfigure $fd -blocking 0
4584 set i [reg_instance $fd]
4585 filerun $fd [list readdifffiles $fd $serial $i]
4587 if {$isdiff && ![commitinview $nullid2 $curview]} {
4588 # add the line for the changes in the index to the graph
4589 set hl [mc "Local changes checked in to index but not committed"]
4590 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4591 set commitdata($nullid2) "\n $hl\n"
4592 if {[commitinview $nullid $curview]} {
4593 removefakerow $nullid
4595 insertfakerow $nullid2 $mainheadid
4596 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4597 removefakerow $nullid2
4599 return 0
4602 proc readdifffiles {fd serial inst} {
4603 global mainheadid nullid nullid2 curview
4604 global commitinfo commitdata lserial
4606 set isdiff 1
4607 if {[gets $fd line] < 0} {
4608 if {![eof $fd]} {
4609 return 1
4611 set isdiff 0
4613 # we only need to see one line and we don't really care what it says...
4614 stop_instance $inst
4616 if {$serial != $lserial} {
4617 return 0
4620 if {$isdiff && ![commitinview $nullid $curview]} {
4621 # add the line for the local diff to the graph
4622 set hl [mc "Local uncommitted changes, not checked in to index"]
4623 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4624 set commitdata($nullid) "\n $hl\n"
4625 if {[commitinview $nullid2 $curview]} {
4626 set p $nullid2
4627 } else {
4628 set p $mainheadid
4630 insertfakerow $nullid $p
4631 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4632 removefakerow $nullid
4634 return 0
4637 proc nextuse {id row} {
4638 global curview children
4640 if {[info exists children($curview,$id)]} {
4641 foreach kid $children($curview,$id) {
4642 if {![commitinview $kid $curview]} {
4643 return -1
4645 if {[rowofcommit $kid] > $row} {
4646 return [rowofcommit $kid]
4650 if {[commitinview $id $curview]} {
4651 return [rowofcommit $id]
4653 return -1
4656 proc prevuse {id row} {
4657 global curview children
4659 set ret -1
4660 if {[info exists children($curview,$id)]} {
4661 foreach kid $children($curview,$id) {
4662 if {![commitinview $kid $curview]} break
4663 if {[rowofcommit $kid] < $row} {
4664 set ret [rowofcommit $kid]
4668 return $ret
4671 proc make_idlist {row} {
4672 global displayorder parentlist uparrowlen downarrowlen mingaplen
4673 global commitidx curview children
4675 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4676 if {$r < 0} {
4677 set r 0
4679 set ra [expr {$row - $downarrowlen}]
4680 if {$ra < 0} {
4681 set ra 0
4683 set rb [expr {$row + $uparrowlen}]
4684 if {$rb > $commitidx($curview)} {
4685 set rb $commitidx($curview)
4687 make_disporder $r [expr {$rb + 1}]
4688 set ids {}
4689 for {} {$r < $ra} {incr r} {
4690 set nextid [lindex $displayorder [expr {$r + 1}]]
4691 foreach p [lindex $parentlist $r] {
4692 if {$p eq $nextid} continue
4693 set rn [nextuse $p $r]
4694 if {$rn >= $row &&
4695 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4696 lappend ids [list [ordertoken $p] $p]
4700 for {} {$r < $row} {incr r} {
4701 set nextid [lindex $displayorder [expr {$r + 1}]]
4702 foreach p [lindex $parentlist $r] {
4703 if {$p eq $nextid} continue
4704 set rn [nextuse $p $r]
4705 if {$rn < 0 || $rn >= $row} {
4706 lappend ids [list [ordertoken $p] $p]
4710 set id [lindex $displayorder $row]
4711 lappend ids [list [ordertoken $id] $id]
4712 while {$r < $rb} {
4713 foreach p [lindex $parentlist $r] {
4714 set firstkid [lindex $children($curview,$p) 0]
4715 if {[rowofcommit $firstkid] < $row} {
4716 lappend ids [list [ordertoken $p] $p]
4719 incr r
4720 set id [lindex $displayorder $r]
4721 if {$id ne {}} {
4722 set firstkid [lindex $children($curview,$id) 0]
4723 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4724 lappend ids [list [ordertoken $id] $id]
4728 set idlist {}
4729 foreach idx [lsort -unique $ids] {
4730 lappend idlist [lindex $idx 1]
4732 return $idlist
4735 proc rowsequal {a b} {
4736 while {[set i [lsearch -exact $a {}]] >= 0} {
4737 set a [lreplace $a $i $i]
4739 while {[set i [lsearch -exact $b {}]] >= 0} {
4740 set b [lreplace $b $i $i]
4742 return [expr {$a eq $b}]
4745 proc makeupline {id row rend col} {
4746 global rowidlist uparrowlen downarrowlen mingaplen
4748 for {set r $rend} {1} {set r $rstart} {
4749 set rstart [prevuse $id $r]
4750 if {$rstart < 0} return
4751 if {$rstart < $row} break
4753 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4754 set rstart [expr {$rend - $uparrowlen - 1}]
4756 for {set r $rstart} {[incr r] <= $row} {} {
4757 set idlist [lindex $rowidlist $r]
4758 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4759 set col [idcol $idlist $id $col]
4760 lset rowidlist $r [linsert $idlist $col $id]
4761 changedrow $r
4766 proc layoutrows {row endrow} {
4767 global rowidlist rowisopt rowfinal displayorder
4768 global uparrowlen downarrowlen maxwidth mingaplen
4769 global children parentlist
4770 global commitidx viewcomplete curview
4772 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4773 set idlist {}
4774 if {$row > 0} {
4775 set rm1 [expr {$row - 1}]
4776 foreach id [lindex $rowidlist $rm1] {
4777 if {$id ne {}} {
4778 lappend idlist $id
4781 set final [lindex $rowfinal $rm1]
4783 for {} {$row < $endrow} {incr row} {
4784 set rm1 [expr {$row - 1}]
4785 if {$rm1 < 0 || $idlist eq {}} {
4786 set idlist [make_idlist $row]
4787 set final 1
4788 } else {
4789 set id [lindex $displayorder $rm1]
4790 set col [lsearch -exact $idlist $id]
4791 set idlist [lreplace $idlist $col $col]
4792 foreach p [lindex $parentlist $rm1] {
4793 if {[lsearch -exact $idlist $p] < 0} {
4794 set col [idcol $idlist $p $col]
4795 set idlist [linsert $idlist $col $p]
4796 # if not the first child, we have to insert a line going up
4797 if {$id ne [lindex $children($curview,$p) 0]} {
4798 makeupline $p $rm1 $row $col
4802 set id [lindex $displayorder $row]
4803 if {$row > $downarrowlen} {
4804 set termrow [expr {$row - $downarrowlen - 1}]
4805 foreach p [lindex $parentlist $termrow] {
4806 set i [lsearch -exact $idlist $p]
4807 if {$i < 0} continue
4808 set nr [nextuse $p $termrow]
4809 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4810 set idlist [lreplace $idlist $i $i]
4814 set col [lsearch -exact $idlist $id]
4815 if {$col < 0} {
4816 set col [idcol $idlist $id]
4817 set idlist [linsert $idlist $col $id]
4818 if {$children($curview,$id) ne {}} {
4819 makeupline $id $rm1 $row $col
4822 set r [expr {$row + $uparrowlen - 1}]
4823 if {$r < $commitidx($curview)} {
4824 set x $col
4825 foreach p [lindex $parentlist $r] {
4826 if {[lsearch -exact $idlist $p] >= 0} continue
4827 set fk [lindex $children($curview,$p) 0]
4828 if {[rowofcommit $fk] < $row} {
4829 set x [idcol $idlist $p $x]
4830 set idlist [linsert $idlist $x $p]
4833 if {[incr r] < $commitidx($curview)} {
4834 set p [lindex $displayorder $r]
4835 if {[lsearch -exact $idlist $p] < 0} {
4836 set fk [lindex $children($curview,$p) 0]
4837 if {$fk ne {} && [rowofcommit $fk] < $row} {
4838 set x [idcol $idlist $p $x]
4839 set idlist [linsert $idlist $x $p]
4845 if {$final && !$viewcomplete($curview) &&
4846 $row + $uparrowlen + $mingaplen + $downarrowlen
4847 >= $commitidx($curview)} {
4848 set final 0
4850 set l [llength $rowidlist]
4851 if {$row == $l} {
4852 lappend rowidlist $idlist
4853 lappend rowisopt 0
4854 lappend rowfinal $final
4855 } elseif {$row < $l} {
4856 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4857 lset rowidlist $row $idlist
4858 changedrow $row
4860 lset rowfinal $row $final
4861 } else {
4862 set pad [ntimes [expr {$row - $l}] {}]
4863 set rowidlist [concat $rowidlist $pad]
4864 lappend rowidlist $idlist
4865 set rowfinal [concat $rowfinal $pad]
4866 lappend rowfinal $final
4867 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4870 return $row
4873 proc changedrow {row} {
4874 global displayorder iddrawn rowisopt need_redisplay
4876 set l [llength $rowisopt]
4877 if {$row < $l} {
4878 lset rowisopt $row 0
4879 if {$row + 1 < $l} {
4880 lset rowisopt [expr {$row + 1}] 0
4881 if {$row + 2 < $l} {
4882 lset rowisopt [expr {$row + 2}] 0
4886 set id [lindex $displayorder $row]
4887 if {[info exists iddrawn($id)]} {
4888 set need_redisplay 1
4892 proc insert_pad {row col npad} {
4893 global rowidlist
4895 set pad [ntimes $npad {}]
4896 set idlist [lindex $rowidlist $row]
4897 set bef [lrange $idlist 0 [expr {$col - 1}]]
4898 set aft [lrange $idlist $col end]
4899 set i [lsearch -exact $aft {}]
4900 if {$i > 0} {
4901 set aft [lreplace $aft $i $i]
4903 lset rowidlist $row [concat $bef $pad $aft]
4904 changedrow $row
4907 proc optimize_rows {row col endrow} {
4908 global rowidlist rowisopt displayorder curview children
4910 if {$row < 1} {
4911 set row 1
4913 for {} {$row < $endrow} {incr row; set col 0} {
4914 if {[lindex $rowisopt $row]} continue
4915 set haspad 0
4916 set y0 [expr {$row - 1}]
4917 set ym [expr {$row - 2}]
4918 set idlist [lindex $rowidlist $row]
4919 set previdlist [lindex $rowidlist $y0]
4920 if {$idlist eq {} || $previdlist eq {}} continue
4921 if {$ym >= 0} {
4922 set pprevidlist [lindex $rowidlist $ym]
4923 if {$pprevidlist eq {}} continue
4924 } else {
4925 set pprevidlist {}
4927 set x0 -1
4928 set xm -1
4929 for {} {$col < [llength $idlist]} {incr col} {
4930 set id [lindex $idlist $col]
4931 if {[lindex $previdlist $col] eq $id} continue
4932 if {$id eq {}} {
4933 set haspad 1
4934 continue
4936 set x0 [lsearch -exact $previdlist $id]
4937 if {$x0 < 0} continue
4938 set z [expr {$x0 - $col}]
4939 set isarrow 0
4940 set z0 {}
4941 if {$ym >= 0} {
4942 set xm [lsearch -exact $pprevidlist $id]
4943 if {$xm >= 0} {
4944 set z0 [expr {$xm - $x0}]
4947 if {$z0 eq {}} {
4948 # if row y0 is the first child of $id then it's not an arrow
4949 if {[lindex $children($curview,$id) 0] ne
4950 [lindex $displayorder $y0]} {
4951 set isarrow 1
4954 if {!$isarrow && $id ne [lindex $displayorder $row] &&
4955 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
4956 set isarrow 1
4958 # Looking at lines from this row to the previous row,
4959 # make them go straight up if they end in an arrow on
4960 # the previous row; otherwise make them go straight up
4961 # or at 45 degrees.
4962 if {$z < -1 || ($z < 0 && $isarrow)} {
4963 # Line currently goes left too much;
4964 # insert pads in the previous row, then optimize it
4965 set npad [expr {-1 - $z + $isarrow}]
4966 insert_pad $y0 $x0 $npad
4967 if {$y0 > 0} {
4968 optimize_rows $y0 $x0 $row
4970 set previdlist [lindex $rowidlist $y0]
4971 set x0 [lsearch -exact $previdlist $id]
4972 set z [expr {$x0 - $col}]
4973 if {$z0 ne {}} {
4974 set pprevidlist [lindex $rowidlist $ym]
4975 set xm [lsearch -exact $pprevidlist $id]
4976 set z0 [expr {$xm - $x0}]
4978 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
4979 # Line currently goes right too much;
4980 # insert pads in this line
4981 set npad [expr {$z - 1 + $isarrow}]
4982 insert_pad $row $col $npad
4983 set idlist [lindex $rowidlist $row]
4984 incr col $npad
4985 set z [expr {$x0 - $col}]
4986 set haspad 1
4988 if {$z0 eq {} && !$isarrow && $ym >= 0} {
4989 # this line links to its first child on row $row-2
4990 set id [lindex $displayorder $ym]
4991 set xc [lsearch -exact $pprevidlist $id]
4992 if {$xc >= 0} {
4993 set z0 [expr {$xc - $x0}]
4996 # avoid lines jigging left then immediately right
4997 if {$z0 ne {} && $z < 0 && $z0 > 0} {
4998 insert_pad $y0 $x0 1
4999 incr x0
5000 optimize_rows $y0 $x0 $row
5001 set previdlist [lindex $rowidlist $y0]
5004 if {!$haspad} {
5005 # Find the first column that doesn't have a line going right
5006 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5007 set id [lindex $idlist $col]
5008 if {$id eq {}} break
5009 set x0 [lsearch -exact $previdlist $id]
5010 if {$x0 < 0} {
5011 # check if this is the link to the first child
5012 set kid [lindex $displayorder $y0]
5013 if {[lindex $children($curview,$id) 0] eq $kid} {
5014 # it is, work out offset to child
5015 set x0 [lsearch -exact $previdlist $kid]
5018 if {$x0 <= $col} break
5020 # Insert a pad at that column as long as it has a line and
5021 # isn't the last column
5022 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5023 set idlist [linsert $idlist $col {}]
5024 lset rowidlist $row $idlist
5025 changedrow $row
5031 proc xc {row col} {
5032 global canvx0 linespc
5033 return [expr {$canvx0 + $col * $linespc}]
5036 proc yc {row} {
5037 global canvy0 linespc
5038 return [expr {$canvy0 + $row * $linespc}]
5041 proc linewidth {id} {
5042 global thickerline lthickness
5044 set wid $lthickness
5045 if {[info exists thickerline] && $id eq $thickerline} {
5046 set wid [expr {2 * $lthickness}]
5048 return $wid
5051 proc rowranges {id} {
5052 global curview children uparrowlen downarrowlen
5053 global rowidlist
5055 set kids $children($curview,$id)
5056 if {$kids eq {}} {
5057 return {}
5059 set ret {}
5060 lappend kids $id
5061 foreach child $kids {
5062 if {![commitinview $child $curview]} break
5063 set row [rowofcommit $child]
5064 if {![info exists prev]} {
5065 lappend ret [expr {$row + 1}]
5066 } else {
5067 if {$row <= $prevrow} {
5068 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5070 # see if the line extends the whole way from prevrow to row
5071 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5072 [lsearch -exact [lindex $rowidlist \
5073 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5074 # it doesn't, see where it ends
5075 set r [expr {$prevrow + $downarrowlen}]
5076 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5077 while {[incr r -1] > $prevrow &&
5078 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5079 } else {
5080 while {[incr r] <= $row &&
5081 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5082 incr r -1
5084 lappend ret $r
5085 # see where it starts up again
5086 set r [expr {$row - $uparrowlen}]
5087 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5088 while {[incr r] < $row &&
5089 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5090 } else {
5091 while {[incr r -1] >= $prevrow &&
5092 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5093 incr r
5095 lappend ret $r
5098 if {$child eq $id} {
5099 lappend ret $row
5101 set prev $child
5102 set prevrow $row
5104 return $ret
5107 proc drawlineseg {id row endrow arrowlow} {
5108 global rowidlist displayorder iddrawn linesegs
5109 global canv colormap linespc curview maxlinelen parentlist
5111 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5112 set le [expr {$row + 1}]
5113 set arrowhigh 1
5114 while {1} {
5115 set c [lsearch -exact [lindex $rowidlist $le] $id]
5116 if {$c < 0} {
5117 incr le -1
5118 break
5120 lappend cols $c
5121 set x [lindex $displayorder $le]
5122 if {$x eq $id} {
5123 set arrowhigh 0
5124 break
5126 if {[info exists iddrawn($x)] || $le == $endrow} {
5127 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5128 if {$c >= 0} {
5129 lappend cols $c
5130 set arrowhigh 0
5132 break
5134 incr le
5136 if {$le <= $row} {
5137 return $row
5140 set lines {}
5141 set i 0
5142 set joinhigh 0
5143 if {[info exists linesegs($id)]} {
5144 set lines $linesegs($id)
5145 foreach li $lines {
5146 set r0 [lindex $li 0]
5147 if {$r0 > $row} {
5148 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5149 set joinhigh 1
5151 break
5153 incr i
5156 set joinlow 0
5157 if {$i > 0} {
5158 set li [lindex $lines [expr {$i-1}]]
5159 set r1 [lindex $li 1]
5160 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5161 set joinlow 1
5165 set x [lindex $cols [expr {$le - $row}]]
5166 set xp [lindex $cols [expr {$le - 1 - $row}]]
5167 set dir [expr {$xp - $x}]
5168 if {$joinhigh} {
5169 set ith [lindex $lines $i 2]
5170 set coords [$canv coords $ith]
5171 set ah [$canv itemcget $ith -arrow]
5172 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5173 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5174 if {$x2 ne {} && $x - $x2 == $dir} {
5175 set coords [lrange $coords 0 end-2]
5177 } else {
5178 set coords [list [xc $le $x] [yc $le]]
5180 if {$joinlow} {
5181 set itl [lindex $lines [expr {$i-1}] 2]
5182 set al [$canv itemcget $itl -arrow]
5183 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5184 } elseif {$arrowlow} {
5185 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5186 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5187 set arrowlow 0
5190 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5191 for {set y $le} {[incr y -1] > $row} {} {
5192 set x $xp
5193 set xp [lindex $cols [expr {$y - 1 - $row}]]
5194 set ndir [expr {$xp - $x}]
5195 if {$dir != $ndir || $xp < 0} {
5196 lappend coords [xc $y $x] [yc $y]
5198 set dir $ndir
5200 if {!$joinlow} {
5201 if {$xp < 0} {
5202 # join parent line to first child
5203 set ch [lindex $displayorder $row]
5204 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5205 if {$xc < 0} {
5206 puts "oops: drawlineseg: child $ch not on row $row"
5207 } elseif {$xc != $x} {
5208 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5209 set d [expr {int(0.5 * $linespc)}]
5210 set x1 [xc $row $x]
5211 if {$xc < $x} {
5212 set x2 [expr {$x1 - $d}]
5213 } else {
5214 set x2 [expr {$x1 + $d}]
5216 set y2 [yc $row]
5217 set y1 [expr {$y2 + $d}]
5218 lappend coords $x1 $y1 $x2 $y2
5219 } elseif {$xc < $x - 1} {
5220 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5221 } elseif {$xc > $x + 1} {
5222 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5224 set x $xc
5226 lappend coords [xc $row $x] [yc $row]
5227 } else {
5228 set xn [xc $row $xp]
5229 set yn [yc $row]
5230 lappend coords $xn $yn
5232 if {!$joinhigh} {
5233 assigncolor $id
5234 set t [$canv create line $coords -width [linewidth $id] \
5235 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5236 $canv lower $t
5237 bindline $t $id
5238 set lines [linsert $lines $i [list $row $le $t]]
5239 } else {
5240 $canv coords $ith $coords
5241 if {$arrow ne $ah} {
5242 $canv itemconf $ith -arrow $arrow
5244 lset lines $i 0 $row
5246 } else {
5247 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5248 set ndir [expr {$xo - $xp}]
5249 set clow [$canv coords $itl]
5250 if {$dir == $ndir} {
5251 set clow [lrange $clow 2 end]
5253 set coords [concat $coords $clow]
5254 if {!$joinhigh} {
5255 lset lines [expr {$i-1}] 1 $le
5256 } else {
5257 # coalesce two pieces
5258 $canv delete $ith
5259 set b [lindex $lines [expr {$i-1}] 0]
5260 set e [lindex $lines $i 1]
5261 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5263 $canv coords $itl $coords
5264 if {$arrow ne $al} {
5265 $canv itemconf $itl -arrow $arrow
5269 set linesegs($id) $lines
5270 return $le
5273 proc drawparentlinks {id row} {
5274 global rowidlist canv colormap curview parentlist
5275 global idpos linespc
5277 set rowids [lindex $rowidlist $row]
5278 set col [lsearch -exact $rowids $id]
5279 if {$col < 0} return
5280 set olds [lindex $parentlist $row]
5281 set row2 [expr {$row + 1}]
5282 set x [xc $row $col]
5283 set y [yc $row]
5284 set y2 [yc $row2]
5285 set d [expr {int(0.5 * $linespc)}]
5286 set ymid [expr {$y + $d}]
5287 set ids [lindex $rowidlist $row2]
5288 # rmx = right-most X coord used
5289 set rmx 0
5290 foreach p $olds {
5291 set i [lsearch -exact $ids $p]
5292 if {$i < 0} {
5293 puts "oops, parent $p of $id not in list"
5294 continue
5296 set x2 [xc $row2 $i]
5297 if {$x2 > $rmx} {
5298 set rmx $x2
5300 set j [lsearch -exact $rowids $p]
5301 if {$j < 0} {
5302 # drawlineseg will do this one for us
5303 continue
5305 assigncolor $p
5306 # should handle duplicated parents here...
5307 set coords [list $x $y]
5308 if {$i != $col} {
5309 # if attaching to a vertical segment, draw a smaller
5310 # slant for visual distinctness
5311 if {$i == $j} {
5312 if {$i < $col} {
5313 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5314 } else {
5315 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5317 } elseif {$i < $col && $i < $j} {
5318 # segment slants towards us already
5319 lappend coords [xc $row $j] $y
5320 } else {
5321 if {$i < $col - 1} {
5322 lappend coords [expr {$x2 + $linespc}] $y
5323 } elseif {$i > $col + 1} {
5324 lappend coords [expr {$x2 - $linespc}] $y
5326 lappend coords $x2 $y2
5328 } else {
5329 lappend coords $x2 $y2
5331 set t [$canv create line $coords -width [linewidth $p] \
5332 -fill $colormap($p) -tags lines.$p]
5333 $canv lower $t
5334 bindline $t $p
5336 if {$rmx > [lindex $idpos($id) 1]} {
5337 lset idpos($id) 1 $rmx
5338 redrawtags $id
5342 proc drawlines {id} {
5343 global canv
5345 $canv itemconf lines.$id -width [linewidth $id]
5348 proc drawcmittext {id row col} {
5349 global linespc canv canv2 canv3 fgcolor curview
5350 global cmitlisted commitinfo rowidlist parentlist
5351 global rowtextx idpos idtags idheads idotherrefs
5352 global linehtag linentag linedtag selectedline
5353 global canvxmax boldrows boldnamerows fgcolor
5354 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5356 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5357 set listed $cmitlisted($curview,$id)
5358 if {$id eq $nullid} {
5359 set ofill red
5360 } elseif {$id eq $nullid2} {
5361 set ofill green
5362 } elseif {$id eq $mainheadid} {
5363 set ofill yellow
5364 } else {
5365 set ofill [lindex $circlecolors $listed]
5367 set x [xc $row $col]
5368 set y [yc $row]
5369 set orad [expr {$linespc / 3}]
5370 if {$listed <= 2} {
5371 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5372 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5373 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5374 } elseif {$listed == 3} {
5375 # triangle pointing left for left-side commits
5376 set t [$canv create polygon \
5377 [expr {$x - $orad}] $y \
5378 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5379 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5380 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5381 } else {
5382 # triangle pointing right for right-side commits
5383 set t [$canv create polygon \
5384 [expr {$x + $orad - 1}] $y \
5385 [expr {$x - $orad}] [expr {$y - $orad}] \
5386 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5387 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5389 set circleitem($row) $t
5390 $canv raise $t
5391 $canv bind $t <1> {selcanvline {} %x %y}
5392 set rmx [llength [lindex $rowidlist $row]]
5393 set olds [lindex $parentlist $row]
5394 if {$olds ne {}} {
5395 set nextids [lindex $rowidlist [expr {$row + 1}]]
5396 foreach p $olds {
5397 set i [lsearch -exact $nextids $p]
5398 if {$i > $rmx} {
5399 set rmx $i
5403 set xt [xc $row $rmx]
5404 set rowtextx($row) $xt
5405 set idpos($id) [list $x $xt $y]
5406 if {[info exists idtags($id)] || [info exists idheads($id)]
5407 || [info exists idotherrefs($id)]} {
5408 set xt [drawtags $id $x $xt $y]
5410 set headline [lindex $commitinfo($id) 0]
5411 set name [lindex $commitinfo($id) 1]
5412 set date [lindex $commitinfo($id) 2]
5413 set date [formatdate $date]
5414 set font mainfont
5415 set nfont mainfont
5416 set isbold [ishighlighted $id]
5417 if {$isbold > 0} {
5418 lappend boldrows $row
5419 set font mainfontbold
5420 if {$isbold > 1} {
5421 lappend boldnamerows $row
5422 set nfont mainfontbold
5425 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
5426 -text $headline -font $font -tags text]
5427 $canv bind $linehtag($row) $ctxbut "rowmenu %X %Y $id"
5428 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5429 -text $name -font $nfont -tags text]
5430 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5431 -text $date -font mainfont -tags text]
5432 if {$selectedline == $row} {
5433 make_secsel $row
5435 set xr [expr {$xt + [font measure $font $headline]}]
5436 if {$xr > $canvxmax} {
5437 set canvxmax $xr
5438 setcanvscroll
5442 proc drawcmitrow {row} {
5443 global displayorder rowidlist nrows_drawn
5444 global iddrawn markingmatches
5445 global commitinfo numcommits
5446 global filehighlight fhighlights findpattern nhighlights
5447 global hlview vhighlights
5448 global highlight_related rhighlights
5450 if {$row >= $numcommits} return
5452 set id [lindex $displayorder $row]
5453 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5454 askvhighlight $row $id
5456 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5457 askfilehighlight $row $id
5459 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5460 askfindhighlight $row $id
5462 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5463 askrelhighlight $row $id
5465 if {![info exists iddrawn($id)]} {
5466 set col [lsearch -exact [lindex $rowidlist $row] $id]
5467 if {$col < 0} {
5468 puts "oops, row $row id $id not in list"
5469 return
5471 if {![info exists commitinfo($id)]} {
5472 getcommit $id
5474 assigncolor $id
5475 drawcmittext $id $row $col
5476 set iddrawn($id) 1
5477 incr nrows_drawn
5479 if {$markingmatches} {
5480 markrowmatches $row $id
5484 proc drawcommits {row {endrow {}}} {
5485 global numcommits iddrawn displayorder curview need_redisplay
5486 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5488 if {$row < 0} {
5489 set row 0
5491 if {$endrow eq {}} {
5492 set endrow $row
5494 if {$endrow >= $numcommits} {
5495 set endrow [expr {$numcommits - 1}]
5498 set rl1 [expr {$row - $downarrowlen - 3}]
5499 if {$rl1 < 0} {
5500 set rl1 0
5502 set ro1 [expr {$row - 3}]
5503 if {$ro1 < 0} {
5504 set ro1 0
5506 set r2 [expr {$endrow + $uparrowlen + 3}]
5507 if {$r2 > $numcommits} {
5508 set r2 $numcommits
5510 for {set r $rl1} {$r < $r2} {incr r} {
5511 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5512 if {$rl1 < $r} {
5513 layoutrows $rl1 $r
5515 set rl1 [expr {$r + 1}]
5518 if {$rl1 < $r} {
5519 layoutrows $rl1 $r
5521 optimize_rows $ro1 0 $r2
5522 if {$need_redisplay || $nrows_drawn > 2000} {
5523 clear_display
5524 drawvisible
5527 # make the lines join to already-drawn rows either side
5528 set r [expr {$row - 1}]
5529 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5530 set r $row
5532 set er [expr {$endrow + 1}]
5533 if {$er >= $numcommits ||
5534 ![info exists iddrawn([lindex $displayorder $er])]} {
5535 set er $endrow
5537 for {} {$r <= $er} {incr r} {
5538 set id [lindex $displayorder $r]
5539 set wasdrawn [info exists iddrawn($id)]
5540 drawcmitrow $r
5541 if {$r == $er} break
5542 set nextid [lindex $displayorder [expr {$r + 1}]]
5543 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5544 drawparentlinks $id $r
5546 set rowids [lindex $rowidlist $r]
5547 foreach lid $rowids {
5548 if {$lid eq {}} continue
5549 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5550 if {$lid eq $id} {
5551 # see if this is the first child of any of its parents
5552 foreach p [lindex $parentlist $r] {
5553 if {[lsearch -exact $rowids $p] < 0} {
5554 # make this line extend up to the child
5555 set lineend($p) [drawlineseg $p $r $er 0]
5558 } else {
5559 set lineend($lid) [drawlineseg $lid $r $er 1]
5565 proc undolayout {row} {
5566 global uparrowlen mingaplen downarrowlen
5567 global rowidlist rowisopt rowfinal need_redisplay
5569 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5570 if {$r < 0} {
5571 set r 0
5573 if {[llength $rowidlist] > $r} {
5574 incr r -1
5575 set rowidlist [lrange $rowidlist 0 $r]
5576 set rowfinal [lrange $rowfinal 0 $r]
5577 set rowisopt [lrange $rowisopt 0 $r]
5578 set need_redisplay 1
5579 run drawvisible
5583 proc drawvisible {} {
5584 global canv linespc curview vrowmod selectedline targetrow targetid
5585 global need_redisplay cscroll numcommits
5587 set fs [$canv yview]
5588 set ymax [lindex [$canv cget -scrollregion] 3]
5589 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5590 set f0 [lindex $fs 0]
5591 set f1 [lindex $fs 1]
5592 set y0 [expr {int($f0 * $ymax)}]
5593 set y1 [expr {int($f1 * $ymax)}]
5595 if {[info exists targetid]} {
5596 if {[commitinview $targetid $curview]} {
5597 set r [rowofcommit $targetid]
5598 if {$r != $targetrow} {
5599 # Fix up the scrollregion and change the scrolling position
5600 # now that our target row has moved.
5601 set diff [expr {($r - $targetrow) * $linespc}]
5602 set targetrow $r
5603 setcanvscroll
5604 set ymax [lindex [$canv cget -scrollregion] 3]
5605 incr y0 $diff
5606 incr y1 $diff
5607 set f0 [expr {$y0 / $ymax}]
5608 set f1 [expr {$y1 / $ymax}]
5609 allcanvs yview moveto $f0
5610 $cscroll set $f0 $f1
5611 set need_redisplay 1
5613 } else {
5614 unset targetid
5618 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5619 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5620 if {$endrow >= $vrowmod($curview)} {
5621 update_arcrows $curview
5623 if {$selectedline ne {} &&
5624 $row <= $selectedline && $selectedline <= $endrow} {
5625 set targetrow $selectedline
5626 } elseif {[info exists targetid]} {
5627 set targetrow [expr {int(($row + $endrow) / 2)}]
5629 if {[info exists targetrow]} {
5630 if {$targetrow >= $numcommits} {
5631 set targetrow [expr {$numcommits - 1}]
5633 set targetid [commitonrow $targetrow]
5635 drawcommits $row $endrow
5638 proc clear_display {} {
5639 global iddrawn linesegs need_redisplay nrows_drawn
5640 global vhighlights fhighlights nhighlights rhighlights
5641 global linehtag linentag linedtag boldrows boldnamerows
5643 allcanvs delete all
5644 catch {unset iddrawn}
5645 catch {unset linesegs}
5646 catch {unset linehtag}
5647 catch {unset linentag}
5648 catch {unset linedtag}
5649 set boldrows {}
5650 set boldnamerows {}
5651 catch {unset vhighlights}
5652 catch {unset fhighlights}
5653 catch {unset nhighlights}
5654 catch {unset rhighlights}
5655 set need_redisplay 0
5656 set nrows_drawn 0
5659 proc findcrossings {id} {
5660 global rowidlist parentlist numcommits displayorder
5662 set cross {}
5663 set ccross {}
5664 foreach {s e} [rowranges $id] {
5665 if {$e >= $numcommits} {
5666 set e [expr {$numcommits - 1}]
5668 if {$e <= $s} continue
5669 for {set row $e} {[incr row -1] >= $s} {} {
5670 set x [lsearch -exact [lindex $rowidlist $row] $id]
5671 if {$x < 0} break
5672 set olds [lindex $parentlist $row]
5673 set kid [lindex $displayorder $row]
5674 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5675 if {$kidx < 0} continue
5676 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5677 foreach p $olds {
5678 set px [lsearch -exact $nextrow $p]
5679 if {$px < 0} continue
5680 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5681 if {[lsearch -exact $ccross $p] >= 0} continue
5682 if {$x == $px + ($kidx < $px? -1: 1)} {
5683 lappend ccross $p
5684 } elseif {[lsearch -exact $cross $p] < 0} {
5685 lappend cross $p
5691 return [concat $ccross {{}} $cross]
5694 proc assigncolor {id} {
5695 global colormap colors nextcolor
5696 global parents children children curview
5698 if {[info exists colormap($id)]} return
5699 set ncolors [llength $colors]
5700 if {[info exists children($curview,$id)]} {
5701 set kids $children($curview,$id)
5702 } else {
5703 set kids {}
5705 if {[llength $kids] == 1} {
5706 set child [lindex $kids 0]
5707 if {[info exists colormap($child)]
5708 && [llength $parents($curview,$child)] == 1} {
5709 set colormap($id) $colormap($child)
5710 return
5713 set badcolors {}
5714 set origbad {}
5715 foreach x [findcrossings $id] {
5716 if {$x eq {}} {
5717 # delimiter between corner crossings and other crossings
5718 if {[llength $badcolors] >= $ncolors - 1} break
5719 set origbad $badcolors
5721 if {[info exists colormap($x)]
5722 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5723 lappend badcolors $colormap($x)
5726 if {[llength $badcolors] >= $ncolors} {
5727 set badcolors $origbad
5729 set origbad $badcolors
5730 if {[llength $badcolors] < $ncolors - 1} {
5731 foreach child $kids {
5732 if {[info exists colormap($child)]
5733 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5734 lappend badcolors $colormap($child)
5736 foreach p $parents($curview,$child) {
5737 if {[info exists colormap($p)]
5738 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5739 lappend badcolors $colormap($p)
5743 if {[llength $badcolors] >= $ncolors} {
5744 set badcolors $origbad
5747 for {set i 0} {$i <= $ncolors} {incr i} {
5748 set c [lindex $colors $nextcolor]
5749 if {[incr nextcolor] >= $ncolors} {
5750 set nextcolor 0
5752 if {[lsearch -exact $badcolors $c]} break
5754 set colormap($id) $c
5757 proc bindline {t id} {
5758 global canv
5760 $canv bind $t <Enter> "lineenter %x %y $id"
5761 $canv bind $t <Motion> "linemotion %x %y $id"
5762 $canv bind $t <Leave> "lineleave $id"
5763 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5766 proc drawtags {id x xt y1} {
5767 global idtags idheads idotherrefs mainhead
5768 global linespc lthickness
5769 global canv rowtextx curview fgcolor bgcolor ctxbut
5771 set marks {}
5772 set ntags 0
5773 set nheads 0
5774 if {[info exists idtags($id)]} {
5775 set marks $idtags($id)
5776 set ntags [llength $marks]
5778 if {[info exists idheads($id)]} {
5779 set marks [concat $marks $idheads($id)]
5780 set nheads [llength $idheads($id)]
5782 if {[info exists idotherrefs($id)]} {
5783 set marks [concat $marks $idotherrefs($id)]
5785 if {$marks eq {}} {
5786 return $xt
5789 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5790 set yt [expr {$y1 - 0.5 * $linespc}]
5791 set yb [expr {$yt + $linespc - 1}]
5792 set xvals {}
5793 set wvals {}
5794 set i -1
5795 foreach tag $marks {
5796 incr i
5797 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5798 set wid [font measure mainfontbold $tag]
5799 } else {
5800 set wid [font measure mainfont $tag]
5802 lappend xvals $xt
5803 lappend wvals $wid
5804 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5806 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5807 -width $lthickness -fill black -tags tag.$id]
5808 $canv lower $t
5809 foreach tag $marks x $xvals wid $wvals {
5810 set xl [expr {$x + $delta}]
5811 set xr [expr {$x + $delta + $wid + $lthickness}]
5812 set font mainfont
5813 if {[incr ntags -1] >= 0} {
5814 # draw a tag
5815 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5816 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5817 -width 1 -outline black -fill yellow -tags tag.$id]
5818 $canv bind $t <1> [list showtag $tag 1]
5819 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5820 } else {
5821 # draw a head or other ref
5822 if {[incr nheads -1] >= 0} {
5823 set col green
5824 if {$tag eq $mainhead} {
5825 set font mainfontbold
5827 } else {
5828 set col "#ddddff"
5830 set xl [expr {$xl - $delta/2}]
5831 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5832 -width 1 -outline black -fill $col -tags tag.$id
5833 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5834 set rwid [font measure mainfont $remoteprefix]
5835 set xi [expr {$x + 1}]
5836 set yti [expr {$yt + 1}]
5837 set xri [expr {$x + $rwid}]
5838 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5839 -width 0 -fill "#ffddaa" -tags tag.$id
5842 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5843 -font $font -tags [list tag.$id text]]
5844 if {$ntags >= 0} {
5845 $canv bind $t <1> [list showtag $tag 1]
5846 } elseif {$nheads >= 0} {
5847 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5850 return $xt
5853 proc xcoord {i level ln} {
5854 global canvx0 xspc1 xspc2
5856 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5857 if {$i > 0 && $i == $level} {
5858 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5859 } elseif {$i > $level} {
5860 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5862 return $x
5865 proc show_status {msg} {
5866 global canv fgcolor
5868 clear_display
5869 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5870 -tags text -fill $fgcolor
5873 # Don't change the text pane cursor if it is currently the hand cursor,
5874 # showing that we are over a sha1 ID link.
5875 proc settextcursor {c} {
5876 global ctext curtextcursor
5878 if {[$ctext cget -cursor] == $curtextcursor} {
5879 $ctext config -cursor $c
5881 set curtextcursor $c
5884 proc nowbusy {what {name {}}} {
5885 global isbusy busyname statusw
5887 if {[array names isbusy] eq {}} {
5888 . config -cursor watch
5889 settextcursor watch
5891 set isbusy($what) 1
5892 set busyname($what) $name
5893 if {$name ne {}} {
5894 $statusw conf -text $name
5898 proc notbusy {what} {
5899 global isbusy maincursor textcursor busyname statusw
5901 catch {
5902 unset isbusy($what)
5903 if {$busyname($what) ne {} &&
5904 [$statusw cget -text] eq $busyname($what)} {
5905 $statusw conf -text {}
5908 if {[array names isbusy] eq {}} {
5909 . config -cursor $maincursor
5910 settextcursor $textcursor
5914 proc findmatches {f} {
5915 global findtype findstring
5916 if {$findtype == [mc "Regexp"]} {
5917 set matches [regexp -indices -all -inline $findstring $f]
5918 } else {
5919 set fs $findstring
5920 if {$findtype == [mc "IgnCase"]} {
5921 set f [string tolower $f]
5922 set fs [string tolower $fs]
5924 set matches {}
5925 set i 0
5926 set l [string length $fs]
5927 while {[set j [string first $fs $f $i]] >= 0} {
5928 lappend matches [list $j [expr {$j+$l-1}]]
5929 set i [expr {$j + $l}]
5932 return $matches
5935 proc dofind {{dirn 1} {wrap 1}} {
5936 global findstring findstartline findcurline selectedline numcommits
5937 global gdttype filehighlight fh_serial find_dirn findallowwrap
5939 if {[info exists find_dirn]} {
5940 if {$find_dirn == $dirn} return
5941 stopfinding
5943 focus .
5944 if {$findstring eq {} || $numcommits == 0} return
5945 if {$selectedline eq {}} {
5946 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
5947 } else {
5948 set findstartline $selectedline
5950 set findcurline $findstartline
5951 nowbusy finding [mc "Searching"]
5952 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
5953 after cancel do_file_hl $fh_serial
5954 do_file_hl $fh_serial
5956 set find_dirn $dirn
5957 set findallowwrap $wrap
5958 run findmore
5961 proc stopfinding {} {
5962 global find_dirn findcurline fprogcoord
5964 if {[info exists find_dirn]} {
5965 unset find_dirn
5966 unset findcurline
5967 notbusy finding
5968 set fprogcoord 0
5969 adjustprogress
5971 stopblaming
5974 proc findmore {} {
5975 global commitdata commitinfo numcommits findpattern findloc
5976 global findstartline findcurline findallowwrap
5977 global find_dirn gdttype fhighlights fprogcoord
5978 global curview varcorder vrownum varccommits vrowmod
5980 if {![info exists find_dirn]} {
5981 return 0
5983 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
5984 set l $findcurline
5985 set moretodo 0
5986 if {$find_dirn > 0} {
5987 incr l
5988 if {$l >= $numcommits} {
5989 set l 0
5991 if {$l <= $findstartline} {
5992 set lim [expr {$findstartline + 1}]
5993 } else {
5994 set lim $numcommits
5995 set moretodo $findallowwrap
5997 } else {
5998 if {$l == 0} {
5999 set l $numcommits
6001 incr l -1
6002 if {$l >= $findstartline} {
6003 set lim [expr {$findstartline - 1}]
6004 } else {
6005 set lim -1
6006 set moretodo $findallowwrap
6009 set n [expr {($lim - $l) * $find_dirn}]
6010 if {$n > 500} {
6011 set n 500
6012 set moretodo 1
6014 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6015 update_arcrows $curview
6017 set found 0
6018 set domore 1
6019 set ai [bsearch $vrownum($curview) $l]
6020 set a [lindex $varcorder($curview) $ai]
6021 set arow [lindex $vrownum($curview) $ai]
6022 set ids [lindex $varccommits($curview,$a)]
6023 set arowend [expr {$arow + [llength $ids]}]
6024 if {$gdttype eq [mc "containing:"]} {
6025 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6026 if {$l < $arow || $l >= $arowend} {
6027 incr ai $find_dirn
6028 set a [lindex $varcorder($curview) $ai]
6029 set arow [lindex $vrownum($curview) $ai]
6030 set ids [lindex $varccommits($curview,$a)]
6031 set arowend [expr {$arow + [llength $ids]}]
6033 set id [lindex $ids [expr {$l - $arow}]]
6034 # shouldn't happen unless git log doesn't give all the commits...
6035 if {![info exists commitdata($id)] ||
6036 ![doesmatch $commitdata($id)]} {
6037 continue
6039 if {![info exists commitinfo($id)]} {
6040 getcommit $id
6042 set info $commitinfo($id)
6043 foreach f $info ty $fldtypes {
6044 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6045 [doesmatch $f]} {
6046 set found 1
6047 break
6050 if {$found} break
6052 } else {
6053 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6054 if {$l < $arow || $l >= $arowend} {
6055 incr ai $find_dirn
6056 set a [lindex $varcorder($curview) $ai]
6057 set arow [lindex $vrownum($curview) $ai]
6058 set ids [lindex $varccommits($curview,$a)]
6059 set arowend [expr {$arow + [llength $ids]}]
6061 set id [lindex $ids [expr {$l - $arow}]]
6062 if {![info exists fhighlights($id)]} {
6063 # this sets fhighlights($id) to -1
6064 askfilehighlight $l $id
6066 if {$fhighlights($id) > 0} {
6067 set found $domore
6068 break
6070 if {$fhighlights($id) < 0} {
6071 if {$domore} {
6072 set domore 0
6073 set findcurline [expr {$l - $find_dirn}]
6078 if {$found || ($domore && !$moretodo)} {
6079 unset findcurline
6080 unset find_dirn
6081 notbusy finding
6082 set fprogcoord 0
6083 adjustprogress
6084 if {$found} {
6085 findselectline $l
6086 } else {
6087 bell
6089 return 0
6091 if {!$domore} {
6092 flushhighlights
6093 } else {
6094 set findcurline [expr {$l - $find_dirn}]
6096 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6097 if {$n < 0} {
6098 incr n $numcommits
6100 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6101 adjustprogress
6102 return $domore
6105 proc findselectline {l} {
6106 global findloc commentend ctext findcurline markingmatches gdttype
6108 set markingmatches 1
6109 set findcurline $l
6110 selectline $l 1
6111 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6112 # highlight the matches in the comments
6113 set f [$ctext get 1.0 $commentend]
6114 set matches [findmatches $f]
6115 foreach match $matches {
6116 set start [lindex $match 0]
6117 set end [expr {[lindex $match 1] + 1}]
6118 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6121 drawvisible
6124 # mark the bits of a headline or author that match a find string
6125 proc markmatches {canv l str tag matches font row} {
6126 global selectedline
6128 set bbox [$canv bbox $tag]
6129 set x0 [lindex $bbox 0]
6130 set y0 [lindex $bbox 1]
6131 set y1 [lindex $bbox 3]
6132 foreach match $matches {
6133 set start [lindex $match 0]
6134 set end [lindex $match 1]
6135 if {$start > $end} continue
6136 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6137 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6138 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6139 [expr {$x0+$xlen+2}] $y1 \
6140 -outline {} -tags [list match$l matches] -fill yellow]
6141 $canv lower $t
6142 if {$row == $selectedline} {
6143 $canv raise $t secsel
6148 proc unmarkmatches {} {
6149 global markingmatches
6151 allcanvs delete matches
6152 set markingmatches 0
6153 stopfinding
6156 proc selcanvline {w x y} {
6157 global canv canvy0 ctext linespc
6158 global rowtextx
6159 set ymax [lindex [$canv cget -scrollregion] 3]
6160 if {$ymax == {}} return
6161 set yfrac [lindex [$canv yview] 0]
6162 set y [expr {$y + $yfrac * $ymax}]
6163 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6164 if {$l < 0} {
6165 set l 0
6167 if {$w eq $canv} {
6168 set xmax [lindex [$canv cget -scrollregion] 2]
6169 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6170 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6172 unmarkmatches
6173 selectline $l 1
6176 proc commit_descriptor {p} {
6177 global commitinfo
6178 if {![info exists commitinfo($p)]} {
6179 getcommit $p
6181 set l "..."
6182 if {[llength $commitinfo($p)] > 1} {
6183 set l [lindex $commitinfo($p) 0]
6185 return "$p ($l)\n"
6188 # append some text to the ctext widget, and make any SHA1 ID
6189 # that we know about be a clickable link.
6190 proc appendwithlinks {text tags} {
6191 global ctext linknum curview
6193 set start [$ctext index "end - 1c"]
6194 $ctext insert end $text $tags
6195 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6196 foreach l $links {
6197 set s [lindex $l 0]
6198 set e [lindex $l 1]
6199 set linkid [string range $text $s $e]
6200 incr e
6201 $ctext tag delete link$linknum
6202 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6203 setlink $linkid link$linknum
6204 incr linknum
6208 proc setlink {id lk} {
6209 global curview ctext pendinglinks
6211 set known 0
6212 if {[string length $id] < 40} {
6213 set matches [longid $id]
6214 if {[llength $matches] > 0} {
6215 if {[llength $matches] > 1} return
6216 set known 1
6217 set id [lindex $matches 0]
6219 } else {
6220 set known [commitinview $id $curview]
6222 if {$known} {
6223 $ctext tag conf $lk -foreground blue -underline 1
6224 $ctext tag bind $lk <1> [list selbyid $id]
6225 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6226 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6227 } else {
6228 lappend pendinglinks($id) $lk
6229 interestedin $id {makelink %P}
6233 proc makelink {id} {
6234 global pendinglinks
6236 if {![info exists pendinglinks($id)]} return
6237 foreach lk $pendinglinks($id) {
6238 setlink $id $lk
6240 unset pendinglinks($id)
6243 proc linkcursor {w inc} {
6244 global linkentercount curtextcursor
6246 if {[incr linkentercount $inc] > 0} {
6247 $w configure -cursor hand2
6248 } else {
6249 $w configure -cursor $curtextcursor
6250 if {$linkentercount < 0} {
6251 set linkentercount 0
6256 proc viewnextline {dir} {
6257 global canv linespc
6259 $canv delete hover
6260 set ymax [lindex [$canv cget -scrollregion] 3]
6261 set wnow [$canv yview]
6262 set wtop [expr {[lindex $wnow 0] * $ymax}]
6263 set newtop [expr {$wtop + $dir * $linespc}]
6264 if {$newtop < 0} {
6265 set newtop 0
6266 } elseif {$newtop > $ymax} {
6267 set newtop $ymax
6269 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6272 # add a list of tag or branch names at position pos
6273 # returns the number of names inserted
6274 proc appendrefs {pos ids var} {
6275 global ctext linknum curview $var maxrefs
6277 if {[catch {$ctext index $pos}]} {
6278 return 0
6280 $ctext conf -state normal
6281 $ctext delete $pos "$pos lineend"
6282 set tags {}
6283 foreach id $ids {
6284 foreach tag [set $var\($id\)] {
6285 lappend tags [list $tag $id]
6288 if {[llength $tags] > $maxrefs} {
6289 $ctext insert $pos "many ([llength $tags])"
6290 } else {
6291 set tags [lsort -index 0 -decreasing $tags]
6292 set sep {}
6293 foreach ti $tags {
6294 set id [lindex $ti 1]
6295 set lk link$linknum
6296 incr linknum
6297 $ctext tag delete $lk
6298 $ctext insert $pos $sep
6299 $ctext insert $pos [lindex $ti 0] $lk
6300 setlink $id $lk
6301 set sep ", "
6304 $ctext conf -state disabled
6305 return [llength $tags]
6308 # called when we have finished computing the nearby tags
6309 proc dispneartags {delay} {
6310 global selectedline currentid showneartags tagphase
6312 if {$selectedline eq {} || !$showneartags} return
6313 after cancel dispnexttag
6314 if {$delay} {
6315 after 200 dispnexttag
6316 set tagphase -1
6317 } else {
6318 after idle dispnexttag
6319 set tagphase 0
6323 proc dispnexttag {} {
6324 global selectedline currentid showneartags tagphase ctext
6326 if {$selectedline eq {} || !$showneartags} return
6327 switch -- $tagphase {
6329 set dtags [desctags $currentid]
6330 if {$dtags ne {}} {
6331 appendrefs precedes $dtags idtags
6335 set atags [anctags $currentid]
6336 if {$atags ne {}} {
6337 appendrefs follows $atags idtags
6341 set dheads [descheads $currentid]
6342 if {$dheads ne {}} {
6343 if {[appendrefs branch $dheads idheads] > 1
6344 && [$ctext get "branch -3c"] eq "h"} {
6345 # turn "Branch" into "Branches"
6346 $ctext conf -state normal
6347 $ctext insert "branch -2c" "es"
6348 $ctext conf -state disabled
6353 if {[incr tagphase] <= 2} {
6354 after idle dispnexttag
6358 proc make_secsel {l} {
6359 global linehtag linentag linedtag canv canv2 canv3
6361 if {![info exists linehtag($l)]} return
6362 $canv delete secsel
6363 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
6364 -tags secsel -fill [$canv cget -selectbackground]]
6365 $canv lower $t
6366 $canv2 delete secsel
6367 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
6368 -tags secsel -fill [$canv2 cget -selectbackground]]
6369 $canv2 lower $t
6370 $canv3 delete secsel
6371 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
6372 -tags secsel -fill [$canv3 cget -selectbackground]]
6373 $canv3 lower $t
6376 proc selectline {l isnew {desired_loc {}}} {
6377 global canv ctext commitinfo selectedline
6378 global canvy0 linespc parents children curview
6379 global currentid sha1entry
6380 global commentend idtags linknum
6381 global mergemax numcommits pending_select
6382 global cmitmode showneartags allcommits
6383 global targetrow targetid lastscrollrows
6384 global autoselect jump_to_here
6386 catch {unset pending_select}
6387 $canv delete hover
6388 normalline
6389 unsel_reflist
6390 stopfinding
6391 if {$l < 0 || $l >= $numcommits} return
6392 set id [commitonrow $l]
6393 set targetid $id
6394 set targetrow $l
6395 set selectedline $l
6396 set currentid $id
6397 if {$lastscrollrows < $numcommits} {
6398 setcanvscroll
6401 set y [expr {$canvy0 + $l * $linespc}]
6402 set ymax [lindex [$canv cget -scrollregion] 3]
6403 set ytop [expr {$y - $linespc - 1}]
6404 set ybot [expr {$y + $linespc + 1}]
6405 set wnow [$canv yview]
6406 set wtop [expr {[lindex $wnow 0] * $ymax}]
6407 set wbot [expr {[lindex $wnow 1] * $ymax}]
6408 set wh [expr {$wbot - $wtop}]
6409 set newtop $wtop
6410 if {$ytop < $wtop} {
6411 if {$ybot < $wtop} {
6412 set newtop [expr {$y - $wh / 2.0}]
6413 } else {
6414 set newtop $ytop
6415 if {$newtop > $wtop - $linespc} {
6416 set newtop [expr {$wtop - $linespc}]
6419 } elseif {$ybot > $wbot} {
6420 if {$ytop > $wbot} {
6421 set newtop [expr {$y - $wh / 2.0}]
6422 } else {
6423 set newtop [expr {$ybot - $wh}]
6424 if {$newtop < $wtop + $linespc} {
6425 set newtop [expr {$wtop + $linespc}]
6429 if {$newtop != $wtop} {
6430 if {$newtop < 0} {
6431 set newtop 0
6433 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6434 drawvisible
6437 make_secsel $l
6439 if {$isnew} {
6440 addtohistory [list selbyid $id]
6443 $sha1entry delete 0 end
6444 $sha1entry insert 0 $id
6445 if {$autoselect} {
6446 $sha1entry selection from 0
6447 $sha1entry selection to end
6449 rhighlight_sel $id
6451 $ctext conf -state normal
6452 clear_ctext
6453 set linknum 0
6454 if {![info exists commitinfo($id)]} {
6455 getcommit $id
6457 set info $commitinfo($id)
6458 set date [formatdate [lindex $info 2]]
6459 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6460 set date [formatdate [lindex $info 4]]
6461 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6462 if {[info exists idtags($id)]} {
6463 $ctext insert end [mc "Tags:"]
6464 foreach tag $idtags($id) {
6465 $ctext insert end " $tag"
6467 $ctext insert end "\n"
6470 set headers {}
6471 set olds $parents($curview,$id)
6472 if {[llength $olds] > 1} {
6473 set np 0
6474 foreach p $olds {
6475 if {$np >= $mergemax} {
6476 set tag mmax
6477 } else {
6478 set tag m$np
6480 $ctext insert end "[mc "Parent"]: " $tag
6481 appendwithlinks [commit_descriptor $p] {}
6482 incr np
6484 } else {
6485 foreach p $olds {
6486 append headers "[mc "Parent"]: [commit_descriptor $p]"
6490 foreach c $children($curview,$id) {
6491 append headers "[mc "Child"]: [commit_descriptor $c]"
6494 # make anything that looks like a SHA1 ID be a clickable link
6495 appendwithlinks $headers {}
6496 if {$showneartags} {
6497 if {![info exists allcommits]} {
6498 getallcommits
6500 $ctext insert end "[mc "Branch"]: "
6501 $ctext mark set branch "end -1c"
6502 $ctext mark gravity branch left
6503 $ctext insert end "\n[mc "Follows"]: "
6504 $ctext mark set follows "end -1c"
6505 $ctext mark gravity follows left
6506 $ctext insert end "\n[mc "Precedes"]: "
6507 $ctext mark set precedes "end -1c"
6508 $ctext mark gravity precedes left
6509 $ctext insert end "\n"
6510 dispneartags 1
6512 $ctext insert end "\n"
6513 set comment [lindex $info 5]
6514 if {[string first "\r" $comment] >= 0} {
6515 set comment [string map {"\r" "\n "} $comment]
6517 appendwithlinks $comment {comment}
6519 $ctext tag remove found 1.0 end
6520 $ctext conf -state disabled
6521 set commentend [$ctext index "end - 1c"]
6523 set jump_to_here $desired_loc
6524 init_flist [mc "Comments"]
6525 if {$cmitmode eq "tree"} {
6526 gettree $id
6527 } elseif {[llength $olds] <= 1} {
6528 startdiff $id
6529 } else {
6530 mergediff $id
6534 proc selfirstline {} {
6535 unmarkmatches
6536 selectline 0 1
6539 proc sellastline {} {
6540 global numcommits
6541 unmarkmatches
6542 set l [expr {$numcommits - 1}]
6543 selectline $l 1
6546 proc selnextline {dir} {
6547 global selectedline
6548 focus .
6549 if {$selectedline eq {}} return
6550 set l [expr {$selectedline + $dir}]
6551 unmarkmatches
6552 selectline $l 1
6555 proc selnextpage {dir} {
6556 global canv linespc selectedline numcommits
6558 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6559 if {$lpp < 1} {
6560 set lpp 1
6562 allcanvs yview scroll [expr {$dir * $lpp}] units
6563 drawvisible
6564 if {$selectedline eq {}} return
6565 set l [expr {$selectedline + $dir * $lpp}]
6566 if {$l < 0} {
6567 set l 0
6568 } elseif {$l >= $numcommits} {
6569 set l [expr $numcommits - 1]
6571 unmarkmatches
6572 selectline $l 1
6575 proc unselectline {} {
6576 global selectedline currentid
6578 set selectedline {}
6579 catch {unset currentid}
6580 allcanvs delete secsel
6581 rhighlight_none
6584 proc reselectline {} {
6585 global selectedline
6587 if {$selectedline ne {}} {
6588 selectline $selectedline 0
6592 proc addtohistory {cmd} {
6593 global history historyindex curview
6595 set elt [list $curview $cmd]
6596 if {$historyindex > 0
6597 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6598 return
6601 if {$historyindex < [llength $history]} {
6602 set history [lreplace $history $historyindex end $elt]
6603 } else {
6604 lappend history $elt
6606 incr historyindex
6607 if {$historyindex > 1} {
6608 .tf.bar.leftbut conf -state normal
6609 } else {
6610 .tf.bar.leftbut conf -state disabled
6612 .tf.bar.rightbut conf -state disabled
6615 proc godo {elt} {
6616 global curview
6618 set view [lindex $elt 0]
6619 set cmd [lindex $elt 1]
6620 if {$curview != $view} {
6621 showview $view
6623 eval $cmd
6626 proc goback {} {
6627 global history historyindex
6628 focus .
6630 if {$historyindex > 1} {
6631 incr historyindex -1
6632 godo [lindex $history [expr {$historyindex - 1}]]
6633 .tf.bar.rightbut conf -state normal
6635 if {$historyindex <= 1} {
6636 .tf.bar.leftbut conf -state disabled
6640 proc goforw {} {
6641 global history historyindex
6642 focus .
6644 if {$historyindex < [llength $history]} {
6645 set cmd [lindex $history $historyindex]
6646 incr historyindex
6647 godo $cmd
6648 .tf.bar.leftbut conf -state normal
6650 if {$historyindex >= [llength $history]} {
6651 .tf.bar.rightbut conf -state disabled
6655 proc gettree {id} {
6656 global treefilelist treeidlist diffids diffmergeid treepending
6657 global nullid nullid2
6659 set diffids $id
6660 catch {unset diffmergeid}
6661 if {![info exists treefilelist($id)]} {
6662 if {![info exists treepending]} {
6663 if {$id eq $nullid} {
6664 set cmd [list | git ls-files]
6665 } elseif {$id eq $nullid2} {
6666 set cmd [list | git ls-files --stage -t]
6667 } else {
6668 set cmd [list | git ls-tree -r $id]
6670 if {[catch {set gtf [open $cmd r]}]} {
6671 return
6673 set treepending $id
6674 set treefilelist($id) {}
6675 set treeidlist($id) {}
6676 fconfigure $gtf -blocking 0 -encoding binary
6677 filerun $gtf [list gettreeline $gtf $id]
6679 } else {
6680 setfilelist $id
6684 proc gettreeline {gtf id} {
6685 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6687 set nl 0
6688 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6689 if {$diffids eq $nullid} {
6690 set fname $line
6691 } else {
6692 set i [string first "\t" $line]
6693 if {$i < 0} continue
6694 set fname [string range $line [expr {$i+1}] end]
6695 set line [string range $line 0 [expr {$i-1}]]
6696 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6697 set sha1 [lindex $line 2]
6698 lappend treeidlist($id) $sha1
6700 if {[string index $fname 0] eq "\""} {
6701 set fname [lindex $fname 0]
6703 set fname [encoding convertfrom $fname]
6704 lappend treefilelist($id) $fname
6706 if {![eof $gtf]} {
6707 return [expr {$nl >= 1000? 2: 1}]
6709 close $gtf
6710 unset treepending
6711 if {$cmitmode ne "tree"} {
6712 if {![info exists diffmergeid]} {
6713 gettreediffs $diffids
6715 } elseif {$id ne $diffids} {
6716 gettree $diffids
6717 } else {
6718 setfilelist $id
6720 return 0
6723 proc showfile {f} {
6724 global treefilelist treeidlist diffids nullid nullid2
6725 global ctext_file_names ctext_file_lines
6726 global ctext commentend
6728 set i [lsearch -exact $treefilelist($diffids) $f]
6729 if {$i < 0} {
6730 puts "oops, $f not in list for id $diffids"
6731 return
6733 if {$diffids eq $nullid} {
6734 if {[catch {set bf [open $f r]} err]} {
6735 puts "oops, can't read $f: $err"
6736 return
6738 } else {
6739 set blob [lindex $treeidlist($diffids) $i]
6740 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6741 puts "oops, error reading blob $blob: $err"
6742 return
6745 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6746 filerun $bf [list getblobline $bf $diffids]
6747 $ctext config -state normal
6748 clear_ctext $commentend
6749 lappend ctext_file_names $f
6750 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6751 $ctext insert end "\n"
6752 $ctext insert end "$f\n" filesep
6753 $ctext config -state disabled
6754 $ctext yview $commentend
6755 settabs 0
6758 proc getblobline {bf id} {
6759 global diffids cmitmode ctext
6761 if {$id ne $diffids || $cmitmode ne "tree"} {
6762 catch {close $bf}
6763 return 0
6765 $ctext config -state normal
6766 set nl 0
6767 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6768 $ctext insert end "$line\n"
6770 if {[eof $bf]} {
6771 global jump_to_here ctext_file_names commentend
6773 # delete last newline
6774 $ctext delete "end - 2c" "end - 1c"
6775 close $bf
6776 if {$jump_to_here ne {} &&
6777 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6778 set lnum [expr {[lindex $jump_to_here 1] +
6779 [lindex [split $commentend .] 0]}]
6780 mark_ctext_line $lnum
6782 return 0
6784 $ctext config -state disabled
6785 return [expr {$nl >= 1000? 2: 1}]
6788 proc mark_ctext_line {lnum} {
6789 global ctext markbgcolor
6791 $ctext tag delete omark
6792 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6793 $ctext tag conf omark -background $markbgcolor
6794 $ctext see $lnum.0
6797 proc mergediff {id} {
6798 global diffmergeid mdifffd
6799 global diffids treediffs
6800 global parents
6801 global diffcontext
6802 global diffencoding
6803 global limitdiffs vfilelimit curview
6804 global targetline
6806 set diffmergeid $id
6807 set diffids $id
6808 set treediffs($id) {}
6809 set targetline {}
6810 # this doesn't seem to actually affect anything...
6811 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
6812 if {$limitdiffs && $vfilelimit($curview) ne {}} {
6813 set cmd [concat $cmd -- $vfilelimit($curview)]
6815 if {[catch {set mdf [open $cmd r]} err]} {
6816 error_popup "[mc "Error getting merge diffs:"] $err"
6817 return
6819 fconfigure $mdf -blocking 0 -encoding binary
6820 set mdifffd($id) $mdf
6821 set np [llength $parents($curview,$id)]
6822 set diffencoding [get_path_encoding {}]
6823 settabs $np
6824 filerun $mdf [list getmergediffline $mdf $id $np]
6827 proc getmergediffline {mdf id np} {
6828 global diffmergeid ctext cflist mergemax
6829 global difffilestart mdifffd treediffs
6830 global ctext_file_names ctext_file_lines
6831 global diffencoding jump_to_here targetline diffline
6833 $ctext conf -state normal
6834 set nr 0
6835 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
6836 if {![info exists diffmergeid] || $id != $diffmergeid
6837 || $mdf != $mdifffd($id)} {
6838 close $mdf
6839 return 0
6841 if {[regexp {^diff --cc (.*)} $line match fname]} {
6842 # start of a new file
6843 set fname [encoding convertfrom $fname]
6844 $ctext insert end "\n"
6845 set here [$ctext index "end - 1c"]
6846 lappend difffilestart $here
6847 lappend treediffs($id) $fname
6848 add_flist [list $fname]
6849 lappend ctext_file_names $fname
6850 lappend ctext_file_lines [lindex [split $here "."] 0]
6851 set diffencoding [get_path_encoding $fname]
6852 set l [expr {(78 - [string length $fname]) / 2}]
6853 set pad [string range "----------------------------------------" 1 $l]
6854 $ctext insert end "$pad $fname $pad\n" filesep
6855 set targetline {}
6856 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
6857 set targetline [lindex $jump_to_here 1]
6859 set diffline 0
6860 } elseif {[regexp {^@@} $line]} {
6861 set line [encoding convertfrom $diffencoding $line]
6862 $ctext insert end "$line\n" hunksep
6863 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
6864 set diffline $nl
6866 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
6867 # do nothing
6868 } else {
6869 set line [encoding convertfrom $diffencoding $line]
6870 # parse the prefix - one ' ', '-' or '+' for each parent
6871 set spaces {}
6872 set minuses {}
6873 set pluses {}
6874 set isbad 0
6875 for {set j 0} {$j < $np} {incr j} {
6876 set c [string range $line $j $j]
6877 if {$c == " "} {
6878 lappend spaces $j
6879 } elseif {$c == "-"} {
6880 lappend minuses $j
6881 } elseif {$c == "+"} {
6882 lappend pluses $j
6883 } else {
6884 set isbad 1
6885 break
6888 set tags {}
6889 set num {}
6890 if {!$isbad && $minuses ne {} && $pluses eq {}} {
6891 # line doesn't appear in result, parents in $minuses have the line
6892 set num [lindex $minuses 0]
6893 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
6894 # line appears in result, parents in $pluses don't have the line
6895 lappend tags mresult
6896 set num [lindex $spaces 0]
6898 if {$num ne {}} {
6899 if {$num >= $mergemax} {
6900 set num "max"
6902 lappend tags m$num
6904 $ctext insert end "$line\n" $tags
6905 if {$targetline ne {} && $minuses eq {}} {
6906 if {$diffline == $targetline} {
6907 set here [$ctext index "end - 1 line"]
6908 mark_ctext_line [lindex [split $here .] 0]
6909 set targetline {}
6910 } else {
6911 incr diffline
6916 $ctext conf -state disabled
6917 if {[eof $mdf]} {
6918 close $mdf
6919 return 0
6921 return [expr {$nr >= 1000? 2: 1}]
6924 proc startdiff {ids} {
6925 global treediffs diffids treepending diffmergeid nullid nullid2
6927 settabs 1
6928 set diffids $ids
6929 catch {unset diffmergeid}
6930 if {![info exists treediffs($ids)] ||
6931 [lsearch -exact $ids $nullid] >= 0 ||
6932 [lsearch -exact $ids $nullid2] >= 0} {
6933 if {![info exists treepending]} {
6934 gettreediffs $ids
6936 } else {
6937 addtocflist $ids
6941 proc path_filter {filter name} {
6942 foreach p $filter {
6943 set l [string length $p]
6944 if {[string index $p end] eq "/"} {
6945 if {[string compare -length $l $p $name] == 0} {
6946 return 1
6948 } else {
6949 if {[string compare -length $l $p $name] == 0 &&
6950 ([string length $name] == $l ||
6951 [string index $name $l] eq "/")} {
6952 return 1
6956 return 0
6959 proc addtocflist {ids} {
6960 global treediffs
6962 add_flist $treediffs($ids)
6963 getblobdiffs $ids
6966 proc diffcmd {ids flags} {
6967 global nullid nullid2
6969 set i [lsearch -exact $ids $nullid]
6970 set j [lsearch -exact $ids $nullid2]
6971 if {$i >= 0} {
6972 if {[llength $ids] > 1 && $j < 0} {
6973 # comparing working directory with some specific revision
6974 set cmd [concat | git diff-index $flags]
6975 if {$i == 0} {
6976 lappend cmd -R [lindex $ids 1]
6977 } else {
6978 lappend cmd [lindex $ids 0]
6980 } else {
6981 # comparing working directory with index
6982 set cmd [concat | git diff-files $flags]
6983 if {$j == 1} {
6984 lappend cmd -R
6987 } elseif {$j >= 0} {
6988 set cmd [concat | git diff-index --cached $flags]
6989 if {[llength $ids] > 1} {
6990 # comparing index with specific revision
6991 if {$i == 0} {
6992 lappend cmd -R [lindex $ids 1]
6993 } else {
6994 lappend cmd [lindex $ids 0]
6996 } else {
6997 # comparing index with HEAD
6998 lappend cmd HEAD
7000 } else {
7001 set cmd [concat | git diff-tree -r $flags $ids]
7003 return $cmd
7006 proc gettreediffs {ids} {
7007 global treediff treepending
7009 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7011 set treepending $ids
7012 set treediff {}
7013 fconfigure $gdtf -blocking 0 -encoding binary
7014 filerun $gdtf [list gettreediffline $gdtf $ids]
7017 proc gettreediffline {gdtf ids} {
7018 global treediff treediffs treepending diffids diffmergeid
7019 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7021 set nr 0
7022 set sublist {}
7023 set max 1000
7024 if {$perfile_attrs} {
7025 # cache_gitattr is slow, and even slower on win32 where we
7026 # have to invoke it for only about 30 paths at a time
7027 set max 500
7028 if {[tk windowingsystem] == "win32"} {
7029 set max 120
7032 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7033 set i [string first "\t" $line]
7034 if {$i >= 0} {
7035 set file [string range $line [expr {$i+1}] end]
7036 if {[string index $file 0] eq "\""} {
7037 set file [lindex $file 0]
7039 set file [encoding convertfrom $file]
7040 lappend treediff $file
7041 lappend sublist $file
7044 if {$perfile_attrs} {
7045 cache_gitattr encoding $sublist
7047 if {![eof $gdtf]} {
7048 return [expr {$nr >= $max? 2: 1}]
7050 close $gdtf
7051 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7052 set flist {}
7053 foreach f $treediff {
7054 if {[path_filter $vfilelimit($curview) $f]} {
7055 lappend flist $f
7058 set treediffs($ids) $flist
7059 } else {
7060 set treediffs($ids) $treediff
7062 unset treepending
7063 if {$cmitmode eq "tree"} {
7064 gettree $diffids
7065 } elseif {$ids != $diffids} {
7066 if {![info exists diffmergeid]} {
7067 gettreediffs $diffids
7069 } else {
7070 addtocflist $ids
7072 return 0
7075 # empty string or positive integer
7076 proc diffcontextvalidate {v} {
7077 return [regexp {^(|[1-9][0-9]*)$} $v]
7080 proc diffcontextchange {n1 n2 op} {
7081 global diffcontextstring diffcontext
7083 if {[string is integer -strict $diffcontextstring]} {
7084 if {$diffcontextstring > 0} {
7085 set diffcontext $diffcontextstring
7086 reselectline
7091 proc changeignorespace {} {
7092 reselectline
7095 proc getblobdiffs {ids} {
7096 global blobdifffd diffids env
7097 global diffinhdr treediffs
7098 global diffcontext
7099 global ignorespace
7100 global limitdiffs vfilelimit curview
7101 global diffencoding targetline
7103 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
7104 if {$ignorespace} {
7105 append cmd " -w"
7107 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7108 set cmd [concat $cmd -- $vfilelimit($curview)]
7110 if {[catch {set bdf [open $cmd r]} err]} {
7111 puts "error getting diffs: $err"
7112 return
7114 set targetline {}
7115 set diffinhdr 0
7116 set diffencoding [get_path_encoding {}]
7117 fconfigure $bdf -blocking 0 -encoding binary
7118 set blobdifffd($ids) $bdf
7119 filerun $bdf [list getblobdiffline $bdf $diffids]
7122 proc setinlist {var i val} {
7123 global $var
7125 while {[llength [set $var]] < $i} {
7126 lappend $var {}
7128 if {[llength [set $var]] == $i} {
7129 lappend $var $val
7130 } else {
7131 lset $var $i $val
7135 proc makediffhdr {fname ids} {
7136 global ctext curdiffstart treediffs
7137 global ctext_file_names jump_to_here targetline diffline
7139 set i [lsearch -exact $treediffs($ids) $fname]
7140 if {$i >= 0} {
7141 setinlist difffilestart $i $curdiffstart
7143 set ctext_file_names [lreplace $ctext_file_names end end $fname]
7144 set l [expr {(78 - [string length $fname]) / 2}]
7145 set pad [string range "----------------------------------------" 1 $l]
7146 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7147 set targetline {}
7148 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7149 set targetline [lindex $jump_to_here 1]
7151 set diffline 0
7154 proc getblobdiffline {bdf ids} {
7155 global diffids blobdifffd ctext curdiffstart
7156 global diffnexthead diffnextnote difffilestart
7157 global ctext_file_names ctext_file_lines
7158 global diffinhdr treediffs
7159 global diffencoding jump_to_here targetline diffline
7161 set nr 0
7162 $ctext conf -state normal
7163 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7164 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7165 close $bdf
7166 return 0
7168 if {![string compare -length 11 "diff --git " $line]} {
7169 # trim off "diff --git "
7170 set line [string range $line 11 end]
7171 set diffinhdr 1
7172 # start of a new file
7173 $ctext insert end "\n"
7174 set curdiffstart [$ctext index "end - 1c"]
7175 lappend ctext_file_names ""
7176 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7177 $ctext insert end "\n" filesep
7178 # If the name hasn't changed the length will be odd,
7179 # the middle char will be a space, and the two bits either
7180 # side will be a/name and b/name, or "a/name" and "b/name".
7181 # If the name has changed we'll get "rename from" and
7182 # "rename to" or "copy from" and "copy to" lines following this,
7183 # and we'll use them to get the filenames.
7184 # This complexity is necessary because spaces in the filename(s)
7185 # don't get escaped.
7186 set l [string length $line]
7187 set i [expr {$l / 2}]
7188 if {!(($l & 1) && [string index $line $i] eq " " &&
7189 [string range $line 2 [expr {$i - 1}]] eq \
7190 [string range $line [expr {$i + 3}] end])} {
7191 continue
7193 # unescape if quoted and chop off the a/ from the front
7194 if {[string index $line 0] eq "\""} {
7195 set fname [string range [lindex $line 0] 2 end]
7196 } else {
7197 set fname [string range $line 2 [expr {$i - 1}]]
7199 set fname [encoding convertfrom $fname]
7200 set diffencoding [get_path_encoding $fname]
7201 makediffhdr $fname $ids
7203 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
7204 $line match f1l f1c f2l f2c rest]} {
7205 set line [encoding convertfrom $diffencoding $line]
7206 $ctext insert end "$line\n" hunksep
7207 set diffinhdr 0
7208 set diffline $f2l
7210 } elseif {$diffinhdr} {
7211 if {![string compare -length 12 "rename from " $line]} {
7212 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7213 if {[string index $fname 0] eq "\""} {
7214 set fname [lindex $fname 0]
7216 set fname [encoding convertfrom $fname]
7217 set i [lsearch -exact $treediffs($ids) $fname]
7218 if {$i >= 0} {
7219 setinlist difffilestart $i $curdiffstart
7221 } elseif {![string compare -length 10 $line "rename to "] ||
7222 ![string compare -length 8 $line "copy to "]} {
7223 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7224 if {[string index $fname 0] eq "\""} {
7225 set fname [lindex $fname 0]
7227 set fname [encoding convertfrom $fname]
7228 set diffencoding [get_path_encoding $fname]
7229 makediffhdr $fname $ids
7230 } elseif {[string compare -length 3 $line "---"] == 0} {
7231 # do nothing
7232 continue
7233 } elseif {[string compare -length 3 $line "+++"] == 0} {
7234 set diffinhdr 0
7235 continue
7237 $ctext insert end "$line\n" filesep
7239 } else {
7240 set line [encoding convertfrom $diffencoding $line]
7241 set x [string range $line 0 0]
7242 set here [$ctext index "end - 1 chars"]
7243 if {$x == "-" || $x == "+"} {
7244 set tag [expr {$x == "+"}]
7245 $ctext insert end "$line\n" d$tag
7246 } elseif {$x == " "} {
7247 $ctext insert end "$line\n"
7248 } else {
7249 # "\ No newline at end of file",
7250 # or something else we don't recognize
7251 $ctext insert end "$line\n" hunksep
7253 if {$targetline ne {} && ($x eq " " || $x eq "+")} {
7254 if {$diffline == $targetline} {
7255 mark_ctext_line [lindex [split $here .] 0]
7256 set targetline {}
7257 } else {
7258 incr diffline
7263 $ctext conf -state disabled
7264 if {[eof $bdf]} {
7265 close $bdf
7266 return 0
7268 return [expr {$nr >= 1000? 2: 1}]
7271 proc changediffdisp {} {
7272 global ctext diffelide
7274 $ctext tag conf d0 -elide [lindex $diffelide 0]
7275 $ctext tag conf d1 -elide [lindex $diffelide 1]
7278 proc highlightfile {loc cline} {
7279 global ctext cflist cflist_top
7281 $ctext yview $loc
7282 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7283 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7284 $cflist see $cline.0
7285 set cflist_top $cline
7288 proc prevfile {} {
7289 global difffilestart ctext cmitmode
7291 if {$cmitmode eq "tree"} return
7292 set prev 0.0
7293 set prevline 1
7294 set here [$ctext index @0,0]
7295 foreach loc $difffilestart {
7296 if {[$ctext compare $loc >= $here]} {
7297 highlightfile $prev $prevline
7298 return
7300 set prev $loc
7301 incr prevline
7303 highlightfile $prev $prevline
7306 proc nextfile {} {
7307 global difffilestart ctext cmitmode
7309 if {$cmitmode eq "tree"} return
7310 set here [$ctext index @0,0]
7311 set line 1
7312 foreach loc $difffilestart {
7313 incr line
7314 if {[$ctext compare $loc > $here]} {
7315 highlightfile $loc $line
7316 return
7321 proc clear_ctext {{first 1.0}} {
7322 global ctext smarktop smarkbot
7323 global ctext_file_names ctext_file_lines
7324 global pendinglinks
7326 set l [lindex [split $first .] 0]
7327 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7328 set smarktop $l
7330 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7331 set smarkbot $l
7333 $ctext delete $first end
7334 if {$first eq "1.0"} {
7335 catch {unset pendinglinks}
7337 set ctext_file_names {}
7338 set ctext_file_lines {}
7341 proc settabs {{firstab {}}} {
7342 global firsttabstop tabstop ctext have_tk85
7344 if {$firstab ne {} && $have_tk85} {
7345 set firsttabstop $firstab
7347 set w [font measure textfont "0"]
7348 if {$firsttabstop != 0} {
7349 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7350 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7351 } elseif {$have_tk85 || $tabstop != 8} {
7352 $ctext conf -tabs [expr {$tabstop * $w}]
7353 } else {
7354 $ctext conf -tabs {}
7358 proc incrsearch {name ix op} {
7359 global ctext searchstring searchdirn
7361 $ctext tag remove found 1.0 end
7362 if {[catch {$ctext index anchor}]} {
7363 # no anchor set, use start of selection, or of visible area
7364 set sel [$ctext tag ranges sel]
7365 if {$sel ne {}} {
7366 $ctext mark set anchor [lindex $sel 0]
7367 } elseif {$searchdirn eq "-forwards"} {
7368 $ctext mark set anchor @0,0
7369 } else {
7370 $ctext mark set anchor @0,[winfo height $ctext]
7373 if {$searchstring ne {}} {
7374 set here [$ctext search $searchdirn -- $searchstring anchor]
7375 if {$here ne {}} {
7376 $ctext see $here
7378 searchmarkvisible 1
7382 proc dosearch {} {
7383 global sstring ctext searchstring searchdirn
7385 focus $sstring
7386 $sstring icursor end
7387 set searchdirn -forwards
7388 if {$searchstring ne {}} {
7389 set sel [$ctext tag ranges sel]
7390 if {$sel ne {}} {
7391 set start "[lindex $sel 0] + 1c"
7392 } elseif {[catch {set start [$ctext index anchor]}]} {
7393 set start "@0,0"
7395 set match [$ctext search -count mlen -- $searchstring $start]
7396 $ctext tag remove sel 1.0 end
7397 if {$match eq {}} {
7398 bell
7399 return
7401 $ctext see $match
7402 set mend "$match + $mlen c"
7403 $ctext tag add sel $match $mend
7404 $ctext mark unset anchor
7408 proc dosearchback {} {
7409 global sstring ctext searchstring searchdirn
7411 focus $sstring
7412 $sstring icursor end
7413 set searchdirn -backwards
7414 if {$searchstring ne {}} {
7415 set sel [$ctext tag ranges sel]
7416 if {$sel ne {}} {
7417 set start [lindex $sel 0]
7418 } elseif {[catch {set start [$ctext index anchor]}]} {
7419 set start @0,[winfo height $ctext]
7421 set match [$ctext search -backwards -count ml -- $searchstring $start]
7422 $ctext tag remove sel 1.0 end
7423 if {$match eq {}} {
7424 bell
7425 return
7427 $ctext see $match
7428 set mend "$match + $ml c"
7429 $ctext tag add sel $match $mend
7430 $ctext mark unset anchor
7434 proc searchmark {first last} {
7435 global ctext searchstring
7437 set mend $first.0
7438 while {1} {
7439 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7440 if {$match eq {}} break
7441 set mend "$match + $mlen c"
7442 $ctext tag add found $match $mend
7446 proc searchmarkvisible {doall} {
7447 global ctext smarktop smarkbot
7449 set topline [lindex [split [$ctext index @0,0] .] 0]
7450 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7451 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7452 # no overlap with previous
7453 searchmark $topline $botline
7454 set smarktop $topline
7455 set smarkbot $botline
7456 } else {
7457 if {$topline < $smarktop} {
7458 searchmark $topline [expr {$smarktop-1}]
7459 set smarktop $topline
7461 if {$botline > $smarkbot} {
7462 searchmark [expr {$smarkbot+1}] $botline
7463 set smarkbot $botline
7468 proc scrolltext {f0 f1} {
7469 global searchstring
7471 .bleft.bottom.sb set $f0 $f1
7472 if {$searchstring ne {}} {
7473 searchmarkvisible 0
7477 proc setcoords {} {
7478 global linespc charspc canvx0 canvy0
7479 global xspc1 xspc2 lthickness
7481 set linespc [font metrics mainfont -linespace]
7482 set charspc [font measure mainfont "m"]
7483 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7484 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7485 set lthickness [expr {int($linespc / 9) + 1}]
7486 set xspc1(0) $linespc
7487 set xspc2 $linespc
7490 proc redisplay {} {
7491 global canv
7492 global selectedline
7494 set ymax [lindex [$canv cget -scrollregion] 3]
7495 if {$ymax eq {} || $ymax == 0} return
7496 set span [$canv yview]
7497 clear_display
7498 setcanvscroll
7499 allcanvs yview moveto [lindex $span 0]
7500 drawvisible
7501 if {$selectedline ne {}} {
7502 selectline $selectedline 0
7503 allcanvs yview moveto [lindex $span 0]
7507 proc parsefont {f n} {
7508 global fontattr
7510 set fontattr($f,family) [lindex $n 0]
7511 set s [lindex $n 1]
7512 if {$s eq {} || $s == 0} {
7513 set s 10
7514 } elseif {$s < 0} {
7515 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7517 set fontattr($f,size) $s
7518 set fontattr($f,weight) normal
7519 set fontattr($f,slant) roman
7520 foreach style [lrange $n 2 end] {
7521 switch -- $style {
7522 "normal" -
7523 "bold" {set fontattr($f,weight) $style}
7524 "roman" -
7525 "italic" {set fontattr($f,slant) $style}
7530 proc fontflags {f {isbold 0}} {
7531 global fontattr
7533 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7534 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7535 -slant $fontattr($f,slant)]
7538 proc fontname {f} {
7539 global fontattr
7541 set n [list $fontattr($f,family) $fontattr($f,size)]
7542 if {$fontattr($f,weight) eq "bold"} {
7543 lappend n "bold"
7545 if {$fontattr($f,slant) eq "italic"} {
7546 lappend n "italic"
7548 return $n
7551 proc incrfont {inc} {
7552 global mainfont textfont ctext canv cflist showrefstop
7553 global stopped entries fontattr
7555 unmarkmatches
7556 set s $fontattr(mainfont,size)
7557 incr s $inc
7558 if {$s < 1} {
7559 set s 1
7561 set fontattr(mainfont,size) $s
7562 font config mainfont -size $s
7563 font config mainfontbold -size $s
7564 set mainfont [fontname mainfont]
7565 set s $fontattr(textfont,size)
7566 incr s $inc
7567 if {$s < 1} {
7568 set s 1
7570 set fontattr(textfont,size) $s
7571 font config textfont -size $s
7572 font config textfontbold -size $s
7573 set textfont [fontname textfont]
7574 setcoords
7575 settabs
7576 redisplay
7579 proc clearsha1 {} {
7580 global sha1entry sha1string
7581 if {[string length $sha1string] == 40} {
7582 $sha1entry delete 0 end
7586 proc sha1change {n1 n2 op} {
7587 global sha1string currentid sha1but
7588 if {$sha1string == {}
7589 || ([info exists currentid] && $sha1string == $currentid)} {
7590 set state disabled
7591 } else {
7592 set state normal
7594 if {[$sha1but cget -state] == $state} return
7595 if {$state == "normal"} {
7596 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7597 } else {
7598 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7602 proc gotocommit {} {
7603 global sha1string tagids headids curview varcid
7605 if {$sha1string == {}
7606 || ([info exists currentid] && $sha1string == $currentid)} return
7607 if {[info exists tagids($sha1string)]} {
7608 set id $tagids($sha1string)
7609 } elseif {[info exists headids($sha1string)]} {
7610 set id $headids($sha1string)
7611 } else {
7612 set id [string tolower $sha1string]
7613 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7614 set matches [longid $id]
7615 if {$matches ne {}} {
7616 if {[llength $matches] > 1} {
7617 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7618 return
7620 set id [lindex $matches 0]
7624 if {[commitinview $id $curview]} {
7625 selectline [rowofcommit $id] 1
7626 return
7628 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7629 set msg [mc "SHA1 id %s is not known" $sha1string]
7630 } else {
7631 set msg [mc "Tag/Head %s is not known" $sha1string]
7633 error_popup $msg
7636 proc lineenter {x y id} {
7637 global hoverx hovery hoverid hovertimer
7638 global commitinfo canv
7640 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7641 set hoverx $x
7642 set hovery $y
7643 set hoverid $id
7644 if {[info exists hovertimer]} {
7645 after cancel $hovertimer
7647 set hovertimer [after 500 linehover]
7648 $canv delete hover
7651 proc linemotion {x y id} {
7652 global hoverx hovery hoverid hovertimer
7654 if {[info exists hoverid] && $id == $hoverid} {
7655 set hoverx $x
7656 set hovery $y
7657 if {[info exists hovertimer]} {
7658 after cancel $hovertimer
7660 set hovertimer [after 500 linehover]
7664 proc lineleave {id} {
7665 global hoverid hovertimer canv
7667 if {[info exists hoverid] && $id == $hoverid} {
7668 $canv delete hover
7669 if {[info exists hovertimer]} {
7670 after cancel $hovertimer
7671 unset hovertimer
7673 unset hoverid
7677 proc linehover {} {
7678 global hoverx hovery hoverid hovertimer
7679 global canv linespc lthickness
7680 global commitinfo
7682 set text [lindex $commitinfo($hoverid) 0]
7683 set ymax [lindex [$canv cget -scrollregion] 3]
7684 if {$ymax == {}} return
7685 set yfrac [lindex [$canv yview] 0]
7686 set x [expr {$hoverx + 2 * $linespc}]
7687 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7688 set x0 [expr {$x - 2 * $lthickness}]
7689 set y0 [expr {$y - 2 * $lthickness}]
7690 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7691 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7692 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7693 -fill \#ffff80 -outline black -width 1 -tags hover]
7694 $canv raise $t
7695 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7696 -font mainfont]
7697 $canv raise $t
7700 proc clickisonarrow {id y} {
7701 global lthickness
7703 set ranges [rowranges $id]
7704 set thresh [expr {2 * $lthickness + 6}]
7705 set n [expr {[llength $ranges] - 1}]
7706 for {set i 1} {$i < $n} {incr i} {
7707 set row [lindex $ranges $i]
7708 if {abs([yc $row] - $y) < $thresh} {
7709 return $i
7712 return {}
7715 proc arrowjump {id n y} {
7716 global canv
7718 # 1 <-> 2, 3 <-> 4, etc...
7719 set n [expr {(($n - 1) ^ 1) + 1}]
7720 set row [lindex [rowranges $id] $n]
7721 set yt [yc $row]
7722 set ymax [lindex [$canv cget -scrollregion] 3]
7723 if {$ymax eq {} || $ymax <= 0} return
7724 set view [$canv yview]
7725 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7726 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7727 if {$yfrac < 0} {
7728 set yfrac 0
7730 allcanvs yview moveto $yfrac
7733 proc lineclick {x y id isnew} {
7734 global ctext commitinfo children canv thickerline curview
7736 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7737 unmarkmatches
7738 unselectline
7739 normalline
7740 $canv delete hover
7741 # draw this line thicker than normal
7742 set thickerline $id
7743 drawlines $id
7744 if {$isnew} {
7745 set ymax [lindex [$canv cget -scrollregion] 3]
7746 if {$ymax eq {}} return
7747 set yfrac [lindex [$canv yview] 0]
7748 set y [expr {$y + $yfrac * $ymax}]
7750 set dirn [clickisonarrow $id $y]
7751 if {$dirn ne {}} {
7752 arrowjump $id $dirn $y
7753 return
7756 if {$isnew} {
7757 addtohistory [list lineclick $x $y $id 0]
7759 # fill the details pane with info about this line
7760 $ctext conf -state normal
7761 clear_ctext
7762 settabs 0
7763 $ctext insert end "[mc "Parent"]:\t"
7764 $ctext insert end $id link0
7765 setlink $id link0
7766 set info $commitinfo($id)
7767 $ctext insert end "\n\t[lindex $info 0]\n"
7768 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7769 set date [formatdate [lindex $info 2]]
7770 $ctext insert end "\t[mc "Date"]:\t$date\n"
7771 set kids $children($curview,$id)
7772 if {$kids ne {}} {
7773 $ctext insert end "\n[mc "Children"]:"
7774 set i 0
7775 foreach child $kids {
7776 incr i
7777 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7778 set info $commitinfo($child)
7779 $ctext insert end "\n\t"
7780 $ctext insert end $child link$i
7781 setlink $child link$i
7782 $ctext insert end "\n\t[lindex $info 0]"
7783 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7784 set date [formatdate [lindex $info 2]]
7785 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7788 $ctext conf -state disabled
7789 init_flist {}
7792 proc normalline {} {
7793 global thickerline
7794 if {[info exists thickerline]} {
7795 set id $thickerline
7796 unset thickerline
7797 drawlines $id
7801 proc selbyid {id} {
7802 global curview
7803 if {[commitinview $id $curview]} {
7804 selectline [rowofcommit $id] 1
7808 proc mstime {} {
7809 global startmstime
7810 if {![info exists startmstime]} {
7811 set startmstime [clock clicks -milliseconds]
7813 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7816 proc rowmenu {x y id} {
7817 global rowctxmenu selectedline rowmenuid curview
7818 global nullid nullid2 fakerowmenu mainhead
7820 stopfinding
7821 set rowmenuid $id
7822 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7823 set state disabled
7824 } else {
7825 set state normal
7827 if {$id ne $nullid && $id ne $nullid2} {
7828 set menu $rowctxmenu
7829 if {$mainhead ne {}} {
7830 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7831 } else {
7832 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7834 } else {
7835 set menu $fakerowmenu
7837 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7838 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7839 $menu entryconfigure [mca "Make patch"] -state $state
7840 tk_popup $menu $x $y
7843 proc diffvssel {dirn} {
7844 global rowmenuid selectedline
7846 if {$selectedline eq {}} return
7847 if {$dirn} {
7848 set oldid [commitonrow $selectedline]
7849 set newid $rowmenuid
7850 } else {
7851 set oldid $rowmenuid
7852 set newid [commitonrow $selectedline]
7854 addtohistory [list doseldiff $oldid $newid]
7855 doseldiff $oldid $newid
7858 proc doseldiff {oldid newid} {
7859 global ctext
7860 global commitinfo
7862 $ctext conf -state normal
7863 clear_ctext
7864 init_flist [mc "Top"]
7865 $ctext insert end "[mc "From"] "
7866 $ctext insert end $oldid link0
7867 setlink $oldid link0
7868 $ctext insert end "\n "
7869 $ctext insert end [lindex $commitinfo($oldid) 0]
7870 $ctext insert end "\n\n[mc "To"] "
7871 $ctext insert end $newid link1
7872 setlink $newid link1
7873 $ctext insert end "\n "
7874 $ctext insert end [lindex $commitinfo($newid) 0]
7875 $ctext insert end "\n"
7876 $ctext conf -state disabled
7877 $ctext tag remove found 1.0 end
7878 startdiff [list $oldid $newid]
7881 proc mkpatch {} {
7882 global rowmenuid currentid commitinfo patchtop patchnum
7884 if {![info exists currentid]} return
7885 set oldid $currentid
7886 set oldhead [lindex $commitinfo($oldid) 0]
7887 set newid $rowmenuid
7888 set newhead [lindex $commitinfo($newid) 0]
7889 set top .patch
7890 set patchtop $top
7891 catch {destroy $top}
7892 toplevel $top
7893 wm transient $top .
7894 label $top.title -text [mc "Generate patch"]
7895 grid $top.title - -pady 10
7896 label $top.from -text [mc "From:"]
7897 entry $top.fromsha1 -width 40 -relief flat
7898 $top.fromsha1 insert 0 $oldid
7899 $top.fromsha1 conf -state readonly
7900 grid $top.from $top.fromsha1 -sticky w
7901 entry $top.fromhead -width 60 -relief flat
7902 $top.fromhead insert 0 $oldhead
7903 $top.fromhead conf -state readonly
7904 grid x $top.fromhead -sticky w
7905 label $top.to -text [mc "To:"]
7906 entry $top.tosha1 -width 40 -relief flat
7907 $top.tosha1 insert 0 $newid
7908 $top.tosha1 conf -state readonly
7909 grid $top.to $top.tosha1 -sticky w
7910 entry $top.tohead -width 60 -relief flat
7911 $top.tohead insert 0 $newhead
7912 $top.tohead conf -state readonly
7913 grid x $top.tohead -sticky w
7914 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7915 grid $top.rev x -pady 10
7916 label $top.flab -text [mc "Output file:"]
7917 entry $top.fname -width 60
7918 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7919 incr patchnum
7920 grid $top.flab $top.fname -sticky w
7921 frame $top.buts
7922 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7923 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7924 bind $top <Key-Return> mkpatchgo
7925 bind $top <Key-Escape> mkpatchcan
7926 grid $top.buts.gen $top.buts.can
7927 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7928 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7929 grid $top.buts - -pady 10 -sticky ew
7930 focus $top.fname
7933 proc mkpatchrev {} {
7934 global patchtop
7936 set oldid [$patchtop.fromsha1 get]
7937 set oldhead [$patchtop.fromhead get]
7938 set newid [$patchtop.tosha1 get]
7939 set newhead [$patchtop.tohead get]
7940 foreach e [list fromsha1 fromhead tosha1 tohead] \
7941 v [list $newid $newhead $oldid $oldhead] {
7942 $patchtop.$e conf -state normal
7943 $patchtop.$e delete 0 end
7944 $patchtop.$e insert 0 $v
7945 $patchtop.$e conf -state readonly
7949 proc mkpatchgo {} {
7950 global patchtop nullid nullid2
7952 set oldid [$patchtop.fromsha1 get]
7953 set newid [$patchtop.tosha1 get]
7954 set fname [$patchtop.fname get]
7955 set cmd [diffcmd [list $oldid $newid] -p]
7956 # trim off the initial "|"
7957 set cmd [lrange $cmd 1 end]
7958 lappend cmd >$fname &
7959 if {[catch {eval exec $cmd} err]} {
7960 error_popup "[mc "Error creating patch:"] $err" $patchtop
7962 catch {destroy $patchtop}
7963 unset patchtop
7966 proc mkpatchcan {} {
7967 global patchtop
7969 catch {destroy $patchtop}
7970 unset patchtop
7973 proc mktag {} {
7974 global rowmenuid mktagtop commitinfo
7976 set top .maketag
7977 set mktagtop $top
7978 catch {destroy $top}
7979 toplevel $top
7980 wm transient $top .
7981 label $top.title -text [mc "Create tag"]
7982 grid $top.title - -pady 10
7983 label $top.id -text [mc "ID:"]
7984 entry $top.sha1 -width 40 -relief flat
7985 $top.sha1 insert 0 $rowmenuid
7986 $top.sha1 conf -state readonly
7987 grid $top.id $top.sha1 -sticky w
7988 entry $top.head -width 60 -relief flat
7989 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
7990 $top.head conf -state readonly
7991 grid x $top.head -sticky w
7992 label $top.tlab -text [mc "Tag name:"]
7993 entry $top.tag -width 60
7994 grid $top.tlab $top.tag -sticky w
7995 frame $top.buts
7996 button $top.buts.gen -text [mc "Create"] -command mktaggo
7997 button $top.buts.can -text [mc "Cancel"] -command mktagcan
7998 bind $top <Key-Return> mktaggo
7999 bind $top <Key-Escape> mktagcan
8000 grid $top.buts.gen $top.buts.can
8001 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8002 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8003 grid $top.buts - -pady 10 -sticky ew
8004 focus $top.tag
8007 proc domktag {} {
8008 global mktagtop env tagids idtags
8010 set id [$mktagtop.sha1 get]
8011 set tag [$mktagtop.tag get]
8012 if {$tag == {}} {
8013 error_popup [mc "No tag name specified"] $mktagtop
8014 return 0
8016 if {[info exists tagids($tag)]} {
8017 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8018 return 0
8020 if {[catch {
8021 exec git tag $tag $id
8022 } err]} {
8023 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8024 return 0
8027 set tagids($tag) $id
8028 lappend idtags($id) $tag
8029 redrawtags $id
8030 addedtag $id
8031 dispneartags 0
8032 run refill_reflist
8033 return 1
8036 proc redrawtags {id} {
8037 global canv linehtag idpos currentid curview cmitlisted
8038 global canvxmax iddrawn circleitem mainheadid circlecolors
8040 if {![commitinview $id $curview]} return
8041 if {![info exists iddrawn($id)]} return
8042 set row [rowofcommit $id]
8043 if {$id eq $mainheadid} {
8044 set ofill yellow
8045 } else {
8046 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8048 $canv itemconf $circleitem($row) -fill $ofill
8049 $canv delete tag.$id
8050 set xt [eval drawtags $id $idpos($id)]
8051 $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
8052 set text [$canv itemcget $linehtag($row) -text]
8053 set font [$canv itemcget $linehtag($row) -font]
8054 set xr [expr {$xt + [font measure $font $text]}]
8055 if {$xr > $canvxmax} {
8056 set canvxmax $xr
8057 setcanvscroll
8059 if {[info exists currentid] && $currentid == $id} {
8060 make_secsel $row
8064 proc mktagcan {} {
8065 global mktagtop
8067 catch {destroy $mktagtop}
8068 unset mktagtop
8071 proc mktaggo {} {
8072 if {![domktag]} return
8073 mktagcan
8076 proc writecommit {} {
8077 global rowmenuid wrcomtop commitinfo wrcomcmd
8079 set top .writecommit
8080 set wrcomtop $top
8081 catch {destroy $top}
8082 toplevel $top
8083 wm transient $top .
8084 label $top.title -text [mc "Write commit to file"]
8085 grid $top.title - -pady 10
8086 label $top.id -text [mc "ID:"]
8087 entry $top.sha1 -width 40 -relief flat
8088 $top.sha1 insert 0 $rowmenuid
8089 $top.sha1 conf -state readonly
8090 grid $top.id $top.sha1 -sticky w
8091 entry $top.head -width 60 -relief flat
8092 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8093 $top.head conf -state readonly
8094 grid x $top.head -sticky w
8095 label $top.clab -text [mc "Command:"]
8096 entry $top.cmd -width 60 -textvariable wrcomcmd
8097 grid $top.clab $top.cmd -sticky w -pady 10
8098 label $top.flab -text [mc "Output file:"]
8099 entry $top.fname -width 60
8100 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8101 grid $top.flab $top.fname -sticky w
8102 frame $top.buts
8103 button $top.buts.gen -text [mc "Write"] -command wrcomgo
8104 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8105 bind $top <Key-Return> wrcomgo
8106 bind $top <Key-Escape> wrcomcan
8107 grid $top.buts.gen $top.buts.can
8108 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8109 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8110 grid $top.buts - -pady 10 -sticky ew
8111 focus $top.fname
8114 proc wrcomgo {} {
8115 global wrcomtop
8117 set id [$wrcomtop.sha1 get]
8118 set cmd "echo $id | [$wrcomtop.cmd get]"
8119 set fname [$wrcomtop.fname get]
8120 if {[catch {exec sh -c $cmd >$fname &} err]} {
8121 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8123 catch {destroy $wrcomtop}
8124 unset wrcomtop
8127 proc wrcomcan {} {
8128 global wrcomtop
8130 catch {destroy $wrcomtop}
8131 unset wrcomtop
8134 proc mkbranch {} {
8135 global rowmenuid mkbrtop
8137 set top .makebranch
8138 catch {destroy $top}
8139 toplevel $top
8140 wm transient $top .
8141 label $top.title -text [mc "Create new branch"]
8142 grid $top.title - -pady 10
8143 label $top.id -text [mc "ID:"]
8144 entry $top.sha1 -width 40 -relief flat
8145 $top.sha1 insert 0 $rowmenuid
8146 $top.sha1 conf -state readonly
8147 grid $top.id $top.sha1 -sticky w
8148 label $top.nlab -text [mc "Name:"]
8149 entry $top.name -width 40
8150 bind $top.name <Key-Return> "[list mkbrgo $top]"
8151 grid $top.nlab $top.name -sticky w
8152 frame $top.buts
8153 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8154 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8155 bind $top <Key-Return> [list mkbrgo $top]
8156 bind $top <Key-Escape> "catch {destroy $top}"
8157 grid $top.buts.go $top.buts.can
8158 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8159 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8160 grid $top.buts - -pady 10 -sticky ew
8161 focus $top.name
8164 proc mkbrgo {top} {
8165 global headids idheads
8167 set name [$top.name get]
8168 set id [$top.sha1 get]
8169 set cmdargs {}
8170 set old_id {}
8171 if {$name eq {}} {
8172 error_popup [mc "Please specify a name for the new branch"] $top
8173 return
8175 if {[info exists headids($name)]} {
8176 if {![confirm_popup [mc \
8177 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8178 return
8180 set old_id $headids($name)
8181 lappend cmdargs -f
8183 catch {destroy $top}
8184 lappend cmdargs $name $id
8185 nowbusy newbranch
8186 update
8187 if {[catch {
8188 eval exec git branch $cmdargs
8189 } err]} {
8190 notbusy newbranch
8191 error_popup $err
8192 } else {
8193 notbusy newbranch
8194 if {$old_id ne {}} {
8195 movehead $id $name
8196 movedhead $id $name
8197 redrawtags $old_id
8198 redrawtags $id
8199 } else {
8200 set headids($name) $id
8201 lappend idheads($id) $name
8202 addedhead $id $name
8203 redrawtags $id
8205 dispneartags 0
8206 run refill_reflist
8210 proc exec_citool {tool_args {baseid {}}} {
8211 global commitinfo env
8213 set save_env [array get env GIT_AUTHOR_*]
8215 if {$baseid ne {}} {
8216 if {![info exists commitinfo($baseid)]} {
8217 getcommit $baseid
8219 set author [lindex $commitinfo($baseid) 1]
8220 set date [lindex $commitinfo($baseid) 2]
8221 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8222 $author author name email]
8223 && $date ne {}} {
8224 set env(GIT_AUTHOR_NAME) $name
8225 set env(GIT_AUTHOR_EMAIL) $email
8226 set env(GIT_AUTHOR_DATE) $date
8230 eval exec git citool $tool_args &
8232 array unset env GIT_AUTHOR_*
8233 array set env $save_env
8236 proc cherrypick {} {
8237 global rowmenuid curview
8238 global mainhead mainheadid
8240 set oldhead [exec git rev-parse HEAD]
8241 set dheads [descheads $rowmenuid]
8242 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8243 set ok [confirm_popup [mc "Commit %s is already\
8244 included in branch %s -- really re-apply it?" \
8245 [string range $rowmenuid 0 7] $mainhead]]
8246 if {!$ok} return
8248 nowbusy cherrypick [mc "Cherry-picking"]
8249 update
8250 # Unfortunately git-cherry-pick writes stuff to stderr even when
8251 # no error occurs, and exec takes that as an indication of error...
8252 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8253 notbusy cherrypick
8254 if {[regexp -line \
8255 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8256 $err msg fname]} {
8257 error_popup [mc "Cherry-pick failed because of local changes\
8258 to file '%s'.\nPlease commit, reset or stash\
8259 your changes and try again." $fname]
8260 } elseif {[regexp -line \
8261 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8262 $err]} {
8263 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8264 conflict.\nDo you wish to run git citool to\
8265 resolve it?"]]} {
8266 # Force citool to read MERGE_MSG
8267 file delete [file join [gitdir] "GITGUI_MSG"]
8268 exec_citool {} $rowmenuid
8270 } else {
8271 error_popup $err
8273 run updatecommits
8274 return
8276 set newhead [exec git rev-parse HEAD]
8277 if {$newhead eq $oldhead} {
8278 notbusy cherrypick
8279 error_popup [mc "No changes committed"]
8280 return
8282 addnewchild $newhead $oldhead
8283 if {[commitinview $oldhead $curview]} {
8284 insertrow $newhead $oldhead $curview
8285 if {$mainhead ne {}} {
8286 movehead $newhead $mainhead
8287 movedhead $newhead $mainhead
8289 set mainheadid $newhead
8290 redrawtags $oldhead
8291 redrawtags $newhead
8292 selbyid $newhead
8294 notbusy cherrypick
8297 proc resethead {} {
8298 global mainhead rowmenuid confirm_ok resettype
8300 set confirm_ok 0
8301 set w ".confirmreset"
8302 toplevel $w
8303 wm transient $w .
8304 wm title $w [mc "Confirm reset"]
8305 message $w.m -text \
8306 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8307 -justify center -aspect 1000
8308 pack $w.m -side top -fill x -padx 20 -pady 20
8309 frame $w.f -relief sunken -border 2
8310 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8311 grid $w.f.rt -sticky w
8312 set resettype mixed
8313 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8314 -text [mc "Soft: Leave working tree and index untouched"]
8315 grid $w.f.soft -sticky w
8316 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8317 -text [mc "Mixed: Leave working tree untouched, reset index"]
8318 grid $w.f.mixed -sticky w
8319 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8320 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8321 grid $w.f.hard -sticky w
8322 pack $w.f -side top -fill x
8323 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8324 pack $w.ok -side left -fill x -padx 20 -pady 20
8325 button $w.cancel -text [mc Cancel] -command "destroy $w"
8326 bind $w <Key-Escape> [list destroy $w]
8327 pack $w.cancel -side right -fill x -padx 20 -pady 20
8328 bind $w <Visibility> "grab $w; focus $w"
8329 tkwait window $w
8330 if {!$confirm_ok} return
8331 if {[catch {set fd [open \
8332 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8333 error_popup $err
8334 } else {
8335 dohidelocalchanges
8336 filerun $fd [list readresetstat $fd]
8337 nowbusy reset [mc "Resetting"]
8338 selbyid $rowmenuid
8342 proc readresetstat {fd} {
8343 global mainhead mainheadid showlocalchanges rprogcoord
8345 if {[gets $fd line] >= 0} {
8346 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8347 set rprogcoord [expr {1.0 * $m / $n}]
8348 adjustprogress
8350 return 1
8352 set rprogcoord 0
8353 adjustprogress
8354 notbusy reset
8355 if {[catch {close $fd} err]} {
8356 error_popup $err
8358 set oldhead $mainheadid
8359 set newhead [exec git rev-parse HEAD]
8360 if {$newhead ne $oldhead} {
8361 movehead $newhead $mainhead
8362 movedhead $newhead $mainhead
8363 set mainheadid $newhead
8364 redrawtags $oldhead
8365 redrawtags $newhead
8367 if {$showlocalchanges} {
8368 doshowlocalchanges
8370 return 0
8373 # context menu for a head
8374 proc headmenu {x y id head} {
8375 global headmenuid headmenuhead headctxmenu mainhead
8377 stopfinding
8378 set headmenuid $id
8379 set headmenuhead $head
8380 set state normal
8381 if {$head eq $mainhead} {
8382 set state disabled
8384 $headctxmenu entryconfigure 0 -state $state
8385 $headctxmenu entryconfigure 1 -state $state
8386 tk_popup $headctxmenu $x $y
8389 proc cobranch {} {
8390 global headmenuid headmenuhead headids
8391 global showlocalchanges mainheadid
8393 # check the tree is clean first??
8394 nowbusy checkout [mc "Checking out"]
8395 update
8396 dohidelocalchanges
8397 if {[catch {
8398 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8399 } err]} {
8400 notbusy checkout
8401 error_popup $err
8402 if {$showlocalchanges} {
8403 dodiffindex
8405 } else {
8406 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8410 proc readcheckoutstat {fd newhead newheadid} {
8411 global mainhead mainheadid headids showlocalchanges progresscoords
8413 if {[gets $fd line] >= 0} {
8414 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8415 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8416 adjustprogress
8418 return 1
8420 set progresscoords {0 0}
8421 adjustprogress
8422 notbusy checkout
8423 if {[catch {close $fd} err]} {
8424 error_popup $err
8426 set oldmainid $mainheadid
8427 set mainhead $newhead
8428 set mainheadid $newheadid
8429 redrawtags $oldmainid
8430 redrawtags $newheadid
8431 selbyid $newheadid
8432 if {$showlocalchanges} {
8433 dodiffindex
8437 proc rmbranch {} {
8438 global headmenuid headmenuhead mainhead
8439 global idheads
8441 set head $headmenuhead
8442 set id $headmenuid
8443 # this check shouldn't be needed any more...
8444 if {$head eq $mainhead} {
8445 error_popup [mc "Cannot delete the currently checked-out branch"]
8446 return
8448 set dheads [descheads $id]
8449 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8450 # the stuff on this branch isn't on any other branch
8451 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8452 branch.\nReally delete branch %s?" $head $head]]} return
8454 nowbusy rmbranch
8455 update
8456 if {[catch {exec git branch -D $head} err]} {
8457 notbusy rmbranch
8458 error_popup $err
8459 return
8461 removehead $id $head
8462 removedhead $id $head
8463 redrawtags $id
8464 notbusy rmbranch
8465 dispneartags 0
8466 run refill_reflist
8469 # Display a list of tags and heads
8470 proc showrefs {} {
8471 global showrefstop bgcolor fgcolor selectbgcolor
8472 global bglist fglist reflistfilter reflist maincursor
8474 set top .showrefs
8475 set showrefstop $top
8476 if {[winfo exists $top]} {
8477 raise $top
8478 refill_reflist
8479 return
8481 toplevel $top
8482 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8483 wm transient $top .
8484 text $top.list -background $bgcolor -foreground $fgcolor \
8485 -selectbackground $selectbgcolor -font mainfont \
8486 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8487 -width 30 -height 20 -cursor $maincursor \
8488 -spacing1 1 -spacing3 1 -state disabled
8489 $top.list tag configure highlight -background $selectbgcolor
8490 lappend bglist $top.list
8491 lappend fglist $top.list
8492 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8493 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8494 grid $top.list $top.ysb -sticky nsew
8495 grid $top.xsb x -sticky ew
8496 frame $top.f
8497 label $top.f.l -text "[mc "Filter"]: "
8498 entry $top.f.e -width 20 -textvariable reflistfilter
8499 set reflistfilter "*"
8500 trace add variable reflistfilter write reflistfilter_change
8501 pack $top.f.e -side right -fill x -expand 1
8502 pack $top.f.l -side left
8503 grid $top.f - -sticky ew -pady 2
8504 button $top.close -command [list destroy $top] -text [mc "Close"]
8505 bind $top <Key-Escape> [list destroy $top]
8506 grid $top.close -
8507 grid columnconfigure $top 0 -weight 1
8508 grid rowconfigure $top 0 -weight 1
8509 bind $top.list <1> {break}
8510 bind $top.list <B1-Motion> {break}
8511 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8512 set reflist {}
8513 refill_reflist
8516 proc sel_reflist {w x y} {
8517 global showrefstop reflist headids tagids otherrefids
8519 if {![winfo exists $showrefstop]} return
8520 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8521 set ref [lindex $reflist [expr {$l-1}]]
8522 set n [lindex $ref 0]
8523 switch -- [lindex $ref 1] {
8524 "H" {selbyid $headids($n)}
8525 "T" {selbyid $tagids($n)}
8526 "o" {selbyid $otherrefids($n)}
8528 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8531 proc unsel_reflist {} {
8532 global showrefstop
8534 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8535 $showrefstop.list tag remove highlight 0.0 end
8538 proc reflistfilter_change {n1 n2 op} {
8539 global reflistfilter
8541 after cancel refill_reflist
8542 after 200 refill_reflist
8545 proc refill_reflist {} {
8546 global reflist reflistfilter showrefstop headids tagids otherrefids
8547 global curview
8549 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8550 set refs {}
8551 foreach n [array names headids] {
8552 if {[string match $reflistfilter $n]} {
8553 if {[commitinview $headids($n) $curview]} {
8554 lappend refs [list $n H]
8555 } else {
8556 interestedin $headids($n) {run refill_reflist}
8560 foreach n [array names tagids] {
8561 if {[string match $reflistfilter $n]} {
8562 if {[commitinview $tagids($n) $curview]} {
8563 lappend refs [list $n T]
8564 } else {
8565 interestedin $tagids($n) {run refill_reflist}
8569 foreach n [array names otherrefids] {
8570 if {[string match $reflistfilter $n]} {
8571 if {[commitinview $otherrefids($n) $curview]} {
8572 lappend refs [list $n o]
8573 } else {
8574 interestedin $otherrefids($n) {run refill_reflist}
8578 set refs [lsort -index 0 $refs]
8579 if {$refs eq $reflist} return
8581 # Update the contents of $showrefstop.list according to the
8582 # differences between $reflist (old) and $refs (new)
8583 $showrefstop.list conf -state normal
8584 $showrefstop.list insert end "\n"
8585 set i 0
8586 set j 0
8587 while {$i < [llength $reflist] || $j < [llength $refs]} {
8588 if {$i < [llength $reflist]} {
8589 if {$j < [llength $refs]} {
8590 set cmp [string compare [lindex $reflist $i 0] \
8591 [lindex $refs $j 0]]
8592 if {$cmp == 0} {
8593 set cmp [string compare [lindex $reflist $i 1] \
8594 [lindex $refs $j 1]]
8596 } else {
8597 set cmp -1
8599 } else {
8600 set cmp 1
8602 switch -- $cmp {
8603 -1 {
8604 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8605 incr i
8608 incr i
8609 incr j
8612 set l [expr {$j + 1}]
8613 $showrefstop.list image create $l.0 -align baseline \
8614 -image reficon-[lindex $refs $j 1] -padx 2
8615 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8616 incr j
8620 set reflist $refs
8621 # delete last newline
8622 $showrefstop.list delete end-2c end-1c
8623 $showrefstop.list conf -state disabled
8626 # Stuff for finding nearby tags
8627 proc getallcommits {} {
8628 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8629 global idheads idtags idotherrefs allparents tagobjid
8631 if {![info exists allcommits]} {
8632 set nextarc 0
8633 set allcommits 0
8634 set seeds {}
8635 set allcwait 0
8636 set cachedarcs 0
8637 set allccache [file join [gitdir] "gitk.cache"]
8638 if {![catch {
8639 set f [open $allccache r]
8640 set allcwait 1
8641 getcache $f
8642 }]} return
8645 if {$allcwait} {
8646 return
8648 set cmd [list | git rev-list --parents]
8649 set allcupdate [expr {$seeds ne {}}]
8650 if {!$allcupdate} {
8651 set ids "--all"
8652 } else {
8653 set refs [concat [array names idheads] [array names idtags] \
8654 [array names idotherrefs]]
8655 set ids {}
8656 set tagobjs {}
8657 foreach name [array names tagobjid] {
8658 lappend tagobjs $tagobjid($name)
8660 foreach id [lsort -unique $refs] {
8661 if {![info exists allparents($id)] &&
8662 [lsearch -exact $tagobjs $id] < 0} {
8663 lappend ids $id
8666 if {$ids ne {}} {
8667 foreach id $seeds {
8668 lappend ids "^$id"
8672 if {$ids ne {}} {
8673 set fd [open [concat $cmd $ids] r]
8674 fconfigure $fd -blocking 0
8675 incr allcommits
8676 nowbusy allcommits
8677 filerun $fd [list getallclines $fd]
8678 } else {
8679 dispneartags 0
8683 # Since most commits have 1 parent and 1 child, we group strings of
8684 # such commits into "arcs" joining branch/merge points (BMPs), which
8685 # are commits that either don't have 1 parent or don't have 1 child.
8687 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8688 # arcout(id) - outgoing arcs for BMP
8689 # arcids(a) - list of IDs on arc including end but not start
8690 # arcstart(a) - BMP ID at start of arc
8691 # arcend(a) - BMP ID at end of arc
8692 # growing(a) - arc a is still growing
8693 # arctags(a) - IDs out of arcids (excluding end) that have tags
8694 # archeads(a) - IDs out of arcids (excluding end) that have heads
8695 # The start of an arc is at the descendent end, so "incoming" means
8696 # coming from descendents, and "outgoing" means going towards ancestors.
8698 proc getallclines {fd} {
8699 global allparents allchildren idtags idheads nextarc
8700 global arcnos arcids arctags arcout arcend arcstart archeads growing
8701 global seeds allcommits cachedarcs allcupdate
8703 set nid 0
8704 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8705 set id [lindex $line 0]
8706 if {[info exists allparents($id)]} {
8707 # seen it already
8708 continue
8710 set cachedarcs 0
8711 set olds [lrange $line 1 end]
8712 set allparents($id) $olds
8713 if {![info exists allchildren($id)]} {
8714 set allchildren($id) {}
8715 set arcnos($id) {}
8716 lappend seeds $id
8717 } else {
8718 set a $arcnos($id)
8719 if {[llength $olds] == 1 && [llength $a] == 1} {
8720 lappend arcids($a) $id
8721 if {[info exists idtags($id)]} {
8722 lappend arctags($a) $id
8724 if {[info exists idheads($id)]} {
8725 lappend archeads($a) $id
8727 if {[info exists allparents($olds)]} {
8728 # seen parent already
8729 if {![info exists arcout($olds)]} {
8730 splitarc $olds
8732 lappend arcids($a) $olds
8733 set arcend($a) $olds
8734 unset growing($a)
8736 lappend allchildren($olds) $id
8737 lappend arcnos($olds) $a
8738 continue
8741 foreach a $arcnos($id) {
8742 lappend arcids($a) $id
8743 set arcend($a) $id
8744 unset growing($a)
8747 set ao {}
8748 foreach p $olds {
8749 lappend allchildren($p) $id
8750 set a [incr nextarc]
8751 set arcstart($a) $id
8752 set archeads($a) {}
8753 set arctags($a) {}
8754 set archeads($a) {}
8755 set arcids($a) {}
8756 lappend ao $a
8757 set growing($a) 1
8758 if {[info exists allparents($p)]} {
8759 # seen it already, may need to make a new branch
8760 if {![info exists arcout($p)]} {
8761 splitarc $p
8763 lappend arcids($a) $p
8764 set arcend($a) $p
8765 unset growing($a)
8767 lappend arcnos($p) $a
8769 set arcout($id) $ao
8771 if {$nid > 0} {
8772 global cached_dheads cached_dtags cached_atags
8773 catch {unset cached_dheads}
8774 catch {unset cached_dtags}
8775 catch {unset cached_atags}
8777 if {![eof $fd]} {
8778 return [expr {$nid >= 1000? 2: 1}]
8780 set cacheok 1
8781 if {[catch {
8782 fconfigure $fd -blocking 1
8783 close $fd
8784 } err]} {
8785 # got an error reading the list of commits
8786 # if we were updating, try rereading the whole thing again
8787 if {$allcupdate} {
8788 incr allcommits -1
8789 dropcache $err
8790 return
8792 error_popup "[mc "Error reading commit topology information;\
8793 branch and preceding/following tag information\
8794 will be incomplete."]\n($err)"
8795 set cacheok 0
8797 if {[incr allcommits -1] == 0} {
8798 notbusy allcommits
8799 if {$cacheok} {
8800 run savecache
8803 dispneartags 0
8804 return 0
8807 proc recalcarc {a} {
8808 global arctags archeads arcids idtags idheads
8810 set at {}
8811 set ah {}
8812 foreach id [lrange $arcids($a) 0 end-1] {
8813 if {[info exists idtags($id)]} {
8814 lappend at $id
8816 if {[info exists idheads($id)]} {
8817 lappend ah $id
8820 set arctags($a) $at
8821 set archeads($a) $ah
8824 proc splitarc {p} {
8825 global arcnos arcids nextarc arctags archeads idtags idheads
8826 global arcstart arcend arcout allparents growing
8828 set a $arcnos($p)
8829 if {[llength $a] != 1} {
8830 puts "oops splitarc called but [llength $a] arcs already"
8831 return
8833 set a [lindex $a 0]
8834 set i [lsearch -exact $arcids($a) $p]
8835 if {$i < 0} {
8836 puts "oops splitarc $p not in arc $a"
8837 return
8839 set na [incr nextarc]
8840 if {[info exists arcend($a)]} {
8841 set arcend($na) $arcend($a)
8842 } else {
8843 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8844 set j [lsearch -exact $arcnos($l) $a]
8845 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8847 set tail [lrange $arcids($a) [expr {$i+1}] end]
8848 set arcids($a) [lrange $arcids($a) 0 $i]
8849 set arcend($a) $p
8850 set arcstart($na) $p
8851 set arcout($p) $na
8852 set arcids($na) $tail
8853 if {[info exists growing($a)]} {
8854 set growing($na) 1
8855 unset growing($a)
8858 foreach id $tail {
8859 if {[llength $arcnos($id)] == 1} {
8860 set arcnos($id) $na
8861 } else {
8862 set j [lsearch -exact $arcnos($id) $a]
8863 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8867 # reconstruct tags and heads lists
8868 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8869 recalcarc $a
8870 recalcarc $na
8871 } else {
8872 set arctags($na) {}
8873 set archeads($na) {}
8877 # Update things for a new commit added that is a child of one
8878 # existing commit. Used when cherry-picking.
8879 proc addnewchild {id p} {
8880 global allparents allchildren idtags nextarc
8881 global arcnos arcids arctags arcout arcend arcstart archeads growing
8882 global seeds allcommits
8884 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8885 set allparents($id) [list $p]
8886 set allchildren($id) {}
8887 set arcnos($id) {}
8888 lappend seeds $id
8889 lappend allchildren($p) $id
8890 set a [incr nextarc]
8891 set arcstart($a) $id
8892 set archeads($a) {}
8893 set arctags($a) {}
8894 set arcids($a) [list $p]
8895 set arcend($a) $p
8896 if {![info exists arcout($p)]} {
8897 splitarc $p
8899 lappend arcnos($p) $a
8900 set arcout($id) [list $a]
8903 # This implements a cache for the topology information.
8904 # The cache saves, for each arc, the start and end of the arc,
8905 # the ids on the arc, and the outgoing arcs from the end.
8906 proc readcache {f} {
8907 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8908 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8909 global allcwait
8911 set a $nextarc
8912 set lim $cachedarcs
8913 if {$lim - $a > 500} {
8914 set lim [expr {$a + 500}]
8916 if {[catch {
8917 if {$a == $lim} {
8918 # finish reading the cache and setting up arctags, etc.
8919 set line [gets $f]
8920 if {$line ne "1"} {error "bad final version"}
8921 close $f
8922 foreach id [array names idtags] {
8923 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8924 [llength $allparents($id)] == 1} {
8925 set a [lindex $arcnos($id) 0]
8926 if {$arctags($a) eq {}} {
8927 recalcarc $a
8931 foreach id [array names idheads] {
8932 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8933 [llength $allparents($id)] == 1} {
8934 set a [lindex $arcnos($id) 0]
8935 if {$archeads($a) eq {}} {
8936 recalcarc $a
8940 foreach id [lsort -unique $possible_seeds] {
8941 if {$arcnos($id) eq {}} {
8942 lappend seeds $id
8945 set allcwait 0
8946 } else {
8947 while {[incr a] <= $lim} {
8948 set line [gets $f]
8949 if {[llength $line] != 3} {error "bad line"}
8950 set s [lindex $line 0]
8951 set arcstart($a) $s
8952 lappend arcout($s) $a
8953 if {![info exists arcnos($s)]} {
8954 lappend possible_seeds $s
8955 set arcnos($s) {}
8957 set e [lindex $line 1]
8958 if {$e eq {}} {
8959 set growing($a) 1
8960 } else {
8961 set arcend($a) $e
8962 if {![info exists arcout($e)]} {
8963 set arcout($e) {}
8966 set arcids($a) [lindex $line 2]
8967 foreach id $arcids($a) {
8968 lappend allparents($s) $id
8969 set s $id
8970 lappend arcnos($id) $a
8972 if {![info exists allparents($s)]} {
8973 set allparents($s) {}
8975 set arctags($a) {}
8976 set archeads($a) {}
8978 set nextarc [expr {$a - 1}]
8980 } err]} {
8981 dropcache $err
8982 return 0
8984 if {!$allcwait} {
8985 getallcommits
8987 return $allcwait
8990 proc getcache {f} {
8991 global nextarc cachedarcs possible_seeds
8993 if {[catch {
8994 set line [gets $f]
8995 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
8996 # make sure it's an integer
8997 set cachedarcs [expr {int([lindex $line 1])}]
8998 if {$cachedarcs < 0} {error "bad number of arcs"}
8999 set nextarc 0
9000 set possible_seeds {}
9001 run readcache $f
9002 } err]} {
9003 dropcache $err
9005 return 0
9008 proc dropcache {err} {
9009 global allcwait nextarc cachedarcs seeds
9011 #puts "dropping cache ($err)"
9012 foreach v {arcnos arcout arcids arcstart arcend growing \
9013 arctags archeads allparents allchildren} {
9014 global $v
9015 catch {unset $v}
9017 set allcwait 0
9018 set nextarc 0
9019 set cachedarcs 0
9020 set seeds {}
9021 getallcommits
9024 proc writecache {f} {
9025 global cachearc cachedarcs allccache
9026 global arcstart arcend arcnos arcids arcout
9028 set a $cachearc
9029 set lim $cachedarcs
9030 if {$lim - $a > 1000} {
9031 set lim [expr {$a + 1000}]
9033 if {[catch {
9034 while {[incr a] <= $lim} {
9035 if {[info exists arcend($a)]} {
9036 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9037 } else {
9038 puts $f [list $arcstart($a) {} $arcids($a)]
9041 } err]} {
9042 catch {close $f}
9043 catch {file delete $allccache}
9044 #puts "writing cache failed ($err)"
9045 return 0
9047 set cachearc [expr {$a - 1}]
9048 if {$a > $cachedarcs} {
9049 puts $f "1"
9050 close $f
9051 return 0
9053 return 1
9056 proc savecache {} {
9057 global nextarc cachedarcs cachearc allccache
9059 if {$nextarc == $cachedarcs} return
9060 set cachearc 0
9061 set cachedarcs $nextarc
9062 catch {
9063 set f [open $allccache w]
9064 puts $f [list 1 $cachedarcs]
9065 run writecache $f
9069 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9070 # or 0 if neither is true.
9071 proc anc_or_desc {a b} {
9072 global arcout arcstart arcend arcnos cached_isanc
9074 if {$arcnos($a) eq $arcnos($b)} {
9075 # Both are on the same arc(s); either both are the same BMP,
9076 # or if one is not a BMP, the other is also not a BMP or is
9077 # the BMP at end of the arc (and it only has 1 incoming arc).
9078 # Or both can be BMPs with no incoming arcs.
9079 if {$a eq $b || $arcnos($a) eq {}} {
9080 return 0
9082 # assert {[llength $arcnos($a)] == 1}
9083 set arc [lindex $arcnos($a) 0]
9084 set i [lsearch -exact $arcids($arc) $a]
9085 set j [lsearch -exact $arcids($arc) $b]
9086 if {$i < 0 || $i > $j} {
9087 return 1
9088 } else {
9089 return -1
9093 if {![info exists arcout($a)]} {
9094 set arc [lindex $arcnos($a) 0]
9095 if {[info exists arcend($arc)]} {
9096 set aend $arcend($arc)
9097 } else {
9098 set aend {}
9100 set a $arcstart($arc)
9101 } else {
9102 set aend $a
9104 if {![info exists arcout($b)]} {
9105 set arc [lindex $arcnos($b) 0]
9106 if {[info exists arcend($arc)]} {
9107 set bend $arcend($arc)
9108 } else {
9109 set bend {}
9111 set b $arcstart($arc)
9112 } else {
9113 set bend $b
9115 if {$a eq $bend} {
9116 return 1
9118 if {$b eq $aend} {
9119 return -1
9121 if {[info exists cached_isanc($a,$bend)]} {
9122 if {$cached_isanc($a,$bend)} {
9123 return 1
9126 if {[info exists cached_isanc($b,$aend)]} {
9127 if {$cached_isanc($b,$aend)} {
9128 return -1
9130 if {[info exists cached_isanc($a,$bend)]} {
9131 return 0
9135 set todo [list $a $b]
9136 set anc($a) a
9137 set anc($b) b
9138 for {set i 0} {$i < [llength $todo]} {incr i} {
9139 set x [lindex $todo $i]
9140 if {$anc($x) eq {}} {
9141 continue
9143 foreach arc $arcnos($x) {
9144 set xd $arcstart($arc)
9145 if {$xd eq $bend} {
9146 set cached_isanc($a,$bend) 1
9147 set cached_isanc($b,$aend) 0
9148 return 1
9149 } elseif {$xd eq $aend} {
9150 set cached_isanc($b,$aend) 1
9151 set cached_isanc($a,$bend) 0
9152 return -1
9154 if {![info exists anc($xd)]} {
9155 set anc($xd) $anc($x)
9156 lappend todo $xd
9157 } elseif {$anc($xd) ne $anc($x)} {
9158 set anc($xd) {}
9162 set cached_isanc($a,$bend) 0
9163 set cached_isanc($b,$aend) 0
9164 return 0
9167 # This identifies whether $desc has an ancestor that is
9168 # a growing tip of the graph and which is not an ancestor of $anc
9169 # and returns 0 if so and 1 if not.
9170 # If we subsequently discover a tag on such a growing tip, and that
9171 # turns out to be a descendent of $anc (which it could, since we
9172 # don't necessarily see children before parents), then $desc
9173 # isn't a good choice to display as a descendent tag of
9174 # $anc (since it is the descendent of another tag which is
9175 # a descendent of $anc). Similarly, $anc isn't a good choice to
9176 # display as a ancestor tag of $desc.
9178 proc is_certain {desc anc} {
9179 global arcnos arcout arcstart arcend growing problems
9181 set certain {}
9182 if {[llength $arcnos($anc)] == 1} {
9183 # tags on the same arc are certain
9184 if {$arcnos($desc) eq $arcnos($anc)} {
9185 return 1
9187 if {![info exists arcout($anc)]} {
9188 # if $anc is partway along an arc, use the start of the arc instead
9189 set a [lindex $arcnos($anc) 0]
9190 set anc $arcstart($a)
9193 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9194 set x $desc
9195 } else {
9196 set a [lindex $arcnos($desc) 0]
9197 set x $arcend($a)
9199 if {$x == $anc} {
9200 return 1
9202 set anclist [list $x]
9203 set dl($x) 1
9204 set nnh 1
9205 set ngrowanc 0
9206 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9207 set x [lindex $anclist $i]
9208 if {$dl($x)} {
9209 incr nnh -1
9211 set done($x) 1
9212 foreach a $arcout($x) {
9213 if {[info exists growing($a)]} {
9214 if {![info exists growanc($x)] && $dl($x)} {
9215 set growanc($x) 1
9216 incr ngrowanc
9218 } else {
9219 set y $arcend($a)
9220 if {[info exists dl($y)]} {
9221 if {$dl($y)} {
9222 if {!$dl($x)} {
9223 set dl($y) 0
9224 if {![info exists done($y)]} {
9225 incr nnh -1
9227 if {[info exists growanc($x)]} {
9228 incr ngrowanc -1
9230 set xl [list $y]
9231 for {set k 0} {$k < [llength $xl]} {incr k} {
9232 set z [lindex $xl $k]
9233 foreach c $arcout($z) {
9234 if {[info exists arcend($c)]} {
9235 set v $arcend($c)
9236 if {[info exists dl($v)] && $dl($v)} {
9237 set dl($v) 0
9238 if {![info exists done($v)]} {
9239 incr nnh -1
9241 if {[info exists growanc($v)]} {
9242 incr ngrowanc -1
9244 lappend xl $v
9251 } elseif {$y eq $anc || !$dl($x)} {
9252 set dl($y) 0
9253 lappend anclist $y
9254 } else {
9255 set dl($y) 1
9256 lappend anclist $y
9257 incr nnh
9262 foreach x [array names growanc] {
9263 if {$dl($x)} {
9264 return 0
9266 return 0
9268 return 1
9271 proc validate_arctags {a} {
9272 global arctags idtags
9274 set i -1
9275 set na $arctags($a)
9276 foreach id $arctags($a) {
9277 incr i
9278 if {![info exists idtags($id)]} {
9279 set na [lreplace $na $i $i]
9280 incr i -1
9283 set arctags($a) $na
9286 proc validate_archeads {a} {
9287 global archeads idheads
9289 set i -1
9290 set na $archeads($a)
9291 foreach id $archeads($a) {
9292 incr i
9293 if {![info exists idheads($id)]} {
9294 set na [lreplace $na $i $i]
9295 incr i -1
9298 set archeads($a) $na
9301 # Return the list of IDs that have tags that are descendents of id,
9302 # ignoring IDs that are descendents of IDs already reported.
9303 proc desctags {id} {
9304 global arcnos arcstart arcids arctags idtags allparents
9305 global growing cached_dtags
9307 if {![info exists allparents($id)]} {
9308 return {}
9310 set t1 [clock clicks -milliseconds]
9311 set argid $id
9312 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9313 # part-way along an arc; check that arc first
9314 set a [lindex $arcnos($id) 0]
9315 if {$arctags($a) ne {}} {
9316 validate_arctags $a
9317 set i [lsearch -exact $arcids($a) $id]
9318 set tid {}
9319 foreach t $arctags($a) {
9320 set j [lsearch -exact $arcids($a) $t]
9321 if {$j >= $i} break
9322 set tid $t
9324 if {$tid ne {}} {
9325 return $tid
9328 set id $arcstart($a)
9329 if {[info exists idtags($id)]} {
9330 return $id
9333 if {[info exists cached_dtags($id)]} {
9334 return $cached_dtags($id)
9337 set origid $id
9338 set todo [list $id]
9339 set queued($id) 1
9340 set nc 1
9341 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9342 set id [lindex $todo $i]
9343 set done($id) 1
9344 set ta [info exists hastaggedancestor($id)]
9345 if {!$ta} {
9346 incr nc -1
9348 # ignore tags on starting node
9349 if {!$ta && $i > 0} {
9350 if {[info exists idtags($id)]} {
9351 set tagloc($id) $id
9352 set ta 1
9353 } elseif {[info exists cached_dtags($id)]} {
9354 set tagloc($id) $cached_dtags($id)
9355 set ta 1
9358 foreach a $arcnos($id) {
9359 set d $arcstart($a)
9360 if {!$ta && $arctags($a) ne {}} {
9361 validate_arctags $a
9362 if {$arctags($a) ne {}} {
9363 lappend tagloc($id) [lindex $arctags($a) end]
9366 if {$ta || $arctags($a) ne {}} {
9367 set tomark [list $d]
9368 for {set j 0} {$j < [llength $tomark]} {incr j} {
9369 set dd [lindex $tomark $j]
9370 if {![info exists hastaggedancestor($dd)]} {
9371 if {[info exists done($dd)]} {
9372 foreach b $arcnos($dd) {
9373 lappend tomark $arcstart($b)
9375 if {[info exists tagloc($dd)]} {
9376 unset tagloc($dd)
9378 } elseif {[info exists queued($dd)]} {
9379 incr nc -1
9381 set hastaggedancestor($dd) 1
9385 if {![info exists queued($d)]} {
9386 lappend todo $d
9387 set queued($d) 1
9388 if {![info exists hastaggedancestor($d)]} {
9389 incr nc
9394 set tags {}
9395 foreach id [array names tagloc] {
9396 if {![info exists hastaggedancestor($id)]} {
9397 foreach t $tagloc($id) {
9398 if {[lsearch -exact $tags $t] < 0} {
9399 lappend tags $t
9404 set t2 [clock clicks -milliseconds]
9405 set loopix $i
9407 # remove tags that are descendents of other tags
9408 for {set i 0} {$i < [llength $tags]} {incr i} {
9409 set a [lindex $tags $i]
9410 for {set j 0} {$j < $i} {incr j} {
9411 set b [lindex $tags $j]
9412 set r [anc_or_desc $a $b]
9413 if {$r == 1} {
9414 set tags [lreplace $tags $j $j]
9415 incr j -1
9416 incr i -1
9417 } elseif {$r == -1} {
9418 set tags [lreplace $tags $i $i]
9419 incr i -1
9420 break
9425 if {[array names growing] ne {}} {
9426 # graph isn't finished, need to check if any tag could get
9427 # eclipsed by another tag coming later. Simply ignore any
9428 # tags that could later get eclipsed.
9429 set ctags {}
9430 foreach t $tags {
9431 if {[is_certain $t $origid]} {
9432 lappend ctags $t
9435 if {$tags eq $ctags} {
9436 set cached_dtags($origid) $tags
9437 } else {
9438 set tags $ctags
9440 } else {
9441 set cached_dtags($origid) $tags
9443 set t3 [clock clicks -milliseconds]
9444 if {0 && $t3 - $t1 >= 100} {
9445 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9446 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9448 return $tags
9451 proc anctags {id} {
9452 global arcnos arcids arcout arcend arctags idtags allparents
9453 global growing cached_atags
9455 if {![info exists allparents($id)]} {
9456 return {}
9458 set t1 [clock clicks -milliseconds]
9459 set argid $id
9460 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9461 # part-way along an arc; check that arc first
9462 set a [lindex $arcnos($id) 0]
9463 if {$arctags($a) ne {}} {
9464 validate_arctags $a
9465 set i [lsearch -exact $arcids($a) $id]
9466 foreach t $arctags($a) {
9467 set j [lsearch -exact $arcids($a) $t]
9468 if {$j > $i} {
9469 return $t
9473 if {![info exists arcend($a)]} {
9474 return {}
9476 set id $arcend($a)
9477 if {[info exists idtags($id)]} {
9478 return $id
9481 if {[info exists cached_atags($id)]} {
9482 return $cached_atags($id)
9485 set origid $id
9486 set todo [list $id]
9487 set queued($id) 1
9488 set taglist {}
9489 set nc 1
9490 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9491 set id [lindex $todo $i]
9492 set done($id) 1
9493 set td [info exists hastaggeddescendent($id)]
9494 if {!$td} {
9495 incr nc -1
9497 # ignore tags on starting node
9498 if {!$td && $i > 0} {
9499 if {[info exists idtags($id)]} {
9500 set tagloc($id) $id
9501 set td 1
9502 } elseif {[info exists cached_atags($id)]} {
9503 set tagloc($id) $cached_atags($id)
9504 set td 1
9507 foreach a $arcout($id) {
9508 if {!$td && $arctags($a) ne {}} {
9509 validate_arctags $a
9510 if {$arctags($a) ne {}} {
9511 lappend tagloc($id) [lindex $arctags($a) 0]
9514 if {![info exists arcend($a)]} continue
9515 set d $arcend($a)
9516 if {$td || $arctags($a) ne {}} {
9517 set tomark [list $d]
9518 for {set j 0} {$j < [llength $tomark]} {incr j} {
9519 set dd [lindex $tomark $j]
9520 if {![info exists hastaggeddescendent($dd)]} {
9521 if {[info exists done($dd)]} {
9522 foreach b $arcout($dd) {
9523 if {[info exists arcend($b)]} {
9524 lappend tomark $arcend($b)
9527 if {[info exists tagloc($dd)]} {
9528 unset tagloc($dd)
9530 } elseif {[info exists queued($dd)]} {
9531 incr nc -1
9533 set hastaggeddescendent($dd) 1
9537 if {![info exists queued($d)]} {
9538 lappend todo $d
9539 set queued($d) 1
9540 if {![info exists hastaggeddescendent($d)]} {
9541 incr nc
9546 set t2 [clock clicks -milliseconds]
9547 set loopix $i
9548 set tags {}
9549 foreach id [array names tagloc] {
9550 if {![info exists hastaggeddescendent($id)]} {
9551 foreach t $tagloc($id) {
9552 if {[lsearch -exact $tags $t] < 0} {
9553 lappend tags $t
9559 # remove tags that are ancestors of other tags
9560 for {set i 0} {$i < [llength $tags]} {incr i} {
9561 set a [lindex $tags $i]
9562 for {set j 0} {$j < $i} {incr j} {
9563 set b [lindex $tags $j]
9564 set r [anc_or_desc $a $b]
9565 if {$r == -1} {
9566 set tags [lreplace $tags $j $j]
9567 incr j -1
9568 incr i -1
9569 } elseif {$r == 1} {
9570 set tags [lreplace $tags $i $i]
9571 incr i -1
9572 break
9577 if {[array names growing] ne {}} {
9578 # graph isn't finished, need to check if any tag could get
9579 # eclipsed by another tag coming later. Simply ignore any
9580 # tags that could later get eclipsed.
9581 set ctags {}
9582 foreach t $tags {
9583 if {[is_certain $origid $t]} {
9584 lappend ctags $t
9587 if {$tags eq $ctags} {
9588 set cached_atags($origid) $tags
9589 } else {
9590 set tags $ctags
9592 } else {
9593 set cached_atags($origid) $tags
9595 set t3 [clock clicks -milliseconds]
9596 if {0 && $t3 - $t1 >= 100} {
9597 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9598 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9600 return $tags
9603 # Return the list of IDs that have heads that are descendents of id,
9604 # including id itself if it has a head.
9605 proc descheads {id} {
9606 global arcnos arcstart arcids archeads idheads cached_dheads
9607 global allparents
9609 if {![info exists allparents($id)]} {
9610 return {}
9612 set aret {}
9613 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9614 # part-way along an arc; check it first
9615 set a [lindex $arcnos($id) 0]
9616 if {$archeads($a) ne {}} {
9617 validate_archeads $a
9618 set i [lsearch -exact $arcids($a) $id]
9619 foreach t $archeads($a) {
9620 set j [lsearch -exact $arcids($a) $t]
9621 if {$j > $i} break
9622 lappend aret $t
9625 set id $arcstart($a)
9627 set origid $id
9628 set todo [list $id]
9629 set seen($id) 1
9630 set ret {}
9631 for {set i 0} {$i < [llength $todo]} {incr i} {
9632 set id [lindex $todo $i]
9633 if {[info exists cached_dheads($id)]} {
9634 set ret [concat $ret $cached_dheads($id)]
9635 } else {
9636 if {[info exists idheads($id)]} {
9637 lappend ret $id
9639 foreach a $arcnos($id) {
9640 if {$archeads($a) ne {}} {
9641 validate_archeads $a
9642 if {$archeads($a) ne {}} {
9643 set ret [concat $ret $archeads($a)]
9646 set d $arcstart($a)
9647 if {![info exists seen($d)]} {
9648 lappend todo $d
9649 set seen($d) 1
9654 set ret [lsort -unique $ret]
9655 set cached_dheads($origid) $ret
9656 return [concat $ret $aret]
9659 proc addedtag {id} {
9660 global arcnos arcout cached_dtags cached_atags
9662 if {![info exists arcnos($id)]} return
9663 if {![info exists arcout($id)]} {
9664 recalcarc [lindex $arcnos($id) 0]
9666 catch {unset cached_dtags}
9667 catch {unset cached_atags}
9670 proc addedhead {hid head} {
9671 global arcnos arcout cached_dheads
9673 if {![info exists arcnos($hid)]} return
9674 if {![info exists arcout($hid)]} {
9675 recalcarc [lindex $arcnos($hid) 0]
9677 catch {unset cached_dheads}
9680 proc removedhead {hid head} {
9681 global cached_dheads
9683 catch {unset cached_dheads}
9686 proc movedhead {hid head} {
9687 global arcnos arcout cached_dheads
9689 if {![info exists arcnos($hid)]} return
9690 if {![info exists arcout($hid)]} {
9691 recalcarc [lindex $arcnos($hid) 0]
9693 catch {unset cached_dheads}
9696 proc changedrefs {} {
9697 global cached_dheads cached_dtags cached_atags
9698 global arctags archeads arcnos arcout idheads idtags
9700 foreach id [concat [array names idheads] [array names idtags]] {
9701 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9702 set a [lindex $arcnos($id) 0]
9703 if {![info exists donearc($a)]} {
9704 recalcarc $a
9705 set donearc($a) 1
9709 catch {unset cached_dtags}
9710 catch {unset cached_atags}
9711 catch {unset cached_dheads}
9714 proc rereadrefs {} {
9715 global idtags idheads idotherrefs mainheadid
9717 set refids [concat [array names idtags] \
9718 [array names idheads] [array names idotherrefs]]
9719 foreach id $refids {
9720 if {![info exists ref($id)]} {
9721 set ref($id) [listrefs $id]
9724 set oldmainhead $mainheadid
9725 readrefs
9726 changedrefs
9727 set refids [lsort -unique [concat $refids [array names idtags] \
9728 [array names idheads] [array names idotherrefs]]]
9729 foreach id $refids {
9730 set v [listrefs $id]
9731 if {![info exists ref($id)] || $ref($id) != $v} {
9732 redrawtags $id
9735 if {$oldmainhead ne $mainheadid} {
9736 redrawtags $oldmainhead
9737 redrawtags $mainheadid
9739 run refill_reflist
9742 proc listrefs {id} {
9743 global idtags idheads idotherrefs
9745 set x {}
9746 if {[info exists idtags($id)]} {
9747 set x $idtags($id)
9749 set y {}
9750 if {[info exists idheads($id)]} {
9751 set y $idheads($id)
9753 set z {}
9754 if {[info exists idotherrefs($id)]} {
9755 set z $idotherrefs($id)
9757 return [list $x $y $z]
9760 proc showtag {tag isnew} {
9761 global ctext tagcontents tagids linknum tagobjid
9763 if {$isnew} {
9764 addtohistory [list showtag $tag 0]
9766 $ctext conf -state normal
9767 clear_ctext
9768 settabs 0
9769 set linknum 0
9770 if {![info exists tagcontents($tag)]} {
9771 catch {
9772 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9775 if {[info exists tagcontents($tag)]} {
9776 set text $tagcontents($tag)
9777 } else {
9778 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9780 appendwithlinks $text {}
9781 $ctext conf -state disabled
9782 init_flist {}
9785 proc doquit {} {
9786 global stopped
9787 global gitktmpdir
9789 set stopped 100
9790 savestuff .
9791 destroy .
9793 if {[info exists gitktmpdir]} {
9794 catch {file delete -force $gitktmpdir}
9798 proc mkfontdisp {font top which} {
9799 global fontattr fontpref $font
9801 set fontpref($font) [set $font]
9802 button $top.${font}but -text $which -font optionfont \
9803 -command [list choosefont $font $which]
9804 label $top.$font -relief flat -font $font \
9805 -text $fontattr($font,family) -justify left
9806 grid x $top.${font}but $top.$font -sticky w
9809 proc choosefont {font which} {
9810 global fontparam fontlist fonttop fontattr
9811 global prefstop
9813 set fontparam(which) $which
9814 set fontparam(font) $font
9815 set fontparam(family) [font actual $font -family]
9816 set fontparam(size) $fontattr($font,size)
9817 set fontparam(weight) $fontattr($font,weight)
9818 set fontparam(slant) $fontattr($font,slant)
9819 set top .gitkfont
9820 set fonttop $top
9821 if {![winfo exists $top]} {
9822 font create sample
9823 eval font config sample [font actual $font]
9824 toplevel $top
9825 wm transient $top $prefstop
9826 wm title $top [mc "Gitk font chooser"]
9827 label $top.l -textvariable fontparam(which)
9828 pack $top.l -side top
9829 set fontlist [lsort [font families]]
9830 frame $top.f
9831 listbox $top.f.fam -listvariable fontlist \
9832 -yscrollcommand [list $top.f.sb set]
9833 bind $top.f.fam <<ListboxSelect>> selfontfam
9834 scrollbar $top.f.sb -command [list $top.f.fam yview]
9835 pack $top.f.sb -side right -fill y
9836 pack $top.f.fam -side left -fill both -expand 1
9837 pack $top.f -side top -fill both -expand 1
9838 frame $top.g
9839 spinbox $top.g.size -from 4 -to 40 -width 4 \
9840 -textvariable fontparam(size) \
9841 -validatecommand {string is integer -strict %s}
9842 checkbutton $top.g.bold -padx 5 \
9843 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9844 -variable fontparam(weight) -onvalue bold -offvalue normal
9845 checkbutton $top.g.ital -padx 5 \
9846 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9847 -variable fontparam(slant) -onvalue italic -offvalue roman
9848 pack $top.g.size $top.g.bold $top.g.ital -side left
9849 pack $top.g -side top
9850 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9851 -background white
9852 $top.c create text 100 25 -anchor center -text $which -font sample \
9853 -fill black -tags text
9854 bind $top.c <Configure> [list centertext $top.c]
9855 pack $top.c -side top -fill x
9856 frame $top.buts
9857 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9858 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9859 bind $top <Key-Return> fontok
9860 bind $top <Key-Escape> fontcan
9861 grid $top.buts.ok $top.buts.can
9862 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9863 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9864 pack $top.buts -side bottom -fill x
9865 trace add variable fontparam write chg_fontparam
9866 } else {
9867 raise $top
9868 $top.c itemconf text -text $which
9870 set i [lsearch -exact $fontlist $fontparam(family)]
9871 if {$i >= 0} {
9872 $top.f.fam selection set $i
9873 $top.f.fam see $i
9877 proc centertext {w} {
9878 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9881 proc fontok {} {
9882 global fontparam fontpref prefstop
9884 set f $fontparam(font)
9885 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9886 if {$fontparam(weight) eq "bold"} {
9887 lappend fontpref($f) "bold"
9889 if {$fontparam(slant) eq "italic"} {
9890 lappend fontpref($f) "italic"
9892 set w $prefstop.$f
9893 $w conf -text $fontparam(family) -font $fontpref($f)
9895 fontcan
9898 proc fontcan {} {
9899 global fonttop fontparam
9901 if {[info exists fonttop]} {
9902 catch {destroy $fonttop}
9903 catch {font delete sample}
9904 unset fonttop
9905 unset fontparam
9909 proc selfontfam {} {
9910 global fonttop fontparam
9912 set i [$fonttop.f.fam curselection]
9913 if {$i ne {}} {
9914 set fontparam(family) [$fonttop.f.fam get $i]
9918 proc chg_fontparam {v sub op} {
9919 global fontparam
9921 font config sample -$sub $fontparam($sub)
9924 proc doprefs {} {
9925 global maxwidth maxgraphpct
9926 global oldprefs prefstop showneartags showlocalchanges
9927 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9928 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9930 set top .gitkprefs
9931 set prefstop $top
9932 if {[winfo exists $top]} {
9933 raise $top
9934 return
9936 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9937 limitdiffs tabstop perfile_attrs} {
9938 set oldprefs($v) [set $v]
9940 toplevel $top
9941 wm title $top [mc "Gitk preferences"]
9942 wm transient $top .
9943 label $top.ldisp -text [mc "Commit list display options"]
9944 grid $top.ldisp - -sticky w -pady 10
9945 label $top.spacer -text " "
9946 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
9947 -font optionfont
9948 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
9949 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
9950 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
9951 -font optionfont
9952 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
9953 grid x $top.maxpctl $top.maxpct -sticky w
9954 frame $top.showlocal
9955 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
9956 checkbutton $top.showlocal.b -variable showlocalchanges
9957 pack $top.showlocal.b $top.showlocal.l -side left
9958 grid x $top.showlocal -sticky w
9959 frame $top.autoselect
9960 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
9961 checkbutton $top.autoselect.b -variable autoselect
9962 pack $top.autoselect.b $top.autoselect.l -side left
9963 grid x $top.autoselect -sticky w
9965 label $top.ddisp -text [mc "Diff display options"]
9966 grid $top.ddisp - -sticky w -pady 10
9967 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
9968 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
9969 grid x $top.tabstopl $top.tabstop -sticky w
9970 frame $top.ntag
9971 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
9972 checkbutton $top.ntag.b -variable showneartags
9973 pack $top.ntag.b $top.ntag.l -side left
9974 grid x $top.ntag -sticky w
9975 frame $top.ldiff
9976 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
9977 checkbutton $top.ldiff.b -variable limitdiffs
9978 pack $top.ldiff.b $top.ldiff.l -side left
9979 grid x $top.ldiff -sticky w
9980 frame $top.lattr
9981 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
9982 checkbutton $top.lattr.b -variable perfile_attrs
9983 pack $top.lattr.b $top.lattr.l -side left
9984 grid x $top.lattr -sticky w
9986 entry $top.extdifft -textvariable extdifftool
9987 frame $top.extdifff
9988 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
9989 -padx 10
9990 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
9991 -command choose_extdiff
9992 pack $top.extdifff.l $top.extdifff.b -side left
9993 grid x $top.extdifff $top.extdifft -sticky w
9995 label $top.cdisp -text [mc "Colors: press to choose"]
9996 grid $top.cdisp - -sticky w -pady 10
9997 label $top.bg -padx 40 -relief sunk -background $bgcolor
9998 button $top.bgbut -text [mc "Background"] -font optionfont \
9999 -command [list choosecolor bgcolor {} $top.bg background setbg]
10000 grid x $top.bgbut $top.bg -sticky w
10001 label $top.fg -padx 40 -relief sunk -background $fgcolor
10002 button $top.fgbut -text [mc "Foreground"] -font optionfont \
10003 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
10004 grid x $top.fgbut $top.fg -sticky w
10005 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10006 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
10007 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
10008 [list $ctext tag conf d0 -foreground]]
10009 grid x $top.diffoldbut $top.diffold -sticky w
10010 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10011 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
10012 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
10013 [list $ctext tag conf d1 -foreground]]
10014 grid x $top.diffnewbut $top.diffnew -sticky w
10015 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10016 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
10017 -command [list choosecolor diffcolors 2 $top.hunksep \
10018 "diff hunk header" \
10019 [list $ctext tag conf hunksep -foreground]]
10020 grid x $top.hunksepbut $top.hunksep -sticky w
10021 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10022 button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
10023 -command [list choosecolor markbgcolor {} $top.markbgsep \
10024 [mc "marked line background"] \
10025 [list $ctext tag conf omark -background]]
10026 grid x $top.markbgbut $top.markbgsep -sticky w
10027 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10028 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
10029 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
10030 grid x $top.selbgbut $top.selbgsep -sticky w
10032 label $top.cfont -text [mc "Fonts: press to choose"]
10033 grid $top.cfont - -sticky w -pady 10
10034 mkfontdisp mainfont $top [mc "Main font"]
10035 mkfontdisp textfont $top [mc "Diff display font"]
10036 mkfontdisp uifont $top [mc "User interface font"]
10038 frame $top.buts
10039 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10040 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10041 bind $top <Key-Return> prefsok
10042 bind $top <Key-Escape> prefscan
10043 grid $top.buts.ok $top.buts.can
10044 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10045 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10046 grid $top.buts - - -pady 10 -sticky ew
10047 bind $top <Visibility> "focus $top.buts.ok"
10050 proc choose_extdiff {} {
10051 global extdifftool
10053 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
10054 if {$prog ne {}} {
10055 set extdifftool $prog
10059 proc choosecolor {v vi w x cmd} {
10060 global $v
10062 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10063 -title [mc "Gitk: choose color for %s" $x]]
10064 if {$c eq {}} return
10065 $w conf -background $c
10066 lset $v $vi $c
10067 eval $cmd $c
10070 proc setselbg {c} {
10071 global bglist cflist
10072 foreach w $bglist {
10073 $w configure -selectbackground $c
10075 $cflist tag configure highlight \
10076 -background [$cflist cget -selectbackground]
10077 allcanvs itemconf secsel -fill $c
10080 proc setbg {c} {
10081 global bglist
10083 foreach w $bglist {
10084 $w conf -background $c
10088 proc setfg {c} {
10089 global fglist canv
10091 foreach w $fglist {
10092 $w conf -foreground $c
10094 allcanvs itemconf text -fill $c
10095 $canv itemconf circle -outline $c
10098 proc prefscan {} {
10099 global oldprefs prefstop
10101 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10102 limitdiffs tabstop perfile_attrs} {
10103 global $v
10104 set $v $oldprefs($v)
10106 catch {destroy $prefstop}
10107 unset prefstop
10108 fontcan
10111 proc prefsok {} {
10112 global maxwidth maxgraphpct
10113 global oldprefs prefstop showneartags showlocalchanges
10114 global fontpref mainfont textfont uifont
10115 global limitdiffs treediffs perfile_attrs
10117 catch {destroy $prefstop}
10118 unset prefstop
10119 fontcan
10120 set fontchanged 0
10121 if {$mainfont ne $fontpref(mainfont)} {
10122 set mainfont $fontpref(mainfont)
10123 parsefont mainfont $mainfont
10124 eval font configure mainfont [fontflags mainfont]
10125 eval font configure mainfontbold [fontflags mainfont 1]
10126 setcoords
10127 set fontchanged 1
10129 if {$textfont ne $fontpref(textfont)} {
10130 set textfont $fontpref(textfont)
10131 parsefont textfont $textfont
10132 eval font configure textfont [fontflags textfont]
10133 eval font configure textfontbold [fontflags textfont 1]
10135 if {$uifont ne $fontpref(uifont)} {
10136 set uifont $fontpref(uifont)
10137 parsefont uifont $uifont
10138 eval font configure uifont [fontflags uifont]
10140 settabs
10141 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10142 if {$showlocalchanges} {
10143 doshowlocalchanges
10144 } else {
10145 dohidelocalchanges
10148 if {$limitdiffs != $oldprefs(limitdiffs) ||
10149 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10150 # treediffs elements are limited by path;
10151 # won't have encodings cached if perfile_attrs was just turned on
10152 catch {unset treediffs}
10154 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10155 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10156 redisplay
10157 } elseif {$showneartags != $oldprefs(showneartags) ||
10158 $limitdiffs != $oldprefs(limitdiffs)} {
10159 reselectline
10163 proc formatdate {d} {
10164 global datetimeformat
10165 if {$d ne {}} {
10166 set d [clock format $d -format $datetimeformat]
10168 return $d
10171 # This list of encoding names and aliases is distilled from
10172 # http://www.iana.org/assignments/character-sets.
10173 # Not all of them are supported by Tcl.
10174 set encoding_aliases {
10175 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10176 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10177 { ISO-10646-UTF-1 csISO10646UTF1 }
10178 { ISO_646.basic:1983 ref csISO646basic1983 }
10179 { INVARIANT csINVARIANT }
10180 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10181 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10182 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10183 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10184 { NATS-DANO iso-ir-9-1 csNATSDANO }
10185 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10186 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10187 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10188 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10189 { ISO-2022-KR csISO2022KR }
10190 { EUC-KR csEUCKR }
10191 { ISO-2022-JP csISO2022JP }
10192 { ISO-2022-JP-2 csISO2022JP2 }
10193 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10194 csISO13JISC6220jp }
10195 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10196 { IT iso-ir-15 ISO646-IT csISO15Italian }
10197 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10198 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10199 { greek7-old iso-ir-18 csISO18Greek7Old }
10200 { latin-greek iso-ir-19 csISO19LatinGreek }
10201 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10202 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10203 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10204 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10205 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10206 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10207 { INIS iso-ir-49 csISO49INIS }
10208 { INIS-8 iso-ir-50 csISO50INIS8 }
10209 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10210 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10211 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10212 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10213 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10214 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10215 csISO60Norwegian1 }
10216 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10217 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10218 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10219 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10220 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10221 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10222 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10223 { greek7 iso-ir-88 csISO88Greek7 }
10224 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10225 { iso-ir-90 csISO90 }
10226 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10227 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10228 csISO92JISC62991984b }
10229 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10230 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10231 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10232 csISO95JIS62291984handadd }
10233 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10234 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10235 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10236 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10237 CP819 csISOLatin1 }
10238 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10239 { T.61-7bit iso-ir-102 csISO102T617bit }
10240 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10241 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10242 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10243 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10244 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10245 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10246 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10247 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10248 arabic csISOLatinArabic }
10249 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10250 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10251 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10252 greek greek8 csISOLatinGreek }
10253 { T.101-G2 iso-ir-128 csISO128T101G2 }
10254 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10255 csISOLatinHebrew }
10256 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10257 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10258 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10259 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10260 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10261 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10262 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10263 csISOLatinCyrillic }
10264 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10265 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10266 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10267 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10268 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10269 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10270 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10271 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10272 { ISO_10367-box iso-ir-155 csISO10367Box }
10273 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10274 { latin-lap lap iso-ir-158 csISO158Lap }
10275 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10276 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10277 { us-dk csUSDK }
10278 { dk-us csDKUS }
10279 { JIS_X0201 X0201 csHalfWidthKatakana }
10280 { KSC5636 ISO646-KR csKSC5636 }
10281 { ISO-10646-UCS-2 csUnicode }
10282 { ISO-10646-UCS-4 csUCS4 }
10283 { DEC-MCS dec csDECMCS }
10284 { hp-roman8 roman8 r8 csHPRoman8 }
10285 { macintosh mac csMacintosh }
10286 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10287 csIBM037 }
10288 { IBM038 EBCDIC-INT cp038 csIBM038 }
10289 { IBM273 CP273 csIBM273 }
10290 { IBM274 EBCDIC-BE CP274 csIBM274 }
10291 { IBM275 EBCDIC-BR cp275 csIBM275 }
10292 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10293 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10294 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10295 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10296 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10297 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10298 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10299 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10300 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10301 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10302 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10303 { IBM437 cp437 437 csPC8CodePage437 }
10304 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10305 { IBM775 cp775 csPC775Baltic }
10306 { IBM850 cp850 850 csPC850Multilingual }
10307 { IBM851 cp851 851 csIBM851 }
10308 { IBM852 cp852 852 csPCp852 }
10309 { IBM855 cp855 855 csIBM855 }
10310 { IBM857 cp857 857 csIBM857 }
10311 { IBM860 cp860 860 csIBM860 }
10312 { IBM861 cp861 861 cp-is csIBM861 }
10313 { IBM862 cp862 862 csPC862LatinHebrew }
10314 { IBM863 cp863 863 csIBM863 }
10315 { IBM864 cp864 csIBM864 }
10316 { IBM865 cp865 865 csIBM865 }
10317 { IBM866 cp866 866 csIBM866 }
10318 { IBM868 CP868 cp-ar csIBM868 }
10319 { IBM869 cp869 869 cp-gr csIBM869 }
10320 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10321 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10322 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10323 { IBM891 cp891 csIBM891 }
10324 { IBM903 cp903 csIBM903 }
10325 { IBM904 cp904 904 csIBBM904 }
10326 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10327 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10328 { IBM1026 CP1026 csIBM1026 }
10329 { EBCDIC-AT-DE csIBMEBCDICATDE }
10330 { EBCDIC-AT-DE-A csEBCDICATDEA }
10331 { EBCDIC-CA-FR csEBCDICCAFR }
10332 { EBCDIC-DK-NO csEBCDICDKNO }
10333 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10334 { EBCDIC-FI-SE csEBCDICFISE }
10335 { EBCDIC-FI-SE-A csEBCDICFISEA }
10336 { EBCDIC-FR csEBCDICFR }
10337 { EBCDIC-IT csEBCDICIT }
10338 { EBCDIC-PT csEBCDICPT }
10339 { EBCDIC-ES csEBCDICES }
10340 { EBCDIC-ES-A csEBCDICESA }
10341 { EBCDIC-ES-S csEBCDICESS }
10342 { EBCDIC-UK csEBCDICUK }
10343 { EBCDIC-US csEBCDICUS }
10344 { UNKNOWN-8BIT csUnknown8BiT }
10345 { MNEMONIC csMnemonic }
10346 { MNEM csMnem }
10347 { VISCII csVISCII }
10348 { VIQR csVIQR }
10349 { KOI8-R csKOI8R }
10350 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10351 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10352 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10353 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10354 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10355 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10356 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10357 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10358 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10359 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10360 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10361 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10362 { IBM1047 IBM-1047 }
10363 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10364 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10365 { UNICODE-1-1 csUnicode11 }
10366 { CESU-8 csCESU-8 }
10367 { BOCU-1 csBOCU-1 }
10368 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10369 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10370 l8 }
10371 { ISO-8859-15 ISO_8859-15 Latin-9 }
10372 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10373 { GBK CP936 MS936 windows-936 }
10374 { JIS_Encoding csJISEncoding }
10375 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10376 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10377 EUC-JP }
10378 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10379 { ISO-10646-UCS-Basic csUnicodeASCII }
10380 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10381 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10382 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10383 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10384 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10385 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10386 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10387 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10388 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10389 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10390 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10391 { Ventura-US csVenturaUS }
10392 { Ventura-International csVenturaInternational }
10393 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10394 { PC8-Turkish csPC8Turkish }
10395 { IBM-Symbols csIBMSymbols }
10396 { IBM-Thai csIBMThai }
10397 { HP-Legal csHPLegal }
10398 { HP-Pi-font csHPPiFont }
10399 { HP-Math8 csHPMath8 }
10400 { Adobe-Symbol-Encoding csHPPSMath }
10401 { HP-DeskTop csHPDesktop }
10402 { Ventura-Math csVenturaMath }
10403 { Microsoft-Publishing csMicrosoftPublishing }
10404 { Windows-31J csWindows31J }
10405 { GB2312 csGB2312 }
10406 { Big5 csBig5 }
10409 proc tcl_encoding {enc} {
10410 global encoding_aliases tcl_encoding_cache
10411 if {[info exists tcl_encoding_cache($enc)]} {
10412 return $tcl_encoding_cache($enc)
10414 set names [encoding names]
10415 set lcnames [string tolower $names]
10416 set enc [string tolower $enc]
10417 set i [lsearch -exact $lcnames $enc]
10418 if {$i < 0} {
10419 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10420 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10421 set i [lsearch -exact $lcnames $encx]
10424 if {$i < 0} {
10425 foreach l $encoding_aliases {
10426 set ll [string tolower $l]
10427 if {[lsearch -exact $ll $enc] < 0} continue
10428 # look through the aliases for one that tcl knows about
10429 foreach e $ll {
10430 set i [lsearch -exact $lcnames $e]
10431 if {$i < 0} {
10432 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10433 set i [lsearch -exact $lcnames $ex]
10436 if {$i >= 0} break
10438 break
10441 set tclenc {}
10442 if {$i >= 0} {
10443 set tclenc [lindex $names $i]
10445 set tcl_encoding_cache($enc) $tclenc
10446 return $tclenc
10449 proc gitattr {path attr default} {
10450 global path_attr_cache
10451 if {[info exists path_attr_cache($attr,$path)]} {
10452 set r $path_attr_cache($attr,$path)
10453 } else {
10454 set r "unspecified"
10455 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10456 regexp "(.*): encoding: (.*)" $line m f r
10458 set path_attr_cache($attr,$path) $r
10460 if {$r eq "unspecified"} {
10461 return $default
10463 return $r
10466 proc cache_gitattr {attr pathlist} {
10467 global path_attr_cache
10468 set newlist {}
10469 foreach path $pathlist {
10470 if {![info exists path_attr_cache($attr,$path)]} {
10471 lappend newlist $path
10474 set lim 1000
10475 if {[tk windowingsystem] == "win32"} {
10476 # windows has a 32k limit on the arguments to a command...
10477 set lim 30
10479 while {$newlist ne {}} {
10480 set head [lrange $newlist 0 [expr {$lim - 1}]]
10481 set newlist [lrange $newlist $lim end]
10482 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10483 foreach row [split $rlist "\n"] {
10484 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10485 if {[string index $path 0] eq "\""} {
10486 set path [encoding convertfrom [lindex $path 0]]
10488 set path_attr_cache($attr,$path) $value
10495 proc get_path_encoding {path} {
10496 global gui_encoding perfile_attrs
10497 set tcl_enc $gui_encoding
10498 if {$path ne {} && $perfile_attrs} {
10499 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10500 if {$enc2 ne {}} {
10501 set tcl_enc $enc2
10504 return $tcl_enc
10507 # First check that Tcl/Tk is recent enough
10508 if {[catch {package require Tk 8.4} err]} {
10509 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10510 Gitk requires at least Tcl/Tk 8.4."]
10511 exit 1
10514 # defaults...
10515 set wrcomcmd "git diff-tree --stdin -p --pretty"
10517 set gitencoding {}
10518 catch {
10519 set gitencoding [exec git config --get i18n.commitencoding]
10521 if {$gitencoding == ""} {
10522 set gitencoding "utf-8"
10524 set tclencoding [tcl_encoding $gitencoding]
10525 if {$tclencoding == {}} {
10526 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10529 set gui_encoding [encoding system]
10530 catch {
10531 set enc [exec git config --get gui.encoding]
10532 if {$enc ne {}} {
10533 set tclenc [tcl_encoding $enc]
10534 if {$tclenc ne {}} {
10535 set gui_encoding $tclenc
10536 } else {
10537 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10542 set mainfont {Helvetica 9}
10543 set textfont {Courier 9}
10544 set uifont {Helvetica 9 bold}
10545 set tabstop 8
10546 set findmergefiles 0
10547 set maxgraphpct 50
10548 set maxwidth 16
10549 set revlistorder 0
10550 set fastdate 0
10551 set uparrowlen 5
10552 set downarrowlen 5
10553 set mingaplen 100
10554 set cmitmode "patch"
10555 set wrapcomment "none"
10556 set showneartags 1
10557 set maxrefs 20
10558 set maxlinelen 200
10559 set showlocalchanges 1
10560 set limitdiffs 1
10561 set datetimeformat "%Y-%m-%d %H:%M:%S"
10562 set autoselect 1
10563 set perfile_attrs 0
10565 set extdifftool "meld"
10567 set colors {green red blue magenta darkgrey brown orange}
10568 set bgcolor white
10569 set fgcolor black
10570 set diffcolors {red "#00a000" blue}
10571 set diffcontext 3
10572 set ignorespace 0
10573 set selectbgcolor gray85
10574 set markbgcolor "#e0e0ff"
10576 set circlecolors {white blue gray blue blue}
10578 # button for popping up context menus
10579 if {[tk windowingsystem] eq "aqua"} {
10580 set ctxbut <Button-2>
10581 } else {
10582 set ctxbut <Button-3>
10585 ## For msgcat loading, first locate the installation location.
10586 if { [info exists ::env(GITK_MSGSDIR)] } {
10587 ## Msgsdir was manually set in the environment.
10588 set gitk_msgsdir $::env(GITK_MSGSDIR)
10589 } else {
10590 ## Let's guess the prefix from argv0.
10591 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10592 set gitk_libdir [file join $gitk_prefix share gitk lib]
10593 set gitk_msgsdir [file join $gitk_libdir msgs]
10594 unset gitk_prefix
10597 ## Internationalization (i18n) through msgcat and gettext. See
10598 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10599 package require msgcat
10600 namespace import ::msgcat::mc
10601 ## And eventually load the actual message catalog
10602 ::msgcat::mcload $gitk_msgsdir
10604 catch {source ~/.gitk}
10606 font create optionfont -family sans-serif -size -12
10608 parsefont mainfont $mainfont
10609 eval font create mainfont [fontflags mainfont]
10610 eval font create mainfontbold [fontflags mainfont 1]
10612 parsefont textfont $textfont
10613 eval font create textfont [fontflags textfont]
10614 eval font create textfontbold [fontflags textfont 1]
10616 parsefont uifont $uifont
10617 eval font create uifont [fontflags uifont]
10619 setoptions
10621 # check that we can find a .git directory somewhere...
10622 if {[catch {set gitdir [gitdir]}]} {
10623 show_error {} . [mc "Cannot find a git repository here."]
10624 exit 1
10626 if {![file isdirectory $gitdir]} {
10627 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10628 exit 1
10631 set selecthead {}
10632 set selectheadid {}
10634 set revtreeargs {}
10635 set cmdline_files {}
10636 set i 0
10637 set revtreeargscmd {}
10638 foreach arg $argv {
10639 switch -glob -- $arg {
10640 "" { }
10641 "--" {
10642 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10643 break
10645 "--select-commit=*" {
10646 set selecthead [string range $arg 16 end]
10648 "--argscmd=*" {
10649 set revtreeargscmd [string range $arg 10 end]
10651 default {
10652 lappend revtreeargs $arg
10655 incr i
10658 if {$selecthead eq "HEAD"} {
10659 set selecthead {}
10662 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10663 # no -- on command line, but some arguments (other than --argscmd)
10664 if {[catch {
10665 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10666 set cmdline_files [split $f "\n"]
10667 set n [llength $cmdline_files]
10668 set revtreeargs [lrange $revtreeargs 0 end-$n]
10669 # Unfortunately git rev-parse doesn't produce an error when
10670 # something is both a revision and a filename. To be consistent
10671 # with git log and git rev-list, check revtreeargs for filenames.
10672 foreach arg $revtreeargs {
10673 if {[file exists $arg]} {
10674 show_error {} . [mc "Ambiguous argument '%s': both revision\
10675 and filename" $arg]
10676 exit 1
10679 } err]} {
10680 # unfortunately we get both stdout and stderr in $err,
10681 # so look for "fatal:".
10682 set i [string first "fatal:" $err]
10683 if {$i > 0} {
10684 set err [string range $err [expr {$i + 6}] end]
10686 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10687 exit 1
10691 set nullid "0000000000000000000000000000000000000000"
10692 set nullid2 "0000000000000000000000000000000000000001"
10693 set nullfile "/dev/null"
10695 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10697 set runq {}
10698 set history {}
10699 set historyindex 0
10700 set fh_serial 0
10701 set nhl_names {}
10702 set highlight_paths {}
10703 set findpattern {}
10704 set searchdirn -forwards
10705 set boldrows {}
10706 set boldnamerows {}
10707 set diffelide {0 0}
10708 set markingmatches 0
10709 set linkentercount 0
10710 set need_redisplay 0
10711 set nrows_drawn 0
10712 set firsttabstop 0
10714 set nextviewnum 1
10715 set curview 0
10716 set selectedview 0
10717 set selectedhlview [mc "None"]
10718 set highlight_related [mc "None"]
10719 set highlight_files {}
10720 set viewfiles(0) {}
10721 set viewperm(0) 0
10722 set viewargs(0) {}
10723 set viewargscmd(0) {}
10725 set selectedline {}
10726 set numcommits 0
10727 set loginstance 0
10728 set cmdlineok 0
10729 set stopped 0
10730 set stuffsaved 0
10731 set patchnum 0
10732 set lserial 0
10733 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10734 setcoords
10735 makewindow
10736 # wait for the window to become visible
10737 tkwait visibility .
10738 wm title . "[file tail $argv0]: [file tail [pwd]]"
10739 readrefs
10741 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10742 # create a view for the files/dirs specified on the command line
10743 set curview 1
10744 set selectedview 1
10745 set nextviewnum 2
10746 set viewname(1) [mc "Command line"]
10747 set viewfiles(1) $cmdline_files
10748 set viewargs(1) $revtreeargs
10749 set viewargscmd(1) $revtreeargscmd
10750 set viewperm(1) 0
10751 set vdatemode(1) 0
10752 addviewmenu 1
10753 .bar.view entryconf [mca "Edit view..."] -state normal
10754 .bar.view entryconf [mca "Delete view"] -state normal
10757 if {[info exists permviews]} {
10758 foreach v $permviews {
10759 set n $nextviewnum
10760 incr nextviewnum
10761 set viewname($n) [lindex $v 0]
10762 set viewfiles($n) [lindex $v 1]
10763 set viewargs($n) [lindex $v 2]
10764 set viewargscmd($n) [lindex $v 3]
10765 set viewperm($n) 1
10766 addviewmenu $n
10769 getcommits {}