gitk: Add a user preference to enable/disable use of themed widgets
[alt-git.git] / gitk
blob4d427e23c6c49d75adff01d2cf91f331892c857f
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 package require Tk
12 proc gitdir {} {
13 global env
14 if {[info exists env(GIT_DIR)]} {
15 return $env(GIT_DIR)
16 } else {
17 return [exec git rev-parse --git-dir]
21 # A simple scheduler for compute-intensive stuff.
22 # The aim is to make sure that event handlers for GUI actions can
23 # run at least every 50-100 ms. Unfortunately fileevent handlers are
24 # run before X event handlers, so reading from a fast source can
25 # make the GUI completely unresponsive.
26 proc run args {
27 global isonrunq runq currunq
29 set script $args
30 if {[info exists isonrunq($script)]} return
31 if {$runq eq {} && ![info exists currunq]} {
32 after idle dorunq
34 lappend runq [list {} $script]
35 set isonrunq($script) 1
38 proc filerun {fd script} {
39 fileevent $fd readable [list filereadable $fd $script]
42 proc filereadable {fd script} {
43 global runq currunq
45 fileevent $fd readable {}
46 if {$runq eq {} && ![info exists currunq]} {
47 after idle dorunq
49 lappend runq [list $fd $script]
52 proc nukefile {fd} {
53 global runq
55 for {set i 0} {$i < [llength $runq]} {} {
56 if {[lindex $runq $i 0] eq $fd} {
57 set runq [lreplace $runq $i $i]
58 } else {
59 incr i
64 proc dorunq {} {
65 global isonrunq runq currunq
67 set tstart [clock clicks -milliseconds]
68 set t0 $tstart
69 while {[llength $runq] > 0} {
70 set fd [lindex $runq 0 0]
71 set script [lindex $runq 0 1]
72 set currunq [lindex $runq 0]
73 set runq [lrange $runq 1 end]
74 set repeat [eval $script]
75 unset currunq
76 set t1 [clock clicks -milliseconds]
77 set t [expr {$t1 - $t0}]
78 if {$repeat ne {} && $repeat} {
79 if {$fd eq {} || $repeat == 2} {
80 # script returns 1 if it wants to be readded
81 # file readers return 2 if they could do more straight away
82 lappend runq [list $fd $script]
83 } else {
84 fileevent $fd readable [list filereadable $fd $script]
86 } elseif {$fd eq {}} {
87 unset isonrunq($script)
89 set t0 $t1
90 if {$t1 - $tstart >= 80} break
92 if {$runq ne {}} {
93 after idle dorunq
97 proc reg_instance {fd} {
98 global commfd leftover loginstance
100 set i [incr loginstance]
101 set commfd($i) $fd
102 set leftover($i) {}
103 return $i
106 proc unmerged_files {files} {
107 global nr_unmerged
109 # find the list of unmerged files
110 set mlist {}
111 set nr_unmerged 0
112 if {[catch {
113 set fd [open "| git ls-files -u" r]
114 } err]} {
115 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
116 exit 1
118 while {[gets $fd line] >= 0} {
119 set i [string first "\t" $line]
120 if {$i < 0} continue
121 set fname [string range $line [expr {$i+1}] end]
122 if {[lsearch -exact $mlist $fname] >= 0} continue
123 incr nr_unmerged
124 if {$files eq {} || [path_filter $files $fname]} {
125 lappend mlist $fname
128 catch {close $fd}
129 return $mlist
132 proc parseviewargs {n arglist} {
133 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
135 set vdatemode($n) 0
136 set vmergeonly($n) 0
137 set glflags {}
138 set diffargs {}
139 set nextisval 0
140 set revargs {}
141 set origargs $arglist
142 set allknown 1
143 set filtered 0
144 set i -1
145 foreach arg $arglist {
146 incr i
147 if {$nextisval} {
148 lappend glflags $arg
149 set nextisval 0
150 continue
152 switch -glob -- $arg {
153 "-d" -
154 "--date-order" {
155 set vdatemode($n) 1
156 # remove from origargs in case we hit an unknown option
157 set origargs [lreplace $origargs $i $i]
158 incr i -1
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 # These request or affect diff output, which we don't want.
167 # Some could be used to set our defaults for diff display.
168 lappend diffargs $arg
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" {
177 # These cause our parsing of git log's output to fail, or else
178 # they're options we want to set ourselves, so ignore them.
180 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
181 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
182 "--full-history" - "--dense" - "--sparse" -
183 "--follow" - "--left-right" - "--encoding=*" {
184 # These are harmless, and some are even useful
185 lappend glflags $arg
187 "--diff-filter=*" - "--no-merges" - "--unpacked" -
188 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
189 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
190 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
191 "--remove-empty" - "--first-parent" - "--cherry-pick" -
192 "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
193 "--simplify-by-decoration" {
194 # These mean that we get a subset of the commits
195 set filtered 1
196 lappend glflags $arg
198 "-n" {
199 # This appears to be the only one that has a value as a
200 # separate word following it
201 set filtered 1
202 set nextisval 1
203 lappend glflags $arg
205 "--not" - "--all" {
206 lappend revargs $arg
208 "--merge" {
209 set vmergeonly($n) 1
210 # git rev-parse doesn't understand --merge
211 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
213 "-*" {
214 # Other flag arguments including -<n>
215 if {[string is digit -strict [string range $arg 1 end]]} {
216 set filtered 1
217 } else {
218 # a flag argument that we don't recognize;
219 # that means we can't optimize
220 set allknown 0
222 lappend glflags $arg
224 default {
225 # Non-flag arguments specify commits or ranges of commits
226 if {[string match "*...*" $arg]} {
227 lappend revargs --gitk-symmetric-diff-marker
229 lappend revargs $arg
233 set vdflags($n) $diffargs
234 set vflags($n) $glflags
235 set vrevs($n) $revargs
236 set vfiltered($n) $filtered
237 set vorigargs($n) $origargs
238 return $allknown
241 proc parseviewrevs {view revs} {
242 global vposids vnegids
244 if {$revs eq {}} {
245 set revs HEAD
247 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
248 # we get stdout followed by stderr in $err
249 # for an unknown rev, git rev-parse echoes it and then errors out
250 set errlines [split $err "\n"]
251 set badrev {}
252 for {set l 0} {$l < [llength $errlines]} {incr l} {
253 set line [lindex $errlines $l]
254 if {!([string length $line] == 40 && [string is xdigit $line])} {
255 if {[string match "fatal:*" $line]} {
256 if {[string match "fatal: ambiguous argument*" $line]
257 && $badrev ne {}} {
258 if {[llength $badrev] == 1} {
259 set err "unknown revision $badrev"
260 } else {
261 set err "unknown revisions: [join $badrev ", "]"
263 } else {
264 set err [join [lrange $errlines $l end] "\n"]
266 break
268 lappend badrev $line
271 error_popup "[mc "Error parsing revisions:"] $err"
272 return {}
274 set ret {}
275 set pos {}
276 set neg {}
277 set sdm 0
278 foreach id [split $ids "\n"] {
279 if {$id eq "--gitk-symmetric-diff-marker"} {
280 set sdm 4
281 } elseif {[string match "^*" $id]} {
282 if {$sdm != 1} {
283 lappend ret $id
284 if {$sdm == 3} {
285 set sdm 0
288 lappend neg [string range $id 1 end]
289 } else {
290 if {$sdm != 2} {
291 lappend ret $id
292 } else {
293 lset ret end $id...[lindex $ret end]
295 lappend pos $id
297 incr sdm -1
299 set vposids($view) $pos
300 set vnegids($view) $neg
301 return $ret
304 # Start off a git log process and arrange to read its output
305 proc start_rev_list {view} {
306 global startmsecs commitidx viewcomplete curview
307 global tclencoding
308 global viewargs viewargscmd viewfiles vfilelimit
309 global showlocalchanges
310 global viewactive viewinstances vmergeonly
311 global mainheadid viewmainheadid viewmainheadid_orig
312 global vcanopt vflags vrevs vorigargs
314 set startmsecs [clock clicks -milliseconds]
315 set commitidx($view) 0
316 # these are set this way for the error exits
317 set viewcomplete($view) 1
318 set viewactive($view) 0
319 varcinit $view
321 set args $viewargs($view)
322 if {$viewargscmd($view) ne {}} {
323 if {[catch {
324 set str [exec sh -c $viewargscmd($view)]
325 } err]} {
326 error_popup "[mc "Error executing --argscmd command:"] $err"
327 return 0
329 set args [concat $args [split $str "\n"]]
331 set vcanopt($view) [parseviewargs $view $args]
333 set files $viewfiles($view)
334 if {$vmergeonly($view)} {
335 set files [unmerged_files $files]
336 if {$files eq {}} {
337 global nr_unmerged
338 if {$nr_unmerged == 0} {
339 error_popup [mc "No files selected: --merge specified but\
340 no files are unmerged."]
341 } else {
342 error_popup [mc "No files selected: --merge specified but\
343 no unmerged files are within file limit."]
345 return 0
348 set vfilelimit($view) $files
350 if {$vcanopt($view)} {
351 set revs [parseviewrevs $view $vrevs($view)]
352 if {$revs eq {}} {
353 return 0
355 set args [concat $vflags($view) $revs]
356 } else {
357 set args $vorigargs($view)
360 if {[catch {
361 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
362 --boundary $args "--" $files] r]
363 } err]} {
364 error_popup "[mc "Error executing git log:"] $err"
365 return 0
367 set i [reg_instance $fd]
368 set viewinstances($view) [list $i]
369 set viewmainheadid($view) $mainheadid
370 set viewmainheadid_orig($view) $mainheadid
371 if {$files ne {} && $mainheadid ne {}} {
372 get_viewmainhead $view
374 if {$showlocalchanges && $viewmainheadid($view) ne {}} {
375 interestedin $viewmainheadid($view) dodiffindex
377 fconfigure $fd -blocking 0 -translation lf -eofchar {}
378 if {$tclencoding != {}} {
379 fconfigure $fd -encoding $tclencoding
381 filerun $fd [list getcommitlines $fd $i $view 0]
382 nowbusy $view [mc "Reading"]
383 set viewcomplete($view) 0
384 set viewactive($view) 1
385 return 1
388 proc stop_instance {inst} {
389 global commfd leftover
391 set fd $commfd($inst)
392 catch {
393 set pid [pid $fd]
395 if {$::tcl_platform(platform) eq {windows}} {
396 exec kill -f $pid
397 } else {
398 exec kill $pid
401 catch {close $fd}
402 nukefile $fd
403 unset commfd($inst)
404 unset leftover($inst)
407 proc stop_backends {} {
408 global commfd
410 foreach inst [array names commfd] {
411 stop_instance $inst
415 proc stop_rev_list {view} {
416 global viewinstances
418 foreach inst $viewinstances($view) {
419 stop_instance $inst
421 set viewinstances($view) {}
424 proc reset_pending_select {selid} {
425 global pending_select mainheadid selectheadid
427 if {$selid ne {}} {
428 set pending_select $selid
429 } elseif {$selectheadid ne {}} {
430 set pending_select $selectheadid
431 } else {
432 set pending_select $mainheadid
436 proc getcommits {selid} {
437 global canv curview need_redisplay viewactive
439 initlayout
440 if {[start_rev_list $curview]} {
441 reset_pending_select $selid
442 show_status [mc "Reading commits..."]
443 set need_redisplay 1
444 } else {
445 show_status [mc "No commits selected"]
449 proc updatecommits {} {
450 global curview vcanopt vorigargs vfilelimit viewinstances
451 global viewactive viewcomplete tclencoding
452 global startmsecs showneartags showlocalchanges
453 global mainheadid viewmainheadid viewmainheadid_orig pending_select
454 global isworktree
455 global varcid vposids vnegids vflags vrevs
457 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
458 rereadrefs
459 set view $curview
460 if {$mainheadid ne $viewmainheadid_orig($view)} {
461 if {$showlocalchanges} {
462 dohidelocalchanges
464 set viewmainheadid($view) $mainheadid
465 set viewmainheadid_orig($view) $mainheadid
466 if {$vfilelimit($view) ne {}} {
467 get_viewmainhead $view
470 if {$showlocalchanges} {
471 doshowlocalchanges
473 if {$vcanopt($view)} {
474 set oldpos $vposids($view)
475 set oldneg $vnegids($view)
476 set revs [parseviewrevs $view $vrevs($view)]
477 if {$revs eq {}} {
478 return
480 # note: getting the delta when negative refs change is hard,
481 # and could require multiple git log invocations, so in that
482 # case we ask git log for all the commits (not just the delta)
483 if {$oldneg eq $vnegids($view)} {
484 set newrevs {}
485 set npos 0
486 # take out positive refs that we asked for before or
487 # that we have already seen
488 foreach rev $revs {
489 if {[string length $rev] == 40} {
490 if {[lsearch -exact $oldpos $rev] < 0
491 && ![info exists varcid($view,$rev)]} {
492 lappend newrevs $rev
493 incr npos
495 } else {
496 lappend $newrevs $rev
499 if {$npos == 0} return
500 set revs $newrevs
501 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
503 set args [concat $vflags($view) $revs --not $oldpos]
504 } else {
505 set args $vorigargs($view)
507 if {[catch {
508 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
509 --boundary $args "--" $vfilelimit($view)] r]
510 } err]} {
511 error_popup "[mc "Error executing git log:"] $err"
512 return
514 if {$viewactive($view) == 0} {
515 set startmsecs [clock clicks -milliseconds]
517 set i [reg_instance $fd]
518 lappend viewinstances($view) $i
519 fconfigure $fd -blocking 0 -translation lf -eofchar {}
520 if {$tclencoding != {}} {
521 fconfigure $fd -encoding $tclencoding
523 filerun $fd [list getcommitlines $fd $i $view 1]
524 incr viewactive($view)
525 set viewcomplete($view) 0
526 reset_pending_select {}
527 nowbusy $view [mc "Reading"]
528 if {$showneartags} {
529 getallcommits
533 proc reloadcommits {} {
534 global curview viewcomplete selectedline currentid thickerline
535 global showneartags treediffs commitinterest cached_commitrow
536 global targetid
538 set selid {}
539 if {$selectedline ne {}} {
540 set selid $currentid
543 if {!$viewcomplete($curview)} {
544 stop_rev_list $curview
546 resetvarcs $curview
547 set selectedline {}
548 catch {unset currentid}
549 catch {unset thickerline}
550 catch {unset treediffs}
551 readrefs
552 changedrefs
553 if {$showneartags} {
554 getallcommits
556 clear_display
557 catch {unset commitinterest}
558 catch {unset cached_commitrow}
559 catch {unset targetid}
560 setcanvscroll
561 getcommits $selid
562 return 0
565 # This makes a string representation of a positive integer which
566 # sorts as a string in numerical order
567 proc strrep {n} {
568 if {$n < 16} {
569 return [format "%x" $n]
570 } elseif {$n < 256} {
571 return [format "x%.2x" $n]
572 } elseif {$n < 65536} {
573 return [format "y%.4x" $n]
575 return [format "z%.8x" $n]
578 # Procedures used in reordering commits from git log (without
579 # --topo-order) into the order for display.
581 proc varcinit {view} {
582 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
583 global vtokmod varcmod vrowmod varcix vlastins
585 set varcstart($view) {{}}
586 set vupptr($view) {0}
587 set vdownptr($view) {0}
588 set vleftptr($view) {0}
589 set vbackptr($view) {0}
590 set varctok($view) {{}}
591 set varcrow($view) {{}}
592 set vtokmod($view) {}
593 set varcmod($view) 0
594 set vrowmod($view) 0
595 set varcix($view) {{}}
596 set vlastins($view) {0}
599 proc resetvarcs {view} {
600 global varcid varccommits parents children vseedcount ordertok
602 foreach vid [array names varcid $view,*] {
603 unset varcid($vid)
604 unset children($vid)
605 unset parents($vid)
607 # some commits might have children but haven't been seen yet
608 foreach vid [array names children $view,*] {
609 unset children($vid)
611 foreach va [array names varccommits $view,*] {
612 unset varccommits($va)
614 foreach vd [array names vseedcount $view,*] {
615 unset vseedcount($vd)
617 catch {unset ordertok}
620 # returns a list of the commits with no children
621 proc seeds {v} {
622 global vdownptr vleftptr varcstart
624 set ret {}
625 set a [lindex $vdownptr($v) 0]
626 while {$a != 0} {
627 lappend ret [lindex $varcstart($v) $a]
628 set a [lindex $vleftptr($v) $a]
630 return $ret
633 proc newvarc {view id} {
634 global varcid varctok parents children vdatemode
635 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
636 global commitdata commitinfo vseedcount varccommits vlastins
638 set a [llength $varctok($view)]
639 set vid $view,$id
640 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
641 if {![info exists commitinfo($id)]} {
642 parsecommit $id $commitdata($id) 1
644 set cdate [lindex $commitinfo($id) 4]
645 if {![string is integer -strict $cdate]} {
646 set cdate 0
648 if {![info exists vseedcount($view,$cdate)]} {
649 set vseedcount($view,$cdate) -1
651 set c [incr vseedcount($view,$cdate)]
652 set cdate [expr {$cdate ^ 0xffffffff}]
653 set tok "s[strrep $cdate][strrep $c]"
654 } else {
655 set tok {}
657 set ka 0
658 if {[llength $children($vid)] > 0} {
659 set kid [lindex $children($vid) end]
660 set k $varcid($view,$kid)
661 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
662 set ki $kid
663 set ka $k
664 set tok [lindex $varctok($view) $k]
667 if {$ka != 0} {
668 set i [lsearch -exact $parents($view,$ki) $id]
669 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
670 append tok [strrep $j]
672 set c [lindex $vlastins($view) $ka]
673 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
674 set c $ka
675 set b [lindex $vdownptr($view) $ka]
676 } else {
677 set b [lindex $vleftptr($view) $c]
679 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
680 set c $b
681 set b [lindex $vleftptr($view) $c]
683 if {$c == $ka} {
684 lset vdownptr($view) $ka $a
685 lappend vbackptr($view) 0
686 } else {
687 lset vleftptr($view) $c $a
688 lappend vbackptr($view) $c
690 lset vlastins($view) $ka $a
691 lappend vupptr($view) $ka
692 lappend vleftptr($view) $b
693 if {$b != 0} {
694 lset vbackptr($view) $b $a
696 lappend varctok($view) $tok
697 lappend varcstart($view) $id
698 lappend vdownptr($view) 0
699 lappend varcrow($view) {}
700 lappend varcix($view) {}
701 set varccommits($view,$a) {}
702 lappend vlastins($view) 0
703 return $a
706 proc splitvarc {p v} {
707 global varcid varcstart varccommits varctok vtokmod
708 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
710 set oa $varcid($v,$p)
711 set otok [lindex $varctok($v) $oa]
712 set ac $varccommits($v,$oa)
713 set i [lsearch -exact $varccommits($v,$oa) $p]
714 if {$i <= 0} return
715 set na [llength $varctok($v)]
716 # "%" sorts before "0"...
717 set tok "$otok%[strrep $i]"
718 lappend varctok($v) $tok
719 lappend varcrow($v) {}
720 lappend varcix($v) {}
721 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
722 set varccommits($v,$na) [lrange $ac $i end]
723 lappend varcstart($v) $p
724 foreach id $varccommits($v,$na) {
725 set varcid($v,$id) $na
727 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
728 lappend vlastins($v) [lindex $vlastins($v) $oa]
729 lset vdownptr($v) $oa $na
730 lset vlastins($v) $oa 0
731 lappend vupptr($v) $oa
732 lappend vleftptr($v) 0
733 lappend vbackptr($v) 0
734 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
735 lset vupptr($v) $b $na
737 if {[string compare $otok $vtokmod($v)] <= 0} {
738 modify_arc $v $oa
742 proc renumbervarc {a v} {
743 global parents children varctok varcstart varccommits
744 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
746 set t1 [clock clicks -milliseconds]
747 set todo {}
748 set isrelated($a) 1
749 set kidchanged($a) 1
750 set ntot 0
751 while {$a != 0} {
752 if {[info exists isrelated($a)]} {
753 lappend todo $a
754 set id [lindex $varccommits($v,$a) end]
755 foreach p $parents($v,$id) {
756 if {[info exists varcid($v,$p)]} {
757 set isrelated($varcid($v,$p)) 1
761 incr ntot
762 set b [lindex $vdownptr($v) $a]
763 if {$b == 0} {
764 while {$a != 0} {
765 set b [lindex $vleftptr($v) $a]
766 if {$b != 0} break
767 set a [lindex $vupptr($v) $a]
770 set a $b
772 foreach a $todo {
773 if {![info exists kidchanged($a)]} continue
774 set id [lindex $varcstart($v) $a]
775 if {[llength $children($v,$id)] > 1} {
776 set children($v,$id) [lsort -command [list vtokcmp $v] \
777 $children($v,$id)]
779 set oldtok [lindex $varctok($v) $a]
780 if {!$vdatemode($v)} {
781 set tok {}
782 } else {
783 set tok $oldtok
785 set ka 0
786 set kid [last_real_child $v,$id]
787 if {$kid ne {}} {
788 set k $varcid($v,$kid)
789 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
790 set ki $kid
791 set ka $k
792 set tok [lindex $varctok($v) $k]
795 if {$ka != 0} {
796 set i [lsearch -exact $parents($v,$ki) $id]
797 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
798 append tok [strrep $j]
800 if {$tok eq $oldtok} {
801 continue
803 set id [lindex $varccommits($v,$a) end]
804 foreach p $parents($v,$id) {
805 if {[info exists varcid($v,$p)]} {
806 set kidchanged($varcid($v,$p)) 1
807 } else {
808 set sortkids($p) 1
811 lset varctok($v) $a $tok
812 set b [lindex $vupptr($v) $a]
813 if {$b != $ka} {
814 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
815 modify_arc $v $ka
817 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
818 modify_arc $v $b
820 set c [lindex $vbackptr($v) $a]
821 set d [lindex $vleftptr($v) $a]
822 if {$c == 0} {
823 lset vdownptr($v) $b $d
824 } else {
825 lset vleftptr($v) $c $d
827 if {$d != 0} {
828 lset vbackptr($v) $d $c
830 if {[lindex $vlastins($v) $b] == $a} {
831 lset vlastins($v) $b $c
833 lset vupptr($v) $a $ka
834 set c [lindex $vlastins($v) $ka]
835 if {$c == 0 || \
836 [string compare $tok [lindex $varctok($v) $c]] < 0} {
837 set c $ka
838 set b [lindex $vdownptr($v) $ka]
839 } else {
840 set b [lindex $vleftptr($v) $c]
842 while {$b != 0 && \
843 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
844 set c $b
845 set b [lindex $vleftptr($v) $c]
847 if {$c == $ka} {
848 lset vdownptr($v) $ka $a
849 lset vbackptr($v) $a 0
850 } else {
851 lset vleftptr($v) $c $a
852 lset vbackptr($v) $a $c
854 lset vleftptr($v) $a $b
855 if {$b != 0} {
856 lset vbackptr($v) $b $a
858 lset vlastins($v) $ka $a
861 foreach id [array names sortkids] {
862 if {[llength $children($v,$id)] > 1} {
863 set children($v,$id) [lsort -command [list vtokcmp $v] \
864 $children($v,$id)]
867 set t2 [clock clicks -milliseconds]
868 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
871 # Fix up the graph after we have found out that in view $v,
872 # $p (a commit that we have already seen) is actually the parent
873 # of the last commit in arc $a.
874 proc fix_reversal {p a v} {
875 global varcid varcstart varctok vupptr
877 set pa $varcid($v,$p)
878 if {$p ne [lindex $varcstart($v) $pa]} {
879 splitvarc $p $v
880 set pa $varcid($v,$p)
882 # seeds always need to be renumbered
883 if {[lindex $vupptr($v) $pa] == 0 ||
884 [string compare [lindex $varctok($v) $a] \
885 [lindex $varctok($v) $pa]] > 0} {
886 renumbervarc $pa $v
890 proc insertrow {id p v} {
891 global cmitlisted children parents varcid varctok vtokmod
892 global varccommits ordertok commitidx numcommits curview
893 global targetid targetrow
895 readcommit $id
896 set vid $v,$id
897 set cmitlisted($vid) 1
898 set children($vid) {}
899 set parents($vid) [list $p]
900 set a [newvarc $v $id]
901 set varcid($vid) $a
902 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
903 modify_arc $v $a
905 lappend varccommits($v,$a) $id
906 set vp $v,$p
907 if {[llength [lappend children($vp) $id]] > 1} {
908 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
909 catch {unset ordertok}
911 fix_reversal $p $a $v
912 incr commitidx($v)
913 if {$v == $curview} {
914 set numcommits $commitidx($v)
915 setcanvscroll
916 if {[info exists targetid]} {
917 if {![comes_before $targetid $p]} {
918 incr targetrow
924 proc insertfakerow {id p} {
925 global varcid varccommits parents children cmitlisted
926 global commitidx varctok vtokmod targetid targetrow curview numcommits
928 set v $curview
929 set a $varcid($v,$p)
930 set i [lsearch -exact $varccommits($v,$a) $p]
931 if {$i < 0} {
932 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
933 return
935 set children($v,$id) {}
936 set parents($v,$id) [list $p]
937 set varcid($v,$id) $a
938 lappend children($v,$p) $id
939 set cmitlisted($v,$id) 1
940 set numcommits [incr commitidx($v)]
941 # note we deliberately don't update varcstart($v) even if $i == 0
942 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
943 modify_arc $v $a $i
944 if {[info exists targetid]} {
945 if {![comes_before $targetid $p]} {
946 incr targetrow
949 setcanvscroll
950 drawvisible
953 proc removefakerow {id} {
954 global varcid varccommits parents children commitidx
955 global varctok vtokmod cmitlisted currentid selectedline
956 global targetid curview numcommits
958 set v $curview
959 if {[llength $parents($v,$id)] != 1} {
960 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
961 return
963 set p [lindex $parents($v,$id) 0]
964 set a $varcid($v,$id)
965 set i [lsearch -exact $varccommits($v,$a) $id]
966 if {$i < 0} {
967 puts "oops: removefakerow can't find [shortids $id] on arc $a"
968 return
970 unset varcid($v,$id)
971 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
972 unset parents($v,$id)
973 unset children($v,$id)
974 unset cmitlisted($v,$id)
975 set numcommits [incr commitidx($v) -1]
976 set j [lsearch -exact $children($v,$p) $id]
977 if {$j >= 0} {
978 set children($v,$p) [lreplace $children($v,$p) $j $j]
980 modify_arc $v $a $i
981 if {[info exist currentid] && $id eq $currentid} {
982 unset currentid
983 set selectedline {}
985 if {[info exists targetid] && $targetid eq $id} {
986 set targetid $p
988 setcanvscroll
989 drawvisible
992 proc first_real_child {vp} {
993 global children nullid nullid2
995 foreach id $children($vp) {
996 if {$id ne $nullid && $id ne $nullid2} {
997 return $id
1000 return {}
1003 proc last_real_child {vp} {
1004 global children nullid nullid2
1006 set kids $children($vp)
1007 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
1008 set id [lindex $kids $i]
1009 if {$id ne $nullid && $id ne $nullid2} {
1010 return $id
1013 return {}
1016 proc vtokcmp {v a b} {
1017 global varctok varcid
1019 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1020 [lindex $varctok($v) $varcid($v,$b)]]
1023 # This assumes that if lim is not given, the caller has checked that
1024 # arc a's token is less than $vtokmod($v)
1025 proc modify_arc {v a {lim {}}} {
1026 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1028 if {$lim ne {}} {
1029 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1030 if {$c > 0} return
1031 if {$c == 0} {
1032 set r [lindex $varcrow($v) $a]
1033 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1036 set vtokmod($v) [lindex $varctok($v) $a]
1037 set varcmod($v) $a
1038 if {$v == $curview} {
1039 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1040 set a [lindex $vupptr($v) $a]
1041 set lim {}
1043 set r 0
1044 if {$a != 0} {
1045 if {$lim eq {}} {
1046 set lim [llength $varccommits($v,$a)]
1048 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1050 set vrowmod($v) $r
1051 undolayout $r
1055 proc update_arcrows {v} {
1056 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1057 global varcid vrownum varcorder varcix varccommits
1058 global vupptr vdownptr vleftptr varctok
1059 global displayorder parentlist curview cached_commitrow
1061 if {$vrowmod($v) == $commitidx($v)} return
1062 if {$v == $curview} {
1063 if {[llength $displayorder] > $vrowmod($v)} {
1064 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1065 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1067 catch {unset cached_commitrow}
1069 set narctot [expr {[llength $varctok($v)] - 1}]
1070 set a $varcmod($v)
1071 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1072 # go up the tree until we find something that has a row number,
1073 # or we get to a seed
1074 set a [lindex $vupptr($v) $a]
1076 if {$a == 0} {
1077 set a [lindex $vdownptr($v) 0]
1078 if {$a == 0} return
1079 set vrownum($v) {0}
1080 set varcorder($v) [list $a]
1081 lset varcix($v) $a 0
1082 lset varcrow($v) $a 0
1083 set arcn 0
1084 set row 0
1085 } else {
1086 set arcn [lindex $varcix($v) $a]
1087 if {[llength $vrownum($v)] > $arcn + 1} {
1088 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1089 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1091 set row [lindex $varcrow($v) $a]
1093 while {1} {
1094 set p $a
1095 incr row [llength $varccommits($v,$a)]
1096 # go down if possible
1097 set b [lindex $vdownptr($v) $a]
1098 if {$b == 0} {
1099 # if not, go left, or go up until we can go left
1100 while {$a != 0} {
1101 set b [lindex $vleftptr($v) $a]
1102 if {$b != 0} break
1103 set a [lindex $vupptr($v) $a]
1105 if {$a == 0} break
1107 set a $b
1108 incr arcn
1109 lappend vrownum($v) $row
1110 lappend varcorder($v) $a
1111 lset varcix($v) $a $arcn
1112 lset varcrow($v) $a $row
1114 set vtokmod($v) [lindex $varctok($v) $p]
1115 set varcmod($v) $p
1116 set vrowmod($v) $row
1117 if {[info exists currentid]} {
1118 set selectedline [rowofcommit $currentid]
1122 # Test whether view $v contains commit $id
1123 proc commitinview {id v} {
1124 global varcid
1126 return [info exists varcid($v,$id)]
1129 # Return the row number for commit $id in the current view
1130 proc rowofcommit {id} {
1131 global varcid varccommits varcrow curview cached_commitrow
1132 global varctok vtokmod
1134 set v $curview
1135 if {![info exists varcid($v,$id)]} {
1136 puts "oops rowofcommit no arc for [shortids $id]"
1137 return {}
1139 set a $varcid($v,$id)
1140 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1141 update_arcrows $v
1143 if {[info exists cached_commitrow($id)]} {
1144 return $cached_commitrow($id)
1146 set i [lsearch -exact $varccommits($v,$a) $id]
1147 if {$i < 0} {
1148 puts "oops didn't find commit [shortids $id] in arc $a"
1149 return {}
1151 incr i [lindex $varcrow($v) $a]
1152 set cached_commitrow($id) $i
1153 return $i
1156 # Returns 1 if a is on an earlier row than b, otherwise 0
1157 proc comes_before {a b} {
1158 global varcid varctok curview
1160 set v $curview
1161 if {$a eq $b || ![info exists varcid($v,$a)] || \
1162 ![info exists varcid($v,$b)]} {
1163 return 0
1165 if {$varcid($v,$a) != $varcid($v,$b)} {
1166 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1167 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1169 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1172 proc bsearch {l elt} {
1173 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1174 return 0
1176 set lo 0
1177 set hi [llength $l]
1178 while {$hi - $lo > 1} {
1179 set mid [expr {int(($lo + $hi) / 2)}]
1180 set t [lindex $l $mid]
1181 if {$elt < $t} {
1182 set hi $mid
1183 } elseif {$elt > $t} {
1184 set lo $mid
1185 } else {
1186 return $mid
1189 return $lo
1192 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1193 proc make_disporder {start end} {
1194 global vrownum curview commitidx displayorder parentlist
1195 global varccommits varcorder parents vrowmod varcrow
1196 global d_valid_start d_valid_end
1198 if {$end > $vrowmod($curview)} {
1199 update_arcrows $curview
1201 set ai [bsearch $vrownum($curview) $start]
1202 set start [lindex $vrownum($curview) $ai]
1203 set narc [llength $vrownum($curview)]
1204 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1205 set a [lindex $varcorder($curview) $ai]
1206 set l [llength $displayorder]
1207 set al [llength $varccommits($curview,$a)]
1208 if {$l < $r + $al} {
1209 if {$l < $r} {
1210 set pad [ntimes [expr {$r - $l}] {}]
1211 set displayorder [concat $displayorder $pad]
1212 set parentlist [concat $parentlist $pad]
1213 } elseif {$l > $r} {
1214 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1215 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1217 foreach id $varccommits($curview,$a) {
1218 lappend displayorder $id
1219 lappend parentlist $parents($curview,$id)
1221 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1222 set i $r
1223 foreach id $varccommits($curview,$a) {
1224 lset displayorder $i $id
1225 lset parentlist $i $parents($curview,$id)
1226 incr i
1229 incr r $al
1233 proc commitonrow {row} {
1234 global displayorder
1236 set id [lindex $displayorder $row]
1237 if {$id eq {}} {
1238 make_disporder $row [expr {$row + 1}]
1239 set id [lindex $displayorder $row]
1241 return $id
1244 proc closevarcs {v} {
1245 global varctok varccommits varcid parents children
1246 global cmitlisted commitidx vtokmod
1248 set missing_parents 0
1249 set scripts {}
1250 set narcs [llength $varctok($v)]
1251 for {set a 1} {$a < $narcs} {incr a} {
1252 set id [lindex $varccommits($v,$a) end]
1253 foreach p $parents($v,$id) {
1254 if {[info exists varcid($v,$p)]} continue
1255 # add p as a new commit
1256 incr missing_parents
1257 set cmitlisted($v,$p) 0
1258 set parents($v,$p) {}
1259 if {[llength $children($v,$p)] == 1 &&
1260 [llength $parents($v,$id)] == 1} {
1261 set b $a
1262 } else {
1263 set b [newvarc $v $p]
1265 set varcid($v,$p) $b
1266 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1267 modify_arc $v $b
1269 lappend varccommits($v,$b) $p
1270 incr commitidx($v)
1271 set scripts [check_interest $p $scripts]
1274 if {$missing_parents > 0} {
1275 foreach s $scripts {
1276 eval $s
1281 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1282 # Assumes we already have an arc for $rwid.
1283 proc rewrite_commit {v id rwid} {
1284 global children parents varcid varctok vtokmod varccommits
1286 foreach ch $children($v,$id) {
1287 # make $rwid be $ch's parent in place of $id
1288 set i [lsearch -exact $parents($v,$ch) $id]
1289 if {$i < 0} {
1290 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1292 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1293 # add $ch to $rwid's children and sort the list if necessary
1294 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1295 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1296 $children($v,$rwid)]
1298 # fix the graph after joining $id to $rwid
1299 set a $varcid($v,$ch)
1300 fix_reversal $rwid $a $v
1301 # parentlist is wrong for the last element of arc $a
1302 # even if displayorder is right, hence the 3rd arg here
1303 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1307 # Mechanism for registering a command to be executed when we come
1308 # across a particular commit. To handle the case when only the
1309 # prefix of the commit is known, the commitinterest array is now
1310 # indexed by the first 4 characters of the ID. Each element is a
1311 # list of id, cmd pairs.
1312 proc interestedin {id cmd} {
1313 global commitinterest
1315 lappend commitinterest([string range $id 0 3]) $id $cmd
1318 proc check_interest {id scripts} {
1319 global commitinterest
1321 set prefix [string range $id 0 3]
1322 if {[info exists commitinterest($prefix)]} {
1323 set newlist {}
1324 foreach {i script} $commitinterest($prefix) {
1325 if {[string match "$i*" $id]} {
1326 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1327 } else {
1328 lappend newlist $i $script
1331 if {$newlist ne {}} {
1332 set commitinterest($prefix) $newlist
1333 } else {
1334 unset commitinterest($prefix)
1337 return $scripts
1340 proc getcommitlines {fd inst view updating} {
1341 global cmitlisted leftover
1342 global commitidx commitdata vdatemode
1343 global parents children curview hlview
1344 global idpending ordertok
1345 global varccommits varcid varctok vtokmod vfilelimit
1347 set stuff [read $fd 500000]
1348 # git log doesn't terminate the last commit with a null...
1349 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1350 set stuff "\0"
1352 if {$stuff == {}} {
1353 if {![eof $fd]} {
1354 return 1
1356 global commfd viewcomplete viewactive viewname
1357 global viewinstances
1358 unset commfd($inst)
1359 set i [lsearch -exact $viewinstances($view) $inst]
1360 if {$i >= 0} {
1361 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1363 # set it blocking so we wait for the process to terminate
1364 fconfigure $fd -blocking 1
1365 if {[catch {close $fd} err]} {
1366 set fv {}
1367 if {$view != $curview} {
1368 set fv " for the \"$viewname($view)\" view"
1370 if {[string range $err 0 4] == "usage"} {
1371 set err "Gitk: error reading commits$fv:\
1372 bad arguments to git log."
1373 if {$viewname($view) eq "Command line"} {
1374 append err \
1375 " (Note: arguments to gitk are passed to git log\
1376 to allow selection of commits to be displayed.)"
1378 } else {
1379 set err "Error reading commits$fv: $err"
1381 error_popup $err
1383 if {[incr viewactive($view) -1] <= 0} {
1384 set viewcomplete($view) 1
1385 # Check if we have seen any ids listed as parents that haven't
1386 # appeared in the list
1387 closevarcs $view
1388 notbusy $view
1390 if {$view == $curview} {
1391 run chewcommits
1393 return 0
1395 set start 0
1396 set gotsome 0
1397 set scripts {}
1398 while 1 {
1399 set i [string first "\0" $stuff $start]
1400 if {$i < 0} {
1401 append leftover($inst) [string range $stuff $start end]
1402 break
1404 if {$start == 0} {
1405 set cmit $leftover($inst)
1406 append cmit [string range $stuff 0 [expr {$i - 1}]]
1407 set leftover($inst) {}
1408 } else {
1409 set cmit [string range $stuff $start [expr {$i - 1}]]
1411 set start [expr {$i + 1}]
1412 set j [string first "\n" $cmit]
1413 set ok 0
1414 set listed 1
1415 if {$j >= 0 && [string match "commit *" $cmit]} {
1416 set ids [string range $cmit 7 [expr {$j - 1}]]
1417 if {[string match {[-^<>]*} $ids]} {
1418 switch -- [string index $ids 0] {
1419 "-" {set listed 0}
1420 "^" {set listed 2}
1421 "<" {set listed 3}
1422 ">" {set listed 4}
1424 set ids [string range $ids 1 end]
1426 set ok 1
1427 foreach id $ids {
1428 if {[string length $id] != 40} {
1429 set ok 0
1430 break
1434 if {!$ok} {
1435 set shortcmit $cmit
1436 if {[string length $shortcmit] > 80} {
1437 set shortcmit "[string range $shortcmit 0 80]..."
1439 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1440 exit 1
1442 set id [lindex $ids 0]
1443 set vid $view,$id
1445 if {!$listed && $updating && ![info exists varcid($vid)] &&
1446 $vfilelimit($view) ne {}} {
1447 # git log doesn't rewrite parents for unlisted commits
1448 # when doing path limiting, so work around that here
1449 # by working out the rewritten parent with git rev-list
1450 # and if we already know about it, using the rewritten
1451 # parent as a substitute parent for $id's children.
1452 if {![catch {
1453 set rwid [exec git rev-list --first-parent --max-count=1 \
1454 $id -- $vfilelimit($view)]
1455 }]} {
1456 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1457 # use $rwid in place of $id
1458 rewrite_commit $view $id $rwid
1459 continue
1464 set a 0
1465 if {[info exists varcid($vid)]} {
1466 if {$cmitlisted($vid) || !$listed} continue
1467 set a $varcid($vid)
1469 if {$listed} {
1470 set olds [lrange $ids 1 end]
1471 } else {
1472 set olds {}
1474 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1475 set cmitlisted($vid) $listed
1476 set parents($vid) $olds
1477 if {![info exists children($vid)]} {
1478 set children($vid) {}
1479 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1480 set k [lindex $children($vid) 0]
1481 if {[llength $parents($view,$k)] == 1 &&
1482 (!$vdatemode($view) ||
1483 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1484 set a $varcid($view,$k)
1487 if {$a == 0} {
1488 # new arc
1489 set a [newvarc $view $id]
1491 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1492 modify_arc $view $a
1494 if {![info exists varcid($vid)]} {
1495 set varcid($vid) $a
1496 lappend varccommits($view,$a) $id
1497 incr commitidx($view)
1500 set i 0
1501 foreach p $olds {
1502 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1503 set vp $view,$p
1504 if {[llength [lappend children($vp) $id]] > 1 &&
1505 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1506 set children($vp) [lsort -command [list vtokcmp $view] \
1507 $children($vp)]
1508 catch {unset ordertok}
1510 if {[info exists varcid($view,$p)]} {
1511 fix_reversal $p $a $view
1514 incr i
1517 set scripts [check_interest $id $scripts]
1518 set gotsome 1
1520 if {$gotsome} {
1521 global numcommits hlview
1523 if {$view == $curview} {
1524 set numcommits $commitidx($view)
1525 run chewcommits
1527 if {[info exists hlview] && $view == $hlview} {
1528 # we never actually get here...
1529 run vhighlightmore
1531 foreach s $scripts {
1532 eval $s
1535 return 2
1538 proc chewcommits {} {
1539 global curview hlview viewcomplete
1540 global pending_select
1542 layoutmore
1543 if {$viewcomplete($curview)} {
1544 global commitidx varctok
1545 global numcommits startmsecs
1547 if {[info exists pending_select]} {
1548 update
1549 reset_pending_select {}
1551 if {[commitinview $pending_select $curview]} {
1552 selectline [rowofcommit $pending_select] 1
1553 } else {
1554 set row [first_real_row]
1555 selectline $row 1
1558 if {$commitidx($curview) > 0} {
1559 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1560 #puts "overall $ms ms for $numcommits commits"
1561 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1562 } else {
1563 show_status [mc "No commits selected"]
1565 notbusy layout
1567 return 0
1570 proc do_readcommit {id} {
1571 global tclencoding
1573 # Invoke git-log to handle automatic encoding conversion
1574 set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1575 # Read the results using i18n.logoutputencoding
1576 fconfigure $fd -translation lf -eofchar {}
1577 if {$tclencoding != {}} {
1578 fconfigure $fd -encoding $tclencoding
1580 set contents [read $fd]
1581 close $fd
1582 # Remove the heading line
1583 regsub {^commit [0-9a-f]+\n} $contents {} contents
1585 return $contents
1588 proc readcommit {id} {
1589 if {[catch {set contents [do_readcommit $id]}]} return
1590 parsecommit $id $contents 1
1593 proc parsecommit {id contents listed} {
1594 global commitinfo cdate
1596 set inhdr 1
1597 set comment {}
1598 set headline {}
1599 set auname {}
1600 set audate {}
1601 set comname {}
1602 set comdate {}
1603 set hdrend [string first "\n\n" $contents]
1604 if {$hdrend < 0} {
1605 # should never happen...
1606 set hdrend [string length $contents]
1608 set header [string range $contents 0 [expr {$hdrend - 1}]]
1609 set comment [string range $contents [expr {$hdrend + 2}] end]
1610 foreach line [split $header "\n"] {
1611 set line [split $line " "]
1612 set tag [lindex $line 0]
1613 if {$tag == "author"} {
1614 set audate [lindex $line end-1]
1615 set auname [join [lrange $line 1 end-2] " "]
1616 } elseif {$tag == "committer"} {
1617 set comdate [lindex $line end-1]
1618 set comname [join [lrange $line 1 end-2] " "]
1621 set headline {}
1622 # take the first non-blank line of the comment as the headline
1623 set headline [string trimleft $comment]
1624 set i [string first "\n" $headline]
1625 if {$i >= 0} {
1626 set headline [string range $headline 0 $i]
1628 set headline [string trimright $headline]
1629 set i [string first "\r" $headline]
1630 if {$i >= 0} {
1631 set headline [string trimright [string range $headline 0 $i]]
1633 if {!$listed} {
1634 # git log indents the comment by 4 spaces;
1635 # if we got this via git cat-file, add the indentation
1636 set newcomment {}
1637 foreach line [split $comment "\n"] {
1638 append newcomment " "
1639 append newcomment $line
1640 append newcomment "\n"
1642 set comment $newcomment
1644 if {$comdate != {}} {
1645 set cdate($id) $comdate
1647 set commitinfo($id) [list $headline $auname $audate \
1648 $comname $comdate $comment]
1651 proc getcommit {id} {
1652 global commitdata commitinfo
1654 if {[info exists commitdata($id)]} {
1655 parsecommit $id $commitdata($id) 1
1656 } else {
1657 readcommit $id
1658 if {![info exists commitinfo($id)]} {
1659 set commitinfo($id) [list [mc "No commit information available"]]
1662 return 1
1665 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1666 # and are present in the current view.
1667 # This is fairly slow...
1668 proc longid {prefix} {
1669 global varcid curview
1671 set ids {}
1672 foreach match [array names varcid "$curview,$prefix*"] {
1673 lappend ids [lindex [split $match ","] 1]
1675 return $ids
1678 proc readrefs {} {
1679 global tagids idtags headids idheads tagobjid
1680 global otherrefids idotherrefs mainhead mainheadid
1681 global selecthead selectheadid
1682 global hideremotes
1684 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1685 catch {unset $v}
1687 set refd [open [list | git show-ref -d] r]
1688 while {[gets $refd line] >= 0} {
1689 if {[string index $line 40] ne " "} continue
1690 set id [string range $line 0 39]
1691 set ref [string range $line 41 end]
1692 if {![string match "refs/*" $ref]} continue
1693 set name [string range $ref 5 end]
1694 if {[string match "remotes/*" $name]} {
1695 if {![string match "*/HEAD" $name] && !$hideremotes} {
1696 set headids($name) $id
1697 lappend idheads($id) $name
1699 } elseif {[string match "heads/*" $name]} {
1700 set name [string range $name 6 end]
1701 set headids($name) $id
1702 lappend idheads($id) $name
1703 } elseif {[string match "tags/*" $name]} {
1704 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1705 # which is what we want since the former is the commit ID
1706 set name [string range $name 5 end]
1707 if {[string match "*^{}" $name]} {
1708 set name [string range $name 0 end-3]
1709 } else {
1710 set tagobjid($name) $id
1712 set tagids($name) $id
1713 lappend idtags($id) $name
1714 } else {
1715 set otherrefids($name) $id
1716 lappend idotherrefs($id) $name
1719 catch {close $refd}
1720 set mainhead {}
1721 set mainheadid {}
1722 catch {
1723 set mainheadid [exec git rev-parse HEAD]
1724 set thehead [exec git symbolic-ref HEAD]
1725 if {[string match "refs/heads/*" $thehead]} {
1726 set mainhead [string range $thehead 11 end]
1729 set selectheadid {}
1730 if {$selecthead ne {}} {
1731 catch {
1732 set selectheadid [exec git rev-parse --verify $selecthead]
1737 # skip over fake commits
1738 proc first_real_row {} {
1739 global nullid nullid2 numcommits
1741 for {set row 0} {$row < $numcommits} {incr row} {
1742 set id [commitonrow $row]
1743 if {$id ne $nullid && $id ne $nullid2} {
1744 break
1747 return $row
1750 # update things for a head moved to a child of its previous location
1751 proc movehead {id name} {
1752 global headids idheads
1754 removehead $headids($name) $name
1755 set headids($name) $id
1756 lappend idheads($id) $name
1759 # update things when a head has been removed
1760 proc removehead {id name} {
1761 global headids idheads
1763 if {$idheads($id) eq $name} {
1764 unset idheads($id)
1765 } else {
1766 set i [lsearch -exact $idheads($id) $name]
1767 if {$i >= 0} {
1768 set idheads($id) [lreplace $idheads($id) $i $i]
1771 unset headids($name)
1774 proc ttk_toplevel {w args} {
1775 global use_ttk
1776 eval [linsert $args 0 ::toplevel $w]
1777 if {$use_ttk} {
1778 place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
1780 return $w
1783 proc make_transient {window origin} {
1784 global have_tk85
1786 # In MacOS Tk 8.4 transient appears to work by setting
1787 # overrideredirect, which is utterly useless, since the
1788 # windows get no border, and are not even kept above
1789 # the parent.
1790 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1792 wm transient $window $origin
1794 # Windows fails to place transient windows normally, so
1795 # schedule a callback to center them on the parent.
1796 if {[tk windowingsystem] eq {win32}} {
1797 after idle [list tk::PlaceWindow $window widget $origin]
1801 proc show_error {w top msg} {
1802 global NS
1803 if {![info exists NS]} {set NS ""}
1804 if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
1805 message $w.m -text $msg -justify center -aspect 400
1806 pack $w.m -side top -fill x -padx 20 -pady 20
1807 ${NS}::button $w.ok -default active -text [mc OK] -command "destroy $top"
1808 pack $w.ok -side bottom -fill x
1809 bind $top <Visibility> "grab $top; focus $top"
1810 bind $top <Key-Return> "destroy $top"
1811 bind $top <Key-space> "destroy $top"
1812 bind $top <Key-Escape> "destroy $top"
1813 tkwait window $top
1816 proc error_popup {msg {owner .}} {
1817 if {[tk windowingsystem] eq "win32"} {
1818 tk_messageBox -icon error -type ok -title [wm title .] \
1819 -parent $owner -message $msg
1820 } else {
1821 set w .error
1822 ttk_toplevel $w
1823 make_transient $w $owner
1824 show_error $w $w $msg
1828 proc confirm_popup {msg {owner .}} {
1829 global confirm_ok NS
1830 set confirm_ok 0
1831 set w .confirm
1832 ttk_toplevel $w
1833 make_transient $w $owner
1834 message $w.m -text $msg -justify center -aspect 400
1835 pack $w.m -side top -fill x -padx 20 -pady 20
1836 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1837 pack $w.ok -side left -fill x
1838 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
1839 pack $w.cancel -side right -fill x
1840 bind $w <Visibility> "grab $w; focus $w"
1841 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1842 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1843 bind $w <Key-Escape> "destroy $w"
1844 tk::PlaceWindow $w widget $owner
1845 tkwait window $w
1846 return $confirm_ok
1849 proc setoptions {} {
1850 if {[tk windowingsystem] ne "win32"} {
1851 option add *Panedwindow.showHandle 1 startupFile
1852 option add *Panedwindow.sashRelief raised startupFile
1853 if {[tk windowingsystem] ne "aqua"} {
1854 option add *Menu.font uifont startupFile
1856 } else {
1857 option add *Menu.TearOff 0 startupFile
1859 option add *Button.font uifont startupFile
1860 option add *Checkbutton.font uifont startupFile
1861 option add *Radiobutton.font uifont startupFile
1862 option add *Menubutton.font uifont startupFile
1863 option add *Label.font uifont startupFile
1864 option add *Message.font uifont startupFile
1865 option add *Entry.font uifont startupFile
1866 option add *Labelframe.font uifont startupFile
1869 # Make a menu and submenus.
1870 # m is the window name for the menu, items is the list of menu items to add.
1871 # Each item is a list {mc label type description options...}
1872 # mc is ignored; it's so we can put mc there to alert xgettext
1873 # label is the string that appears in the menu
1874 # type is cascade, command or radiobutton (should add checkbutton)
1875 # description depends on type; it's the sublist for cascade, the
1876 # command to invoke for command, or {variable value} for radiobutton
1877 proc makemenu {m items} {
1878 menu $m
1879 if {[tk windowingsystem] eq {aqua}} {
1880 set Meta1 Cmd
1881 } else {
1882 set Meta1 Ctrl
1884 foreach i $items {
1885 set name [mc [lindex $i 1]]
1886 set type [lindex $i 2]
1887 set thing [lindex $i 3]
1888 set params [list $type]
1889 if {$name ne {}} {
1890 set u [string first "&" [string map {&& x} $name]]
1891 lappend params -label [string map {&& & & {}} $name]
1892 if {$u >= 0} {
1893 lappend params -underline $u
1896 switch -- $type {
1897 "cascade" {
1898 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1899 lappend params -menu $m.$submenu
1901 "command" {
1902 lappend params -command $thing
1904 "radiobutton" {
1905 lappend params -variable [lindex $thing 0] \
1906 -value [lindex $thing 1]
1909 set tail [lrange $i 4 end]
1910 regsub -all {\yMeta1\y} $tail $Meta1 tail
1911 eval $m add $params $tail
1912 if {$type eq "cascade"} {
1913 makemenu $m.$submenu $thing
1918 # translate string and remove ampersands
1919 proc mca {str} {
1920 return [string map {&& & & {}} [mc $str]]
1923 proc makedroplist {w varname args} {
1924 global use_ttk
1925 if {$use_ttk} {
1926 set width 0
1927 foreach label $args {
1928 set cx [string length $label]
1929 if {$cx > $width} {set width $cx}
1931 set gm [ttk::combobox $w -width $width -state readonly\
1932 -textvariable $varname -values $args]
1933 } else {
1934 set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
1936 return $gm
1939 proc makewindow {} {
1940 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1941 global tabstop
1942 global findtype findtypemenu findloc findstring fstring geometry
1943 global entries sha1entry sha1string sha1but
1944 global diffcontextstring diffcontext
1945 global ignorespace
1946 global maincursor textcursor curtextcursor
1947 global rowctxmenu fakerowmenu mergemax wrapcomment
1948 global highlight_files gdttype
1949 global searchstring sstring
1950 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1951 global headctxmenu progresscanv progressitem progresscoords statusw
1952 global fprogitem fprogcoord lastprogupdate progupdatepending
1953 global rprogitem rprogcoord rownumsel numcommits
1954 global have_tk85 use_ttk NS
1956 # The "mc" arguments here are purely so that xgettext
1957 # sees the following string as needing to be translated
1958 set file {
1959 mc "File" cascade {
1960 {mc "Update" command updatecommits -accelerator F5}
1961 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
1962 {mc "Reread references" command rereadrefs}
1963 {mc "List references" command showrefs -accelerator F2}
1964 {xx "" separator}
1965 {mc "Start git gui" command {exec git gui &}}
1966 {xx "" separator}
1967 {mc "Quit" command doquit -accelerator Meta1-Q}
1969 set edit {
1970 mc "Edit" cascade {
1971 {mc "Preferences" command doprefs}
1973 set view {
1974 mc "View" cascade {
1975 {mc "New view..." command {newview 0} -accelerator Shift-F4}
1976 {mc "Edit view..." command editview -state disabled -accelerator F4}
1977 {mc "Delete view" command delview -state disabled}
1978 {xx "" separator}
1979 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1981 if {[tk windowingsystem] ne "aqua"} {
1982 set help {
1983 mc "Help" cascade {
1984 {mc "About gitk" command about}
1985 {mc "Key bindings" command keys}
1987 set bar [list $file $edit $view $help]
1988 } else {
1989 proc ::tk::mac::ShowPreferences {} {doprefs}
1990 proc ::tk::mac::Quit {} {doquit}
1991 lset file end [lreplace [lindex $file end] end-1 end]
1992 set apple {
1993 xx "Apple" cascade {
1994 {mc "About gitk" command about}
1995 {xx "" separator}
1997 set help {
1998 mc "Help" cascade {
1999 {mc "Key bindings" command keys}
2001 set bar [list $apple $file $view $help]
2003 makemenu .bar $bar
2004 . configure -menu .bar
2006 if {$use_ttk} {
2007 # cover the non-themed toplevel with a themed frame.
2008 place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
2011 # the gui has upper and lower half, parts of a paned window.
2012 ${NS}::panedwindow .ctop -orient vertical
2014 # possibly use assumed geometry
2015 if {![info exists geometry(pwsash0)]} {
2016 set geometry(topheight) [expr {15 * $linespc}]
2017 set geometry(topwidth) [expr {80 * $charspc}]
2018 set geometry(botheight) [expr {15 * $linespc}]
2019 set geometry(botwidth) [expr {50 * $charspc}]
2020 set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
2021 set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
2024 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
2025 ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
2026 ${NS}::frame .tf.histframe
2027 ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
2028 if {!$use_ttk} {
2029 .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
2032 # create three canvases
2033 set cscroll .tf.histframe.csb
2034 set canv .tf.histframe.pwclist.canv
2035 canvas $canv \
2036 -selectbackground $selectbgcolor \
2037 -background $bgcolor -bd 0 \
2038 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
2039 .tf.histframe.pwclist add $canv
2040 set canv2 .tf.histframe.pwclist.canv2
2041 canvas $canv2 \
2042 -selectbackground $selectbgcolor \
2043 -background $bgcolor -bd 0 -yscrollincr $linespc
2044 .tf.histframe.pwclist add $canv2
2045 set canv3 .tf.histframe.pwclist.canv3
2046 canvas $canv3 \
2047 -selectbackground $selectbgcolor \
2048 -background $bgcolor -bd 0 -yscrollincr $linespc
2049 .tf.histframe.pwclist add $canv3
2050 if {$use_ttk} {
2051 bind .tf.histframe.pwclist <Map> {
2052 bind %W <Map> {}
2053 .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
2054 .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
2056 } else {
2057 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
2058 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
2061 # a scroll bar to rule them
2062 ${NS}::scrollbar $cscroll -command {allcanvs yview}
2063 if {!$use_ttk} {$cscroll configure -highlightthickness 0}
2064 pack $cscroll -side right -fill y
2065 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
2066 lappend bglist $canv $canv2 $canv3
2067 pack .tf.histframe.pwclist -fill both -expand 1 -side left
2069 # we have two button bars at bottom of top frame. Bar 1
2070 ${NS}::frame .tf.bar
2071 ${NS}::frame .tf.lbar -height 15
2073 set sha1entry .tf.bar.sha1
2074 set entries $sha1entry
2075 set sha1but .tf.bar.sha1label
2076 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
2077 -command gotocommit -width 8
2078 $sha1but conf -disabledforeground [$sha1but cget -foreground]
2079 pack .tf.bar.sha1label -side left
2080 ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
2081 trace add variable sha1string write sha1change
2082 pack $sha1entry -side left -pady 2
2084 image create bitmap bm-left -data {
2085 #define left_width 16
2086 #define left_height 16
2087 static unsigned char left_bits[] = {
2088 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
2089 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
2090 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
2092 image create bitmap bm-right -data {
2093 #define right_width 16
2094 #define right_height 16
2095 static unsigned char right_bits[] = {
2096 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2097 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2098 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2100 ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
2101 -state disabled -width 26
2102 pack .tf.bar.leftbut -side left -fill y
2103 ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
2104 -state disabled -width 26
2105 pack .tf.bar.rightbut -side left -fill y
2107 ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
2108 set rownumsel {}
2109 ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
2110 -relief sunken -anchor e
2111 ${NS}::label .tf.bar.rowlabel2 -text "/"
2112 ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
2113 -relief sunken -anchor e
2114 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2115 -side left
2116 if {!$use_ttk} {
2117 foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
2119 global selectedline
2120 trace add variable selectedline write selectedline_change
2122 # Status label and progress bar
2123 set statusw .tf.bar.status
2124 ${NS}::label $statusw -width 15 -relief sunken
2125 pack $statusw -side left -padx 5
2126 if {$use_ttk} {
2127 set progresscanv [ttk::progressbar .tf.bar.progress]
2128 } else {
2129 set h [expr {[font metrics uifont -linespace] + 2}]
2130 set progresscanv .tf.bar.progress
2131 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2132 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2133 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2134 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2136 pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
2137 set progresscoords {0 0}
2138 set fprogcoord 0
2139 set rprogcoord 0
2140 bind $progresscanv <Configure> adjustprogress
2141 set lastprogupdate [clock clicks -milliseconds]
2142 set progupdatepending 0
2144 # build up the bottom bar of upper window
2145 ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
2146 ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2147 ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2148 ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
2149 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2150 -side left -fill y
2151 set gdttype [mc "containing:"]
2152 set gm [makedroplist .tf.lbar.gdttype gdttype \
2153 [mc "containing:"] \
2154 [mc "touching paths:"] \
2155 [mc "adding/removing string:"]]
2156 trace add variable gdttype write gdttype_change
2157 pack .tf.lbar.gdttype -side left -fill y
2159 set findstring {}
2160 set fstring .tf.lbar.findstring
2161 lappend entries $fstring
2162 ${NS}::entry $fstring -width 30 -font textfont -textvariable findstring
2163 trace add variable findstring write find_change
2164 set findtype [mc "Exact"]
2165 set findtypemenu [makedroplist .tf.lbar.findtype \
2166 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2167 trace add variable findtype write findcom_change
2168 set findloc [mc "All fields"]
2169 makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2170 [mc "Comments"] [mc "Author"] [mc "Committer"]
2171 trace add variable findloc write find_change
2172 pack .tf.lbar.findloc -side right
2173 pack .tf.lbar.findtype -side right
2174 pack $fstring -side left -expand 1 -fill x
2176 # Finish putting the upper half of the viewer together
2177 pack .tf.lbar -in .tf -side bottom -fill x
2178 pack .tf.bar -in .tf -side bottom -fill x
2179 pack .tf.histframe -fill both -side top -expand 1
2180 .ctop add .tf
2181 if {!$use_ttk} {
2182 .ctop paneconfigure .tf -height $geometry(topheight)
2183 .ctop paneconfigure .tf -width $geometry(topwidth)
2186 # now build up the bottom
2187 ${NS}::panedwindow .pwbottom -orient horizontal
2189 # lower left, a text box over search bar, scroll bar to the right
2190 # if we know window height, then that will set the lower text height, otherwise
2191 # we set lower text height which will drive window height
2192 if {[info exists geometry(main)]} {
2193 ${NS}::frame .bleft -width $geometry(botwidth)
2194 } else {
2195 ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2197 ${NS}::frame .bleft.top
2198 ${NS}::frame .bleft.mid
2199 ${NS}::frame .bleft.bottom
2201 ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
2202 pack .bleft.top.search -side left -padx 5
2203 set sstring .bleft.top.sstring
2204 set searchstring ""
2205 ${NS}::entry $sstring -width 20 -font textfont -textvariable searchstring
2206 lappend entries $sstring
2207 trace add variable searchstring write incrsearch
2208 pack $sstring -side left -expand 1 -fill x
2209 ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
2210 -command changediffdisp -variable diffelide -value {0 0}
2211 ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
2212 -command changediffdisp -variable diffelide -value {0 1}
2213 ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
2214 -command changediffdisp -variable diffelide -value {1 0}
2215 ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2216 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2217 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2218 -from 0 -increment 1 -to 10000000 \
2219 -validate all -validatecommand "diffcontextvalidate %P" \
2220 -textvariable diffcontextstring
2221 .bleft.mid.diffcontext set $diffcontext
2222 trace add variable diffcontextstring write diffcontextchange
2223 lappend entries .bleft.mid.diffcontext
2224 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2225 ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2226 -command changeignorespace -variable ignorespace
2227 pack .bleft.mid.ignspace -side left -padx 5
2228 set ctext .bleft.bottom.ctext
2229 text $ctext -background $bgcolor -foreground $fgcolor \
2230 -state disabled -font textfont \
2231 -yscrollcommand scrolltext -wrap none \
2232 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2233 if {$have_tk85} {
2234 $ctext conf -tabstyle wordprocessor
2236 ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
2237 ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
2238 pack .bleft.top -side top -fill x
2239 pack .bleft.mid -side top -fill x
2240 grid $ctext .bleft.bottom.sb -sticky nsew
2241 grid .bleft.bottom.sbhorizontal -sticky ew
2242 grid columnconfigure .bleft.bottom 0 -weight 1
2243 grid rowconfigure .bleft.bottom 0 -weight 1
2244 grid rowconfigure .bleft.bottom 1 -weight 0
2245 pack .bleft.bottom -side top -fill both -expand 1
2246 lappend bglist $ctext
2247 lappend fglist $ctext
2249 $ctext tag conf comment -wrap $wrapcomment
2250 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2251 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2252 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2253 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2254 $ctext tag conf m0 -fore red
2255 $ctext tag conf m1 -fore blue
2256 $ctext tag conf m2 -fore green
2257 $ctext tag conf m3 -fore purple
2258 $ctext tag conf m4 -fore brown
2259 $ctext tag conf m5 -fore "#009090"
2260 $ctext tag conf m6 -fore magenta
2261 $ctext tag conf m7 -fore "#808000"
2262 $ctext tag conf m8 -fore "#009000"
2263 $ctext tag conf m9 -fore "#ff0080"
2264 $ctext tag conf m10 -fore cyan
2265 $ctext tag conf m11 -fore "#b07070"
2266 $ctext tag conf m12 -fore "#70b0f0"
2267 $ctext tag conf m13 -fore "#70f0b0"
2268 $ctext tag conf m14 -fore "#f0b070"
2269 $ctext tag conf m15 -fore "#ff70b0"
2270 $ctext tag conf mmax -fore darkgrey
2271 set mergemax 16
2272 $ctext tag conf mresult -font textfontbold
2273 $ctext tag conf msep -font textfontbold
2274 $ctext tag conf found -back yellow
2276 .pwbottom add .bleft
2277 if {!$use_ttk} {
2278 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2281 # lower right
2282 ${NS}::frame .bright
2283 ${NS}::frame .bright.mode
2284 ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
2285 -command reselectline -variable cmitmode -value "patch"
2286 ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
2287 -command reselectline -variable cmitmode -value "tree"
2288 grid .bright.mode.patch .bright.mode.tree -sticky ew
2289 pack .bright.mode -side top -fill x
2290 set cflist .bright.cfiles
2291 set indent [font measure mainfont "nn"]
2292 text $cflist \
2293 -selectbackground $selectbgcolor \
2294 -background $bgcolor -foreground $fgcolor \
2295 -font mainfont \
2296 -tabs [list $indent [expr {2 * $indent}]] \
2297 -yscrollcommand ".bright.sb set" \
2298 -cursor [. cget -cursor] \
2299 -spacing1 1 -spacing3 1
2300 lappend bglist $cflist
2301 lappend fglist $cflist
2302 ${NS}::scrollbar .bright.sb -command "$cflist yview"
2303 pack .bright.sb -side right -fill y
2304 pack $cflist -side left -fill both -expand 1
2305 $cflist tag configure highlight \
2306 -background [$cflist cget -selectbackground]
2307 $cflist tag configure bold -font mainfontbold
2309 .pwbottom add .bright
2310 .ctop add .pwbottom
2312 # restore window width & height if known
2313 if {[info exists geometry(main)]} {
2314 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2315 if {$w > [winfo screenwidth .]} {
2316 set w [winfo screenwidth .]
2318 if {$h > [winfo screenheight .]} {
2319 set h [winfo screenheight .]
2321 wm geometry . "${w}x$h"
2325 if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
2326 wm state . $geometry(state)
2329 if {[tk windowingsystem] eq {aqua}} {
2330 set M1B M1
2331 set ::BM "3"
2332 } else {
2333 set M1B Control
2334 set ::BM "2"
2337 if {$use_ttk} {
2338 bind .ctop <Map> {
2339 bind %W <Map> {}
2340 %W sashpos 0 $::geometry(topheight)
2342 bind .pwbottom <Map> {
2343 bind %W <Map> {}
2344 %W sashpos 0 $::geometry(botwidth)
2348 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2349 pack .ctop -fill both -expand 1
2350 bindall <1> {selcanvline %W %x %y}
2351 #bindall <B1-Motion> {selcanvline %W %x %y}
2352 if {[tk windowingsystem] == "win32"} {
2353 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2354 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2355 } else {
2356 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2357 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2358 if {[tk windowingsystem] eq "aqua"} {
2359 bindall <MouseWheel> {
2360 set delta [expr {- (%D)}]
2361 allcanvs yview scroll $delta units
2363 bindall <Shift-MouseWheel> {
2364 set delta [expr {- (%D)}]
2365 $canv xview scroll $delta units
2369 bindall <$::BM> "canvscan mark %W %x %y"
2370 bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
2371 bindkey <Home> selfirstline
2372 bindkey <End> sellastline
2373 bind . <Key-Up> "selnextline -1"
2374 bind . <Key-Down> "selnextline 1"
2375 bind . <Shift-Key-Up> "dofind -1 0"
2376 bind . <Shift-Key-Down> "dofind 1 0"
2377 bindkey <Key-Right> "goforw"
2378 bindkey <Key-Left> "goback"
2379 bind . <Key-Prior> "selnextpage -1"
2380 bind . <Key-Next> "selnextpage 1"
2381 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2382 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2383 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2384 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2385 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2386 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2387 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2388 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2389 bindkey <Key-space> "$ctext yview scroll 1 pages"
2390 bindkey p "selnextline -1"
2391 bindkey n "selnextline 1"
2392 bindkey z "goback"
2393 bindkey x "goforw"
2394 bindkey i "selnextline -1"
2395 bindkey k "selnextline 1"
2396 bindkey j "goback"
2397 bindkey l "goforw"
2398 bindkey b prevfile
2399 bindkey d "$ctext yview scroll 18 units"
2400 bindkey u "$ctext yview scroll -18 units"
2401 bindkey / {focus $fstring}
2402 bindkey <Key-KP_Divide> {focus $fstring}
2403 bindkey <Key-Return> {dofind 1 1}
2404 bindkey ? {dofind -1 1}
2405 bindkey f nextfile
2406 bind . <F5> updatecommits
2407 bind . <$M1B-F5> reloadcommits
2408 bind . <F2> showrefs
2409 bind . <Shift-F4> {newview 0}
2410 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2411 bind . <F4> edit_or_newview
2412 bind . <$M1B-q> doquit
2413 bind . <$M1B-f> {dofind 1 1}
2414 bind . <$M1B-g> {dofind 1 0}
2415 bind . <$M1B-r> dosearchback
2416 bind . <$M1B-s> dosearch
2417 bind . <$M1B-equal> {incrfont 1}
2418 bind . <$M1B-plus> {incrfont 1}
2419 bind . <$M1B-KP_Add> {incrfont 1}
2420 bind . <$M1B-minus> {incrfont -1}
2421 bind . <$M1B-KP_Subtract> {incrfont -1}
2422 wm protocol . WM_DELETE_WINDOW doquit
2423 bind . <Destroy> {stop_backends}
2424 bind . <Button-1> "click %W"
2425 bind $fstring <Key-Return> {dofind 1 1}
2426 bind $sha1entry <Key-Return> {gotocommit; break}
2427 bind $sha1entry <<PasteSelection>> clearsha1
2428 bind $cflist <1> {sel_flist %W %x %y; break}
2429 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2430 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2431 global ctxbut
2432 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2433 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2435 set maincursor [. cget -cursor]
2436 set textcursor [$ctext cget -cursor]
2437 set curtextcursor $textcursor
2439 set rowctxmenu .rowctxmenu
2440 makemenu $rowctxmenu {
2441 {mc "Diff this -> selected" command {diffvssel 0}}
2442 {mc "Diff selected -> this" command {diffvssel 1}}
2443 {mc "Make patch" command mkpatch}
2444 {mc "Create tag" command mktag}
2445 {mc "Write commit to file" command writecommit}
2446 {mc "Create new branch" command mkbranch}
2447 {mc "Cherry-pick this commit" command cherrypick}
2448 {mc "Reset HEAD branch to here" command resethead}
2449 {mc "Mark this commit" command markhere}
2450 {mc "Return to mark" command gotomark}
2451 {mc "Find descendant of this and mark" command find_common_desc}
2452 {mc "Compare with marked commit" command compare_commits}
2454 $rowctxmenu configure -tearoff 0
2456 set fakerowmenu .fakerowmenu
2457 makemenu $fakerowmenu {
2458 {mc "Diff this -> selected" command {diffvssel 0}}
2459 {mc "Diff selected -> this" command {diffvssel 1}}
2460 {mc "Make patch" command mkpatch}
2462 $fakerowmenu configure -tearoff 0
2464 set headctxmenu .headctxmenu
2465 makemenu $headctxmenu {
2466 {mc "Check out this branch" command cobranch}
2467 {mc "Remove this branch" command rmbranch}
2469 $headctxmenu configure -tearoff 0
2471 global flist_menu
2472 set flist_menu .flistctxmenu
2473 makemenu $flist_menu {
2474 {mc "Highlight this too" command {flist_hl 0}}
2475 {mc "Highlight this only" command {flist_hl 1}}
2476 {mc "External diff" command {external_diff}}
2477 {mc "Blame parent commit" command {external_blame 1}}
2479 $flist_menu configure -tearoff 0
2481 global diff_menu
2482 set diff_menu .diffctxmenu
2483 makemenu $diff_menu {
2484 {mc "Show origin of this line" command show_line_source}
2485 {mc "Run git gui blame on this line" command {external_blame_diff}}
2487 $diff_menu configure -tearoff 0
2490 # Windows sends all mouse wheel events to the current focused window, not
2491 # the one where the mouse hovers, so bind those events here and redirect
2492 # to the correct window
2493 proc windows_mousewheel_redirector {W X Y D} {
2494 global canv canv2 canv3
2495 set w [winfo containing -displayof $W $X $Y]
2496 if {$w ne ""} {
2497 set u [expr {$D < 0 ? 5 : -5}]
2498 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2499 allcanvs yview scroll $u units
2500 } else {
2501 catch {
2502 $w yview scroll $u units
2508 # Update row number label when selectedline changes
2509 proc selectedline_change {n1 n2 op} {
2510 global selectedline rownumsel
2512 if {$selectedline eq {}} {
2513 set rownumsel {}
2514 } else {
2515 set rownumsel [expr {$selectedline + 1}]
2519 # mouse-2 makes all windows scan vertically, but only the one
2520 # the cursor is in scans horizontally
2521 proc canvscan {op w x y} {
2522 global canv canv2 canv3
2523 foreach c [list $canv $canv2 $canv3] {
2524 if {$c == $w} {
2525 $c scan $op $x $y
2526 } else {
2527 $c scan $op 0 $y
2532 proc scrollcanv {cscroll f0 f1} {
2533 $cscroll set $f0 $f1
2534 drawvisible
2535 flushhighlights
2538 # when we make a key binding for the toplevel, make sure
2539 # it doesn't get triggered when that key is pressed in the
2540 # find string entry widget.
2541 proc bindkey {ev script} {
2542 global entries
2543 bind . $ev $script
2544 set escript [bind Entry $ev]
2545 if {$escript == {}} {
2546 set escript [bind Entry <Key>]
2548 foreach e $entries {
2549 bind $e $ev "$escript; break"
2553 # set the focus back to the toplevel for any click outside
2554 # the entry widgets
2555 proc click {w} {
2556 global ctext entries
2557 foreach e [concat $entries $ctext] {
2558 if {$w == $e} return
2560 focus .
2563 # Adjust the progress bar for a change in requested extent or canvas size
2564 proc adjustprogress {} {
2565 global progresscanv progressitem progresscoords
2566 global fprogitem fprogcoord lastprogupdate progupdatepending
2567 global rprogitem rprogcoord use_ttk
2569 if {$use_ttk} {
2570 $progresscanv configure -value [expr {int($fprogcoord * 100)}]
2571 return
2574 set w [expr {[winfo width $progresscanv] - 4}]
2575 set x0 [expr {$w * [lindex $progresscoords 0]}]
2576 set x1 [expr {$w * [lindex $progresscoords 1]}]
2577 set h [winfo height $progresscanv]
2578 $progresscanv coords $progressitem $x0 0 $x1 $h
2579 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2580 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2581 set now [clock clicks -milliseconds]
2582 if {$now >= $lastprogupdate + 100} {
2583 set progupdatepending 0
2584 update
2585 } elseif {!$progupdatepending} {
2586 set progupdatepending 1
2587 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2591 proc doprogupdate {} {
2592 global lastprogupdate progupdatepending
2594 if {$progupdatepending} {
2595 set progupdatepending 0
2596 set lastprogupdate [clock clicks -milliseconds]
2597 update
2601 proc savestuff {w} {
2602 global canv canv2 canv3 mainfont textfont uifont tabstop
2603 global stuffsaved findmergefiles maxgraphpct
2604 global maxwidth showneartags showlocalchanges
2605 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2606 global cmitmode wrapcomment datetimeformat limitdiffs
2607 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2608 global autoselect extdifftool perfile_attrs markbgcolor use_ttk
2609 global hideremotes want_ttk
2611 if {$stuffsaved} return
2612 if {![winfo viewable .]} return
2613 catch {
2614 set f [open "~/.gitk-new" w]
2615 if {$::tcl_platform(platform) eq {windows}} {
2616 file attributes "~/.gitk-new" -hidden true
2618 puts $f [list set mainfont $mainfont]
2619 puts $f [list set textfont $textfont]
2620 puts $f [list set uifont $uifont]
2621 puts $f [list set tabstop $tabstop]
2622 puts $f [list set findmergefiles $findmergefiles]
2623 puts $f [list set maxgraphpct $maxgraphpct]
2624 puts $f [list set maxwidth $maxwidth]
2625 puts $f [list set cmitmode $cmitmode]
2626 puts $f [list set wrapcomment $wrapcomment]
2627 puts $f [list set autoselect $autoselect]
2628 puts $f [list set showneartags $showneartags]
2629 puts $f [list set hideremotes $hideremotes]
2630 puts $f [list set showlocalchanges $showlocalchanges]
2631 puts $f [list set datetimeformat $datetimeformat]
2632 puts $f [list set limitdiffs $limitdiffs]
2633 puts $f [list set want_ttk $want_ttk]
2634 puts $f [list set bgcolor $bgcolor]
2635 puts $f [list set fgcolor $fgcolor]
2636 puts $f [list set colors $colors]
2637 puts $f [list set diffcolors $diffcolors]
2638 puts $f [list set markbgcolor $markbgcolor]
2639 puts $f [list set diffcontext $diffcontext]
2640 puts $f [list set selectbgcolor $selectbgcolor]
2641 puts $f [list set extdifftool $extdifftool]
2642 puts $f [list set perfile_attrs $perfile_attrs]
2644 puts $f "set geometry(main) [wm geometry .]"
2645 puts $f "set geometry(state) [wm state .]"
2646 puts $f "set geometry(topwidth) [winfo width .tf]"
2647 puts $f "set geometry(topheight) [winfo height .tf]"
2648 if {$use_ttk} {
2649 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
2650 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
2651 } else {
2652 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2653 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2655 puts $f "set geometry(botwidth) [winfo width .bleft]"
2656 puts $f "set geometry(botheight) [winfo height .bleft]"
2658 puts -nonewline $f "set permviews {"
2659 for {set v 0} {$v < $nextviewnum} {incr v} {
2660 if {$viewperm($v)} {
2661 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2664 puts $f "}"
2665 close $f
2666 file rename -force "~/.gitk-new" "~/.gitk"
2668 set stuffsaved 1
2671 proc resizeclistpanes {win w} {
2672 global oldwidth use_ttk
2673 if {[info exists oldwidth($win)]} {
2674 if {$use_ttk} {
2675 set s0 [$win sashpos 0]
2676 set s1 [$win sashpos 1]
2677 } else {
2678 set s0 [$win sash coord 0]
2679 set s1 [$win sash coord 1]
2681 if {$w < 60} {
2682 set sash0 [expr {int($w/2 - 2)}]
2683 set sash1 [expr {int($w*5/6 - 2)}]
2684 } else {
2685 set factor [expr {1.0 * $w / $oldwidth($win)}]
2686 set sash0 [expr {int($factor * [lindex $s0 0])}]
2687 set sash1 [expr {int($factor * [lindex $s1 0])}]
2688 if {$sash0 < 30} {
2689 set sash0 30
2691 if {$sash1 < $sash0 + 20} {
2692 set sash1 [expr {$sash0 + 20}]
2694 if {$sash1 > $w - 10} {
2695 set sash1 [expr {$w - 10}]
2696 if {$sash0 > $sash1 - 20} {
2697 set sash0 [expr {$sash1 - 20}]
2701 if {$use_ttk} {
2702 $win sashpos 0 $sash0
2703 $win sashpos 1 $sash1
2704 } else {
2705 $win sash place 0 $sash0 [lindex $s0 1]
2706 $win sash place 1 $sash1 [lindex $s1 1]
2709 set oldwidth($win) $w
2712 proc resizecdetpanes {win w} {
2713 global oldwidth use_ttk
2714 if {[info exists oldwidth($win)]} {
2715 if {$use_ttk} {
2716 set s0 [$win sashpos 0]
2717 } else {
2718 set s0 [$win sash coord 0]
2720 if {$w < 60} {
2721 set sash0 [expr {int($w*3/4 - 2)}]
2722 } else {
2723 set factor [expr {1.0 * $w / $oldwidth($win)}]
2724 set sash0 [expr {int($factor * [lindex $s0 0])}]
2725 if {$sash0 < 45} {
2726 set sash0 45
2728 if {$sash0 > $w - 15} {
2729 set sash0 [expr {$w - 15}]
2732 if {$use_ttk} {
2733 $win sashpos 0 $sash0
2734 } else {
2735 $win sash place 0 $sash0 [lindex $s0 1]
2738 set oldwidth($win) $w
2741 proc allcanvs args {
2742 global canv canv2 canv3
2743 eval $canv $args
2744 eval $canv2 $args
2745 eval $canv3 $args
2748 proc bindall {event action} {
2749 global canv canv2 canv3
2750 bind $canv $event $action
2751 bind $canv2 $event $action
2752 bind $canv3 $event $action
2755 proc about {} {
2756 global uifont NS
2757 set w .about
2758 if {[winfo exists $w]} {
2759 raise $w
2760 return
2762 ttk_toplevel $w
2763 wm title $w [mc "About gitk"]
2764 make_transient $w .
2765 message $w.m -text [mc "
2766 Gitk - a commit viewer for git
2768 Copyright \u00a9 2005-2009 Paul Mackerras
2770 Use and redistribute under the terms of the GNU General Public License"] \
2771 -justify center -aspect 400 -border 2 -bg white -relief groove
2772 pack $w.m -side top -fill x -padx 2 -pady 2
2773 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2774 pack $w.ok -side bottom
2775 bind $w <Visibility> "focus $w.ok"
2776 bind $w <Key-Escape> "destroy $w"
2777 bind $w <Key-Return> "destroy $w"
2778 tk::PlaceWindow $w widget .
2781 proc keys {} {
2782 global NS
2783 set w .keys
2784 if {[winfo exists $w]} {
2785 raise $w
2786 return
2788 if {[tk windowingsystem] eq {aqua}} {
2789 set M1T Cmd
2790 } else {
2791 set M1T Ctrl
2793 ttk_toplevel $w
2794 wm title $w [mc "Gitk key bindings"]
2795 make_transient $w .
2796 message $w.m -text "
2797 [mc "Gitk key bindings:"]
2799 [mc "<%s-Q> Quit" $M1T]
2800 [mc "<Home> Move to first commit"]
2801 [mc "<End> Move to last commit"]
2802 [mc "<Up>, p, i Move up one commit"]
2803 [mc "<Down>, n, k Move down one commit"]
2804 [mc "<Left>, z, j Go back in history list"]
2805 [mc "<Right>, x, l Go forward in history list"]
2806 [mc "<PageUp> Move up one page in commit list"]
2807 [mc "<PageDown> Move down one page in commit list"]
2808 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2809 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2810 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2811 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2812 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2813 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2814 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2815 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2816 [mc "<Delete>, b Scroll diff view up one page"]
2817 [mc "<Backspace> Scroll diff view up one page"]
2818 [mc "<Space> Scroll diff view down one page"]
2819 [mc "u Scroll diff view up 18 lines"]
2820 [mc "d Scroll diff view down 18 lines"]
2821 [mc "<%s-F> Find" $M1T]
2822 [mc "<%s-G> Move to next find hit" $M1T]
2823 [mc "<Return> Move to next find hit"]
2824 [mc "/ Focus the search box"]
2825 [mc "? Move to previous find hit"]
2826 [mc "f Scroll diff view to next file"]
2827 [mc "<%s-S> Search for next hit in diff view" $M1T]
2828 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2829 [mc "<%s-KP+> Increase font size" $M1T]
2830 [mc "<%s-plus> Increase font size" $M1T]
2831 [mc "<%s-KP-> Decrease font size" $M1T]
2832 [mc "<%s-minus> Decrease font size" $M1T]
2833 [mc "<F5> Update"]
2835 -justify left -bg white -border 2 -relief groove
2836 pack $w.m -side top -fill both -padx 2 -pady 2
2837 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2838 bind $w <Key-Escape> [list destroy $w]
2839 pack $w.ok -side bottom
2840 bind $w <Visibility> "focus $w.ok"
2841 bind $w <Key-Escape> "destroy $w"
2842 bind $w <Key-Return> "destroy $w"
2845 # Procedures for manipulating the file list window at the
2846 # bottom right of the overall window.
2848 proc treeview {w l openlevs} {
2849 global treecontents treediropen treeheight treeparent treeindex
2851 set ix 0
2852 set treeindex() 0
2853 set lev 0
2854 set prefix {}
2855 set prefixend -1
2856 set prefendstack {}
2857 set htstack {}
2858 set ht 0
2859 set treecontents() {}
2860 $w conf -state normal
2861 foreach f $l {
2862 while {[string range $f 0 $prefixend] ne $prefix} {
2863 if {$lev <= $openlevs} {
2864 $w mark set e:$treeindex($prefix) "end -1c"
2865 $w mark gravity e:$treeindex($prefix) left
2867 set treeheight($prefix) $ht
2868 incr ht [lindex $htstack end]
2869 set htstack [lreplace $htstack end end]
2870 set prefixend [lindex $prefendstack end]
2871 set prefendstack [lreplace $prefendstack end end]
2872 set prefix [string range $prefix 0 $prefixend]
2873 incr lev -1
2875 set tail [string range $f [expr {$prefixend+1}] end]
2876 while {[set slash [string first "/" $tail]] >= 0} {
2877 lappend htstack $ht
2878 set ht 0
2879 lappend prefendstack $prefixend
2880 incr prefixend [expr {$slash + 1}]
2881 set d [string range $tail 0 $slash]
2882 lappend treecontents($prefix) $d
2883 set oldprefix $prefix
2884 append prefix $d
2885 set treecontents($prefix) {}
2886 set treeindex($prefix) [incr ix]
2887 set treeparent($prefix) $oldprefix
2888 set tail [string range $tail [expr {$slash+1}] end]
2889 if {$lev <= $openlevs} {
2890 set ht 1
2891 set treediropen($prefix) [expr {$lev < $openlevs}]
2892 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2893 $w mark set d:$ix "end -1c"
2894 $w mark gravity d:$ix left
2895 set str "\n"
2896 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2897 $w insert end $str
2898 $w image create end -align center -image $bm -padx 1 \
2899 -name a:$ix
2900 $w insert end $d [highlight_tag $prefix]
2901 $w mark set s:$ix "end -1c"
2902 $w mark gravity s:$ix left
2904 incr lev
2906 if {$tail ne {}} {
2907 if {$lev <= $openlevs} {
2908 incr ht
2909 set str "\n"
2910 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2911 $w insert end $str
2912 $w insert end $tail [highlight_tag $f]
2914 lappend treecontents($prefix) $tail
2917 while {$htstack ne {}} {
2918 set treeheight($prefix) $ht
2919 incr ht [lindex $htstack end]
2920 set htstack [lreplace $htstack end end]
2921 set prefixend [lindex $prefendstack end]
2922 set prefendstack [lreplace $prefendstack end end]
2923 set prefix [string range $prefix 0 $prefixend]
2925 $w conf -state disabled
2928 proc linetoelt {l} {
2929 global treeheight treecontents
2931 set y 2
2932 set prefix {}
2933 while {1} {
2934 foreach e $treecontents($prefix) {
2935 if {$y == $l} {
2936 return "$prefix$e"
2938 set n 1
2939 if {[string index $e end] eq "/"} {
2940 set n $treeheight($prefix$e)
2941 if {$y + $n > $l} {
2942 append prefix $e
2943 incr y
2944 break
2947 incr y $n
2952 proc highlight_tree {y prefix} {
2953 global treeheight treecontents cflist
2955 foreach e $treecontents($prefix) {
2956 set path $prefix$e
2957 if {[highlight_tag $path] ne {}} {
2958 $cflist tag add bold $y.0 "$y.0 lineend"
2960 incr y
2961 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2962 set y [highlight_tree $y $path]
2965 return $y
2968 proc treeclosedir {w dir} {
2969 global treediropen treeheight treeparent treeindex
2971 set ix $treeindex($dir)
2972 $w conf -state normal
2973 $w delete s:$ix e:$ix
2974 set treediropen($dir) 0
2975 $w image configure a:$ix -image tri-rt
2976 $w conf -state disabled
2977 set n [expr {1 - $treeheight($dir)}]
2978 while {$dir ne {}} {
2979 incr treeheight($dir) $n
2980 set dir $treeparent($dir)
2984 proc treeopendir {w dir} {
2985 global treediropen treeheight treeparent treecontents treeindex
2987 set ix $treeindex($dir)
2988 $w conf -state normal
2989 $w image configure a:$ix -image tri-dn
2990 $w mark set e:$ix s:$ix
2991 $w mark gravity e:$ix right
2992 set lev 0
2993 set str "\n"
2994 set n [llength $treecontents($dir)]
2995 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2996 incr lev
2997 append str "\t"
2998 incr treeheight($x) $n
3000 foreach e $treecontents($dir) {
3001 set de $dir$e
3002 if {[string index $e end] eq "/"} {
3003 set iy $treeindex($de)
3004 $w mark set d:$iy e:$ix
3005 $w mark gravity d:$iy left
3006 $w insert e:$ix $str
3007 set treediropen($de) 0
3008 $w image create e:$ix -align center -image tri-rt -padx 1 \
3009 -name a:$iy
3010 $w insert e:$ix $e [highlight_tag $de]
3011 $w mark set s:$iy e:$ix
3012 $w mark gravity s:$iy left
3013 set treeheight($de) 1
3014 } else {
3015 $w insert e:$ix $str
3016 $w insert e:$ix $e [highlight_tag $de]
3019 $w mark gravity e:$ix right
3020 $w conf -state disabled
3021 set treediropen($dir) 1
3022 set top [lindex [split [$w index @0,0] .] 0]
3023 set ht [$w cget -height]
3024 set l [lindex [split [$w index s:$ix] .] 0]
3025 if {$l < $top} {
3026 $w yview $l.0
3027 } elseif {$l + $n + 1 > $top + $ht} {
3028 set top [expr {$l + $n + 2 - $ht}]
3029 if {$l < $top} {
3030 set top $l
3032 $w yview $top.0
3036 proc treeclick {w x y} {
3037 global treediropen cmitmode ctext cflist cflist_top
3039 if {$cmitmode ne "tree"} return
3040 if {![info exists cflist_top]} return
3041 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3042 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3043 $cflist tag add highlight $l.0 "$l.0 lineend"
3044 set cflist_top $l
3045 if {$l == 1} {
3046 $ctext yview 1.0
3047 return
3049 set e [linetoelt $l]
3050 if {[string index $e end] ne "/"} {
3051 showfile $e
3052 } elseif {$treediropen($e)} {
3053 treeclosedir $w $e
3054 } else {
3055 treeopendir $w $e
3059 proc setfilelist {id} {
3060 global treefilelist cflist jump_to_here
3062 treeview $cflist $treefilelist($id) 0
3063 if {$jump_to_here ne {}} {
3064 set f [lindex $jump_to_here 0]
3065 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
3066 showfile $f
3071 image create bitmap tri-rt -background black -foreground blue -data {
3072 #define tri-rt_width 13
3073 #define tri-rt_height 13
3074 static unsigned char tri-rt_bits[] = {
3075 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
3076 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
3077 0x00, 0x00};
3078 } -maskdata {
3079 #define tri-rt-mask_width 13
3080 #define tri-rt-mask_height 13
3081 static unsigned char tri-rt-mask_bits[] = {
3082 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
3083 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
3084 0x08, 0x00};
3086 image create bitmap tri-dn -background black -foreground blue -data {
3087 #define tri-dn_width 13
3088 #define tri-dn_height 13
3089 static unsigned char tri-dn_bits[] = {
3090 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
3091 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3092 0x00, 0x00};
3093 } -maskdata {
3094 #define tri-dn-mask_width 13
3095 #define tri-dn-mask_height 13
3096 static unsigned char tri-dn-mask_bits[] = {
3097 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
3098 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
3099 0x00, 0x00};
3102 image create bitmap reficon-T -background black -foreground yellow -data {
3103 #define tagicon_width 13
3104 #define tagicon_height 9
3105 static unsigned char tagicon_bits[] = {
3106 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
3107 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
3108 } -maskdata {
3109 #define tagicon-mask_width 13
3110 #define tagicon-mask_height 9
3111 static unsigned char tagicon-mask_bits[] = {
3112 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
3113 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
3115 set rectdata {
3116 #define headicon_width 13
3117 #define headicon_height 9
3118 static unsigned char headicon_bits[] = {
3119 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
3120 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
3122 set rectmask {
3123 #define headicon-mask_width 13
3124 #define headicon-mask_height 9
3125 static unsigned char headicon-mask_bits[] = {
3126 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
3127 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
3129 image create bitmap reficon-H -background black -foreground green \
3130 -data $rectdata -maskdata $rectmask
3131 image create bitmap reficon-o -background black -foreground "#ddddff" \
3132 -data $rectdata -maskdata $rectmask
3134 proc init_flist {first} {
3135 global cflist cflist_top difffilestart
3137 $cflist conf -state normal
3138 $cflist delete 0.0 end
3139 if {$first ne {}} {
3140 $cflist insert end $first
3141 set cflist_top 1
3142 $cflist tag add highlight 1.0 "1.0 lineend"
3143 } else {
3144 catch {unset cflist_top}
3146 $cflist conf -state disabled
3147 set difffilestart {}
3150 proc highlight_tag {f} {
3151 global highlight_paths
3153 foreach p $highlight_paths {
3154 if {[string match $p $f]} {
3155 return "bold"
3158 return {}
3161 proc highlight_filelist {} {
3162 global cmitmode cflist
3164 $cflist conf -state normal
3165 if {$cmitmode ne "tree"} {
3166 set end [lindex [split [$cflist index end] .] 0]
3167 for {set l 2} {$l < $end} {incr l} {
3168 set line [$cflist get $l.0 "$l.0 lineend"]
3169 if {[highlight_tag $line] ne {}} {
3170 $cflist tag add bold $l.0 "$l.0 lineend"
3173 } else {
3174 highlight_tree 2 {}
3176 $cflist conf -state disabled
3179 proc unhighlight_filelist {} {
3180 global cflist
3182 $cflist conf -state normal
3183 $cflist tag remove bold 1.0 end
3184 $cflist conf -state disabled
3187 proc add_flist {fl} {
3188 global cflist
3190 $cflist conf -state normal
3191 foreach f $fl {
3192 $cflist insert end "\n"
3193 $cflist insert end $f [highlight_tag $f]
3195 $cflist conf -state disabled
3198 proc sel_flist {w x y} {
3199 global ctext difffilestart cflist cflist_top cmitmode
3201 if {$cmitmode eq "tree"} return
3202 if {![info exists cflist_top]} return
3203 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3204 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3205 $cflist tag add highlight $l.0 "$l.0 lineend"
3206 set cflist_top $l
3207 if {$l == 1} {
3208 $ctext yview 1.0
3209 } else {
3210 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3214 proc pop_flist_menu {w X Y x y} {
3215 global ctext cflist cmitmode flist_menu flist_menu_file
3216 global treediffs diffids
3218 stopfinding
3219 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3220 if {$l <= 1} return
3221 if {$cmitmode eq "tree"} {
3222 set e [linetoelt $l]
3223 if {[string index $e end] eq "/"} return
3224 } else {
3225 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3227 set flist_menu_file $e
3228 set xdiffstate "normal"
3229 if {$cmitmode eq "tree"} {
3230 set xdiffstate "disabled"
3232 # Disable "External diff" item in tree mode
3233 $flist_menu entryconf 2 -state $xdiffstate
3234 tk_popup $flist_menu $X $Y
3237 proc find_ctext_fileinfo {line} {
3238 global ctext_file_names ctext_file_lines
3240 set ok [bsearch $ctext_file_lines $line]
3241 set tline [lindex $ctext_file_lines $ok]
3243 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3244 return {}
3245 } else {
3246 return [list [lindex $ctext_file_names $ok] $tline]
3250 proc pop_diff_menu {w X Y x y} {
3251 global ctext diff_menu flist_menu_file
3252 global diff_menu_txtpos diff_menu_line
3253 global diff_menu_filebase
3255 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3256 set diff_menu_line [lindex $diff_menu_txtpos 0]
3257 # don't pop up the menu on hunk-separator or file-separator lines
3258 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3259 return
3261 stopfinding
3262 set f [find_ctext_fileinfo $diff_menu_line]
3263 if {$f eq {}} return
3264 set flist_menu_file [lindex $f 0]
3265 set diff_menu_filebase [lindex $f 1]
3266 tk_popup $diff_menu $X $Y
3269 proc flist_hl {only} {
3270 global flist_menu_file findstring gdttype
3272 set x [shellquote $flist_menu_file]
3273 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3274 set findstring $x
3275 } else {
3276 append findstring " " $x
3278 set gdttype [mc "touching paths:"]
3281 proc save_file_from_commit {filename output what} {
3282 global nullfile
3284 if {[catch {exec git show $filename -- > $output} err]} {
3285 if {[string match "fatal: bad revision *" $err]} {
3286 return $nullfile
3288 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3289 return {}
3291 return $output
3294 proc external_diff_get_one_file {diffid filename diffdir} {
3295 global nullid nullid2 nullfile
3296 global gitdir
3298 if {$diffid == $nullid} {
3299 set difffile [file join [file dirname $gitdir] $filename]
3300 if {[file exists $difffile]} {
3301 return $difffile
3303 return $nullfile
3305 if {$diffid == $nullid2} {
3306 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3307 return [save_file_from_commit :$filename $difffile index]
3309 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3310 return [save_file_from_commit $diffid:$filename $difffile \
3311 "revision $diffid"]
3314 proc external_diff {} {
3315 global gitktmpdir nullid nullid2
3316 global flist_menu_file
3317 global diffids
3318 global diffnum
3319 global gitdir extdifftool
3321 if {[llength $diffids] == 1} {
3322 # no reference commit given
3323 set diffidto [lindex $diffids 0]
3324 if {$diffidto eq $nullid} {
3325 # diffing working copy with index
3326 set diffidfrom $nullid2
3327 } elseif {$diffidto eq $nullid2} {
3328 # diffing index with HEAD
3329 set diffidfrom "HEAD"
3330 } else {
3331 # use first parent commit
3332 global parentlist selectedline
3333 set diffidfrom [lindex $parentlist $selectedline 0]
3335 } else {
3336 set diffidfrom [lindex $diffids 0]
3337 set diffidto [lindex $diffids 1]
3340 # make sure that several diffs wont collide
3341 if {![info exists gitktmpdir]} {
3342 set gitktmpdir [file join [file dirname $gitdir] \
3343 [format ".gitk-tmp.%s" [pid]]]
3344 if {[catch {file mkdir $gitktmpdir} err]} {
3345 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3346 unset gitktmpdir
3347 return
3349 set diffnum 0
3351 incr diffnum
3352 set diffdir [file join $gitktmpdir $diffnum]
3353 if {[catch {file mkdir $diffdir} err]} {
3354 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3355 return
3358 # gather files to diff
3359 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3360 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3362 if {$difffromfile ne {} && $difftofile ne {}} {
3363 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
3364 if {[catch {set fl [open |$cmd r]} err]} {
3365 file delete -force $diffdir
3366 error_popup "$extdifftool: [mc "command failed:"] $err"
3367 } else {
3368 fconfigure $fl -blocking 0
3369 filerun $fl [list delete_at_eof $fl $diffdir]
3374 proc find_hunk_blamespec {base line} {
3375 global ctext
3377 # Find and parse the hunk header
3378 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3379 if {$s_lix eq {}} return
3381 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3382 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3383 s_line old_specs osz osz1 new_line nsz]} {
3384 return
3387 # base lines for the parents
3388 set base_lines [list $new_line]
3389 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3390 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3391 old_spec old_line osz]} {
3392 return
3394 lappend base_lines $old_line
3397 # Now scan the lines to determine offset within the hunk
3398 set max_parent [expr {[llength $base_lines]-2}]
3399 set dline 0
3400 set s_lno [lindex [split $s_lix "."] 0]
3402 # Determine if the line is removed
3403 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3404 if {[string match {[-+ ]*} $chunk]} {
3405 set removed_idx [string first "-" $chunk]
3406 # Choose a parent index
3407 if {$removed_idx >= 0} {
3408 set parent $removed_idx
3409 } else {
3410 set unchanged_idx [string first " " $chunk]
3411 if {$unchanged_idx >= 0} {
3412 set parent $unchanged_idx
3413 } else {
3414 # blame the current commit
3415 set parent -1
3418 # then count other lines that belong to it
3419 for {set i $line} {[incr i -1] > $s_lno} {} {
3420 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3421 # Determine if the line is removed
3422 set removed_idx [string first "-" $chunk]
3423 if {$parent >= 0} {
3424 set code [string index $chunk $parent]
3425 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3426 incr dline
3428 } else {
3429 if {$removed_idx < 0} {
3430 incr dline
3434 incr parent
3435 } else {
3436 set parent 0
3439 incr dline [lindex $base_lines $parent]
3440 return [list $parent $dline]
3443 proc external_blame_diff {} {
3444 global currentid cmitmode
3445 global diff_menu_txtpos diff_menu_line
3446 global diff_menu_filebase flist_menu_file
3448 if {$cmitmode eq "tree"} {
3449 set parent_idx 0
3450 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3451 } else {
3452 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3453 if {$hinfo ne {}} {
3454 set parent_idx [lindex $hinfo 0]
3455 set line [lindex $hinfo 1]
3456 } else {
3457 set parent_idx 0
3458 set line 0
3462 external_blame $parent_idx $line
3465 # Find the SHA1 ID of the blob for file $fname in the index
3466 # at stage 0 or 2
3467 proc index_sha1 {fname} {
3468 set f [open [list | git ls-files -s $fname] r]
3469 while {[gets $f line] >= 0} {
3470 set info [lindex [split $line "\t"] 0]
3471 set stage [lindex $info 2]
3472 if {$stage eq "0" || $stage eq "2"} {
3473 close $f
3474 return [lindex $info 1]
3477 close $f
3478 return {}
3481 # Turn an absolute path into one relative to the current directory
3482 proc make_relative {f} {
3483 set elts [file split $f]
3484 set here [file split [pwd]]
3485 set ei 0
3486 set hi 0
3487 set res {}
3488 foreach d $here {
3489 if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
3490 lappend res ".."
3491 } else {
3492 incr ei
3494 incr hi
3496 set elts [concat $res [lrange $elts $ei end]]
3497 return [eval file join $elts]
3500 proc external_blame {parent_idx {line {}}} {
3501 global flist_menu_file gitdir
3502 global nullid nullid2
3503 global parentlist selectedline currentid
3505 if {$parent_idx > 0} {
3506 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3507 } else {
3508 set base_commit $currentid
3511 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3512 error_popup [mc "No such commit"]
3513 return
3516 set cmdline [list git gui blame]
3517 if {$line ne {} && $line > 1} {
3518 lappend cmdline "--line=$line"
3520 set f [file join [file dirname $gitdir] $flist_menu_file]
3521 # Unfortunately it seems git gui blame doesn't like
3522 # being given an absolute path...
3523 set f [make_relative $f]
3524 lappend cmdline $base_commit $f
3525 if {[catch {eval exec $cmdline &} err]} {
3526 error_popup "[mc "git gui blame: command failed:"] $err"
3530 proc show_line_source {} {
3531 global cmitmode currentid parents curview blamestuff blameinst
3532 global diff_menu_line diff_menu_filebase flist_menu_file
3533 global nullid nullid2 gitdir
3535 set from_index {}
3536 if {$cmitmode eq "tree"} {
3537 set id $currentid
3538 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3539 } else {
3540 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3541 if {$h eq {}} return
3542 set pi [lindex $h 0]
3543 if {$pi == 0} {
3544 mark_ctext_line $diff_menu_line
3545 return
3547 incr pi -1
3548 if {$currentid eq $nullid} {
3549 if {$pi > 0} {
3550 # must be a merge in progress...
3551 if {[catch {
3552 # get the last line from .git/MERGE_HEAD
3553 set f [open [file join $gitdir MERGE_HEAD] r]
3554 set id [lindex [split [read $f] "\n"] end-1]
3555 close $f
3556 } err]} {
3557 error_popup [mc "Couldn't read merge head: %s" $err]
3558 return
3560 } elseif {$parents($curview,$currentid) eq $nullid2} {
3561 # need to do the blame from the index
3562 if {[catch {
3563 set from_index [index_sha1 $flist_menu_file]
3564 } err]} {
3565 error_popup [mc "Error reading index: %s" $err]
3566 return
3568 } else {
3569 set id $parents($curview,$currentid)
3571 } else {
3572 set id [lindex $parents($curview,$currentid) $pi]
3574 set line [lindex $h 1]
3576 set blameargs {}
3577 if {$from_index ne {}} {
3578 lappend blameargs | git cat-file blob $from_index
3580 lappend blameargs | git blame -p -L$line,+1
3581 if {$from_index ne {}} {
3582 lappend blameargs --contents -
3583 } else {
3584 lappend blameargs $id
3586 lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
3587 if {[catch {
3588 set f [open $blameargs r]
3589 } err]} {
3590 error_popup [mc "Couldn't start git blame: %s" $err]
3591 return
3593 nowbusy blaming [mc "Searching"]
3594 fconfigure $f -blocking 0
3595 set i [reg_instance $f]
3596 set blamestuff($i) {}
3597 set blameinst $i
3598 filerun $f [list read_line_source $f $i]
3601 proc stopblaming {} {
3602 global blameinst
3604 if {[info exists blameinst]} {
3605 stop_instance $blameinst
3606 unset blameinst
3607 notbusy blaming
3611 proc read_line_source {fd inst} {
3612 global blamestuff curview commfd blameinst nullid nullid2
3614 while {[gets $fd line] >= 0} {
3615 lappend blamestuff($inst) $line
3617 if {![eof $fd]} {
3618 return 1
3620 unset commfd($inst)
3621 unset blameinst
3622 notbusy blaming
3623 fconfigure $fd -blocking 1
3624 if {[catch {close $fd} err]} {
3625 error_popup [mc "Error running git blame: %s" $err]
3626 return 0
3629 set fname {}
3630 set line [split [lindex $blamestuff($inst) 0] " "]
3631 set id [lindex $line 0]
3632 set lnum [lindex $line 1]
3633 if {[string length $id] == 40 && [string is xdigit $id] &&
3634 [string is digit -strict $lnum]} {
3635 # look for "filename" line
3636 foreach l $blamestuff($inst) {
3637 if {[string match "filename *" $l]} {
3638 set fname [string range $l 9 end]
3639 break
3643 if {$fname ne {}} {
3644 # all looks good, select it
3645 if {$id eq $nullid} {
3646 # blame uses all-zeroes to mean not committed,
3647 # which would mean a change in the index
3648 set id $nullid2
3650 if {[commitinview $id $curview]} {
3651 selectline [rowofcommit $id] 1 [list $fname $lnum]
3652 } else {
3653 error_popup [mc "That line comes from commit %s, \
3654 which is not in this view" [shortids $id]]
3656 } else {
3657 puts "oops couldn't parse git blame output"
3659 return 0
3662 # delete $dir when we see eof on $f (presumably because the child has exited)
3663 proc delete_at_eof {f dir} {
3664 while {[gets $f line] >= 0} {}
3665 if {[eof $f]} {
3666 if {[catch {close $f} err]} {
3667 error_popup "[mc "External diff viewer failed:"] $err"
3669 file delete -force $dir
3670 return 0
3672 return 1
3675 # Functions for adding and removing shell-type quoting
3677 proc shellquote {str} {
3678 if {![string match "*\['\"\\ \t]*" $str]} {
3679 return $str
3681 if {![string match "*\['\"\\]*" $str]} {
3682 return "\"$str\""
3684 if {![string match "*'*" $str]} {
3685 return "'$str'"
3687 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3690 proc shellarglist {l} {
3691 set str {}
3692 foreach a $l {
3693 if {$str ne {}} {
3694 append str " "
3696 append str [shellquote $a]
3698 return $str
3701 proc shelldequote {str} {
3702 set ret {}
3703 set used -1
3704 while {1} {
3705 incr used
3706 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3707 append ret [string range $str $used end]
3708 set used [string length $str]
3709 break
3711 set first [lindex $first 0]
3712 set ch [string index $str $first]
3713 if {$first > $used} {
3714 append ret [string range $str $used [expr {$first - 1}]]
3715 set used $first
3717 if {$ch eq " " || $ch eq "\t"} break
3718 incr used
3719 if {$ch eq "'"} {
3720 set first [string first "'" $str $used]
3721 if {$first < 0} {
3722 error "unmatched single-quote"
3724 append ret [string range $str $used [expr {$first - 1}]]
3725 set used $first
3726 continue
3728 if {$ch eq "\\"} {
3729 if {$used >= [string length $str]} {
3730 error "trailing backslash"
3732 append ret [string index $str $used]
3733 continue
3735 # here ch == "\""
3736 while {1} {
3737 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3738 error "unmatched double-quote"
3740 set first [lindex $first 0]
3741 set ch [string index $str $first]
3742 if {$first > $used} {
3743 append ret [string range $str $used [expr {$first - 1}]]
3744 set used $first
3746 if {$ch eq "\""} break
3747 incr used
3748 append ret [string index $str $used]
3749 incr used
3752 return [list $used $ret]
3755 proc shellsplit {str} {
3756 set l {}
3757 while {1} {
3758 set str [string trimleft $str]
3759 if {$str eq {}} break
3760 set dq [shelldequote $str]
3761 set n [lindex $dq 0]
3762 set word [lindex $dq 1]
3763 set str [string range $str $n end]
3764 lappend l $word
3766 return $l
3769 # Code to implement multiple views
3771 proc newview {ishighlight} {
3772 global nextviewnum newviewname newishighlight
3773 global revtreeargs viewargscmd newviewopts curview
3775 set newishighlight $ishighlight
3776 set top .gitkview
3777 if {[winfo exists $top]} {
3778 raise $top
3779 return
3781 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3782 set newviewopts($nextviewnum,perm) 0
3783 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3784 decode_view_opts $nextviewnum $revtreeargs
3785 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3788 set known_view_options {
3789 {perm b . {} {mc "Remember this view"}}
3790 {reflabel l + {} {mc "References (space separated list):"}}
3791 {refs t15 .. {} {mc "Branches & tags:"}}
3792 {allrefs b *. "--all" {mc "All refs"}}
3793 {branches b . "--branches" {mc "All (local) branches"}}
3794 {tags b . "--tags" {mc "All tags"}}
3795 {remotes b . "--remotes" {mc "All remote-tracking branches"}}
3796 {commitlbl l + {} {mc "Commit Info (regular expressions):"}}
3797 {author t15 .. "--author=*" {mc "Author:"}}
3798 {committer t15 . "--committer=*" {mc "Committer:"}}
3799 {loginfo t15 .. "--grep=*" {mc "Commit Message:"}}
3800 {allmatch b .. "--all-match" {mc "Matches all Commit Info criteria"}}
3801 {changes_l l + {} {mc "Changes to Files:"}}
3802 {pickaxe_s r0 . {} {mc "Fixed String"}}
3803 {pickaxe_t r1 . "--pickaxe-regex" {mc "Regular Expression"}}
3804 {pickaxe t15 .. "-S*" {mc "Search string:"}}
3805 {datelabel l + {} {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
3806 {since t15 .. {"--since=*" "--after=*"} {mc "Since:"}}
3807 {until t15 . {"--until=*" "--before=*"} {mc "Until:"}}
3808 {limit_lbl l + {} {mc "Limit and/or skip a number of revisions (positive integer):"}}
3809 {limit t10 *. "--max-count=*" {mc "Number to show:"}}
3810 {skip t10 . "--skip=*" {mc "Number to skip:"}}
3811 {misc_lbl l + {} {mc "Miscellaneous options:"}}
3812 {dorder b *. {"--date-order" "-d"} {mc "Strictly sort by date"}}
3813 {lright b . "--left-right" {mc "Mark branch sides"}}
3814 {first b . "--first-parent" {mc "Limit to first parent"}}
3815 {smplhst b . "--simplify-by-decoration" {mc "Simple history"}}
3816 {args t50 *. {} {mc "Additional arguments to git log:"}}
3817 {allpaths path + {} {mc "Enter files and directories to include, one per line:"}}
3818 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3821 proc encode_view_opts {n} {
3822 global known_view_options newviewopts
3824 set rargs [list]
3825 foreach opt $known_view_options {
3826 set patterns [lindex $opt 3]
3827 if {$patterns eq {}} continue
3828 set pattern [lindex $patterns 0]
3830 if {[lindex $opt 1] eq "b"} {
3831 set val $newviewopts($n,[lindex $opt 0])
3832 if {$val} {
3833 lappend rargs $pattern
3835 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
3836 regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
3837 set val $newviewopts($n,$button_id)
3838 if {$val eq $value} {
3839 lappend rargs $pattern
3841 } else {
3842 set val $newviewopts($n,[lindex $opt 0])
3843 set val [string trim $val]
3844 if {$val ne {}} {
3845 set pfix [string range $pattern 0 end-1]
3846 lappend rargs $pfix$val
3850 set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
3851 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3854 proc decode_view_opts {n view_args} {
3855 global known_view_options newviewopts
3857 foreach opt $known_view_options {
3858 set id [lindex $opt 0]
3859 if {[lindex $opt 1] eq "b"} {
3860 # Checkboxes
3861 set val 0
3862 } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
3863 # Radiobuttons
3864 regexp {^(.*_)} $id uselessvar id
3865 set val 0
3866 } else {
3867 # Text fields
3868 set val {}
3870 set newviewopts($n,$id) $val
3872 set oargs [list]
3873 set refargs [list]
3874 foreach arg $view_args {
3875 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3876 && ![info exists found(limit)]} {
3877 set newviewopts($n,limit) $cnt
3878 set found(limit) 1
3879 continue
3881 catch { unset val }
3882 foreach opt $known_view_options {
3883 set id [lindex $opt 0]
3884 if {[info exists found($id)]} continue
3885 foreach pattern [lindex $opt 3] {
3886 if {![string match $pattern $arg]} continue
3887 if {[lindex $opt 1] eq "b"} {
3888 # Check buttons
3889 set val 1
3890 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
3891 # Radio buttons
3892 regexp {^(.*_)} $id uselessvar id
3893 set val $num
3894 } else {
3895 # Text input fields
3896 set size [string length $pattern]
3897 set val [string range $arg [expr {$size-1}] end]
3899 set newviewopts($n,$id) $val
3900 set found($id) 1
3901 break
3903 if {[info exists val]} break
3905 if {[info exists val]} continue
3906 if {[regexp {^-} $arg]} {
3907 lappend oargs $arg
3908 } else {
3909 lappend refargs $arg
3912 set newviewopts($n,refs) [shellarglist $refargs]
3913 set newviewopts($n,args) [shellarglist $oargs]
3916 proc edit_or_newview {} {
3917 global curview
3919 if {$curview > 0} {
3920 editview
3921 } else {
3922 newview 0
3926 proc editview {} {
3927 global curview
3928 global viewname viewperm newviewname newviewopts
3929 global viewargs viewargscmd
3931 set top .gitkvedit-$curview
3932 if {[winfo exists $top]} {
3933 raise $top
3934 return
3936 set newviewname($curview) $viewname($curview)
3937 set newviewopts($curview,perm) $viewperm($curview)
3938 set newviewopts($curview,cmd) $viewargscmd($curview)
3939 decode_view_opts $curview $viewargs($curview)
3940 vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
3943 proc vieweditor {top n title} {
3944 global newviewname newviewopts viewfiles bgcolor
3945 global known_view_options NS
3947 ttk_toplevel $top
3948 wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
3949 make_transient $top .
3951 # View name
3952 ${NS}::frame $top.nfr
3953 ${NS}::label $top.nl -text [mc "View Name"]
3954 ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
3955 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3956 pack $top.nl -in $top.nfr -side left -padx {0 5}
3957 pack $top.name -in $top.nfr -side left -padx {0 25}
3959 # View options
3960 set cframe $top.nfr
3961 set cexpand 0
3962 set cnt 0
3963 foreach opt $known_view_options {
3964 set id [lindex $opt 0]
3965 set type [lindex $opt 1]
3966 set flags [lindex $opt 2]
3967 set title [eval [lindex $opt 4]]
3968 set lxpad 0
3970 if {$flags eq "+" || $flags eq "*"} {
3971 set cframe $top.fr$cnt
3972 incr cnt
3973 ${NS}::frame $cframe
3974 pack $cframe -in $top -fill x -pady 3 -padx 3
3975 set cexpand [expr {$flags eq "*"}]
3976 } elseif {$flags eq ".." || $flags eq "*."} {
3977 set cframe $top.fr$cnt
3978 incr cnt
3979 ${NS}::frame $cframe
3980 pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
3981 set cexpand [expr {$flags eq "*."}]
3982 } else {
3983 set lxpad 5
3986 if {$type eq "l"} {
3987 ${NS}::label $cframe.l_$id -text $title
3988 pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
3989 } elseif {$type eq "b"} {
3990 ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3991 pack $cframe.c_$id -in $cframe -side left \
3992 -padx [list $lxpad 0] -expand $cexpand -anchor w
3993 } elseif {[regexp {^r(\d+)$} $type type sz]} {
3994 regexp {^(.*_)} $id uselessvar button_id
3995 ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
3996 pack $cframe.c_$id -in $cframe -side left \
3997 -padx [list $lxpad 0] -expand $cexpand -anchor w
3998 } elseif {[regexp {^t(\d+)$} $type type sz]} {
3999 ${NS}::label $cframe.l_$id -text $title
4000 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4001 -textvariable newviewopts($n,$id)
4002 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
4003 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
4004 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
4005 ${NS}::label $cframe.l_$id -text $title
4006 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4007 -textvariable newviewopts($n,$id)
4008 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
4009 pack $cframe.e_$id -in $cframe -side top -fill x
4010 } elseif {$type eq "path"} {
4011 ${NS}::label $top.l -text $title
4012 pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
4013 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
4014 if {[info exists viewfiles($n)]} {
4015 foreach f $viewfiles($n) {
4016 $top.t insert end $f
4017 $top.t insert end "\n"
4019 $top.t delete {end - 1c} end
4020 $top.t mark set insert 0.0
4022 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
4026 ${NS}::frame $top.buts
4027 ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
4028 ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
4029 ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
4030 bind $top <Control-Return> [list newviewok $top $n]
4031 bind $top <F5> [list newviewok $top $n 1]
4032 bind $top <Escape> [list destroy $top]
4033 grid $top.buts.ok $top.buts.apply $top.buts.can
4034 grid columnconfigure $top.buts 0 -weight 1 -uniform a
4035 grid columnconfigure $top.buts 1 -weight 1 -uniform a
4036 grid columnconfigure $top.buts 2 -weight 1 -uniform a
4037 pack $top.buts -in $top -side top -fill x
4038 focus $top.t
4041 proc doviewmenu {m first cmd op argv} {
4042 set nmenu [$m index end]
4043 for {set i $first} {$i <= $nmenu} {incr i} {
4044 if {[$m entrycget $i -command] eq $cmd} {
4045 eval $m $op $i $argv
4046 break
4051 proc allviewmenus {n op args} {
4052 # global viewhlmenu
4054 doviewmenu .bar.view 5 [list showview $n] $op $args
4055 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
4058 proc newviewok {top n {apply 0}} {
4059 global nextviewnum newviewperm newviewname newishighlight
4060 global viewname viewfiles viewperm selectedview curview
4061 global viewargs viewargscmd newviewopts viewhlmenu
4063 if {[catch {
4064 set newargs [encode_view_opts $n]
4065 } err]} {
4066 error_popup "[mc "Error in commit selection arguments:"] $err" $top
4067 return
4069 set files {}
4070 foreach f [split [$top.t get 0.0 end] "\n"] {
4071 set ft [string trim $f]
4072 if {$ft ne {}} {
4073 lappend files $ft
4076 if {![info exists viewfiles($n)]} {
4077 # creating a new view
4078 incr nextviewnum
4079 set viewname($n) $newviewname($n)
4080 set viewperm($n) $newviewopts($n,perm)
4081 set viewfiles($n) $files
4082 set viewargs($n) $newargs
4083 set viewargscmd($n) $newviewopts($n,cmd)
4084 addviewmenu $n
4085 if {!$newishighlight} {
4086 run showview $n
4087 } else {
4088 run addvhighlight $n
4090 } else {
4091 # editing an existing view
4092 set viewperm($n) $newviewopts($n,perm)
4093 if {$newviewname($n) ne $viewname($n)} {
4094 set viewname($n) $newviewname($n)
4095 doviewmenu .bar.view 5 [list showview $n] \
4096 entryconf [list -label $viewname($n)]
4097 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
4098 # entryconf [list -label $viewname($n) -value $viewname($n)]
4100 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
4101 $newviewopts($n,cmd) ne $viewargscmd($n)} {
4102 set viewfiles($n) $files
4103 set viewargs($n) $newargs
4104 set viewargscmd($n) $newviewopts($n,cmd)
4105 if {$curview == $n} {
4106 run reloadcommits
4110 if {$apply} return
4111 catch {destroy $top}
4114 proc delview {} {
4115 global curview viewperm hlview selectedhlview
4117 if {$curview == 0} return
4118 if {[info exists hlview] && $hlview == $curview} {
4119 set selectedhlview [mc "None"]
4120 unset hlview
4122 allviewmenus $curview delete
4123 set viewperm($curview) 0
4124 showview 0
4127 proc addviewmenu {n} {
4128 global viewname viewhlmenu
4130 .bar.view add radiobutton -label $viewname($n) \
4131 -command [list showview $n] -variable selectedview -value $n
4132 #$viewhlmenu add radiobutton -label $viewname($n) \
4133 # -command [list addvhighlight $n] -variable selectedhlview
4136 proc showview {n} {
4137 global curview cached_commitrow ordertok
4138 global displayorder parentlist rowidlist rowisopt rowfinal
4139 global colormap rowtextx nextcolor canvxmax
4140 global numcommits viewcomplete
4141 global selectedline currentid canv canvy0
4142 global treediffs
4143 global pending_select mainheadid
4144 global commitidx
4145 global selectedview
4146 global hlview selectedhlview commitinterest
4148 if {$n == $curview} return
4149 set selid {}
4150 set ymax [lindex [$canv cget -scrollregion] 3]
4151 set span [$canv yview]
4152 set ytop [expr {[lindex $span 0] * $ymax}]
4153 set ybot [expr {[lindex $span 1] * $ymax}]
4154 set yscreen [expr {($ybot - $ytop) / 2}]
4155 if {$selectedline ne {}} {
4156 set selid $currentid
4157 set y [yc $selectedline]
4158 if {$ytop < $y && $y < $ybot} {
4159 set yscreen [expr {$y - $ytop}]
4161 } elseif {[info exists pending_select]} {
4162 set selid $pending_select
4163 unset pending_select
4165 unselectline
4166 normalline
4167 catch {unset treediffs}
4168 clear_display
4169 if {[info exists hlview] && $hlview == $n} {
4170 unset hlview
4171 set selectedhlview [mc "None"]
4173 catch {unset commitinterest}
4174 catch {unset cached_commitrow}
4175 catch {unset ordertok}
4177 set curview $n
4178 set selectedview $n
4179 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
4180 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
4182 run refill_reflist
4183 if {![info exists viewcomplete($n)]} {
4184 getcommits $selid
4185 return
4188 set displayorder {}
4189 set parentlist {}
4190 set rowidlist {}
4191 set rowisopt {}
4192 set rowfinal {}
4193 set numcommits $commitidx($n)
4195 catch {unset colormap}
4196 catch {unset rowtextx}
4197 set nextcolor 0
4198 set canvxmax [$canv cget -width]
4199 set curview $n
4200 set row 0
4201 setcanvscroll
4202 set yf 0
4203 set row {}
4204 if {$selid ne {} && [commitinview $selid $n]} {
4205 set row [rowofcommit $selid]
4206 # try to get the selected row in the same position on the screen
4207 set ymax [lindex [$canv cget -scrollregion] 3]
4208 set ytop [expr {[yc $row] - $yscreen}]
4209 if {$ytop < 0} {
4210 set ytop 0
4212 set yf [expr {$ytop * 1.0 / $ymax}]
4214 allcanvs yview moveto $yf
4215 drawvisible
4216 if {$row ne {}} {
4217 selectline $row 0
4218 } elseif {!$viewcomplete($n)} {
4219 reset_pending_select $selid
4220 } else {
4221 reset_pending_select {}
4223 if {[commitinview $pending_select $curview]} {
4224 selectline [rowofcommit $pending_select] 1
4225 } else {
4226 set row [first_real_row]
4227 if {$row < $numcommits} {
4228 selectline $row 0
4232 if {!$viewcomplete($n)} {
4233 if {$numcommits == 0} {
4234 show_status [mc "Reading commits..."]
4236 } elseif {$numcommits == 0} {
4237 show_status [mc "No commits selected"]
4241 # Stuff relating to the highlighting facility
4243 proc ishighlighted {id} {
4244 global vhighlights fhighlights nhighlights rhighlights
4246 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
4247 return $nhighlights($id)
4249 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
4250 return $vhighlights($id)
4252 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
4253 return $fhighlights($id)
4255 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4256 return $rhighlights($id)
4258 return 0
4261 proc bolden {id font} {
4262 global canv linehtag currentid boldids need_redisplay markedid
4264 # need_redisplay = 1 means the display is stale and about to be redrawn
4265 if {$need_redisplay} return
4266 lappend boldids $id
4267 $canv itemconf $linehtag($id) -font $font
4268 if {[info exists currentid] && $id eq $currentid} {
4269 $canv delete secsel
4270 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4271 -outline {{}} -tags secsel \
4272 -fill [$canv cget -selectbackground]]
4273 $canv lower $t
4275 if {[info exists markedid] && $id eq $markedid} {
4276 make_idmark $id
4280 proc bolden_name {id font} {
4281 global canv2 linentag currentid boldnameids need_redisplay
4283 if {$need_redisplay} return
4284 lappend boldnameids $id
4285 $canv2 itemconf $linentag($id) -font $font
4286 if {[info exists currentid] && $id eq $currentid} {
4287 $canv2 delete secsel
4288 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4289 -outline {{}} -tags secsel \
4290 -fill [$canv2 cget -selectbackground]]
4291 $canv2 lower $t
4295 proc unbolden {} {
4296 global boldids
4298 set stillbold {}
4299 foreach id $boldids {
4300 if {![ishighlighted $id]} {
4301 bolden $id mainfont
4302 } else {
4303 lappend stillbold $id
4306 set boldids $stillbold
4309 proc addvhighlight {n} {
4310 global hlview viewcomplete curview vhl_done commitidx
4312 if {[info exists hlview]} {
4313 delvhighlight
4315 set hlview $n
4316 if {$n != $curview && ![info exists viewcomplete($n)]} {
4317 start_rev_list $n
4319 set vhl_done $commitidx($hlview)
4320 if {$vhl_done > 0} {
4321 drawvisible
4325 proc delvhighlight {} {
4326 global hlview vhighlights
4328 if {![info exists hlview]} return
4329 unset hlview
4330 catch {unset vhighlights}
4331 unbolden
4334 proc vhighlightmore {} {
4335 global hlview vhl_done commitidx vhighlights curview
4337 set max $commitidx($hlview)
4338 set vr [visiblerows]
4339 set r0 [lindex $vr 0]
4340 set r1 [lindex $vr 1]
4341 for {set i $vhl_done} {$i < $max} {incr i} {
4342 set id [commitonrow $i $hlview]
4343 if {[commitinview $id $curview]} {
4344 set row [rowofcommit $id]
4345 if {$r0 <= $row && $row <= $r1} {
4346 if {![highlighted $row]} {
4347 bolden $id mainfontbold
4349 set vhighlights($id) 1
4353 set vhl_done $max
4354 return 0
4357 proc askvhighlight {row id} {
4358 global hlview vhighlights iddrawn
4360 if {[commitinview $id $hlview]} {
4361 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4362 bolden $id mainfontbold
4364 set vhighlights($id) 1
4365 } else {
4366 set vhighlights($id) 0
4370 proc hfiles_change {} {
4371 global highlight_files filehighlight fhighlights fh_serial
4372 global highlight_paths
4374 if {[info exists filehighlight]} {
4375 # delete previous highlights
4376 catch {close $filehighlight}
4377 unset filehighlight
4378 catch {unset fhighlights}
4379 unbolden
4380 unhighlight_filelist
4382 set highlight_paths {}
4383 after cancel do_file_hl $fh_serial
4384 incr fh_serial
4385 if {$highlight_files ne {}} {
4386 after 300 do_file_hl $fh_serial
4390 proc gdttype_change {name ix op} {
4391 global gdttype highlight_files findstring findpattern
4393 stopfinding
4394 if {$findstring ne {}} {
4395 if {$gdttype eq [mc "containing:"]} {
4396 if {$highlight_files ne {}} {
4397 set highlight_files {}
4398 hfiles_change
4400 findcom_change
4401 } else {
4402 if {$findpattern ne {}} {
4403 set findpattern {}
4404 findcom_change
4406 set highlight_files $findstring
4407 hfiles_change
4409 drawvisible
4411 # enable/disable findtype/findloc menus too
4414 proc find_change {name ix op} {
4415 global gdttype findstring highlight_files
4417 stopfinding
4418 if {$gdttype eq [mc "containing:"]} {
4419 findcom_change
4420 } else {
4421 if {$highlight_files ne $findstring} {
4422 set highlight_files $findstring
4423 hfiles_change
4426 drawvisible
4429 proc findcom_change args {
4430 global nhighlights boldnameids
4431 global findpattern findtype findstring gdttype
4433 stopfinding
4434 # delete previous highlights, if any
4435 foreach id $boldnameids {
4436 bolden_name $id mainfont
4438 set boldnameids {}
4439 catch {unset nhighlights}
4440 unbolden
4441 unmarkmatches
4442 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4443 set findpattern {}
4444 } elseif {$findtype eq [mc "Regexp"]} {
4445 set findpattern $findstring
4446 } else {
4447 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4448 $findstring]
4449 set findpattern "*$e*"
4453 proc makepatterns {l} {
4454 set ret {}
4455 foreach e $l {
4456 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4457 if {[string index $ee end] eq "/"} {
4458 lappend ret "$ee*"
4459 } else {
4460 lappend ret $ee
4461 lappend ret "$ee/*"
4464 return $ret
4467 proc do_file_hl {serial} {
4468 global highlight_files filehighlight highlight_paths gdttype fhl_list
4470 if {$gdttype eq [mc "touching paths:"]} {
4471 if {[catch {set paths [shellsplit $highlight_files]}]} return
4472 set highlight_paths [makepatterns $paths]
4473 highlight_filelist
4474 set gdtargs [concat -- $paths]
4475 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4476 set gdtargs [list "-S$highlight_files"]
4477 } else {
4478 # must be "containing:", i.e. we're searching commit info
4479 return
4481 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4482 set filehighlight [open $cmd r+]
4483 fconfigure $filehighlight -blocking 0
4484 filerun $filehighlight readfhighlight
4485 set fhl_list {}
4486 drawvisible
4487 flushhighlights
4490 proc flushhighlights {} {
4491 global filehighlight fhl_list
4493 if {[info exists filehighlight]} {
4494 lappend fhl_list {}
4495 puts $filehighlight ""
4496 flush $filehighlight
4500 proc askfilehighlight {row id} {
4501 global filehighlight fhighlights fhl_list
4503 lappend fhl_list $id
4504 set fhighlights($id) -1
4505 puts $filehighlight $id
4508 proc readfhighlight {} {
4509 global filehighlight fhighlights curview iddrawn
4510 global fhl_list find_dirn
4512 if {![info exists filehighlight]} {
4513 return 0
4515 set nr 0
4516 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4517 set line [string trim $line]
4518 set i [lsearch -exact $fhl_list $line]
4519 if {$i < 0} continue
4520 for {set j 0} {$j < $i} {incr j} {
4521 set id [lindex $fhl_list $j]
4522 set fhighlights($id) 0
4524 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4525 if {$line eq {}} continue
4526 if {![commitinview $line $curview]} continue
4527 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4528 bolden $line mainfontbold
4530 set fhighlights($line) 1
4532 if {[eof $filehighlight]} {
4533 # strange...
4534 puts "oops, git diff-tree died"
4535 catch {close $filehighlight}
4536 unset filehighlight
4537 return 0
4539 if {[info exists find_dirn]} {
4540 run findmore
4542 return 1
4545 proc doesmatch {f} {
4546 global findtype findpattern
4548 if {$findtype eq [mc "Regexp"]} {
4549 return [regexp $findpattern $f]
4550 } elseif {$findtype eq [mc "IgnCase"]} {
4551 return [string match -nocase $findpattern $f]
4552 } else {
4553 return [string match $findpattern $f]
4557 proc askfindhighlight {row id} {
4558 global nhighlights commitinfo iddrawn
4559 global findloc
4560 global markingmatches
4562 if {![info exists commitinfo($id)]} {
4563 getcommit $id
4565 set info $commitinfo($id)
4566 set isbold 0
4567 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4568 foreach f $info ty $fldtypes {
4569 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4570 [doesmatch $f]} {
4571 if {$ty eq [mc "Author"]} {
4572 set isbold 2
4573 break
4575 set isbold 1
4578 if {$isbold && [info exists iddrawn($id)]} {
4579 if {![ishighlighted $id]} {
4580 bolden $id mainfontbold
4581 if {$isbold > 1} {
4582 bolden_name $id mainfontbold
4585 if {$markingmatches} {
4586 markrowmatches $row $id
4589 set nhighlights($id) $isbold
4592 proc markrowmatches {row id} {
4593 global canv canv2 linehtag linentag commitinfo findloc
4595 set headline [lindex $commitinfo($id) 0]
4596 set author [lindex $commitinfo($id) 1]
4597 $canv delete match$row
4598 $canv2 delete match$row
4599 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4600 set m [findmatches $headline]
4601 if {$m ne {}} {
4602 markmatches $canv $row $headline $linehtag($id) $m \
4603 [$canv itemcget $linehtag($id) -font] $row
4606 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4607 set m [findmatches $author]
4608 if {$m ne {}} {
4609 markmatches $canv2 $row $author $linentag($id) $m \
4610 [$canv2 itemcget $linentag($id) -font] $row
4615 proc vrel_change {name ix op} {
4616 global highlight_related
4618 rhighlight_none
4619 if {$highlight_related ne [mc "None"]} {
4620 run drawvisible
4624 # prepare for testing whether commits are descendents or ancestors of a
4625 proc rhighlight_sel {a} {
4626 global descendent desc_todo ancestor anc_todo
4627 global highlight_related
4629 catch {unset descendent}
4630 set desc_todo [list $a]
4631 catch {unset ancestor}
4632 set anc_todo [list $a]
4633 if {$highlight_related ne [mc "None"]} {
4634 rhighlight_none
4635 run drawvisible
4639 proc rhighlight_none {} {
4640 global rhighlights
4642 catch {unset rhighlights}
4643 unbolden
4646 proc is_descendent {a} {
4647 global curview children descendent desc_todo
4649 set v $curview
4650 set la [rowofcommit $a]
4651 set todo $desc_todo
4652 set leftover {}
4653 set done 0
4654 for {set i 0} {$i < [llength $todo]} {incr i} {
4655 set do [lindex $todo $i]
4656 if {[rowofcommit $do] < $la} {
4657 lappend leftover $do
4658 continue
4660 foreach nk $children($v,$do) {
4661 if {![info exists descendent($nk)]} {
4662 set descendent($nk) 1
4663 lappend todo $nk
4664 if {$nk eq $a} {
4665 set done 1
4669 if {$done} {
4670 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4671 return
4674 set descendent($a) 0
4675 set desc_todo $leftover
4678 proc is_ancestor {a} {
4679 global curview parents ancestor anc_todo
4681 set v $curview
4682 set la [rowofcommit $a]
4683 set todo $anc_todo
4684 set leftover {}
4685 set done 0
4686 for {set i 0} {$i < [llength $todo]} {incr i} {
4687 set do [lindex $todo $i]
4688 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4689 lappend leftover $do
4690 continue
4692 foreach np $parents($v,$do) {
4693 if {![info exists ancestor($np)]} {
4694 set ancestor($np) 1
4695 lappend todo $np
4696 if {$np eq $a} {
4697 set done 1
4701 if {$done} {
4702 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4703 return
4706 set ancestor($a) 0
4707 set anc_todo $leftover
4710 proc askrelhighlight {row id} {
4711 global descendent highlight_related iddrawn rhighlights
4712 global selectedline ancestor
4714 if {$selectedline eq {}} return
4715 set isbold 0
4716 if {$highlight_related eq [mc "Descendant"] ||
4717 $highlight_related eq [mc "Not descendant"]} {
4718 if {![info exists descendent($id)]} {
4719 is_descendent $id
4721 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4722 set isbold 1
4724 } elseif {$highlight_related eq [mc "Ancestor"] ||
4725 $highlight_related eq [mc "Not ancestor"]} {
4726 if {![info exists ancestor($id)]} {
4727 is_ancestor $id
4729 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4730 set isbold 1
4733 if {[info exists iddrawn($id)]} {
4734 if {$isbold && ![ishighlighted $id]} {
4735 bolden $id mainfontbold
4738 set rhighlights($id) $isbold
4741 # Graph layout functions
4743 proc shortids {ids} {
4744 set res {}
4745 foreach id $ids {
4746 if {[llength $id] > 1} {
4747 lappend res [shortids $id]
4748 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4749 lappend res [string range $id 0 7]
4750 } else {
4751 lappend res $id
4754 return $res
4757 proc ntimes {n o} {
4758 set ret {}
4759 set o [list $o]
4760 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4761 if {($n & $mask) != 0} {
4762 set ret [concat $ret $o]
4764 set o [concat $o $o]
4766 return $ret
4769 proc ordertoken {id} {
4770 global ordertok curview varcid varcstart varctok curview parents children
4771 global nullid nullid2
4773 if {[info exists ordertok($id)]} {
4774 return $ordertok($id)
4776 set origid $id
4777 set todo {}
4778 while {1} {
4779 if {[info exists varcid($curview,$id)]} {
4780 set a $varcid($curview,$id)
4781 set p [lindex $varcstart($curview) $a]
4782 } else {
4783 set p [lindex $children($curview,$id) 0]
4785 if {[info exists ordertok($p)]} {
4786 set tok $ordertok($p)
4787 break
4789 set id [first_real_child $curview,$p]
4790 if {$id eq {}} {
4791 # it's a root
4792 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4793 break
4795 if {[llength $parents($curview,$id)] == 1} {
4796 lappend todo [list $p {}]
4797 } else {
4798 set j [lsearch -exact $parents($curview,$id) $p]
4799 if {$j < 0} {
4800 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4802 lappend todo [list $p [strrep $j]]
4805 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4806 set p [lindex $todo $i 0]
4807 append tok [lindex $todo $i 1]
4808 set ordertok($p) $tok
4810 set ordertok($origid) $tok
4811 return $tok
4814 # Work out where id should go in idlist so that order-token
4815 # values increase from left to right
4816 proc idcol {idlist id {i 0}} {
4817 set t [ordertoken $id]
4818 if {$i < 0} {
4819 set i 0
4821 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4822 if {$i > [llength $idlist]} {
4823 set i [llength $idlist]
4825 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4826 incr i
4827 } else {
4828 if {$t > [ordertoken [lindex $idlist $i]]} {
4829 while {[incr i] < [llength $idlist] &&
4830 $t >= [ordertoken [lindex $idlist $i]]} {}
4833 return $i
4836 proc initlayout {} {
4837 global rowidlist rowisopt rowfinal displayorder parentlist
4838 global numcommits canvxmax canv
4839 global nextcolor
4840 global colormap rowtextx
4842 set numcommits 0
4843 set displayorder {}
4844 set parentlist {}
4845 set nextcolor 0
4846 set rowidlist {}
4847 set rowisopt {}
4848 set rowfinal {}
4849 set canvxmax [$canv cget -width]
4850 catch {unset colormap}
4851 catch {unset rowtextx}
4852 setcanvscroll
4855 proc setcanvscroll {} {
4856 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4857 global lastscrollset lastscrollrows
4859 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4860 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4861 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4862 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4863 set lastscrollset [clock clicks -milliseconds]
4864 set lastscrollrows $numcommits
4867 proc visiblerows {} {
4868 global canv numcommits linespc
4870 set ymax [lindex [$canv cget -scrollregion] 3]
4871 if {$ymax eq {} || $ymax == 0} return
4872 set f [$canv yview]
4873 set y0 [expr {int([lindex $f 0] * $ymax)}]
4874 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4875 if {$r0 < 0} {
4876 set r0 0
4878 set y1 [expr {int([lindex $f 1] * $ymax)}]
4879 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4880 if {$r1 >= $numcommits} {
4881 set r1 [expr {$numcommits - 1}]
4883 return [list $r0 $r1]
4886 proc layoutmore {} {
4887 global commitidx viewcomplete curview
4888 global numcommits pending_select curview
4889 global lastscrollset lastscrollrows
4891 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4892 [clock clicks -milliseconds] - $lastscrollset > 500} {
4893 setcanvscroll
4895 if {[info exists pending_select] &&
4896 [commitinview $pending_select $curview]} {
4897 update
4898 selectline [rowofcommit $pending_select] 1
4900 drawvisible
4903 # With path limiting, we mightn't get the actual HEAD commit,
4904 # so ask git rev-list what is the first ancestor of HEAD that
4905 # touches a file in the path limit.
4906 proc get_viewmainhead {view} {
4907 global viewmainheadid vfilelimit viewinstances mainheadid
4909 catch {
4910 set rfd [open [concat | git rev-list -1 $mainheadid \
4911 -- $vfilelimit($view)] r]
4912 set j [reg_instance $rfd]
4913 lappend viewinstances($view) $j
4914 fconfigure $rfd -blocking 0
4915 filerun $rfd [list getviewhead $rfd $j $view]
4916 set viewmainheadid($curview) {}
4920 # git rev-list should give us just 1 line to use as viewmainheadid($view)
4921 proc getviewhead {fd inst view} {
4922 global viewmainheadid commfd curview viewinstances showlocalchanges
4924 set id {}
4925 if {[gets $fd line] < 0} {
4926 if {![eof $fd]} {
4927 return 1
4929 } elseif {[string length $line] == 40 && [string is xdigit $line]} {
4930 set id $line
4932 set viewmainheadid($view) $id
4933 close $fd
4934 unset commfd($inst)
4935 set i [lsearch -exact $viewinstances($view) $inst]
4936 if {$i >= 0} {
4937 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
4939 if {$showlocalchanges && $id ne {} && $view == $curview} {
4940 doshowlocalchanges
4942 return 0
4945 proc doshowlocalchanges {} {
4946 global curview viewmainheadid
4948 if {$viewmainheadid($curview) eq {}} return
4949 if {[commitinview $viewmainheadid($curview) $curview]} {
4950 dodiffindex
4951 } else {
4952 interestedin $viewmainheadid($curview) dodiffindex
4956 proc dohidelocalchanges {} {
4957 global nullid nullid2 lserial curview
4959 if {[commitinview $nullid $curview]} {
4960 removefakerow $nullid
4962 if {[commitinview $nullid2 $curview]} {
4963 removefakerow $nullid2
4965 incr lserial
4968 # spawn off a process to do git diff-index --cached HEAD
4969 proc dodiffindex {} {
4970 global lserial showlocalchanges vfilelimit curview
4971 global isworktree
4973 if {!$showlocalchanges || !$isworktree} return
4974 incr lserial
4975 set cmd "|git diff-index --cached HEAD"
4976 if {$vfilelimit($curview) ne {}} {
4977 set cmd [concat $cmd -- $vfilelimit($curview)]
4979 set fd [open $cmd r]
4980 fconfigure $fd -blocking 0
4981 set i [reg_instance $fd]
4982 filerun $fd [list readdiffindex $fd $lserial $i]
4985 proc readdiffindex {fd serial inst} {
4986 global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial
4987 global vfilelimit
4989 set isdiff 1
4990 if {[gets $fd line] < 0} {
4991 if {![eof $fd]} {
4992 return 1
4994 set isdiff 0
4996 # we only need to see one line and we don't really care what it says...
4997 stop_instance $inst
4999 if {$serial != $lserial} {
5000 return 0
5003 # now see if there are any local changes not checked in to the index
5004 set cmd "|git diff-files"
5005 if {$vfilelimit($curview) ne {}} {
5006 set cmd [concat $cmd -- $vfilelimit($curview)]
5008 set fd [open $cmd r]
5009 fconfigure $fd -blocking 0
5010 set i [reg_instance $fd]
5011 filerun $fd [list readdifffiles $fd $serial $i]
5013 if {$isdiff && ![commitinview $nullid2 $curview]} {
5014 # add the line for the changes in the index to the graph
5015 set hl [mc "Local changes checked in to index but not committed"]
5016 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
5017 set commitdata($nullid2) "\n $hl\n"
5018 if {[commitinview $nullid $curview]} {
5019 removefakerow $nullid
5021 insertfakerow $nullid2 $viewmainheadid($curview)
5022 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
5023 if {[commitinview $nullid $curview]} {
5024 removefakerow $nullid
5026 removefakerow $nullid2
5028 return 0
5031 proc readdifffiles {fd serial inst} {
5032 global viewmainheadid nullid nullid2 curview
5033 global commitinfo commitdata lserial
5035 set isdiff 1
5036 if {[gets $fd line] < 0} {
5037 if {![eof $fd]} {
5038 return 1
5040 set isdiff 0
5042 # we only need to see one line and we don't really care what it says...
5043 stop_instance $inst
5045 if {$serial != $lserial} {
5046 return 0
5049 if {$isdiff && ![commitinview $nullid $curview]} {
5050 # add the line for the local diff to the graph
5051 set hl [mc "Local uncommitted changes, not checked in to index"]
5052 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
5053 set commitdata($nullid) "\n $hl\n"
5054 if {[commitinview $nullid2 $curview]} {
5055 set p $nullid2
5056 } else {
5057 set p $viewmainheadid($curview)
5059 insertfakerow $nullid $p
5060 } elseif {!$isdiff && [commitinview $nullid $curview]} {
5061 removefakerow $nullid
5063 return 0
5066 proc nextuse {id row} {
5067 global curview children
5069 if {[info exists children($curview,$id)]} {
5070 foreach kid $children($curview,$id) {
5071 if {![commitinview $kid $curview]} {
5072 return -1
5074 if {[rowofcommit $kid] > $row} {
5075 return [rowofcommit $kid]
5079 if {[commitinview $id $curview]} {
5080 return [rowofcommit $id]
5082 return -1
5085 proc prevuse {id row} {
5086 global curview children
5088 set ret -1
5089 if {[info exists children($curview,$id)]} {
5090 foreach kid $children($curview,$id) {
5091 if {![commitinview $kid $curview]} break
5092 if {[rowofcommit $kid] < $row} {
5093 set ret [rowofcommit $kid]
5097 return $ret
5100 proc make_idlist {row} {
5101 global displayorder parentlist uparrowlen downarrowlen mingaplen
5102 global commitidx curview children
5104 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
5105 if {$r < 0} {
5106 set r 0
5108 set ra [expr {$row - $downarrowlen}]
5109 if {$ra < 0} {
5110 set ra 0
5112 set rb [expr {$row + $uparrowlen}]
5113 if {$rb > $commitidx($curview)} {
5114 set rb $commitidx($curview)
5116 make_disporder $r [expr {$rb + 1}]
5117 set ids {}
5118 for {} {$r < $ra} {incr r} {
5119 set nextid [lindex $displayorder [expr {$r + 1}]]
5120 foreach p [lindex $parentlist $r] {
5121 if {$p eq $nextid} continue
5122 set rn [nextuse $p $r]
5123 if {$rn >= $row &&
5124 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
5125 lappend ids [list [ordertoken $p] $p]
5129 for {} {$r < $row} {incr r} {
5130 set nextid [lindex $displayorder [expr {$r + 1}]]
5131 foreach p [lindex $parentlist $r] {
5132 if {$p eq $nextid} continue
5133 set rn [nextuse $p $r]
5134 if {$rn < 0 || $rn >= $row} {
5135 lappend ids [list [ordertoken $p] $p]
5139 set id [lindex $displayorder $row]
5140 lappend ids [list [ordertoken $id] $id]
5141 while {$r < $rb} {
5142 foreach p [lindex $parentlist $r] {
5143 set firstkid [lindex $children($curview,$p) 0]
5144 if {[rowofcommit $firstkid] < $row} {
5145 lappend ids [list [ordertoken $p] $p]
5148 incr r
5149 set id [lindex $displayorder $r]
5150 if {$id ne {}} {
5151 set firstkid [lindex $children($curview,$id) 0]
5152 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
5153 lappend ids [list [ordertoken $id] $id]
5157 set idlist {}
5158 foreach idx [lsort -unique $ids] {
5159 lappend idlist [lindex $idx 1]
5161 return $idlist
5164 proc rowsequal {a b} {
5165 while {[set i [lsearch -exact $a {}]] >= 0} {
5166 set a [lreplace $a $i $i]
5168 while {[set i [lsearch -exact $b {}]] >= 0} {
5169 set b [lreplace $b $i $i]
5171 return [expr {$a eq $b}]
5174 proc makeupline {id row rend col} {
5175 global rowidlist uparrowlen downarrowlen mingaplen
5177 for {set r $rend} {1} {set r $rstart} {
5178 set rstart [prevuse $id $r]
5179 if {$rstart < 0} return
5180 if {$rstart < $row} break
5182 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
5183 set rstart [expr {$rend - $uparrowlen - 1}]
5185 for {set r $rstart} {[incr r] <= $row} {} {
5186 set idlist [lindex $rowidlist $r]
5187 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
5188 set col [idcol $idlist $id $col]
5189 lset rowidlist $r [linsert $idlist $col $id]
5190 changedrow $r
5195 proc layoutrows {row endrow} {
5196 global rowidlist rowisopt rowfinal displayorder
5197 global uparrowlen downarrowlen maxwidth mingaplen
5198 global children parentlist
5199 global commitidx viewcomplete curview
5201 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
5202 set idlist {}
5203 if {$row > 0} {
5204 set rm1 [expr {$row - 1}]
5205 foreach id [lindex $rowidlist $rm1] {
5206 if {$id ne {}} {
5207 lappend idlist $id
5210 set final [lindex $rowfinal $rm1]
5212 for {} {$row < $endrow} {incr row} {
5213 set rm1 [expr {$row - 1}]
5214 if {$rm1 < 0 || $idlist eq {}} {
5215 set idlist [make_idlist $row]
5216 set final 1
5217 } else {
5218 set id [lindex $displayorder $rm1]
5219 set col [lsearch -exact $idlist $id]
5220 set idlist [lreplace $idlist $col $col]
5221 foreach p [lindex $parentlist $rm1] {
5222 if {[lsearch -exact $idlist $p] < 0} {
5223 set col [idcol $idlist $p $col]
5224 set idlist [linsert $idlist $col $p]
5225 # if not the first child, we have to insert a line going up
5226 if {$id ne [lindex $children($curview,$p) 0]} {
5227 makeupline $p $rm1 $row $col
5231 set id [lindex $displayorder $row]
5232 if {$row > $downarrowlen} {
5233 set termrow [expr {$row - $downarrowlen - 1}]
5234 foreach p [lindex $parentlist $termrow] {
5235 set i [lsearch -exact $idlist $p]
5236 if {$i < 0} continue
5237 set nr [nextuse $p $termrow]
5238 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
5239 set idlist [lreplace $idlist $i $i]
5243 set col [lsearch -exact $idlist $id]
5244 if {$col < 0} {
5245 set col [idcol $idlist $id]
5246 set idlist [linsert $idlist $col $id]
5247 if {$children($curview,$id) ne {}} {
5248 makeupline $id $rm1 $row $col
5251 set r [expr {$row + $uparrowlen - 1}]
5252 if {$r < $commitidx($curview)} {
5253 set x $col
5254 foreach p [lindex $parentlist $r] {
5255 if {[lsearch -exact $idlist $p] >= 0} continue
5256 set fk [lindex $children($curview,$p) 0]
5257 if {[rowofcommit $fk] < $row} {
5258 set x [idcol $idlist $p $x]
5259 set idlist [linsert $idlist $x $p]
5262 if {[incr r] < $commitidx($curview)} {
5263 set p [lindex $displayorder $r]
5264 if {[lsearch -exact $idlist $p] < 0} {
5265 set fk [lindex $children($curview,$p) 0]
5266 if {$fk ne {} && [rowofcommit $fk] < $row} {
5267 set x [idcol $idlist $p $x]
5268 set idlist [linsert $idlist $x $p]
5274 if {$final && !$viewcomplete($curview) &&
5275 $row + $uparrowlen + $mingaplen + $downarrowlen
5276 >= $commitidx($curview)} {
5277 set final 0
5279 set l [llength $rowidlist]
5280 if {$row == $l} {
5281 lappend rowidlist $idlist
5282 lappend rowisopt 0
5283 lappend rowfinal $final
5284 } elseif {$row < $l} {
5285 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
5286 lset rowidlist $row $idlist
5287 changedrow $row
5289 lset rowfinal $row $final
5290 } else {
5291 set pad [ntimes [expr {$row - $l}] {}]
5292 set rowidlist [concat $rowidlist $pad]
5293 lappend rowidlist $idlist
5294 set rowfinal [concat $rowfinal $pad]
5295 lappend rowfinal $final
5296 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
5299 return $row
5302 proc changedrow {row} {
5303 global displayorder iddrawn rowisopt need_redisplay
5305 set l [llength $rowisopt]
5306 if {$row < $l} {
5307 lset rowisopt $row 0
5308 if {$row + 1 < $l} {
5309 lset rowisopt [expr {$row + 1}] 0
5310 if {$row + 2 < $l} {
5311 lset rowisopt [expr {$row + 2}] 0
5315 set id [lindex $displayorder $row]
5316 if {[info exists iddrawn($id)]} {
5317 set need_redisplay 1
5321 proc insert_pad {row col npad} {
5322 global rowidlist
5324 set pad [ntimes $npad {}]
5325 set idlist [lindex $rowidlist $row]
5326 set bef [lrange $idlist 0 [expr {$col - 1}]]
5327 set aft [lrange $idlist $col end]
5328 set i [lsearch -exact $aft {}]
5329 if {$i > 0} {
5330 set aft [lreplace $aft $i $i]
5332 lset rowidlist $row [concat $bef $pad $aft]
5333 changedrow $row
5336 proc optimize_rows {row col endrow} {
5337 global rowidlist rowisopt displayorder curview children
5339 if {$row < 1} {
5340 set row 1
5342 for {} {$row < $endrow} {incr row; set col 0} {
5343 if {[lindex $rowisopt $row]} continue
5344 set haspad 0
5345 set y0 [expr {$row - 1}]
5346 set ym [expr {$row - 2}]
5347 set idlist [lindex $rowidlist $row]
5348 set previdlist [lindex $rowidlist $y0]
5349 if {$idlist eq {} || $previdlist eq {}} continue
5350 if {$ym >= 0} {
5351 set pprevidlist [lindex $rowidlist $ym]
5352 if {$pprevidlist eq {}} continue
5353 } else {
5354 set pprevidlist {}
5356 set x0 -1
5357 set xm -1
5358 for {} {$col < [llength $idlist]} {incr col} {
5359 set id [lindex $idlist $col]
5360 if {[lindex $previdlist $col] eq $id} continue
5361 if {$id eq {}} {
5362 set haspad 1
5363 continue
5365 set x0 [lsearch -exact $previdlist $id]
5366 if {$x0 < 0} continue
5367 set z [expr {$x0 - $col}]
5368 set isarrow 0
5369 set z0 {}
5370 if {$ym >= 0} {
5371 set xm [lsearch -exact $pprevidlist $id]
5372 if {$xm >= 0} {
5373 set z0 [expr {$xm - $x0}]
5376 if {$z0 eq {}} {
5377 # if row y0 is the first child of $id then it's not an arrow
5378 if {[lindex $children($curview,$id) 0] ne
5379 [lindex $displayorder $y0]} {
5380 set isarrow 1
5383 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5384 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5385 set isarrow 1
5387 # Looking at lines from this row to the previous row,
5388 # make them go straight up if they end in an arrow on
5389 # the previous row; otherwise make them go straight up
5390 # or at 45 degrees.
5391 if {$z < -1 || ($z < 0 && $isarrow)} {
5392 # Line currently goes left too much;
5393 # insert pads in the previous row, then optimize it
5394 set npad [expr {-1 - $z + $isarrow}]
5395 insert_pad $y0 $x0 $npad
5396 if {$y0 > 0} {
5397 optimize_rows $y0 $x0 $row
5399 set previdlist [lindex $rowidlist $y0]
5400 set x0 [lsearch -exact $previdlist $id]
5401 set z [expr {$x0 - $col}]
5402 if {$z0 ne {}} {
5403 set pprevidlist [lindex $rowidlist $ym]
5404 set xm [lsearch -exact $pprevidlist $id]
5405 set z0 [expr {$xm - $x0}]
5407 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5408 # Line currently goes right too much;
5409 # insert pads in this line
5410 set npad [expr {$z - 1 + $isarrow}]
5411 insert_pad $row $col $npad
5412 set idlist [lindex $rowidlist $row]
5413 incr col $npad
5414 set z [expr {$x0 - $col}]
5415 set haspad 1
5417 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5418 # this line links to its first child on row $row-2
5419 set id [lindex $displayorder $ym]
5420 set xc [lsearch -exact $pprevidlist $id]
5421 if {$xc >= 0} {
5422 set z0 [expr {$xc - $x0}]
5425 # avoid lines jigging left then immediately right
5426 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5427 insert_pad $y0 $x0 1
5428 incr x0
5429 optimize_rows $y0 $x0 $row
5430 set previdlist [lindex $rowidlist $y0]
5433 if {!$haspad} {
5434 # Find the first column that doesn't have a line going right
5435 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5436 set id [lindex $idlist $col]
5437 if {$id eq {}} break
5438 set x0 [lsearch -exact $previdlist $id]
5439 if {$x0 < 0} {
5440 # check if this is the link to the first child
5441 set kid [lindex $displayorder $y0]
5442 if {[lindex $children($curview,$id) 0] eq $kid} {
5443 # it is, work out offset to child
5444 set x0 [lsearch -exact $previdlist $kid]
5447 if {$x0 <= $col} break
5449 # Insert a pad at that column as long as it has a line and
5450 # isn't the last column
5451 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5452 set idlist [linsert $idlist $col {}]
5453 lset rowidlist $row $idlist
5454 changedrow $row
5460 proc xc {row col} {
5461 global canvx0 linespc
5462 return [expr {$canvx0 + $col * $linespc}]
5465 proc yc {row} {
5466 global canvy0 linespc
5467 return [expr {$canvy0 + $row * $linespc}]
5470 proc linewidth {id} {
5471 global thickerline lthickness
5473 set wid $lthickness
5474 if {[info exists thickerline] && $id eq $thickerline} {
5475 set wid [expr {2 * $lthickness}]
5477 return $wid
5480 proc rowranges {id} {
5481 global curview children uparrowlen downarrowlen
5482 global rowidlist
5484 set kids $children($curview,$id)
5485 if {$kids eq {}} {
5486 return {}
5488 set ret {}
5489 lappend kids $id
5490 foreach child $kids {
5491 if {![commitinview $child $curview]} break
5492 set row [rowofcommit $child]
5493 if {![info exists prev]} {
5494 lappend ret [expr {$row + 1}]
5495 } else {
5496 if {$row <= $prevrow} {
5497 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5499 # see if the line extends the whole way from prevrow to row
5500 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5501 [lsearch -exact [lindex $rowidlist \
5502 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5503 # it doesn't, see where it ends
5504 set r [expr {$prevrow + $downarrowlen}]
5505 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5506 while {[incr r -1] > $prevrow &&
5507 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5508 } else {
5509 while {[incr r] <= $row &&
5510 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5511 incr r -1
5513 lappend ret $r
5514 # see where it starts up again
5515 set r [expr {$row - $uparrowlen}]
5516 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5517 while {[incr r] < $row &&
5518 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5519 } else {
5520 while {[incr r -1] >= $prevrow &&
5521 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5522 incr r
5524 lappend ret $r
5527 if {$child eq $id} {
5528 lappend ret $row
5530 set prev $child
5531 set prevrow $row
5533 return $ret
5536 proc drawlineseg {id row endrow arrowlow} {
5537 global rowidlist displayorder iddrawn linesegs
5538 global canv colormap linespc curview maxlinelen parentlist
5540 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5541 set le [expr {$row + 1}]
5542 set arrowhigh 1
5543 while {1} {
5544 set c [lsearch -exact [lindex $rowidlist $le] $id]
5545 if {$c < 0} {
5546 incr le -1
5547 break
5549 lappend cols $c
5550 set x [lindex $displayorder $le]
5551 if {$x eq $id} {
5552 set arrowhigh 0
5553 break
5555 if {[info exists iddrawn($x)] || $le == $endrow} {
5556 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5557 if {$c >= 0} {
5558 lappend cols $c
5559 set arrowhigh 0
5561 break
5563 incr le
5565 if {$le <= $row} {
5566 return $row
5569 set lines {}
5570 set i 0
5571 set joinhigh 0
5572 if {[info exists linesegs($id)]} {
5573 set lines $linesegs($id)
5574 foreach li $lines {
5575 set r0 [lindex $li 0]
5576 if {$r0 > $row} {
5577 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5578 set joinhigh 1
5580 break
5582 incr i
5585 set joinlow 0
5586 if {$i > 0} {
5587 set li [lindex $lines [expr {$i-1}]]
5588 set r1 [lindex $li 1]
5589 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5590 set joinlow 1
5594 set x [lindex $cols [expr {$le - $row}]]
5595 set xp [lindex $cols [expr {$le - 1 - $row}]]
5596 set dir [expr {$xp - $x}]
5597 if {$joinhigh} {
5598 set ith [lindex $lines $i 2]
5599 set coords [$canv coords $ith]
5600 set ah [$canv itemcget $ith -arrow]
5601 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5602 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5603 if {$x2 ne {} && $x - $x2 == $dir} {
5604 set coords [lrange $coords 0 end-2]
5606 } else {
5607 set coords [list [xc $le $x] [yc $le]]
5609 if {$joinlow} {
5610 set itl [lindex $lines [expr {$i-1}] 2]
5611 set al [$canv itemcget $itl -arrow]
5612 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5613 } elseif {$arrowlow} {
5614 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5615 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5616 set arrowlow 0
5619 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5620 for {set y $le} {[incr y -1] > $row} {} {
5621 set x $xp
5622 set xp [lindex $cols [expr {$y - 1 - $row}]]
5623 set ndir [expr {$xp - $x}]
5624 if {$dir != $ndir || $xp < 0} {
5625 lappend coords [xc $y $x] [yc $y]
5627 set dir $ndir
5629 if {!$joinlow} {
5630 if {$xp < 0} {
5631 # join parent line to first child
5632 set ch [lindex $displayorder $row]
5633 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5634 if {$xc < 0} {
5635 puts "oops: drawlineseg: child $ch not on row $row"
5636 } elseif {$xc != $x} {
5637 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5638 set d [expr {int(0.5 * $linespc)}]
5639 set x1 [xc $row $x]
5640 if {$xc < $x} {
5641 set x2 [expr {$x1 - $d}]
5642 } else {
5643 set x2 [expr {$x1 + $d}]
5645 set y2 [yc $row]
5646 set y1 [expr {$y2 + $d}]
5647 lappend coords $x1 $y1 $x2 $y2
5648 } elseif {$xc < $x - 1} {
5649 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5650 } elseif {$xc > $x + 1} {
5651 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5653 set x $xc
5655 lappend coords [xc $row $x] [yc $row]
5656 } else {
5657 set xn [xc $row $xp]
5658 set yn [yc $row]
5659 lappend coords $xn $yn
5661 if {!$joinhigh} {
5662 assigncolor $id
5663 set t [$canv create line $coords -width [linewidth $id] \
5664 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5665 $canv lower $t
5666 bindline $t $id
5667 set lines [linsert $lines $i [list $row $le $t]]
5668 } else {
5669 $canv coords $ith $coords
5670 if {$arrow ne $ah} {
5671 $canv itemconf $ith -arrow $arrow
5673 lset lines $i 0 $row
5675 } else {
5676 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5677 set ndir [expr {$xo - $xp}]
5678 set clow [$canv coords $itl]
5679 if {$dir == $ndir} {
5680 set clow [lrange $clow 2 end]
5682 set coords [concat $coords $clow]
5683 if {!$joinhigh} {
5684 lset lines [expr {$i-1}] 1 $le
5685 } else {
5686 # coalesce two pieces
5687 $canv delete $ith
5688 set b [lindex $lines [expr {$i-1}] 0]
5689 set e [lindex $lines $i 1]
5690 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5692 $canv coords $itl $coords
5693 if {$arrow ne $al} {
5694 $canv itemconf $itl -arrow $arrow
5698 set linesegs($id) $lines
5699 return $le
5702 proc drawparentlinks {id row} {
5703 global rowidlist canv colormap curview parentlist
5704 global idpos linespc
5706 set rowids [lindex $rowidlist $row]
5707 set col [lsearch -exact $rowids $id]
5708 if {$col < 0} return
5709 set olds [lindex $parentlist $row]
5710 set row2 [expr {$row + 1}]
5711 set x [xc $row $col]
5712 set y [yc $row]
5713 set y2 [yc $row2]
5714 set d [expr {int(0.5 * $linespc)}]
5715 set ymid [expr {$y + $d}]
5716 set ids [lindex $rowidlist $row2]
5717 # rmx = right-most X coord used
5718 set rmx 0
5719 foreach p $olds {
5720 set i [lsearch -exact $ids $p]
5721 if {$i < 0} {
5722 puts "oops, parent $p of $id not in list"
5723 continue
5725 set x2 [xc $row2 $i]
5726 if {$x2 > $rmx} {
5727 set rmx $x2
5729 set j [lsearch -exact $rowids $p]
5730 if {$j < 0} {
5731 # drawlineseg will do this one for us
5732 continue
5734 assigncolor $p
5735 # should handle duplicated parents here...
5736 set coords [list $x $y]
5737 if {$i != $col} {
5738 # if attaching to a vertical segment, draw a smaller
5739 # slant for visual distinctness
5740 if {$i == $j} {
5741 if {$i < $col} {
5742 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5743 } else {
5744 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5746 } elseif {$i < $col && $i < $j} {
5747 # segment slants towards us already
5748 lappend coords [xc $row $j] $y
5749 } else {
5750 if {$i < $col - 1} {
5751 lappend coords [expr {$x2 + $linespc}] $y
5752 } elseif {$i > $col + 1} {
5753 lappend coords [expr {$x2 - $linespc}] $y
5755 lappend coords $x2 $y2
5757 } else {
5758 lappend coords $x2 $y2
5760 set t [$canv create line $coords -width [linewidth $p] \
5761 -fill $colormap($p) -tags lines.$p]
5762 $canv lower $t
5763 bindline $t $p
5765 if {$rmx > [lindex $idpos($id) 1]} {
5766 lset idpos($id) 1 $rmx
5767 redrawtags $id
5771 proc drawlines {id} {
5772 global canv
5774 $canv itemconf lines.$id -width [linewidth $id]
5777 proc drawcmittext {id row col} {
5778 global linespc canv canv2 canv3 fgcolor curview
5779 global cmitlisted commitinfo rowidlist parentlist
5780 global rowtextx idpos idtags idheads idotherrefs
5781 global linehtag linentag linedtag selectedline
5782 global canvxmax boldids boldnameids fgcolor markedid
5783 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5785 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5786 set listed $cmitlisted($curview,$id)
5787 if {$id eq $nullid} {
5788 set ofill red
5789 } elseif {$id eq $nullid2} {
5790 set ofill green
5791 } elseif {$id eq $mainheadid} {
5792 set ofill yellow
5793 } else {
5794 set ofill [lindex $circlecolors $listed]
5796 set x [xc $row $col]
5797 set y [yc $row]
5798 set orad [expr {$linespc / 3}]
5799 if {$listed <= 2} {
5800 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5801 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5802 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5803 } elseif {$listed == 3} {
5804 # triangle pointing left for left-side commits
5805 set t [$canv create polygon \
5806 [expr {$x - $orad}] $y \
5807 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5808 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5809 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5810 } else {
5811 # triangle pointing right for right-side commits
5812 set t [$canv create polygon \
5813 [expr {$x + $orad - 1}] $y \
5814 [expr {$x - $orad}] [expr {$y - $orad}] \
5815 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5816 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5818 set circleitem($row) $t
5819 $canv raise $t
5820 $canv bind $t <1> {selcanvline {} %x %y}
5821 set rmx [llength [lindex $rowidlist $row]]
5822 set olds [lindex $parentlist $row]
5823 if {$olds ne {}} {
5824 set nextids [lindex $rowidlist [expr {$row + 1}]]
5825 foreach p $olds {
5826 set i [lsearch -exact $nextids $p]
5827 if {$i > $rmx} {
5828 set rmx $i
5832 set xt [xc $row $rmx]
5833 set rowtextx($row) $xt
5834 set idpos($id) [list $x $xt $y]
5835 if {[info exists idtags($id)] || [info exists idheads($id)]
5836 || [info exists idotherrefs($id)]} {
5837 set xt [drawtags $id $x $xt $y]
5839 set headline [lindex $commitinfo($id) 0]
5840 set name [lindex $commitinfo($id) 1]
5841 set date [lindex $commitinfo($id) 2]
5842 set date [formatdate $date]
5843 set font mainfont
5844 set nfont mainfont
5845 set isbold [ishighlighted $id]
5846 if {$isbold > 0} {
5847 lappend boldids $id
5848 set font mainfontbold
5849 if {$isbold > 1} {
5850 lappend boldnameids $id
5851 set nfont mainfontbold
5854 set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
5855 -text $headline -font $font -tags text]
5856 $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
5857 set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5858 -text $name -font $nfont -tags text]
5859 set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5860 -text $date -font mainfont -tags text]
5861 if {$selectedline == $row} {
5862 make_secsel $id
5864 if {[info exists markedid] && $markedid eq $id} {
5865 make_idmark $id
5867 set xr [expr {$xt + [font measure $font $headline]}]
5868 if {$xr > $canvxmax} {
5869 set canvxmax $xr
5870 setcanvscroll
5874 proc drawcmitrow {row} {
5875 global displayorder rowidlist nrows_drawn
5876 global iddrawn markingmatches
5877 global commitinfo numcommits
5878 global filehighlight fhighlights findpattern nhighlights
5879 global hlview vhighlights
5880 global highlight_related rhighlights
5882 if {$row >= $numcommits} return
5884 set id [lindex $displayorder $row]
5885 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5886 askvhighlight $row $id
5888 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5889 askfilehighlight $row $id
5891 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5892 askfindhighlight $row $id
5894 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5895 askrelhighlight $row $id
5897 if {![info exists iddrawn($id)]} {
5898 set col [lsearch -exact [lindex $rowidlist $row] $id]
5899 if {$col < 0} {
5900 puts "oops, row $row id $id not in list"
5901 return
5903 if {![info exists commitinfo($id)]} {
5904 getcommit $id
5906 assigncolor $id
5907 drawcmittext $id $row $col
5908 set iddrawn($id) 1
5909 incr nrows_drawn
5911 if {$markingmatches} {
5912 markrowmatches $row $id
5916 proc drawcommits {row {endrow {}}} {
5917 global numcommits iddrawn displayorder curview need_redisplay
5918 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5920 if {$row < 0} {
5921 set row 0
5923 if {$endrow eq {}} {
5924 set endrow $row
5926 if {$endrow >= $numcommits} {
5927 set endrow [expr {$numcommits - 1}]
5930 set rl1 [expr {$row - $downarrowlen - 3}]
5931 if {$rl1 < 0} {
5932 set rl1 0
5934 set ro1 [expr {$row - 3}]
5935 if {$ro1 < 0} {
5936 set ro1 0
5938 set r2 [expr {$endrow + $uparrowlen + 3}]
5939 if {$r2 > $numcommits} {
5940 set r2 $numcommits
5942 for {set r $rl1} {$r < $r2} {incr r} {
5943 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5944 if {$rl1 < $r} {
5945 layoutrows $rl1 $r
5947 set rl1 [expr {$r + 1}]
5950 if {$rl1 < $r} {
5951 layoutrows $rl1 $r
5953 optimize_rows $ro1 0 $r2
5954 if {$need_redisplay || $nrows_drawn > 2000} {
5955 clear_display
5958 # make the lines join to already-drawn rows either side
5959 set r [expr {$row - 1}]
5960 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5961 set r $row
5963 set er [expr {$endrow + 1}]
5964 if {$er >= $numcommits ||
5965 ![info exists iddrawn([lindex $displayorder $er])]} {
5966 set er $endrow
5968 for {} {$r <= $er} {incr r} {
5969 set id [lindex $displayorder $r]
5970 set wasdrawn [info exists iddrawn($id)]
5971 drawcmitrow $r
5972 if {$r == $er} break
5973 set nextid [lindex $displayorder [expr {$r + 1}]]
5974 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5975 drawparentlinks $id $r
5977 set rowids [lindex $rowidlist $r]
5978 foreach lid $rowids {
5979 if {$lid eq {}} continue
5980 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5981 if {$lid eq $id} {
5982 # see if this is the first child of any of its parents
5983 foreach p [lindex $parentlist $r] {
5984 if {[lsearch -exact $rowids $p] < 0} {
5985 # make this line extend up to the child
5986 set lineend($p) [drawlineseg $p $r $er 0]
5989 } else {
5990 set lineend($lid) [drawlineseg $lid $r $er 1]
5996 proc undolayout {row} {
5997 global uparrowlen mingaplen downarrowlen
5998 global rowidlist rowisopt rowfinal need_redisplay
6000 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
6001 if {$r < 0} {
6002 set r 0
6004 if {[llength $rowidlist] > $r} {
6005 incr r -1
6006 set rowidlist [lrange $rowidlist 0 $r]
6007 set rowfinal [lrange $rowfinal 0 $r]
6008 set rowisopt [lrange $rowisopt 0 $r]
6009 set need_redisplay 1
6010 run drawvisible
6014 proc drawvisible {} {
6015 global canv linespc curview vrowmod selectedline targetrow targetid
6016 global need_redisplay cscroll numcommits
6018 set fs [$canv yview]
6019 set ymax [lindex [$canv cget -scrollregion] 3]
6020 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
6021 set f0 [lindex $fs 0]
6022 set f1 [lindex $fs 1]
6023 set y0 [expr {int($f0 * $ymax)}]
6024 set y1 [expr {int($f1 * $ymax)}]
6026 if {[info exists targetid]} {
6027 if {[commitinview $targetid $curview]} {
6028 set r [rowofcommit $targetid]
6029 if {$r != $targetrow} {
6030 # Fix up the scrollregion and change the scrolling position
6031 # now that our target row has moved.
6032 set diff [expr {($r - $targetrow) * $linespc}]
6033 set targetrow $r
6034 setcanvscroll
6035 set ymax [lindex [$canv cget -scrollregion] 3]
6036 incr y0 $diff
6037 incr y1 $diff
6038 set f0 [expr {$y0 / $ymax}]
6039 set f1 [expr {$y1 / $ymax}]
6040 allcanvs yview moveto $f0
6041 $cscroll set $f0 $f1
6042 set need_redisplay 1
6044 } else {
6045 unset targetid
6049 set row [expr {int(($y0 - 3) / $linespc) - 1}]
6050 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
6051 if {$endrow >= $vrowmod($curview)} {
6052 update_arcrows $curview
6054 if {$selectedline ne {} &&
6055 $row <= $selectedline && $selectedline <= $endrow} {
6056 set targetrow $selectedline
6057 } elseif {[info exists targetid]} {
6058 set targetrow [expr {int(($row + $endrow) / 2)}]
6060 if {[info exists targetrow]} {
6061 if {$targetrow >= $numcommits} {
6062 set targetrow [expr {$numcommits - 1}]
6064 set targetid [commitonrow $targetrow]
6066 drawcommits $row $endrow
6069 proc clear_display {} {
6070 global iddrawn linesegs need_redisplay nrows_drawn
6071 global vhighlights fhighlights nhighlights rhighlights
6072 global linehtag linentag linedtag boldids boldnameids
6074 allcanvs delete all
6075 catch {unset iddrawn}
6076 catch {unset linesegs}
6077 catch {unset linehtag}
6078 catch {unset linentag}
6079 catch {unset linedtag}
6080 set boldids {}
6081 set boldnameids {}
6082 catch {unset vhighlights}
6083 catch {unset fhighlights}
6084 catch {unset nhighlights}
6085 catch {unset rhighlights}
6086 set need_redisplay 0
6087 set nrows_drawn 0
6090 proc findcrossings {id} {
6091 global rowidlist parentlist numcommits displayorder
6093 set cross {}
6094 set ccross {}
6095 foreach {s e} [rowranges $id] {
6096 if {$e >= $numcommits} {
6097 set e [expr {$numcommits - 1}]
6099 if {$e <= $s} continue
6100 for {set row $e} {[incr row -1] >= $s} {} {
6101 set x [lsearch -exact [lindex $rowidlist $row] $id]
6102 if {$x < 0} break
6103 set olds [lindex $parentlist $row]
6104 set kid [lindex $displayorder $row]
6105 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
6106 if {$kidx < 0} continue
6107 set nextrow [lindex $rowidlist [expr {$row + 1}]]
6108 foreach p $olds {
6109 set px [lsearch -exact $nextrow $p]
6110 if {$px < 0} continue
6111 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
6112 if {[lsearch -exact $ccross $p] >= 0} continue
6113 if {$x == $px + ($kidx < $px? -1: 1)} {
6114 lappend ccross $p
6115 } elseif {[lsearch -exact $cross $p] < 0} {
6116 lappend cross $p
6122 return [concat $ccross {{}} $cross]
6125 proc assigncolor {id} {
6126 global colormap colors nextcolor
6127 global parents children children curview
6129 if {[info exists colormap($id)]} return
6130 set ncolors [llength $colors]
6131 if {[info exists children($curview,$id)]} {
6132 set kids $children($curview,$id)
6133 } else {
6134 set kids {}
6136 if {[llength $kids] == 1} {
6137 set child [lindex $kids 0]
6138 if {[info exists colormap($child)]
6139 && [llength $parents($curview,$child)] == 1} {
6140 set colormap($id) $colormap($child)
6141 return
6144 set badcolors {}
6145 set origbad {}
6146 foreach x [findcrossings $id] {
6147 if {$x eq {}} {
6148 # delimiter between corner crossings and other crossings
6149 if {[llength $badcolors] >= $ncolors - 1} break
6150 set origbad $badcolors
6152 if {[info exists colormap($x)]
6153 && [lsearch -exact $badcolors $colormap($x)] < 0} {
6154 lappend badcolors $colormap($x)
6157 if {[llength $badcolors] >= $ncolors} {
6158 set badcolors $origbad
6160 set origbad $badcolors
6161 if {[llength $badcolors] < $ncolors - 1} {
6162 foreach child $kids {
6163 if {[info exists colormap($child)]
6164 && [lsearch -exact $badcolors $colormap($child)] < 0} {
6165 lappend badcolors $colormap($child)
6167 foreach p $parents($curview,$child) {
6168 if {[info exists colormap($p)]
6169 && [lsearch -exact $badcolors $colormap($p)] < 0} {
6170 lappend badcolors $colormap($p)
6174 if {[llength $badcolors] >= $ncolors} {
6175 set badcolors $origbad
6178 for {set i 0} {$i <= $ncolors} {incr i} {
6179 set c [lindex $colors $nextcolor]
6180 if {[incr nextcolor] >= $ncolors} {
6181 set nextcolor 0
6183 if {[lsearch -exact $badcolors $c]} break
6185 set colormap($id) $c
6188 proc bindline {t id} {
6189 global canv
6191 $canv bind $t <Enter> "lineenter %x %y $id"
6192 $canv bind $t <Motion> "linemotion %x %y $id"
6193 $canv bind $t <Leave> "lineleave $id"
6194 $canv bind $t <Button-1> "lineclick %x %y $id 1"
6197 proc drawtags {id x xt y1} {
6198 global idtags idheads idotherrefs mainhead
6199 global linespc lthickness
6200 global canv rowtextx curview fgcolor bgcolor ctxbut
6202 set marks {}
6203 set ntags 0
6204 set nheads 0
6205 if {[info exists idtags($id)]} {
6206 set marks $idtags($id)
6207 set ntags [llength $marks]
6209 if {[info exists idheads($id)]} {
6210 set marks [concat $marks $idheads($id)]
6211 set nheads [llength $idheads($id)]
6213 if {[info exists idotherrefs($id)]} {
6214 set marks [concat $marks $idotherrefs($id)]
6216 if {$marks eq {}} {
6217 return $xt
6220 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
6221 set yt [expr {$y1 - 0.5 * $linespc}]
6222 set yb [expr {$yt + $linespc - 1}]
6223 set xvals {}
6224 set wvals {}
6225 set i -1
6226 foreach tag $marks {
6227 incr i
6228 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
6229 set wid [font measure mainfontbold $tag]
6230 } else {
6231 set wid [font measure mainfont $tag]
6233 lappend xvals $xt
6234 lappend wvals $wid
6235 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
6237 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
6238 -width $lthickness -fill black -tags tag.$id]
6239 $canv lower $t
6240 foreach tag $marks x $xvals wid $wvals {
6241 set xl [expr {$x + $delta}]
6242 set xr [expr {$x + $delta + $wid + $lthickness}]
6243 set font mainfont
6244 if {[incr ntags -1] >= 0} {
6245 # draw a tag
6246 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
6247 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
6248 -width 1 -outline black -fill yellow -tags tag.$id]
6249 $canv bind $t <1> [list showtag $tag 1]
6250 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
6251 } else {
6252 # draw a head or other ref
6253 if {[incr nheads -1] >= 0} {
6254 set col green
6255 if {$tag eq $mainhead} {
6256 set font mainfontbold
6258 } else {
6259 set col "#ddddff"
6261 set xl [expr {$xl - $delta/2}]
6262 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
6263 -width 1 -outline black -fill $col -tags tag.$id
6264 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
6265 set rwid [font measure mainfont $remoteprefix]
6266 set xi [expr {$x + 1}]
6267 set yti [expr {$yt + 1}]
6268 set xri [expr {$x + $rwid}]
6269 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
6270 -width 0 -fill "#ffddaa" -tags tag.$id
6273 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
6274 -font $font -tags [list tag.$id text]]
6275 if {$ntags >= 0} {
6276 $canv bind $t <1> [list showtag $tag 1]
6277 } elseif {$nheads >= 0} {
6278 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
6281 return $xt
6284 proc xcoord {i level ln} {
6285 global canvx0 xspc1 xspc2
6287 set x [expr {$canvx0 + $i * $xspc1($ln)}]
6288 if {$i > 0 && $i == $level} {
6289 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
6290 } elseif {$i > $level} {
6291 set x [expr {$x + $xspc2 - $xspc1($ln)}]
6293 return $x
6296 proc show_status {msg} {
6297 global canv fgcolor
6299 clear_display
6300 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
6301 -tags text -fill $fgcolor
6304 # Don't change the text pane cursor if it is currently the hand cursor,
6305 # showing that we are over a sha1 ID link.
6306 proc settextcursor {c} {
6307 global ctext curtextcursor
6309 if {[$ctext cget -cursor] == $curtextcursor} {
6310 $ctext config -cursor $c
6312 set curtextcursor $c
6315 proc nowbusy {what {name {}}} {
6316 global isbusy busyname statusw
6318 if {[array names isbusy] eq {}} {
6319 . config -cursor watch
6320 settextcursor watch
6322 set isbusy($what) 1
6323 set busyname($what) $name
6324 if {$name ne {}} {
6325 $statusw conf -text $name
6329 proc notbusy {what} {
6330 global isbusy maincursor textcursor busyname statusw
6332 catch {
6333 unset isbusy($what)
6334 if {$busyname($what) ne {} &&
6335 [$statusw cget -text] eq $busyname($what)} {
6336 $statusw conf -text {}
6339 if {[array names isbusy] eq {}} {
6340 . config -cursor $maincursor
6341 settextcursor $textcursor
6345 proc findmatches {f} {
6346 global findtype findstring
6347 if {$findtype == [mc "Regexp"]} {
6348 set matches [regexp -indices -all -inline $findstring $f]
6349 } else {
6350 set fs $findstring
6351 if {$findtype == [mc "IgnCase"]} {
6352 set f [string tolower $f]
6353 set fs [string tolower $fs]
6355 set matches {}
6356 set i 0
6357 set l [string length $fs]
6358 while {[set j [string first $fs $f $i]] >= 0} {
6359 lappend matches [list $j [expr {$j+$l-1}]]
6360 set i [expr {$j + $l}]
6363 return $matches
6366 proc dofind {{dirn 1} {wrap 1}} {
6367 global findstring findstartline findcurline selectedline numcommits
6368 global gdttype filehighlight fh_serial find_dirn findallowwrap
6370 if {[info exists find_dirn]} {
6371 if {$find_dirn == $dirn} return
6372 stopfinding
6374 focus .
6375 if {$findstring eq {} || $numcommits == 0} return
6376 if {$selectedline eq {}} {
6377 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6378 } else {
6379 set findstartline $selectedline
6381 set findcurline $findstartline
6382 nowbusy finding [mc "Searching"]
6383 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6384 after cancel do_file_hl $fh_serial
6385 do_file_hl $fh_serial
6387 set find_dirn $dirn
6388 set findallowwrap $wrap
6389 run findmore
6392 proc stopfinding {} {
6393 global find_dirn findcurline fprogcoord
6395 if {[info exists find_dirn]} {
6396 unset find_dirn
6397 unset findcurline
6398 notbusy finding
6399 set fprogcoord 0
6400 adjustprogress
6402 stopblaming
6405 proc findmore {} {
6406 global commitdata commitinfo numcommits findpattern findloc
6407 global findstartline findcurline findallowwrap
6408 global find_dirn gdttype fhighlights fprogcoord
6409 global curview varcorder vrownum varccommits vrowmod
6411 if {![info exists find_dirn]} {
6412 return 0
6414 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6415 set l $findcurline
6416 set moretodo 0
6417 if {$find_dirn > 0} {
6418 incr l
6419 if {$l >= $numcommits} {
6420 set l 0
6422 if {$l <= $findstartline} {
6423 set lim [expr {$findstartline + 1}]
6424 } else {
6425 set lim $numcommits
6426 set moretodo $findallowwrap
6428 } else {
6429 if {$l == 0} {
6430 set l $numcommits
6432 incr l -1
6433 if {$l >= $findstartline} {
6434 set lim [expr {$findstartline - 1}]
6435 } else {
6436 set lim -1
6437 set moretodo $findallowwrap
6440 set n [expr {($lim - $l) * $find_dirn}]
6441 if {$n > 500} {
6442 set n 500
6443 set moretodo 1
6445 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6446 update_arcrows $curview
6448 set found 0
6449 set domore 1
6450 set ai [bsearch $vrownum($curview) $l]
6451 set a [lindex $varcorder($curview) $ai]
6452 set arow [lindex $vrownum($curview) $ai]
6453 set ids [lindex $varccommits($curview,$a)]
6454 set arowend [expr {$arow + [llength $ids]}]
6455 if {$gdttype eq [mc "containing:"]} {
6456 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6457 if {$l < $arow || $l >= $arowend} {
6458 incr ai $find_dirn
6459 set a [lindex $varcorder($curview) $ai]
6460 set arow [lindex $vrownum($curview) $ai]
6461 set ids [lindex $varccommits($curview,$a)]
6462 set arowend [expr {$arow + [llength $ids]}]
6464 set id [lindex $ids [expr {$l - $arow}]]
6465 # shouldn't happen unless git log doesn't give all the commits...
6466 if {![info exists commitdata($id)] ||
6467 ![doesmatch $commitdata($id)]} {
6468 continue
6470 if {![info exists commitinfo($id)]} {
6471 getcommit $id
6473 set info $commitinfo($id)
6474 foreach f $info ty $fldtypes {
6475 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6476 [doesmatch $f]} {
6477 set found 1
6478 break
6481 if {$found} break
6483 } else {
6484 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6485 if {$l < $arow || $l >= $arowend} {
6486 incr ai $find_dirn
6487 set a [lindex $varcorder($curview) $ai]
6488 set arow [lindex $vrownum($curview) $ai]
6489 set ids [lindex $varccommits($curview,$a)]
6490 set arowend [expr {$arow + [llength $ids]}]
6492 set id [lindex $ids [expr {$l - $arow}]]
6493 if {![info exists fhighlights($id)]} {
6494 # this sets fhighlights($id) to -1
6495 askfilehighlight $l $id
6497 if {$fhighlights($id) > 0} {
6498 set found $domore
6499 break
6501 if {$fhighlights($id) < 0} {
6502 if {$domore} {
6503 set domore 0
6504 set findcurline [expr {$l - $find_dirn}]
6509 if {$found || ($domore && !$moretodo)} {
6510 unset findcurline
6511 unset find_dirn
6512 notbusy finding
6513 set fprogcoord 0
6514 adjustprogress
6515 if {$found} {
6516 findselectline $l
6517 } else {
6518 bell
6520 return 0
6522 if {!$domore} {
6523 flushhighlights
6524 } else {
6525 set findcurline [expr {$l - $find_dirn}]
6527 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6528 if {$n < 0} {
6529 incr n $numcommits
6531 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6532 adjustprogress
6533 return $domore
6536 proc findselectline {l} {
6537 global findloc commentend ctext findcurline markingmatches gdttype
6539 set markingmatches [expr {$gdttype eq [mc "containing:"]}]
6540 set findcurline $l
6541 selectline $l 1
6542 if {$markingmatches &&
6543 ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} {
6544 # highlight the matches in the comments
6545 set f [$ctext get 1.0 $commentend]
6546 set matches [findmatches $f]
6547 foreach match $matches {
6548 set start [lindex $match 0]
6549 set end [expr {[lindex $match 1] + 1}]
6550 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6553 drawvisible
6556 # mark the bits of a headline or author that match a find string
6557 proc markmatches {canv l str tag matches font row} {
6558 global selectedline
6560 set bbox [$canv bbox $tag]
6561 set x0 [lindex $bbox 0]
6562 set y0 [lindex $bbox 1]
6563 set y1 [lindex $bbox 3]
6564 foreach match $matches {
6565 set start [lindex $match 0]
6566 set end [lindex $match 1]
6567 if {$start > $end} continue
6568 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6569 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6570 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6571 [expr {$x0+$xlen+2}] $y1 \
6572 -outline {} -tags [list match$l matches] -fill yellow]
6573 $canv lower $t
6574 if {$row == $selectedline} {
6575 $canv raise $t secsel
6580 proc unmarkmatches {} {
6581 global markingmatches
6583 allcanvs delete matches
6584 set markingmatches 0
6585 stopfinding
6588 proc selcanvline {w x y} {
6589 global canv canvy0 ctext linespc
6590 global rowtextx
6591 set ymax [lindex [$canv cget -scrollregion] 3]
6592 if {$ymax == {}} return
6593 set yfrac [lindex [$canv yview] 0]
6594 set y [expr {$y + $yfrac * $ymax}]
6595 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6596 if {$l < 0} {
6597 set l 0
6599 if {$w eq $canv} {
6600 set xmax [lindex [$canv cget -scrollregion] 2]
6601 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6602 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6604 unmarkmatches
6605 selectline $l 1
6608 proc commit_descriptor {p} {
6609 global commitinfo
6610 if {![info exists commitinfo($p)]} {
6611 getcommit $p
6613 set l "..."
6614 if {[llength $commitinfo($p)] > 1} {
6615 set l [lindex $commitinfo($p) 0]
6617 return "$p ($l)\n"
6620 # append some text to the ctext widget, and make any SHA1 ID
6621 # that we know about be a clickable link.
6622 proc appendwithlinks {text tags} {
6623 global ctext linknum curview
6625 set start [$ctext index "end - 1c"]
6626 $ctext insert end $text $tags
6627 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6628 foreach l $links {
6629 set s [lindex $l 0]
6630 set e [lindex $l 1]
6631 set linkid [string range $text $s $e]
6632 incr e
6633 $ctext tag delete link$linknum
6634 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6635 setlink $linkid link$linknum
6636 incr linknum
6640 proc setlink {id lk} {
6641 global curview ctext pendinglinks
6643 set known 0
6644 if {[string length $id] < 40} {
6645 set matches [longid $id]
6646 if {[llength $matches] > 0} {
6647 if {[llength $matches] > 1} return
6648 set known 1
6649 set id [lindex $matches 0]
6651 } else {
6652 set known [commitinview $id $curview]
6654 if {$known} {
6655 $ctext tag conf $lk -foreground blue -underline 1
6656 $ctext tag bind $lk <1> [list selbyid $id]
6657 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6658 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6659 } else {
6660 lappend pendinglinks($id) $lk
6661 interestedin $id {makelink %P}
6665 proc appendshortlink {id {pre {}} {post {}}} {
6666 global ctext linknum
6668 $ctext insert end $pre
6669 $ctext tag delete link$linknum
6670 $ctext insert end [string range $id 0 7] link$linknum
6671 $ctext insert end $post
6672 setlink $id link$linknum
6673 incr linknum
6676 proc makelink {id} {
6677 global pendinglinks
6679 if {![info exists pendinglinks($id)]} return
6680 foreach lk $pendinglinks($id) {
6681 setlink $id $lk
6683 unset pendinglinks($id)
6686 proc linkcursor {w inc} {
6687 global linkentercount curtextcursor
6689 if {[incr linkentercount $inc] > 0} {
6690 $w configure -cursor hand2
6691 } else {
6692 $w configure -cursor $curtextcursor
6693 if {$linkentercount < 0} {
6694 set linkentercount 0
6699 proc viewnextline {dir} {
6700 global canv linespc
6702 $canv delete hover
6703 set ymax [lindex [$canv cget -scrollregion] 3]
6704 set wnow [$canv yview]
6705 set wtop [expr {[lindex $wnow 0] * $ymax}]
6706 set newtop [expr {$wtop + $dir * $linespc}]
6707 if {$newtop < 0} {
6708 set newtop 0
6709 } elseif {$newtop > $ymax} {
6710 set newtop $ymax
6712 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6715 # add a list of tag or branch names at position pos
6716 # returns the number of names inserted
6717 proc appendrefs {pos ids var} {
6718 global ctext linknum curview $var maxrefs
6720 if {[catch {$ctext index $pos}]} {
6721 return 0
6723 $ctext conf -state normal
6724 $ctext delete $pos "$pos lineend"
6725 set tags {}
6726 foreach id $ids {
6727 foreach tag [set $var\($id\)] {
6728 lappend tags [list $tag $id]
6731 if {[llength $tags] > $maxrefs} {
6732 $ctext insert $pos "[mc "many"] ([llength $tags])"
6733 } else {
6734 set tags [lsort -index 0 -decreasing $tags]
6735 set sep {}
6736 foreach ti $tags {
6737 set id [lindex $ti 1]
6738 set lk link$linknum
6739 incr linknum
6740 $ctext tag delete $lk
6741 $ctext insert $pos $sep
6742 $ctext insert $pos [lindex $ti 0] $lk
6743 setlink $id $lk
6744 set sep ", "
6747 $ctext conf -state disabled
6748 return [llength $tags]
6751 # called when we have finished computing the nearby tags
6752 proc dispneartags {delay} {
6753 global selectedline currentid showneartags tagphase
6755 if {$selectedline eq {} || !$showneartags} return
6756 after cancel dispnexttag
6757 if {$delay} {
6758 after 200 dispnexttag
6759 set tagphase -1
6760 } else {
6761 after idle dispnexttag
6762 set tagphase 0
6766 proc dispnexttag {} {
6767 global selectedline currentid showneartags tagphase ctext
6769 if {$selectedline eq {} || !$showneartags} return
6770 switch -- $tagphase {
6772 set dtags [desctags $currentid]
6773 if {$dtags ne {}} {
6774 appendrefs precedes $dtags idtags
6778 set atags [anctags $currentid]
6779 if {$atags ne {}} {
6780 appendrefs follows $atags idtags
6784 set dheads [descheads $currentid]
6785 if {$dheads ne {}} {
6786 if {[appendrefs branch $dheads idheads] > 1
6787 && [$ctext get "branch -3c"] eq "h"} {
6788 # turn "Branch" into "Branches"
6789 $ctext conf -state normal
6790 $ctext insert "branch -2c" "es"
6791 $ctext conf -state disabled
6796 if {[incr tagphase] <= 2} {
6797 after idle dispnexttag
6801 proc make_secsel {id} {
6802 global linehtag linentag linedtag canv canv2 canv3
6804 if {![info exists linehtag($id)]} return
6805 $canv delete secsel
6806 set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
6807 -tags secsel -fill [$canv cget -selectbackground]]
6808 $canv lower $t
6809 $canv2 delete secsel
6810 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
6811 -tags secsel -fill [$canv2 cget -selectbackground]]
6812 $canv2 lower $t
6813 $canv3 delete secsel
6814 set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
6815 -tags secsel -fill [$canv3 cget -selectbackground]]
6816 $canv3 lower $t
6819 proc make_idmark {id} {
6820 global linehtag canv fgcolor
6822 if {![info exists linehtag($id)]} return
6823 $canv delete markid
6824 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
6825 -tags markid -outline $fgcolor]
6826 $canv raise $t
6829 proc selectline {l isnew {desired_loc {}}} {
6830 global canv ctext commitinfo selectedline
6831 global canvy0 linespc parents children curview
6832 global currentid sha1entry
6833 global commentend idtags linknum
6834 global mergemax numcommits pending_select
6835 global cmitmode showneartags allcommits
6836 global targetrow targetid lastscrollrows
6837 global autoselect jump_to_here
6839 catch {unset pending_select}
6840 $canv delete hover
6841 normalline
6842 unsel_reflist
6843 stopfinding
6844 if {$l < 0 || $l >= $numcommits} return
6845 set id [commitonrow $l]
6846 set targetid $id
6847 set targetrow $l
6848 set selectedline $l
6849 set currentid $id
6850 if {$lastscrollrows < $numcommits} {
6851 setcanvscroll
6854 set y [expr {$canvy0 + $l * $linespc}]
6855 set ymax [lindex [$canv cget -scrollregion] 3]
6856 set ytop [expr {$y - $linespc - 1}]
6857 set ybot [expr {$y + $linespc + 1}]
6858 set wnow [$canv yview]
6859 set wtop [expr {[lindex $wnow 0] * $ymax}]
6860 set wbot [expr {[lindex $wnow 1] * $ymax}]
6861 set wh [expr {$wbot - $wtop}]
6862 set newtop $wtop
6863 if {$ytop < $wtop} {
6864 if {$ybot < $wtop} {
6865 set newtop [expr {$y - $wh / 2.0}]
6866 } else {
6867 set newtop $ytop
6868 if {$newtop > $wtop - $linespc} {
6869 set newtop [expr {$wtop - $linespc}]
6872 } elseif {$ybot > $wbot} {
6873 if {$ytop > $wbot} {
6874 set newtop [expr {$y - $wh / 2.0}]
6875 } else {
6876 set newtop [expr {$ybot - $wh}]
6877 if {$newtop < $wtop + $linespc} {
6878 set newtop [expr {$wtop + $linespc}]
6882 if {$newtop != $wtop} {
6883 if {$newtop < 0} {
6884 set newtop 0
6886 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6887 drawvisible
6890 make_secsel $id
6892 if {$isnew} {
6893 addtohistory [list selbyid $id 0] savecmitpos
6896 $sha1entry delete 0 end
6897 $sha1entry insert 0 $id
6898 if {$autoselect} {
6899 $sha1entry selection range 0 end
6901 rhighlight_sel $id
6903 $ctext conf -state normal
6904 clear_ctext
6905 set linknum 0
6906 if {![info exists commitinfo($id)]} {
6907 getcommit $id
6909 set info $commitinfo($id)
6910 set date [formatdate [lindex $info 2]]
6911 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6912 set date [formatdate [lindex $info 4]]
6913 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6914 if {[info exists idtags($id)]} {
6915 $ctext insert end [mc "Tags:"]
6916 foreach tag $idtags($id) {
6917 $ctext insert end " $tag"
6919 $ctext insert end "\n"
6922 set headers {}
6923 set olds $parents($curview,$id)
6924 if {[llength $olds] > 1} {
6925 set np 0
6926 foreach p $olds {
6927 if {$np >= $mergemax} {
6928 set tag mmax
6929 } else {
6930 set tag m$np
6932 $ctext insert end "[mc "Parent"]: " $tag
6933 appendwithlinks [commit_descriptor $p] {}
6934 incr np
6936 } else {
6937 foreach p $olds {
6938 append headers "[mc "Parent"]: [commit_descriptor $p]"
6942 foreach c $children($curview,$id) {
6943 append headers "[mc "Child"]: [commit_descriptor $c]"
6946 # make anything that looks like a SHA1 ID be a clickable link
6947 appendwithlinks $headers {}
6948 if {$showneartags} {
6949 if {![info exists allcommits]} {
6950 getallcommits
6952 $ctext insert end "[mc "Branch"]: "
6953 $ctext mark set branch "end -1c"
6954 $ctext mark gravity branch left
6955 $ctext insert end "\n[mc "Follows"]: "
6956 $ctext mark set follows "end -1c"
6957 $ctext mark gravity follows left
6958 $ctext insert end "\n[mc "Precedes"]: "
6959 $ctext mark set precedes "end -1c"
6960 $ctext mark gravity precedes left
6961 $ctext insert end "\n"
6962 dispneartags 1
6964 $ctext insert end "\n"
6965 set comment [lindex $info 5]
6966 if {[string first "\r" $comment] >= 0} {
6967 set comment [string map {"\r" "\n "} $comment]
6969 appendwithlinks $comment {comment}
6971 $ctext tag remove found 1.0 end
6972 $ctext conf -state disabled
6973 set commentend [$ctext index "end - 1c"]
6975 set jump_to_here $desired_loc
6976 init_flist [mc "Comments"]
6977 if {$cmitmode eq "tree"} {
6978 gettree $id
6979 } elseif {[llength $olds] <= 1} {
6980 startdiff $id
6981 } else {
6982 mergediff $id
6986 proc selfirstline {} {
6987 unmarkmatches
6988 selectline 0 1
6991 proc sellastline {} {
6992 global numcommits
6993 unmarkmatches
6994 set l [expr {$numcommits - 1}]
6995 selectline $l 1
6998 proc selnextline {dir} {
6999 global selectedline
7000 focus .
7001 if {$selectedline eq {}} return
7002 set l [expr {$selectedline + $dir}]
7003 unmarkmatches
7004 selectline $l 1
7007 proc selnextpage {dir} {
7008 global canv linespc selectedline numcommits
7010 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
7011 if {$lpp < 1} {
7012 set lpp 1
7014 allcanvs yview scroll [expr {$dir * $lpp}] units
7015 drawvisible
7016 if {$selectedline eq {}} return
7017 set l [expr {$selectedline + $dir * $lpp}]
7018 if {$l < 0} {
7019 set l 0
7020 } elseif {$l >= $numcommits} {
7021 set l [expr $numcommits - 1]
7023 unmarkmatches
7024 selectline $l 1
7027 proc unselectline {} {
7028 global selectedline currentid
7030 set selectedline {}
7031 catch {unset currentid}
7032 allcanvs delete secsel
7033 rhighlight_none
7036 proc reselectline {} {
7037 global selectedline
7039 if {$selectedline ne {}} {
7040 selectline $selectedline 0
7044 proc addtohistory {cmd {saveproc {}}} {
7045 global history historyindex curview
7047 unset_posvars
7048 save_position
7049 set elt [list $curview $cmd $saveproc {}]
7050 if {$historyindex > 0
7051 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
7052 return
7055 if {$historyindex < [llength $history]} {
7056 set history [lreplace $history $historyindex end $elt]
7057 } else {
7058 lappend history $elt
7060 incr historyindex
7061 if {$historyindex > 1} {
7062 .tf.bar.leftbut conf -state normal
7063 } else {
7064 .tf.bar.leftbut conf -state disabled
7066 .tf.bar.rightbut conf -state disabled
7069 # save the scrolling position of the diff display pane
7070 proc save_position {} {
7071 global historyindex history
7073 if {$historyindex < 1} return
7074 set hi [expr {$historyindex - 1}]
7075 set fn [lindex $history $hi 2]
7076 if {$fn ne {}} {
7077 lset history $hi 3 [eval $fn]
7081 proc unset_posvars {} {
7082 global last_posvars
7084 if {[info exists last_posvars]} {
7085 foreach {var val} $last_posvars {
7086 global $var
7087 catch {unset $var}
7089 unset last_posvars
7093 proc godo {elt} {
7094 global curview last_posvars
7096 set view [lindex $elt 0]
7097 set cmd [lindex $elt 1]
7098 set pv [lindex $elt 3]
7099 if {$curview != $view} {
7100 showview $view
7102 unset_posvars
7103 foreach {var val} $pv {
7104 global $var
7105 set $var $val
7107 set last_posvars $pv
7108 eval $cmd
7111 proc goback {} {
7112 global history historyindex
7113 focus .
7115 if {$historyindex > 1} {
7116 save_position
7117 incr historyindex -1
7118 godo [lindex $history [expr {$historyindex - 1}]]
7119 .tf.bar.rightbut conf -state normal
7121 if {$historyindex <= 1} {
7122 .tf.bar.leftbut conf -state disabled
7126 proc goforw {} {
7127 global history historyindex
7128 focus .
7130 if {$historyindex < [llength $history]} {
7131 save_position
7132 set cmd [lindex $history $historyindex]
7133 incr historyindex
7134 godo $cmd
7135 .tf.bar.leftbut conf -state normal
7137 if {$historyindex >= [llength $history]} {
7138 .tf.bar.rightbut conf -state disabled
7142 proc gettree {id} {
7143 global treefilelist treeidlist diffids diffmergeid treepending
7144 global nullid nullid2
7146 set diffids $id
7147 catch {unset diffmergeid}
7148 if {![info exists treefilelist($id)]} {
7149 if {![info exists treepending]} {
7150 if {$id eq $nullid} {
7151 set cmd [list | git ls-files]
7152 } elseif {$id eq $nullid2} {
7153 set cmd [list | git ls-files --stage -t]
7154 } else {
7155 set cmd [list | git ls-tree -r $id]
7157 if {[catch {set gtf [open $cmd r]}]} {
7158 return
7160 set treepending $id
7161 set treefilelist($id) {}
7162 set treeidlist($id) {}
7163 fconfigure $gtf -blocking 0 -encoding binary
7164 filerun $gtf [list gettreeline $gtf $id]
7166 } else {
7167 setfilelist $id
7171 proc gettreeline {gtf id} {
7172 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
7174 set nl 0
7175 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
7176 if {$diffids eq $nullid} {
7177 set fname $line
7178 } else {
7179 set i [string first "\t" $line]
7180 if {$i < 0} continue
7181 set fname [string range $line [expr {$i+1}] end]
7182 set line [string range $line 0 [expr {$i-1}]]
7183 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
7184 set sha1 [lindex $line 2]
7185 lappend treeidlist($id) $sha1
7187 if {[string index $fname 0] eq "\""} {
7188 set fname [lindex $fname 0]
7190 set fname [encoding convertfrom $fname]
7191 lappend treefilelist($id) $fname
7193 if {![eof $gtf]} {
7194 return [expr {$nl >= 1000? 2: 1}]
7196 close $gtf
7197 unset treepending
7198 if {$cmitmode ne "tree"} {
7199 if {![info exists diffmergeid]} {
7200 gettreediffs $diffids
7202 } elseif {$id ne $diffids} {
7203 gettree $diffids
7204 } else {
7205 setfilelist $id
7207 return 0
7210 proc showfile {f} {
7211 global treefilelist treeidlist diffids nullid nullid2
7212 global ctext_file_names ctext_file_lines
7213 global ctext commentend
7215 set i [lsearch -exact $treefilelist($diffids) $f]
7216 if {$i < 0} {
7217 puts "oops, $f not in list for id $diffids"
7218 return
7220 if {$diffids eq $nullid} {
7221 if {[catch {set bf [open $f r]} err]} {
7222 puts "oops, can't read $f: $err"
7223 return
7225 } else {
7226 set blob [lindex $treeidlist($diffids) $i]
7227 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
7228 puts "oops, error reading blob $blob: $err"
7229 return
7232 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
7233 filerun $bf [list getblobline $bf $diffids]
7234 $ctext config -state normal
7235 clear_ctext $commentend
7236 lappend ctext_file_names $f
7237 lappend ctext_file_lines [lindex [split $commentend "."] 0]
7238 $ctext insert end "\n"
7239 $ctext insert end "$f\n" filesep
7240 $ctext config -state disabled
7241 $ctext yview $commentend
7242 settabs 0
7245 proc getblobline {bf id} {
7246 global diffids cmitmode ctext
7248 if {$id ne $diffids || $cmitmode ne "tree"} {
7249 catch {close $bf}
7250 return 0
7252 $ctext config -state normal
7253 set nl 0
7254 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
7255 $ctext insert end "$line\n"
7257 if {[eof $bf]} {
7258 global jump_to_here ctext_file_names commentend
7260 # delete last newline
7261 $ctext delete "end - 2c" "end - 1c"
7262 close $bf
7263 if {$jump_to_here ne {} &&
7264 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
7265 set lnum [expr {[lindex $jump_to_here 1] +
7266 [lindex [split $commentend .] 0]}]
7267 mark_ctext_line $lnum
7269 return 0
7271 $ctext config -state disabled
7272 return [expr {$nl >= 1000? 2: 1}]
7275 proc mark_ctext_line {lnum} {
7276 global ctext markbgcolor
7278 $ctext tag delete omark
7279 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
7280 $ctext tag conf omark -background $markbgcolor
7281 $ctext see $lnum.0
7284 proc mergediff {id} {
7285 global diffmergeid
7286 global diffids treediffs
7287 global parents curview
7289 set diffmergeid $id
7290 set diffids $id
7291 set treediffs($id) {}
7292 set np [llength $parents($curview,$id)]
7293 settabs $np
7294 getblobdiffs $id
7297 proc startdiff {ids} {
7298 global treediffs diffids treepending diffmergeid nullid nullid2
7300 settabs 1
7301 set diffids $ids
7302 catch {unset diffmergeid}
7303 if {![info exists treediffs($ids)] ||
7304 [lsearch -exact $ids $nullid] >= 0 ||
7305 [lsearch -exact $ids $nullid2] >= 0} {
7306 if {![info exists treepending]} {
7307 gettreediffs $ids
7309 } else {
7310 addtocflist $ids
7314 proc path_filter {filter name} {
7315 foreach p $filter {
7316 set l [string length $p]
7317 if {[string index $p end] eq "/"} {
7318 if {[string compare -length $l $p $name] == 0} {
7319 return 1
7321 } else {
7322 if {[string compare -length $l $p $name] == 0 &&
7323 ([string length $name] == $l ||
7324 [string index $name $l] eq "/")} {
7325 return 1
7329 return 0
7332 proc addtocflist {ids} {
7333 global treediffs
7335 add_flist $treediffs($ids)
7336 getblobdiffs $ids
7339 proc diffcmd {ids flags} {
7340 global nullid nullid2
7342 set i [lsearch -exact $ids $nullid]
7343 set j [lsearch -exact $ids $nullid2]
7344 if {$i >= 0} {
7345 if {[llength $ids] > 1 && $j < 0} {
7346 # comparing working directory with some specific revision
7347 set cmd [concat | git diff-index $flags]
7348 if {$i == 0} {
7349 lappend cmd -R [lindex $ids 1]
7350 } else {
7351 lappend cmd [lindex $ids 0]
7353 } else {
7354 # comparing working directory with index
7355 set cmd [concat | git diff-files $flags]
7356 if {$j == 1} {
7357 lappend cmd -R
7360 } elseif {$j >= 0} {
7361 set cmd [concat | git diff-index --cached $flags]
7362 if {[llength $ids] > 1} {
7363 # comparing index with specific revision
7364 if {$i == 0} {
7365 lappend cmd -R [lindex $ids 1]
7366 } else {
7367 lappend cmd [lindex $ids 0]
7369 } else {
7370 # comparing index with HEAD
7371 lappend cmd HEAD
7373 } else {
7374 set cmd [concat | git diff-tree -r $flags $ids]
7376 return $cmd
7379 proc gettreediffs {ids} {
7380 global treediff treepending
7382 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7384 set treepending $ids
7385 set treediff {}
7386 fconfigure $gdtf -blocking 0 -encoding binary
7387 filerun $gdtf [list gettreediffline $gdtf $ids]
7390 proc gettreediffline {gdtf ids} {
7391 global treediff treediffs treepending diffids diffmergeid
7392 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7394 set nr 0
7395 set sublist {}
7396 set max 1000
7397 if {$perfile_attrs} {
7398 # cache_gitattr is slow, and even slower on win32 where we
7399 # have to invoke it for only about 30 paths at a time
7400 set max 500
7401 if {[tk windowingsystem] == "win32"} {
7402 set max 120
7405 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7406 set i [string first "\t" $line]
7407 if {$i >= 0} {
7408 set file [string range $line [expr {$i+1}] end]
7409 if {[string index $file 0] eq "\""} {
7410 set file [lindex $file 0]
7412 set file [encoding convertfrom $file]
7413 if {$file ne [lindex $treediff end]} {
7414 lappend treediff $file
7415 lappend sublist $file
7419 if {$perfile_attrs} {
7420 cache_gitattr encoding $sublist
7422 if {![eof $gdtf]} {
7423 return [expr {$nr >= $max? 2: 1}]
7425 close $gdtf
7426 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7427 set flist {}
7428 foreach f $treediff {
7429 if {[path_filter $vfilelimit($curview) $f]} {
7430 lappend flist $f
7433 set treediffs($ids) $flist
7434 } else {
7435 set treediffs($ids) $treediff
7437 unset treepending
7438 if {$cmitmode eq "tree" && [llength $diffids] == 1} {
7439 gettree $diffids
7440 } elseif {$ids != $diffids} {
7441 if {![info exists diffmergeid]} {
7442 gettreediffs $diffids
7444 } else {
7445 addtocflist $ids
7447 return 0
7450 # empty string or positive integer
7451 proc diffcontextvalidate {v} {
7452 return [regexp {^(|[1-9][0-9]*)$} $v]
7455 proc diffcontextchange {n1 n2 op} {
7456 global diffcontextstring diffcontext
7458 if {[string is integer -strict $diffcontextstring]} {
7459 if {$diffcontextstring >= 0} {
7460 set diffcontext $diffcontextstring
7461 reselectline
7466 proc changeignorespace {} {
7467 reselectline
7470 proc getblobdiffs {ids} {
7471 global blobdifffd diffids env
7472 global diffinhdr treediffs
7473 global diffcontext
7474 global ignorespace
7475 global limitdiffs vfilelimit curview
7476 global diffencoding targetline diffnparents
7477 global git_version
7479 set textconv {}
7480 if {[package vcompare $git_version "1.6.1"] >= 0} {
7481 set textconv "--textconv"
7483 set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"]
7484 if {$ignorespace} {
7485 append cmd " -w"
7487 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7488 set cmd [concat $cmd -- $vfilelimit($curview)]
7490 if {[catch {set bdf [open $cmd r]} err]} {
7491 error_popup [mc "Error getting diffs: %s" $err]
7492 return
7494 set targetline {}
7495 set diffnparents 0
7496 set diffinhdr 0
7497 set diffencoding [get_path_encoding {}]
7498 fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
7499 set blobdifffd($ids) $bdf
7500 filerun $bdf [list getblobdiffline $bdf $diffids]
7503 proc savecmitpos {} {
7504 global ctext cmitmode
7506 if {$cmitmode eq "tree"} {
7507 return {}
7509 return [list target_scrollpos [$ctext index @0,0]]
7512 proc savectextpos {} {
7513 global ctext
7515 return [list target_scrollpos [$ctext index @0,0]]
7518 proc maybe_scroll_ctext {ateof} {
7519 global ctext target_scrollpos
7521 if {![info exists target_scrollpos]} return
7522 if {!$ateof} {
7523 set nlines [expr {[winfo height $ctext]
7524 / [font metrics textfont -linespace]}]
7525 if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
7527 $ctext yview $target_scrollpos
7528 unset target_scrollpos
7531 proc setinlist {var i val} {
7532 global $var
7534 while {[llength [set $var]] < $i} {
7535 lappend $var {}
7537 if {[llength [set $var]] == $i} {
7538 lappend $var $val
7539 } else {
7540 lset $var $i $val
7544 proc makediffhdr {fname ids} {
7545 global ctext curdiffstart treediffs diffencoding
7546 global ctext_file_names jump_to_here targetline diffline
7548 set fname [encoding convertfrom $fname]
7549 set diffencoding [get_path_encoding $fname]
7550 set i [lsearch -exact $treediffs($ids) $fname]
7551 if {$i >= 0} {
7552 setinlist difffilestart $i $curdiffstart
7554 lset ctext_file_names end $fname
7555 set l [expr {(78 - [string length $fname]) / 2}]
7556 set pad [string range "----------------------------------------" 1 $l]
7557 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7558 set targetline {}
7559 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7560 set targetline [lindex $jump_to_here 1]
7562 set diffline 0
7565 proc getblobdiffline {bdf ids} {
7566 global diffids blobdifffd ctext curdiffstart
7567 global diffnexthead diffnextnote difffilestart
7568 global ctext_file_names ctext_file_lines
7569 global diffinhdr treediffs mergemax diffnparents
7570 global diffencoding jump_to_here targetline diffline
7572 set nr 0
7573 $ctext conf -state normal
7574 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7575 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7576 close $bdf
7577 return 0
7579 if {![string compare -length 5 "diff " $line]} {
7580 if {![regexp {^diff (--cc|--git) } $line m type]} {
7581 set line [encoding convertfrom $line]
7582 $ctext insert end "$line\n" hunksep
7583 continue
7585 # start of a new file
7586 set diffinhdr 1
7587 $ctext insert end "\n"
7588 set curdiffstart [$ctext index "end - 1c"]
7589 lappend ctext_file_names ""
7590 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7591 $ctext insert end "\n" filesep
7593 if {$type eq "--cc"} {
7594 # start of a new file in a merge diff
7595 set fname [string range $line 10 end]
7596 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7597 lappend treediffs($ids) $fname
7598 add_flist [list $fname]
7601 } else {
7602 set line [string range $line 11 end]
7603 # If the name hasn't changed the length will be odd,
7604 # the middle char will be a space, and the two bits either
7605 # side will be a/name and b/name, or "a/name" and "b/name".
7606 # If the name has changed we'll get "rename from" and
7607 # "rename to" or "copy from" and "copy to" lines following
7608 # this, and we'll use them to get the filenames.
7609 # This complexity is necessary because spaces in the
7610 # filename(s) don't get escaped.
7611 set l [string length $line]
7612 set i [expr {$l / 2}]
7613 if {!(($l & 1) && [string index $line $i] eq " " &&
7614 [string range $line 2 [expr {$i - 1}]] eq \
7615 [string range $line [expr {$i + 3}] end])} {
7616 continue
7618 # unescape if quoted and chop off the a/ from the front
7619 if {[string index $line 0] eq "\""} {
7620 set fname [string range [lindex $line 0] 2 end]
7621 } else {
7622 set fname [string range $line 2 [expr {$i - 1}]]
7625 makediffhdr $fname $ids
7627 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7628 set fname [encoding convertfrom [string range $line 16 end]]
7629 $ctext insert end "\n"
7630 set curdiffstart [$ctext index "end - 1c"]
7631 lappend ctext_file_names $fname
7632 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7633 $ctext insert end "$line\n" filesep
7634 set i [lsearch -exact $treediffs($ids) $fname]
7635 if {$i >= 0} {
7636 setinlist difffilestart $i $curdiffstart
7639 } elseif {![string compare -length 2 "@@" $line]} {
7640 regexp {^@@+} $line ats
7641 set line [encoding convertfrom $diffencoding $line]
7642 $ctext insert end "$line\n" hunksep
7643 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7644 set diffline $nl
7646 set diffnparents [expr {[string length $ats] - 1}]
7647 set diffinhdr 0
7649 } elseif {$diffinhdr} {
7650 if {![string compare -length 12 "rename from " $line]} {
7651 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7652 if {[string index $fname 0] eq "\""} {
7653 set fname [lindex $fname 0]
7655 set fname [encoding convertfrom $fname]
7656 set i [lsearch -exact $treediffs($ids) $fname]
7657 if {$i >= 0} {
7658 setinlist difffilestart $i $curdiffstart
7660 } elseif {![string compare -length 10 $line "rename to "] ||
7661 ![string compare -length 8 $line "copy to "]} {
7662 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7663 if {[string index $fname 0] eq "\""} {
7664 set fname [lindex $fname 0]
7666 makediffhdr $fname $ids
7667 } elseif {[string compare -length 3 $line "---"] == 0} {
7668 # do nothing
7669 continue
7670 } elseif {[string compare -length 3 $line "+++"] == 0} {
7671 set diffinhdr 0
7672 continue
7674 $ctext insert end "$line\n" filesep
7676 } else {
7677 set line [string map {\x1A ^Z} \
7678 [encoding convertfrom $diffencoding $line]]
7679 # parse the prefix - one ' ', '-' or '+' for each parent
7680 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7681 set tag [expr {$diffnparents > 1? "m": "d"}]
7682 if {[string trim $prefix " -+"] eq {}} {
7683 # prefix only has " ", "-" and "+" in it: normal diff line
7684 set num [string first "-" $prefix]
7685 if {$num >= 0} {
7686 # removed line, first parent with line is $num
7687 if {$num >= $mergemax} {
7688 set num "max"
7690 $ctext insert end "$line\n" $tag$num
7691 } else {
7692 set tags {}
7693 if {[string first "+" $prefix] >= 0} {
7694 # added line
7695 lappend tags ${tag}result
7696 if {$diffnparents > 1} {
7697 set num [string first " " $prefix]
7698 if {$num >= 0} {
7699 if {$num >= $mergemax} {
7700 set num "max"
7702 lappend tags m$num
7706 if {$targetline ne {}} {
7707 if {$diffline == $targetline} {
7708 set seehere [$ctext index "end - 1 chars"]
7709 set targetline {}
7710 } else {
7711 incr diffline
7714 $ctext insert end "$line\n" $tags
7716 } else {
7717 # "\ No newline at end of file",
7718 # or something else we don't recognize
7719 $ctext insert end "$line\n" hunksep
7723 if {[info exists seehere]} {
7724 mark_ctext_line [lindex [split $seehere .] 0]
7726 maybe_scroll_ctext [eof $bdf]
7727 $ctext conf -state disabled
7728 if {[eof $bdf]} {
7729 close $bdf
7730 return 0
7732 return [expr {$nr >= 1000? 2: 1}]
7735 proc changediffdisp {} {
7736 global ctext diffelide
7738 $ctext tag conf d0 -elide [lindex $diffelide 0]
7739 $ctext tag conf dresult -elide [lindex $diffelide 1]
7742 proc highlightfile {loc cline} {
7743 global ctext cflist cflist_top
7745 $ctext yview $loc
7746 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7747 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7748 $cflist see $cline.0
7749 set cflist_top $cline
7752 proc prevfile {} {
7753 global difffilestart ctext cmitmode
7755 if {$cmitmode eq "tree"} return
7756 set prev 0.0
7757 set prevline 1
7758 set here [$ctext index @0,0]
7759 foreach loc $difffilestart {
7760 if {[$ctext compare $loc >= $here]} {
7761 highlightfile $prev $prevline
7762 return
7764 set prev $loc
7765 incr prevline
7767 highlightfile $prev $prevline
7770 proc nextfile {} {
7771 global difffilestart ctext cmitmode
7773 if {$cmitmode eq "tree"} return
7774 set here [$ctext index @0,0]
7775 set line 1
7776 foreach loc $difffilestart {
7777 incr line
7778 if {[$ctext compare $loc > $here]} {
7779 highlightfile $loc $line
7780 return
7785 proc clear_ctext {{first 1.0}} {
7786 global ctext smarktop smarkbot
7787 global ctext_file_names ctext_file_lines
7788 global pendinglinks
7790 set l [lindex [split $first .] 0]
7791 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7792 set smarktop $l
7794 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7795 set smarkbot $l
7797 $ctext delete $first end
7798 if {$first eq "1.0"} {
7799 catch {unset pendinglinks}
7801 set ctext_file_names {}
7802 set ctext_file_lines {}
7805 proc settabs {{firstab {}}} {
7806 global firsttabstop tabstop ctext have_tk85
7808 if {$firstab ne {} && $have_tk85} {
7809 set firsttabstop $firstab
7811 set w [font measure textfont "0"]
7812 if {$firsttabstop != 0} {
7813 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7814 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7815 } elseif {$have_tk85 || $tabstop != 8} {
7816 $ctext conf -tabs [expr {$tabstop * $w}]
7817 } else {
7818 $ctext conf -tabs {}
7822 proc incrsearch {name ix op} {
7823 global ctext searchstring searchdirn
7825 $ctext tag remove found 1.0 end
7826 if {[catch {$ctext index anchor}]} {
7827 # no anchor set, use start of selection, or of visible area
7828 set sel [$ctext tag ranges sel]
7829 if {$sel ne {}} {
7830 $ctext mark set anchor [lindex $sel 0]
7831 } elseif {$searchdirn eq "-forwards"} {
7832 $ctext mark set anchor @0,0
7833 } else {
7834 $ctext mark set anchor @0,[winfo height $ctext]
7837 if {$searchstring ne {}} {
7838 set here [$ctext search $searchdirn -- $searchstring anchor]
7839 if {$here ne {}} {
7840 $ctext see $here
7842 searchmarkvisible 1
7846 proc dosearch {} {
7847 global sstring ctext searchstring searchdirn
7849 focus $sstring
7850 $sstring icursor end
7851 set searchdirn -forwards
7852 if {$searchstring ne {}} {
7853 set sel [$ctext tag ranges sel]
7854 if {$sel ne {}} {
7855 set start "[lindex $sel 0] + 1c"
7856 } elseif {[catch {set start [$ctext index anchor]}]} {
7857 set start "@0,0"
7859 set match [$ctext search -count mlen -- $searchstring $start]
7860 $ctext tag remove sel 1.0 end
7861 if {$match eq {}} {
7862 bell
7863 return
7865 $ctext see $match
7866 set mend "$match + $mlen c"
7867 $ctext tag add sel $match $mend
7868 $ctext mark unset anchor
7872 proc dosearchback {} {
7873 global sstring ctext searchstring searchdirn
7875 focus $sstring
7876 $sstring icursor end
7877 set searchdirn -backwards
7878 if {$searchstring ne {}} {
7879 set sel [$ctext tag ranges sel]
7880 if {$sel ne {}} {
7881 set start [lindex $sel 0]
7882 } elseif {[catch {set start [$ctext index anchor]}]} {
7883 set start @0,[winfo height $ctext]
7885 set match [$ctext search -backwards -count ml -- $searchstring $start]
7886 $ctext tag remove sel 1.0 end
7887 if {$match eq {}} {
7888 bell
7889 return
7891 $ctext see $match
7892 set mend "$match + $ml c"
7893 $ctext tag add sel $match $mend
7894 $ctext mark unset anchor
7898 proc searchmark {first last} {
7899 global ctext searchstring
7901 set mend $first.0
7902 while {1} {
7903 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7904 if {$match eq {}} break
7905 set mend "$match + $mlen c"
7906 $ctext tag add found $match $mend
7910 proc searchmarkvisible {doall} {
7911 global ctext smarktop smarkbot
7913 set topline [lindex [split [$ctext index @0,0] .] 0]
7914 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7915 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7916 # no overlap with previous
7917 searchmark $topline $botline
7918 set smarktop $topline
7919 set smarkbot $botline
7920 } else {
7921 if {$topline < $smarktop} {
7922 searchmark $topline [expr {$smarktop-1}]
7923 set smarktop $topline
7925 if {$botline > $smarkbot} {
7926 searchmark [expr {$smarkbot+1}] $botline
7927 set smarkbot $botline
7932 proc scrolltext {f0 f1} {
7933 global searchstring
7935 .bleft.bottom.sb set $f0 $f1
7936 if {$searchstring ne {}} {
7937 searchmarkvisible 0
7941 proc setcoords {} {
7942 global linespc charspc canvx0 canvy0
7943 global xspc1 xspc2 lthickness
7945 set linespc [font metrics mainfont -linespace]
7946 set charspc [font measure mainfont "m"]
7947 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7948 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7949 set lthickness [expr {int($linespc / 9) + 1}]
7950 set xspc1(0) $linespc
7951 set xspc2 $linespc
7954 proc redisplay {} {
7955 global canv
7956 global selectedline
7958 set ymax [lindex [$canv cget -scrollregion] 3]
7959 if {$ymax eq {} || $ymax == 0} return
7960 set span [$canv yview]
7961 clear_display
7962 setcanvscroll
7963 allcanvs yview moveto [lindex $span 0]
7964 drawvisible
7965 if {$selectedline ne {}} {
7966 selectline $selectedline 0
7967 allcanvs yview moveto [lindex $span 0]
7971 proc parsefont {f n} {
7972 global fontattr
7974 set fontattr($f,family) [lindex $n 0]
7975 set s [lindex $n 1]
7976 if {$s eq {} || $s == 0} {
7977 set s 10
7978 } elseif {$s < 0} {
7979 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7981 set fontattr($f,size) $s
7982 set fontattr($f,weight) normal
7983 set fontattr($f,slant) roman
7984 foreach style [lrange $n 2 end] {
7985 switch -- $style {
7986 "normal" -
7987 "bold" {set fontattr($f,weight) $style}
7988 "roman" -
7989 "italic" {set fontattr($f,slant) $style}
7994 proc fontflags {f {isbold 0}} {
7995 global fontattr
7997 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7998 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7999 -slant $fontattr($f,slant)]
8002 proc fontname {f} {
8003 global fontattr
8005 set n [list $fontattr($f,family) $fontattr($f,size)]
8006 if {$fontattr($f,weight) eq "bold"} {
8007 lappend n "bold"
8009 if {$fontattr($f,slant) eq "italic"} {
8010 lappend n "italic"
8012 return $n
8015 proc incrfont {inc} {
8016 global mainfont textfont ctext canv cflist showrefstop
8017 global stopped entries fontattr
8019 unmarkmatches
8020 set s $fontattr(mainfont,size)
8021 incr s $inc
8022 if {$s < 1} {
8023 set s 1
8025 set fontattr(mainfont,size) $s
8026 font config mainfont -size $s
8027 font config mainfontbold -size $s
8028 set mainfont [fontname mainfont]
8029 set s $fontattr(textfont,size)
8030 incr s $inc
8031 if {$s < 1} {
8032 set s 1
8034 set fontattr(textfont,size) $s
8035 font config textfont -size $s
8036 font config textfontbold -size $s
8037 set textfont [fontname textfont]
8038 setcoords
8039 settabs
8040 redisplay
8043 proc clearsha1 {} {
8044 global sha1entry sha1string
8045 if {[string length $sha1string] == 40} {
8046 $sha1entry delete 0 end
8050 proc sha1change {n1 n2 op} {
8051 global sha1string currentid sha1but
8052 if {$sha1string == {}
8053 || ([info exists currentid] && $sha1string == $currentid)} {
8054 set state disabled
8055 } else {
8056 set state normal
8058 if {[$sha1but cget -state] == $state} return
8059 if {$state == "normal"} {
8060 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
8061 } else {
8062 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
8066 proc gotocommit {} {
8067 global sha1string tagids headids curview varcid
8069 if {$sha1string == {}
8070 || ([info exists currentid] && $sha1string == $currentid)} return
8071 if {[info exists tagids($sha1string)]} {
8072 set id $tagids($sha1string)
8073 } elseif {[info exists headids($sha1string)]} {
8074 set id $headids($sha1string)
8075 } else {
8076 set id [string tolower $sha1string]
8077 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
8078 set matches [longid $id]
8079 if {$matches ne {}} {
8080 if {[llength $matches] > 1} {
8081 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
8082 return
8084 set id [lindex $matches 0]
8086 } else {
8087 if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
8088 error_popup [mc "Revision %s is not known" $sha1string]
8089 return
8093 if {[commitinview $id $curview]} {
8094 selectline [rowofcommit $id] 1
8095 return
8097 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
8098 set msg [mc "SHA1 id %s is not known" $sha1string]
8099 } else {
8100 set msg [mc "Revision %s is not in the current view" $sha1string]
8102 error_popup $msg
8105 proc lineenter {x y id} {
8106 global hoverx hovery hoverid hovertimer
8107 global commitinfo canv
8109 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8110 set hoverx $x
8111 set hovery $y
8112 set hoverid $id
8113 if {[info exists hovertimer]} {
8114 after cancel $hovertimer
8116 set hovertimer [after 500 linehover]
8117 $canv delete hover
8120 proc linemotion {x y id} {
8121 global hoverx hovery hoverid hovertimer
8123 if {[info exists hoverid] && $id == $hoverid} {
8124 set hoverx $x
8125 set hovery $y
8126 if {[info exists hovertimer]} {
8127 after cancel $hovertimer
8129 set hovertimer [after 500 linehover]
8133 proc lineleave {id} {
8134 global hoverid hovertimer canv
8136 if {[info exists hoverid] && $id == $hoverid} {
8137 $canv delete hover
8138 if {[info exists hovertimer]} {
8139 after cancel $hovertimer
8140 unset hovertimer
8142 unset hoverid
8146 proc linehover {} {
8147 global hoverx hovery hoverid hovertimer
8148 global canv linespc lthickness
8149 global commitinfo
8151 set text [lindex $commitinfo($hoverid) 0]
8152 set ymax [lindex [$canv cget -scrollregion] 3]
8153 if {$ymax == {}} return
8154 set yfrac [lindex [$canv yview] 0]
8155 set x [expr {$hoverx + 2 * $linespc}]
8156 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
8157 set x0 [expr {$x - 2 * $lthickness}]
8158 set y0 [expr {$y - 2 * $lthickness}]
8159 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
8160 set y1 [expr {$y + $linespc + 2 * $lthickness}]
8161 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
8162 -fill \#ffff80 -outline black -width 1 -tags hover]
8163 $canv raise $t
8164 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
8165 -font mainfont]
8166 $canv raise $t
8169 proc clickisonarrow {id y} {
8170 global lthickness
8172 set ranges [rowranges $id]
8173 set thresh [expr {2 * $lthickness + 6}]
8174 set n [expr {[llength $ranges] - 1}]
8175 for {set i 1} {$i < $n} {incr i} {
8176 set row [lindex $ranges $i]
8177 if {abs([yc $row] - $y) < $thresh} {
8178 return $i
8181 return {}
8184 proc arrowjump {id n y} {
8185 global canv
8187 # 1 <-> 2, 3 <-> 4, etc...
8188 set n [expr {(($n - 1) ^ 1) + 1}]
8189 set row [lindex [rowranges $id] $n]
8190 set yt [yc $row]
8191 set ymax [lindex [$canv cget -scrollregion] 3]
8192 if {$ymax eq {} || $ymax <= 0} return
8193 set view [$canv yview]
8194 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
8195 set yfrac [expr {$yt / $ymax - $yspan / 2}]
8196 if {$yfrac < 0} {
8197 set yfrac 0
8199 allcanvs yview moveto $yfrac
8202 proc lineclick {x y id isnew} {
8203 global ctext commitinfo children canv thickerline curview
8205 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8206 unmarkmatches
8207 unselectline
8208 normalline
8209 $canv delete hover
8210 # draw this line thicker than normal
8211 set thickerline $id
8212 drawlines $id
8213 if {$isnew} {
8214 set ymax [lindex [$canv cget -scrollregion] 3]
8215 if {$ymax eq {}} return
8216 set yfrac [lindex [$canv yview] 0]
8217 set y [expr {$y + $yfrac * $ymax}]
8219 set dirn [clickisonarrow $id $y]
8220 if {$dirn ne {}} {
8221 arrowjump $id $dirn $y
8222 return
8225 if {$isnew} {
8226 addtohistory [list lineclick $x $y $id 0] savectextpos
8228 # fill the details pane with info about this line
8229 $ctext conf -state normal
8230 clear_ctext
8231 settabs 0
8232 $ctext insert end "[mc "Parent"]:\t"
8233 $ctext insert end $id link0
8234 setlink $id link0
8235 set info $commitinfo($id)
8236 $ctext insert end "\n\t[lindex $info 0]\n"
8237 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
8238 set date [formatdate [lindex $info 2]]
8239 $ctext insert end "\t[mc "Date"]:\t$date\n"
8240 set kids $children($curview,$id)
8241 if {$kids ne {}} {
8242 $ctext insert end "\n[mc "Children"]:"
8243 set i 0
8244 foreach child $kids {
8245 incr i
8246 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
8247 set info $commitinfo($child)
8248 $ctext insert end "\n\t"
8249 $ctext insert end $child link$i
8250 setlink $child link$i
8251 $ctext insert end "\n\t[lindex $info 0]"
8252 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
8253 set date [formatdate [lindex $info 2]]
8254 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
8257 maybe_scroll_ctext 1
8258 $ctext conf -state disabled
8259 init_flist {}
8262 proc normalline {} {
8263 global thickerline
8264 if {[info exists thickerline]} {
8265 set id $thickerline
8266 unset thickerline
8267 drawlines $id
8271 proc selbyid {id {isnew 1}} {
8272 global curview
8273 if {[commitinview $id $curview]} {
8274 selectline [rowofcommit $id] $isnew
8278 proc mstime {} {
8279 global startmstime
8280 if {![info exists startmstime]} {
8281 set startmstime [clock clicks -milliseconds]
8283 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
8286 proc rowmenu {x y id} {
8287 global rowctxmenu selectedline rowmenuid curview
8288 global nullid nullid2 fakerowmenu mainhead markedid
8290 stopfinding
8291 set rowmenuid $id
8292 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
8293 set state disabled
8294 } else {
8295 set state normal
8297 if {$id ne $nullid && $id ne $nullid2} {
8298 set menu $rowctxmenu
8299 if {$mainhead ne {}} {
8300 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal
8301 } else {
8302 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
8304 if {[info exists markedid] && $markedid ne $id} {
8305 $menu entryconfigure 9 -state normal
8306 $menu entryconfigure 10 -state normal
8307 $menu entryconfigure 11 -state normal
8308 } else {
8309 $menu entryconfigure 9 -state disabled
8310 $menu entryconfigure 10 -state disabled
8311 $menu entryconfigure 11 -state disabled
8313 } else {
8314 set menu $fakerowmenu
8316 $menu entryconfigure [mca "Diff this -> selected"] -state $state
8317 $menu entryconfigure [mca "Diff selected -> this"] -state $state
8318 $menu entryconfigure [mca "Make patch"] -state $state
8319 tk_popup $menu $x $y
8322 proc markhere {} {
8323 global rowmenuid markedid canv
8325 set markedid $rowmenuid
8326 make_idmark $markedid
8329 proc gotomark {} {
8330 global markedid
8332 if {[info exists markedid]} {
8333 selbyid $markedid
8337 proc replace_by_kids {l r} {
8338 global curview children
8340 set id [commitonrow $r]
8341 set l [lreplace $l 0 0]
8342 foreach kid $children($curview,$id) {
8343 lappend l [rowofcommit $kid]
8345 return [lsort -integer -decreasing -unique $l]
8348 proc find_common_desc {} {
8349 global markedid rowmenuid curview children
8351 if {![info exists markedid]} return
8352 if {![commitinview $markedid $curview] ||
8353 ![commitinview $rowmenuid $curview]} return
8354 #set t1 [clock clicks -milliseconds]
8355 set l1 [list [rowofcommit $markedid]]
8356 set l2 [list [rowofcommit $rowmenuid]]
8357 while 1 {
8358 set r1 [lindex $l1 0]
8359 set r2 [lindex $l2 0]
8360 if {$r1 eq {} || $r2 eq {}} break
8361 if {$r1 == $r2} {
8362 selectline $r1 1
8363 break
8365 if {$r1 > $r2} {
8366 set l1 [replace_by_kids $l1 $r1]
8367 } else {
8368 set l2 [replace_by_kids $l2 $r2]
8371 #set t2 [clock clicks -milliseconds]
8372 #puts "took [expr {$t2-$t1}]ms"
8375 proc compare_commits {} {
8376 global markedid rowmenuid curview children
8378 if {![info exists markedid]} return
8379 if {![commitinview $markedid $curview]} return
8380 addtohistory [list do_cmp_commits $markedid $rowmenuid]
8381 do_cmp_commits $markedid $rowmenuid
8384 proc getpatchid {id} {
8385 global patchids
8387 if {![info exists patchids($id)]} {
8388 set cmd [diffcmd [list $id] {-p --root}]
8389 # trim off the initial "|"
8390 set cmd [lrange $cmd 1 end]
8391 if {[catch {
8392 set x [eval exec $cmd | git patch-id]
8393 set patchids($id) [lindex $x 0]
8394 }]} {
8395 set patchids($id) "error"
8398 return $patchids($id)
8401 proc do_cmp_commits {a b} {
8402 global ctext curview parents children patchids commitinfo
8404 $ctext conf -state normal
8405 clear_ctext
8406 init_flist {}
8407 for {set i 0} {$i < 100} {incr i} {
8408 set skipa 0
8409 set skipb 0
8410 if {[llength $parents($curview,$a)] > 1} {
8411 appendshortlink $a [mc "Skipping merge commit "] "\n"
8412 set skipa 1
8413 } else {
8414 set patcha [getpatchid $a]
8416 if {[llength $parents($curview,$b)] > 1} {
8417 appendshortlink $b [mc "Skipping merge commit "] "\n"
8418 set skipb 1
8419 } else {
8420 set patchb [getpatchid $b]
8422 if {!$skipa && !$skipb} {
8423 set heada [lindex $commitinfo($a) 0]
8424 set headb [lindex $commitinfo($b) 0]
8425 if {$patcha eq "error"} {
8426 appendshortlink $a [mc "Error getting patch ID for "] \
8427 [mc " - stopping\n"]
8428 break
8430 if {$patchb eq "error"} {
8431 appendshortlink $b [mc "Error getting patch ID for "] \
8432 [mc " - stopping\n"]
8433 break
8435 if {$patcha eq $patchb} {
8436 if {$heada eq $headb} {
8437 appendshortlink $a [mc "Commit "]
8438 appendshortlink $b " == " " $heada\n"
8439 } else {
8440 appendshortlink $a [mc "Commit "] " $heada\n"
8441 appendshortlink $b [mc " is the same patch as\n "] \
8442 " $headb\n"
8444 set skipa 1
8445 set skipb 1
8446 } else {
8447 $ctext insert end "\n"
8448 appendshortlink $a [mc "Commit "] " $heada\n"
8449 appendshortlink $b [mc " differs from\n "] \
8450 " $headb\n"
8451 $ctext insert end [mc "- stopping\n"]
8452 break
8455 if {$skipa} {
8456 if {[llength $children($curview,$a)] != 1} {
8457 $ctext insert end "\n"
8458 appendshortlink $a [mc "Commit "] \
8459 [mc " has %s children - stopping\n" \
8460 [llength $children($curview,$a)]]
8461 break
8463 set a [lindex $children($curview,$a) 0]
8465 if {$skipb} {
8466 if {[llength $children($curview,$b)] != 1} {
8467 appendshortlink $b [mc "Commit "] \
8468 [mc " has %s children - stopping\n" \
8469 [llength $children($curview,$b)]]
8470 break
8472 set b [lindex $children($curview,$b) 0]
8475 $ctext conf -state disabled
8478 proc diffvssel {dirn} {
8479 global rowmenuid selectedline
8481 if {$selectedline eq {}} return
8482 if {$dirn} {
8483 set oldid [commitonrow $selectedline]
8484 set newid $rowmenuid
8485 } else {
8486 set oldid $rowmenuid
8487 set newid [commitonrow $selectedline]
8489 addtohistory [list doseldiff $oldid $newid] savectextpos
8490 doseldiff $oldid $newid
8493 proc doseldiff {oldid newid} {
8494 global ctext
8495 global commitinfo
8497 $ctext conf -state normal
8498 clear_ctext
8499 init_flist [mc "Top"]
8500 $ctext insert end "[mc "From"] "
8501 $ctext insert end $oldid link0
8502 setlink $oldid link0
8503 $ctext insert end "\n "
8504 $ctext insert end [lindex $commitinfo($oldid) 0]
8505 $ctext insert end "\n\n[mc "To"] "
8506 $ctext insert end $newid link1
8507 setlink $newid link1
8508 $ctext insert end "\n "
8509 $ctext insert end [lindex $commitinfo($newid) 0]
8510 $ctext insert end "\n"
8511 $ctext conf -state disabled
8512 $ctext tag remove found 1.0 end
8513 startdiff [list $oldid $newid]
8516 proc mkpatch {} {
8517 global rowmenuid currentid commitinfo patchtop patchnum NS
8519 if {![info exists currentid]} return
8520 set oldid $currentid
8521 set oldhead [lindex $commitinfo($oldid) 0]
8522 set newid $rowmenuid
8523 set newhead [lindex $commitinfo($newid) 0]
8524 set top .patch
8525 set patchtop $top
8526 catch {destroy $top}
8527 ttk_toplevel $top
8528 make_transient $top .
8529 ${NS}::label $top.title -text [mc "Generate patch"]
8530 grid $top.title - -pady 10
8531 ${NS}::label $top.from -text [mc "From:"]
8532 ${NS}::entry $top.fromsha1 -width 40
8533 $top.fromsha1 insert 0 $oldid
8534 $top.fromsha1 conf -state readonly
8535 grid $top.from $top.fromsha1 -sticky w
8536 ${NS}::entry $top.fromhead -width 60
8537 $top.fromhead insert 0 $oldhead
8538 $top.fromhead conf -state readonly
8539 grid x $top.fromhead -sticky w
8540 ${NS}::label $top.to -text [mc "To:"]
8541 ${NS}::entry $top.tosha1 -width 40
8542 $top.tosha1 insert 0 $newid
8543 $top.tosha1 conf -state readonly
8544 grid $top.to $top.tosha1 -sticky w
8545 ${NS}::entry $top.tohead -width 60
8546 $top.tohead insert 0 $newhead
8547 $top.tohead conf -state readonly
8548 grid x $top.tohead -sticky w
8549 ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
8550 grid $top.rev x -pady 10 -padx 5
8551 ${NS}::label $top.flab -text [mc "Output file:"]
8552 ${NS}::entry $top.fname -width 60
8553 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
8554 incr patchnum
8555 grid $top.flab $top.fname -sticky w
8556 ${NS}::frame $top.buts
8557 ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
8558 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
8559 bind $top <Key-Return> mkpatchgo
8560 bind $top <Key-Escape> mkpatchcan
8561 grid $top.buts.gen $top.buts.can
8562 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8563 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8564 grid $top.buts - -pady 10 -sticky ew
8565 focus $top.fname
8568 proc mkpatchrev {} {
8569 global patchtop
8571 set oldid [$patchtop.fromsha1 get]
8572 set oldhead [$patchtop.fromhead get]
8573 set newid [$patchtop.tosha1 get]
8574 set newhead [$patchtop.tohead get]
8575 foreach e [list fromsha1 fromhead tosha1 tohead] \
8576 v [list $newid $newhead $oldid $oldhead] {
8577 $patchtop.$e conf -state normal
8578 $patchtop.$e delete 0 end
8579 $patchtop.$e insert 0 $v
8580 $patchtop.$e conf -state readonly
8584 proc mkpatchgo {} {
8585 global patchtop nullid nullid2
8587 set oldid [$patchtop.fromsha1 get]
8588 set newid [$patchtop.tosha1 get]
8589 set fname [$patchtop.fname get]
8590 set cmd [diffcmd [list $oldid $newid] -p]
8591 # trim off the initial "|"
8592 set cmd [lrange $cmd 1 end]
8593 lappend cmd >$fname &
8594 if {[catch {eval exec $cmd} err]} {
8595 error_popup "[mc "Error creating patch:"] $err" $patchtop
8597 catch {destroy $patchtop}
8598 unset patchtop
8601 proc mkpatchcan {} {
8602 global patchtop
8604 catch {destroy $patchtop}
8605 unset patchtop
8608 proc mktag {} {
8609 global rowmenuid mktagtop commitinfo NS
8611 set top .maketag
8612 set mktagtop $top
8613 catch {destroy $top}
8614 ttk_toplevel $top
8615 make_transient $top .
8616 ${NS}::label $top.title -text [mc "Create tag"]
8617 grid $top.title - -pady 10
8618 ${NS}::label $top.id -text [mc "ID:"]
8619 ${NS}::entry $top.sha1 -width 40
8620 $top.sha1 insert 0 $rowmenuid
8621 $top.sha1 conf -state readonly
8622 grid $top.id $top.sha1 -sticky w
8623 ${NS}::entry $top.head -width 60
8624 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8625 $top.head conf -state readonly
8626 grid x $top.head -sticky w
8627 ${NS}::label $top.tlab -text [mc "Tag name:"]
8628 ${NS}::entry $top.tag -width 60
8629 grid $top.tlab $top.tag -sticky w
8630 ${NS}::frame $top.buts
8631 ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
8632 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
8633 bind $top <Key-Return> mktaggo
8634 bind $top <Key-Escape> mktagcan
8635 grid $top.buts.gen $top.buts.can
8636 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8637 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8638 grid $top.buts - -pady 10 -sticky ew
8639 focus $top.tag
8642 proc domktag {} {
8643 global mktagtop env tagids idtags
8645 set id [$mktagtop.sha1 get]
8646 set tag [$mktagtop.tag get]
8647 if {$tag == {}} {
8648 error_popup [mc "No tag name specified"] $mktagtop
8649 return 0
8651 if {[info exists tagids($tag)]} {
8652 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8653 return 0
8655 if {[catch {
8656 exec git tag $tag $id
8657 } err]} {
8658 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8659 return 0
8662 set tagids($tag) $id
8663 lappend idtags($id) $tag
8664 redrawtags $id
8665 addedtag $id
8666 dispneartags 0
8667 run refill_reflist
8668 return 1
8671 proc redrawtags {id} {
8672 global canv linehtag idpos currentid curview cmitlisted markedid
8673 global canvxmax iddrawn circleitem mainheadid circlecolors
8675 if {![commitinview $id $curview]} return
8676 if {![info exists iddrawn($id)]} return
8677 set row [rowofcommit $id]
8678 if {$id eq $mainheadid} {
8679 set ofill yellow
8680 } else {
8681 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8683 $canv itemconf $circleitem($row) -fill $ofill
8684 $canv delete tag.$id
8685 set xt [eval drawtags $id $idpos($id)]
8686 $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
8687 set text [$canv itemcget $linehtag($id) -text]
8688 set font [$canv itemcget $linehtag($id) -font]
8689 set xr [expr {$xt + [font measure $font $text]}]
8690 if {$xr > $canvxmax} {
8691 set canvxmax $xr
8692 setcanvscroll
8694 if {[info exists currentid] && $currentid == $id} {
8695 make_secsel $id
8697 if {[info exists markedid] && $markedid eq $id} {
8698 make_idmark $id
8702 proc mktagcan {} {
8703 global mktagtop
8705 catch {destroy $mktagtop}
8706 unset mktagtop
8709 proc mktaggo {} {
8710 if {![domktag]} return
8711 mktagcan
8714 proc writecommit {} {
8715 global rowmenuid wrcomtop commitinfo wrcomcmd NS
8717 set top .writecommit
8718 set wrcomtop $top
8719 catch {destroy $top}
8720 ttk_toplevel $top
8721 make_transient $top .
8722 ${NS}::label $top.title -text [mc "Write commit to file"]
8723 grid $top.title - -pady 10
8724 ${NS}::label $top.id -text [mc "ID:"]
8725 ${NS}::entry $top.sha1 -width 40
8726 $top.sha1 insert 0 $rowmenuid
8727 $top.sha1 conf -state readonly
8728 grid $top.id $top.sha1 -sticky w
8729 ${NS}::entry $top.head -width 60
8730 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8731 $top.head conf -state readonly
8732 grid x $top.head -sticky w
8733 ${NS}::label $top.clab -text [mc "Command:"]
8734 ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
8735 grid $top.clab $top.cmd -sticky w -pady 10
8736 ${NS}::label $top.flab -text [mc "Output file:"]
8737 ${NS}::entry $top.fname -width 60
8738 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8739 grid $top.flab $top.fname -sticky w
8740 ${NS}::frame $top.buts
8741 ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
8742 ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8743 bind $top <Key-Return> wrcomgo
8744 bind $top <Key-Escape> wrcomcan
8745 grid $top.buts.gen $top.buts.can
8746 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8747 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8748 grid $top.buts - -pady 10 -sticky ew
8749 focus $top.fname
8752 proc wrcomgo {} {
8753 global wrcomtop
8755 set id [$wrcomtop.sha1 get]
8756 set cmd "echo $id | [$wrcomtop.cmd get]"
8757 set fname [$wrcomtop.fname get]
8758 if {[catch {exec sh -c $cmd >$fname &} err]} {
8759 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8761 catch {destroy $wrcomtop}
8762 unset wrcomtop
8765 proc wrcomcan {} {
8766 global wrcomtop
8768 catch {destroy $wrcomtop}
8769 unset wrcomtop
8772 proc mkbranch {} {
8773 global rowmenuid mkbrtop NS
8775 set top .makebranch
8776 catch {destroy $top}
8777 ttk_toplevel $top
8778 make_transient $top .
8779 ${NS}::label $top.title -text [mc "Create new branch"]
8780 grid $top.title - -pady 10
8781 ${NS}::label $top.id -text [mc "ID:"]
8782 ${NS}::entry $top.sha1 -width 40
8783 $top.sha1 insert 0 $rowmenuid
8784 $top.sha1 conf -state readonly
8785 grid $top.id $top.sha1 -sticky w
8786 ${NS}::label $top.nlab -text [mc "Name:"]
8787 ${NS}::entry $top.name -width 40
8788 grid $top.nlab $top.name -sticky w
8789 ${NS}::frame $top.buts
8790 ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8791 ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8792 bind $top <Key-Return> [list mkbrgo $top]
8793 bind $top <Key-Escape> "catch {destroy $top}"
8794 grid $top.buts.go $top.buts.can
8795 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8796 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8797 grid $top.buts - -pady 10 -sticky ew
8798 focus $top.name
8801 proc mkbrgo {top} {
8802 global headids idheads
8804 set name [$top.name get]
8805 set id [$top.sha1 get]
8806 set cmdargs {}
8807 set old_id {}
8808 if {$name eq {}} {
8809 error_popup [mc "Please specify a name for the new branch"] $top
8810 return
8812 if {[info exists headids($name)]} {
8813 if {![confirm_popup [mc \
8814 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8815 return
8817 set old_id $headids($name)
8818 lappend cmdargs -f
8820 catch {destroy $top}
8821 lappend cmdargs $name $id
8822 nowbusy newbranch
8823 update
8824 if {[catch {
8825 eval exec git branch $cmdargs
8826 } err]} {
8827 notbusy newbranch
8828 error_popup $err
8829 } else {
8830 notbusy newbranch
8831 if {$old_id ne {}} {
8832 movehead $id $name
8833 movedhead $id $name
8834 redrawtags $old_id
8835 redrawtags $id
8836 } else {
8837 set headids($name) $id
8838 lappend idheads($id) $name
8839 addedhead $id $name
8840 redrawtags $id
8842 dispneartags 0
8843 run refill_reflist
8847 proc exec_citool {tool_args {baseid {}}} {
8848 global commitinfo env
8850 set save_env [array get env GIT_AUTHOR_*]
8852 if {$baseid ne {}} {
8853 if {![info exists commitinfo($baseid)]} {
8854 getcommit $baseid
8856 set author [lindex $commitinfo($baseid) 1]
8857 set date [lindex $commitinfo($baseid) 2]
8858 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8859 $author author name email]
8860 && $date ne {}} {
8861 set env(GIT_AUTHOR_NAME) $name
8862 set env(GIT_AUTHOR_EMAIL) $email
8863 set env(GIT_AUTHOR_DATE) $date
8867 eval exec git citool $tool_args &
8869 array unset env GIT_AUTHOR_*
8870 array set env $save_env
8873 proc cherrypick {} {
8874 global rowmenuid curview
8875 global mainhead mainheadid
8877 set oldhead [exec git rev-parse HEAD]
8878 set dheads [descheads $rowmenuid]
8879 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8880 set ok [confirm_popup [mc "Commit %s is already\
8881 included in branch %s -- really re-apply it?" \
8882 [string range $rowmenuid 0 7] $mainhead]]
8883 if {!$ok} return
8885 nowbusy cherrypick [mc "Cherry-picking"]
8886 update
8887 # Unfortunately git-cherry-pick writes stuff to stderr even when
8888 # no error occurs, and exec takes that as an indication of error...
8889 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8890 notbusy cherrypick
8891 if {[regexp -line \
8892 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8893 $err msg fname]} {
8894 error_popup [mc "Cherry-pick failed because of local changes\
8895 to file '%s'.\nPlease commit, reset or stash\
8896 your changes and try again." $fname]
8897 } elseif {[regexp -line \
8898 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8899 $err]} {
8900 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8901 conflict.\nDo you wish to run git citool to\
8902 resolve it?"]]} {
8903 # Force citool to read MERGE_MSG
8904 file delete [file join [gitdir] "GITGUI_MSG"]
8905 exec_citool {} $rowmenuid
8907 } else {
8908 error_popup $err
8910 run updatecommits
8911 return
8913 set newhead [exec git rev-parse HEAD]
8914 if {$newhead eq $oldhead} {
8915 notbusy cherrypick
8916 error_popup [mc "No changes committed"]
8917 return
8919 addnewchild $newhead $oldhead
8920 if {[commitinview $oldhead $curview]} {
8921 # XXX this isn't right if we have a path limit...
8922 insertrow $newhead $oldhead $curview
8923 if {$mainhead ne {}} {
8924 movehead $newhead $mainhead
8925 movedhead $newhead $mainhead
8927 set mainheadid $newhead
8928 redrawtags $oldhead
8929 redrawtags $newhead
8930 selbyid $newhead
8932 notbusy cherrypick
8935 proc resethead {} {
8936 global mainhead rowmenuid confirm_ok resettype NS
8938 set confirm_ok 0
8939 set w ".confirmreset"
8940 ttk_toplevel $w
8941 make_transient $w .
8942 wm title $w [mc "Confirm reset"]
8943 ${NS}::label $w.m -text \
8944 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
8945 pack $w.m -side top -fill x -padx 20 -pady 20
8946 ${NS}::labelframe $w.f -text [mc "Reset type:"]
8947 set resettype mixed
8948 ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
8949 -text [mc "Soft: Leave working tree and index untouched"]
8950 grid $w.f.soft -sticky w
8951 ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
8952 -text [mc "Mixed: Leave working tree untouched, reset index"]
8953 grid $w.f.mixed -sticky w
8954 ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
8955 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8956 grid $w.f.hard -sticky w
8957 pack $w.f -side top -fill x -padx 4
8958 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8959 pack $w.ok -side left -fill x -padx 20 -pady 20
8960 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
8961 bind $w <Key-Escape> [list destroy $w]
8962 pack $w.cancel -side right -fill x -padx 20 -pady 20
8963 bind $w <Visibility> "grab $w; focus $w"
8964 tkwait window $w
8965 if {!$confirm_ok} return
8966 if {[catch {set fd [open \
8967 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8968 error_popup $err
8969 } else {
8970 dohidelocalchanges
8971 filerun $fd [list readresetstat $fd]
8972 nowbusy reset [mc "Resetting"]
8973 selbyid $rowmenuid
8977 proc readresetstat {fd} {
8978 global mainhead mainheadid showlocalchanges rprogcoord
8980 if {[gets $fd line] >= 0} {
8981 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8982 set rprogcoord [expr {1.0 * $m / $n}]
8983 adjustprogress
8985 return 1
8987 set rprogcoord 0
8988 adjustprogress
8989 notbusy reset
8990 if {[catch {close $fd} err]} {
8991 error_popup $err
8993 set oldhead $mainheadid
8994 set newhead [exec git rev-parse HEAD]
8995 if {$newhead ne $oldhead} {
8996 movehead $newhead $mainhead
8997 movedhead $newhead $mainhead
8998 set mainheadid $newhead
8999 redrawtags $oldhead
9000 redrawtags $newhead
9002 if {$showlocalchanges} {
9003 doshowlocalchanges
9005 return 0
9008 # context menu for a head
9009 proc headmenu {x y id head} {
9010 global headmenuid headmenuhead headctxmenu mainhead
9012 stopfinding
9013 set headmenuid $id
9014 set headmenuhead $head
9015 set state normal
9016 if {$head eq $mainhead} {
9017 set state disabled
9019 $headctxmenu entryconfigure 0 -state $state
9020 $headctxmenu entryconfigure 1 -state $state
9021 tk_popup $headctxmenu $x $y
9024 proc cobranch {} {
9025 global headmenuid headmenuhead headids
9026 global showlocalchanges
9028 # check the tree is clean first??
9029 nowbusy checkout [mc "Checking out"]
9030 update
9031 dohidelocalchanges
9032 if {[catch {
9033 set fd [open [list | git checkout $headmenuhead 2>@1] r]
9034 } err]} {
9035 notbusy checkout
9036 error_popup $err
9037 if {$showlocalchanges} {
9038 dodiffindex
9040 } else {
9041 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
9045 proc readcheckoutstat {fd newhead newheadid} {
9046 global mainhead mainheadid headids showlocalchanges progresscoords
9047 global viewmainheadid curview
9049 if {[gets $fd line] >= 0} {
9050 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9051 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
9052 adjustprogress
9054 return 1
9056 set progresscoords {0 0}
9057 adjustprogress
9058 notbusy checkout
9059 if {[catch {close $fd} err]} {
9060 error_popup $err
9062 set oldmainid $mainheadid
9063 set mainhead $newhead
9064 set mainheadid $newheadid
9065 set viewmainheadid($curview) $newheadid
9066 redrawtags $oldmainid
9067 redrawtags $newheadid
9068 selbyid $newheadid
9069 if {$showlocalchanges} {
9070 dodiffindex
9074 proc rmbranch {} {
9075 global headmenuid headmenuhead mainhead
9076 global idheads
9078 set head $headmenuhead
9079 set id $headmenuid
9080 # this check shouldn't be needed any more...
9081 if {$head eq $mainhead} {
9082 error_popup [mc "Cannot delete the currently checked-out branch"]
9083 return
9085 set dheads [descheads $id]
9086 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
9087 # the stuff on this branch isn't on any other branch
9088 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
9089 branch.\nReally delete branch %s?" $head $head]]} return
9091 nowbusy rmbranch
9092 update
9093 if {[catch {exec git branch -D $head} err]} {
9094 notbusy rmbranch
9095 error_popup $err
9096 return
9098 removehead $id $head
9099 removedhead $id $head
9100 redrawtags $id
9101 notbusy rmbranch
9102 dispneartags 0
9103 run refill_reflist
9106 # Display a list of tags and heads
9107 proc showrefs {} {
9108 global showrefstop bgcolor fgcolor selectbgcolor NS
9109 global bglist fglist reflistfilter reflist maincursor
9111 set top .showrefs
9112 set showrefstop $top
9113 if {[winfo exists $top]} {
9114 raise $top
9115 refill_reflist
9116 return
9118 ttk_toplevel $top
9119 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
9120 make_transient $top .
9121 text $top.list -background $bgcolor -foreground $fgcolor \
9122 -selectbackground $selectbgcolor -font mainfont \
9123 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
9124 -width 30 -height 20 -cursor $maincursor \
9125 -spacing1 1 -spacing3 1 -state disabled
9126 $top.list tag configure highlight -background $selectbgcolor
9127 lappend bglist $top.list
9128 lappend fglist $top.list
9129 ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
9130 ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
9131 grid $top.list $top.ysb -sticky nsew
9132 grid $top.xsb x -sticky ew
9133 ${NS}::frame $top.f
9134 ${NS}::label $top.f.l -text "[mc "Filter"]: "
9135 ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
9136 set reflistfilter "*"
9137 trace add variable reflistfilter write reflistfilter_change
9138 pack $top.f.e -side right -fill x -expand 1
9139 pack $top.f.l -side left
9140 grid $top.f - -sticky ew -pady 2
9141 ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
9142 bind $top <Key-Escape> [list destroy $top]
9143 grid $top.close -
9144 grid columnconfigure $top 0 -weight 1
9145 grid rowconfigure $top 0 -weight 1
9146 bind $top.list <1> {break}
9147 bind $top.list <B1-Motion> {break}
9148 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
9149 set reflist {}
9150 refill_reflist
9153 proc sel_reflist {w x y} {
9154 global showrefstop reflist headids tagids otherrefids
9156 if {![winfo exists $showrefstop]} return
9157 set l [lindex [split [$w index "@$x,$y"] "."] 0]
9158 set ref [lindex $reflist [expr {$l-1}]]
9159 set n [lindex $ref 0]
9160 switch -- [lindex $ref 1] {
9161 "H" {selbyid $headids($n)}
9162 "T" {selbyid $tagids($n)}
9163 "o" {selbyid $otherrefids($n)}
9165 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
9168 proc unsel_reflist {} {
9169 global showrefstop
9171 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9172 $showrefstop.list tag remove highlight 0.0 end
9175 proc reflistfilter_change {n1 n2 op} {
9176 global reflistfilter
9178 after cancel refill_reflist
9179 after 200 refill_reflist
9182 proc refill_reflist {} {
9183 global reflist reflistfilter showrefstop headids tagids otherrefids
9184 global curview
9186 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9187 set refs {}
9188 foreach n [array names headids] {
9189 if {[string match $reflistfilter $n]} {
9190 if {[commitinview $headids($n) $curview]} {
9191 lappend refs [list $n H]
9192 } else {
9193 interestedin $headids($n) {run refill_reflist}
9197 foreach n [array names tagids] {
9198 if {[string match $reflistfilter $n]} {
9199 if {[commitinview $tagids($n) $curview]} {
9200 lappend refs [list $n T]
9201 } else {
9202 interestedin $tagids($n) {run refill_reflist}
9206 foreach n [array names otherrefids] {
9207 if {[string match $reflistfilter $n]} {
9208 if {[commitinview $otherrefids($n) $curview]} {
9209 lappend refs [list $n o]
9210 } else {
9211 interestedin $otherrefids($n) {run refill_reflist}
9215 set refs [lsort -index 0 $refs]
9216 if {$refs eq $reflist} return
9218 # Update the contents of $showrefstop.list according to the
9219 # differences between $reflist (old) and $refs (new)
9220 $showrefstop.list conf -state normal
9221 $showrefstop.list insert end "\n"
9222 set i 0
9223 set j 0
9224 while {$i < [llength $reflist] || $j < [llength $refs]} {
9225 if {$i < [llength $reflist]} {
9226 if {$j < [llength $refs]} {
9227 set cmp [string compare [lindex $reflist $i 0] \
9228 [lindex $refs $j 0]]
9229 if {$cmp == 0} {
9230 set cmp [string compare [lindex $reflist $i 1] \
9231 [lindex $refs $j 1]]
9233 } else {
9234 set cmp -1
9236 } else {
9237 set cmp 1
9239 switch -- $cmp {
9240 -1 {
9241 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
9242 incr i
9245 incr i
9246 incr j
9249 set l [expr {$j + 1}]
9250 $showrefstop.list image create $l.0 -align baseline \
9251 -image reficon-[lindex $refs $j 1] -padx 2
9252 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
9253 incr j
9257 set reflist $refs
9258 # delete last newline
9259 $showrefstop.list delete end-2c end-1c
9260 $showrefstop.list conf -state disabled
9263 # Stuff for finding nearby tags
9264 proc getallcommits {} {
9265 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
9266 global idheads idtags idotherrefs allparents tagobjid
9268 if {![info exists allcommits]} {
9269 set nextarc 0
9270 set allcommits 0
9271 set seeds {}
9272 set allcwait 0
9273 set cachedarcs 0
9274 set allccache [file join [gitdir] "gitk.cache"]
9275 if {![catch {
9276 set f [open $allccache r]
9277 set allcwait 1
9278 getcache $f
9279 }]} return
9282 if {$allcwait} {
9283 return
9285 set cmd [list | git rev-list --parents]
9286 set allcupdate [expr {$seeds ne {}}]
9287 if {!$allcupdate} {
9288 set ids "--all"
9289 } else {
9290 set refs [concat [array names idheads] [array names idtags] \
9291 [array names idotherrefs]]
9292 set ids {}
9293 set tagobjs {}
9294 foreach name [array names tagobjid] {
9295 lappend tagobjs $tagobjid($name)
9297 foreach id [lsort -unique $refs] {
9298 if {![info exists allparents($id)] &&
9299 [lsearch -exact $tagobjs $id] < 0} {
9300 lappend ids $id
9303 if {$ids ne {}} {
9304 foreach id $seeds {
9305 lappend ids "^$id"
9309 if {$ids ne {}} {
9310 set fd [open [concat $cmd $ids] r]
9311 fconfigure $fd -blocking 0
9312 incr allcommits
9313 nowbusy allcommits
9314 filerun $fd [list getallclines $fd]
9315 } else {
9316 dispneartags 0
9320 # Since most commits have 1 parent and 1 child, we group strings of
9321 # such commits into "arcs" joining branch/merge points (BMPs), which
9322 # are commits that either don't have 1 parent or don't have 1 child.
9324 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
9325 # arcout(id) - outgoing arcs for BMP
9326 # arcids(a) - list of IDs on arc including end but not start
9327 # arcstart(a) - BMP ID at start of arc
9328 # arcend(a) - BMP ID at end of arc
9329 # growing(a) - arc a is still growing
9330 # arctags(a) - IDs out of arcids (excluding end) that have tags
9331 # archeads(a) - IDs out of arcids (excluding end) that have heads
9332 # The start of an arc is at the descendent end, so "incoming" means
9333 # coming from descendents, and "outgoing" means going towards ancestors.
9335 proc getallclines {fd} {
9336 global allparents allchildren idtags idheads nextarc
9337 global arcnos arcids arctags arcout arcend arcstart archeads growing
9338 global seeds allcommits cachedarcs allcupdate
9340 set nid 0
9341 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
9342 set id [lindex $line 0]
9343 if {[info exists allparents($id)]} {
9344 # seen it already
9345 continue
9347 set cachedarcs 0
9348 set olds [lrange $line 1 end]
9349 set allparents($id) $olds
9350 if {![info exists allchildren($id)]} {
9351 set allchildren($id) {}
9352 set arcnos($id) {}
9353 lappend seeds $id
9354 } else {
9355 set a $arcnos($id)
9356 if {[llength $olds] == 1 && [llength $a] == 1} {
9357 lappend arcids($a) $id
9358 if {[info exists idtags($id)]} {
9359 lappend arctags($a) $id
9361 if {[info exists idheads($id)]} {
9362 lappend archeads($a) $id
9364 if {[info exists allparents($olds)]} {
9365 # seen parent already
9366 if {![info exists arcout($olds)]} {
9367 splitarc $olds
9369 lappend arcids($a) $olds
9370 set arcend($a) $olds
9371 unset growing($a)
9373 lappend allchildren($olds) $id
9374 lappend arcnos($olds) $a
9375 continue
9378 foreach a $arcnos($id) {
9379 lappend arcids($a) $id
9380 set arcend($a) $id
9381 unset growing($a)
9384 set ao {}
9385 foreach p $olds {
9386 lappend allchildren($p) $id
9387 set a [incr nextarc]
9388 set arcstart($a) $id
9389 set archeads($a) {}
9390 set arctags($a) {}
9391 set archeads($a) {}
9392 set arcids($a) {}
9393 lappend ao $a
9394 set growing($a) 1
9395 if {[info exists allparents($p)]} {
9396 # seen it already, may need to make a new branch
9397 if {![info exists arcout($p)]} {
9398 splitarc $p
9400 lappend arcids($a) $p
9401 set arcend($a) $p
9402 unset growing($a)
9404 lappend arcnos($p) $a
9406 set arcout($id) $ao
9408 if {$nid > 0} {
9409 global cached_dheads cached_dtags cached_atags
9410 catch {unset cached_dheads}
9411 catch {unset cached_dtags}
9412 catch {unset cached_atags}
9414 if {![eof $fd]} {
9415 return [expr {$nid >= 1000? 2: 1}]
9417 set cacheok 1
9418 if {[catch {
9419 fconfigure $fd -blocking 1
9420 close $fd
9421 } err]} {
9422 # got an error reading the list of commits
9423 # if we were updating, try rereading the whole thing again
9424 if {$allcupdate} {
9425 incr allcommits -1
9426 dropcache $err
9427 return
9429 error_popup "[mc "Error reading commit topology information;\
9430 branch and preceding/following tag information\
9431 will be incomplete."]\n($err)"
9432 set cacheok 0
9434 if {[incr allcommits -1] == 0} {
9435 notbusy allcommits
9436 if {$cacheok} {
9437 run savecache
9440 dispneartags 0
9441 return 0
9444 proc recalcarc {a} {
9445 global arctags archeads arcids idtags idheads
9447 set at {}
9448 set ah {}
9449 foreach id [lrange $arcids($a) 0 end-1] {
9450 if {[info exists idtags($id)]} {
9451 lappend at $id
9453 if {[info exists idheads($id)]} {
9454 lappend ah $id
9457 set arctags($a) $at
9458 set archeads($a) $ah
9461 proc splitarc {p} {
9462 global arcnos arcids nextarc arctags archeads idtags idheads
9463 global arcstart arcend arcout allparents growing
9465 set a $arcnos($p)
9466 if {[llength $a] != 1} {
9467 puts "oops splitarc called but [llength $a] arcs already"
9468 return
9470 set a [lindex $a 0]
9471 set i [lsearch -exact $arcids($a) $p]
9472 if {$i < 0} {
9473 puts "oops splitarc $p not in arc $a"
9474 return
9476 set na [incr nextarc]
9477 if {[info exists arcend($a)]} {
9478 set arcend($na) $arcend($a)
9479 } else {
9480 set l [lindex $allparents([lindex $arcids($a) end]) 0]
9481 set j [lsearch -exact $arcnos($l) $a]
9482 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
9484 set tail [lrange $arcids($a) [expr {$i+1}] end]
9485 set arcids($a) [lrange $arcids($a) 0 $i]
9486 set arcend($a) $p
9487 set arcstart($na) $p
9488 set arcout($p) $na
9489 set arcids($na) $tail
9490 if {[info exists growing($a)]} {
9491 set growing($na) 1
9492 unset growing($a)
9495 foreach id $tail {
9496 if {[llength $arcnos($id)] == 1} {
9497 set arcnos($id) $na
9498 } else {
9499 set j [lsearch -exact $arcnos($id) $a]
9500 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
9504 # reconstruct tags and heads lists
9505 if {$arctags($a) ne {} || $archeads($a) ne {}} {
9506 recalcarc $a
9507 recalcarc $na
9508 } else {
9509 set arctags($na) {}
9510 set archeads($na) {}
9514 # Update things for a new commit added that is a child of one
9515 # existing commit. Used when cherry-picking.
9516 proc addnewchild {id p} {
9517 global allparents allchildren idtags nextarc
9518 global arcnos arcids arctags arcout arcend arcstart archeads growing
9519 global seeds allcommits
9521 if {![info exists allcommits] || ![info exists arcnos($p)]} return
9522 set allparents($id) [list $p]
9523 set allchildren($id) {}
9524 set arcnos($id) {}
9525 lappend seeds $id
9526 lappend allchildren($p) $id
9527 set a [incr nextarc]
9528 set arcstart($a) $id
9529 set archeads($a) {}
9530 set arctags($a) {}
9531 set arcids($a) [list $p]
9532 set arcend($a) $p
9533 if {![info exists arcout($p)]} {
9534 splitarc $p
9536 lappend arcnos($p) $a
9537 set arcout($id) [list $a]
9540 # This implements a cache for the topology information.
9541 # The cache saves, for each arc, the start and end of the arc,
9542 # the ids on the arc, and the outgoing arcs from the end.
9543 proc readcache {f} {
9544 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
9545 global idtags idheads allparents cachedarcs possible_seeds seeds growing
9546 global allcwait
9548 set a $nextarc
9549 set lim $cachedarcs
9550 if {$lim - $a > 500} {
9551 set lim [expr {$a + 500}]
9553 if {[catch {
9554 if {$a == $lim} {
9555 # finish reading the cache and setting up arctags, etc.
9556 set line [gets $f]
9557 if {$line ne "1"} {error "bad final version"}
9558 close $f
9559 foreach id [array names idtags] {
9560 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9561 [llength $allparents($id)] == 1} {
9562 set a [lindex $arcnos($id) 0]
9563 if {$arctags($a) eq {}} {
9564 recalcarc $a
9568 foreach id [array names idheads] {
9569 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9570 [llength $allparents($id)] == 1} {
9571 set a [lindex $arcnos($id) 0]
9572 if {$archeads($a) eq {}} {
9573 recalcarc $a
9577 foreach id [lsort -unique $possible_seeds] {
9578 if {$arcnos($id) eq {}} {
9579 lappend seeds $id
9582 set allcwait 0
9583 } else {
9584 while {[incr a] <= $lim} {
9585 set line [gets $f]
9586 if {[llength $line] != 3} {error "bad line"}
9587 set s [lindex $line 0]
9588 set arcstart($a) $s
9589 lappend arcout($s) $a
9590 if {![info exists arcnos($s)]} {
9591 lappend possible_seeds $s
9592 set arcnos($s) {}
9594 set e [lindex $line 1]
9595 if {$e eq {}} {
9596 set growing($a) 1
9597 } else {
9598 set arcend($a) $e
9599 if {![info exists arcout($e)]} {
9600 set arcout($e) {}
9603 set arcids($a) [lindex $line 2]
9604 foreach id $arcids($a) {
9605 lappend allparents($s) $id
9606 set s $id
9607 lappend arcnos($id) $a
9609 if {![info exists allparents($s)]} {
9610 set allparents($s) {}
9612 set arctags($a) {}
9613 set archeads($a) {}
9615 set nextarc [expr {$a - 1}]
9617 } err]} {
9618 dropcache $err
9619 return 0
9621 if {!$allcwait} {
9622 getallcommits
9624 return $allcwait
9627 proc getcache {f} {
9628 global nextarc cachedarcs possible_seeds
9630 if {[catch {
9631 set line [gets $f]
9632 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9633 # make sure it's an integer
9634 set cachedarcs [expr {int([lindex $line 1])}]
9635 if {$cachedarcs < 0} {error "bad number of arcs"}
9636 set nextarc 0
9637 set possible_seeds {}
9638 run readcache $f
9639 } err]} {
9640 dropcache $err
9642 return 0
9645 proc dropcache {err} {
9646 global allcwait nextarc cachedarcs seeds
9648 #puts "dropping cache ($err)"
9649 foreach v {arcnos arcout arcids arcstart arcend growing \
9650 arctags archeads allparents allchildren} {
9651 global $v
9652 catch {unset $v}
9654 set allcwait 0
9655 set nextarc 0
9656 set cachedarcs 0
9657 set seeds {}
9658 getallcommits
9661 proc writecache {f} {
9662 global cachearc cachedarcs allccache
9663 global arcstart arcend arcnos arcids arcout
9665 set a $cachearc
9666 set lim $cachedarcs
9667 if {$lim - $a > 1000} {
9668 set lim [expr {$a + 1000}]
9670 if {[catch {
9671 while {[incr a] <= $lim} {
9672 if {[info exists arcend($a)]} {
9673 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9674 } else {
9675 puts $f [list $arcstart($a) {} $arcids($a)]
9678 } err]} {
9679 catch {close $f}
9680 catch {file delete $allccache}
9681 #puts "writing cache failed ($err)"
9682 return 0
9684 set cachearc [expr {$a - 1}]
9685 if {$a > $cachedarcs} {
9686 puts $f "1"
9687 close $f
9688 return 0
9690 return 1
9693 proc savecache {} {
9694 global nextarc cachedarcs cachearc allccache
9696 if {$nextarc == $cachedarcs} return
9697 set cachearc 0
9698 set cachedarcs $nextarc
9699 catch {
9700 set f [open $allccache w]
9701 puts $f [list 1 $cachedarcs]
9702 run writecache $f
9706 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9707 # or 0 if neither is true.
9708 proc anc_or_desc {a b} {
9709 global arcout arcstart arcend arcnos cached_isanc
9711 if {$arcnos($a) eq $arcnos($b)} {
9712 # Both are on the same arc(s); either both are the same BMP,
9713 # or if one is not a BMP, the other is also not a BMP or is
9714 # the BMP at end of the arc (and it only has 1 incoming arc).
9715 # Or both can be BMPs with no incoming arcs.
9716 if {$a eq $b || $arcnos($a) eq {}} {
9717 return 0
9719 # assert {[llength $arcnos($a)] == 1}
9720 set arc [lindex $arcnos($a) 0]
9721 set i [lsearch -exact $arcids($arc) $a]
9722 set j [lsearch -exact $arcids($arc) $b]
9723 if {$i < 0 || $i > $j} {
9724 return 1
9725 } else {
9726 return -1
9730 if {![info exists arcout($a)]} {
9731 set arc [lindex $arcnos($a) 0]
9732 if {[info exists arcend($arc)]} {
9733 set aend $arcend($arc)
9734 } else {
9735 set aend {}
9737 set a $arcstart($arc)
9738 } else {
9739 set aend $a
9741 if {![info exists arcout($b)]} {
9742 set arc [lindex $arcnos($b) 0]
9743 if {[info exists arcend($arc)]} {
9744 set bend $arcend($arc)
9745 } else {
9746 set bend {}
9748 set b $arcstart($arc)
9749 } else {
9750 set bend $b
9752 if {$a eq $bend} {
9753 return 1
9755 if {$b eq $aend} {
9756 return -1
9758 if {[info exists cached_isanc($a,$bend)]} {
9759 if {$cached_isanc($a,$bend)} {
9760 return 1
9763 if {[info exists cached_isanc($b,$aend)]} {
9764 if {$cached_isanc($b,$aend)} {
9765 return -1
9767 if {[info exists cached_isanc($a,$bend)]} {
9768 return 0
9772 set todo [list $a $b]
9773 set anc($a) a
9774 set anc($b) b
9775 for {set i 0} {$i < [llength $todo]} {incr i} {
9776 set x [lindex $todo $i]
9777 if {$anc($x) eq {}} {
9778 continue
9780 foreach arc $arcnos($x) {
9781 set xd $arcstart($arc)
9782 if {$xd eq $bend} {
9783 set cached_isanc($a,$bend) 1
9784 set cached_isanc($b,$aend) 0
9785 return 1
9786 } elseif {$xd eq $aend} {
9787 set cached_isanc($b,$aend) 1
9788 set cached_isanc($a,$bend) 0
9789 return -1
9791 if {![info exists anc($xd)]} {
9792 set anc($xd) $anc($x)
9793 lappend todo $xd
9794 } elseif {$anc($xd) ne $anc($x)} {
9795 set anc($xd) {}
9799 set cached_isanc($a,$bend) 0
9800 set cached_isanc($b,$aend) 0
9801 return 0
9804 # This identifies whether $desc has an ancestor that is
9805 # a growing tip of the graph and which is not an ancestor of $anc
9806 # and returns 0 if so and 1 if not.
9807 # If we subsequently discover a tag on such a growing tip, and that
9808 # turns out to be a descendent of $anc (which it could, since we
9809 # don't necessarily see children before parents), then $desc
9810 # isn't a good choice to display as a descendent tag of
9811 # $anc (since it is the descendent of another tag which is
9812 # a descendent of $anc). Similarly, $anc isn't a good choice to
9813 # display as a ancestor tag of $desc.
9815 proc is_certain {desc anc} {
9816 global arcnos arcout arcstart arcend growing problems
9818 set certain {}
9819 if {[llength $arcnos($anc)] == 1} {
9820 # tags on the same arc are certain
9821 if {$arcnos($desc) eq $arcnos($anc)} {
9822 return 1
9824 if {![info exists arcout($anc)]} {
9825 # if $anc is partway along an arc, use the start of the arc instead
9826 set a [lindex $arcnos($anc) 0]
9827 set anc $arcstart($a)
9830 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9831 set x $desc
9832 } else {
9833 set a [lindex $arcnos($desc) 0]
9834 set x $arcend($a)
9836 if {$x == $anc} {
9837 return 1
9839 set anclist [list $x]
9840 set dl($x) 1
9841 set nnh 1
9842 set ngrowanc 0
9843 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9844 set x [lindex $anclist $i]
9845 if {$dl($x)} {
9846 incr nnh -1
9848 set done($x) 1
9849 foreach a $arcout($x) {
9850 if {[info exists growing($a)]} {
9851 if {![info exists growanc($x)] && $dl($x)} {
9852 set growanc($x) 1
9853 incr ngrowanc
9855 } else {
9856 set y $arcend($a)
9857 if {[info exists dl($y)]} {
9858 if {$dl($y)} {
9859 if {!$dl($x)} {
9860 set dl($y) 0
9861 if {![info exists done($y)]} {
9862 incr nnh -1
9864 if {[info exists growanc($x)]} {
9865 incr ngrowanc -1
9867 set xl [list $y]
9868 for {set k 0} {$k < [llength $xl]} {incr k} {
9869 set z [lindex $xl $k]
9870 foreach c $arcout($z) {
9871 if {[info exists arcend($c)]} {
9872 set v $arcend($c)
9873 if {[info exists dl($v)] && $dl($v)} {
9874 set dl($v) 0
9875 if {![info exists done($v)]} {
9876 incr nnh -1
9878 if {[info exists growanc($v)]} {
9879 incr ngrowanc -1
9881 lappend xl $v
9888 } elseif {$y eq $anc || !$dl($x)} {
9889 set dl($y) 0
9890 lappend anclist $y
9891 } else {
9892 set dl($y) 1
9893 lappend anclist $y
9894 incr nnh
9899 foreach x [array names growanc] {
9900 if {$dl($x)} {
9901 return 0
9903 return 0
9905 return 1
9908 proc validate_arctags {a} {
9909 global arctags idtags
9911 set i -1
9912 set na $arctags($a)
9913 foreach id $arctags($a) {
9914 incr i
9915 if {![info exists idtags($id)]} {
9916 set na [lreplace $na $i $i]
9917 incr i -1
9920 set arctags($a) $na
9923 proc validate_archeads {a} {
9924 global archeads idheads
9926 set i -1
9927 set na $archeads($a)
9928 foreach id $archeads($a) {
9929 incr i
9930 if {![info exists idheads($id)]} {
9931 set na [lreplace $na $i $i]
9932 incr i -1
9935 set archeads($a) $na
9938 # Return the list of IDs that have tags that are descendents of id,
9939 # ignoring IDs that are descendents of IDs already reported.
9940 proc desctags {id} {
9941 global arcnos arcstart arcids arctags idtags allparents
9942 global growing cached_dtags
9944 if {![info exists allparents($id)]} {
9945 return {}
9947 set t1 [clock clicks -milliseconds]
9948 set argid $id
9949 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9950 # part-way along an arc; check that arc first
9951 set a [lindex $arcnos($id) 0]
9952 if {$arctags($a) ne {}} {
9953 validate_arctags $a
9954 set i [lsearch -exact $arcids($a) $id]
9955 set tid {}
9956 foreach t $arctags($a) {
9957 set j [lsearch -exact $arcids($a) $t]
9958 if {$j >= $i} break
9959 set tid $t
9961 if {$tid ne {}} {
9962 return $tid
9965 set id $arcstart($a)
9966 if {[info exists idtags($id)]} {
9967 return $id
9970 if {[info exists cached_dtags($id)]} {
9971 return $cached_dtags($id)
9974 set origid $id
9975 set todo [list $id]
9976 set queued($id) 1
9977 set nc 1
9978 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9979 set id [lindex $todo $i]
9980 set done($id) 1
9981 set ta [info exists hastaggedancestor($id)]
9982 if {!$ta} {
9983 incr nc -1
9985 # ignore tags on starting node
9986 if {!$ta && $i > 0} {
9987 if {[info exists idtags($id)]} {
9988 set tagloc($id) $id
9989 set ta 1
9990 } elseif {[info exists cached_dtags($id)]} {
9991 set tagloc($id) $cached_dtags($id)
9992 set ta 1
9995 foreach a $arcnos($id) {
9996 set d $arcstart($a)
9997 if {!$ta && $arctags($a) ne {}} {
9998 validate_arctags $a
9999 if {$arctags($a) ne {}} {
10000 lappend tagloc($id) [lindex $arctags($a) end]
10003 if {$ta || $arctags($a) ne {}} {
10004 set tomark [list $d]
10005 for {set j 0} {$j < [llength $tomark]} {incr j} {
10006 set dd [lindex $tomark $j]
10007 if {![info exists hastaggedancestor($dd)]} {
10008 if {[info exists done($dd)]} {
10009 foreach b $arcnos($dd) {
10010 lappend tomark $arcstart($b)
10012 if {[info exists tagloc($dd)]} {
10013 unset tagloc($dd)
10015 } elseif {[info exists queued($dd)]} {
10016 incr nc -1
10018 set hastaggedancestor($dd) 1
10022 if {![info exists queued($d)]} {
10023 lappend todo $d
10024 set queued($d) 1
10025 if {![info exists hastaggedancestor($d)]} {
10026 incr nc
10031 set tags {}
10032 foreach id [array names tagloc] {
10033 if {![info exists hastaggedancestor($id)]} {
10034 foreach t $tagloc($id) {
10035 if {[lsearch -exact $tags $t] < 0} {
10036 lappend tags $t
10041 set t2 [clock clicks -milliseconds]
10042 set loopix $i
10044 # remove tags that are descendents of other tags
10045 for {set i 0} {$i < [llength $tags]} {incr i} {
10046 set a [lindex $tags $i]
10047 for {set j 0} {$j < $i} {incr j} {
10048 set b [lindex $tags $j]
10049 set r [anc_or_desc $a $b]
10050 if {$r == 1} {
10051 set tags [lreplace $tags $j $j]
10052 incr j -1
10053 incr i -1
10054 } elseif {$r == -1} {
10055 set tags [lreplace $tags $i $i]
10056 incr i -1
10057 break
10062 if {[array names growing] ne {}} {
10063 # graph isn't finished, need to check if any tag could get
10064 # eclipsed by another tag coming later. Simply ignore any
10065 # tags that could later get eclipsed.
10066 set ctags {}
10067 foreach t $tags {
10068 if {[is_certain $t $origid]} {
10069 lappend ctags $t
10072 if {$tags eq $ctags} {
10073 set cached_dtags($origid) $tags
10074 } else {
10075 set tags $ctags
10077 } else {
10078 set cached_dtags($origid) $tags
10080 set t3 [clock clicks -milliseconds]
10081 if {0 && $t3 - $t1 >= 100} {
10082 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
10083 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10085 return $tags
10088 proc anctags {id} {
10089 global arcnos arcids arcout arcend arctags idtags allparents
10090 global growing cached_atags
10092 if {![info exists allparents($id)]} {
10093 return {}
10095 set t1 [clock clicks -milliseconds]
10096 set argid $id
10097 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10098 # part-way along an arc; check that arc first
10099 set a [lindex $arcnos($id) 0]
10100 if {$arctags($a) ne {}} {
10101 validate_arctags $a
10102 set i [lsearch -exact $arcids($a) $id]
10103 foreach t $arctags($a) {
10104 set j [lsearch -exact $arcids($a) $t]
10105 if {$j > $i} {
10106 return $t
10110 if {![info exists arcend($a)]} {
10111 return {}
10113 set id $arcend($a)
10114 if {[info exists idtags($id)]} {
10115 return $id
10118 if {[info exists cached_atags($id)]} {
10119 return $cached_atags($id)
10122 set origid $id
10123 set todo [list $id]
10124 set queued($id) 1
10125 set taglist {}
10126 set nc 1
10127 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10128 set id [lindex $todo $i]
10129 set done($id) 1
10130 set td [info exists hastaggeddescendent($id)]
10131 if {!$td} {
10132 incr nc -1
10134 # ignore tags on starting node
10135 if {!$td && $i > 0} {
10136 if {[info exists idtags($id)]} {
10137 set tagloc($id) $id
10138 set td 1
10139 } elseif {[info exists cached_atags($id)]} {
10140 set tagloc($id) $cached_atags($id)
10141 set td 1
10144 foreach a $arcout($id) {
10145 if {!$td && $arctags($a) ne {}} {
10146 validate_arctags $a
10147 if {$arctags($a) ne {}} {
10148 lappend tagloc($id) [lindex $arctags($a) 0]
10151 if {![info exists arcend($a)]} continue
10152 set d $arcend($a)
10153 if {$td || $arctags($a) ne {}} {
10154 set tomark [list $d]
10155 for {set j 0} {$j < [llength $tomark]} {incr j} {
10156 set dd [lindex $tomark $j]
10157 if {![info exists hastaggeddescendent($dd)]} {
10158 if {[info exists done($dd)]} {
10159 foreach b $arcout($dd) {
10160 if {[info exists arcend($b)]} {
10161 lappend tomark $arcend($b)
10164 if {[info exists tagloc($dd)]} {
10165 unset tagloc($dd)
10167 } elseif {[info exists queued($dd)]} {
10168 incr nc -1
10170 set hastaggeddescendent($dd) 1
10174 if {![info exists queued($d)]} {
10175 lappend todo $d
10176 set queued($d) 1
10177 if {![info exists hastaggeddescendent($d)]} {
10178 incr nc
10183 set t2 [clock clicks -milliseconds]
10184 set loopix $i
10185 set tags {}
10186 foreach id [array names tagloc] {
10187 if {![info exists hastaggeddescendent($id)]} {
10188 foreach t $tagloc($id) {
10189 if {[lsearch -exact $tags $t] < 0} {
10190 lappend tags $t
10196 # remove tags that are ancestors of other tags
10197 for {set i 0} {$i < [llength $tags]} {incr i} {
10198 set a [lindex $tags $i]
10199 for {set j 0} {$j < $i} {incr j} {
10200 set b [lindex $tags $j]
10201 set r [anc_or_desc $a $b]
10202 if {$r == -1} {
10203 set tags [lreplace $tags $j $j]
10204 incr j -1
10205 incr i -1
10206 } elseif {$r == 1} {
10207 set tags [lreplace $tags $i $i]
10208 incr i -1
10209 break
10214 if {[array names growing] ne {}} {
10215 # graph isn't finished, need to check if any tag could get
10216 # eclipsed by another tag coming later. Simply ignore any
10217 # tags that could later get eclipsed.
10218 set ctags {}
10219 foreach t $tags {
10220 if {[is_certain $origid $t]} {
10221 lappend ctags $t
10224 if {$tags eq $ctags} {
10225 set cached_atags($origid) $tags
10226 } else {
10227 set tags $ctags
10229 } else {
10230 set cached_atags($origid) $tags
10232 set t3 [clock clicks -milliseconds]
10233 if {0 && $t3 - $t1 >= 100} {
10234 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
10235 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10237 return $tags
10240 # Return the list of IDs that have heads that are descendents of id,
10241 # including id itself if it has a head.
10242 proc descheads {id} {
10243 global arcnos arcstart arcids archeads idheads cached_dheads
10244 global allparents
10246 if {![info exists allparents($id)]} {
10247 return {}
10249 set aret {}
10250 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10251 # part-way along an arc; check it first
10252 set a [lindex $arcnos($id) 0]
10253 if {$archeads($a) ne {}} {
10254 validate_archeads $a
10255 set i [lsearch -exact $arcids($a) $id]
10256 foreach t $archeads($a) {
10257 set j [lsearch -exact $arcids($a) $t]
10258 if {$j > $i} break
10259 lappend aret $t
10262 set id $arcstart($a)
10264 set origid $id
10265 set todo [list $id]
10266 set seen($id) 1
10267 set ret {}
10268 for {set i 0} {$i < [llength $todo]} {incr i} {
10269 set id [lindex $todo $i]
10270 if {[info exists cached_dheads($id)]} {
10271 set ret [concat $ret $cached_dheads($id)]
10272 } else {
10273 if {[info exists idheads($id)]} {
10274 lappend ret $id
10276 foreach a $arcnos($id) {
10277 if {$archeads($a) ne {}} {
10278 validate_archeads $a
10279 if {$archeads($a) ne {}} {
10280 set ret [concat $ret $archeads($a)]
10283 set d $arcstart($a)
10284 if {![info exists seen($d)]} {
10285 lappend todo $d
10286 set seen($d) 1
10291 set ret [lsort -unique $ret]
10292 set cached_dheads($origid) $ret
10293 return [concat $ret $aret]
10296 proc addedtag {id} {
10297 global arcnos arcout cached_dtags cached_atags
10299 if {![info exists arcnos($id)]} return
10300 if {![info exists arcout($id)]} {
10301 recalcarc [lindex $arcnos($id) 0]
10303 catch {unset cached_dtags}
10304 catch {unset cached_atags}
10307 proc addedhead {hid head} {
10308 global arcnos arcout cached_dheads
10310 if {![info exists arcnos($hid)]} return
10311 if {![info exists arcout($hid)]} {
10312 recalcarc [lindex $arcnos($hid) 0]
10314 catch {unset cached_dheads}
10317 proc removedhead {hid head} {
10318 global cached_dheads
10320 catch {unset cached_dheads}
10323 proc movedhead {hid head} {
10324 global arcnos arcout cached_dheads
10326 if {![info exists arcnos($hid)]} return
10327 if {![info exists arcout($hid)]} {
10328 recalcarc [lindex $arcnos($hid) 0]
10330 catch {unset cached_dheads}
10333 proc changedrefs {} {
10334 global cached_dheads cached_dtags cached_atags
10335 global arctags archeads arcnos arcout idheads idtags
10337 foreach id [concat [array names idheads] [array names idtags]] {
10338 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
10339 set a [lindex $arcnos($id) 0]
10340 if {![info exists donearc($a)]} {
10341 recalcarc $a
10342 set donearc($a) 1
10346 catch {unset cached_dtags}
10347 catch {unset cached_atags}
10348 catch {unset cached_dheads}
10351 proc rereadrefs {} {
10352 global idtags idheads idotherrefs mainheadid
10354 set refids [concat [array names idtags] \
10355 [array names idheads] [array names idotherrefs]]
10356 foreach id $refids {
10357 if {![info exists ref($id)]} {
10358 set ref($id) [listrefs $id]
10361 set oldmainhead $mainheadid
10362 readrefs
10363 changedrefs
10364 set refids [lsort -unique [concat $refids [array names idtags] \
10365 [array names idheads] [array names idotherrefs]]]
10366 foreach id $refids {
10367 set v [listrefs $id]
10368 if {![info exists ref($id)] || $ref($id) != $v} {
10369 redrawtags $id
10372 if {$oldmainhead ne $mainheadid} {
10373 redrawtags $oldmainhead
10374 redrawtags $mainheadid
10376 run refill_reflist
10379 proc listrefs {id} {
10380 global idtags idheads idotherrefs
10382 set x {}
10383 if {[info exists idtags($id)]} {
10384 set x $idtags($id)
10386 set y {}
10387 if {[info exists idheads($id)]} {
10388 set y $idheads($id)
10390 set z {}
10391 if {[info exists idotherrefs($id)]} {
10392 set z $idotherrefs($id)
10394 return [list $x $y $z]
10397 proc showtag {tag isnew} {
10398 global ctext tagcontents tagids linknum tagobjid
10400 if {$isnew} {
10401 addtohistory [list showtag $tag 0] savectextpos
10403 $ctext conf -state normal
10404 clear_ctext
10405 settabs 0
10406 set linknum 0
10407 if {![info exists tagcontents($tag)]} {
10408 catch {
10409 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
10412 if {[info exists tagcontents($tag)]} {
10413 set text $tagcontents($tag)
10414 } else {
10415 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
10417 appendwithlinks $text {}
10418 maybe_scroll_ctext
10419 $ctext conf -state disabled
10420 init_flist {}
10423 proc doquit {} {
10424 global stopped
10425 global gitktmpdir
10427 set stopped 100
10428 savestuff .
10429 destroy .
10431 if {[info exists gitktmpdir]} {
10432 catch {file delete -force $gitktmpdir}
10436 proc mkfontdisp {font top which} {
10437 global fontattr fontpref $font NS use_ttk
10439 set fontpref($font) [set $font]
10440 ${NS}::button $top.${font}but -text $which \
10441 -command [list choosefont $font $which]
10442 if {!$use_ttk} {$top.${font}but configure -font optionfont}
10443 ${NS}::label $top.$font -relief flat -font $font \
10444 -text $fontattr($font,family) -justify left
10445 grid x $top.${font}but $top.$font -sticky w
10448 proc choosefont {font which} {
10449 global fontparam fontlist fonttop fontattr
10450 global prefstop NS
10452 set fontparam(which) $which
10453 set fontparam(font) $font
10454 set fontparam(family) [font actual $font -family]
10455 set fontparam(size) $fontattr($font,size)
10456 set fontparam(weight) $fontattr($font,weight)
10457 set fontparam(slant) $fontattr($font,slant)
10458 set top .gitkfont
10459 set fonttop $top
10460 if {![winfo exists $top]} {
10461 font create sample
10462 eval font config sample [font actual $font]
10463 ttk_toplevel $top
10464 make_transient $top $prefstop
10465 wm title $top [mc "Gitk font chooser"]
10466 ${NS}::label $top.l -textvariable fontparam(which)
10467 pack $top.l -side top
10468 set fontlist [lsort [font families]]
10469 ${NS}::frame $top.f
10470 listbox $top.f.fam -listvariable fontlist \
10471 -yscrollcommand [list $top.f.sb set]
10472 bind $top.f.fam <<ListboxSelect>> selfontfam
10473 ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
10474 pack $top.f.sb -side right -fill y
10475 pack $top.f.fam -side left -fill both -expand 1
10476 pack $top.f -side top -fill both -expand 1
10477 ${NS}::frame $top.g
10478 spinbox $top.g.size -from 4 -to 40 -width 4 \
10479 -textvariable fontparam(size) \
10480 -validatecommand {string is integer -strict %s}
10481 checkbutton $top.g.bold -padx 5 \
10482 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
10483 -variable fontparam(weight) -onvalue bold -offvalue normal
10484 checkbutton $top.g.ital -padx 5 \
10485 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
10486 -variable fontparam(slant) -onvalue italic -offvalue roman
10487 pack $top.g.size $top.g.bold $top.g.ital -side left
10488 pack $top.g -side top
10489 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
10490 -background white
10491 $top.c create text 100 25 -anchor center -text $which -font sample \
10492 -fill black -tags text
10493 bind $top.c <Configure> [list centertext $top.c]
10494 pack $top.c -side top -fill x
10495 ${NS}::frame $top.buts
10496 ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
10497 ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
10498 bind $top <Key-Return> fontok
10499 bind $top <Key-Escape> fontcan
10500 grid $top.buts.ok $top.buts.can
10501 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10502 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10503 pack $top.buts -side bottom -fill x
10504 trace add variable fontparam write chg_fontparam
10505 } else {
10506 raise $top
10507 $top.c itemconf text -text $which
10509 set i [lsearch -exact $fontlist $fontparam(family)]
10510 if {$i >= 0} {
10511 $top.f.fam selection set $i
10512 $top.f.fam see $i
10516 proc centertext {w} {
10517 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
10520 proc fontok {} {
10521 global fontparam fontpref prefstop
10523 set f $fontparam(font)
10524 set fontpref($f) [list $fontparam(family) $fontparam(size)]
10525 if {$fontparam(weight) eq "bold"} {
10526 lappend fontpref($f) "bold"
10528 if {$fontparam(slant) eq "italic"} {
10529 lappend fontpref($f) "italic"
10531 set w $prefstop.$f
10532 $w conf -text $fontparam(family) -font $fontpref($f)
10534 fontcan
10537 proc fontcan {} {
10538 global fonttop fontparam
10540 if {[info exists fonttop]} {
10541 catch {destroy $fonttop}
10542 catch {font delete sample}
10543 unset fonttop
10544 unset fontparam
10548 if {[package vsatisfies [package provide Tk] 8.6]} {
10549 # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
10550 # function to make use of it.
10551 proc choosefont {font which} {
10552 tk fontchooser configure -title $which -font $font \
10553 -command [list on_choosefont $font $which]
10554 tk fontchooser show
10556 proc on_choosefont {font which newfont} {
10557 global fontparam
10558 puts stderr "$font $newfont"
10559 array set f [font actual $newfont]
10560 set fontparam(which) $which
10561 set fontparam(font) $font
10562 set fontparam(family) $f(-family)
10563 set fontparam(size) $f(-size)
10564 set fontparam(weight) $f(-weight)
10565 set fontparam(slant) $f(-slant)
10566 fontok
10570 proc selfontfam {} {
10571 global fonttop fontparam
10573 set i [$fonttop.f.fam curselection]
10574 if {$i ne {}} {
10575 set fontparam(family) [$fonttop.f.fam get $i]
10579 proc chg_fontparam {v sub op} {
10580 global fontparam
10582 font config sample -$sub $fontparam($sub)
10585 proc doprefs {} {
10586 global maxwidth maxgraphpct use_ttk NS
10587 global oldprefs prefstop showneartags showlocalchanges
10588 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
10589 global tabstop limitdiffs autoselect extdifftool perfile_attrs
10590 global hideremotes want_ttk have_ttk
10592 set top .gitkprefs
10593 set prefstop $top
10594 if {[winfo exists $top]} {
10595 raise $top
10596 return
10598 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10599 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
10600 set oldprefs($v) [set $v]
10602 ttk_toplevel $top
10603 wm title $top [mc "Gitk preferences"]
10604 make_transient $top .
10605 ${NS}::label $top.ldisp -text [mc "Commit list display options"]
10606 grid $top.ldisp - -sticky w -pady 10
10607 ${NS}::label $top.spacer -text " "
10608 ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"]
10609 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10610 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
10611 ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"]
10612 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10613 grid x $top.maxpctl $top.maxpct -sticky w
10614 ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
10615 -variable showlocalchanges
10616 grid x $top.showlocal -sticky w
10617 ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
10618 -variable autoselect
10619 grid x $top.autoselect -sticky w
10620 ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
10621 -variable hideremotes
10622 grid x $top.hideremotes -sticky w
10624 ${NS}::label $top.ddisp -text [mc "Diff display options"]
10625 grid $top.ddisp - -sticky w -pady 10
10626 ${NS}::label $top.tabstopl -text [mc "Tab spacing"]
10627 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10628 grid x $top.tabstopl $top.tabstop -sticky w
10629 ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \
10630 -variable showneartags
10631 grid x $top.ntag -sticky w
10632 ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
10633 -variable limitdiffs
10634 grid x $top.ldiff -sticky w
10635 ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \
10636 -variable perfile_attrs
10637 grid x $top.lattr -sticky w
10639 ${NS}::entry $top.extdifft -textvariable extdifftool
10640 ${NS}::frame $top.extdifff
10641 ${NS}::label $top.extdifff.l -text [mc "External diff tool" ]
10642 ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff
10643 pack $top.extdifff.l $top.extdifff.b -side left
10644 pack configure $top.extdifff.l -padx 10
10645 grid x $top.extdifff $top.extdifft -sticky ew
10647 ${NS}::label $top.lgen -text [mc "General options"]
10648 grid $top.lgen - -sticky w -pady 10
10649 ${NS}::checkbutton $top.want_ttk -variable want_ttk \
10650 -text [mc "Use themed widgets"]
10651 if {$have_ttk} {
10652 ${NS}::label $top.ttk_note -text [mc "(change requires restart)"]
10653 } else {
10654 ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"]
10656 grid x $top.want_ttk $top.ttk_note -sticky w
10658 ${NS}::label $top.cdisp -text [mc "Colors: press to choose"]
10659 grid $top.cdisp - -sticky w -pady 10
10660 label $top.bg -padx 40 -relief sunk -background $bgcolor
10661 ${NS}::button $top.bgbut -text [mc "Background"] \
10662 -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg]
10663 grid x $top.bgbut $top.bg -sticky w
10664 label $top.fg -padx 40 -relief sunk -background $fgcolor
10665 ${NS}::button $top.fgbut -text [mc "Foreground"] \
10666 -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg]
10667 grid x $top.fgbut $top.fg -sticky w
10668 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10669 ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \
10670 -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \
10671 [list $ctext tag conf d0 -foreground]]
10672 grid x $top.diffoldbut $top.diffold -sticky w
10673 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10674 ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \
10675 -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
10676 [list $ctext tag conf dresult -foreground]]
10677 grid x $top.diffnewbut $top.diffnew -sticky w
10678 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10679 ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \
10680 -command [list choosecolor diffcolors 2 $top.hunksep \
10681 [mc "diff hunk header"] \
10682 [list $ctext tag conf hunksep -foreground]]
10683 grid x $top.hunksepbut $top.hunksep -sticky w
10684 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10685 ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \
10686 -command [list choosecolor markbgcolor {} $top.markbgsep \
10687 [mc "marked line background"] \
10688 [list $ctext tag conf omark -background]]
10689 grid x $top.markbgbut $top.markbgsep -sticky w
10690 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10691 ${NS}::button $top.selbgbut -text [mc "Select bg"] \
10692 -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
10693 grid x $top.selbgbut $top.selbgsep -sticky w
10695 ${NS}::label $top.cfont -text [mc "Fonts: press to choose"]
10696 grid $top.cfont - -sticky w -pady 10
10697 mkfontdisp mainfont $top [mc "Main font"]
10698 mkfontdisp textfont $top [mc "Diff display font"]
10699 mkfontdisp uifont $top [mc "User interface font"]
10701 if {!$use_ttk} {
10702 foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag
10703 ldiff lattr extdifff.l extdifff.b bgbut fgbut
10704 diffoldbut diffnewbut hunksepbut markbgbut selbgbut
10705 want_ttk ttk_note} {
10706 $top.$w configure -font optionfont
10710 ${NS}::frame $top.buts
10711 ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10712 ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10713 bind $top <Key-Return> prefsok
10714 bind $top <Key-Escape> prefscan
10715 grid $top.buts.ok $top.buts.can
10716 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10717 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10718 grid $top.buts - - -pady 10 -sticky ew
10719 grid columnconfigure $top 2 -weight 1
10720 bind $top <Visibility> "focus $top.buts.ok"
10723 proc choose_extdiff {} {
10724 global extdifftool
10726 set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false]
10727 if {$prog ne {}} {
10728 set extdifftool $prog
10732 proc choosecolor {v vi w x cmd} {
10733 global $v
10735 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10736 -title [mc "Gitk: choose color for %s" $x]]
10737 if {$c eq {}} return
10738 $w conf -background $c
10739 lset $v $vi $c
10740 eval $cmd $c
10743 proc setselbg {c} {
10744 global bglist cflist
10745 foreach w $bglist {
10746 $w configure -selectbackground $c
10748 $cflist tag configure highlight \
10749 -background [$cflist cget -selectbackground]
10750 allcanvs itemconf secsel -fill $c
10753 proc setbg {c} {
10754 global bglist
10756 foreach w $bglist {
10757 $w conf -background $c
10761 proc setfg {c} {
10762 global fglist canv
10764 foreach w $fglist {
10765 $w conf -foreground $c
10767 allcanvs itemconf text -fill $c
10768 $canv itemconf circle -outline $c
10769 $canv itemconf markid -outline $c
10772 proc prefscan {} {
10773 global oldprefs prefstop
10775 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10776 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
10777 global $v
10778 set $v $oldprefs($v)
10780 catch {destroy $prefstop}
10781 unset prefstop
10782 fontcan
10785 proc prefsok {} {
10786 global maxwidth maxgraphpct
10787 global oldprefs prefstop showneartags showlocalchanges
10788 global fontpref mainfont textfont uifont
10789 global limitdiffs treediffs perfile_attrs
10790 global hideremotes
10792 catch {destroy $prefstop}
10793 unset prefstop
10794 fontcan
10795 set fontchanged 0
10796 if {$mainfont ne $fontpref(mainfont)} {
10797 set mainfont $fontpref(mainfont)
10798 parsefont mainfont $mainfont
10799 eval font configure mainfont [fontflags mainfont]
10800 eval font configure mainfontbold [fontflags mainfont 1]
10801 setcoords
10802 set fontchanged 1
10804 if {$textfont ne $fontpref(textfont)} {
10805 set textfont $fontpref(textfont)
10806 parsefont textfont $textfont
10807 eval font configure textfont [fontflags textfont]
10808 eval font configure textfontbold [fontflags textfont 1]
10810 if {$uifont ne $fontpref(uifont)} {
10811 set uifont $fontpref(uifont)
10812 parsefont uifont $uifont
10813 eval font configure uifont [fontflags uifont]
10815 settabs
10816 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10817 if {$showlocalchanges} {
10818 doshowlocalchanges
10819 } else {
10820 dohidelocalchanges
10823 if {$limitdiffs != $oldprefs(limitdiffs) ||
10824 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10825 # treediffs elements are limited by path;
10826 # won't have encodings cached if perfile_attrs was just turned on
10827 catch {unset treediffs}
10829 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10830 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10831 redisplay
10832 } elseif {$showneartags != $oldprefs(showneartags) ||
10833 $limitdiffs != $oldprefs(limitdiffs)} {
10834 reselectline
10836 if {$hideremotes != $oldprefs(hideremotes)} {
10837 rereadrefs
10841 proc formatdate {d} {
10842 global datetimeformat
10843 if {$d ne {}} {
10844 set d [clock format $d -format $datetimeformat]
10846 return $d
10849 # This list of encoding names and aliases is distilled from
10850 # http://www.iana.org/assignments/character-sets.
10851 # Not all of them are supported by Tcl.
10852 set encoding_aliases {
10853 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10854 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10855 { ISO-10646-UTF-1 csISO10646UTF1 }
10856 { ISO_646.basic:1983 ref csISO646basic1983 }
10857 { INVARIANT csINVARIANT }
10858 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10859 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10860 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10861 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10862 { NATS-DANO iso-ir-9-1 csNATSDANO }
10863 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10864 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10865 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10866 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10867 { ISO-2022-KR csISO2022KR }
10868 { EUC-KR csEUCKR }
10869 { ISO-2022-JP csISO2022JP }
10870 { ISO-2022-JP-2 csISO2022JP2 }
10871 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10872 csISO13JISC6220jp }
10873 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10874 { IT iso-ir-15 ISO646-IT csISO15Italian }
10875 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10876 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10877 { greek7-old iso-ir-18 csISO18Greek7Old }
10878 { latin-greek iso-ir-19 csISO19LatinGreek }
10879 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10880 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10881 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10882 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10883 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10884 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10885 { INIS iso-ir-49 csISO49INIS }
10886 { INIS-8 iso-ir-50 csISO50INIS8 }
10887 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10888 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10889 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10890 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10891 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10892 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10893 csISO60Norwegian1 }
10894 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10895 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10896 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10897 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10898 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10899 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10900 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10901 { greek7 iso-ir-88 csISO88Greek7 }
10902 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10903 { iso-ir-90 csISO90 }
10904 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10905 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10906 csISO92JISC62991984b }
10907 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10908 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10909 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10910 csISO95JIS62291984handadd }
10911 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10912 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10913 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10914 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10915 CP819 csISOLatin1 }
10916 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10917 { T.61-7bit iso-ir-102 csISO102T617bit }
10918 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10919 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10920 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10921 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10922 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10923 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10924 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10925 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10926 arabic csISOLatinArabic }
10927 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10928 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10929 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10930 greek greek8 csISOLatinGreek }
10931 { T.101-G2 iso-ir-128 csISO128T101G2 }
10932 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10933 csISOLatinHebrew }
10934 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10935 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10936 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10937 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10938 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10939 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10940 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10941 csISOLatinCyrillic }
10942 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10943 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10944 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10945 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10946 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10947 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10948 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10949 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10950 { ISO_10367-box iso-ir-155 csISO10367Box }
10951 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10952 { latin-lap lap iso-ir-158 csISO158Lap }
10953 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10954 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10955 { us-dk csUSDK }
10956 { dk-us csDKUS }
10957 { JIS_X0201 X0201 csHalfWidthKatakana }
10958 { KSC5636 ISO646-KR csKSC5636 }
10959 { ISO-10646-UCS-2 csUnicode }
10960 { ISO-10646-UCS-4 csUCS4 }
10961 { DEC-MCS dec csDECMCS }
10962 { hp-roman8 roman8 r8 csHPRoman8 }
10963 { macintosh mac csMacintosh }
10964 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10965 csIBM037 }
10966 { IBM038 EBCDIC-INT cp038 csIBM038 }
10967 { IBM273 CP273 csIBM273 }
10968 { IBM274 EBCDIC-BE CP274 csIBM274 }
10969 { IBM275 EBCDIC-BR cp275 csIBM275 }
10970 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10971 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10972 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10973 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10974 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10975 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10976 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10977 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10978 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10979 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10980 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10981 { IBM437 cp437 437 csPC8CodePage437 }
10982 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10983 { IBM775 cp775 csPC775Baltic }
10984 { IBM850 cp850 850 csPC850Multilingual }
10985 { IBM851 cp851 851 csIBM851 }
10986 { IBM852 cp852 852 csPCp852 }
10987 { IBM855 cp855 855 csIBM855 }
10988 { IBM857 cp857 857 csIBM857 }
10989 { IBM860 cp860 860 csIBM860 }
10990 { IBM861 cp861 861 cp-is csIBM861 }
10991 { IBM862 cp862 862 csPC862LatinHebrew }
10992 { IBM863 cp863 863 csIBM863 }
10993 { IBM864 cp864 csIBM864 }
10994 { IBM865 cp865 865 csIBM865 }
10995 { IBM866 cp866 866 csIBM866 }
10996 { IBM868 CP868 cp-ar csIBM868 }
10997 { IBM869 cp869 869 cp-gr csIBM869 }
10998 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10999 { IBM871 CP871 ebcdic-cp-is csIBM871 }
11000 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
11001 { IBM891 cp891 csIBM891 }
11002 { IBM903 cp903 csIBM903 }
11003 { IBM904 cp904 904 csIBBM904 }
11004 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
11005 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
11006 { IBM1026 CP1026 csIBM1026 }
11007 { EBCDIC-AT-DE csIBMEBCDICATDE }
11008 { EBCDIC-AT-DE-A csEBCDICATDEA }
11009 { EBCDIC-CA-FR csEBCDICCAFR }
11010 { EBCDIC-DK-NO csEBCDICDKNO }
11011 { EBCDIC-DK-NO-A csEBCDICDKNOA }
11012 { EBCDIC-FI-SE csEBCDICFISE }
11013 { EBCDIC-FI-SE-A csEBCDICFISEA }
11014 { EBCDIC-FR csEBCDICFR }
11015 { EBCDIC-IT csEBCDICIT }
11016 { EBCDIC-PT csEBCDICPT }
11017 { EBCDIC-ES csEBCDICES }
11018 { EBCDIC-ES-A csEBCDICESA }
11019 { EBCDIC-ES-S csEBCDICESS }
11020 { EBCDIC-UK csEBCDICUK }
11021 { EBCDIC-US csEBCDICUS }
11022 { UNKNOWN-8BIT csUnknown8BiT }
11023 { MNEMONIC csMnemonic }
11024 { MNEM csMnem }
11025 { VISCII csVISCII }
11026 { VIQR csVIQR }
11027 { KOI8-R csKOI8R }
11028 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
11029 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
11030 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
11031 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
11032 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
11033 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
11034 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
11035 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
11036 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
11037 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
11038 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
11039 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
11040 { IBM1047 IBM-1047 }
11041 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
11042 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
11043 { UNICODE-1-1 csUnicode11 }
11044 { CESU-8 csCESU-8 }
11045 { BOCU-1 csBOCU-1 }
11046 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
11047 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
11048 l8 }
11049 { ISO-8859-15 ISO_8859-15 Latin-9 }
11050 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
11051 { GBK CP936 MS936 windows-936 }
11052 { JIS_Encoding csJISEncoding }
11053 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
11054 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
11055 EUC-JP }
11056 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
11057 { ISO-10646-UCS-Basic csUnicodeASCII }
11058 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
11059 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
11060 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
11061 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
11062 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
11063 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
11064 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
11065 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
11066 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
11067 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
11068 { Adobe-Standard-Encoding csAdobeStandardEncoding }
11069 { Ventura-US csVenturaUS }
11070 { Ventura-International csVenturaInternational }
11071 { PC8-Danish-Norwegian csPC8DanishNorwegian }
11072 { PC8-Turkish csPC8Turkish }
11073 { IBM-Symbols csIBMSymbols }
11074 { IBM-Thai csIBMThai }
11075 { HP-Legal csHPLegal }
11076 { HP-Pi-font csHPPiFont }
11077 { HP-Math8 csHPMath8 }
11078 { Adobe-Symbol-Encoding csHPPSMath }
11079 { HP-DeskTop csHPDesktop }
11080 { Ventura-Math csVenturaMath }
11081 { Microsoft-Publishing csMicrosoftPublishing }
11082 { Windows-31J csWindows31J }
11083 { GB2312 csGB2312 }
11084 { Big5 csBig5 }
11087 proc tcl_encoding {enc} {
11088 global encoding_aliases tcl_encoding_cache
11089 if {[info exists tcl_encoding_cache($enc)]} {
11090 return $tcl_encoding_cache($enc)
11092 set names [encoding names]
11093 set lcnames [string tolower $names]
11094 set enc [string tolower $enc]
11095 set i [lsearch -exact $lcnames $enc]
11096 if {$i < 0} {
11097 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
11098 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
11099 set i [lsearch -exact $lcnames $encx]
11102 if {$i < 0} {
11103 foreach l $encoding_aliases {
11104 set ll [string tolower $l]
11105 if {[lsearch -exact $ll $enc] < 0} continue
11106 # look through the aliases for one that tcl knows about
11107 foreach e $ll {
11108 set i [lsearch -exact $lcnames $e]
11109 if {$i < 0} {
11110 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
11111 set i [lsearch -exact $lcnames $ex]
11114 if {$i >= 0} break
11116 break
11119 set tclenc {}
11120 if {$i >= 0} {
11121 set tclenc [lindex $names $i]
11123 set tcl_encoding_cache($enc) $tclenc
11124 return $tclenc
11127 proc gitattr {path attr default} {
11128 global path_attr_cache
11129 if {[info exists path_attr_cache($attr,$path)]} {
11130 set r $path_attr_cache($attr,$path)
11131 } else {
11132 set r "unspecified"
11133 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
11134 regexp "(.*): $attr: (.*)" $line m f r
11136 set path_attr_cache($attr,$path) $r
11138 if {$r eq "unspecified"} {
11139 return $default
11141 return $r
11144 proc cache_gitattr {attr pathlist} {
11145 global path_attr_cache
11146 set newlist {}
11147 foreach path $pathlist {
11148 if {![info exists path_attr_cache($attr,$path)]} {
11149 lappend newlist $path
11152 set lim 1000
11153 if {[tk windowingsystem] == "win32"} {
11154 # windows has a 32k limit on the arguments to a command...
11155 set lim 30
11157 while {$newlist ne {}} {
11158 set head [lrange $newlist 0 [expr {$lim - 1}]]
11159 set newlist [lrange $newlist $lim end]
11160 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
11161 foreach row [split $rlist "\n"] {
11162 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
11163 if {[string index $path 0] eq "\""} {
11164 set path [encoding convertfrom [lindex $path 0]]
11166 set path_attr_cache($attr,$path) $value
11173 proc get_path_encoding {path} {
11174 global gui_encoding perfile_attrs
11175 set tcl_enc $gui_encoding
11176 if {$path ne {} && $perfile_attrs} {
11177 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
11178 if {$enc2 ne {}} {
11179 set tcl_enc $enc2
11182 return $tcl_enc
11185 # First check that Tcl/Tk is recent enough
11186 if {[catch {package require Tk 8.4} err]} {
11187 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
11188 Gitk requires at least Tcl/Tk 8.4."]
11189 exit 1
11192 # defaults...
11193 set wrcomcmd "git diff-tree --stdin -p --pretty"
11195 set gitencoding {}
11196 catch {
11197 set gitencoding [exec git config --get i18n.commitencoding]
11199 catch {
11200 set gitencoding [exec git config --get i18n.logoutputencoding]
11202 if {$gitencoding == ""} {
11203 set gitencoding "utf-8"
11205 set tclencoding [tcl_encoding $gitencoding]
11206 if {$tclencoding == {}} {
11207 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
11210 set gui_encoding [encoding system]
11211 catch {
11212 set enc [exec git config --get gui.encoding]
11213 if {$enc ne {}} {
11214 set tclenc [tcl_encoding $enc]
11215 if {$tclenc ne {}} {
11216 set gui_encoding $tclenc
11217 } else {
11218 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
11223 if {[tk windowingsystem] eq "aqua"} {
11224 set mainfont {{Lucida Grande} 9}
11225 set textfont {Monaco 9}
11226 set uifont {{Lucida Grande} 9 bold}
11227 } else {
11228 set mainfont {Helvetica 9}
11229 set textfont {Courier 9}
11230 set uifont {Helvetica 9 bold}
11232 set tabstop 8
11233 set findmergefiles 0
11234 set maxgraphpct 50
11235 set maxwidth 16
11236 set revlistorder 0
11237 set fastdate 0
11238 set uparrowlen 5
11239 set downarrowlen 5
11240 set mingaplen 100
11241 set cmitmode "patch"
11242 set wrapcomment "none"
11243 set showneartags 1
11244 set hideremotes 0
11245 set maxrefs 20
11246 set maxlinelen 200
11247 set showlocalchanges 1
11248 set limitdiffs 1
11249 set datetimeformat "%Y-%m-%d %H:%M:%S"
11250 set autoselect 1
11251 set perfile_attrs 0
11252 set want_ttk 1
11254 if {[tk windowingsystem] eq "aqua"} {
11255 set extdifftool "opendiff"
11256 } else {
11257 set extdifftool "meld"
11260 set colors {green red blue magenta darkgrey brown orange}
11261 set bgcolor white
11262 set fgcolor black
11263 set diffcolors {red "#00a000" blue}
11264 set diffcontext 3
11265 set ignorespace 0
11266 set selectbgcolor gray85
11267 set markbgcolor "#e0e0ff"
11269 set circlecolors {white blue gray blue blue}
11271 # button for popping up context menus
11272 if {[tk windowingsystem] eq "aqua"} {
11273 set ctxbut <Button-2>
11274 } else {
11275 set ctxbut <Button-3>
11278 ## For msgcat loading, first locate the installation location.
11279 if { [info exists ::env(GITK_MSGSDIR)] } {
11280 ## Msgsdir was manually set in the environment.
11281 set gitk_msgsdir $::env(GITK_MSGSDIR)
11282 } else {
11283 ## Let's guess the prefix from argv0.
11284 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
11285 set gitk_libdir [file join $gitk_prefix share gitk lib]
11286 set gitk_msgsdir [file join $gitk_libdir msgs]
11287 unset gitk_prefix
11290 ## Internationalization (i18n) through msgcat and gettext. See
11291 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
11292 package require msgcat
11293 namespace import ::msgcat::mc
11294 ## And eventually load the actual message catalog
11295 ::msgcat::mcload $gitk_msgsdir
11297 catch {source ~/.gitk}
11299 font create optionfont -family sans-serif -size -12
11301 parsefont mainfont $mainfont
11302 eval font create mainfont [fontflags mainfont]
11303 eval font create mainfontbold [fontflags mainfont 1]
11305 parsefont textfont $textfont
11306 eval font create textfont [fontflags textfont]
11307 eval font create textfontbold [fontflags textfont 1]
11309 parsefont uifont $uifont
11310 eval font create uifont [fontflags uifont]
11312 setoptions
11314 # check that we can find a .git directory somewhere...
11315 if {[catch {set gitdir [gitdir]}]} {
11316 show_error {} . [mc "Cannot find a git repository here."]
11317 exit 1
11319 if {![file isdirectory $gitdir]} {
11320 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
11321 exit 1
11324 set selecthead {}
11325 set selectheadid {}
11327 set revtreeargs {}
11328 set cmdline_files {}
11329 set i 0
11330 set revtreeargscmd {}
11331 foreach arg $argv {
11332 switch -glob -- $arg {
11333 "" { }
11334 "--" {
11335 set cmdline_files [lrange $argv [expr {$i + 1}] end]
11336 break
11338 "--select-commit=*" {
11339 set selecthead [string range $arg 16 end]
11341 "--argscmd=*" {
11342 set revtreeargscmd [string range $arg 10 end]
11344 default {
11345 lappend revtreeargs $arg
11348 incr i
11351 if {$selecthead eq "HEAD"} {
11352 set selecthead {}
11355 if {$i >= [llength $argv] && $revtreeargs ne {}} {
11356 # no -- on command line, but some arguments (other than --argscmd)
11357 if {[catch {
11358 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
11359 set cmdline_files [split $f "\n"]
11360 set n [llength $cmdline_files]
11361 set revtreeargs [lrange $revtreeargs 0 end-$n]
11362 # Unfortunately git rev-parse doesn't produce an error when
11363 # something is both a revision and a filename. To be consistent
11364 # with git log and git rev-list, check revtreeargs for filenames.
11365 foreach arg $revtreeargs {
11366 if {[file exists $arg]} {
11367 show_error {} . [mc "Ambiguous argument '%s': both revision\
11368 and filename" $arg]
11369 exit 1
11372 } err]} {
11373 # unfortunately we get both stdout and stderr in $err,
11374 # so look for "fatal:".
11375 set i [string first "fatal:" $err]
11376 if {$i > 0} {
11377 set err [string range $err [expr {$i + 6}] end]
11379 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
11380 exit 1
11384 set nullid "0000000000000000000000000000000000000000"
11385 set nullid2 "0000000000000000000000000000000000000001"
11386 set nullfile "/dev/null"
11388 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
11389 if {![info exists have_ttk]} {
11390 set have_ttk [llength [info commands ::ttk::style]]
11392 set use_ttk [expr {$have_ttk && $want_ttk}]
11393 set NS [expr {$use_ttk ? "ttk" : ""}]
11395 set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
11397 set runq {}
11398 set history {}
11399 set historyindex 0
11400 set fh_serial 0
11401 set nhl_names {}
11402 set highlight_paths {}
11403 set findpattern {}
11404 set searchdirn -forwards
11405 set boldids {}
11406 set boldnameids {}
11407 set diffelide {0 0}
11408 set markingmatches 0
11409 set linkentercount 0
11410 set need_redisplay 0
11411 set nrows_drawn 0
11412 set firsttabstop 0
11414 set nextviewnum 1
11415 set curview 0
11416 set selectedview 0
11417 set selectedhlview [mc "None"]
11418 set highlight_related [mc "None"]
11419 set highlight_files {}
11420 set viewfiles(0) {}
11421 set viewperm(0) 0
11422 set viewargs(0) {}
11423 set viewargscmd(0) {}
11425 set selectedline {}
11426 set numcommits 0
11427 set loginstance 0
11428 set cmdlineok 0
11429 set stopped 0
11430 set stuffsaved 0
11431 set patchnum 0
11432 set lserial 0
11433 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
11434 setcoords
11435 makewindow
11436 catch {
11437 image create photo gitlogo -width 16 -height 16
11439 image create photo gitlogominus -width 4 -height 2
11440 gitlogominus put #C00000 -to 0 0 4 2
11441 gitlogo copy gitlogominus -to 1 5
11442 gitlogo copy gitlogominus -to 6 5
11443 gitlogo copy gitlogominus -to 11 5
11444 image delete gitlogominus
11446 image create photo gitlogoplus -width 4 -height 4
11447 gitlogoplus put #008000 -to 1 0 3 4
11448 gitlogoplus put #008000 -to 0 1 4 3
11449 gitlogo copy gitlogoplus -to 1 9
11450 gitlogo copy gitlogoplus -to 6 9
11451 gitlogo copy gitlogoplus -to 11 9
11452 image delete gitlogoplus
11454 image create photo gitlogo32 -width 32 -height 32
11455 gitlogo32 copy gitlogo -zoom 2 2
11457 wm iconphoto . -default gitlogo gitlogo32
11459 # wait for the window to become visible
11460 tkwait visibility .
11461 wm title . "[file tail $argv0]: [file tail [pwd]]"
11462 update
11463 readrefs
11465 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
11466 # create a view for the files/dirs specified on the command line
11467 set curview 1
11468 set selectedview 1
11469 set nextviewnum 2
11470 set viewname(1) [mc "Command line"]
11471 set viewfiles(1) $cmdline_files
11472 set viewargs(1) $revtreeargs
11473 set viewargscmd(1) $revtreeargscmd
11474 set viewperm(1) 0
11475 set vdatemode(1) 0
11476 addviewmenu 1
11477 .bar.view entryconf [mca "Edit view..."] -state normal
11478 .bar.view entryconf [mca "Delete view"] -state normal
11481 if {[info exists permviews]} {
11482 foreach v $permviews {
11483 set n $nextviewnum
11484 incr nextviewnum
11485 set viewname($n) [lindex $v 0]
11486 set viewfiles($n) [lindex $v 1]
11487 set viewargs($n) [lindex $v 2]
11488 set viewargscmd($n) [lindex $v 3]
11489 set viewperm($n) 1
11490 addviewmenu $n
11494 if {[tk windowingsystem] eq "win32"} {
11495 focus -force .
11498 getcommits {}