gitk: Refactor code for binding modified function keys
[git/mingw/j6t.git] / gitk
blobf8f89a5052c5917fb2dc56ebd9e4902c61b2f4c8
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright © 2005-2011 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 hasworktree {} {
13 return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
14 [exec git rev-parse --is-inside-git-dir] == "false"}]
17 proc reponame {} {
18 global gitdir
19 set n [file normalize $gitdir]
20 if {[string match "*/.git" $n]} {
21 set n [string range $n 0 end-5]
23 return [file tail $n]
26 proc gitworktree {} {
27 variable _gitworktree
28 if {[info exists _gitworktree]} {
29 return $_gitworktree
31 # v1.7.0 introduced --show-toplevel to return the canonical work-tree
32 if {[catch {set _gitworktree [exec git rev-parse --show-toplevel]}]} {
33 # try to set work tree from environment, core.worktree or use
34 # cdup to obtain a relative path to the top of the worktree. If
35 # run from the top, the ./ prefix ensures normalize expands pwd.
36 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
37 catch {set _gitworktree [exec git config --get core.worktree]}
38 if {$_gitworktree eq ""} {
39 set _gitworktree [file normalize ./[exec git rev-parse --show-cdup]]
43 return $_gitworktree
46 # A simple scheduler for compute-intensive stuff.
47 # The aim is to make sure that event handlers for GUI actions can
48 # run at least every 50-100 ms. Unfortunately fileevent handlers are
49 # run before X event handlers, so reading from a fast source can
50 # make the GUI completely unresponsive.
51 proc run args {
52 global isonrunq runq currunq
54 set script $args
55 if {[info exists isonrunq($script)]} return
56 if {$runq eq {} && ![info exists currunq]} {
57 after idle dorunq
59 lappend runq [list {} $script]
60 set isonrunq($script) 1
63 proc filerun {fd script} {
64 fileevent $fd readable [list filereadable $fd $script]
67 proc filereadable {fd script} {
68 global runq currunq
70 fileevent $fd readable {}
71 if {$runq eq {} && ![info exists currunq]} {
72 after idle dorunq
74 lappend runq [list $fd $script]
77 proc nukefile {fd} {
78 global runq
80 for {set i 0} {$i < [llength $runq]} {} {
81 if {[lindex $runq $i 0] eq $fd} {
82 set runq [lreplace $runq $i $i]
83 } else {
84 incr i
89 proc dorunq {} {
90 global isonrunq runq currunq
92 set tstart [clock clicks -milliseconds]
93 set t0 $tstart
94 while {[llength $runq] > 0} {
95 set fd [lindex $runq 0 0]
96 set script [lindex $runq 0 1]
97 set currunq [lindex $runq 0]
98 set runq [lrange $runq 1 end]
99 set repeat [eval $script]
100 unset currunq
101 set t1 [clock clicks -milliseconds]
102 set t [expr {$t1 - $t0}]
103 if {$repeat ne {} && $repeat} {
104 if {$fd eq {} || $repeat == 2} {
105 # script returns 1 if it wants to be readded
106 # file readers return 2 if they could do more straight away
107 lappend runq [list $fd $script]
108 } else {
109 fileevent $fd readable [list filereadable $fd $script]
111 } elseif {$fd eq {}} {
112 unset isonrunq($script)
114 set t0 $t1
115 if {$t1 - $tstart >= 80} break
117 if {$runq ne {}} {
118 after idle dorunq
122 proc reg_instance {fd} {
123 global commfd leftover loginstance
125 set i [incr loginstance]
126 set commfd($i) $fd
127 set leftover($i) {}
128 return $i
131 proc unmerged_files {files} {
132 global nr_unmerged
134 # find the list of unmerged files
135 set mlist {}
136 set nr_unmerged 0
137 if {[catch {
138 set fd [open "| git ls-files -u" r]
139 } err]} {
140 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
141 exit 1
143 while {[gets $fd line] >= 0} {
144 set i [string first "\t" $line]
145 if {$i < 0} continue
146 set fname [string range $line [expr {$i+1}] end]
147 if {[lsearch -exact $mlist $fname] >= 0} continue
148 incr nr_unmerged
149 if {$files eq {} || [path_filter $files $fname]} {
150 lappend mlist $fname
153 catch {close $fd}
154 return $mlist
157 proc parseviewargs {n arglist} {
158 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
159 global worddiff git_version
161 set vdatemode($n) 0
162 set vmergeonly($n) 0
163 set glflags {}
164 set diffargs {}
165 set nextisval 0
166 set revargs {}
167 set origargs $arglist
168 set allknown 1
169 set filtered 0
170 set i -1
171 foreach arg $arglist {
172 incr i
173 if {$nextisval} {
174 lappend glflags $arg
175 set nextisval 0
176 continue
178 switch -glob -- $arg {
179 "-d" -
180 "--date-order" {
181 set vdatemode($n) 1
182 # remove from origargs in case we hit an unknown option
183 set origargs [lreplace $origargs $i $i]
184 incr i -1
186 "-[puabwcrRBMC]" -
187 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
188 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
189 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
190 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
191 "--ignore-space-change" - "-U*" - "--unified=*" {
192 # These request or affect diff output, which we don't want.
193 # Some could be used to set our defaults for diff display.
194 lappend diffargs $arg
196 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
197 "--name-only" - "--name-status" - "--color" -
198 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
199 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
200 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
201 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
202 "--objects" - "--objects-edge" - "--reverse" {
203 # These cause our parsing of git log's output to fail, or else
204 # they're options we want to set ourselves, so ignore them.
206 "--color-words*" - "--word-diff=color" {
207 # These trigger a word diff in the console interface,
208 # so help the user by enabling our own support
209 if {[package vcompare $git_version "1.7.2"] >= 0} {
210 set worddiff [mc "Color words"]
213 "--word-diff*" {
214 if {[package vcompare $git_version "1.7.2"] >= 0} {
215 set worddiff [mc "Markup words"]
218 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
219 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
220 "--full-history" - "--dense" - "--sparse" -
221 "--follow" - "--left-right" - "--encoding=*" {
222 # These are harmless, and some are even useful
223 lappend glflags $arg
225 "--diff-filter=*" - "--no-merges" - "--unpacked" -
226 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
227 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
228 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
229 "--remove-empty" - "--first-parent" - "--cherry-pick" -
230 "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
231 "--simplify-by-decoration" {
232 # These mean that we get a subset of the commits
233 set filtered 1
234 lappend glflags $arg
236 "-n" {
237 # This appears to be the only one that has a value as a
238 # separate word following it
239 set filtered 1
240 set nextisval 1
241 lappend glflags $arg
243 "--not" - "--all" {
244 lappend revargs $arg
246 "--merge" {
247 set vmergeonly($n) 1
248 # git rev-parse doesn't understand --merge
249 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
251 "--no-replace-objects" {
252 set env(GIT_NO_REPLACE_OBJECTS) "1"
254 "-*" {
255 # Other flag arguments including -<n>
256 if {[string is digit -strict [string range $arg 1 end]]} {
257 set filtered 1
258 } else {
259 # a flag argument that we don't recognize;
260 # that means we can't optimize
261 set allknown 0
263 lappend glflags $arg
265 default {
266 # Non-flag arguments specify commits or ranges of commits
267 if {[string match "*...*" $arg]} {
268 lappend revargs --gitk-symmetric-diff-marker
270 lappend revargs $arg
274 set vdflags($n) $diffargs
275 set vflags($n) $glflags
276 set vrevs($n) $revargs
277 set vfiltered($n) $filtered
278 set vorigargs($n) $origargs
279 return $allknown
282 proc parseviewrevs {view revs} {
283 global vposids vnegids
285 if {$revs eq {}} {
286 set revs HEAD
288 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
289 # we get stdout followed by stderr in $err
290 # for an unknown rev, git rev-parse echoes it and then errors out
291 set errlines [split $err "\n"]
292 set badrev {}
293 for {set l 0} {$l < [llength $errlines]} {incr l} {
294 set line [lindex $errlines $l]
295 if {!([string length $line] == 40 && [string is xdigit $line])} {
296 if {[string match "fatal:*" $line]} {
297 if {[string match "fatal: ambiguous argument*" $line]
298 && $badrev ne {}} {
299 if {[llength $badrev] == 1} {
300 set err "unknown revision $badrev"
301 } else {
302 set err "unknown revisions: [join $badrev ", "]"
304 } else {
305 set err [join [lrange $errlines $l end] "\n"]
307 break
309 lappend badrev $line
312 error_popup "[mc "Error parsing revisions:"] $err"
313 return {}
315 set ret {}
316 set pos {}
317 set neg {}
318 set sdm 0
319 foreach id [split $ids "\n"] {
320 if {$id eq "--gitk-symmetric-diff-marker"} {
321 set sdm 4
322 } elseif {[string match "^*" $id]} {
323 if {$sdm != 1} {
324 lappend ret $id
325 if {$sdm == 3} {
326 set sdm 0
329 lappend neg [string range $id 1 end]
330 } else {
331 if {$sdm != 2} {
332 lappend ret $id
333 } else {
334 lset ret end $id...[lindex $ret end]
336 lappend pos $id
338 incr sdm -1
340 set vposids($view) $pos
341 set vnegids($view) $neg
342 return $ret
345 # Start off a git log process and arrange to read its output
346 proc start_rev_list {view} {
347 global startmsecs commitidx viewcomplete curview
348 global tclencoding
349 global viewargs viewargscmd viewfiles vfilelimit
350 global showlocalchanges
351 global viewactive viewinstances vmergeonly
352 global mainheadid viewmainheadid viewmainheadid_orig
353 global vcanopt vflags vrevs vorigargs
354 global show_notes
356 set startmsecs [clock clicks -milliseconds]
357 set commitidx($view) 0
358 # these are set this way for the error exits
359 set viewcomplete($view) 1
360 set viewactive($view) 0
361 varcinit $view
363 set args $viewargs($view)
364 if {$viewargscmd($view) ne {}} {
365 if {[catch {
366 set str [exec sh -c $viewargscmd($view)]
367 } err]} {
368 error_popup "[mc "Error executing --argscmd command:"] $err"
369 return 0
371 set args [concat $args [split $str "\n"]]
373 set vcanopt($view) [parseviewargs $view $args]
375 set files $viewfiles($view)
376 if {$vmergeonly($view)} {
377 set files [unmerged_files $files]
378 if {$files eq {}} {
379 global nr_unmerged
380 if {$nr_unmerged == 0} {
381 error_popup [mc "No files selected: --merge specified but\
382 no files are unmerged."]
383 } else {
384 error_popup [mc "No files selected: --merge specified but\
385 no unmerged files are within file limit."]
387 return 0
390 set vfilelimit($view) $files
392 if {$vcanopt($view)} {
393 set revs [parseviewrevs $view $vrevs($view)]
394 if {$revs eq {}} {
395 return 0
397 set args [concat $vflags($view) $revs]
398 } else {
399 set args $vorigargs($view)
402 if {[catch {
403 set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
404 --parents --boundary $args "--" $files] r]
405 } err]} {
406 error_popup "[mc "Error executing git log:"] $err"
407 return 0
409 set i [reg_instance $fd]
410 set viewinstances($view) [list $i]
411 set viewmainheadid($view) $mainheadid
412 set viewmainheadid_orig($view) $mainheadid
413 if {$files ne {} && $mainheadid ne {}} {
414 get_viewmainhead $view
416 if {$showlocalchanges && $viewmainheadid($view) ne {}} {
417 interestedin $viewmainheadid($view) dodiffindex
419 fconfigure $fd -blocking 0 -translation lf -eofchar {}
420 if {$tclencoding != {}} {
421 fconfigure $fd -encoding $tclencoding
423 filerun $fd [list getcommitlines $fd $i $view 0]
424 nowbusy $view [mc "Reading"]
425 set viewcomplete($view) 0
426 set viewactive($view) 1
427 return 1
430 proc stop_instance {inst} {
431 global commfd leftover
433 set fd $commfd($inst)
434 catch {
435 set pid [pid $fd]
437 if {$::tcl_platform(platform) eq {windows}} {
438 exec kill -f $pid
439 } else {
440 exec kill $pid
443 catch {close $fd}
444 nukefile $fd
445 unset commfd($inst)
446 unset leftover($inst)
449 proc stop_backends {} {
450 global commfd
452 foreach inst [array names commfd] {
453 stop_instance $inst
457 proc stop_rev_list {view} {
458 global viewinstances
460 foreach inst $viewinstances($view) {
461 stop_instance $inst
463 set viewinstances($view) {}
466 proc reset_pending_select {selid} {
467 global pending_select mainheadid selectheadid
469 if {$selid ne {}} {
470 set pending_select $selid
471 } elseif {$selectheadid ne {}} {
472 set pending_select $selectheadid
473 } else {
474 set pending_select $mainheadid
478 proc getcommits {selid} {
479 global canv curview need_redisplay viewactive
481 initlayout
482 if {[start_rev_list $curview]} {
483 reset_pending_select $selid
484 show_status [mc "Reading commits..."]
485 set need_redisplay 1
486 } else {
487 show_status [mc "No commits selected"]
491 proc updatecommits {} {
492 global curview vcanopt vorigargs vfilelimit viewinstances
493 global viewactive viewcomplete tclencoding
494 global startmsecs showneartags showlocalchanges
495 global mainheadid viewmainheadid viewmainheadid_orig pending_select
496 global hasworktree
497 global varcid vposids vnegids vflags vrevs
498 global show_notes
500 set hasworktree [hasworktree]
501 rereadrefs
502 set view $curview
503 if {$mainheadid ne $viewmainheadid_orig($view)} {
504 if {$showlocalchanges} {
505 dohidelocalchanges
507 set viewmainheadid($view) $mainheadid
508 set viewmainheadid_orig($view) $mainheadid
509 if {$vfilelimit($view) ne {}} {
510 get_viewmainhead $view
513 if {$showlocalchanges} {
514 doshowlocalchanges
516 if {$vcanopt($view)} {
517 set oldpos $vposids($view)
518 set oldneg $vnegids($view)
519 set revs [parseviewrevs $view $vrevs($view)]
520 if {$revs eq {}} {
521 return
523 # note: getting the delta when negative refs change is hard,
524 # and could require multiple git log invocations, so in that
525 # case we ask git log for all the commits (not just the delta)
526 if {$oldneg eq $vnegids($view)} {
527 set newrevs {}
528 set npos 0
529 # take out positive refs that we asked for before or
530 # that we have already seen
531 foreach rev $revs {
532 if {[string length $rev] == 40} {
533 if {[lsearch -exact $oldpos $rev] < 0
534 && ![info exists varcid($view,$rev)]} {
535 lappend newrevs $rev
536 incr npos
538 } else {
539 lappend $newrevs $rev
542 if {$npos == 0} return
543 set revs $newrevs
544 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
546 set args [concat $vflags($view) $revs --not $oldpos]
547 } else {
548 set args $vorigargs($view)
550 if {[catch {
551 set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
552 --parents --boundary $args "--" $vfilelimit($view)] r]
553 } err]} {
554 error_popup "[mc "Error executing git log:"] $err"
555 return
557 if {$viewactive($view) == 0} {
558 set startmsecs [clock clicks -milliseconds]
560 set i [reg_instance $fd]
561 lappend viewinstances($view) $i
562 fconfigure $fd -blocking 0 -translation lf -eofchar {}
563 if {$tclencoding != {}} {
564 fconfigure $fd -encoding $tclencoding
566 filerun $fd [list getcommitlines $fd $i $view 1]
567 incr viewactive($view)
568 set viewcomplete($view) 0
569 reset_pending_select {}
570 nowbusy $view [mc "Reading"]
571 if {$showneartags} {
572 getallcommits
576 proc reloadcommits {} {
577 global curview viewcomplete selectedline currentid thickerline
578 global showneartags treediffs commitinterest cached_commitrow
579 global targetid
581 set selid {}
582 if {$selectedline ne {}} {
583 set selid $currentid
586 if {!$viewcomplete($curview)} {
587 stop_rev_list $curview
589 resetvarcs $curview
590 set selectedline {}
591 catch {unset currentid}
592 catch {unset thickerline}
593 catch {unset treediffs}
594 readrefs
595 changedrefs
596 if {$showneartags} {
597 getallcommits
599 clear_display
600 catch {unset commitinterest}
601 catch {unset cached_commitrow}
602 catch {unset targetid}
603 setcanvscroll
604 getcommits $selid
605 return 0
608 # This makes a string representation of a positive integer which
609 # sorts as a string in numerical order
610 proc strrep {n} {
611 if {$n < 16} {
612 return [format "%x" $n]
613 } elseif {$n < 256} {
614 return [format "x%.2x" $n]
615 } elseif {$n < 65536} {
616 return [format "y%.4x" $n]
618 return [format "z%.8x" $n]
621 # Procedures used in reordering commits from git log (without
622 # --topo-order) into the order for display.
624 proc varcinit {view} {
625 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
626 global vtokmod varcmod vrowmod varcix vlastins
628 set varcstart($view) {{}}
629 set vupptr($view) {0}
630 set vdownptr($view) {0}
631 set vleftptr($view) {0}
632 set vbackptr($view) {0}
633 set varctok($view) {{}}
634 set varcrow($view) {{}}
635 set vtokmod($view) {}
636 set varcmod($view) 0
637 set vrowmod($view) 0
638 set varcix($view) {{}}
639 set vlastins($view) {0}
642 proc resetvarcs {view} {
643 global varcid varccommits parents children vseedcount ordertok
644 global vshortids
646 foreach vid [array names varcid $view,*] {
647 unset varcid($vid)
648 unset children($vid)
649 unset parents($vid)
651 foreach vid [array names vshortids $view,*] {
652 unset vshortids($vid)
654 # some commits might have children but haven't been seen yet
655 foreach vid [array names children $view,*] {
656 unset children($vid)
658 foreach va [array names varccommits $view,*] {
659 unset varccommits($va)
661 foreach vd [array names vseedcount $view,*] {
662 unset vseedcount($vd)
664 catch {unset ordertok}
667 # returns a list of the commits with no children
668 proc seeds {v} {
669 global vdownptr vleftptr varcstart
671 set ret {}
672 set a [lindex $vdownptr($v) 0]
673 while {$a != 0} {
674 lappend ret [lindex $varcstart($v) $a]
675 set a [lindex $vleftptr($v) $a]
677 return $ret
680 proc newvarc {view id} {
681 global varcid varctok parents children vdatemode
682 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
683 global commitdata commitinfo vseedcount varccommits vlastins
685 set a [llength $varctok($view)]
686 set vid $view,$id
687 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
688 if {![info exists commitinfo($id)]} {
689 parsecommit $id $commitdata($id) 1
691 set cdate [lindex [lindex $commitinfo($id) 4] 0]
692 if {![string is integer -strict $cdate]} {
693 set cdate 0
695 if {![info exists vseedcount($view,$cdate)]} {
696 set vseedcount($view,$cdate) -1
698 set c [incr vseedcount($view,$cdate)]
699 set cdate [expr {$cdate ^ 0xffffffff}]
700 set tok "s[strrep $cdate][strrep $c]"
701 } else {
702 set tok {}
704 set ka 0
705 if {[llength $children($vid)] > 0} {
706 set kid [lindex $children($vid) end]
707 set k $varcid($view,$kid)
708 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
709 set ki $kid
710 set ka $k
711 set tok [lindex $varctok($view) $k]
714 if {$ka != 0} {
715 set i [lsearch -exact $parents($view,$ki) $id]
716 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
717 append tok [strrep $j]
719 set c [lindex $vlastins($view) $ka]
720 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
721 set c $ka
722 set b [lindex $vdownptr($view) $ka]
723 } else {
724 set b [lindex $vleftptr($view) $c]
726 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
727 set c $b
728 set b [lindex $vleftptr($view) $c]
730 if {$c == $ka} {
731 lset vdownptr($view) $ka $a
732 lappend vbackptr($view) 0
733 } else {
734 lset vleftptr($view) $c $a
735 lappend vbackptr($view) $c
737 lset vlastins($view) $ka $a
738 lappend vupptr($view) $ka
739 lappend vleftptr($view) $b
740 if {$b != 0} {
741 lset vbackptr($view) $b $a
743 lappend varctok($view) $tok
744 lappend varcstart($view) $id
745 lappend vdownptr($view) 0
746 lappend varcrow($view) {}
747 lappend varcix($view) {}
748 set varccommits($view,$a) {}
749 lappend vlastins($view) 0
750 return $a
753 proc splitvarc {p v} {
754 global varcid varcstart varccommits varctok vtokmod
755 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
757 set oa $varcid($v,$p)
758 set otok [lindex $varctok($v) $oa]
759 set ac $varccommits($v,$oa)
760 set i [lsearch -exact $varccommits($v,$oa) $p]
761 if {$i <= 0} return
762 set na [llength $varctok($v)]
763 # "%" sorts before "0"...
764 set tok "$otok%[strrep $i]"
765 lappend varctok($v) $tok
766 lappend varcrow($v) {}
767 lappend varcix($v) {}
768 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
769 set varccommits($v,$na) [lrange $ac $i end]
770 lappend varcstart($v) $p
771 foreach id $varccommits($v,$na) {
772 set varcid($v,$id) $na
774 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
775 lappend vlastins($v) [lindex $vlastins($v) $oa]
776 lset vdownptr($v) $oa $na
777 lset vlastins($v) $oa 0
778 lappend vupptr($v) $oa
779 lappend vleftptr($v) 0
780 lappend vbackptr($v) 0
781 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
782 lset vupptr($v) $b $na
784 if {[string compare $otok $vtokmod($v)] <= 0} {
785 modify_arc $v $oa
789 proc renumbervarc {a v} {
790 global parents children varctok varcstart varccommits
791 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
793 set t1 [clock clicks -milliseconds]
794 set todo {}
795 set isrelated($a) 1
796 set kidchanged($a) 1
797 set ntot 0
798 while {$a != 0} {
799 if {[info exists isrelated($a)]} {
800 lappend todo $a
801 set id [lindex $varccommits($v,$a) end]
802 foreach p $parents($v,$id) {
803 if {[info exists varcid($v,$p)]} {
804 set isrelated($varcid($v,$p)) 1
808 incr ntot
809 set b [lindex $vdownptr($v) $a]
810 if {$b == 0} {
811 while {$a != 0} {
812 set b [lindex $vleftptr($v) $a]
813 if {$b != 0} break
814 set a [lindex $vupptr($v) $a]
817 set a $b
819 foreach a $todo {
820 if {![info exists kidchanged($a)]} continue
821 set id [lindex $varcstart($v) $a]
822 if {[llength $children($v,$id)] > 1} {
823 set children($v,$id) [lsort -command [list vtokcmp $v] \
824 $children($v,$id)]
826 set oldtok [lindex $varctok($v) $a]
827 if {!$vdatemode($v)} {
828 set tok {}
829 } else {
830 set tok $oldtok
832 set ka 0
833 set kid [last_real_child $v,$id]
834 if {$kid ne {}} {
835 set k $varcid($v,$kid)
836 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
837 set ki $kid
838 set ka $k
839 set tok [lindex $varctok($v) $k]
842 if {$ka != 0} {
843 set i [lsearch -exact $parents($v,$ki) $id]
844 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
845 append tok [strrep $j]
847 if {$tok eq $oldtok} {
848 continue
850 set id [lindex $varccommits($v,$a) end]
851 foreach p $parents($v,$id) {
852 if {[info exists varcid($v,$p)]} {
853 set kidchanged($varcid($v,$p)) 1
854 } else {
855 set sortkids($p) 1
858 lset varctok($v) $a $tok
859 set b [lindex $vupptr($v) $a]
860 if {$b != $ka} {
861 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
862 modify_arc $v $ka
864 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
865 modify_arc $v $b
867 set c [lindex $vbackptr($v) $a]
868 set d [lindex $vleftptr($v) $a]
869 if {$c == 0} {
870 lset vdownptr($v) $b $d
871 } else {
872 lset vleftptr($v) $c $d
874 if {$d != 0} {
875 lset vbackptr($v) $d $c
877 if {[lindex $vlastins($v) $b] == $a} {
878 lset vlastins($v) $b $c
880 lset vupptr($v) $a $ka
881 set c [lindex $vlastins($v) $ka]
882 if {$c == 0 || \
883 [string compare $tok [lindex $varctok($v) $c]] < 0} {
884 set c $ka
885 set b [lindex $vdownptr($v) $ka]
886 } else {
887 set b [lindex $vleftptr($v) $c]
889 while {$b != 0 && \
890 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
891 set c $b
892 set b [lindex $vleftptr($v) $c]
894 if {$c == $ka} {
895 lset vdownptr($v) $ka $a
896 lset vbackptr($v) $a 0
897 } else {
898 lset vleftptr($v) $c $a
899 lset vbackptr($v) $a $c
901 lset vleftptr($v) $a $b
902 if {$b != 0} {
903 lset vbackptr($v) $b $a
905 lset vlastins($v) $ka $a
908 foreach id [array names sortkids] {
909 if {[llength $children($v,$id)] > 1} {
910 set children($v,$id) [lsort -command [list vtokcmp $v] \
911 $children($v,$id)]
914 set t2 [clock clicks -milliseconds]
915 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
918 # Fix up the graph after we have found out that in view $v,
919 # $p (a commit that we have already seen) is actually the parent
920 # of the last commit in arc $a.
921 proc fix_reversal {p a v} {
922 global varcid varcstart varctok vupptr
924 set pa $varcid($v,$p)
925 if {$p ne [lindex $varcstart($v) $pa]} {
926 splitvarc $p $v
927 set pa $varcid($v,$p)
929 # seeds always need to be renumbered
930 if {[lindex $vupptr($v) $pa] == 0 ||
931 [string compare [lindex $varctok($v) $a] \
932 [lindex $varctok($v) $pa]] > 0} {
933 renumbervarc $pa $v
937 proc insertrow {id p v} {
938 global cmitlisted children parents varcid varctok vtokmod
939 global varccommits ordertok commitidx numcommits curview
940 global targetid targetrow vshortids
942 readcommit $id
943 set vid $v,$id
944 set cmitlisted($vid) 1
945 set children($vid) {}
946 set parents($vid) [list $p]
947 set a [newvarc $v $id]
948 set varcid($vid) $a
949 lappend vshortids($v,[string range $id 0 3]) $id
950 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
951 modify_arc $v $a
953 lappend varccommits($v,$a) $id
954 set vp $v,$p
955 if {[llength [lappend children($vp) $id]] > 1} {
956 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
957 catch {unset ordertok}
959 fix_reversal $p $a $v
960 incr commitidx($v)
961 if {$v == $curview} {
962 set numcommits $commitidx($v)
963 setcanvscroll
964 if {[info exists targetid]} {
965 if {![comes_before $targetid $p]} {
966 incr targetrow
972 proc insertfakerow {id p} {
973 global varcid varccommits parents children cmitlisted
974 global commitidx varctok vtokmod targetid targetrow curview numcommits
976 set v $curview
977 set a $varcid($v,$p)
978 set i [lsearch -exact $varccommits($v,$a) $p]
979 if {$i < 0} {
980 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
981 return
983 set children($v,$id) {}
984 set parents($v,$id) [list $p]
985 set varcid($v,$id) $a
986 lappend children($v,$p) $id
987 set cmitlisted($v,$id) 1
988 set numcommits [incr commitidx($v)]
989 # note we deliberately don't update varcstart($v) even if $i == 0
990 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
991 modify_arc $v $a $i
992 if {[info exists targetid]} {
993 if {![comes_before $targetid $p]} {
994 incr targetrow
997 setcanvscroll
998 drawvisible
1001 proc removefakerow {id} {
1002 global varcid varccommits parents children commitidx
1003 global varctok vtokmod cmitlisted currentid selectedline
1004 global targetid curview numcommits
1006 set v $curview
1007 if {[llength $parents($v,$id)] != 1} {
1008 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
1009 return
1011 set p [lindex $parents($v,$id) 0]
1012 set a $varcid($v,$id)
1013 set i [lsearch -exact $varccommits($v,$a) $id]
1014 if {$i < 0} {
1015 puts "oops: removefakerow can't find [shortids $id] on arc $a"
1016 return
1018 unset varcid($v,$id)
1019 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
1020 unset parents($v,$id)
1021 unset children($v,$id)
1022 unset cmitlisted($v,$id)
1023 set numcommits [incr commitidx($v) -1]
1024 set j [lsearch -exact $children($v,$p) $id]
1025 if {$j >= 0} {
1026 set children($v,$p) [lreplace $children($v,$p) $j $j]
1028 modify_arc $v $a $i
1029 if {[info exist currentid] && $id eq $currentid} {
1030 unset currentid
1031 set selectedline {}
1033 if {[info exists targetid] && $targetid eq $id} {
1034 set targetid $p
1036 setcanvscroll
1037 drawvisible
1040 proc real_children {vp} {
1041 global children nullid nullid2
1043 set kids {}
1044 foreach id $children($vp) {
1045 if {$id ne $nullid && $id ne $nullid2} {
1046 lappend kids $id
1049 return $kids
1052 proc first_real_child {vp} {
1053 global children nullid nullid2
1055 foreach id $children($vp) {
1056 if {$id ne $nullid && $id ne $nullid2} {
1057 return $id
1060 return {}
1063 proc last_real_child {vp} {
1064 global children nullid nullid2
1066 set kids $children($vp)
1067 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
1068 set id [lindex $kids $i]
1069 if {$id ne $nullid && $id ne $nullid2} {
1070 return $id
1073 return {}
1076 proc vtokcmp {v a b} {
1077 global varctok varcid
1079 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1080 [lindex $varctok($v) $varcid($v,$b)]]
1083 # This assumes that if lim is not given, the caller has checked that
1084 # arc a's token is less than $vtokmod($v)
1085 proc modify_arc {v a {lim {}}} {
1086 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1088 if {$lim ne {}} {
1089 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1090 if {$c > 0} return
1091 if {$c == 0} {
1092 set r [lindex $varcrow($v) $a]
1093 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1096 set vtokmod($v) [lindex $varctok($v) $a]
1097 set varcmod($v) $a
1098 if {$v == $curview} {
1099 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1100 set a [lindex $vupptr($v) $a]
1101 set lim {}
1103 set r 0
1104 if {$a != 0} {
1105 if {$lim eq {}} {
1106 set lim [llength $varccommits($v,$a)]
1108 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1110 set vrowmod($v) $r
1111 undolayout $r
1115 proc update_arcrows {v} {
1116 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1117 global varcid vrownum varcorder varcix varccommits
1118 global vupptr vdownptr vleftptr varctok
1119 global displayorder parentlist curview cached_commitrow
1121 if {$vrowmod($v) == $commitidx($v)} return
1122 if {$v == $curview} {
1123 if {[llength $displayorder] > $vrowmod($v)} {
1124 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1125 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1127 catch {unset cached_commitrow}
1129 set narctot [expr {[llength $varctok($v)] - 1}]
1130 set a $varcmod($v)
1131 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1132 # go up the tree until we find something that has a row number,
1133 # or we get to a seed
1134 set a [lindex $vupptr($v) $a]
1136 if {$a == 0} {
1137 set a [lindex $vdownptr($v) 0]
1138 if {$a == 0} return
1139 set vrownum($v) {0}
1140 set varcorder($v) [list $a]
1141 lset varcix($v) $a 0
1142 lset varcrow($v) $a 0
1143 set arcn 0
1144 set row 0
1145 } else {
1146 set arcn [lindex $varcix($v) $a]
1147 if {[llength $vrownum($v)] > $arcn + 1} {
1148 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1149 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1151 set row [lindex $varcrow($v) $a]
1153 while {1} {
1154 set p $a
1155 incr row [llength $varccommits($v,$a)]
1156 # go down if possible
1157 set b [lindex $vdownptr($v) $a]
1158 if {$b == 0} {
1159 # if not, go left, or go up until we can go left
1160 while {$a != 0} {
1161 set b [lindex $vleftptr($v) $a]
1162 if {$b != 0} break
1163 set a [lindex $vupptr($v) $a]
1165 if {$a == 0} break
1167 set a $b
1168 incr arcn
1169 lappend vrownum($v) $row
1170 lappend varcorder($v) $a
1171 lset varcix($v) $a $arcn
1172 lset varcrow($v) $a $row
1174 set vtokmod($v) [lindex $varctok($v) $p]
1175 set varcmod($v) $p
1176 set vrowmod($v) $row
1177 if {[info exists currentid]} {
1178 set selectedline [rowofcommit $currentid]
1182 # Test whether view $v contains commit $id
1183 proc commitinview {id v} {
1184 global varcid
1186 return [info exists varcid($v,$id)]
1189 # Return the row number for commit $id in the current view
1190 proc rowofcommit {id} {
1191 global varcid varccommits varcrow curview cached_commitrow
1192 global varctok vtokmod
1194 set v $curview
1195 if {![info exists varcid($v,$id)]} {
1196 puts "oops rowofcommit no arc for [shortids $id]"
1197 return {}
1199 set a $varcid($v,$id)
1200 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1201 update_arcrows $v
1203 if {[info exists cached_commitrow($id)]} {
1204 return $cached_commitrow($id)
1206 set i [lsearch -exact $varccommits($v,$a) $id]
1207 if {$i < 0} {
1208 puts "oops didn't find commit [shortids $id] in arc $a"
1209 return {}
1211 incr i [lindex $varcrow($v) $a]
1212 set cached_commitrow($id) $i
1213 return $i
1216 # Returns 1 if a is on an earlier row than b, otherwise 0
1217 proc comes_before {a b} {
1218 global varcid varctok curview
1220 set v $curview
1221 if {$a eq $b || ![info exists varcid($v,$a)] || \
1222 ![info exists varcid($v,$b)]} {
1223 return 0
1225 if {$varcid($v,$a) != $varcid($v,$b)} {
1226 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1227 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1229 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1232 proc bsearch {l elt} {
1233 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1234 return 0
1236 set lo 0
1237 set hi [llength $l]
1238 while {$hi - $lo > 1} {
1239 set mid [expr {int(($lo + $hi) / 2)}]
1240 set t [lindex $l $mid]
1241 if {$elt < $t} {
1242 set hi $mid
1243 } elseif {$elt > $t} {
1244 set lo $mid
1245 } else {
1246 return $mid
1249 return $lo
1252 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1253 proc make_disporder {start end} {
1254 global vrownum curview commitidx displayorder parentlist
1255 global varccommits varcorder parents vrowmod varcrow
1256 global d_valid_start d_valid_end
1258 if {$end > $vrowmod($curview)} {
1259 update_arcrows $curview
1261 set ai [bsearch $vrownum($curview) $start]
1262 set start [lindex $vrownum($curview) $ai]
1263 set narc [llength $vrownum($curview)]
1264 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1265 set a [lindex $varcorder($curview) $ai]
1266 set l [llength $displayorder]
1267 set al [llength $varccommits($curview,$a)]
1268 if {$l < $r + $al} {
1269 if {$l < $r} {
1270 set pad [ntimes [expr {$r - $l}] {}]
1271 set displayorder [concat $displayorder $pad]
1272 set parentlist [concat $parentlist $pad]
1273 } elseif {$l > $r} {
1274 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1275 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1277 foreach id $varccommits($curview,$a) {
1278 lappend displayorder $id
1279 lappend parentlist $parents($curview,$id)
1281 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1282 set i $r
1283 foreach id $varccommits($curview,$a) {
1284 lset displayorder $i $id
1285 lset parentlist $i $parents($curview,$id)
1286 incr i
1289 incr r $al
1293 proc commitonrow {row} {
1294 global displayorder
1296 set id [lindex $displayorder $row]
1297 if {$id eq {}} {
1298 make_disporder $row [expr {$row + 1}]
1299 set id [lindex $displayorder $row]
1301 return $id
1304 proc closevarcs {v} {
1305 global varctok varccommits varcid parents children
1306 global cmitlisted commitidx vtokmod
1308 set missing_parents 0
1309 set scripts {}
1310 set narcs [llength $varctok($v)]
1311 for {set a 1} {$a < $narcs} {incr a} {
1312 set id [lindex $varccommits($v,$a) end]
1313 foreach p $parents($v,$id) {
1314 if {[info exists varcid($v,$p)]} continue
1315 # add p as a new commit
1316 incr missing_parents
1317 set cmitlisted($v,$p) 0
1318 set parents($v,$p) {}
1319 if {[llength $children($v,$p)] == 1 &&
1320 [llength $parents($v,$id)] == 1} {
1321 set b $a
1322 } else {
1323 set b [newvarc $v $p]
1325 set varcid($v,$p) $b
1326 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1327 modify_arc $v $b
1329 lappend varccommits($v,$b) $p
1330 incr commitidx($v)
1331 set scripts [check_interest $p $scripts]
1334 if {$missing_parents > 0} {
1335 foreach s $scripts {
1336 eval $s
1341 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1342 # Assumes we already have an arc for $rwid.
1343 proc rewrite_commit {v id rwid} {
1344 global children parents varcid varctok vtokmod varccommits
1346 foreach ch $children($v,$id) {
1347 # make $rwid be $ch's parent in place of $id
1348 set i [lsearch -exact $parents($v,$ch) $id]
1349 if {$i < 0} {
1350 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1352 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1353 # add $ch to $rwid's children and sort the list if necessary
1354 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1355 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1356 $children($v,$rwid)]
1358 # fix the graph after joining $id to $rwid
1359 set a $varcid($v,$ch)
1360 fix_reversal $rwid $a $v
1361 # parentlist is wrong for the last element of arc $a
1362 # even if displayorder is right, hence the 3rd arg here
1363 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1367 # Mechanism for registering a command to be executed when we come
1368 # across a particular commit. To handle the case when only the
1369 # prefix of the commit is known, the commitinterest array is now
1370 # indexed by the first 4 characters of the ID. Each element is a
1371 # list of id, cmd pairs.
1372 proc interestedin {id cmd} {
1373 global commitinterest
1375 lappend commitinterest([string range $id 0 3]) $id $cmd
1378 proc check_interest {id scripts} {
1379 global commitinterest
1381 set prefix [string range $id 0 3]
1382 if {[info exists commitinterest($prefix)]} {
1383 set newlist {}
1384 foreach {i script} $commitinterest($prefix) {
1385 if {[string match "$i*" $id]} {
1386 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1387 } else {
1388 lappend newlist $i $script
1391 if {$newlist ne {}} {
1392 set commitinterest($prefix) $newlist
1393 } else {
1394 unset commitinterest($prefix)
1397 return $scripts
1400 proc getcommitlines {fd inst view updating} {
1401 global cmitlisted leftover
1402 global commitidx commitdata vdatemode
1403 global parents children curview hlview
1404 global idpending ordertok
1405 global varccommits varcid varctok vtokmod vfilelimit vshortids
1407 set stuff [read $fd 500000]
1408 # git log doesn't terminate the last commit with a null...
1409 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1410 set stuff "\0"
1412 if {$stuff == {}} {
1413 if {![eof $fd]} {
1414 return 1
1416 global commfd viewcomplete viewactive viewname
1417 global viewinstances
1418 unset commfd($inst)
1419 set i [lsearch -exact $viewinstances($view) $inst]
1420 if {$i >= 0} {
1421 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1423 # set it blocking so we wait for the process to terminate
1424 fconfigure $fd -blocking 1
1425 if {[catch {close $fd} err]} {
1426 set fv {}
1427 if {$view != $curview} {
1428 set fv " for the \"$viewname($view)\" view"
1430 if {[string range $err 0 4] == "usage"} {
1431 set err "Gitk: error reading commits$fv:\
1432 bad arguments to git log."
1433 if {$viewname($view) eq "Command line"} {
1434 append err \
1435 " (Note: arguments to gitk are passed to git log\
1436 to allow selection of commits to be displayed.)"
1438 } else {
1439 set err "Error reading commits$fv: $err"
1441 error_popup $err
1443 if {[incr viewactive($view) -1] <= 0} {
1444 set viewcomplete($view) 1
1445 # Check if we have seen any ids listed as parents that haven't
1446 # appeared in the list
1447 closevarcs $view
1448 notbusy $view
1450 if {$view == $curview} {
1451 run chewcommits
1453 return 0
1455 set start 0
1456 set gotsome 0
1457 set scripts {}
1458 while 1 {
1459 set i [string first "\0" $stuff $start]
1460 if {$i < 0} {
1461 append leftover($inst) [string range $stuff $start end]
1462 break
1464 if {$start == 0} {
1465 set cmit $leftover($inst)
1466 append cmit [string range $stuff 0 [expr {$i - 1}]]
1467 set leftover($inst) {}
1468 } else {
1469 set cmit [string range $stuff $start [expr {$i - 1}]]
1471 set start [expr {$i + 1}]
1472 set j [string first "\n" $cmit]
1473 set ok 0
1474 set listed 1
1475 if {$j >= 0 && [string match "commit *" $cmit]} {
1476 set ids [string range $cmit 7 [expr {$j - 1}]]
1477 if {[string match {[-^<>]*} $ids]} {
1478 switch -- [string index $ids 0] {
1479 "-" {set listed 0}
1480 "^" {set listed 2}
1481 "<" {set listed 3}
1482 ">" {set listed 4}
1484 set ids [string range $ids 1 end]
1486 set ok 1
1487 foreach id $ids {
1488 if {[string length $id] != 40} {
1489 set ok 0
1490 break
1494 if {!$ok} {
1495 set shortcmit $cmit
1496 if {[string length $shortcmit] > 80} {
1497 set shortcmit "[string range $shortcmit 0 80]..."
1499 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1500 exit 1
1502 set id [lindex $ids 0]
1503 set vid $view,$id
1505 lappend vshortids($view,[string range $id 0 3]) $id
1507 if {!$listed && $updating && ![info exists varcid($vid)] &&
1508 $vfilelimit($view) ne {}} {
1509 # git log doesn't rewrite parents for unlisted commits
1510 # when doing path limiting, so work around that here
1511 # by working out the rewritten parent with git rev-list
1512 # and if we already know about it, using the rewritten
1513 # parent as a substitute parent for $id's children.
1514 if {![catch {
1515 set rwid [exec git rev-list --first-parent --max-count=1 \
1516 $id -- $vfilelimit($view)]
1517 }]} {
1518 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1519 # use $rwid in place of $id
1520 rewrite_commit $view $id $rwid
1521 continue
1526 set a 0
1527 if {[info exists varcid($vid)]} {
1528 if {$cmitlisted($vid) || !$listed} continue
1529 set a $varcid($vid)
1531 if {$listed} {
1532 set olds [lrange $ids 1 end]
1533 } else {
1534 set olds {}
1536 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1537 set cmitlisted($vid) $listed
1538 set parents($vid) $olds
1539 if {![info exists children($vid)]} {
1540 set children($vid) {}
1541 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1542 set k [lindex $children($vid) 0]
1543 if {[llength $parents($view,$k)] == 1 &&
1544 (!$vdatemode($view) ||
1545 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1546 set a $varcid($view,$k)
1549 if {$a == 0} {
1550 # new arc
1551 set a [newvarc $view $id]
1553 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1554 modify_arc $view $a
1556 if {![info exists varcid($vid)]} {
1557 set varcid($vid) $a
1558 lappend varccommits($view,$a) $id
1559 incr commitidx($view)
1562 set i 0
1563 foreach p $olds {
1564 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1565 set vp $view,$p
1566 if {[llength [lappend children($vp) $id]] > 1 &&
1567 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1568 set children($vp) [lsort -command [list vtokcmp $view] \
1569 $children($vp)]
1570 catch {unset ordertok}
1572 if {[info exists varcid($view,$p)]} {
1573 fix_reversal $p $a $view
1576 incr i
1579 set scripts [check_interest $id $scripts]
1580 set gotsome 1
1582 if {$gotsome} {
1583 global numcommits hlview
1585 if {$view == $curview} {
1586 set numcommits $commitidx($view)
1587 run chewcommits
1589 if {[info exists hlview] && $view == $hlview} {
1590 # we never actually get here...
1591 run vhighlightmore
1593 foreach s $scripts {
1594 eval $s
1597 return 2
1600 proc chewcommits {} {
1601 global curview hlview viewcomplete
1602 global pending_select
1604 layoutmore
1605 if {$viewcomplete($curview)} {
1606 global commitidx varctok
1607 global numcommits startmsecs
1609 if {[info exists pending_select]} {
1610 update
1611 reset_pending_select {}
1613 if {[commitinview $pending_select $curview]} {
1614 selectline [rowofcommit $pending_select] 1
1615 } else {
1616 set row [first_real_row]
1617 selectline $row 1
1620 if {$commitidx($curview) > 0} {
1621 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1622 #puts "overall $ms ms for $numcommits commits"
1623 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1624 } else {
1625 show_status [mc "No commits selected"]
1627 notbusy layout
1629 return 0
1632 proc do_readcommit {id} {
1633 global tclencoding
1635 # Invoke git-log to handle automatic encoding conversion
1636 set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1637 # Read the results using i18n.logoutputencoding
1638 fconfigure $fd -translation lf -eofchar {}
1639 if {$tclencoding != {}} {
1640 fconfigure $fd -encoding $tclencoding
1642 set contents [read $fd]
1643 close $fd
1644 # Remove the heading line
1645 regsub {^commit [0-9a-f]+\n} $contents {} contents
1647 return $contents
1650 proc readcommit {id} {
1651 if {[catch {set contents [do_readcommit $id]}]} return
1652 parsecommit $id $contents 1
1655 proc parsecommit {id contents listed} {
1656 global commitinfo
1658 set inhdr 1
1659 set comment {}
1660 set headline {}
1661 set auname {}
1662 set audate {}
1663 set comname {}
1664 set comdate {}
1665 set hdrend [string first "\n\n" $contents]
1666 if {$hdrend < 0} {
1667 # should never happen...
1668 set hdrend [string length $contents]
1670 set header [string range $contents 0 [expr {$hdrend - 1}]]
1671 set comment [string range $contents [expr {$hdrend + 2}] end]
1672 foreach line [split $header "\n"] {
1673 set line [split $line " "]
1674 set tag [lindex $line 0]
1675 if {$tag == "author"} {
1676 set audate [lrange $line end-1 end]
1677 set auname [join [lrange $line 1 end-2] " "]
1678 } elseif {$tag == "committer"} {
1679 set comdate [lrange $line end-1 end]
1680 set comname [join [lrange $line 1 end-2] " "]
1683 set headline {}
1684 # take the first non-blank line of the comment as the headline
1685 set headline [string trimleft $comment]
1686 set i [string first "\n" $headline]
1687 if {$i >= 0} {
1688 set headline [string range $headline 0 $i]
1690 set headline [string trimright $headline]
1691 set i [string first "\r" $headline]
1692 if {$i >= 0} {
1693 set headline [string trimright [string range $headline 0 $i]]
1695 if {!$listed} {
1696 # git log indents the comment by 4 spaces;
1697 # if we got this via git cat-file, add the indentation
1698 set newcomment {}
1699 foreach line [split $comment "\n"] {
1700 append newcomment " "
1701 append newcomment $line
1702 append newcomment "\n"
1704 set comment $newcomment
1706 set hasnote [string first "\nNotes:\n" $contents]
1707 set commitinfo($id) [list $headline $auname $audate \
1708 $comname $comdate $comment $hasnote]
1711 proc getcommit {id} {
1712 global commitdata commitinfo
1714 if {[info exists commitdata($id)]} {
1715 parsecommit $id $commitdata($id) 1
1716 } else {
1717 readcommit $id
1718 if {![info exists commitinfo($id)]} {
1719 set commitinfo($id) [list [mc "No commit information available"]]
1722 return 1
1725 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1726 # and are present in the current view.
1727 # This is fairly slow...
1728 proc longid {prefix} {
1729 global varcid curview vshortids
1731 set ids {}
1732 if {[string length $prefix] >= 4} {
1733 set vshortid $curview,[string range $prefix 0 3]
1734 if {[info exists vshortids($vshortid)]} {
1735 foreach id $vshortids($vshortid) {
1736 if {[string match "$prefix*" $id]} {
1737 if {[lsearch -exact $ids $id] < 0} {
1738 lappend ids $id
1739 if {[llength $ids] >= 2} break
1744 } else {
1745 foreach match [array names varcid "$curview,$prefix*"] {
1746 lappend ids [lindex [split $match ","] 1]
1747 if {[llength $ids] >= 2} break
1750 return $ids
1753 proc readrefs {} {
1754 global tagids idtags headids idheads tagobjid
1755 global otherrefids idotherrefs mainhead mainheadid
1756 global selecthead selectheadid
1757 global hideremotes
1759 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1760 catch {unset $v}
1762 set refd [open [list | git show-ref -d] r]
1763 while {[gets $refd line] >= 0} {
1764 if {[string index $line 40] ne " "} continue
1765 set id [string range $line 0 39]
1766 set ref [string range $line 41 end]
1767 if {![string match "refs/*" $ref]} continue
1768 set name [string range $ref 5 end]
1769 if {[string match "remotes/*" $name]} {
1770 if {![string match "*/HEAD" $name] && !$hideremotes} {
1771 set headids($name) $id
1772 lappend idheads($id) $name
1774 } elseif {[string match "heads/*" $name]} {
1775 set name [string range $name 6 end]
1776 set headids($name) $id
1777 lappend idheads($id) $name
1778 } elseif {[string match "tags/*" $name]} {
1779 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1780 # which is what we want since the former is the commit ID
1781 set name [string range $name 5 end]
1782 if {[string match "*^{}" $name]} {
1783 set name [string range $name 0 end-3]
1784 } else {
1785 set tagobjid($name) $id
1787 set tagids($name) $id
1788 lappend idtags($id) $name
1789 } else {
1790 set otherrefids($name) $id
1791 lappend idotherrefs($id) $name
1794 catch {close $refd}
1795 set mainhead {}
1796 set mainheadid {}
1797 catch {
1798 set mainheadid [exec git rev-parse HEAD]
1799 set thehead [exec git symbolic-ref HEAD]
1800 if {[string match "refs/heads/*" $thehead]} {
1801 set mainhead [string range $thehead 11 end]
1804 set selectheadid {}
1805 if {$selecthead ne {}} {
1806 catch {
1807 set selectheadid [exec git rev-parse --verify $selecthead]
1812 # skip over fake commits
1813 proc first_real_row {} {
1814 global nullid nullid2 numcommits
1816 for {set row 0} {$row < $numcommits} {incr row} {
1817 set id [commitonrow $row]
1818 if {$id ne $nullid && $id ne $nullid2} {
1819 break
1822 return $row
1825 # update things for a head moved to a child of its previous location
1826 proc movehead {id name} {
1827 global headids idheads
1829 removehead $headids($name) $name
1830 set headids($name) $id
1831 lappend idheads($id) $name
1834 # update things when a head has been removed
1835 proc removehead {id name} {
1836 global headids idheads
1838 if {$idheads($id) eq $name} {
1839 unset idheads($id)
1840 } else {
1841 set i [lsearch -exact $idheads($id) $name]
1842 if {$i >= 0} {
1843 set idheads($id) [lreplace $idheads($id) $i $i]
1846 unset headids($name)
1849 proc ttk_toplevel {w args} {
1850 global use_ttk
1851 eval [linsert $args 0 ::toplevel $w]
1852 if {$use_ttk} {
1853 place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
1855 return $w
1858 proc make_transient {window origin} {
1859 global have_tk85
1861 # In MacOS Tk 8.4 transient appears to work by setting
1862 # overrideredirect, which is utterly useless, since the
1863 # windows get no border, and are not even kept above
1864 # the parent.
1865 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1867 wm transient $window $origin
1869 # Windows fails to place transient windows normally, so
1870 # schedule a callback to center them on the parent.
1871 if {[tk windowingsystem] eq {win32}} {
1872 after idle [list tk::PlaceWindow $window widget $origin]
1876 proc show_error {w top msg {mc mc}} {
1877 global NS
1878 if {![info exists NS]} {set NS ""}
1879 if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
1880 message $w.m -text $msg -justify center -aspect 400
1881 pack $w.m -side top -fill x -padx 20 -pady 20
1882 ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top"
1883 pack $w.ok -side bottom -fill x
1884 bind $top <Visibility> "grab $top; focus $top"
1885 bind $top <Key-Return> "destroy $top"
1886 bind $top <Key-space> "destroy $top"
1887 bind $top <Key-Escape> "destroy $top"
1888 tkwait window $top
1891 proc error_popup {msg {owner .}} {
1892 if {[tk windowingsystem] eq "win32"} {
1893 tk_messageBox -icon error -type ok -title [wm title .] \
1894 -parent $owner -message $msg
1895 } else {
1896 set w .error
1897 ttk_toplevel $w
1898 make_transient $w $owner
1899 show_error $w $w $msg
1903 proc confirm_popup {msg {owner .}} {
1904 global confirm_ok NS
1905 set confirm_ok 0
1906 set w .confirm
1907 ttk_toplevel $w
1908 make_transient $w $owner
1909 message $w.m -text $msg -justify center -aspect 400
1910 pack $w.m -side top -fill x -padx 20 -pady 20
1911 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1912 pack $w.ok -side left -fill x
1913 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
1914 pack $w.cancel -side right -fill x
1915 bind $w <Visibility> "grab $w; focus $w"
1916 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1917 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1918 bind $w <Key-Escape> "destroy $w"
1919 tk::PlaceWindow $w widget $owner
1920 tkwait window $w
1921 return $confirm_ok
1924 proc setoptions {} {
1925 if {[tk windowingsystem] ne "win32"} {
1926 option add *Panedwindow.showHandle 1 startupFile
1927 option add *Panedwindow.sashRelief raised startupFile
1928 if {[tk windowingsystem] ne "aqua"} {
1929 option add *Menu.font uifont startupFile
1931 } else {
1932 option add *Menu.TearOff 0 startupFile
1934 option add *Button.font uifont startupFile
1935 option add *Checkbutton.font uifont startupFile
1936 option add *Radiobutton.font uifont startupFile
1937 option add *Menubutton.font uifont startupFile
1938 option add *Label.font uifont startupFile
1939 option add *Message.font uifont startupFile
1940 option add *Entry.font textfont startupFile
1941 option add *Text.font textfont startupFile
1942 option add *Labelframe.font uifont startupFile
1943 option add *Spinbox.font textfont startupFile
1944 option add *Listbox.font mainfont startupFile
1947 # Make a menu and submenus.
1948 # m is the window name for the menu, items is the list of menu items to add.
1949 # Each item is a list {mc label type description options...}
1950 # mc is ignored; it's so we can put mc there to alert xgettext
1951 # label is the string that appears in the menu
1952 # type is cascade, command or radiobutton (should add checkbutton)
1953 # description depends on type; it's the sublist for cascade, the
1954 # command to invoke for command, or {variable value} for radiobutton
1955 proc makemenu {m items} {
1956 menu $m
1957 if {[tk windowingsystem] eq {aqua}} {
1958 set Meta1 Cmd
1959 } else {
1960 set Meta1 Ctrl
1962 foreach i $items {
1963 set name [mc [lindex $i 1]]
1964 set type [lindex $i 2]
1965 set thing [lindex $i 3]
1966 set params [list $type]
1967 if {$name ne {}} {
1968 set u [string first "&" [string map {&& x} $name]]
1969 lappend params -label [string map {&& & & {}} $name]
1970 if {$u >= 0} {
1971 lappend params -underline $u
1974 switch -- $type {
1975 "cascade" {
1976 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1977 lappend params -menu $m.$submenu
1979 "command" {
1980 lappend params -command $thing
1982 "radiobutton" {
1983 lappend params -variable [lindex $thing 0] \
1984 -value [lindex $thing 1]
1987 set tail [lrange $i 4 end]
1988 regsub -all {\yMeta1\y} $tail $Meta1 tail
1989 eval $m add $params $tail
1990 if {$type eq "cascade"} {
1991 makemenu $m.$submenu $thing
1996 # translate string and remove ampersands
1997 proc mca {str} {
1998 return [string map {&& & & {}} [mc $str]]
2001 proc makedroplist {w varname args} {
2002 global use_ttk
2003 if {$use_ttk} {
2004 set width 0
2005 foreach label $args {
2006 set cx [string length $label]
2007 if {$cx > $width} {set width $cx}
2009 set gm [ttk::combobox $w -width $width -state readonly\
2010 -textvariable $varname -values $args]
2011 } else {
2012 set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
2014 return $gm
2017 proc makewindow {} {
2018 global canv canv2 canv3 linespc charspc ctext cflist cscroll
2019 global tabstop
2020 global findtype findtypemenu findloc findstring fstring geometry
2021 global entries sha1entry sha1string sha1but
2022 global diffcontextstring diffcontext
2023 global ignorespace
2024 global maincursor textcursor curtextcursor
2025 global rowctxmenu fakerowmenu mergemax wrapcomment
2026 global highlight_files gdttype
2027 global searchstring sstring
2028 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
2029 global headctxmenu progresscanv progressitem progresscoords statusw
2030 global fprogitem fprogcoord lastprogupdate progupdatepending
2031 global rprogitem rprogcoord rownumsel numcommits
2032 global have_tk85 use_ttk NS
2033 global git_version
2034 global worddiff
2036 # The "mc" arguments here are purely so that xgettext
2037 # sees the following string as needing to be translated
2038 set file {
2039 mc "File" cascade {
2040 {mc "Update" command updatecommits -accelerator F5}
2041 {mc "Reload" command reloadcommits -accelerator Shift-F5}
2042 {mc "Reread references" command rereadrefs}
2043 {mc "List references" command showrefs -accelerator F2}
2044 {xx "" separator}
2045 {mc "Start git gui" command {exec git gui &}}
2046 {xx "" separator}
2047 {mc "Quit" command doquit -accelerator Meta1-Q}
2049 set edit {
2050 mc "Edit" cascade {
2051 {mc "Preferences" command doprefs}
2053 set view {
2054 mc "View" cascade {
2055 {mc "New view..." command {newview 0} -accelerator Shift-F4}
2056 {mc "Edit view..." command editview -state disabled -accelerator F4}
2057 {mc "Delete view" command delview -state disabled}
2058 {xx "" separator}
2059 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
2061 if {[tk windowingsystem] ne "aqua"} {
2062 set help {
2063 mc "Help" cascade {
2064 {mc "About gitk" command about}
2065 {mc "Key bindings" command keys}
2067 set bar [list $file $edit $view $help]
2068 } else {
2069 proc ::tk::mac::ShowPreferences {} {doprefs}
2070 proc ::tk::mac::Quit {} {doquit}
2071 lset file end [lreplace [lindex $file end] end-1 end]
2072 set apple {
2073 xx "Apple" cascade {
2074 {mc "About gitk" command about}
2075 {xx "" separator}
2077 set help {
2078 mc "Help" cascade {
2079 {mc "Key bindings" command keys}
2081 set bar [list $apple $file $view $help]
2083 makemenu .bar $bar
2084 . configure -menu .bar
2086 if {$use_ttk} {
2087 # cover the non-themed toplevel with a themed frame.
2088 place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
2091 # the gui has upper and lower half, parts of a paned window.
2092 ${NS}::panedwindow .ctop -orient vertical
2094 # possibly use assumed geometry
2095 if {![info exists geometry(pwsash0)]} {
2096 set geometry(topheight) [expr {15 * $linespc}]
2097 set geometry(topwidth) [expr {80 * $charspc}]
2098 set geometry(botheight) [expr {15 * $linespc}]
2099 set geometry(botwidth) [expr {50 * $charspc}]
2100 set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
2101 set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
2104 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
2105 ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
2106 ${NS}::frame .tf.histframe
2107 ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
2108 if {!$use_ttk} {
2109 .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
2112 # create three canvases
2113 set cscroll .tf.histframe.csb
2114 set canv .tf.histframe.pwclist.canv
2115 canvas $canv \
2116 -selectbackground $selectbgcolor \
2117 -background $bgcolor -bd 0 \
2118 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
2119 .tf.histframe.pwclist add $canv
2120 set canv2 .tf.histframe.pwclist.canv2
2121 canvas $canv2 \
2122 -selectbackground $selectbgcolor \
2123 -background $bgcolor -bd 0 -yscrollincr $linespc
2124 .tf.histframe.pwclist add $canv2
2125 set canv3 .tf.histframe.pwclist.canv3
2126 canvas $canv3 \
2127 -selectbackground $selectbgcolor \
2128 -background $bgcolor -bd 0 -yscrollincr $linespc
2129 .tf.histframe.pwclist add $canv3
2130 if {$use_ttk} {
2131 bind .tf.histframe.pwclist <Map> {
2132 bind %W <Map> {}
2133 .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
2134 .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
2136 } else {
2137 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
2138 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
2141 # a scroll bar to rule them
2142 ${NS}::scrollbar $cscroll -command {allcanvs yview}
2143 if {!$use_ttk} {$cscroll configure -highlightthickness 0}
2144 pack $cscroll -side right -fill y
2145 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
2146 lappend bglist $canv $canv2 $canv3
2147 pack .tf.histframe.pwclist -fill both -expand 1 -side left
2149 # we have two button bars at bottom of top frame. Bar 1
2150 ${NS}::frame .tf.bar
2151 ${NS}::frame .tf.lbar -height 15
2153 set sha1entry .tf.bar.sha1
2154 set entries $sha1entry
2155 set sha1but .tf.bar.sha1label
2156 button $sha1but -text "[mc "SHA1 ID:"] " -state disabled -relief flat \
2157 -command gotocommit -width 8
2158 $sha1but conf -disabledforeground [$sha1but cget -foreground]
2159 pack .tf.bar.sha1label -side left
2160 ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
2161 trace add variable sha1string write sha1change
2162 pack $sha1entry -side left -pady 2
2164 set bm_left_data {
2165 #define left_width 16
2166 #define left_height 16
2167 static unsigned char left_bits[] = {
2168 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
2169 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
2170 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
2172 set bm_right_data {
2173 #define right_width 16
2174 #define right_height 16
2175 static unsigned char right_bits[] = {
2176 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2177 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2178 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2180 image create bitmap bm-left -data $bm_left_data
2181 image create bitmap bm-left-gray -data $bm_left_data -foreground "#999"
2182 image create bitmap bm-right -data $bm_right_data
2183 image create bitmap bm-right-gray -data $bm_right_data -foreground "#999"
2185 ${NS}::button .tf.bar.leftbut -image [list bm-left disabled bm-left-gray] \
2186 -command goback -state disabled -width 26
2187 pack .tf.bar.leftbut -side left -fill y
2188 ${NS}::button .tf.bar.rightbut -image [list bm-right disabled bm-right-gray] \
2189 -command goforw -state disabled -width 26
2190 pack .tf.bar.rightbut -side left -fill y
2192 ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
2193 set rownumsel {}
2194 ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
2195 -relief sunken -anchor e
2196 ${NS}::label .tf.bar.rowlabel2 -text "/"
2197 ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
2198 -relief sunken -anchor e
2199 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2200 -side left
2201 if {!$use_ttk} {
2202 foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
2204 global selectedline
2205 trace add variable selectedline write selectedline_change
2207 # Status label and progress bar
2208 set statusw .tf.bar.status
2209 ${NS}::label $statusw -width 15 -relief sunken
2210 pack $statusw -side left -padx 5
2211 if {$use_ttk} {
2212 set progresscanv [ttk::progressbar .tf.bar.progress]
2213 } else {
2214 set h [expr {[font metrics uifont -linespace] + 2}]
2215 set progresscanv .tf.bar.progress
2216 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2217 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2218 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2219 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2221 pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
2222 set progresscoords {0 0}
2223 set fprogcoord 0
2224 set rprogcoord 0
2225 bind $progresscanv <Configure> adjustprogress
2226 set lastprogupdate [clock clicks -milliseconds]
2227 set progupdatepending 0
2229 # build up the bottom bar of upper window
2230 ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
2231 ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2232 ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2233 ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
2234 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2235 -side left -fill y
2236 set gdttype [mc "containing:"]
2237 set gm [makedroplist .tf.lbar.gdttype gdttype \
2238 [mc "containing:"] \
2239 [mc "touching paths:"] \
2240 [mc "adding/removing string:"]]
2241 trace add variable gdttype write gdttype_change
2242 pack .tf.lbar.gdttype -side left -fill y
2244 set findstring {}
2245 set fstring .tf.lbar.findstring
2246 lappend entries $fstring
2247 ${NS}::entry $fstring -width 30 -textvariable findstring
2248 trace add variable findstring write find_change
2249 set findtype [mc "Exact"]
2250 set findtypemenu [makedroplist .tf.lbar.findtype \
2251 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2252 trace add variable findtype write findcom_change
2253 set findloc [mc "All fields"]
2254 makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2255 [mc "Comments"] [mc "Author"] [mc "Committer"]
2256 trace add variable findloc write find_change
2257 pack .tf.lbar.findloc -side right
2258 pack .tf.lbar.findtype -side right
2259 pack $fstring -side left -expand 1 -fill x
2261 # Finish putting the upper half of the viewer together
2262 pack .tf.lbar -in .tf -side bottom -fill x
2263 pack .tf.bar -in .tf -side bottom -fill x
2264 pack .tf.histframe -fill both -side top -expand 1
2265 .ctop add .tf
2266 if {!$use_ttk} {
2267 .ctop paneconfigure .tf -height $geometry(topheight)
2268 .ctop paneconfigure .tf -width $geometry(topwidth)
2271 # now build up the bottom
2272 ${NS}::panedwindow .pwbottom -orient horizontal
2274 # lower left, a text box over search bar, scroll bar to the right
2275 # if we know window height, then that will set the lower text height, otherwise
2276 # we set lower text height which will drive window height
2277 if {[info exists geometry(main)]} {
2278 ${NS}::frame .bleft -width $geometry(botwidth)
2279 } else {
2280 ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2282 ${NS}::frame .bleft.top
2283 ${NS}::frame .bleft.mid
2284 ${NS}::frame .bleft.bottom
2286 ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
2287 pack .bleft.top.search -side left -padx 5
2288 set sstring .bleft.top.sstring
2289 set searchstring ""
2290 ${NS}::entry $sstring -width 20 -textvariable searchstring
2291 lappend entries $sstring
2292 trace add variable searchstring write incrsearch
2293 pack $sstring -side left -expand 1 -fill x
2294 ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
2295 -command changediffdisp -variable diffelide -value {0 0}
2296 ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
2297 -command changediffdisp -variable diffelide -value {0 1}
2298 ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
2299 -command changediffdisp -variable diffelide -value {1 0}
2300 ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2301 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2302 spinbox .bleft.mid.diffcontext -width 5 \
2303 -from 0 -increment 1 -to 10000000 \
2304 -validate all -validatecommand "diffcontextvalidate %P" \
2305 -textvariable diffcontextstring
2306 .bleft.mid.diffcontext set $diffcontext
2307 trace add variable diffcontextstring write diffcontextchange
2308 lappend entries .bleft.mid.diffcontext
2309 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2310 ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2311 -command changeignorespace -variable ignorespace
2312 pack .bleft.mid.ignspace -side left -padx 5
2314 set worddiff [mc "Line diff"]
2315 if {[package vcompare $git_version "1.7.2"] >= 0} {
2316 makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
2317 [mc "Markup words"] [mc "Color words"]
2318 trace add variable worddiff write changeworddiff
2319 pack .bleft.mid.worddiff -side left -padx 5
2322 set ctext .bleft.bottom.ctext
2323 text $ctext -background $bgcolor -foreground $fgcolor \
2324 -state disabled -font textfont \
2325 -yscrollcommand scrolltext -wrap none \
2326 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2327 if {$have_tk85} {
2328 $ctext conf -tabstyle wordprocessor
2330 ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
2331 ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
2332 pack .bleft.top -side top -fill x
2333 pack .bleft.mid -side top -fill x
2334 grid $ctext .bleft.bottom.sb -sticky nsew
2335 grid .bleft.bottom.sbhorizontal -sticky ew
2336 grid columnconfigure .bleft.bottom 0 -weight 1
2337 grid rowconfigure .bleft.bottom 0 -weight 1
2338 grid rowconfigure .bleft.bottom 1 -weight 0
2339 pack .bleft.bottom -side top -fill both -expand 1
2340 lappend bglist $ctext
2341 lappend fglist $ctext
2343 $ctext tag conf comment -wrap $wrapcomment
2344 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2345 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2346 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2347 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2348 $ctext tag conf m0 -fore red
2349 $ctext tag conf m1 -fore blue
2350 $ctext tag conf m2 -fore green
2351 $ctext tag conf m3 -fore purple
2352 $ctext tag conf m4 -fore brown
2353 $ctext tag conf m5 -fore "#009090"
2354 $ctext tag conf m6 -fore magenta
2355 $ctext tag conf m7 -fore "#808000"
2356 $ctext tag conf m8 -fore "#009000"
2357 $ctext tag conf m9 -fore "#ff0080"
2358 $ctext tag conf m10 -fore cyan
2359 $ctext tag conf m11 -fore "#b07070"
2360 $ctext tag conf m12 -fore "#70b0f0"
2361 $ctext tag conf m13 -fore "#70f0b0"
2362 $ctext tag conf m14 -fore "#f0b070"
2363 $ctext tag conf m15 -fore "#ff70b0"
2364 $ctext tag conf mmax -fore darkgrey
2365 set mergemax 16
2366 $ctext tag conf mresult -font textfontbold
2367 $ctext tag conf msep -font textfontbold
2368 $ctext tag conf found -back yellow
2369 $ctext tag conf currentsearchhit -back orange
2371 .pwbottom add .bleft
2372 if {!$use_ttk} {
2373 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2376 # lower right
2377 ${NS}::frame .bright
2378 ${NS}::frame .bright.mode
2379 ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
2380 -command reselectline -variable cmitmode -value "patch"
2381 ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
2382 -command reselectline -variable cmitmode -value "tree"
2383 grid .bright.mode.patch .bright.mode.tree -sticky ew
2384 pack .bright.mode -side top -fill x
2385 set cflist .bright.cfiles
2386 set indent [font measure mainfont "nn"]
2387 text $cflist \
2388 -selectbackground $selectbgcolor \
2389 -background $bgcolor -foreground $fgcolor \
2390 -font mainfont \
2391 -tabs [list $indent [expr {2 * $indent}]] \
2392 -yscrollcommand ".bright.sb set" \
2393 -cursor [. cget -cursor] \
2394 -spacing1 1 -spacing3 1
2395 lappend bglist $cflist
2396 lappend fglist $cflist
2397 ${NS}::scrollbar .bright.sb -command "$cflist yview"
2398 pack .bright.sb -side right -fill y
2399 pack $cflist -side left -fill both -expand 1
2400 $cflist tag configure highlight \
2401 -background [$cflist cget -selectbackground]
2402 $cflist tag configure bold -font mainfontbold
2404 .pwbottom add .bright
2405 .ctop add .pwbottom
2407 # restore window width & height if known
2408 if {[info exists geometry(main)]} {
2409 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2410 if {$w > [winfo screenwidth .]} {
2411 set w [winfo screenwidth .]
2413 if {$h > [winfo screenheight .]} {
2414 set h [winfo screenheight .]
2416 wm geometry . "${w}x$h"
2420 if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
2421 wm state . $geometry(state)
2424 if {[tk windowingsystem] eq {aqua}} {
2425 set M1B M1
2426 set ::BM "3"
2427 } else {
2428 set M1B Control
2429 set ::BM "2"
2432 if {$use_ttk} {
2433 bind .ctop <Map> {
2434 bind %W <Map> {}
2435 %W sashpos 0 $::geometry(topheight)
2437 bind .pwbottom <Map> {
2438 bind %W <Map> {}
2439 %W sashpos 0 $::geometry(botwidth)
2443 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2444 pack .ctop -fill both -expand 1
2445 bindall <1> {selcanvline %W %x %y}
2446 #bindall <B1-Motion> {selcanvline %W %x %y}
2447 if {[tk windowingsystem] == "win32"} {
2448 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2449 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2450 } else {
2451 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2452 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2453 if {[tk windowingsystem] eq "aqua"} {
2454 bindall <MouseWheel> {
2455 set delta [expr {- (%D)}]
2456 allcanvs yview scroll $delta units
2458 bindall <Shift-MouseWheel> {
2459 set delta [expr {- (%D)}]
2460 $canv xview scroll $delta units
2464 bindall <$::BM> "canvscan mark %W %x %y"
2465 bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
2466 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2467 bind . <$M1B-Key-w> doquit
2468 bindkey <Home> selfirstline
2469 bindkey <End> sellastline
2470 bind . <Key-Up> "selnextline -1"
2471 bind . <Key-Down> "selnextline 1"
2472 bind . <Shift-Key-Up> "dofind -1 0"
2473 bind . <Shift-Key-Down> "dofind 1 0"
2474 bindkey <Key-Right> "goforw"
2475 bindkey <Key-Left> "goback"
2476 bind . <Key-Prior> "selnextpage -1"
2477 bind . <Key-Next> "selnextpage 1"
2478 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2479 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2480 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2481 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2482 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2483 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2484 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2485 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2486 bindkey <Key-space> "$ctext yview scroll 1 pages"
2487 bindkey p "selnextline -1"
2488 bindkey n "selnextline 1"
2489 bindkey z "goback"
2490 bindkey x "goforw"
2491 bindkey k "selnextline -1"
2492 bindkey j "selnextline 1"
2493 bindkey h "goback"
2494 bindkey l "goforw"
2495 bindkey b prevfile
2496 bindkey d "$ctext yview scroll 18 units"
2497 bindkey u "$ctext yview scroll -18 units"
2498 bindkey / {focus $fstring}
2499 bindkey <Key-KP_Divide> {focus $fstring}
2500 bindkey <Key-Return> {dofind 1 1}
2501 bindkey ? {dofind -1 1}
2502 bindkey f nextfile
2503 bind . <F5> updatecommits
2504 bind . <Shift-F5> reloadcommits
2505 bind . <F2> showrefs
2506 bindmodfunctionkey Shift 4 {newview 0}
2507 bind . <F4> edit_or_newview
2508 bind . <$M1B-q> doquit
2509 bind . <$M1B-f> {dofind 1 1}
2510 bind . <$M1B-g> {dofind 1 0}
2511 bind . <$M1B-r> dosearchback
2512 bind . <$M1B-s> dosearch
2513 bind . <$M1B-equal> {incrfont 1}
2514 bind . <$M1B-plus> {incrfont 1}
2515 bind . <$M1B-KP_Add> {incrfont 1}
2516 bind . <$M1B-minus> {incrfont -1}
2517 bind . <$M1B-KP_Subtract> {incrfont -1}
2518 wm protocol . WM_DELETE_WINDOW doquit
2519 bind . <Destroy> {stop_backends}
2520 bind . <Button-1> "click %W"
2521 bind $fstring <Key-Return> {dofind 1 1}
2522 bind $sha1entry <Key-Return> {gotocommit; break}
2523 bind $sha1entry <<PasteSelection>> clearsha1
2524 bind $cflist <1> {sel_flist %W %x %y; break}
2525 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2526 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2527 global ctxbut
2528 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2529 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2530 bind $ctext <Button-1> {focus %W}
2531 bind $ctext <<Selection>> rehighlight_search_results
2533 set maincursor [. cget -cursor]
2534 set textcursor [$ctext cget -cursor]
2535 set curtextcursor $textcursor
2537 set rowctxmenu .rowctxmenu
2538 makemenu $rowctxmenu {
2539 {mc "Diff this -> selected" command {diffvssel 0}}
2540 {mc "Diff selected -> this" command {diffvssel 1}}
2541 {mc "Make patch" command mkpatch}
2542 {mc "Create tag" command mktag}
2543 {mc "Write commit to file" command writecommit}
2544 {mc "Create new branch" command mkbranch}
2545 {mc "Cherry-pick this commit" command cherrypick}
2546 {mc "Reset HEAD branch to here" command resethead}
2547 {mc "Mark this commit" command markhere}
2548 {mc "Return to mark" command gotomark}
2549 {mc "Find descendant of this and mark" command find_common_desc}
2550 {mc "Compare with marked commit" command compare_commits}
2551 {mc "Diff this -> marked commit" command {diffvsmark 0}}
2552 {mc "Diff marked commit -> this" command {diffvsmark 1}}
2554 $rowctxmenu configure -tearoff 0
2556 set fakerowmenu .fakerowmenu
2557 makemenu $fakerowmenu {
2558 {mc "Diff this -> selected" command {diffvssel 0}}
2559 {mc "Diff selected -> this" command {diffvssel 1}}
2560 {mc "Make patch" command mkpatch}
2561 {mc "Diff this -> marked commit" command {diffvsmark 0}}
2562 {mc "Diff marked commit -> this" command {diffvsmark 1}}
2564 $fakerowmenu configure -tearoff 0
2566 set headctxmenu .headctxmenu
2567 makemenu $headctxmenu {
2568 {mc "Check out this branch" command cobranch}
2569 {mc "Remove this branch" command rmbranch}
2571 $headctxmenu configure -tearoff 0
2573 global flist_menu
2574 set flist_menu .flistctxmenu
2575 makemenu $flist_menu {
2576 {mc "Highlight this too" command {flist_hl 0}}
2577 {mc "Highlight this only" command {flist_hl 1}}
2578 {mc "External diff" command {external_diff}}
2579 {mc "Blame parent commit" command {external_blame 1}}
2581 $flist_menu configure -tearoff 0
2583 global diff_menu
2584 set diff_menu .diffctxmenu
2585 makemenu $diff_menu {
2586 {mc "Show origin of this line" command show_line_source}
2587 {mc "Run git gui blame on this line" command {external_blame_diff}}
2589 $diff_menu configure -tearoff 0
2592 # Windows sends all mouse wheel events to the current focused window, not
2593 # the one where the mouse hovers, so bind those events here and redirect
2594 # to the correct window
2595 proc windows_mousewheel_redirector {W X Y D} {
2596 global canv canv2 canv3
2597 set w [winfo containing -displayof $W $X $Y]
2598 if {$w ne ""} {
2599 set u [expr {$D < 0 ? 5 : -5}]
2600 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2601 allcanvs yview scroll $u units
2602 } else {
2603 catch {
2604 $w yview scroll $u units
2610 # Update row number label when selectedline changes
2611 proc selectedline_change {n1 n2 op} {
2612 global selectedline rownumsel
2614 if {$selectedline eq {}} {
2615 set rownumsel {}
2616 } else {
2617 set rownumsel [expr {$selectedline + 1}]
2621 # mouse-2 makes all windows scan vertically, but only the one
2622 # the cursor is in scans horizontally
2623 proc canvscan {op w x y} {
2624 global canv canv2 canv3
2625 foreach c [list $canv $canv2 $canv3] {
2626 if {$c == $w} {
2627 $c scan $op $x $y
2628 } else {
2629 $c scan $op 0 $y
2634 proc scrollcanv {cscroll f0 f1} {
2635 $cscroll set $f0 $f1
2636 drawvisible
2637 flushhighlights
2640 # when we make a key binding for the toplevel, make sure
2641 # it doesn't get triggered when that key is pressed in the
2642 # find string entry widget.
2643 proc bindkey {ev script} {
2644 global entries
2645 bind . $ev $script
2646 set escript [bind Entry $ev]
2647 if {$escript == {}} {
2648 set escript [bind Entry <Key>]
2650 foreach e $entries {
2651 bind $e $ev "$escript; break"
2655 proc bindmodfunctionkey {mod n script} {
2656 bind . <$mod-F$n> $script
2657 catch { bind . <$mod-XF86_Switch_VT_$n> $script }
2660 # set the focus back to the toplevel for any click outside
2661 # the entry widgets
2662 proc click {w} {
2663 global ctext entries
2664 foreach e [concat $entries $ctext] {
2665 if {$w == $e} return
2667 focus .
2670 # Adjust the progress bar for a change in requested extent or canvas size
2671 proc adjustprogress {} {
2672 global progresscanv progressitem progresscoords
2673 global fprogitem fprogcoord lastprogupdate progupdatepending
2674 global rprogitem rprogcoord use_ttk
2676 if {$use_ttk} {
2677 $progresscanv configure -value [expr {int($fprogcoord * 100)}]
2678 return
2681 set w [expr {[winfo width $progresscanv] - 4}]
2682 set x0 [expr {$w * [lindex $progresscoords 0]}]
2683 set x1 [expr {$w * [lindex $progresscoords 1]}]
2684 set h [winfo height $progresscanv]
2685 $progresscanv coords $progressitem $x0 0 $x1 $h
2686 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2687 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2688 set now [clock clicks -milliseconds]
2689 if {$now >= $lastprogupdate + 100} {
2690 set progupdatepending 0
2691 update
2692 } elseif {!$progupdatepending} {
2693 set progupdatepending 1
2694 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2698 proc doprogupdate {} {
2699 global lastprogupdate progupdatepending
2701 if {$progupdatepending} {
2702 set progupdatepending 0
2703 set lastprogupdate [clock clicks -milliseconds]
2704 update
2708 proc savestuff {w} {
2709 global canv canv2 canv3 mainfont textfont uifont tabstop
2710 global stuffsaved findmergefiles maxgraphpct
2711 global maxwidth showneartags showlocalchanges
2712 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2713 global cmitmode wrapcomment datetimeformat limitdiffs
2714 global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
2715 global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
2716 global hideremotes want_ttk
2718 if {$stuffsaved} return
2719 if {![winfo viewable .]} return
2720 catch {
2721 if {[file exists ~/.gitk-new]} {file delete -force ~/.gitk-new}
2722 set f [open "~/.gitk-new" w]
2723 if {$::tcl_platform(platform) eq {windows}} {
2724 file attributes "~/.gitk-new" -hidden true
2726 puts $f [list set mainfont $mainfont]
2727 puts $f [list set textfont $textfont]
2728 puts $f [list set uifont $uifont]
2729 puts $f [list set tabstop $tabstop]
2730 puts $f [list set findmergefiles $findmergefiles]
2731 puts $f [list set maxgraphpct $maxgraphpct]
2732 puts $f [list set maxwidth $maxwidth]
2733 puts $f [list set cmitmode $cmitmode]
2734 puts $f [list set wrapcomment $wrapcomment]
2735 puts $f [list set autoselect $autoselect]
2736 puts $f [list set autosellen $autosellen]
2737 puts $f [list set showneartags $showneartags]
2738 puts $f [list set hideremotes $hideremotes]
2739 puts $f [list set showlocalchanges $showlocalchanges]
2740 puts $f [list set datetimeformat $datetimeformat]
2741 puts $f [list set limitdiffs $limitdiffs]
2742 puts $f [list set uicolor $uicolor]
2743 puts $f [list set want_ttk $want_ttk]
2744 puts $f [list set bgcolor $bgcolor]
2745 puts $f [list set fgcolor $fgcolor]
2746 puts $f [list set colors $colors]
2747 puts $f [list set diffcolors $diffcolors]
2748 puts $f [list set markbgcolor $markbgcolor]
2749 puts $f [list set diffcontext $diffcontext]
2750 puts $f [list set selectbgcolor $selectbgcolor]
2751 puts $f [list set extdifftool $extdifftool]
2752 puts $f [list set perfile_attrs $perfile_attrs]
2754 puts $f "set geometry(main) [wm geometry .]"
2755 puts $f "set geometry(state) [wm state .]"
2756 puts $f "set geometry(topwidth) [winfo width .tf]"
2757 puts $f "set geometry(topheight) [winfo height .tf]"
2758 if {$use_ttk} {
2759 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
2760 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
2761 } else {
2762 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2763 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2765 puts $f "set geometry(botwidth) [winfo width .bleft]"
2766 puts $f "set geometry(botheight) [winfo height .bleft]"
2768 puts -nonewline $f "set permviews {"
2769 for {set v 0} {$v < $nextviewnum} {incr v} {
2770 if {$viewperm($v)} {
2771 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2774 puts $f "}"
2775 close $f
2776 file rename -force "~/.gitk-new" "~/.gitk"
2778 set stuffsaved 1
2781 proc resizeclistpanes {win w} {
2782 global oldwidth use_ttk
2783 if {[info exists oldwidth($win)]} {
2784 if {$use_ttk} {
2785 set s0 [$win sashpos 0]
2786 set s1 [$win sashpos 1]
2787 } else {
2788 set s0 [$win sash coord 0]
2789 set s1 [$win sash coord 1]
2791 if {$w < 60} {
2792 set sash0 [expr {int($w/2 - 2)}]
2793 set sash1 [expr {int($w*5/6 - 2)}]
2794 } else {
2795 set factor [expr {1.0 * $w / $oldwidth($win)}]
2796 set sash0 [expr {int($factor * [lindex $s0 0])}]
2797 set sash1 [expr {int($factor * [lindex $s1 0])}]
2798 if {$sash0 < 30} {
2799 set sash0 30
2801 if {$sash1 < $sash0 + 20} {
2802 set sash1 [expr {$sash0 + 20}]
2804 if {$sash1 > $w - 10} {
2805 set sash1 [expr {$w - 10}]
2806 if {$sash0 > $sash1 - 20} {
2807 set sash0 [expr {$sash1 - 20}]
2811 if {$use_ttk} {
2812 $win sashpos 0 $sash0
2813 $win sashpos 1 $sash1
2814 } else {
2815 $win sash place 0 $sash0 [lindex $s0 1]
2816 $win sash place 1 $sash1 [lindex $s1 1]
2819 set oldwidth($win) $w
2822 proc resizecdetpanes {win w} {
2823 global oldwidth use_ttk
2824 if {[info exists oldwidth($win)]} {
2825 if {$use_ttk} {
2826 set s0 [$win sashpos 0]
2827 } else {
2828 set s0 [$win sash coord 0]
2830 if {$w < 60} {
2831 set sash0 [expr {int($w*3/4 - 2)}]
2832 } else {
2833 set factor [expr {1.0 * $w / $oldwidth($win)}]
2834 set sash0 [expr {int($factor * [lindex $s0 0])}]
2835 if {$sash0 < 45} {
2836 set sash0 45
2838 if {$sash0 > $w - 15} {
2839 set sash0 [expr {$w - 15}]
2842 if {$use_ttk} {
2843 $win sashpos 0 $sash0
2844 } else {
2845 $win sash place 0 $sash0 [lindex $s0 1]
2848 set oldwidth($win) $w
2851 proc allcanvs args {
2852 global canv canv2 canv3
2853 eval $canv $args
2854 eval $canv2 $args
2855 eval $canv3 $args
2858 proc bindall {event action} {
2859 global canv canv2 canv3
2860 bind $canv $event $action
2861 bind $canv2 $event $action
2862 bind $canv3 $event $action
2865 proc about {} {
2866 global uifont NS
2867 set w .about
2868 if {[winfo exists $w]} {
2869 raise $w
2870 return
2872 ttk_toplevel $w
2873 wm title $w [mc "About gitk"]
2874 make_transient $w .
2875 message $w.m -text [mc "
2876 Gitk - a commit viewer for git
2878 Copyright \u00a9 2005-2011 Paul Mackerras
2880 Use and redistribute under the terms of the GNU General Public License"] \
2881 -justify center -aspect 400 -border 2 -bg white -relief groove
2882 pack $w.m -side top -fill x -padx 2 -pady 2
2883 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2884 pack $w.ok -side bottom
2885 bind $w <Visibility> "focus $w.ok"
2886 bind $w <Key-Escape> "destroy $w"
2887 bind $w <Key-Return> "destroy $w"
2888 tk::PlaceWindow $w widget .
2891 proc keys {} {
2892 global NS
2893 set w .keys
2894 if {[winfo exists $w]} {
2895 raise $w
2896 return
2898 if {[tk windowingsystem] eq {aqua}} {
2899 set M1T Cmd
2900 } else {
2901 set M1T Ctrl
2903 ttk_toplevel $w
2904 wm title $w [mc "Gitk key bindings"]
2905 make_transient $w .
2906 message $w.m -text "
2907 [mc "Gitk key bindings:"]
2909 [mc "<%s-Q> Quit" $M1T]
2910 [mc "<%s-W> Close window" $M1T]
2911 [mc "<Home> Move to first commit"]
2912 [mc "<End> Move to last commit"]
2913 [mc "<Up>, p, k Move up one commit"]
2914 [mc "<Down>, n, j Move down one commit"]
2915 [mc "<Left>, z, h Go back in history list"]
2916 [mc "<Right>, x, l Go forward in history list"]
2917 [mc "<PageUp> Move up one page in commit list"]
2918 [mc "<PageDown> Move down one page in commit list"]
2919 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2920 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2921 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2922 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2923 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2924 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2925 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2926 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2927 [mc "<Delete>, b Scroll diff view up one page"]
2928 [mc "<Backspace> Scroll diff view up one page"]
2929 [mc "<Space> Scroll diff view down one page"]
2930 [mc "u Scroll diff view up 18 lines"]
2931 [mc "d Scroll diff view down 18 lines"]
2932 [mc "<%s-F> Find" $M1T]
2933 [mc "<%s-G> Move to next find hit" $M1T]
2934 [mc "<Return> Move to next find hit"]
2935 [mc "/ Focus the search box"]
2936 [mc "? Move to previous find hit"]
2937 [mc "f Scroll diff view to next file"]
2938 [mc "<%s-S> Search for next hit in diff view" $M1T]
2939 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2940 [mc "<%s-KP+> Increase font size" $M1T]
2941 [mc "<%s-plus> Increase font size" $M1T]
2942 [mc "<%s-KP-> Decrease font size" $M1T]
2943 [mc "<%s-minus> Decrease font size" $M1T]
2944 [mc "<F5> Update"]
2946 -justify left -bg white -border 2 -relief groove
2947 pack $w.m -side top -fill both -padx 2 -pady 2
2948 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2949 bind $w <Key-Escape> [list destroy $w]
2950 pack $w.ok -side bottom
2951 bind $w <Visibility> "focus $w.ok"
2952 bind $w <Key-Escape> "destroy $w"
2953 bind $w <Key-Return> "destroy $w"
2956 # Procedures for manipulating the file list window at the
2957 # bottom right of the overall window.
2959 proc treeview {w l openlevs} {
2960 global treecontents treediropen treeheight treeparent treeindex
2962 set ix 0
2963 set treeindex() 0
2964 set lev 0
2965 set prefix {}
2966 set prefixend -1
2967 set prefendstack {}
2968 set htstack {}
2969 set ht 0
2970 set treecontents() {}
2971 $w conf -state normal
2972 foreach f $l {
2973 while {[string range $f 0 $prefixend] ne $prefix} {
2974 if {$lev <= $openlevs} {
2975 $w mark set e:$treeindex($prefix) "end -1c"
2976 $w mark gravity e:$treeindex($prefix) left
2978 set treeheight($prefix) $ht
2979 incr ht [lindex $htstack end]
2980 set htstack [lreplace $htstack end end]
2981 set prefixend [lindex $prefendstack end]
2982 set prefendstack [lreplace $prefendstack end end]
2983 set prefix [string range $prefix 0 $prefixend]
2984 incr lev -1
2986 set tail [string range $f [expr {$prefixend+1}] end]
2987 while {[set slash [string first "/" $tail]] >= 0} {
2988 lappend htstack $ht
2989 set ht 0
2990 lappend prefendstack $prefixend
2991 incr prefixend [expr {$slash + 1}]
2992 set d [string range $tail 0 $slash]
2993 lappend treecontents($prefix) $d
2994 set oldprefix $prefix
2995 append prefix $d
2996 set treecontents($prefix) {}
2997 set treeindex($prefix) [incr ix]
2998 set treeparent($prefix) $oldprefix
2999 set tail [string range $tail [expr {$slash+1}] end]
3000 if {$lev <= $openlevs} {
3001 set ht 1
3002 set treediropen($prefix) [expr {$lev < $openlevs}]
3003 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
3004 $w mark set d:$ix "end -1c"
3005 $w mark gravity d:$ix left
3006 set str "\n"
3007 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
3008 $w insert end $str
3009 $w image create end -align center -image $bm -padx 1 \
3010 -name a:$ix
3011 $w insert end $d [highlight_tag $prefix]
3012 $w mark set s:$ix "end -1c"
3013 $w mark gravity s:$ix left
3015 incr lev
3017 if {$tail ne {}} {
3018 if {$lev <= $openlevs} {
3019 incr ht
3020 set str "\n"
3021 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
3022 $w insert end $str
3023 $w insert end $tail [highlight_tag $f]
3025 lappend treecontents($prefix) $tail
3028 while {$htstack ne {}} {
3029 set treeheight($prefix) $ht
3030 incr ht [lindex $htstack end]
3031 set htstack [lreplace $htstack end end]
3032 set prefixend [lindex $prefendstack end]
3033 set prefendstack [lreplace $prefendstack end end]
3034 set prefix [string range $prefix 0 $prefixend]
3036 $w conf -state disabled
3039 proc linetoelt {l} {
3040 global treeheight treecontents
3042 set y 2
3043 set prefix {}
3044 while {1} {
3045 foreach e $treecontents($prefix) {
3046 if {$y == $l} {
3047 return "$prefix$e"
3049 set n 1
3050 if {[string index $e end] eq "/"} {
3051 set n $treeheight($prefix$e)
3052 if {$y + $n > $l} {
3053 append prefix $e
3054 incr y
3055 break
3058 incr y $n
3063 proc highlight_tree {y prefix} {
3064 global treeheight treecontents cflist
3066 foreach e $treecontents($prefix) {
3067 set path $prefix$e
3068 if {[highlight_tag $path] ne {}} {
3069 $cflist tag add bold $y.0 "$y.0 lineend"
3071 incr y
3072 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
3073 set y [highlight_tree $y $path]
3076 return $y
3079 proc treeclosedir {w dir} {
3080 global treediropen treeheight treeparent treeindex
3082 set ix $treeindex($dir)
3083 $w conf -state normal
3084 $w delete s:$ix e:$ix
3085 set treediropen($dir) 0
3086 $w image configure a:$ix -image tri-rt
3087 $w conf -state disabled
3088 set n [expr {1 - $treeheight($dir)}]
3089 while {$dir ne {}} {
3090 incr treeheight($dir) $n
3091 set dir $treeparent($dir)
3095 proc treeopendir {w dir} {
3096 global treediropen treeheight treeparent treecontents treeindex
3098 set ix $treeindex($dir)
3099 $w conf -state normal
3100 $w image configure a:$ix -image tri-dn
3101 $w mark set e:$ix s:$ix
3102 $w mark gravity e:$ix right
3103 set lev 0
3104 set str "\n"
3105 set n [llength $treecontents($dir)]
3106 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
3107 incr lev
3108 append str "\t"
3109 incr treeheight($x) $n
3111 foreach e $treecontents($dir) {
3112 set de $dir$e
3113 if {[string index $e end] eq "/"} {
3114 set iy $treeindex($de)
3115 $w mark set d:$iy e:$ix
3116 $w mark gravity d:$iy left
3117 $w insert e:$ix $str
3118 set treediropen($de) 0
3119 $w image create e:$ix -align center -image tri-rt -padx 1 \
3120 -name a:$iy
3121 $w insert e:$ix $e [highlight_tag $de]
3122 $w mark set s:$iy e:$ix
3123 $w mark gravity s:$iy left
3124 set treeheight($de) 1
3125 } else {
3126 $w insert e:$ix $str
3127 $w insert e:$ix $e [highlight_tag $de]
3130 $w mark gravity e:$ix right
3131 $w conf -state disabled
3132 set treediropen($dir) 1
3133 set top [lindex [split [$w index @0,0] .] 0]
3134 set ht [$w cget -height]
3135 set l [lindex [split [$w index s:$ix] .] 0]
3136 if {$l < $top} {
3137 $w yview $l.0
3138 } elseif {$l + $n + 1 > $top + $ht} {
3139 set top [expr {$l + $n + 2 - $ht}]
3140 if {$l < $top} {
3141 set top $l
3143 $w yview $top.0
3147 proc treeclick {w x y} {
3148 global treediropen cmitmode ctext cflist cflist_top
3150 if {$cmitmode ne "tree"} return
3151 if {![info exists cflist_top]} return
3152 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3153 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3154 $cflist tag add highlight $l.0 "$l.0 lineend"
3155 set cflist_top $l
3156 if {$l == 1} {
3157 $ctext yview 1.0
3158 return
3160 set e [linetoelt $l]
3161 if {[string index $e end] ne "/"} {
3162 showfile $e
3163 } elseif {$treediropen($e)} {
3164 treeclosedir $w $e
3165 } else {
3166 treeopendir $w $e
3170 proc setfilelist {id} {
3171 global treefilelist cflist jump_to_here
3173 treeview $cflist $treefilelist($id) 0
3174 if {$jump_to_here ne {}} {
3175 set f [lindex $jump_to_here 0]
3176 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
3177 showfile $f
3182 image create bitmap tri-rt -background black -foreground blue -data {
3183 #define tri-rt_width 13
3184 #define tri-rt_height 13
3185 static unsigned char tri-rt_bits[] = {
3186 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
3187 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
3188 0x00, 0x00};
3189 } -maskdata {
3190 #define tri-rt-mask_width 13
3191 #define tri-rt-mask_height 13
3192 static unsigned char tri-rt-mask_bits[] = {
3193 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
3194 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
3195 0x08, 0x00};
3197 image create bitmap tri-dn -background black -foreground blue -data {
3198 #define tri-dn_width 13
3199 #define tri-dn_height 13
3200 static unsigned char tri-dn_bits[] = {
3201 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
3202 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3203 0x00, 0x00};
3204 } -maskdata {
3205 #define tri-dn-mask_width 13
3206 #define tri-dn-mask_height 13
3207 static unsigned char tri-dn-mask_bits[] = {
3208 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
3209 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
3210 0x00, 0x00};
3213 image create bitmap reficon-T -background black -foreground yellow -data {
3214 #define tagicon_width 13
3215 #define tagicon_height 9
3216 static unsigned char tagicon_bits[] = {
3217 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
3218 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
3219 } -maskdata {
3220 #define tagicon-mask_width 13
3221 #define tagicon-mask_height 9
3222 static unsigned char tagicon-mask_bits[] = {
3223 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
3224 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
3226 set rectdata {
3227 #define headicon_width 13
3228 #define headicon_height 9
3229 static unsigned char headicon_bits[] = {
3230 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
3231 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
3233 set rectmask {
3234 #define headicon-mask_width 13
3235 #define headicon-mask_height 9
3236 static unsigned char headicon-mask_bits[] = {
3237 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
3238 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
3240 image create bitmap reficon-H -background black -foreground green \
3241 -data $rectdata -maskdata $rectmask
3242 image create bitmap reficon-o -background black -foreground "#ddddff" \
3243 -data $rectdata -maskdata $rectmask
3245 proc init_flist {first} {
3246 global cflist cflist_top difffilestart
3248 $cflist conf -state normal
3249 $cflist delete 0.0 end
3250 if {$first ne {}} {
3251 $cflist insert end $first
3252 set cflist_top 1
3253 $cflist tag add highlight 1.0 "1.0 lineend"
3254 } else {
3255 catch {unset cflist_top}
3257 $cflist conf -state disabled
3258 set difffilestart {}
3261 proc highlight_tag {f} {
3262 global highlight_paths
3264 foreach p $highlight_paths {
3265 if {[string match $p $f]} {
3266 return "bold"
3269 return {}
3272 proc highlight_filelist {} {
3273 global cmitmode cflist
3275 $cflist conf -state normal
3276 if {$cmitmode ne "tree"} {
3277 set end [lindex [split [$cflist index end] .] 0]
3278 for {set l 2} {$l < $end} {incr l} {
3279 set line [$cflist get $l.0 "$l.0 lineend"]
3280 if {[highlight_tag $line] ne {}} {
3281 $cflist tag add bold $l.0 "$l.0 lineend"
3284 } else {
3285 highlight_tree 2 {}
3287 $cflist conf -state disabled
3290 proc unhighlight_filelist {} {
3291 global cflist
3293 $cflist conf -state normal
3294 $cflist tag remove bold 1.0 end
3295 $cflist conf -state disabled
3298 proc add_flist {fl} {
3299 global cflist
3301 $cflist conf -state normal
3302 foreach f $fl {
3303 $cflist insert end "\n"
3304 $cflist insert end $f [highlight_tag $f]
3306 $cflist conf -state disabled
3309 proc sel_flist {w x y} {
3310 global ctext difffilestart cflist cflist_top cmitmode
3312 if {$cmitmode eq "tree"} return
3313 if {![info exists cflist_top]} return
3314 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3315 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3316 $cflist tag add highlight $l.0 "$l.0 lineend"
3317 set cflist_top $l
3318 if {$l == 1} {
3319 $ctext yview 1.0
3320 } else {
3321 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3323 suppress_highlighting_file_for_current_scrollpos
3326 proc pop_flist_menu {w X Y x y} {
3327 global ctext cflist cmitmode flist_menu flist_menu_file
3328 global treediffs diffids
3330 stopfinding
3331 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3332 if {$l <= 1} return
3333 if {$cmitmode eq "tree"} {
3334 set e [linetoelt $l]
3335 if {[string index $e end] eq "/"} return
3336 } else {
3337 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3339 set flist_menu_file $e
3340 set xdiffstate "normal"
3341 if {$cmitmode eq "tree"} {
3342 set xdiffstate "disabled"
3344 # Disable "External diff" item in tree mode
3345 $flist_menu entryconf 2 -state $xdiffstate
3346 tk_popup $flist_menu $X $Y
3349 proc find_ctext_fileinfo {line} {
3350 global ctext_file_names ctext_file_lines
3352 set ok [bsearch $ctext_file_lines $line]
3353 set tline [lindex $ctext_file_lines $ok]
3355 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3356 return {}
3357 } else {
3358 return [list [lindex $ctext_file_names $ok] $tline]
3362 proc pop_diff_menu {w X Y x y} {
3363 global ctext diff_menu flist_menu_file
3364 global diff_menu_txtpos diff_menu_line
3365 global diff_menu_filebase
3367 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3368 set diff_menu_line [lindex $diff_menu_txtpos 0]
3369 # don't pop up the menu on hunk-separator or file-separator lines
3370 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3371 return
3373 stopfinding
3374 set f [find_ctext_fileinfo $diff_menu_line]
3375 if {$f eq {}} return
3376 set flist_menu_file [lindex $f 0]
3377 set diff_menu_filebase [lindex $f 1]
3378 tk_popup $diff_menu $X $Y
3381 proc flist_hl {only} {
3382 global flist_menu_file findstring gdttype
3384 set x [shellquote $flist_menu_file]
3385 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3386 set findstring $x
3387 } else {
3388 append findstring " " $x
3390 set gdttype [mc "touching paths:"]
3393 proc gitknewtmpdir {} {
3394 global diffnum gitktmpdir gitdir
3396 if {![info exists gitktmpdir]} {
3397 set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
3398 if {[catch {file mkdir $gitktmpdir} err]} {
3399 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3400 unset gitktmpdir
3401 return {}
3403 set diffnum 0
3405 incr diffnum
3406 set diffdir [file join $gitktmpdir $diffnum]
3407 if {[catch {file mkdir $diffdir} err]} {
3408 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3409 return {}
3411 return $diffdir
3414 proc save_file_from_commit {filename output what} {
3415 global nullfile
3417 if {[catch {exec git show $filename -- > $output} err]} {
3418 if {[string match "fatal: bad revision *" $err]} {
3419 return $nullfile
3421 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3422 return {}
3424 return $output
3427 proc external_diff_get_one_file {diffid filename diffdir} {
3428 global nullid nullid2 nullfile
3429 global worktree
3431 if {$diffid == $nullid} {
3432 set difffile [file join $worktree $filename]
3433 if {[file exists $difffile]} {
3434 return $difffile
3436 return $nullfile
3438 if {$diffid == $nullid2} {
3439 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3440 return [save_file_from_commit :$filename $difffile index]
3442 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3443 return [save_file_from_commit $diffid:$filename $difffile \
3444 "revision $diffid"]
3447 proc external_diff {} {
3448 global nullid nullid2
3449 global flist_menu_file
3450 global diffids
3451 global extdifftool
3453 if {[llength $diffids] == 1} {
3454 # no reference commit given
3455 set diffidto [lindex $diffids 0]
3456 if {$diffidto eq $nullid} {
3457 # diffing working copy with index
3458 set diffidfrom $nullid2
3459 } elseif {$diffidto eq $nullid2} {
3460 # diffing index with HEAD
3461 set diffidfrom "HEAD"
3462 } else {
3463 # use first parent commit
3464 global parentlist selectedline
3465 set diffidfrom [lindex $parentlist $selectedline 0]
3467 } else {
3468 set diffidfrom [lindex $diffids 0]
3469 set diffidto [lindex $diffids 1]
3472 # make sure that several diffs wont collide
3473 set diffdir [gitknewtmpdir]
3474 if {$diffdir eq {}} return
3476 # gather files to diff
3477 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3478 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3480 if {$difffromfile ne {} && $difftofile ne {}} {
3481 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
3482 if {[catch {set fl [open |$cmd r]} err]} {
3483 file delete -force $diffdir
3484 error_popup "$extdifftool: [mc "command failed:"] $err"
3485 } else {
3486 fconfigure $fl -blocking 0
3487 filerun $fl [list delete_at_eof $fl $diffdir]
3492 proc find_hunk_blamespec {base line} {
3493 global ctext
3495 # Find and parse the hunk header
3496 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3497 if {$s_lix eq {}} return
3499 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3500 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3501 s_line old_specs osz osz1 new_line nsz]} {
3502 return
3505 # base lines for the parents
3506 set base_lines [list $new_line]
3507 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3508 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3509 old_spec old_line osz]} {
3510 return
3512 lappend base_lines $old_line
3515 # Now scan the lines to determine offset within the hunk
3516 set max_parent [expr {[llength $base_lines]-2}]
3517 set dline 0
3518 set s_lno [lindex [split $s_lix "."] 0]
3520 # Determine if the line is removed
3521 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3522 if {[string match {[-+ ]*} $chunk]} {
3523 set removed_idx [string first "-" $chunk]
3524 # Choose a parent index
3525 if {$removed_idx >= 0} {
3526 set parent $removed_idx
3527 } else {
3528 set unchanged_idx [string first " " $chunk]
3529 if {$unchanged_idx >= 0} {
3530 set parent $unchanged_idx
3531 } else {
3532 # blame the current commit
3533 set parent -1
3536 # then count other lines that belong to it
3537 for {set i $line} {[incr i -1] > $s_lno} {} {
3538 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3539 # Determine if the line is removed
3540 set removed_idx [string first "-" $chunk]
3541 if {$parent >= 0} {
3542 set code [string index $chunk $parent]
3543 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3544 incr dline
3546 } else {
3547 if {$removed_idx < 0} {
3548 incr dline
3552 incr parent
3553 } else {
3554 set parent 0
3557 incr dline [lindex $base_lines $parent]
3558 return [list $parent $dline]
3561 proc external_blame_diff {} {
3562 global currentid cmitmode
3563 global diff_menu_txtpos diff_menu_line
3564 global diff_menu_filebase flist_menu_file
3566 if {$cmitmode eq "tree"} {
3567 set parent_idx 0
3568 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3569 } else {
3570 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3571 if {$hinfo ne {}} {
3572 set parent_idx [lindex $hinfo 0]
3573 set line [lindex $hinfo 1]
3574 } else {
3575 set parent_idx 0
3576 set line 0
3580 external_blame $parent_idx $line
3583 # Find the SHA1 ID of the blob for file $fname in the index
3584 # at stage 0 or 2
3585 proc index_sha1 {fname} {
3586 set f [open [list | git ls-files -s $fname] r]
3587 while {[gets $f line] >= 0} {
3588 set info [lindex [split $line "\t"] 0]
3589 set stage [lindex $info 2]
3590 if {$stage eq "0" || $stage eq "2"} {
3591 close $f
3592 return [lindex $info 1]
3595 close $f
3596 return {}
3599 # Turn an absolute path into one relative to the current directory
3600 proc make_relative {f} {
3601 if {[file pathtype $f] eq "relative"} {
3602 return $f
3604 set elts [file split $f]
3605 set here [file split [pwd]]
3606 set ei 0
3607 set hi 0
3608 set res {}
3609 foreach d $here {
3610 if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
3611 lappend res ".."
3612 } else {
3613 incr ei
3615 incr hi
3617 set elts [concat $res [lrange $elts $ei end]]
3618 return [eval file join $elts]
3621 proc external_blame {parent_idx {line {}}} {
3622 global flist_menu_file cdup
3623 global nullid nullid2
3624 global parentlist selectedline currentid
3626 if {$parent_idx > 0} {
3627 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3628 } else {
3629 set base_commit $currentid
3632 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3633 error_popup [mc "No such commit"]
3634 return
3637 set cmdline [list git gui blame]
3638 if {$line ne {} && $line > 1} {
3639 lappend cmdline "--line=$line"
3641 set f [file join $cdup $flist_menu_file]
3642 # Unfortunately it seems git gui blame doesn't like
3643 # being given an absolute path...
3644 set f [make_relative $f]
3645 lappend cmdline $base_commit $f
3646 if {[catch {eval exec $cmdline &} err]} {
3647 error_popup "[mc "git gui blame: command failed:"] $err"
3651 proc show_line_source {} {
3652 global cmitmode currentid parents curview blamestuff blameinst
3653 global diff_menu_line diff_menu_filebase flist_menu_file
3654 global nullid nullid2 gitdir cdup
3656 set from_index {}
3657 if {$cmitmode eq "tree"} {
3658 set id $currentid
3659 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3660 } else {
3661 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3662 if {$h eq {}} return
3663 set pi [lindex $h 0]
3664 if {$pi == 0} {
3665 mark_ctext_line $diff_menu_line
3666 return
3668 incr pi -1
3669 if {$currentid eq $nullid} {
3670 if {$pi > 0} {
3671 # must be a merge in progress...
3672 if {[catch {
3673 # get the last line from .git/MERGE_HEAD
3674 set f [open [file join $gitdir MERGE_HEAD] r]
3675 set id [lindex [split [read $f] "\n"] end-1]
3676 close $f
3677 } err]} {
3678 error_popup [mc "Couldn't read merge head: %s" $err]
3679 return
3681 } elseif {$parents($curview,$currentid) eq $nullid2} {
3682 # need to do the blame from the index
3683 if {[catch {
3684 set from_index [index_sha1 $flist_menu_file]
3685 } err]} {
3686 error_popup [mc "Error reading index: %s" $err]
3687 return
3689 } else {
3690 set id $parents($curview,$currentid)
3692 } else {
3693 set id [lindex $parents($curview,$currentid) $pi]
3695 set line [lindex $h 1]
3697 set blameargs {}
3698 if {$from_index ne {}} {
3699 lappend blameargs | git cat-file blob $from_index
3701 lappend blameargs | git blame -p -L$line,+1
3702 if {$from_index ne {}} {
3703 lappend blameargs --contents -
3704 } else {
3705 lappend blameargs $id
3707 lappend blameargs -- [file join $cdup $flist_menu_file]
3708 if {[catch {
3709 set f [open $blameargs r]
3710 } err]} {
3711 error_popup [mc "Couldn't start git blame: %s" $err]
3712 return
3714 nowbusy blaming [mc "Searching"]
3715 fconfigure $f -blocking 0
3716 set i [reg_instance $f]
3717 set blamestuff($i) {}
3718 set blameinst $i
3719 filerun $f [list read_line_source $f $i]
3722 proc stopblaming {} {
3723 global blameinst
3725 if {[info exists blameinst]} {
3726 stop_instance $blameinst
3727 unset blameinst
3728 notbusy blaming
3732 proc read_line_source {fd inst} {
3733 global blamestuff curview commfd blameinst nullid nullid2
3735 while {[gets $fd line] >= 0} {
3736 lappend blamestuff($inst) $line
3738 if {![eof $fd]} {
3739 return 1
3741 unset commfd($inst)
3742 unset blameinst
3743 notbusy blaming
3744 fconfigure $fd -blocking 1
3745 if {[catch {close $fd} err]} {
3746 error_popup [mc "Error running git blame: %s" $err]
3747 return 0
3750 set fname {}
3751 set line [split [lindex $blamestuff($inst) 0] " "]
3752 set id [lindex $line 0]
3753 set lnum [lindex $line 1]
3754 if {[string length $id] == 40 && [string is xdigit $id] &&
3755 [string is digit -strict $lnum]} {
3756 # look for "filename" line
3757 foreach l $blamestuff($inst) {
3758 if {[string match "filename *" $l]} {
3759 set fname [string range $l 9 end]
3760 break
3764 if {$fname ne {}} {
3765 # all looks good, select it
3766 if {$id eq $nullid} {
3767 # blame uses all-zeroes to mean not committed,
3768 # which would mean a change in the index
3769 set id $nullid2
3771 if {[commitinview $id $curview]} {
3772 selectline [rowofcommit $id] 1 [list $fname $lnum]
3773 } else {
3774 error_popup [mc "That line comes from commit %s, \
3775 which is not in this view" [shortids $id]]
3777 } else {
3778 puts "oops couldn't parse git blame output"
3780 return 0
3783 # delete $dir when we see eof on $f (presumably because the child has exited)
3784 proc delete_at_eof {f dir} {
3785 while {[gets $f line] >= 0} {}
3786 if {[eof $f]} {
3787 if {[catch {close $f} err]} {
3788 error_popup "[mc "External diff viewer failed:"] $err"
3790 file delete -force $dir
3791 return 0
3793 return 1
3796 # Functions for adding and removing shell-type quoting
3798 proc shellquote {str} {
3799 if {![string match "*\['\"\\ \t]*" $str]} {
3800 return $str
3802 if {![string match "*\['\"\\]*" $str]} {
3803 return "\"$str\""
3805 if {![string match "*'*" $str]} {
3806 return "'$str'"
3808 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3811 proc shellarglist {l} {
3812 set str {}
3813 foreach a $l {
3814 if {$str ne {}} {
3815 append str " "
3817 append str [shellquote $a]
3819 return $str
3822 proc shelldequote {str} {
3823 set ret {}
3824 set used -1
3825 while {1} {
3826 incr used
3827 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3828 append ret [string range $str $used end]
3829 set used [string length $str]
3830 break
3832 set first [lindex $first 0]
3833 set ch [string index $str $first]
3834 if {$first > $used} {
3835 append ret [string range $str $used [expr {$first - 1}]]
3836 set used $first
3838 if {$ch eq " " || $ch eq "\t"} break
3839 incr used
3840 if {$ch eq "'"} {
3841 set first [string first "'" $str $used]
3842 if {$first < 0} {
3843 error "unmatched single-quote"
3845 append ret [string range $str $used [expr {$first - 1}]]
3846 set used $first
3847 continue
3849 if {$ch eq "\\"} {
3850 if {$used >= [string length $str]} {
3851 error "trailing backslash"
3853 append ret [string index $str $used]
3854 continue
3856 # here ch == "\""
3857 while {1} {
3858 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3859 error "unmatched double-quote"
3861 set first [lindex $first 0]
3862 set ch [string index $str $first]
3863 if {$first > $used} {
3864 append ret [string range $str $used [expr {$first - 1}]]
3865 set used $first
3867 if {$ch eq "\""} break
3868 incr used
3869 append ret [string index $str $used]
3870 incr used
3873 return [list $used $ret]
3876 proc shellsplit {str} {
3877 set l {}
3878 while {1} {
3879 set str [string trimleft $str]
3880 if {$str eq {}} break
3881 set dq [shelldequote $str]
3882 set n [lindex $dq 0]
3883 set word [lindex $dq 1]
3884 set str [string range $str $n end]
3885 lappend l $word
3887 return $l
3890 # Code to implement multiple views
3892 proc newview {ishighlight} {
3893 global nextviewnum newviewname newishighlight
3894 global revtreeargs viewargscmd newviewopts curview
3896 set newishighlight $ishighlight
3897 set top .gitkview
3898 if {[winfo exists $top]} {
3899 raise $top
3900 return
3902 decode_view_opts $nextviewnum $revtreeargs
3903 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3904 set newviewopts($nextviewnum,perm) 0
3905 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3906 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3909 set known_view_options {
3910 {perm b . {} {mc "Remember this view"}}
3911 {reflabel l + {} {mc "References (space separated list):"}}
3912 {refs t15 .. {} {mc "Branches & tags:"}}
3913 {allrefs b *. "--all" {mc "All refs"}}
3914 {branches b . "--branches" {mc "All (local) branches"}}
3915 {tags b . "--tags" {mc "All tags"}}
3916 {remotes b . "--remotes" {mc "All remote-tracking branches"}}
3917 {commitlbl l + {} {mc "Commit Info (regular expressions):"}}
3918 {author t15 .. "--author=*" {mc "Author:"}}
3919 {committer t15 . "--committer=*" {mc "Committer:"}}
3920 {loginfo t15 .. "--grep=*" {mc "Commit Message:"}}
3921 {allmatch b .. "--all-match" {mc "Matches all Commit Info criteria"}}
3922 {changes_l l + {} {mc "Changes to Files:"}}
3923 {pickaxe_s r0 . {} {mc "Fixed String"}}
3924 {pickaxe_t r1 . "--pickaxe-regex" {mc "Regular Expression"}}
3925 {pickaxe t15 .. "-S*" {mc "Search string:"}}
3926 {datelabel l + {} {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
3927 {since t15 .. {"--since=*" "--after=*"} {mc "Since:"}}
3928 {until t15 . {"--until=*" "--before=*"} {mc "Until:"}}
3929 {limit_lbl l + {} {mc "Limit and/or skip a number of revisions (positive integer):"}}
3930 {limit t10 *. "--max-count=*" {mc "Number to show:"}}
3931 {skip t10 . "--skip=*" {mc "Number to skip:"}}
3932 {misc_lbl l + {} {mc "Miscellaneous options:"}}
3933 {dorder b *. {"--date-order" "-d"} {mc "Strictly sort by date"}}
3934 {lright b . "--left-right" {mc "Mark branch sides"}}
3935 {first b . "--first-parent" {mc "Limit to first parent"}}
3936 {smplhst b . "--simplify-by-decoration" {mc "Simple history"}}
3937 {args t50 *. {} {mc "Additional arguments to git log:"}}
3938 {allpaths path + {} {mc "Enter files and directories to include, one per line:"}}
3939 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3942 # Convert $newviewopts($n, ...) into args for git log.
3943 proc encode_view_opts {n} {
3944 global known_view_options newviewopts
3946 set rargs [list]
3947 foreach opt $known_view_options {
3948 set patterns [lindex $opt 3]
3949 if {$patterns eq {}} continue
3950 set pattern [lindex $patterns 0]
3952 if {[lindex $opt 1] eq "b"} {
3953 set val $newviewopts($n,[lindex $opt 0])
3954 if {$val} {
3955 lappend rargs $pattern
3957 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
3958 regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
3959 set val $newviewopts($n,$button_id)
3960 if {$val eq $value} {
3961 lappend rargs $pattern
3963 } else {
3964 set val $newviewopts($n,[lindex $opt 0])
3965 set val [string trim $val]
3966 if {$val ne {}} {
3967 set pfix [string range $pattern 0 end-1]
3968 lappend rargs $pfix$val
3972 set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
3973 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3976 # Fill $newviewopts($n, ...) based on args for git log.
3977 proc decode_view_opts {n view_args} {
3978 global known_view_options newviewopts
3980 foreach opt $known_view_options {
3981 set id [lindex $opt 0]
3982 if {[lindex $opt 1] eq "b"} {
3983 # Checkboxes
3984 set val 0
3985 } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
3986 # Radiobuttons
3987 regexp {^(.*_)} $id uselessvar id
3988 set val 0
3989 } else {
3990 # Text fields
3991 set val {}
3993 set newviewopts($n,$id) $val
3995 set oargs [list]
3996 set refargs [list]
3997 foreach arg $view_args {
3998 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3999 && ![info exists found(limit)]} {
4000 set newviewopts($n,limit) $cnt
4001 set found(limit) 1
4002 continue
4004 catch { unset val }
4005 foreach opt $known_view_options {
4006 set id [lindex $opt 0]
4007 if {[info exists found($id)]} continue
4008 foreach pattern [lindex $opt 3] {
4009 if {![string match $pattern $arg]} continue
4010 if {[lindex $opt 1] eq "b"} {
4011 # Check buttons
4012 set val 1
4013 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
4014 # Radio buttons
4015 regexp {^(.*_)} $id uselessvar id
4016 set val $num
4017 } else {
4018 # Text input fields
4019 set size [string length $pattern]
4020 set val [string range $arg [expr {$size-1}] end]
4022 set newviewopts($n,$id) $val
4023 set found($id) 1
4024 break
4026 if {[info exists val]} break
4028 if {[info exists val]} continue
4029 if {[regexp {^-} $arg]} {
4030 lappend oargs $arg
4031 } else {
4032 lappend refargs $arg
4035 set newviewopts($n,refs) [shellarglist $refargs]
4036 set newviewopts($n,args) [shellarglist $oargs]
4039 proc edit_or_newview {} {
4040 global curview
4042 if {$curview > 0} {
4043 editview
4044 } else {
4045 newview 0
4049 proc editview {} {
4050 global curview
4051 global viewname viewperm newviewname newviewopts
4052 global viewargs viewargscmd
4054 set top .gitkvedit-$curview
4055 if {[winfo exists $top]} {
4056 raise $top
4057 return
4059 decode_view_opts $curview $viewargs($curview)
4060 set newviewname($curview) $viewname($curview)
4061 set newviewopts($curview,perm) $viewperm($curview)
4062 set newviewopts($curview,cmd) $viewargscmd($curview)
4063 vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
4066 proc vieweditor {top n title} {
4067 global newviewname newviewopts viewfiles bgcolor
4068 global known_view_options NS
4070 ttk_toplevel $top
4071 wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
4072 make_transient $top .
4074 # View name
4075 ${NS}::frame $top.nfr
4076 ${NS}::label $top.nl -text [mc "View Name"]
4077 ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
4078 pack $top.nfr -in $top -fill x -pady 5 -padx 3
4079 pack $top.nl -in $top.nfr -side left -padx {0 5}
4080 pack $top.name -in $top.nfr -side left -padx {0 25}
4082 # View options
4083 set cframe $top.nfr
4084 set cexpand 0
4085 set cnt 0
4086 foreach opt $known_view_options {
4087 set id [lindex $opt 0]
4088 set type [lindex $opt 1]
4089 set flags [lindex $opt 2]
4090 set title [eval [lindex $opt 4]]
4091 set lxpad 0
4093 if {$flags eq "+" || $flags eq "*"} {
4094 set cframe $top.fr$cnt
4095 incr cnt
4096 ${NS}::frame $cframe
4097 pack $cframe -in $top -fill x -pady 3 -padx 3
4098 set cexpand [expr {$flags eq "*"}]
4099 } elseif {$flags eq ".." || $flags eq "*."} {
4100 set cframe $top.fr$cnt
4101 incr cnt
4102 ${NS}::frame $cframe
4103 pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
4104 set cexpand [expr {$flags eq "*."}]
4105 } else {
4106 set lxpad 5
4109 if {$type eq "l"} {
4110 ${NS}::label $cframe.l_$id -text $title
4111 pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
4112 } elseif {$type eq "b"} {
4113 ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
4114 pack $cframe.c_$id -in $cframe -side left \
4115 -padx [list $lxpad 0] -expand $cexpand -anchor w
4116 } elseif {[regexp {^r(\d+)$} $type type sz]} {
4117 regexp {^(.*_)} $id uselessvar button_id
4118 ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
4119 pack $cframe.c_$id -in $cframe -side left \
4120 -padx [list $lxpad 0] -expand $cexpand -anchor w
4121 } elseif {[regexp {^t(\d+)$} $type type sz]} {
4122 ${NS}::label $cframe.l_$id -text $title
4123 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4124 -textvariable newviewopts($n,$id)
4125 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
4126 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
4127 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
4128 ${NS}::label $cframe.l_$id -text $title
4129 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4130 -textvariable newviewopts($n,$id)
4131 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
4132 pack $cframe.e_$id -in $cframe -side top -fill x
4133 } elseif {$type eq "path"} {
4134 ${NS}::label $top.l -text $title
4135 pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
4136 text $top.t -width 40 -height 5 -background $bgcolor
4137 if {[info exists viewfiles($n)]} {
4138 foreach f $viewfiles($n) {
4139 $top.t insert end $f
4140 $top.t insert end "\n"
4142 $top.t delete {end - 1c} end
4143 $top.t mark set insert 0.0
4145 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
4149 ${NS}::frame $top.buts
4150 ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
4151 ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
4152 ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
4153 bind $top <Control-Return> [list newviewok $top $n]
4154 bind $top <F5> [list newviewok $top $n 1]
4155 bind $top <Escape> [list destroy $top]
4156 grid $top.buts.ok $top.buts.apply $top.buts.can
4157 grid columnconfigure $top.buts 0 -weight 1 -uniform a
4158 grid columnconfigure $top.buts 1 -weight 1 -uniform a
4159 grid columnconfigure $top.buts 2 -weight 1 -uniform a
4160 pack $top.buts -in $top -side top -fill x
4161 focus $top.t
4164 proc doviewmenu {m first cmd op argv} {
4165 set nmenu [$m index end]
4166 for {set i $first} {$i <= $nmenu} {incr i} {
4167 if {[$m entrycget $i -command] eq $cmd} {
4168 eval $m $op $i $argv
4169 break
4174 proc allviewmenus {n op args} {
4175 # global viewhlmenu
4177 doviewmenu .bar.view 5 [list showview $n] $op $args
4178 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
4181 proc newviewok {top n {apply 0}} {
4182 global nextviewnum newviewperm newviewname newishighlight
4183 global viewname viewfiles viewperm selectedview curview
4184 global viewargs viewargscmd newviewopts viewhlmenu
4186 if {[catch {
4187 set newargs [encode_view_opts $n]
4188 } err]} {
4189 error_popup "[mc "Error in commit selection arguments:"] $err" $top
4190 return
4192 set files {}
4193 foreach f [split [$top.t get 0.0 end] "\n"] {
4194 set ft [string trim $f]
4195 if {$ft ne {}} {
4196 lappend files $ft
4199 if {![info exists viewfiles($n)]} {
4200 # creating a new view
4201 incr nextviewnum
4202 set viewname($n) $newviewname($n)
4203 set viewperm($n) $newviewopts($n,perm)
4204 set viewfiles($n) $files
4205 set viewargs($n) $newargs
4206 set viewargscmd($n) $newviewopts($n,cmd)
4207 addviewmenu $n
4208 if {!$newishighlight} {
4209 run showview $n
4210 } else {
4211 run addvhighlight $n
4213 } else {
4214 # editing an existing view
4215 set viewperm($n) $newviewopts($n,perm)
4216 if {$newviewname($n) ne $viewname($n)} {
4217 set viewname($n) $newviewname($n)
4218 doviewmenu .bar.view 5 [list showview $n] \
4219 entryconf [list -label $viewname($n)]
4220 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
4221 # entryconf [list -label $viewname($n) -value $viewname($n)]
4223 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
4224 $newviewopts($n,cmd) ne $viewargscmd($n)} {
4225 set viewfiles($n) $files
4226 set viewargs($n) $newargs
4227 set viewargscmd($n) $newviewopts($n,cmd)
4228 if {$curview == $n} {
4229 run reloadcommits
4233 if {$apply} return
4234 catch {destroy $top}
4237 proc delview {} {
4238 global curview viewperm hlview selectedhlview
4240 if {$curview == 0} return
4241 if {[info exists hlview] && $hlview == $curview} {
4242 set selectedhlview [mc "None"]
4243 unset hlview
4245 allviewmenus $curview delete
4246 set viewperm($curview) 0
4247 showview 0
4250 proc addviewmenu {n} {
4251 global viewname viewhlmenu
4253 .bar.view add radiobutton -label $viewname($n) \
4254 -command [list showview $n] -variable selectedview -value $n
4255 #$viewhlmenu add radiobutton -label $viewname($n) \
4256 # -command [list addvhighlight $n] -variable selectedhlview
4259 proc showview {n} {
4260 global curview cached_commitrow ordertok
4261 global displayorder parentlist rowidlist rowisopt rowfinal
4262 global colormap rowtextx nextcolor canvxmax
4263 global numcommits viewcomplete
4264 global selectedline currentid canv canvy0
4265 global treediffs
4266 global pending_select mainheadid
4267 global commitidx
4268 global selectedview
4269 global hlview selectedhlview commitinterest
4271 if {$n == $curview} return
4272 set selid {}
4273 set ymax [lindex [$canv cget -scrollregion] 3]
4274 set span [$canv yview]
4275 set ytop [expr {[lindex $span 0] * $ymax}]
4276 set ybot [expr {[lindex $span 1] * $ymax}]
4277 set yscreen [expr {($ybot - $ytop) / 2}]
4278 if {$selectedline ne {}} {
4279 set selid $currentid
4280 set y [yc $selectedline]
4281 if {$ytop < $y && $y < $ybot} {
4282 set yscreen [expr {$y - $ytop}]
4284 } elseif {[info exists pending_select]} {
4285 set selid $pending_select
4286 unset pending_select
4288 unselectline
4289 normalline
4290 catch {unset treediffs}
4291 clear_display
4292 if {[info exists hlview] && $hlview == $n} {
4293 unset hlview
4294 set selectedhlview [mc "None"]
4296 catch {unset commitinterest}
4297 catch {unset cached_commitrow}
4298 catch {unset ordertok}
4300 set curview $n
4301 set selectedview $n
4302 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
4303 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
4305 run refill_reflist
4306 if {![info exists viewcomplete($n)]} {
4307 getcommits $selid
4308 return
4311 set displayorder {}
4312 set parentlist {}
4313 set rowidlist {}
4314 set rowisopt {}
4315 set rowfinal {}
4316 set numcommits $commitidx($n)
4318 catch {unset colormap}
4319 catch {unset rowtextx}
4320 set nextcolor 0
4321 set canvxmax [$canv cget -width]
4322 set curview $n
4323 set row 0
4324 setcanvscroll
4325 set yf 0
4326 set row {}
4327 if {$selid ne {} && [commitinview $selid $n]} {
4328 set row [rowofcommit $selid]
4329 # try to get the selected row in the same position on the screen
4330 set ymax [lindex [$canv cget -scrollregion] 3]
4331 set ytop [expr {[yc $row] - $yscreen}]
4332 if {$ytop < 0} {
4333 set ytop 0
4335 set yf [expr {$ytop * 1.0 / $ymax}]
4337 allcanvs yview moveto $yf
4338 drawvisible
4339 if {$row ne {}} {
4340 selectline $row 0
4341 } elseif {!$viewcomplete($n)} {
4342 reset_pending_select $selid
4343 } else {
4344 reset_pending_select {}
4346 if {[commitinview $pending_select $curview]} {
4347 selectline [rowofcommit $pending_select] 1
4348 } else {
4349 set row [first_real_row]
4350 if {$row < $numcommits} {
4351 selectline $row 0
4355 if {!$viewcomplete($n)} {
4356 if {$numcommits == 0} {
4357 show_status [mc "Reading commits..."]
4359 } elseif {$numcommits == 0} {
4360 show_status [mc "No commits selected"]
4364 # Stuff relating to the highlighting facility
4366 proc ishighlighted {id} {
4367 global vhighlights fhighlights nhighlights rhighlights
4369 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
4370 return $nhighlights($id)
4372 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
4373 return $vhighlights($id)
4375 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
4376 return $fhighlights($id)
4378 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4379 return $rhighlights($id)
4381 return 0
4384 proc bolden {id font} {
4385 global canv linehtag currentid boldids need_redisplay markedid
4387 # need_redisplay = 1 means the display is stale and about to be redrawn
4388 if {$need_redisplay} return
4389 lappend boldids $id
4390 $canv itemconf $linehtag($id) -font $font
4391 if {[info exists currentid] && $id eq $currentid} {
4392 $canv delete secsel
4393 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4394 -outline {{}} -tags secsel \
4395 -fill [$canv cget -selectbackground]]
4396 $canv lower $t
4398 if {[info exists markedid] && $id eq $markedid} {
4399 make_idmark $id
4403 proc bolden_name {id font} {
4404 global canv2 linentag currentid boldnameids need_redisplay
4406 if {$need_redisplay} return
4407 lappend boldnameids $id
4408 $canv2 itemconf $linentag($id) -font $font
4409 if {[info exists currentid] && $id eq $currentid} {
4410 $canv2 delete secsel
4411 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4412 -outline {{}} -tags secsel \
4413 -fill [$canv2 cget -selectbackground]]
4414 $canv2 lower $t
4418 proc unbolden {} {
4419 global boldids
4421 set stillbold {}
4422 foreach id $boldids {
4423 if {![ishighlighted $id]} {
4424 bolden $id mainfont
4425 } else {
4426 lappend stillbold $id
4429 set boldids $stillbold
4432 proc addvhighlight {n} {
4433 global hlview viewcomplete curview vhl_done commitidx
4435 if {[info exists hlview]} {
4436 delvhighlight
4438 set hlview $n
4439 if {$n != $curview && ![info exists viewcomplete($n)]} {
4440 start_rev_list $n
4442 set vhl_done $commitidx($hlview)
4443 if {$vhl_done > 0} {
4444 drawvisible
4448 proc delvhighlight {} {
4449 global hlview vhighlights
4451 if {![info exists hlview]} return
4452 unset hlview
4453 catch {unset vhighlights}
4454 unbolden
4457 proc vhighlightmore {} {
4458 global hlview vhl_done commitidx vhighlights curview
4460 set max $commitidx($hlview)
4461 set vr [visiblerows]
4462 set r0 [lindex $vr 0]
4463 set r1 [lindex $vr 1]
4464 for {set i $vhl_done} {$i < $max} {incr i} {
4465 set id [commitonrow $i $hlview]
4466 if {[commitinview $id $curview]} {
4467 set row [rowofcommit $id]
4468 if {$r0 <= $row && $row <= $r1} {
4469 if {![highlighted $row]} {
4470 bolden $id mainfontbold
4472 set vhighlights($id) 1
4476 set vhl_done $max
4477 return 0
4480 proc askvhighlight {row id} {
4481 global hlview vhighlights iddrawn
4483 if {[commitinview $id $hlview]} {
4484 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4485 bolden $id mainfontbold
4487 set vhighlights($id) 1
4488 } else {
4489 set vhighlights($id) 0
4493 proc hfiles_change {} {
4494 global highlight_files filehighlight fhighlights fh_serial
4495 global highlight_paths
4497 if {[info exists filehighlight]} {
4498 # delete previous highlights
4499 catch {close $filehighlight}
4500 unset filehighlight
4501 catch {unset fhighlights}
4502 unbolden
4503 unhighlight_filelist
4505 set highlight_paths {}
4506 after cancel do_file_hl $fh_serial
4507 incr fh_serial
4508 if {$highlight_files ne {}} {
4509 after 300 do_file_hl $fh_serial
4513 proc gdttype_change {name ix op} {
4514 global gdttype highlight_files findstring findpattern
4516 stopfinding
4517 if {$findstring ne {}} {
4518 if {$gdttype eq [mc "containing:"]} {
4519 if {$highlight_files ne {}} {
4520 set highlight_files {}
4521 hfiles_change
4523 findcom_change
4524 } else {
4525 if {$findpattern ne {}} {
4526 set findpattern {}
4527 findcom_change
4529 set highlight_files $findstring
4530 hfiles_change
4532 drawvisible
4534 # enable/disable findtype/findloc menus too
4537 proc find_change {name ix op} {
4538 global gdttype findstring highlight_files
4540 stopfinding
4541 if {$gdttype eq [mc "containing:"]} {
4542 findcom_change
4543 } else {
4544 if {$highlight_files ne $findstring} {
4545 set highlight_files $findstring
4546 hfiles_change
4549 drawvisible
4552 proc findcom_change args {
4553 global nhighlights boldnameids
4554 global findpattern findtype findstring gdttype
4556 stopfinding
4557 # delete previous highlights, if any
4558 foreach id $boldnameids {
4559 bolden_name $id mainfont
4561 set boldnameids {}
4562 catch {unset nhighlights}
4563 unbolden
4564 unmarkmatches
4565 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4566 set findpattern {}
4567 } elseif {$findtype eq [mc "Regexp"]} {
4568 set findpattern $findstring
4569 } else {
4570 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4571 $findstring]
4572 set findpattern "*$e*"
4576 proc makepatterns {l} {
4577 set ret {}
4578 foreach e $l {
4579 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4580 if {[string index $ee end] eq "/"} {
4581 lappend ret "$ee*"
4582 } else {
4583 lappend ret $ee
4584 lappend ret "$ee/*"
4587 return $ret
4590 proc do_file_hl {serial} {
4591 global highlight_files filehighlight highlight_paths gdttype fhl_list
4592 global cdup findtype
4594 if {$gdttype eq [mc "touching paths:"]} {
4595 # If "exact" match then convert backslashes to forward slashes.
4596 # Most useful to support Windows-flavoured file paths.
4597 if {$findtype eq [mc "Exact"]} {
4598 set highlight_files [string map {"\\" "/"} $highlight_files]
4600 if {[catch {set paths [shellsplit $highlight_files]}]} return
4601 set highlight_paths [makepatterns $paths]
4602 highlight_filelist
4603 set relative_paths {}
4604 foreach path $paths {
4605 lappend relative_paths [file join $cdup $path]
4607 set gdtargs [concat -- $relative_paths]
4608 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4609 set gdtargs [list "-S$highlight_files"]
4610 } else {
4611 # must be "containing:", i.e. we're searching commit info
4612 return
4614 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4615 set filehighlight [open $cmd r+]
4616 fconfigure $filehighlight -blocking 0
4617 filerun $filehighlight readfhighlight
4618 set fhl_list {}
4619 drawvisible
4620 flushhighlights
4623 proc flushhighlights {} {
4624 global filehighlight fhl_list
4626 if {[info exists filehighlight]} {
4627 lappend fhl_list {}
4628 puts $filehighlight ""
4629 flush $filehighlight
4633 proc askfilehighlight {row id} {
4634 global filehighlight fhighlights fhl_list
4636 lappend fhl_list $id
4637 set fhighlights($id) -1
4638 puts $filehighlight $id
4641 proc readfhighlight {} {
4642 global filehighlight fhighlights curview iddrawn
4643 global fhl_list find_dirn
4645 if {![info exists filehighlight]} {
4646 return 0
4648 set nr 0
4649 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4650 set line [string trim $line]
4651 set i [lsearch -exact $fhl_list $line]
4652 if {$i < 0} continue
4653 for {set j 0} {$j < $i} {incr j} {
4654 set id [lindex $fhl_list $j]
4655 set fhighlights($id) 0
4657 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4658 if {$line eq {}} continue
4659 if {![commitinview $line $curview]} continue
4660 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4661 bolden $line mainfontbold
4663 set fhighlights($line) 1
4665 if {[eof $filehighlight]} {
4666 # strange...
4667 puts "oops, git diff-tree died"
4668 catch {close $filehighlight}
4669 unset filehighlight
4670 return 0
4672 if {[info exists find_dirn]} {
4673 run findmore
4675 return 1
4678 proc doesmatch {f} {
4679 global findtype findpattern
4681 if {$findtype eq [mc "Regexp"]} {
4682 return [regexp $findpattern $f]
4683 } elseif {$findtype eq [mc "IgnCase"]} {
4684 return [string match -nocase $findpattern $f]
4685 } else {
4686 return [string match $findpattern $f]
4690 proc askfindhighlight {row id} {
4691 global nhighlights commitinfo iddrawn
4692 global findloc
4693 global markingmatches
4695 if {![info exists commitinfo($id)]} {
4696 getcommit $id
4698 set info $commitinfo($id)
4699 set isbold 0
4700 set fldtypes [list [mc Headline] [mc Author] "" [mc Committer] "" [mc Comments]]
4701 foreach f $info ty $fldtypes {
4702 if {$ty eq ""} continue
4703 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4704 [doesmatch $f]} {
4705 if {$ty eq [mc "Author"]} {
4706 set isbold 2
4707 break
4709 set isbold 1
4712 if {$isbold && [info exists iddrawn($id)]} {
4713 if {![ishighlighted $id]} {
4714 bolden $id mainfontbold
4715 if {$isbold > 1} {
4716 bolden_name $id mainfontbold
4719 if {$markingmatches} {
4720 markrowmatches $row $id
4723 set nhighlights($id) $isbold
4726 proc markrowmatches {row id} {
4727 global canv canv2 linehtag linentag commitinfo findloc
4729 set headline [lindex $commitinfo($id) 0]
4730 set author [lindex $commitinfo($id) 1]
4731 $canv delete match$row
4732 $canv2 delete match$row
4733 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4734 set m [findmatches $headline]
4735 if {$m ne {}} {
4736 markmatches $canv $row $headline $linehtag($id) $m \
4737 [$canv itemcget $linehtag($id) -font] $row
4740 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4741 set m [findmatches $author]
4742 if {$m ne {}} {
4743 markmatches $canv2 $row $author $linentag($id) $m \
4744 [$canv2 itemcget $linentag($id) -font] $row
4749 proc vrel_change {name ix op} {
4750 global highlight_related
4752 rhighlight_none
4753 if {$highlight_related ne [mc "None"]} {
4754 run drawvisible
4758 # prepare for testing whether commits are descendents or ancestors of a
4759 proc rhighlight_sel {a} {
4760 global descendent desc_todo ancestor anc_todo
4761 global highlight_related
4763 catch {unset descendent}
4764 set desc_todo [list $a]
4765 catch {unset ancestor}
4766 set anc_todo [list $a]
4767 if {$highlight_related ne [mc "None"]} {
4768 rhighlight_none
4769 run drawvisible
4773 proc rhighlight_none {} {
4774 global rhighlights
4776 catch {unset rhighlights}
4777 unbolden
4780 proc is_descendent {a} {
4781 global curview children descendent desc_todo
4783 set v $curview
4784 set la [rowofcommit $a]
4785 set todo $desc_todo
4786 set leftover {}
4787 set done 0
4788 for {set i 0} {$i < [llength $todo]} {incr i} {
4789 set do [lindex $todo $i]
4790 if {[rowofcommit $do] < $la} {
4791 lappend leftover $do
4792 continue
4794 foreach nk $children($v,$do) {
4795 if {![info exists descendent($nk)]} {
4796 set descendent($nk) 1
4797 lappend todo $nk
4798 if {$nk eq $a} {
4799 set done 1
4803 if {$done} {
4804 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4805 return
4808 set descendent($a) 0
4809 set desc_todo $leftover
4812 proc is_ancestor {a} {
4813 global curview parents ancestor anc_todo
4815 set v $curview
4816 set la [rowofcommit $a]
4817 set todo $anc_todo
4818 set leftover {}
4819 set done 0
4820 for {set i 0} {$i < [llength $todo]} {incr i} {
4821 set do [lindex $todo $i]
4822 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4823 lappend leftover $do
4824 continue
4826 foreach np $parents($v,$do) {
4827 if {![info exists ancestor($np)]} {
4828 set ancestor($np) 1
4829 lappend todo $np
4830 if {$np eq $a} {
4831 set done 1
4835 if {$done} {
4836 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4837 return
4840 set ancestor($a) 0
4841 set anc_todo $leftover
4844 proc askrelhighlight {row id} {
4845 global descendent highlight_related iddrawn rhighlights
4846 global selectedline ancestor
4848 if {$selectedline eq {}} return
4849 set isbold 0
4850 if {$highlight_related eq [mc "Descendant"] ||
4851 $highlight_related eq [mc "Not descendant"]} {
4852 if {![info exists descendent($id)]} {
4853 is_descendent $id
4855 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4856 set isbold 1
4858 } elseif {$highlight_related eq [mc "Ancestor"] ||
4859 $highlight_related eq [mc "Not ancestor"]} {
4860 if {![info exists ancestor($id)]} {
4861 is_ancestor $id
4863 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4864 set isbold 1
4867 if {[info exists iddrawn($id)]} {
4868 if {$isbold && ![ishighlighted $id]} {
4869 bolden $id mainfontbold
4872 set rhighlights($id) $isbold
4875 # Graph layout functions
4877 proc shortids {ids} {
4878 set res {}
4879 foreach id $ids {
4880 if {[llength $id] > 1} {
4881 lappend res [shortids $id]
4882 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4883 lappend res [string range $id 0 7]
4884 } else {
4885 lappend res $id
4888 return $res
4891 proc ntimes {n o} {
4892 set ret {}
4893 set o [list $o]
4894 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4895 if {($n & $mask) != 0} {
4896 set ret [concat $ret $o]
4898 set o [concat $o $o]
4900 return $ret
4903 proc ordertoken {id} {
4904 global ordertok curview varcid varcstart varctok curview parents children
4905 global nullid nullid2
4907 if {[info exists ordertok($id)]} {
4908 return $ordertok($id)
4910 set origid $id
4911 set todo {}
4912 while {1} {
4913 if {[info exists varcid($curview,$id)]} {
4914 set a $varcid($curview,$id)
4915 set p [lindex $varcstart($curview) $a]
4916 } else {
4917 set p [lindex $children($curview,$id) 0]
4919 if {[info exists ordertok($p)]} {
4920 set tok $ordertok($p)
4921 break
4923 set id [first_real_child $curview,$p]
4924 if {$id eq {}} {
4925 # it's a root
4926 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4927 break
4929 if {[llength $parents($curview,$id)] == 1} {
4930 lappend todo [list $p {}]
4931 } else {
4932 set j [lsearch -exact $parents($curview,$id) $p]
4933 if {$j < 0} {
4934 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4936 lappend todo [list $p [strrep $j]]
4939 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4940 set p [lindex $todo $i 0]
4941 append tok [lindex $todo $i 1]
4942 set ordertok($p) $tok
4944 set ordertok($origid) $tok
4945 return $tok
4948 # Work out where id should go in idlist so that order-token
4949 # values increase from left to right
4950 proc idcol {idlist id {i 0}} {
4951 set t [ordertoken $id]
4952 if {$i < 0} {
4953 set i 0
4955 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4956 if {$i > [llength $idlist]} {
4957 set i [llength $idlist]
4959 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4960 incr i
4961 } else {
4962 if {$t > [ordertoken [lindex $idlist $i]]} {
4963 while {[incr i] < [llength $idlist] &&
4964 $t >= [ordertoken [lindex $idlist $i]]} {}
4967 return $i
4970 proc initlayout {} {
4971 global rowidlist rowisopt rowfinal displayorder parentlist
4972 global numcommits canvxmax canv
4973 global nextcolor
4974 global colormap rowtextx
4976 set numcommits 0
4977 set displayorder {}
4978 set parentlist {}
4979 set nextcolor 0
4980 set rowidlist {}
4981 set rowisopt {}
4982 set rowfinal {}
4983 set canvxmax [$canv cget -width]
4984 catch {unset colormap}
4985 catch {unset rowtextx}
4986 setcanvscroll
4989 proc setcanvscroll {} {
4990 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4991 global lastscrollset lastscrollrows
4993 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4994 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4995 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4996 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4997 set lastscrollset [clock clicks -milliseconds]
4998 set lastscrollrows $numcommits
5001 proc visiblerows {} {
5002 global canv numcommits linespc
5004 set ymax [lindex [$canv cget -scrollregion] 3]
5005 if {$ymax eq {} || $ymax == 0} return
5006 set f [$canv yview]
5007 set y0 [expr {int([lindex $f 0] * $ymax)}]
5008 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
5009 if {$r0 < 0} {
5010 set r0 0
5012 set y1 [expr {int([lindex $f 1] * $ymax)}]
5013 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
5014 if {$r1 >= $numcommits} {
5015 set r1 [expr {$numcommits - 1}]
5017 return [list $r0 $r1]
5020 proc layoutmore {} {
5021 global commitidx viewcomplete curview
5022 global numcommits pending_select curview
5023 global lastscrollset lastscrollrows
5025 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
5026 [clock clicks -milliseconds] - $lastscrollset > 500} {
5027 setcanvscroll
5029 if {[info exists pending_select] &&
5030 [commitinview $pending_select $curview]} {
5031 update
5032 selectline [rowofcommit $pending_select] 1
5034 drawvisible
5037 # With path limiting, we mightn't get the actual HEAD commit,
5038 # so ask git rev-list what is the first ancestor of HEAD that
5039 # touches a file in the path limit.
5040 proc get_viewmainhead {view} {
5041 global viewmainheadid vfilelimit viewinstances mainheadid
5043 catch {
5044 set rfd [open [concat | git rev-list -1 $mainheadid \
5045 -- $vfilelimit($view)] r]
5046 set j [reg_instance $rfd]
5047 lappend viewinstances($view) $j
5048 fconfigure $rfd -blocking 0
5049 filerun $rfd [list getviewhead $rfd $j $view]
5050 set viewmainheadid($curview) {}
5054 # git rev-list should give us just 1 line to use as viewmainheadid($view)
5055 proc getviewhead {fd inst view} {
5056 global viewmainheadid commfd curview viewinstances showlocalchanges
5058 set id {}
5059 if {[gets $fd line] < 0} {
5060 if {![eof $fd]} {
5061 return 1
5063 } elseif {[string length $line] == 40 && [string is xdigit $line]} {
5064 set id $line
5066 set viewmainheadid($view) $id
5067 close $fd
5068 unset commfd($inst)
5069 set i [lsearch -exact $viewinstances($view) $inst]
5070 if {$i >= 0} {
5071 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
5073 if {$showlocalchanges && $id ne {} && $view == $curview} {
5074 doshowlocalchanges
5076 return 0
5079 proc doshowlocalchanges {} {
5080 global curview viewmainheadid
5082 if {$viewmainheadid($curview) eq {}} return
5083 if {[commitinview $viewmainheadid($curview) $curview]} {
5084 dodiffindex
5085 } else {
5086 interestedin $viewmainheadid($curview) dodiffindex
5090 proc dohidelocalchanges {} {
5091 global nullid nullid2 lserial curview
5093 if {[commitinview $nullid $curview]} {
5094 removefakerow $nullid
5096 if {[commitinview $nullid2 $curview]} {
5097 removefakerow $nullid2
5099 incr lserial
5102 # spawn off a process to do git diff-index --cached HEAD
5103 proc dodiffindex {} {
5104 global lserial showlocalchanges vfilelimit curview
5105 global hasworktree
5107 if {!$showlocalchanges || !$hasworktree} return
5108 incr lserial
5109 set cmd "|git diff-index --cached HEAD"
5110 if {$vfilelimit($curview) ne {}} {
5111 set cmd [concat $cmd -- $vfilelimit($curview)]
5113 set fd [open $cmd r]
5114 fconfigure $fd -blocking 0
5115 set i [reg_instance $fd]
5116 filerun $fd [list readdiffindex $fd $lserial $i]
5119 proc readdiffindex {fd serial inst} {
5120 global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial
5121 global vfilelimit
5123 set isdiff 1
5124 if {[gets $fd line] < 0} {
5125 if {![eof $fd]} {
5126 return 1
5128 set isdiff 0
5130 # we only need to see one line and we don't really care what it says...
5131 stop_instance $inst
5133 if {$serial != $lserial} {
5134 return 0
5137 # now see if there are any local changes not checked in to the index
5138 set cmd "|git diff-files"
5139 if {$vfilelimit($curview) ne {}} {
5140 set cmd [concat $cmd -- $vfilelimit($curview)]
5142 set fd [open $cmd r]
5143 fconfigure $fd -blocking 0
5144 set i [reg_instance $fd]
5145 filerun $fd [list readdifffiles $fd $serial $i]
5147 if {$isdiff && ![commitinview $nullid2 $curview]} {
5148 # add the line for the changes in the index to the graph
5149 set hl [mc "Local changes checked in to index but not committed"]
5150 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
5151 set commitdata($nullid2) "\n $hl\n"
5152 if {[commitinview $nullid $curview]} {
5153 removefakerow $nullid
5155 insertfakerow $nullid2 $viewmainheadid($curview)
5156 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
5157 if {[commitinview $nullid $curview]} {
5158 removefakerow $nullid
5160 removefakerow $nullid2
5162 return 0
5165 proc readdifffiles {fd serial inst} {
5166 global viewmainheadid nullid nullid2 curview
5167 global commitinfo commitdata lserial
5169 set isdiff 1
5170 if {[gets $fd line] < 0} {
5171 if {![eof $fd]} {
5172 return 1
5174 set isdiff 0
5176 # we only need to see one line and we don't really care what it says...
5177 stop_instance $inst
5179 if {$serial != $lserial} {
5180 return 0
5183 if {$isdiff && ![commitinview $nullid $curview]} {
5184 # add the line for the local diff to the graph
5185 set hl [mc "Local uncommitted changes, not checked in to index"]
5186 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
5187 set commitdata($nullid) "\n $hl\n"
5188 if {[commitinview $nullid2 $curview]} {
5189 set p $nullid2
5190 } else {
5191 set p $viewmainheadid($curview)
5193 insertfakerow $nullid $p
5194 } elseif {!$isdiff && [commitinview $nullid $curview]} {
5195 removefakerow $nullid
5197 return 0
5200 proc nextuse {id row} {
5201 global curview children
5203 if {[info exists children($curview,$id)]} {
5204 foreach kid $children($curview,$id) {
5205 if {![commitinview $kid $curview]} {
5206 return -1
5208 if {[rowofcommit $kid] > $row} {
5209 return [rowofcommit $kid]
5213 if {[commitinview $id $curview]} {
5214 return [rowofcommit $id]
5216 return -1
5219 proc prevuse {id row} {
5220 global curview children
5222 set ret -1
5223 if {[info exists children($curview,$id)]} {
5224 foreach kid $children($curview,$id) {
5225 if {![commitinview $kid $curview]} break
5226 if {[rowofcommit $kid] < $row} {
5227 set ret [rowofcommit $kid]
5231 return $ret
5234 proc make_idlist {row} {
5235 global displayorder parentlist uparrowlen downarrowlen mingaplen
5236 global commitidx curview children
5238 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
5239 if {$r < 0} {
5240 set r 0
5242 set ra [expr {$row - $downarrowlen}]
5243 if {$ra < 0} {
5244 set ra 0
5246 set rb [expr {$row + $uparrowlen}]
5247 if {$rb > $commitidx($curview)} {
5248 set rb $commitidx($curview)
5250 make_disporder $r [expr {$rb + 1}]
5251 set ids {}
5252 for {} {$r < $ra} {incr r} {
5253 set nextid [lindex $displayorder [expr {$r + 1}]]
5254 foreach p [lindex $parentlist $r] {
5255 if {$p eq $nextid} continue
5256 set rn [nextuse $p $r]
5257 if {$rn >= $row &&
5258 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
5259 lappend ids [list [ordertoken $p] $p]
5263 for {} {$r < $row} {incr r} {
5264 set nextid [lindex $displayorder [expr {$r + 1}]]
5265 foreach p [lindex $parentlist $r] {
5266 if {$p eq $nextid} continue
5267 set rn [nextuse $p $r]
5268 if {$rn < 0 || $rn >= $row} {
5269 lappend ids [list [ordertoken $p] $p]
5273 set id [lindex $displayorder $row]
5274 lappend ids [list [ordertoken $id] $id]
5275 while {$r < $rb} {
5276 foreach p [lindex $parentlist $r] {
5277 set firstkid [lindex $children($curview,$p) 0]
5278 if {[rowofcommit $firstkid] < $row} {
5279 lappend ids [list [ordertoken $p] $p]
5282 incr r
5283 set id [lindex $displayorder $r]
5284 if {$id ne {}} {
5285 set firstkid [lindex $children($curview,$id) 0]
5286 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
5287 lappend ids [list [ordertoken $id] $id]
5291 set idlist {}
5292 foreach idx [lsort -unique $ids] {
5293 lappend idlist [lindex $idx 1]
5295 return $idlist
5298 proc rowsequal {a b} {
5299 while {[set i [lsearch -exact $a {}]] >= 0} {
5300 set a [lreplace $a $i $i]
5302 while {[set i [lsearch -exact $b {}]] >= 0} {
5303 set b [lreplace $b $i $i]
5305 return [expr {$a eq $b}]
5308 proc makeupline {id row rend col} {
5309 global rowidlist uparrowlen downarrowlen mingaplen
5311 for {set r $rend} {1} {set r $rstart} {
5312 set rstart [prevuse $id $r]
5313 if {$rstart < 0} return
5314 if {$rstart < $row} break
5316 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
5317 set rstart [expr {$rend - $uparrowlen - 1}]
5319 for {set r $rstart} {[incr r] <= $row} {} {
5320 set idlist [lindex $rowidlist $r]
5321 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
5322 set col [idcol $idlist $id $col]
5323 lset rowidlist $r [linsert $idlist $col $id]
5324 changedrow $r
5329 proc layoutrows {row endrow} {
5330 global rowidlist rowisopt rowfinal displayorder
5331 global uparrowlen downarrowlen maxwidth mingaplen
5332 global children parentlist
5333 global commitidx viewcomplete curview
5335 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
5336 set idlist {}
5337 if {$row > 0} {
5338 set rm1 [expr {$row - 1}]
5339 foreach id [lindex $rowidlist $rm1] {
5340 if {$id ne {}} {
5341 lappend idlist $id
5344 set final [lindex $rowfinal $rm1]
5346 for {} {$row < $endrow} {incr row} {
5347 set rm1 [expr {$row - 1}]
5348 if {$rm1 < 0 || $idlist eq {}} {
5349 set idlist [make_idlist $row]
5350 set final 1
5351 } else {
5352 set id [lindex $displayorder $rm1]
5353 set col [lsearch -exact $idlist $id]
5354 set idlist [lreplace $idlist $col $col]
5355 foreach p [lindex $parentlist $rm1] {
5356 if {[lsearch -exact $idlist $p] < 0} {
5357 set col [idcol $idlist $p $col]
5358 set idlist [linsert $idlist $col $p]
5359 # if not the first child, we have to insert a line going up
5360 if {$id ne [lindex $children($curview,$p) 0]} {
5361 makeupline $p $rm1 $row $col
5365 set id [lindex $displayorder $row]
5366 if {$row > $downarrowlen} {
5367 set termrow [expr {$row - $downarrowlen - 1}]
5368 foreach p [lindex $parentlist $termrow] {
5369 set i [lsearch -exact $idlist $p]
5370 if {$i < 0} continue
5371 set nr [nextuse $p $termrow]
5372 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
5373 set idlist [lreplace $idlist $i $i]
5377 set col [lsearch -exact $idlist $id]
5378 if {$col < 0} {
5379 set col [idcol $idlist $id]
5380 set idlist [linsert $idlist $col $id]
5381 if {$children($curview,$id) ne {}} {
5382 makeupline $id $rm1 $row $col
5385 set r [expr {$row + $uparrowlen - 1}]
5386 if {$r < $commitidx($curview)} {
5387 set x $col
5388 foreach p [lindex $parentlist $r] {
5389 if {[lsearch -exact $idlist $p] >= 0} continue
5390 set fk [lindex $children($curview,$p) 0]
5391 if {[rowofcommit $fk] < $row} {
5392 set x [idcol $idlist $p $x]
5393 set idlist [linsert $idlist $x $p]
5396 if {[incr r] < $commitidx($curview)} {
5397 set p [lindex $displayorder $r]
5398 if {[lsearch -exact $idlist $p] < 0} {
5399 set fk [lindex $children($curview,$p) 0]
5400 if {$fk ne {} && [rowofcommit $fk] < $row} {
5401 set x [idcol $idlist $p $x]
5402 set idlist [linsert $idlist $x $p]
5408 if {$final && !$viewcomplete($curview) &&
5409 $row + $uparrowlen + $mingaplen + $downarrowlen
5410 >= $commitidx($curview)} {
5411 set final 0
5413 set l [llength $rowidlist]
5414 if {$row == $l} {
5415 lappend rowidlist $idlist
5416 lappend rowisopt 0
5417 lappend rowfinal $final
5418 } elseif {$row < $l} {
5419 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
5420 lset rowidlist $row $idlist
5421 changedrow $row
5423 lset rowfinal $row $final
5424 } else {
5425 set pad [ntimes [expr {$row - $l}] {}]
5426 set rowidlist [concat $rowidlist $pad]
5427 lappend rowidlist $idlist
5428 set rowfinal [concat $rowfinal $pad]
5429 lappend rowfinal $final
5430 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
5433 return $row
5436 proc changedrow {row} {
5437 global displayorder iddrawn rowisopt need_redisplay
5439 set l [llength $rowisopt]
5440 if {$row < $l} {
5441 lset rowisopt $row 0
5442 if {$row + 1 < $l} {
5443 lset rowisopt [expr {$row + 1}] 0
5444 if {$row + 2 < $l} {
5445 lset rowisopt [expr {$row + 2}] 0
5449 set id [lindex $displayorder $row]
5450 if {[info exists iddrawn($id)]} {
5451 set need_redisplay 1
5455 proc insert_pad {row col npad} {
5456 global rowidlist
5458 set pad [ntimes $npad {}]
5459 set idlist [lindex $rowidlist $row]
5460 set bef [lrange $idlist 0 [expr {$col - 1}]]
5461 set aft [lrange $idlist $col end]
5462 set i [lsearch -exact $aft {}]
5463 if {$i > 0} {
5464 set aft [lreplace $aft $i $i]
5466 lset rowidlist $row [concat $bef $pad $aft]
5467 changedrow $row
5470 proc optimize_rows {row col endrow} {
5471 global rowidlist rowisopt displayorder curview children
5473 if {$row < 1} {
5474 set row 1
5476 for {} {$row < $endrow} {incr row; set col 0} {
5477 if {[lindex $rowisopt $row]} continue
5478 set haspad 0
5479 set y0 [expr {$row - 1}]
5480 set ym [expr {$row - 2}]
5481 set idlist [lindex $rowidlist $row]
5482 set previdlist [lindex $rowidlist $y0]
5483 if {$idlist eq {} || $previdlist eq {}} continue
5484 if {$ym >= 0} {
5485 set pprevidlist [lindex $rowidlist $ym]
5486 if {$pprevidlist eq {}} continue
5487 } else {
5488 set pprevidlist {}
5490 set x0 -1
5491 set xm -1
5492 for {} {$col < [llength $idlist]} {incr col} {
5493 set id [lindex $idlist $col]
5494 if {[lindex $previdlist $col] eq $id} continue
5495 if {$id eq {}} {
5496 set haspad 1
5497 continue
5499 set x0 [lsearch -exact $previdlist $id]
5500 if {$x0 < 0} continue
5501 set z [expr {$x0 - $col}]
5502 set isarrow 0
5503 set z0 {}
5504 if {$ym >= 0} {
5505 set xm [lsearch -exact $pprevidlist $id]
5506 if {$xm >= 0} {
5507 set z0 [expr {$xm - $x0}]
5510 if {$z0 eq {}} {
5511 # if row y0 is the first child of $id then it's not an arrow
5512 if {[lindex $children($curview,$id) 0] ne
5513 [lindex $displayorder $y0]} {
5514 set isarrow 1
5517 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5518 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5519 set isarrow 1
5521 # Looking at lines from this row to the previous row,
5522 # make them go straight up if they end in an arrow on
5523 # the previous row; otherwise make them go straight up
5524 # or at 45 degrees.
5525 if {$z < -1 || ($z < 0 && $isarrow)} {
5526 # Line currently goes left too much;
5527 # insert pads in the previous row, then optimize it
5528 set npad [expr {-1 - $z + $isarrow}]
5529 insert_pad $y0 $x0 $npad
5530 if {$y0 > 0} {
5531 optimize_rows $y0 $x0 $row
5533 set previdlist [lindex $rowidlist $y0]
5534 set x0 [lsearch -exact $previdlist $id]
5535 set z [expr {$x0 - $col}]
5536 if {$z0 ne {}} {
5537 set pprevidlist [lindex $rowidlist $ym]
5538 set xm [lsearch -exact $pprevidlist $id]
5539 set z0 [expr {$xm - $x0}]
5541 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5542 # Line currently goes right too much;
5543 # insert pads in this line
5544 set npad [expr {$z - 1 + $isarrow}]
5545 insert_pad $row $col $npad
5546 set idlist [lindex $rowidlist $row]
5547 incr col $npad
5548 set z [expr {$x0 - $col}]
5549 set haspad 1
5551 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5552 # this line links to its first child on row $row-2
5553 set id [lindex $displayorder $ym]
5554 set xc [lsearch -exact $pprevidlist $id]
5555 if {$xc >= 0} {
5556 set z0 [expr {$xc - $x0}]
5559 # avoid lines jigging left then immediately right
5560 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5561 insert_pad $y0 $x0 1
5562 incr x0
5563 optimize_rows $y0 $x0 $row
5564 set previdlist [lindex $rowidlist $y0]
5567 if {!$haspad} {
5568 # Find the first column that doesn't have a line going right
5569 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5570 set id [lindex $idlist $col]
5571 if {$id eq {}} break
5572 set x0 [lsearch -exact $previdlist $id]
5573 if {$x0 < 0} {
5574 # check if this is the link to the first child
5575 set kid [lindex $displayorder $y0]
5576 if {[lindex $children($curview,$id) 0] eq $kid} {
5577 # it is, work out offset to child
5578 set x0 [lsearch -exact $previdlist $kid]
5581 if {$x0 <= $col} break
5583 # Insert a pad at that column as long as it has a line and
5584 # isn't the last column
5585 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5586 set idlist [linsert $idlist $col {}]
5587 lset rowidlist $row $idlist
5588 changedrow $row
5594 proc xc {row col} {
5595 global canvx0 linespc
5596 return [expr {$canvx0 + $col * $linespc}]
5599 proc yc {row} {
5600 global canvy0 linespc
5601 return [expr {$canvy0 + $row * $linespc}]
5604 proc linewidth {id} {
5605 global thickerline lthickness
5607 set wid $lthickness
5608 if {[info exists thickerline] && $id eq $thickerline} {
5609 set wid [expr {2 * $lthickness}]
5611 return $wid
5614 proc rowranges {id} {
5615 global curview children uparrowlen downarrowlen
5616 global rowidlist
5618 set kids $children($curview,$id)
5619 if {$kids eq {}} {
5620 return {}
5622 set ret {}
5623 lappend kids $id
5624 foreach child $kids {
5625 if {![commitinview $child $curview]} break
5626 set row [rowofcommit $child]
5627 if {![info exists prev]} {
5628 lappend ret [expr {$row + 1}]
5629 } else {
5630 if {$row <= $prevrow} {
5631 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5633 # see if the line extends the whole way from prevrow to row
5634 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5635 [lsearch -exact [lindex $rowidlist \
5636 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5637 # it doesn't, see where it ends
5638 set r [expr {$prevrow + $downarrowlen}]
5639 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5640 while {[incr r -1] > $prevrow &&
5641 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5642 } else {
5643 while {[incr r] <= $row &&
5644 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5645 incr r -1
5647 lappend ret $r
5648 # see where it starts up again
5649 set r [expr {$row - $uparrowlen}]
5650 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5651 while {[incr r] < $row &&
5652 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5653 } else {
5654 while {[incr r -1] >= $prevrow &&
5655 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5656 incr r
5658 lappend ret $r
5661 if {$child eq $id} {
5662 lappend ret $row
5664 set prev $child
5665 set prevrow $row
5667 return $ret
5670 proc drawlineseg {id row endrow arrowlow} {
5671 global rowidlist displayorder iddrawn linesegs
5672 global canv colormap linespc curview maxlinelen parentlist
5674 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5675 set le [expr {$row + 1}]
5676 set arrowhigh 1
5677 while {1} {
5678 set c [lsearch -exact [lindex $rowidlist $le] $id]
5679 if {$c < 0} {
5680 incr le -1
5681 break
5683 lappend cols $c
5684 set x [lindex $displayorder $le]
5685 if {$x eq $id} {
5686 set arrowhigh 0
5687 break
5689 if {[info exists iddrawn($x)] || $le == $endrow} {
5690 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5691 if {$c >= 0} {
5692 lappend cols $c
5693 set arrowhigh 0
5695 break
5697 incr le
5699 if {$le <= $row} {
5700 return $row
5703 set lines {}
5704 set i 0
5705 set joinhigh 0
5706 if {[info exists linesegs($id)]} {
5707 set lines $linesegs($id)
5708 foreach li $lines {
5709 set r0 [lindex $li 0]
5710 if {$r0 > $row} {
5711 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5712 set joinhigh 1
5714 break
5716 incr i
5719 set joinlow 0
5720 if {$i > 0} {
5721 set li [lindex $lines [expr {$i-1}]]
5722 set r1 [lindex $li 1]
5723 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5724 set joinlow 1
5728 set x [lindex $cols [expr {$le - $row}]]
5729 set xp [lindex $cols [expr {$le - 1 - $row}]]
5730 set dir [expr {$xp - $x}]
5731 if {$joinhigh} {
5732 set ith [lindex $lines $i 2]
5733 set coords [$canv coords $ith]
5734 set ah [$canv itemcget $ith -arrow]
5735 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5736 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5737 if {$x2 ne {} && $x - $x2 == $dir} {
5738 set coords [lrange $coords 0 end-2]
5740 } else {
5741 set coords [list [xc $le $x] [yc $le]]
5743 if {$joinlow} {
5744 set itl [lindex $lines [expr {$i-1}] 2]
5745 set al [$canv itemcget $itl -arrow]
5746 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5747 } elseif {$arrowlow} {
5748 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5749 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5750 set arrowlow 0
5753 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5754 for {set y $le} {[incr y -1] > $row} {} {
5755 set x $xp
5756 set xp [lindex $cols [expr {$y - 1 - $row}]]
5757 set ndir [expr {$xp - $x}]
5758 if {$dir != $ndir || $xp < 0} {
5759 lappend coords [xc $y $x] [yc $y]
5761 set dir $ndir
5763 if {!$joinlow} {
5764 if {$xp < 0} {
5765 # join parent line to first child
5766 set ch [lindex $displayorder $row]
5767 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5768 if {$xc < 0} {
5769 puts "oops: drawlineseg: child $ch not on row $row"
5770 } elseif {$xc != $x} {
5771 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5772 set d [expr {int(0.5 * $linespc)}]
5773 set x1 [xc $row $x]
5774 if {$xc < $x} {
5775 set x2 [expr {$x1 - $d}]
5776 } else {
5777 set x2 [expr {$x1 + $d}]
5779 set y2 [yc $row]
5780 set y1 [expr {$y2 + $d}]
5781 lappend coords $x1 $y1 $x2 $y2
5782 } elseif {$xc < $x - 1} {
5783 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5784 } elseif {$xc > $x + 1} {
5785 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5787 set x $xc
5789 lappend coords [xc $row $x] [yc $row]
5790 } else {
5791 set xn [xc $row $xp]
5792 set yn [yc $row]
5793 lappend coords $xn $yn
5795 if {!$joinhigh} {
5796 assigncolor $id
5797 set t [$canv create line $coords -width [linewidth $id] \
5798 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5799 $canv lower $t
5800 bindline $t $id
5801 set lines [linsert $lines $i [list $row $le $t]]
5802 } else {
5803 $canv coords $ith $coords
5804 if {$arrow ne $ah} {
5805 $canv itemconf $ith -arrow $arrow
5807 lset lines $i 0 $row
5809 } else {
5810 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5811 set ndir [expr {$xo - $xp}]
5812 set clow [$canv coords $itl]
5813 if {$dir == $ndir} {
5814 set clow [lrange $clow 2 end]
5816 set coords [concat $coords $clow]
5817 if {!$joinhigh} {
5818 lset lines [expr {$i-1}] 1 $le
5819 } else {
5820 # coalesce two pieces
5821 $canv delete $ith
5822 set b [lindex $lines [expr {$i-1}] 0]
5823 set e [lindex $lines $i 1]
5824 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5826 $canv coords $itl $coords
5827 if {$arrow ne $al} {
5828 $canv itemconf $itl -arrow $arrow
5832 set linesegs($id) $lines
5833 return $le
5836 proc drawparentlinks {id row} {
5837 global rowidlist canv colormap curview parentlist
5838 global idpos linespc
5840 set rowids [lindex $rowidlist $row]
5841 set col [lsearch -exact $rowids $id]
5842 if {$col < 0} return
5843 set olds [lindex $parentlist $row]
5844 set row2 [expr {$row + 1}]
5845 set x [xc $row $col]
5846 set y [yc $row]
5847 set y2 [yc $row2]
5848 set d [expr {int(0.5 * $linespc)}]
5849 set ymid [expr {$y + $d}]
5850 set ids [lindex $rowidlist $row2]
5851 # rmx = right-most X coord used
5852 set rmx 0
5853 foreach p $olds {
5854 set i [lsearch -exact $ids $p]
5855 if {$i < 0} {
5856 puts "oops, parent $p of $id not in list"
5857 continue
5859 set x2 [xc $row2 $i]
5860 if {$x2 > $rmx} {
5861 set rmx $x2
5863 set j [lsearch -exact $rowids $p]
5864 if {$j < 0} {
5865 # drawlineseg will do this one for us
5866 continue
5868 assigncolor $p
5869 # should handle duplicated parents here...
5870 set coords [list $x $y]
5871 if {$i != $col} {
5872 # if attaching to a vertical segment, draw a smaller
5873 # slant for visual distinctness
5874 if {$i == $j} {
5875 if {$i < $col} {
5876 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5877 } else {
5878 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5880 } elseif {$i < $col && $i < $j} {
5881 # segment slants towards us already
5882 lappend coords [xc $row $j] $y
5883 } else {
5884 if {$i < $col - 1} {
5885 lappend coords [expr {$x2 + $linespc}] $y
5886 } elseif {$i > $col + 1} {
5887 lappend coords [expr {$x2 - $linespc}] $y
5889 lappend coords $x2 $y2
5891 } else {
5892 lappend coords $x2 $y2
5894 set t [$canv create line $coords -width [linewidth $p] \
5895 -fill $colormap($p) -tags lines.$p]
5896 $canv lower $t
5897 bindline $t $p
5899 if {$rmx > [lindex $idpos($id) 1]} {
5900 lset idpos($id) 1 $rmx
5901 redrawtags $id
5905 proc drawlines {id} {
5906 global canv
5908 $canv itemconf lines.$id -width [linewidth $id]
5911 proc drawcmittext {id row col} {
5912 global linespc canv canv2 canv3 fgcolor curview
5913 global cmitlisted commitinfo rowidlist parentlist
5914 global rowtextx idpos idtags idheads idotherrefs
5915 global linehtag linentag linedtag selectedline
5916 global canvxmax boldids boldnameids fgcolor markedid
5917 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5919 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5920 set listed $cmitlisted($curview,$id)
5921 if {$id eq $nullid} {
5922 set ofill red
5923 } elseif {$id eq $nullid2} {
5924 set ofill green
5925 } elseif {$id eq $mainheadid} {
5926 set ofill yellow
5927 } else {
5928 set ofill [lindex $circlecolors $listed]
5930 set x [xc $row $col]
5931 set y [yc $row]
5932 set orad [expr {$linespc / 3}]
5933 if {$listed <= 2} {
5934 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5935 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5936 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5937 } elseif {$listed == 3} {
5938 # triangle pointing left for left-side commits
5939 set t [$canv create polygon \
5940 [expr {$x - $orad}] $y \
5941 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5942 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5943 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5944 } else {
5945 # triangle pointing right for right-side commits
5946 set t [$canv create polygon \
5947 [expr {$x + $orad - 1}] $y \
5948 [expr {$x - $orad}] [expr {$y - $orad}] \
5949 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5950 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5952 set circleitem($row) $t
5953 $canv raise $t
5954 $canv bind $t <1> {selcanvline {} %x %y}
5955 set rmx [llength [lindex $rowidlist $row]]
5956 set olds [lindex $parentlist $row]
5957 if {$olds ne {}} {
5958 set nextids [lindex $rowidlist [expr {$row + 1}]]
5959 foreach p $olds {
5960 set i [lsearch -exact $nextids $p]
5961 if {$i > $rmx} {
5962 set rmx $i
5966 set xt [xc $row $rmx]
5967 set rowtextx($row) $xt
5968 set idpos($id) [list $x $xt $y]
5969 if {[info exists idtags($id)] || [info exists idheads($id)]
5970 || [info exists idotherrefs($id)]} {
5971 set xt [drawtags $id $x $xt $y]
5973 if {[lindex $commitinfo($id) 6] > 0} {
5974 set xt [drawnotesign $xt $y]
5976 set headline [lindex $commitinfo($id) 0]
5977 set name [lindex $commitinfo($id) 1]
5978 set date [lindex $commitinfo($id) 2]
5979 set date [formatdate $date]
5980 set font mainfont
5981 set nfont mainfont
5982 set isbold [ishighlighted $id]
5983 if {$isbold > 0} {
5984 lappend boldids $id
5985 set font mainfontbold
5986 if {$isbold > 1} {
5987 lappend boldnameids $id
5988 set nfont mainfontbold
5991 set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
5992 -text $headline -font $font -tags text]
5993 $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
5994 set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5995 -text $name -font $nfont -tags text]
5996 set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5997 -text $date -font mainfont -tags text]
5998 if {$selectedline == $row} {
5999 make_secsel $id
6001 if {[info exists markedid] && $markedid eq $id} {
6002 make_idmark $id
6004 set xr [expr {$xt + [font measure $font $headline]}]
6005 if {$xr > $canvxmax} {
6006 set canvxmax $xr
6007 setcanvscroll
6011 proc drawcmitrow {row} {
6012 global displayorder rowidlist nrows_drawn
6013 global iddrawn markingmatches
6014 global commitinfo numcommits
6015 global filehighlight fhighlights findpattern nhighlights
6016 global hlview vhighlights
6017 global highlight_related rhighlights
6019 if {$row >= $numcommits} return
6021 set id [lindex $displayorder $row]
6022 if {[info exists hlview] && ![info exists vhighlights($id)]} {
6023 askvhighlight $row $id
6025 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
6026 askfilehighlight $row $id
6028 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
6029 askfindhighlight $row $id
6031 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
6032 askrelhighlight $row $id
6034 if {![info exists iddrawn($id)]} {
6035 set col [lsearch -exact [lindex $rowidlist $row] $id]
6036 if {$col < 0} {
6037 puts "oops, row $row id $id not in list"
6038 return
6040 if {![info exists commitinfo($id)]} {
6041 getcommit $id
6043 assigncolor $id
6044 drawcmittext $id $row $col
6045 set iddrawn($id) 1
6046 incr nrows_drawn
6048 if {$markingmatches} {
6049 markrowmatches $row $id
6053 proc drawcommits {row {endrow {}}} {
6054 global numcommits iddrawn displayorder curview need_redisplay
6055 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
6057 if {$row < 0} {
6058 set row 0
6060 if {$endrow eq {}} {
6061 set endrow $row
6063 if {$endrow >= $numcommits} {
6064 set endrow [expr {$numcommits - 1}]
6067 set rl1 [expr {$row - $downarrowlen - 3}]
6068 if {$rl1 < 0} {
6069 set rl1 0
6071 set ro1 [expr {$row - 3}]
6072 if {$ro1 < 0} {
6073 set ro1 0
6075 set r2 [expr {$endrow + $uparrowlen + 3}]
6076 if {$r2 > $numcommits} {
6077 set r2 $numcommits
6079 for {set r $rl1} {$r < $r2} {incr r} {
6080 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
6081 if {$rl1 < $r} {
6082 layoutrows $rl1 $r
6084 set rl1 [expr {$r + 1}]
6087 if {$rl1 < $r} {
6088 layoutrows $rl1 $r
6090 optimize_rows $ro1 0 $r2
6091 if {$need_redisplay || $nrows_drawn > 2000} {
6092 clear_display
6095 # make the lines join to already-drawn rows either side
6096 set r [expr {$row - 1}]
6097 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
6098 set r $row
6100 set er [expr {$endrow + 1}]
6101 if {$er >= $numcommits ||
6102 ![info exists iddrawn([lindex $displayorder $er])]} {
6103 set er $endrow
6105 for {} {$r <= $er} {incr r} {
6106 set id [lindex $displayorder $r]
6107 set wasdrawn [info exists iddrawn($id)]
6108 drawcmitrow $r
6109 if {$r == $er} break
6110 set nextid [lindex $displayorder [expr {$r + 1}]]
6111 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
6112 drawparentlinks $id $r
6114 set rowids [lindex $rowidlist $r]
6115 foreach lid $rowids {
6116 if {$lid eq {}} continue
6117 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
6118 if {$lid eq $id} {
6119 # see if this is the first child of any of its parents
6120 foreach p [lindex $parentlist $r] {
6121 if {[lsearch -exact $rowids $p] < 0} {
6122 # make this line extend up to the child
6123 set lineend($p) [drawlineseg $p $r $er 0]
6126 } else {
6127 set lineend($lid) [drawlineseg $lid $r $er 1]
6133 proc undolayout {row} {
6134 global uparrowlen mingaplen downarrowlen
6135 global rowidlist rowisopt rowfinal need_redisplay
6137 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
6138 if {$r < 0} {
6139 set r 0
6141 if {[llength $rowidlist] > $r} {
6142 incr r -1
6143 set rowidlist [lrange $rowidlist 0 $r]
6144 set rowfinal [lrange $rowfinal 0 $r]
6145 set rowisopt [lrange $rowisopt 0 $r]
6146 set need_redisplay 1
6147 run drawvisible
6151 proc drawvisible {} {
6152 global canv linespc curview vrowmod selectedline targetrow targetid
6153 global need_redisplay cscroll numcommits
6155 set fs [$canv yview]
6156 set ymax [lindex [$canv cget -scrollregion] 3]
6157 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
6158 set f0 [lindex $fs 0]
6159 set f1 [lindex $fs 1]
6160 set y0 [expr {int($f0 * $ymax)}]
6161 set y1 [expr {int($f1 * $ymax)}]
6163 if {[info exists targetid]} {
6164 if {[commitinview $targetid $curview]} {
6165 set r [rowofcommit $targetid]
6166 if {$r != $targetrow} {
6167 # Fix up the scrollregion and change the scrolling position
6168 # now that our target row has moved.
6169 set diff [expr {($r - $targetrow) * $linespc}]
6170 set targetrow $r
6171 setcanvscroll
6172 set ymax [lindex [$canv cget -scrollregion] 3]
6173 incr y0 $diff
6174 incr y1 $diff
6175 set f0 [expr {$y0 / $ymax}]
6176 set f1 [expr {$y1 / $ymax}]
6177 allcanvs yview moveto $f0
6178 $cscroll set $f0 $f1
6179 set need_redisplay 1
6181 } else {
6182 unset targetid
6186 set row [expr {int(($y0 - 3) / $linespc) - 1}]
6187 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
6188 if {$endrow >= $vrowmod($curview)} {
6189 update_arcrows $curview
6191 if {$selectedline ne {} &&
6192 $row <= $selectedline && $selectedline <= $endrow} {
6193 set targetrow $selectedline
6194 } elseif {[info exists targetid]} {
6195 set targetrow [expr {int(($row + $endrow) / 2)}]
6197 if {[info exists targetrow]} {
6198 if {$targetrow >= $numcommits} {
6199 set targetrow [expr {$numcommits - 1}]
6201 set targetid [commitonrow $targetrow]
6203 drawcommits $row $endrow
6206 proc clear_display {} {
6207 global iddrawn linesegs need_redisplay nrows_drawn
6208 global vhighlights fhighlights nhighlights rhighlights
6209 global linehtag linentag linedtag boldids boldnameids
6211 allcanvs delete all
6212 catch {unset iddrawn}
6213 catch {unset linesegs}
6214 catch {unset linehtag}
6215 catch {unset linentag}
6216 catch {unset linedtag}
6217 set boldids {}
6218 set boldnameids {}
6219 catch {unset vhighlights}
6220 catch {unset fhighlights}
6221 catch {unset nhighlights}
6222 catch {unset rhighlights}
6223 set need_redisplay 0
6224 set nrows_drawn 0
6227 proc findcrossings {id} {
6228 global rowidlist parentlist numcommits displayorder
6230 set cross {}
6231 set ccross {}
6232 foreach {s e} [rowranges $id] {
6233 if {$e >= $numcommits} {
6234 set e [expr {$numcommits - 1}]
6236 if {$e <= $s} continue
6237 for {set row $e} {[incr row -1] >= $s} {} {
6238 set x [lsearch -exact [lindex $rowidlist $row] $id]
6239 if {$x < 0} break
6240 set olds [lindex $parentlist $row]
6241 set kid [lindex $displayorder $row]
6242 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
6243 if {$kidx < 0} continue
6244 set nextrow [lindex $rowidlist [expr {$row + 1}]]
6245 foreach p $olds {
6246 set px [lsearch -exact $nextrow $p]
6247 if {$px < 0} continue
6248 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
6249 if {[lsearch -exact $ccross $p] >= 0} continue
6250 if {$x == $px + ($kidx < $px? -1: 1)} {
6251 lappend ccross $p
6252 } elseif {[lsearch -exact $cross $p] < 0} {
6253 lappend cross $p
6259 return [concat $ccross {{}} $cross]
6262 proc assigncolor {id} {
6263 global colormap colors nextcolor
6264 global parents children children curview
6266 if {[info exists colormap($id)]} return
6267 set ncolors [llength $colors]
6268 if {[info exists children($curview,$id)]} {
6269 set kids $children($curview,$id)
6270 } else {
6271 set kids {}
6273 if {[llength $kids] == 1} {
6274 set child [lindex $kids 0]
6275 if {[info exists colormap($child)]
6276 && [llength $parents($curview,$child)] == 1} {
6277 set colormap($id) $colormap($child)
6278 return
6281 set badcolors {}
6282 set origbad {}
6283 foreach x [findcrossings $id] {
6284 if {$x eq {}} {
6285 # delimiter between corner crossings and other crossings
6286 if {[llength $badcolors] >= $ncolors - 1} break
6287 set origbad $badcolors
6289 if {[info exists colormap($x)]
6290 && [lsearch -exact $badcolors $colormap($x)] < 0} {
6291 lappend badcolors $colormap($x)
6294 if {[llength $badcolors] >= $ncolors} {
6295 set badcolors $origbad
6297 set origbad $badcolors
6298 if {[llength $badcolors] < $ncolors - 1} {
6299 foreach child $kids {
6300 if {[info exists colormap($child)]
6301 && [lsearch -exact $badcolors $colormap($child)] < 0} {
6302 lappend badcolors $colormap($child)
6304 foreach p $parents($curview,$child) {
6305 if {[info exists colormap($p)]
6306 && [lsearch -exact $badcolors $colormap($p)] < 0} {
6307 lappend badcolors $colormap($p)
6311 if {[llength $badcolors] >= $ncolors} {
6312 set badcolors $origbad
6315 for {set i 0} {$i <= $ncolors} {incr i} {
6316 set c [lindex $colors $nextcolor]
6317 if {[incr nextcolor] >= $ncolors} {
6318 set nextcolor 0
6320 if {[lsearch -exact $badcolors $c]} break
6322 set colormap($id) $c
6325 proc bindline {t id} {
6326 global canv
6328 $canv bind $t <Enter> "lineenter %x %y $id"
6329 $canv bind $t <Motion> "linemotion %x %y $id"
6330 $canv bind $t <Leave> "lineleave $id"
6331 $canv bind $t <Button-1> "lineclick %x %y $id 1"
6334 proc drawtags {id x xt y1} {
6335 global idtags idheads idotherrefs mainhead
6336 global linespc lthickness
6337 global canv rowtextx curview fgcolor bgcolor ctxbut
6339 set marks {}
6340 set ntags 0
6341 set nheads 0
6342 if {[info exists idtags($id)]} {
6343 set marks $idtags($id)
6344 set ntags [llength $marks]
6346 if {[info exists idheads($id)]} {
6347 set marks [concat $marks $idheads($id)]
6348 set nheads [llength $idheads($id)]
6350 if {[info exists idotherrefs($id)]} {
6351 set marks [concat $marks $idotherrefs($id)]
6353 if {$marks eq {}} {
6354 return $xt
6357 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
6358 set yt [expr {$y1 - 0.5 * $linespc}]
6359 set yb [expr {$yt + $linespc - 1}]
6360 set xvals {}
6361 set wvals {}
6362 set i -1
6363 foreach tag $marks {
6364 incr i
6365 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
6366 set wid [font measure mainfontbold $tag]
6367 } else {
6368 set wid [font measure mainfont $tag]
6370 lappend xvals $xt
6371 lappend wvals $wid
6372 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
6374 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
6375 -width $lthickness -fill black -tags tag.$id]
6376 $canv lower $t
6377 foreach tag $marks x $xvals wid $wvals {
6378 set tag_quoted [string map {% %%} $tag]
6379 set xl [expr {$x + $delta}]
6380 set xr [expr {$x + $delta + $wid + $lthickness}]
6381 set font mainfont
6382 if {[incr ntags -1] >= 0} {
6383 # draw a tag
6384 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
6385 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
6386 -width 1 -outline black -fill yellow -tags tag.$id]
6387 $canv bind $t <1> [list showtag $tag_quoted 1]
6388 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
6389 } else {
6390 # draw a head or other ref
6391 if {[incr nheads -1] >= 0} {
6392 set col green
6393 if {$tag eq $mainhead} {
6394 set font mainfontbold
6396 } else {
6397 set col "#ddddff"
6399 set xl [expr {$xl - $delta/2}]
6400 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
6401 -width 1 -outline black -fill $col -tags tag.$id
6402 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
6403 set rwid [font measure mainfont $remoteprefix]
6404 set xi [expr {$x + 1}]
6405 set yti [expr {$yt + 1}]
6406 set xri [expr {$x + $rwid}]
6407 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
6408 -width 0 -fill "#ffddaa" -tags tag.$id
6411 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
6412 -font $font -tags [list tag.$id text]]
6413 if {$ntags >= 0} {
6414 $canv bind $t <1> [list showtag $tag_quoted 1]
6415 } elseif {$nheads >= 0} {
6416 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag_quoted]
6419 return $xt
6422 proc drawnotesign {xt y} {
6423 global linespc canv fgcolor
6425 set orad [expr {$linespc / 3}]
6426 set t [$canv create rectangle [expr {$xt - $orad}] [expr {$y - $orad}] \
6427 [expr {$xt + $orad - 1}] [expr {$y + $orad - 1}] \
6428 -fill yellow -outline $fgcolor -width 1 -tags circle]
6429 set xt [expr {$xt + $orad * 3}]
6430 return $xt
6433 proc xcoord {i level ln} {
6434 global canvx0 xspc1 xspc2
6436 set x [expr {$canvx0 + $i * $xspc1($ln)}]
6437 if {$i > 0 && $i == $level} {
6438 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
6439 } elseif {$i > $level} {
6440 set x [expr {$x + $xspc2 - $xspc1($ln)}]
6442 return $x
6445 proc show_status {msg} {
6446 global canv fgcolor
6448 clear_display
6449 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
6450 -tags text -fill $fgcolor
6453 # Don't change the text pane cursor if it is currently the hand cursor,
6454 # showing that we are over a sha1 ID link.
6455 proc settextcursor {c} {
6456 global ctext curtextcursor
6458 if {[$ctext cget -cursor] == $curtextcursor} {
6459 $ctext config -cursor $c
6461 set curtextcursor $c
6464 proc nowbusy {what {name {}}} {
6465 global isbusy busyname statusw
6467 if {[array names isbusy] eq {}} {
6468 . config -cursor watch
6469 settextcursor watch
6471 set isbusy($what) 1
6472 set busyname($what) $name
6473 if {$name ne {}} {
6474 $statusw conf -text $name
6478 proc notbusy {what} {
6479 global isbusy maincursor textcursor busyname statusw
6481 catch {
6482 unset isbusy($what)
6483 if {$busyname($what) ne {} &&
6484 [$statusw cget -text] eq $busyname($what)} {
6485 $statusw conf -text {}
6488 if {[array names isbusy] eq {}} {
6489 . config -cursor $maincursor
6490 settextcursor $textcursor
6494 proc findmatches {f} {
6495 global findtype findstring
6496 if {$findtype == [mc "Regexp"]} {
6497 set matches [regexp -indices -all -inline $findstring $f]
6498 } else {
6499 set fs $findstring
6500 if {$findtype == [mc "IgnCase"]} {
6501 set f [string tolower $f]
6502 set fs [string tolower $fs]
6504 set matches {}
6505 set i 0
6506 set l [string length $fs]
6507 while {[set j [string first $fs $f $i]] >= 0} {
6508 lappend matches [list $j [expr {$j+$l-1}]]
6509 set i [expr {$j + $l}]
6512 return $matches
6515 proc dofind {{dirn 1} {wrap 1}} {
6516 global findstring findstartline findcurline selectedline numcommits
6517 global gdttype filehighlight fh_serial find_dirn findallowwrap
6519 if {[info exists find_dirn]} {
6520 if {$find_dirn == $dirn} return
6521 stopfinding
6523 focus .
6524 if {$findstring eq {} || $numcommits == 0} return
6525 if {$selectedline eq {}} {
6526 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6527 } else {
6528 set findstartline $selectedline
6530 set findcurline $findstartline
6531 nowbusy finding [mc "Searching"]
6532 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6533 after cancel do_file_hl $fh_serial
6534 do_file_hl $fh_serial
6536 set find_dirn $dirn
6537 set findallowwrap $wrap
6538 run findmore
6541 proc stopfinding {} {
6542 global find_dirn findcurline fprogcoord
6544 if {[info exists find_dirn]} {
6545 unset find_dirn
6546 unset findcurline
6547 notbusy finding
6548 set fprogcoord 0
6549 adjustprogress
6551 stopblaming
6554 proc findmore {} {
6555 global commitdata commitinfo numcommits findpattern findloc
6556 global findstartline findcurline findallowwrap
6557 global find_dirn gdttype fhighlights fprogcoord
6558 global curview varcorder vrownum varccommits vrowmod
6560 if {![info exists find_dirn]} {
6561 return 0
6563 set fldtypes [list [mc "Headline"] [mc "Author"] "" [mc "Committer"] "" [mc "Comments"]]
6564 set l $findcurline
6565 set moretodo 0
6566 if {$find_dirn > 0} {
6567 incr l
6568 if {$l >= $numcommits} {
6569 set l 0
6571 if {$l <= $findstartline} {
6572 set lim [expr {$findstartline + 1}]
6573 } else {
6574 set lim $numcommits
6575 set moretodo $findallowwrap
6577 } else {
6578 if {$l == 0} {
6579 set l $numcommits
6581 incr l -1
6582 if {$l >= $findstartline} {
6583 set lim [expr {$findstartline - 1}]
6584 } else {
6585 set lim -1
6586 set moretodo $findallowwrap
6589 set n [expr {($lim - $l) * $find_dirn}]
6590 if {$n > 500} {
6591 set n 500
6592 set moretodo 1
6594 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6595 update_arcrows $curview
6597 set found 0
6598 set domore 1
6599 set ai [bsearch $vrownum($curview) $l]
6600 set a [lindex $varcorder($curview) $ai]
6601 set arow [lindex $vrownum($curview) $ai]
6602 set ids [lindex $varccommits($curview,$a)]
6603 set arowend [expr {$arow + [llength $ids]}]
6604 if {$gdttype eq [mc "containing:"]} {
6605 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6606 if {$l < $arow || $l >= $arowend} {
6607 incr ai $find_dirn
6608 set a [lindex $varcorder($curview) $ai]
6609 set arow [lindex $vrownum($curview) $ai]
6610 set ids [lindex $varccommits($curview,$a)]
6611 set arowend [expr {$arow + [llength $ids]}]
6613 set id [lindex $ids [expr {$l - $arow}]]
6614 # shouldn't happen unless git log doesn't give all the commits...
6615 if {![info exists commitdata($id)] ||
6616 ![doesmatch $commitdata($id)]} {
6617 continue
6619 if {![info exists commitinfo($id)]} {
6620 getcommit $id
6622 set info $commitinfo($id)
6623 foreach f $info ty $fldtypes {
6624 if {$ty eq ""} continue
6625 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6626 [doesmatch $f]} {
6627 set found 1
6628 break
6631 if {$found} break
6633 } else {
6634 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6635 if {$l < $arow || $l >= $arowend} {
6636 incr ai $find_dirn
6637 set a [lindex $varcorder($curview) $ai]
6638 set arow [lindex $vrownum($curview) $ai]
6639 set ids [lindex $varccommits($curview,$a)]
6640 set arowend [expr {$arow + [llength $ids]}]
6642 set id [lindex $ids [expr {$l - $arow}]]
6643 if {![info exists fhighlights($id)]} {
6644 # this sets fhighlights($id) to -1
6645 askfilehighlight $l $id
6647 if {$fhighlights($id) > 0} {
6648 set found $domore
6649 break
6651 if {$fhighlights($id) < 0} {
6652 if {$domore} {
6653 set domore 0
6654 set findcurline [expr {$l - $find_dirn}]
6659 if {$found || ($domore && !$moretodo)} {
6660 unset findcurline
6661 unset find_dirn
6662 notbusy finding
6663 set fprogcoord 0
6664 adjustprogress
6665 if {$found} {
6666 findselectline $l
6667 } else {
6668 bell
6670 return 0
6672 if {!$domore} {
6673 flushhighlights
6674 } else {
6675 set findcurline [expr {$l - $find_dirn}]
6677 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6678 if {$n < 0} {
6679 incr n $numcommits
6681 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6682 adjustprogress
6683 return $domore
6686 proc findselectline {l} {
6687 global findloc commentend ctext findcurline markingmatches gdttype
6689 set markingmatches [expr {$gdttype eq [mc "containing:"]}]
6690 set findcurline $l
6691 selectline $l 1
6692 if {$markingmatches &&
6693 ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} {
6694 # highlight the matches in the comments
6695 set f [$ctext get 1.0 $commentend]
6696 set matches [findmatches $f]
6697 foreach match $matches {
6698 set start [lindex $match 0]
6699 set end [expr {[lindex $match 1] + 1}]
6700 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6703 drawvisible
6706 # mark the bits of a headline or author that match a find string
6707 proc markmatches {canv l str tag matches font row} {
6708 global selectedline
6710 set bbox [$canv bbox $tag]
6711 set x0 [lindex $bbox 0]
6712 set y0 [lindex $bbox 1]
6713 set y1 [lindex $bbox 3]
6714 foreach match $matches {
6715 set start [lindex $match 0]
6716 set end [lindex $match 1]
6717 if {$start > $end} continue
6718 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6719 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6720 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6721 [expr {$x0+$xlen+2}] $y1 \
6722 -outline {} -tags [list match$l matches] -fill yellow]
6723 $canv lower $t
6724 if {$row == $selectedline} {
6725 $canv raise $t secsel
6730 proc unmarkmatches {} {
6731 global markingmatches
6733 allcanvs delete matches
6734 set markingmatches 0
6735 stopfinding
6738 proc selcanvline {w x y} {
6739 global canv canvy0 ctext linespc
6740 global rowtextx
6741 set ymax [lindex [$canv cget -scrollregion] 3]
6742 if {$ymax == {}} return
6743 set yfrac [lindex [$canv yview] 0]
6744 set y [expr {$y + $yfrac * $ymax}]
6745 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6746 if {$l < 0} {
6747 set l 0
6749 if {$w eq $canv} {
6750 set xmax [lindex [$canv cget -scrollregion] 2]
6751 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6752 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6754 unmarkmatches
6755 selectline $l 1
6758 proc commit_descriptor {p} {
6759 global commitinfo
6760 if {![info exists commitinfo($p)]} {
6761 getcommit $p
6763 set l "..."
6764 if {[llength $commitinfo($p)] > 1} {
6765 set l [lindex $commitinfo($p) 0]
6767 return "$p ($l)\n"
6770 # append some text to the ctext widget, and make any SHA1 ID
6771 # that we know about be a clickable link.
6772 proc appendwithlinks {text tags} {
6773 global ctext linknum curview
6775 set start [$ctext index "end - 1c"]
6776 $ctext insert end $text $tags
6777 set links [regexp -indices -all -inline {(?:\m|-g)[0-9a-f]{6,40}\M} $text]
6778 foreach l $links {
6779 set s [lindex $l 0]
6780 set e [lindex $l 1]
6781 set linkid [string range $text $s $e]
6782 incr e
6783 $ctext tag delete link$linknum
6784 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6785 setlink $linkid link$linknum
6786 incr linknum
6790 proc setlink {id lk} {
6791 global curview ctext pendinglinks
6793 if {[string range $id 0 1] eq "-g"} {
6794 set id [string range $id 2 end]
6797 set known 0
6798 if {[string length $id] < 40} {
6799 set matches [longid $id]
6800 if {[llength $matches] > 0} {
6801 if {[llength $matches] > 1} return
6802 set known 1
6803 set id [lindex $matches 0]
6805 } else {
6806 set known [commitinview $id $curview]
6808 if {$known} {
6809 $ctext tag conf $lk -foreground blue -underline 1
6810 $ctext tag bind $lk <1> [list selbyid $id]
6811 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6812 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6813 } else {
6814 lappend pendinglinks($id) $lk
6815 interestedin $id {makelink %P}
6819 proc appendshortlink {id {pre {}} {post {}}} {
6820 global ctext linknum
6822 $ctext insert end $pre
6823 $ctext tag delete link$linknum
6824 $ctext insert end [string range $id 0 7] link$linknum
6825 $ctext insert end $post
6826 setlink $id link$linknum
6827 incr linknum
6830 proc makelink {id} {
6831 global pendinglinks
6833 if {![info exists pendinglinks($id)]} return
6834 foreach lk $pendinglinks($id) {
6835 setlink $id $lk
6837 unset pendinglinks($id)
6840 proc linkcursor {w inc} {
6841 global linkentercount curtextcursor
6843 if {[incr linkentercount $inc] > 0} {
6844 $w configure -cursor hand2
6845 } else {
6846 $w configure -cursor $curtextcursor
6847 if {$linkentercount < 0} {
6848 set linkentercount 0
6853 proc viewnextline {dir} {
6854 global canv linespc
6856 $canv delete hover
6857 set ymax [lindex [$canv cget -scrollregion] 3]
6858 set wnow [$canv yview]
6859 set wtop [expr {[lindex $wnow 0] * $ymax}]
6860 set newtop [expr {$wtop + $dir * $linespc}]
6861 if {$newtop < 0} {
6862 set newtop 0
6863 } elseif {$newtop > $ymax} {
6864 set newtop $ymax
6866 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6869 # add a list of tag or branch names at position pos
6870 # returns the number of names inserted
6871 proc appendrefs {pos ids var} {
6872 global ctext linknum curview $var maxrefs
6874 if {[catch {$ctext index $pos}]} {
6875 return 0
6877 $ctext conf -state normal
6878 $ctext delete $pos "$pos lineend"
6879 set tags {}
6880 foreach id $ids {
6881 foreach tag [set $var\($id\)] {
6882 lappend tags [list $tag $id]
6885 if {[llength $tags] > $maxrefs} {
6886 $ctext insert $pos "[mc "many"] ([llength $tags])"
6887 } else {
6888 set tags [lsort -index 0 -decreasing $tags]
6889 set sep {}
6890 foreach ti $tags {
6891 set id [lindex $ti 1]
6892 set lk link$linknum
6893 incr linknum
6894 $ctext tag delete $lk
6895 $ctext insert $pos $sep
6896 $ctext insert $pos [lindex $ti 0] $lk
6897 setlink $id $lk
6898 set sep ", "
6901 $ctext conf -state disabled
6902 return [llength $tags]
6905 # called when we have finished computing the nearby tags
6906 proc dispneartags {delay} {
6907 global selectedline currentid showneartags tagphase
6909 if {$selectedline eq {} || !$showneartags} return
6910 after cancel dispnexttag
6911 if {$delay} {
6912 after 200 dispnexttag
6913 set tagphase -1
6914 } else {
6915 after idle dispnexttag
6916 set tagphase 0
6920 proc dispnexttag {} {
6921 global selectedline currentid showneartags tagphase ctext
6923 if {$selectedline eq {} || !$showneartags} return
6924 switch -- $tagphase {
6926 set dtags [desctags $currentid]
6927 if {$dtags ne {}} {
6928 appendrefs precedes $dtags idtags
6932 set atags [anctags $currentid]
6933 if {$atags ne {}} {
6934 appendrefs follows $atags idtags
6938 set dheads [descheads $currentid]
6939 if {$dheads ne {}} {
6940 if {[appendrefs branch $dheads idheads] > 1
6941 && [$ctext get "branch -3c"] eq "h"} {
6942 # turn "Branch" into "Branches"
6943 $ctext conf -state normal
6944 $ctext insert "branch -2c" "es"
6945 $ctext conf -state disabled
6950 if {[incr tagphase] <= 2} {
6951 after idle dispnexttag
6955 proc make_secsel {id} {
6956 global linehtag linentag linedtag canv canv2 canv3
6958 if {![info exists linehtag($id)]} return
6959 $canv delete secsel
6960 set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
6961 -tags secsel -fill [$canv cget -selectbackground]]
6962 $canv lower $t
6963 $canv2 delete secsel
6964 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
6965 -tags secsel -fill [$canv2 cget -selectbackground]]
6966 $canv2 lower $t
6967 $canv3 delete secsel
6968 set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
6969 -tags secsel -fill [$canv3 cget -selectbackground]]
6970 $canv3 lower $t
6973 proc make_idmark {id} {
6974 global linehtag canv fgcolor
6976 if {![info exists linehtag($id)]} return
6977 $canv delete markid
6978 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
6979 -tags markid -outline $fgcolor]
6980 $canv raise $t
6983 proc selectline {l isnew {desired_loc {}}} {
6984 global canv ctext commitinfo selectedline
6985 global canvy0 linespc parents children curview
6986 global currentid sha1entry
6987 global commentend idtags linknum
6988 global mergemax numcommits pending_select
6989 global cmitmode showneartags allcommits
6990 global targetrow targetid lastscrollrows
6991 global autoselect autosellen jump_to_here
6993 catch {unset pending_select}
6994 $canv delete hover
6995 normalline
6996 unsel_reflist
6997 stopfinding
6998 if {$l < 0 || $l >= $numcommits} return
6999 set id [commitonrow $l]
7000 set targetid $id
7001 set targetrow $l
7002 set selectedline $l
7003 set currentid $id
7004 if {$lastscrollrows < $numcommits} {
7005 setcanvscroll
7008 set y [expr {$canvy0 + $l * $linespc}]
7009 set ymax [lindex [$canv cget -scrollregion] 3]
7010 set ytop [expr {$y - $linespc - 1}]
7011 set ybot [expr {$y + $linespc + 1}]
7012 set wnow [$canv yview]
7013 set wtop [expr {[lindex $wnow 0] * $ymax}]
7014 set wbot [expr {[lindex $wnow 1] * $ymax}]
7015 set wh [expr {$wbot - $wtop}]
7016 set newtop $wtop
7017 if {$ytop < $wtop} {
7018 if {$ybot < $wtop} {
7019 set newtop [expr {$y - $wh / 2.0}]
7020 } else {
7021 set newtop $ytop
7022 if {$newtop > $wtop - $linespc} {
7023 set newtop [expr {$wtop - $linespc}]
7026 } elseif {$ybot > $wbot} {
7027 if {$ytop > $wbot} {
7028 set newtop [expr {$y - $wh / 2.0}]
7029 } else {
7030 set newtop [expr {$ybot - $wh}]
7031 if {$newtop < $wtop + $linespc} {
7032 set newtop [expr {$wtop + $linespc}]
7036 if {$newtop != $wtop} {
7037 if {$newtop < 0} {
7038 set newtop 0
7040 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
7041 drawvisible
7044 make_secsel $id
7046 if {$isnew} {
7047 addtohistory [list selbyid $id 0] savecmitpos
7050 $sha1entry delete 0 end
7051 $sha1entry insert 0 $id
7052 if {$autoselect} {
7053 $sha1entry selection range 0 $autosellen
7055 rhighlight_sel $id
7057 $ctext conf -state normal
7058 clear_ctext
7059 set linknum 0
7060 if {![info exists commitinfo($id)]} {
7061 getcommit $id
7063 set info $commitinfo($id)
7064 set date [formatdate [lindex $info 2]]
7065 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
7066 set date [formatdate [lindex $info 4]]
7067 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
7068 if {[info exists idtags($id)]} {
7069 $ctext insert end [mc "Tags:"]
7070 foreach tag $idtags($id) {
7071 $ctext insert end " $tag"
7073 $ctext insert end "\n"
7076 set headers {}
7077 set olds $parents($curview,$id)
7078 if {[llength $olds] > 1} {
7079 set np 0
7080 foreach p $olds {
7081 if {$np >= $mergemax} {
7082 set tag mmax
7083 } else {
7084 set tag m$np
7086 $ctext insert end "[mc "Parent"]: " $tag
7087 appendwithlinks [commit_descriptor $p] {}
7088 incr np
7090 } else {
7091 foreach p $olds {
7092 append headers "[mc "Parent"]: [commit_descriptor $p]"
7096 foreach c $children($curview,$id) {
7097 append headers "[mc "Child"]: [commit_descriptor $c]"
7100 # make anything that looks like a SHA1 ID be a clickable link
7101 appendwithlinks $headers {}
7102 if {$showneartags} {
7103 if {![info exists allcommits]} {
7104 getallcommits
7106 $ctext insert end "[mc "Branch"]: "
7107 $ctext mark set branch "end -1c"
7108 $ctext mark gravity branch left
7109 $ctext insert end "\n[mc "Follows"]: "
7110 $ctext mark set follows "end -1c"
7111 $ctext mark gravity follows left
7112 $ctext insert end "\n[mc "Precedes"]: "
7113 $ctext mark set precedes "end -1c"
7114 $ctext mark gravity precedes left
7115 $ctext insert end "\n"
7116 dispneartags 1
7118 $ctext insert end "\n"
7119 set comment [lindex $info 5]
7120 if {[string first "\r" $comment] >= 0} {
7121 set comment [string map {"\r" "\n "} $comment]
7123 appendwithlinks $comment {comment}
7125 $ctext tag remove found 1.0 end
7126 $ctext conf -state disabled
7127 set commentend [$ctext index "end - 1c"]
7129 set jump_to_here $desired_loc
7130 init_flist [mc "Comments"]
7131 if {$cmitmode eq "tree"} {
7132 gettree $id
7133 } elseif {[llength $olds] <= 1} {
7134 startdiff $id
7135 } else {
7136 mergediff $id
7140 proc selfirstline {} {
7141 unmarkmatches
7142 selectline 0 1
7145 proc sellastline {} {
7146 global numcommits
7147 unmarkmatches
7148 set l [expr {$numcommits - 1}]
7149 selectline $l 1
7152 proc selnextline {dir} {
7153 global selectedline
7154 focus .
7155 if {$selectedline eq {}} return
7156 set l [expr {$selectedline + $dir}]
7157 unmarkmatches
7158 selectline $l 1
7161 proc selnextpage {dir} {
7162 global canv linespc selectedline numcommits
7164 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
7165 if {$lpp < 1} {
7166 set lpp 1
7168 allcanvs yview scroll [expr {$dir * $lpp}] units
7169 drawvisible
7170 if {$selectedline eq {}} return
7171 set l [expr {$selectedline + $dir * $lpp}]
7172 if {$l < 0} {
7173 set l 0
7174 } elseif {$l >= $numcommits} {
7175 set l [expr $numcommits - 1]
7177 unmarkmatches
7178 selectline $l 1
7181 proc unselectline {} {
7182 global selectedline currentid
7184 set selectedline {}
7185 catch {unset currentid}
7186 allcanvs delete secsel
7187 rhighlight_none
7190 proc reselectline {} {
7191 global selectedline
7193 if {$selectedline ne {}} {
7194 selectline $selectedline 0
7198 proc addtohistory {cmd {saveproc {}}} {
7199 global history historyindex curview
7201 unset_posvars
7202 save_position
7203 set elt [list $curview $cmd $saveproc {}]
7204 if {$historyindex > 0
7205 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
7206 return
7209 if {$historyindex < [llength $history]} {
7210 set history [lreplace $history $historyindex end $elt]
7211 } else {
7212 lappend history $elt
7214 incr historyindex
7215 if {$historyindex > 1} {
7216 .tf.bar.leftbut conf -state normal
7217 } else {
7218 .tf.bar.leftbut conf -state disabled
7220 .tf.bar.rightbut conf -state disabled
7223 # save the scrolling position of the diff display pane
7224 proc save_position {} {
7225 global historyindex history
7227 if {$historyindex < 1} return
7228 set hi [expr {$historyindex - 1}]
7229 set fn [lindex $history $hi 2]
7230 if {$fn ne {}} {
7231 lset history $hi 3 [eval $fn]
7235 proc unset_posvars {} {
7236 global last_posvars
7238 if {[info exists last_posvars]} {
7239 foreach {var val} $last_posvars {
7240 global $var
7241 catch {unset $var}
7243 unset last_posvars
7247 proc godo {elt} {
7248 global curview last_posvars
7250 set view [lindex $elt 0]
7251 set cmd [lindex $elt 1]
7252 set pv [lindex $elt 3]
7253 if {$curview != $view} {
7254 showview $view
7256 unset_posvars
7257 foreach {var val} $pv {
7258 global $var
7259 set $var $val
7261 set last_posvars $pv
7262 eval $cmd
7265 proc goback {} {
7266 global history historyindex
7267 focus .
7269 if {$historyindex > 1} {
7270 save_position
7271 incr historyindex -1
7272 godo [lindex $history [expr {$historyindex - 1}]]
7273 .tf.bar.rightbut conf -state normal
7275 if {$historyindex <= 1} {
7276 .tf.bar.leftbut conf -state disabled
7280 proc goforw {} {
7281 global history historyindex
7282 focus .
7284 if {$historyindex < [llength $history]} {
7285 save_position
7286 set cmd [lindex $history $historyindex]
7287 incr historyindex
7288 godo $cmd
7289 .tf.bar.leftbut conf -state normal
7291 if {$historyindex >= [llength $history]} {
7292 .tf.bar.rightbut conf -state disabled
7296 proc gettree {id} {
7297 global treefilelist treeidlist diffids diffmergeid treepending
7298 global nullid nullid2
7300 set diffids $id
7301 catch {unset diffmergeid}
7302 if {![info exists treefilelist($id)]} {
7303 if {![info exists treepending]} {
7304 if {$id eq $nullid} {
7305 set cmd [list | git ls-files]
7306 } elseif {$id eq $nullid2} {
7307 set cmd [list | git ls-files --stage -t]
7308 } else {
7309 set cmd [list | git ls-tree -r $id]
7311 if {[catch {set gtf [open $cmd r]}]} {
7312 return
7314 set treepending $id
7315 set treefilelist($id) {}
7316 set treeidlist($id) {}
7317 fconfigure $gtf -blocking 0 -encoding binary
7318 filerun $gtf [list gettreeline $gtf $id]
7320 } else {
7321 setfilelist $id
7325 proc gettreeline {gtf id} {
7326 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
7328 set nl 0
7329 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
7330 if {$diffids eq $nullid} {
7331 set fname $line
7332 } else {
7333 set i [string first "\t" $line]
7334 if {$i < 0} continue
7335 set fname [string range $line [expr {$i+1}] end]
7336 set line [string range $line 0 [expr {$i-1}]]
7337 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
7338 set sha1 [lindex $line 2]
7339 lappend treeidlist($id) $sha1
7341 if {[string index $fname 0] eq "\""} {
7342 set fname [lindex $fname 0]
7344 set fname [encoding convertfrom $fname]
7345 lappend treefilelist($id) $fname
7347 if {![eof $gtf]} {
7348 return [expr {$nl >= 1000? 2: 1}]
7350 close $gtf
7351 unset treepending
7352 if {$cmitmode ne "tree"} {
7353 if {![info exists diffmergeid]} {
7354 gettreediffs $diffids
7356 } elseif {$id ne $diffids} {
7357 gettree $diffids
7358 } else {
7359 setfilelist $id
7361 return 0
7364 proc showfile {f} {
7365 global treefilelist treeidlist diffids nullid nullid2
7366 global ctext_file_names ctext_file_lines
7367 global ctext commentend
7369 set i [lsearch -exact $treefilelist($diffids) $f]
7370 if {$i < 0} {
7371 puts "oops, $f not in list for id $diffids"
7372 return
7374 if {$diffids eq $nullid} {
7375 if {[catch {set bf [open $f r]} err]} {
7376 puts "oops, can't read $f: $err"
7377 return
7379 } else {
7380 set blob [lindex $treeidlist($diffids) $i]
7381 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
7382 puts "oops, error reading blob $blob: $err"
7383 return
7386 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
7387 filerun $bf [list getblobline $bf $diffids]
7388 $ctext config -state normal
7389 clear_ctext $commentend
7390 lappend ctext_file_names $f
7391 lappend ctext_file_lines [lindex [split $commentend "."] 0]
7392 $ctext insert end "\n"
7393 $ctext insert end "$f\n" filesep
7394 $ctext config -state disabled
7395 $ctext yview $commentend
7396 settabs 0
7399 proc getblobline {bf id} {
7400 global diffids cmitmode ctext
7402 if {$id ne $diffids || $cmitmode ne "tree"} {
7403 catch {close $bf}
7404 return 0
7406 $ctext config -state normal
7407 set nl 0
7408 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
7409 $ctext insert end "$line\n"
7411 if {[eof $bf]} {
7412 global jump_to_here ctext_file_names commentend
7414 # delete last newline
7415 $ctext delete "end - 2c" "end - 1c"
7416 close $bf
7417 if {$jump_to_here ne {} &&
7418 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
7419 set lnum [expr {[lindex $jump_to_here 1] +
7420 [lindex [split $commentend .] 0]}]
7421 mark_ctext_line $lnum
7423 $ctext config -state disabled
7424 return 0
7426 $ctext config -state disabled
7427 return [expr {$nl >= 1000? 2: 1}]
7430 proc mark_ctext_line {lnum} {
7431 global ctext markbgcolor
7433 $ctext tag delete omark
7434 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
7435 $ctext tag conf omark -background $markbgcolor
7436 $ctext see $lnum.0
7439 proc mergediff {id} {
7440 global diffmergeid
7441 global diffids treediffs
7442 global parents curview
7444 set diffmergeid $id
7445 set diffids $id
7446 set treediffs($id) {}
7447 set np [llength $parents($curview,$id)]
7448 settabs $np
7449 getblobdiffs $id
7452 proc startdiff {ids} {
7453 global treediffs diffids treepending diffmergeid nullid nullid2
7455 settabs 1
7456 set diffids $ids
7457 catch {unset diffmergeid}
7458 if {![info exists treediffs($ids)] ||
7459 [lsearch -exact $ids $nullid] >= 0 ||
7460 [lsearch -exact $ids $nullid2] >= 0} {
7461 if {![info exists treepending]} {
7462 gettreediffs $ids
7464 } else {
7465 addtocflist $ids
7469 # If the filename (name) is under any of the passed filter paths
7470 # then return true to include the file in the listing.
7471 proc path_filter {filter name} {
7472 set worktree [gitworktree]
7473 foreach p $filter {
7474 set fq_p [file normalize $p]
7475 set fq_n [file normalize [file join $worktree $name]]
7476 if {[string match [file normalize $fq_p]* $fq_n]} {
7477 return 1
7480 return 0
7483 proc addtocflist {ids} {
7484 global treediffs
7486 add_flist $treediffs($ids)
7487 getblobdiffs $ids
7490 proc diffcmd {ids flags} {
7491 global log_showroot nullid nullid2
7493 set i [lsearch -exact $ids $nullid]
7494 set j [lsearch -exact $ids $nullid2]
7495 if {$i >= 0} {
7496 if {[llength $ids] > 1 && $j < 0} {
7497 # comparing working directory with some specific revision
7498 set cmd [concat | git diff-index $flags]
7499 if {$i == 0} {
7500 lappend cmd -R [lindex $ids 1]
7501 } else {
7502 lappend cmd [lindex $ids 0]
7504 } else {
7505 # comparing working directory with index
7506 set cmd [concat | git diff-files $flags]
7507 if {$j == 1} {
7508 lappend cmd -R
7511 } elseif {$j >= 0} {
7512 set cmd [concat | git diff-index --cached $flags]
7513 if {[llength $ids] > 1} {
7514 # comparing index with specific revision
7515 if {$j == 0} {
7516 lappend cmd -R [lindex $ids 1]
7517 } else {
7518 lappend cmd [lindex $ids 0]
7520 } else {
7521 # comparing index with HEAD
7522 lappend cmd HEAD
7524 } else {
7525 if {$log_showroot} {
7526 lappend flags --root
7528 set cmd [concat | git diff-tree -r $flags $ids]
7530 return $cmd
7533 proc gettreediffs {ids} {
7534 global treediff treepending
7536 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7538 set treepending $ids
7539 set treediff {}
7540 fconfigure $gdtf -blocking 0 -encoding binary
7541 filerun $gdtf [list gettreediffline $gdtf $ids]
7544 proc gettreediffline {gdtf ids} {
7545 global treediff treediffs treepending diffids diffmergeid
7546 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7548 set nr 0
7549 set sublist {}
7550 set max 1000
7551 if {$perfile_attrs} {
7552 # cache_gitattr is slow, and even slower on win32 where we
7553 # have to invoke it for only about 30 paths at a time
7554 set max 500
7555 if {[tk windowingsystem] == "win32"} {
7556 set max 120
7559 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7560 set i [string first "\t" $line]
7561 if {$i >= 0} {
7562 set file [string range $line [expr {$i+1}] end]
7563 if {[string index $file 0] eq "\""} {
7564 set file [lindex $file 0]
7566 set file [encoding convertfrom $file]
7567 if {$file ne [lindex $treediff end]} {
7568 lappend treediff $file
7569 lappend sublist $file
7573 if {$perfile_attrs} {
7574 cache_gitattr encoding $sublist
7576 if {![eof $gdtf]} {
7577 return [expr {$nr >= $max? 2: 1}]
7579 close $gdtf
7580 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7581 set flist {}
7582 foreach f $treediff {
7583 if {[path_filter $vfilelimit($curview) $f]} {
7584 lappend flist $f
7587 set treediffs($ids) $flist
7588 } else {
7589 set treediffs($ids) $treediff
7591 unset treepending
7592 if {$cmitmode eq "tree" && [llength $diffids] == 1} {
7593 gettree $diffids
7594 } elseif {$ids != $diffids} {
7595 if {![info exists diffmergeid]} {
7596 gettreediffs $diffids
7598 } else {
7599 addtocflist $ids
7601 return 0
7604 # empty string or positive integer
7605 proc diffcontextvalidate {v} {
7606 return [regexp {^(|[1-9][0-9]*)$} $v]
7609 proc diffcontextchange {n1 n2 op} {
7610 global diffcontextstring diffcontext
7612 if {[string is integer -strict $diffcontextstring]} {
7613 if {$diffcontextstring >= 0} {
7614 set diffcontext $diffcontextstring
7615 reselectline
7620 proc changeignorespace {} {
7621 reselectline
7624 proc changeworddiff {name ix op} {
7625 reselectline
7628 proc getblobdiffs {ids} {
7629 global blobdifffd diffids env
7630 global diffinhdr treediffs
7631 global diffcontext
7632 global ignorespace
7633 global worddiff
7634 global limitdiffs vfilelimit curview
7635 global diffencoding targetline diffnparents
7636 global git_version currdiffsubmod
7638 set textconv {}
7639 if {[package vcompare $git_version "1.6.1"] >= 0} {
7640 set textconv "--textconv"
7642 set submodule {}
7643 if {[package vcompare $git_version "1.6.6"] >= 0} {
7644 set submodule "--submodule"
7646 set cmd [diffcmd $ids "-p $textconv $submodule -C --cc --no-commit-id -U$diffcontext"]
7647 if {$ignorespace} {
7648 append cmd " -w"
7650 if {$worddiff ne [mc "Line diff"]} {
7651 append cmd " --word-diff=porcelain"
7653 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7654 set cmd [concat $cmd -- $vfilelimit($curview)]
7656 if {[catch {set bdf [open $cmd r]} err]} {
7657 error_popup [mc "Error getting diffs: %s" $err]
7658 return
7660 set targetline {}
7661 set diffnparents 0
7662 set diffinhdr 0
7663 set diffencoding [get_path_encoding {}]
7664 fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
7665 set blobdifffd($ids) $bdf
7666 set currdiffsubmod ""
7667 filerun $bdf [list getblobdiffline $bdf $diffids]
7670 proc savecmitpos {} {
7671 global ctext cmitmode
7673 if {$cmitmode eq "tree"} {
7674 return {}
7676 return [list target_scrollpos [$ctext index @0,0]]
7679 proc savectextpos {} {
7680 global ctext
7682 return [list target_scrollpos [$ctext index @0,0]]
7685 proc maybe_scroll_ctext {ateof} {
7686 global ctext target_scrollpos
7688 if {![info exists target_scrollpos]} return
7689 if {!$ateof} {
7690 set nlines [expr {[winfo height $ctext]
7691 / [font metrics textfont -linespace]}]
7692 if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
7694 $ctext yview $target_scrollpos
7695 unset target_scrollpos
7698 proc setinlist {var i val} {
7699 global $var
7701 while {[llength [set $var]] < $i} {
7702 lappend $var {}
7704 if {[llength [set $var]] == $i} {
7705 lappend $var $val
7706 } else {
7707 lset $var $i $val
7711 proc makediffhdr {fname ids} {
7712 global ctext curdiffstart treediffs diffencoding
7713 global ctext_file_names jump_to_here targetline diffline
7715 set fname [encoding convertfrom $fname]
7716 set diffencoding [get_path_encoding $fname]
7717 set i [lsearch -exact $treediffs($ids) $fname]
7718 if {$i >= 0} {
7719 setinlist difffilestart $i $curdiffstart
7721 lset ctext_file_names end $fname
7722 set l [expr {(78 - [string length $fname]) / 2}]
7723 set pad [string range "----------------------------------------" 1 $l]
7724 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7725 set targetline {}
7726 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7727 set targetline [lindex $jump_to_here 1]
7729 set diffline 0
7732 proc getblobdiffline {bdf ids} {
7733 global diffids blobdifffd ctext curdiffstart
7734 global diffnexthead diffnextnote difffilestart
7735 global ctext_file_names ctext_file_lines
7736 global diffinhdr treediffs mergemax diffnparents
7737 global diffencoding jump_to_here targetline diffline currdiffsubmod
7738 global worddiff
7740 set nr 0
7741 $ctext conf -state normal
7742 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7743 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7744 catch {close $bdf}
7745 return 0
7747 if {![string compare -length 5 "diff " $line]} {
7748 if {![regexp {^diff (--cc|--git) } $line m type]} {
7749 set line [encoding convertfrom $line]
7750 $ctext insert end "$line\n" hunksep
7751 continue
7753 # start of a new file
7754 set diffinhdr 1
7755 $ctext insert end "\n"
7756 set curdiffstart [$ctext index "end - 1c"]
7757 lappend ctext_file_names ""
7758 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7759 $ctext insert end "\n" filesep
7761 if {$type eq "--cc"} {
7762 # start of a new file in a merge diff
7763 set fname [string range $line 10 end]
7764 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7765 lappend treediffs($ids) $fname
7766 add_flist [list $fname]
7769 } else {
7770 set line [string range $line 11 end]
7771 # If the name hasn't changed the length will be odd,
7772 # the middle char will be a space, and the two bits either
7773 # side will be a/name and b/name, or "a/name" and "b/name".
7774 # If the name has changed we'll get "rename from" and
7775 # "rename to" or "copy from" and "copy to" lines following
7776 # this, and we'll use them to get the filenames.
7777 # This complexity is necessary because spaces in the
7778 # filename(s) don't get escaped.
7779 set l [string length $line]
7780 set i [expr {$l / 2}]
7781 if {!(($l & 1) && [string index $line $i] eq " " &&
7782 [string range $line 2 [expr {$i - 1}]] eq \
7783 [string range $line [expr {$i + 3}] end])} {
7784 continue
7786 # unescape if quoted and chop off the a/ from the front
7787 if {[string index $line 0] eq "\""} {
7788 set fname [string range [lindex $line 0] 2 end]
7789 } else {
7790 set fname [string range $line 2 [expr {$i - 1}]]
7793 makediffhdr $fname $ids
7795 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7796 set fname [encoding convertfrom [string range $line 16 end]]
7797 $ctext insert end "\n"
7798 set curdiffstart [$ctext index "end - 1c"]
7799 lappend ctext_file_names $fname
7800 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7801 $ctext insert end "$line\n" filesep
7802 set i [lsearch -exact $treediffs($ids) $fname]
7803 if {$i >= 0} {
7804 setinlist difffilestart $i $curdiffstart
7807 } elseif {![string compare -length 2 "@@" $line]} {
7808 regexp {^@@+} $line ats
7809 set line [encoding convertfrom $diffencoding $line]
7810 $ctext insert end "$line\n" hunksep
7811 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7812 set diffline $nl
7814 set diffnparents [expr {[string length $ats] - 1}]
7815 set diffinhdr 0
7817 } elseif {![string compare -length 10 "Submodule " $line]} {
7818 # start of a new submodule
7819 if {[regexp -indices "\[0-9a-f\]+\\.\\." $line nameend]} {
7820 set fname [string range $line 10 [expr [lindex $nameend 0] - 2]]
7821 } else {
7822 set fname [string range $line 10 [expr [string first "contains " $line] - 2]]
7824 if {$currdiffsubmod != $fname} {
7825 $ctext insert end "\n"; # Add newline after commit message
7827 set curdiffstart [$ctext index "end - 1c"]
7828 lappend ctext_file_names ""
7829 if {$currdiffsubmod != $fname} {
7830 lappend ctext_file_lines $fname
7831 makediffhdr $fname $ids
7832 set currdiffsubmod $fname
7833 $ctext insert end "\n$line\n" filesep
7834 } else {
7835 $ctext insert end "$line\n" filesep
7837 } elseif {![string compare -length 3 " >" $line]} {
7838 set $currdiffsubmod ""
7839 set line [encoding convertfrom $diffencoding $line]
7840 $ctext insert end "$line\n" dresult
7841 } elseif {![string compare -length 3 " <" $line]} {
7842 set $currdiffsubmod ""
7843 set line [encoding convertfrom $diffencoding $line]
7844 $ctext insert end "$line\n" d0
7845 } elseif {$diffinhdr} {
7846 if {![string compare -length 12 "rename from " $line]} {
7847 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7848 if {[string index $fname 0] eq "\""} {
7849 set fname [lindex $fname 0]
7851 set fname [encoding convertfrom $fname]
7852 set i [lsearch -exact $treediffs($ids) $fname]
7853 if {$i >= 0} {
7854 setinlist difffilestart $i $curdiffstart
7856 } elseif {![string compare -length 10 $line "rename to "] ||
7857 ![string compare -length 8 $line "copy to "]} {
7858 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7859 if {[string index $fname 0] eq "\""} {
7860 set fname [lindex $fname 0]
7862 makediffhdr $fname $ids
7863 } elseif {[string compare -length 3 $line "---"] == 0} {
7864 # do nothing
7865 continue
7866 } elseif {[string compare -length 3 $line "+++"] == 0} {
7867 set diffinhdr 0
7868 continue
7870 $ctext insert end "$line\n" filesep
7872 } else {
7873 set line [string map {\x1A ^Z} \
7874 [encoding convertfrom $diffencoding $line]]
7875 # parse the prefix - one ' ', '-' or '+' for each parent
7876 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7877 set tag [expr {$diffnparents > 1? "m": "d"}]
7878 set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}]
7879 set words_pre_markup ""
7880 set words_post_markup ""
7881 if {[string trim $prefix " -+"] eq {}} {
7882 # prefix only has " ", "-" and "+" in it: normal diff line
7883 set num [string first "-" $prefix]
7884 if {$dowords} {
7885 set line [string range $line 1 end]
7887 if {$num >= 0} {
7888 # removed line, first parent with line is $num
7889 if {$num >= $mergemax} {
7890 set num "max"
7892 if {$dowords && $worddiff eq [mc "Markup words"]} {
7893 $ctext insert end "\[-$line-\]" $tag$num
7894 } else {
7895 $ctext insert end "$line" $tag$num
7897 if {!$dowords} {
7898 $ctext insert end "\n" $tag$num
7900 } else {
7901 set tags {}
7902 if {[string first "+" $prefix] >= 0} {
7903 # added line
7904 lappend tags ${tag}result
7905 if {$diffnparents > 1} {
7906 set num [string first " " $prefix]
7907 if {$num >= 0} {
7908 if {$num >= $mergemax} {
7909 set num "max"
7911 lappend tags m$num
7914 set words_pre_markup "{+"
7915 set words_post_markup "+}"
7917 if {$targetline ne {}} {
7918 if {$diffline == $targetline} {
7919 set seehere [$ctext index "end - 1 chars"]
7920 set targetline {}
7921 } else {
7922 incr diffline
7925 if {$dowords && $worddiff eq [mc "Markup words"]} {
7926 $ctext insert end "$words_pre_markup$line$words_post_markup" $tags
7927 } else {
7928 $ctext insert end "$line" $tags
7930 if {!$dowords} {
7931 $ctext insert end "\n" $tags
7934 } elseif {$dowords && $prefix eq "~"} {
7935 $ctext insert end "\n" {}
7936 } else {
7937 # "\ No newline at end of file",
7938 # or something else we don't recognize
7939 $ctext insert end "$line\n" hunksep
7943 if {[info exists seehere]} {
7944 mark_ctext_line [lindex [split $seehere .] 0]
7946 maybe_scroll_ctext [eof $bdf]
7947 $ctext conf -state disabled
7948 if {[eof $bdf]} {
7949 catch {close $bdf}
7950 return 0
7952 return [expr {$nr >= 1000? 2: 1}]
7955 proc changediffdisp {} {
7956 global ctext diffelide
7958 $ctext tag conf d0 -elide [lindex $diffelide 0]
7959 $ctext tag conf dresult -elide [lindex $diffelide 1]
7962 proc highlightfile {cline} {
7963 global cflist cflist_top
7965 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7966 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7967 $cflist see $cline.0
7968 set cflist_top $cline
7971 proc highlightfile_for_scrollpos {topidx} {
7972 global difffilestart
7974 if {![info exists difffilestart]} return
7976 set top [lindex [split $topidx .] 0]
7977 if {$difffilestart eq {} || $top < [lindex $difffilestart 0]} {
7978 highlightfile 0
7979 } else {
7980 highlightfile [expr {[bsearch $difffilestart $top] + 2}]
7984 proc prevfile {} {
7985 global difffilestart ctext cmitmode
7987 if {$cmitmode eq "tree"} return
7988 set prev 0.0
7989 set here [$ctext index @0,0]
7990 foreach loc $difffilestart {
7991 if {[$ctext compare $loc >= $here]} {
7992 $ctext yview $prev
7993 return
7995 set prev $loc
7997 $ctext yview $prev
8000 proc nextfile {} {
8001 global difffilestart ctext cmitmode
8003 if {$cmitmode eq "tree"} return
8004 set here [$ctext index @0,0]
8005 foreach loc $difffilestart {
8006 if {[$ctext compare $loc > $here]} {
8007 $ctext yview $loc
8008 return
8013 proc clear_ctext {{first 1.0}} {
8014 global ctext smarktop smarkbot
8015 global ctext_file_names ctext_file_lines
8016 global pendinglinks
8018 set l [lindex [split $first .] 0]
8019 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
8020 set smarktop $l
8022 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
8023 set smarkbot $l
8025 $ctext delete $first end
8026 if {$first eq "1.0"} {
8027 catch {unset pendinglinks}
8029 set ctext_file_names {}
8030 set ctext_file_lines {}
8033 proc settabs {{firstab {}}} {
8034 global firsttabstop tabstop ctext have_tk85
8036 if {$firstab ne {} && $have_tk85} {
8037 set firsttabstop $firstab
8039 set w [font measure textfont "0"]
8040 if {$firsttabstop != 0} {
8041 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
8042 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
8043 } elseif {$have_tk85 || $tabstop != 8} {
8044 $ctext conf -tabs [expr {$tabstop * $w}]
8045 } else {
8046 $ctext conf -tabs {}
8050 proc incrsearch {name ix op} {
8051 global ctext searchstring searchdirn
8053 if {[catch {$ctext index anchor}]} {
8054 # no anchor set, use start of selection, or of visible area
8055 set sel [$ctext tag ranges sel]
8056 if {$sel ne {}} {
8057 $ctext mark set anchor [lindex $sel 0]
8058 } elseif {$searchdirn eq "-forwards"} {
8059 $ctext mark set anchor @0,0
8060 } else {
8061 $ctext mark set anchor @0,[winfo height $ctext]
8064 if {$searchstring ne {}} {
8065 set here [$ctext search -count mlen $searchdirn -- $searchstring anchor]
8066 if {$here ne {}} {
8067 $ctext see $here
8068 set mend "$here + $mlen c"
8069 $ctext tag remove sel 1.0 end
8070 $ctext tag add sel $here $mend
8071 suppress_highlighting_file_for_current_scrollpos
8072 highlightfile_for_scrollpos $here
8075 rehighlight_search_results
8078 proc dosearch {} {
8079 global sstring ctext searchstring searchdirn
8081 focus $sstring
8082 $sstring icursor end
8083 set searchdirn -forwards
8084 if {$searchstring ne {}} {
8085 set sel [$ctext tag ranges sel]
8086 if {$sel ne {}} {
8087 set start "[lindex $sel 0] + 1c"
8088 } elseif {[catch {set start [$ctext index anchor]}]} {
8089 set start "@0,0"
8091 set match [$ctext search -count mlen -- $searchstring $start]
8092 $ctext tag remove sel 1.0 end
8093 if {$match eq {}} {
8094 bell
8095 return
8097 $ctext see $match
8098 suppress_highlighting_file_for_current_scrollpos
8099 highlightfile_for_scrollpos $match
8100 set mend "$match + $mlen c"
8101 $ctext tag add sel $match $mend
8102 $ctext mark unset anchor
8103 rehighlight_search_results
8107 proc dosearchback {} {
8108 global sstring ctext searchstring searchdirn
8110 focus $sstring
8111 $sstring icursor end
8112 set searchdirn -backwards
8113 if {$searchstring ne {}} {
8114 set sel [$ctext tag ranges sel]
8115 if {$sel ne {}} {
8116 set start [lindex $sel 0]
8117 } elseif {[catch {set start [$ctext index anchor]}]} {
8118 set start @0,[winfo height $ctext]
8120 set match [$ctext search -backwards -count ml -- $searchstring $start]
8121 $ctext tag remove sel 1.0 end
8122 if {$match eq {}} {
8123 bell
8124 return
8126 $ctext see $match
8127 suppress_highlighting_file_for_current_scrollpos
8128 highlightfile_for_scrollpos $match
8129 set mend "$match + $ml c"
8130 $ctext tag add sel $match $mend
8131 $ctext mark unset anchor
8132 rehighlight_search_results
8136 proc rehighlight_search_results {} {
8137 global ctext searchstring
8139 $ctext tag remove found 1.0 end
8140 $ctext tag remove currentsearchhit 1.0 end
8142 if {$searchstring ne {}} {
8143 searchmarkvisible 1
8147 proc searchmark {first last} {
8148 global ctext searchstring
8150 set sel [$ctext tag ranges sel]
8152 set mend $first.0
8153 while {1} {
8154 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
8155 if {$match eq {}} break
8156 set mend "$match + $mlen c"
8157 if {$sel ne {} && [$ctext compare $match == [lindex $sel 0]]} {
8158 $ctext tag add currentsearchhit $match $mend
8159 } else {
8160 $ctext tag add found $match $mend
8165 proc searchmarkvisible {doall} {
8166 global ctext smarktop smarkbot
8168 set topline [lindex [split [$ctext index @0,0] .] 0]
8169 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
8170 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
8171 # no overlap with previous
8172 searchmark $topline $botline
8173 set smarktop $topline
8174 set smarkbot $botline
8175 } else {
8176 if {$topline < $smarktop} {
8177 searchmark $topline [expr {$smarktop-1}]
8178 set smarktop $topline
8180 if {$botline > $smarkbot} {
8181 searchmark [expr {$smarkbot+1}] $botline
8182 set smarkbot $botline
8187 proc suppress_highlighting_file_for_current_scrollpos {} {
8188 global ctext suppress_highlighting_file_for_this_scrollpos
8190 set suppress_highlighting_file_for_this_scrollpos [$ctext index @0,0]
8193 proc scrolltext {f0 f1} {
8194 global searchstring cmitmode ctext
8195 global suppress_highlighting_file_for_this_scrollpos
8197 if {$cmitmode ne "tree"} {
8198 set topidx [$ctext index @0,0]
8199 if {![info exists suppress_highlighting_file_for_this_scrollpos]
8200 || $topidx ne $suppress_highlighting_file_for_this_scrollpos} {
8201 highlightfile_for_scrollpos $topidx
8205 catch {unset suppress_highlighting_file_for_this_scrollpos}
8207 .bleft.bottom.sb set $f0 $f1
8208 if {$searchstring ne {}} {
8209 searchmarkvisible 0
8213 proc setcoords {} {
8214 global linespc charspc canvx0 canvy0
8215 global xspc1 xspc2 lthickness
8217 set linespc [font metrics mainfont -linespace]
8218 set charspc [font measure mainfont "m"]
8219 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
8220 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
8221 set lthickness [expr {int($linespc / 9) + 1}]
8222 set xspc1(0) $linespc
8223 set xspc2 $linespc
8226 proc redisplay {} {
8227 global canv
8228 global selectedline
8230 set ymax [lindex [$canv cget -scrollregion] 3]
8231 if {$ymax eq {} || $ymax == 0} return
8232 set span [$canv yview]
8233 clear_display
8234 setcanvscroll
8235 allcanvs yview moveto [lindex $span 0]
8236 drawvisible
8237 if {$selectedline ne {}} {
8238 selectline $selectedline 0
8239 allcanvs yview moveto [lindex $span 0]
8243 proc parsefont {f n} {
8244 global fontattr
8246 set fontattr($f,family) [lindex $n 0]
8247 set s [lindex $n 1]
8248 if {$s eq {} || $s == 0} {
8249 set s 10
8250 } elseif {$s < 0} {
8251 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
8253 set fontattr($f,size) $s
8254 set fontattr($f,weight) normal
8255 set fontattr($f,slant) roman
8256 foreach style [lrange $n 2 end] {
8257 switch -- $style {
8258 "normal" -
8259 "bold" {set fontattr($f,weight) $style}
8260 "roman" -
8261 "italic" {set fontattr($f,slant) $style}
8266 proc fontflags {f {isbold 0}} {
8267 global fontattr
8269 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
8270 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
8271 -slant $fontattr($f,slant)]
8274 proc fontname {f} {
8275 global fontattr
8277 set n [list $fontattr($f,family) $fontattr($f,size)]
8278 if {$fontattr($f,weight) eq "bold"} {
8279 lappend n "bold"
8281 if {$fontattr($f,slant) eq "italic"} {
8282 lappend n "italic"
8284 return $n
8287 proc incrfont {inc} {
8288 global mainfont textfont ctext canv cflist showrefstop
8289 global stopped entries fontattr
8291 unmarkmatches
8292 set s $fontattr(mainfont,size)
8293 incr s $inc
8294 if {$s < 1} {
8295 set s 1
8297 set fontattr(mainfont,size) $s
8298 font config mainfont -size $s
8299 font config mainfontbold -size $s
8300 set mainfont [fontname mainfont]
8301 set s $fontattr(textfont,size)
8302 incr s $inc
8303 if {$s < 1} {
8304 set s 1
8306 set fontattr(textfont,size) $s
8307 font config textfont -size $s
8308 font config textfontbold -size $s
8309 set textfont [fontname textfont]
8310 setcoords
8311 settabs
8312 redisplay
8315 proc clearsha1 {} {
8316 global sha1entry sha1string
8317 if {[string length $sha1string] == 40} {
8318 $sha1entry delete 0 end
8322 proc sha1change {n1 n2 op} {
8323 global sha1string currentid sha1but
8324 if {$sha1string == {}
8325 || ([info exists currentid] && $sha1string == $currentid)} {
8326 set state disabled
8327 } else {
8328 set state normal
8330 if {[$sha1but cget -state] == $state} return
8331 if {$state == "normal"} {
8332 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
8333 } else {
8334 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
8338 proc gotocommit {} {
8339 global sha1string tagids headids curview varcid
8341 if {$sha1string == {}
8342 || ([info exists currentid] && $sha1string == $currentid)} return
8343 if {[info exists tagids($sha1string)]} {
8344 set id $tagids($sha1string)
8345 } elseif {[info exists headids($sha1string)]} {
8346 set id $headids($sha1string)
8347 } else {
8348 set id [string tolower $sha1string]
8349 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
8350 set matches [longid $id]
8351 if {$matches ne {}} {
8352 if {[llength $matches] > 1} {
8353 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
8354 return
8356 set id [lindex $matches 0]
8358 } else {
8359 if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
8360 error_popup [mc "Revision %s is not known" $sha1string]
8361 return
8365 if {[commitinview $id $curview]} {
8366 selectline [rowofcommit $id] 1
8367 return
8369 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
8370 set msg [mc "SHA1 id %s is not known" $sha1string]
8371 } else {
8372 set msg [mc "Revision %s is not in the current view" $sha1string]
8374 error_popup $msg
8377 proc lineenter {x y id} {
8378 global hoverx hovery hoverid hovertimer
8379 global commitinfo canv
8381 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8382 set hoverx $x
8383 set hovery $y
8384 set hoverid $id
8385 if {[info exists hovertimer]} {
8386 after cancel $hovertimer
8388 set hovertimer [after 500 linehover]
8389 $canv delete hover
8392 proc linemotion {x y id} {
8393 global hoverx hovery hoverid hovertimer
8395 if {[info exists hoverid] && $id == $hoverid} {
8396 set hoverx $x
8397 set hovery $y
8398 if {[info exists hovertimer]} {
8399 after cancel $hovertimer
8401 set hovertimer [after 500 linehover]
8405 proc lineleave {id} {
8406 global hoverid hovertimer canv
8408 if {[info exists hoverid] && $id == $hoverid} {
8409 $canv delete hover
8410 if {[info exists hovertimer]} {
8411 after cancel $hovertimer
8412 unset hovertimer
8414 unset hoverid
8418 proc linehover {} {
8419 global hoverx hovery hoverid hovertimer
8420 global canv linespc lthickness
8421 global commitinfo
8423 set text [lindex $commitinfo($hoverid) 0]
8424 set ymax [lindex [$canv cget -scrollregion] 3]
8425 if {$ymax == {}} return
8426 set yfrac [lindex [$canv yview] 0]
8427 set x [expr {$hoverx + 2 * $linespc}]
8428 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
8429 set x0 [expr {$x - 2 * $lthickness}]
8430 set y0 [expr {$y - 2 * $lthickness}]
8431 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
8432 set y1 [expr {$y + $linespc + 2 * $lthickness}]
8433 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
8434 -fill \#ffff80 -outline black -width 1 -tags hover]
8435 $canv raise $t
8436 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
8437 -font mainfont]
8438 $canv raise $t
8441 proc clickisonarrow {id y} {
8442 global lthickness
8444 set ranges [rowranges $id]
8445 set thresh [expr {2 * $lthickness + 6}]
8446 set n [expr {[llength $ranges] - 1}]
8447 for {set i 1} {$i < $n} {incr i} {
8448 set row [lindex $ranges $i]
8449 if {abs([yc $row] - $y) < $thresh} {
8450 return $i
8453 return {}
8456 proc arrowjump {id n y} {
8457 global canv
8459 # 1 <-> 2, 3 <-> 4, etc...
8460 set n [expr {(($n - 1) ^ 1) + 1}]
8461 set row [lindex [rowranges $id] $n]
8462 set yt [yc $row]
8463 set ymax [lindex [$canv cget -scrollregion] 3]
8464 if {$ymax eq {} || $ymax <= 0} return
8465 set view [$canv yview]
8466 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
8467 set yfrac [expr {$yt / $ymax - $yspan / 2}]
8468 if {$yfrac < 0} {
8469 set yfrac 0
8471 allcanvs yview moveto $yfrac
8474 proc lineclick {x y id isnew} {
8475 global ctext commitinfo children canv thickerline curview
8477 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8478 unmarkmatches
8479 unselectline
8480 normalline
8481 $canv delete hover
8482 # draw this line thicker than normal
8483 set thickerline $id
8484 drawlines $id
8485 if {$isnew} {
8486 set ymax [lindex [$canv cget -scrollregion] 3]
8487 if {$ymax eq {}} return
8488 set yfrac [lindex [$canv yview] 0]
8489 set y [expr {$y + $yfrac * $ymax}]
8491 set dirn [clickisonarrow $id $y]
8492 if {$dirn ne {}} {
8493 arrowjump $id $dirn $y
8494 return
8497 if {$isnew} {
8498 addtohistory [list lineclick $x $y $id 0] savectextpos
8500 # fill the details pane with info about this line
8501 $ctext conf -state normal
8502 clear_ctext
8503 settabs 0
8504 $ctext insert end "[mc "Parent"]:\t"
8505 $ctext insert end $id link0
8506 setlink $id link0
8507 set info $commitinfo($id)
8508 $ctext insert end "\n\t[lindex $info 0]\n"
8509 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
8510 set date [formatdate [lindex $info 2]]
8511 $ctext insert end "\t[mc "Date"]:\t$date\n"
8512 set kids $children($curview,$id)
8513 if {$kids ne {}} {
8514 $ctext insert end "\n[mc "Children"]:"
8515 set i 0
8516 foreach child $kids {
8517 incr i
8518 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
8519 set info $commitinfo($child)
8520 $ctext insert end "\n\t"
8521 $ctext insert end $child link$i
8522 setlink $child link$i
8523 $ctext insert end "\n\t[lindex $info 0]"
8524 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
8525 set date [formatdate [lindex $info 2]]
8526 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
8529 maybe_scroll_ctext 1
8530 $ctext conf -state disabled
8531 init_flist {}
8534 proc normalline {} {
8535 global thickerline
8536 if {[info exists thickerline]} {
8537 set id $thickerline
8538 unset thickerline
8539 drawlines $id
8543 proc selbyid {id {isnew 1}} {
8544 global curview
8545 if {[commitinview $id $curview]} {
8546 selectline [rowofcommit $id] $isnew
8550 proc mstime {} {
8551 global startmstime
8552 if {![info exists startmstime]} {
8553 set startmstime [clock clicks -milliseconds]
8555 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
8558 proc rowmenu {x y id} {
8559 global rowctxmenu selectedline rowmenuid curview
8560 global nullid nullid2 fakerowmenu mainhead markedid
8562 stopfinding
8563 set rowmenuid $id
8564 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
8565 set state disabled
8566 } else {
8567 set state normal
8569 if {[info exists markedid] && $markedid ne $id} {
8570 set mstate normal
8571 } else {
8572 set mstate disabled
8574 if {$id ne $nullid && $id ne $nullid2} {
8575 set menu $rowctxmenu
8576 if {$mainhead ne {}} {
8577 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal
8578 } else {
8579 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
8581 $menu entryconfigure 9 -state $mstate
8582 $menu entryconfigure 10 -state $mstate
8583 $menu entryconfigure 11 -state $mstate
8584 } else {
8585 set menu $fakerowmenu
8587 $menu entryconfigure [mca "Diff this -> selected"] -state $state
8588 $menu entryconfigure [mca "Diff selected -> this"] -state $state
8589 $menu entryconfigure [mca "Make patch"] -state $state
8590 $menu entryconfigure [mca "Diff this -> marked commit"] -state $mstate
8591 $menu entryconfigure [mca "Diff marked commit -> this"] -state $mstate
8592 tk_popup $menu $x $y
8595 proc markhere {} {
8596 global rowmenuid markedid canv
8598 set markedid $rowmenuid
8599 make_idmark $markedid
8602 proc gotomark {} {
8603 global markedid
8605 if {[info exists markedid]} {
8606 selbyid $markedid
8610 proc replace_by_kids {l r} {
8611 global curview children
8613 set id [commitonrow $r]
8614 set l [lreplace $l 0 0]
8615 foreach kid $children($curview,$id) {
8616 lappend l [rowofcommit $kid]
8618 return [lsort -integer -decreasing -unique $l]
8621 proc find_common_desc {} {
8622 global markedid rowmenuid curview children
8624 if {![info exists markedid]} return
8625 if {![commitinview $markedid $curview] ||
8626 ![commitinview $rowmenuid $curview]} return
8627 #set t1 [clock clicks -milliseconds]
8628 set l1 [list [rowofcommit $markedid]]
8629 set l2 [list [rowofcommit $rowmenuid]]
8630 while 1 {
8631 set r1 [lindex $l1 0]
8632 set r2 [lindex $l2 0]
8633 if {$r1 eq {} || $r2 eq {}} break
8634 if {$r1 == $r2} {
8635 selectline $r1 1
8636 break
8638 if {$r1 > $r2} {
8639 set l1 [replace_by_kids $l1 $r1]
8640 } else {
8641 set l2 [replace_by_kids $l2 $r2]
8644 #set t2 [clock clicks -milliseconds]
8645 #puts "took [expr {$t2-$t1}]ms"
8648 proc compare_commits {} {
8649 global markedid rowmenuid curview children
8651 if {![info exists markedid]} return
8652 if {![commitinview $markedid $curview]} return
8653 addtohistory [list do_cmp_commits $markedid $rowmenuid]
8654 do_cmp_commits $markedid $rowmenuid
8657 proc getpatchid {id} {
8658 global patchids
8660 if {![info exists patchids($id)]} {
8661 set cmd [diffcmd [list $id] {-p --root}]
8662 # trim off the initial "|"
8663 set cmd [lrange $cmd 1 end]
8664 if {[catch {
8665 set x [eval exec $cmd | git patch-id]
8666 set patchids($id) [lindex $x 0]
8667 }]} {
8668 set patchids($id) "error"
8671 return $patchids($id)
8674 proc do_cmp_commits {a b} {
8675 global ctext curview parents children patchids commitinfo
8677 $ctext conf -state normal
8678 clear_ctext
8679 init_flist {}
8680 for {set i 0} {$i < 100} {incr i} {
8681 set skipa 0
8682 set skipb 0
8683 if {[llength $parents($curview,$a)] > 1} {
8684 appendshortlink $a [mc "Skipping merge commit "] "\n"
8685 set skipa 1
8686 } else {
8687 set patcha [getpatchid $a]
8689 if {[llength $parents($curview,$b)] > 1} {
8690 appendshortlink $b [mc "Skipping merge commit "] "\n"
8691 set skipb 1
8692 } else {
8693 set patchb [getpatchid $b]
8695 if {!$skipa && !$skipb} {
8696 set heada [lindex $commitinfo($a) 0]
8697 set headb [lindex $commitinfo($b) 0]
8698 if {$patcha eq "error"} {
8699 appendshortlink $a [mc "Error getting patch ID for "] \
8700 [mc " - stopping\n"]
8701 break
8703 if {$patchb eq "error"} {
8704 appendshortlink $b [mc "Error getting patch ID for "] \
8705 [mc " - stopping\n"]
8706 break
8708 if {$patcha eq $patchb} {
8709 if {$heada eq $headb} {
8710 appendshortlink $a [mc "Commit "]
8711 appendshortlink $b " == " " $heada\n"
8712 } else {
8713 appendshortlink $a [mc "Commit "] " $heada\n"
8714 appendshortlink $b [mc " is the same patch as\n "] \
8715 " $headb\n"
8717 set skipa 1
8718 set skipb 1
8719 } else {
8720 $ctext insert end "\n"
8721 appendshortlink $a [mc "Commit "] " $heada\n"
8722 appendshortlink $b [mc " differs from\n "] \
8723 " $headb\n"
8724 $ctext insert end [mc "Diff of commits:\n\n"]
8725 $ctext conf -state disabled
8726 update
8727 diffcommits $a $b
8728 return
8731 if {$skipa} {
8732 set kids [real_children $curview,$a]
8733 if {[llength $kids] != 1} {
8734 $ctext insert end "\n"
8735 appendshortlink $a [mc "Commit "] \
8736 [mc " has %s children - stopping\n" [llength $kids]]
8737 break
8739 set a [lindex $kids 0]
8741 if {$skipb} {
8742 set kids [real_children $curview,$b]
8743 if {[llength $kids] != 1} {
8744 appendshortlink $b [mc "Commit "] \
8745 [mc " has %s children - stopping\n" [llength $kids]]
8746 break
8748 set b [lindex $kids 0]
8751 $ctext conf -state disabled
8754 proc diffcommits {a b} {
8755 global diffcontext diffids blobdifffd diffinhdr currdiffsubmod
8757 set tmpdir [gitknewtmpdir]
8758 set fna [file join $tmpdir "commit-[string range $a 0 7]"]
8759 set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
8760 if {[catch {
8761 exec git diff-tree -p --pretty $a >$fna
8762 exec git diff-tree -p --pretty $b >$fnb
8763 } err]} {
8764 error_popup [mc "Error writing commit to file: %s" $err]
8765 return
8767 if {[catch {
8768 set fd [open "| diff -U$diffcontext $fna $fnb" r]
8769 } err]} {
8770 error_popup [mc "Error diffing commits: %s" $err]
8771 return
8773 set diffids [list commits $a $b]
8774 set blobdifffd($diffids) $fd
8775 set diffinhdr 0
8776 set currdiffsubmod ""
8777 filerun $fd [list getblobdiffline $fd $diffids]
8780 proc diffvssel {dirn} {
8781 global rowmenuid selectedline
8783 if {$selectedline eq {}} return
8784 if {$dirn} {
8785 set oldid [commitonrow $selectedline]
8786 set newid $rowmenuid
8787 } else {
8788 set oldid $rowmenuid
8789 set newid [commitonrow $selectedline]
8791 addtohistory [list doseldiff $oldid $newid] savectextpos
8792 doseldiff $oldid $newid
8795 proc diffvsmark {dirn} {
8796 global rowmenuid markedid
8798 if {![info exists markedid]} return
8799 if {$dirn} {
8800 set oldid $markedid
8801 set newid $rowmenuid
8802 } else {
8803 set oldid $rowmenuid
8804 set newid $markedid
8806 addtohistory [list doseldiff $oldid $newid] savectextpos
8807 doseldiff $oldid $newid
8810 proc doseldiff {oldid newid} {
8811 global ctext
8812 global commitinfo
8814 $ctext conf -state normal
8815 clear_ctext
8816 init_flist [mc "Top"]
8817 $ctext insert end "[mc "From"] "
8818 $ctext insert end $oldid link0
8819 setlink $oldid link0
8820 $ctext insert end "\n "
8821 $ctext insert end [lindex $commitinfo($oldid) 0]
8822 $ctext insert end "\n\n[mc "To"] "
8823 $ctext insert end $newid link1
8824 setlink $newid link1
8825 $ctext insert end "\n "
8826 $ctext insert end [lindex $commitinfo($newid) 0]
8827 $ctext insert end "\n"
8828 $ctext conf -state disabled
8829 $ctext tag remove found 1.0 end
8830 startdiff [list $oldid $newid]
8833 proc mkpatch {} {
8834 global rowmenuid currentid commitinfo patchtop patchnum NS
8836 if {![info exists currentid]} return
8837 set oldid $currentid
8838 set oldhead [lindex $commitinfo($oldid) 0]
8839 set newid $rowmenuid
8840 set newhead [lindex $commitinfo($newid) 0]
8841 set top .patch
8842 set patchtop $top
8843 catch {destroy $top}
8844 ttk_toplevel $top
8845 make_transient $top .
8846 ${NS}::label $top.title -text [mc "Generate patch"]
8847 grid $top.title - -pady 10
8848 ${NS}::label $top.from -text [mc "From:"]
8849 ${NS}::entry $top.fromsha1 -width 40
8850 $top.fromsha1 insert 0 $oldid
8851 $top.fromsha1 conf -state readonly
8852 grid $top.from $top.fromsha1 -sticky w
8853 ${NS}::entry $top.fromhead -width 60
8854 $top.fromhead insert 0 $oldhead
8855 $top.fromhead conf -state readonly
8856 grid x $top.fromhead -sticky w
8857 ${NS}::label $top.to -text [mc "To:"]
8858 ${NS}::entry $top.tosha1 -width 40
8859 $top.tosha1 insert 0 $newid
8860 $top.tosha1 conf -state readonly
8861 grid $top.to $top.tosha1 -sticky w
8862 ${NS}::entry $top.tohead -width 60
8863 $top.tohead insert 0 $newhead
8864 $top.tohead conf -state readonly
8865 grid x $top.tohead -sticky w
8866 ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
8867 grid $top.rev x -pady 10 -padx 5
8868 ${NS}::label $top.flab -text [mc "Output file:"]
8869 ${NS}::entry $top.fname -width 60
8870 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
8871 incr patchnum
8872 grid $top.flab $top.fname -sticky w
8873 ${NS}::frame $top.buts
8874 ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
8875 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
8876 bind $top <Key-Return> mkpatchgo
8877 bind $top <Key-Escape> mkpatchcan
8878 grid $top.buts.gen $top.buts.can
8879 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8880 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8881 grid $top.buts - -pady 10 -sticky ew
8882 focus $top.fname
8885 proc mkpatchrev {} {
8886 global patchtop
8888 set oldid [$patchtop.fromsha1 get]
8889 set oldhead [$patchtop.fromhead get]
8890 set newid [$patchtop.tosha1 get]
8891 set newhead [$patchtop.tohead get]
8892 foreach e [list fromsha1 fromhead tosha1 tohead] \
8893 v [list $newid $newhead $oldid $oldhead] {
8894 $patchtop.$e conf -state normal
8895 $patchtop.$e delete 0 end
8896 $patchtop.$e insert 0 $v
8897 $patchtop.$e conf -state readonly
8901 proc mkpatchgo {} {
8902 global patchtop nullid nullid2
8904 set oldid [$patchtop.fromsha1 get]
8905 set newid [$patchtop.tosha1 get]
8906 set fname [$patchtop.fname get]
8907 set cmd [diffcmd [list $oldid $newid] -p]
8908 # trim off the initial "|"
8909 set cmd [lrange $cmd 1 end]
8910 lappend cmd >$fname &
8911 if {[catch {eval exec $cmd} err]} {
8912 error_popup "[mc "Error creating patch:"] $err" $patchtop
8914 catch {destroy $patchtop}
8915 unset patchtop
8918 proc mkpatchcan {} {
8919 global patchtop
8921 catch {destroy $patchtop}
8922 unset patchtop
8925 proc mktag {} {
8926 global rowmenuid mktagtop commitinfo NS
8928 set top .maketag
8929 set mktagtop $top
8930 catch {destroy $top}
8931 ttk_toplevel $top
8932 make_transient $top .
8933 ${NS}::label $top.title -text [mc "Create tag"]
8934 grid $top.title - -pady 10
8935 ${NS}::label $top.id -text [mc "ID:"]
8936 ${NS}::entry $top.sha1 -width 40
8937 $top.sha1 insert 0 $rowmenuid
8938 $top.sha1 conf -state readonly
8939 grid $top.id $top.sha1 -sticky w
8940 ${NS}::entry $top.head -width 60
8941 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8942 $top.head conf -state readonly
8943 grid x $top.head -sticky w
8944 ${NS}::label $top.tlab -text [mc "Tag name:"]
8945 ${NS}::entry $top.tag -width 60
8946 grid $top.tlab $top.tag -sticky w
8947 ${NS}::label $top.op -text [mc "Tag message is optional"]
8948 grid $top.op -columnspan 2 -sticky we
8949 ${NS}::label $top.mlab -text [mc "Tag message:"]
8950 ${NS}::entry $top.msg -width 60
8951 grid $top.mlab $top.msg -sticky w
8952 ${NS}::frame $top.buts
8953 ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
8954 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
8955 bind $top <Key-Return> mktaggo
8956 bind $top <Key-Escape> mktagcan
8957 grid $top.buts.gen $top.buts.can
8958 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8959 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8960 grid $top.buts - -pady 10 -sticky ew
8961 focus $top.tag
8964 proc domktag {} {
8965 global mktagtop env tagids idtags
8967 set id [$mktagtop.sha1 get]
8968 set tag [$mktagtop.tag get]
8969 set msg [$mktagtop.msg get]
8970 if {$tag == {}} {
8971 error_popup [mc "No tag name specified"] $mktagtop
8972 return 0
8974 if {[info exists tagids($tag)]} {
8975 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8976 return 0
8978 if {[catch {
8979 if {$msg != {}} {
8980 exec git tag -a -m $msg $tag $id
8981 } else {
8982 exec git tag $tag $id
8984 } err]} {
8985 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8986 return 0
8989 set tagids($tag) $id
8990 lappend idtags($id) $tag
8991 redrawtags $id
8992 addedtag $id
8993 dispneartags 0
8994 run refill_reflist
8995 return 1
8998 proc redrawtags {id} {
8999 global canv linehtag idpos currentid curview cmitlisted markedid
9000 global canvxmax iddrawn circleitem mainheadid circlecolors
9002 if {![commitinview $id $curview]} return
9003 if {![info exists iddrawn($id)]} return
9004 set row [rowofcommit $id]
9005 if {$id eq $mainheadid} {
9006 set ofill yellow
9007 } else {
9008 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
9010 $canv itemconf $circleitem($row) -fill $ofill
9011 $canv delete tag.$id
9012 set xt [eval drawtags $id $idpos($id)]
9013 $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
9014 set text [$canv itemcget $linehtag($id) -text]
9015 set font [$canv itemcget $linehtag($id) -font]
9016 set xr [expr {$xt + [font measure $font $text]}]
9017 if {$xr > $canvxmax} {
9018 set canvxmax $xr
9019 setcanvscroll
9021 if {[info exists currentid] && $currentid == $id} {
9022 make_secsel $id
9024 if {[info exists markedid] && $markedid eq $id} {
9025 make_idmark $id
9029 proc mktagcan {} {
9030 global mktagtop
9032 catch {destroy $mktagtop}
9033 unset mktagtop
9036 proc mktaggo {} {
9037 if {![domktag]} return
9038 mktagcan
9041 proc writecommit {} {
9042 global rowmenuid wrcomtop commitinfo wrcomcmd NS
9044 set top .writecommit
9045 set wrcomtop $top
9046 catch {destroy $top}
9047 ttk_toplevel $top
9048 make_transient $top .
9049 ${NS}::label $top.title -text [mc "Write commit to file"]
9050 grid $top.title - -pady 10
9051 ${NS}::label $top.id -text [mc "ID:"]
9052 ${NS}::entry $top.sha1 -width 40
9053 $top.sha1 insert 0 $rowmenuid
9054 $top.sha1 conf -state readonly
9055 grid $top.id $top.sha1 -sticky w
9056 ${NS}::entry $top.head -width 60
9057 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
9058 $top.head conf -state readonly
9059 grid x $top.head -sticky w
9060 ${NS}::label $top.clab -text [mc "Command:"]
9061 ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
9062 grid $top.clab $top.cmd -sticky w -pady 10
9063 ${NS}::label $top.flab -text [mc "Output file:"]
9064 ${NS}::entry $top.fname -width 60
9065 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
9066 grid $top.flab $top.fname -sticky w
9067 ${NS}::frame $top.buts
9068 ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
9069 ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
9070 bind $top <Key-Return> wrcomgo
9071 bind $top <Key-Escape> wrcomcan
9072 grid $top.buts.gen $top.buts.can
9073 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9074 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9075 grid $top.buts - -pady 10 -sticky ew
9076 focus $top.fname
9079 proc wrcomgo {} {
9080 global wrcomtop
9082 set id [$wrcomtop.sha1 get]
9083 set cmd "echo $id | [$wrcomtop.cmd get]"
9084 set fname [$wrcomtop.fname get]
9085 if {[catch {exec sh -c $cmd >$fname &} err]} {
9086 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
9088 catch {destroy $wrcomtop}
9089 unset wrcomtop
9092 proc wrcomcan {} {
9093 global wrcomtop
9095 catch {destroy $wrcomtop}
9096 unset wrcomtop
9099 proc mkbranch {} {
9100 global rowmenuid mkbrtop NS
9102 set top .makebranch
9103 catch {destroy $top}
9104 ttk_toplevel $top
9105 make_transient $top .
9106 ${NS}::label $top.title -text [mc "Create new branch"]
9107 grid $top.title - -pady 10
9108 ${NS}::label $top.id -text [mc "ID:"]
9109 ${NS}::entry $top.sha1 -width 40
9110 $top.sha1 insert 0 $rowmenuid
9111 $top.sha1 conf -state readonly
9112 grid $top.id $top.sha1 -sticky w
9113 ${NS}::label $top.nlab -text [mc "Name:"]
9114 ${NS}::entry $top.name -width 40
9115 grid $top.nlab $top.name -sticky w
9116 ${NS}::frame $top.buts
9117 ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
9118 ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
9119 bind $top <Key-Return> [list mkbrgo $top]
9120 bind $top <Key-Escape> "catch {destroy $top}"
9121 grid $top.buts.go $top.buts.can
9122 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9123 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9124 grid $top.buts - -pady 10 -sticky ew
9125 focus $top.name
9128 proc mkbrgo {top} {
9129 global headids idheads
9131 set name [$top.name get]
9132 set id [$top.sha1 get]
9133 set cmdargs {}
9134 set old_id {}
9135 if {$name eq {}} {
9136 error_popup [mc "Please specify a name for the new branch"] $top
9137 return
9139 if {[info exists headids($name)]} {
9140 if {![confirm_popup [mc \
9141 "Branch '%s' already exists. Overwrite?" $name] $top]} {
9142 return
9144 set old_id $headids($name)
9145 lappend cmdargs -f
9147 catch {destroy $top}
9148 lappend cmdargs $name $id
9149 nowbusy newbranch
9150 update
9151 if {[catch {
9152 eval exec git branch $cmdargs
9153 } err]} {
9154 notbusy newbranch
9155 error_popup $err
9156 } else {
9157 notbusy newbranch
9158 if {$old_id ne {}} {
9159 movehead $id $name
9160 movedhead $id $name
9161 redrawtags $old_id
9162 redrawtags $id
9163 } else {
9164 set headids($name) $id
9165 lappend idheads($id) $name
9166 addedhead $id $name
9167 redrawtags $id
9169 dispneartags 0
9170 run refill_reflist
9174 proc exec_citool {tool_args {baseid {}}} {
9175 global commitinfo env
9177 set save_env [array get env GIT_AUTHOR_*]
9179 if {$baseid ne {}} {
9180 if {![info exists commitinfo($baseid)]} {
9181 getcommit $baseid
9183 set author [lindex $commitinfo($baseid) 1]
9184 set date [lindex $commitinfo($baseid) 2]
9185 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
9186 $author author name email]
9187 && $date ne {}} {
9188 set env(GIT_AUTHOR_NAME) $name
9189 set env(GIT_AUTHOR_EMAIL) $email
9190 set env(GIT_AUTHOR_DATE) $date
9194 eval exec git citool $tool_args &
9196 array unset env GIT_AUTHOR_*
9197 array set env $save_env
9200 proc cherrypick {} {
9201 global rowmenuid curview
9202 global mainhead mainheadid
9203 global gitdir
9205 set oldhead [exec git rev-parse HEAD]
9206 set dheads [descheads $rowmenuid]
9207 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
9208 set ok [confirm_popup [mc "Commit %s is already\
9209 included in branch %s -- really re-apply it?" \
9210 [string range $rowmenuid 0 7] $mainhead]]
9211 if {!$ok} return
9213 nowbusy cherrypick [mc "Cherry-picking"]
9214 update
9215 # Unfortunately git-cherry-pick writes stuff to stderr even when
9216 # no error occurs, and exec takes that as an indication of error...
9217 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
9218 notbusy cherrypick
9219 if {[regexp -line \
9220 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
9221 $err msg fname]} {
9222 error_popup [mc "Cherry-pick failed because of local changes\
9223 to file '%s'.\nPlease commit, reset or stash\
9224 your changes and try again." $fname]
9225 } elseif {[regexp -line \
9226 {^(CONFLICT \(.*\):|Automatic cherry-pick failed|error: could not apply)} \
9227 $err]} {
9228 if {[confirm_popup [mc "Cherry-pick failed because of merge\
9229 conflict.\nDo you wish to run git citool to\
9230 resolve it?"]]} {
9231 # Force citool to read MERGE_MSG
9232 file delete [file join $gitdir "GITGUI_MSG"]
9233 exec_citool {} $rowmenuid
9235 } else {
9236 error_popup $err
9238 run updatecommits
9239 return
9241 set newhead [exec git rev-parse HEAD]
9242 if {$newhead eq $oldhead} {
9243 notbusy cherrypick
9244 error_popup [mc "No changes committed"]
9245 return
9247 addnewchild $newhead $oldhead
9248 if {[commitinview $oldhead $curview]} {
9249 # XXX this isn't right if we have a path limit...
9250 insertrow $newhead $oldhead $curview
9251 if {$mainhead ne {}} {
9252 movehead $newhead $mainhead
9253 movedhead $newhead $mainhead
9255 set mainheadid $newhead
9256 redrawtags $oldhead
9257 redrawtags $newhead
9258 selbyid $newhead
9260 notbusy cherrypick
9263 proc resethead {} {
9264 global mainhead rowmenuid confirm_ok resettype NS
9266 set confirm_ok 0
9267 set w ".confirmreset"
9268 ttk_toplevel $w
9269 make_transient $w .
9270 wm title $w [mc "Confirm reset"]
9271 ${NS}::label $w.m -text \
9272 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
9273 pack $w.m -side top -fill x -padx 20 -pady 20
9274 ${NS}::labelframe $w.f -text [mc "Reset type:"]
9275 set resettype mixed
9276 ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
9277 -text [mc "Soft: Leave working tree and index untouched"]
9278 grid $w.f.soft -sticky w
9279 ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
9280 -text [mc "Mixed: Leave working tree untouched, reset index"]
9281 grid $w.f.mixed -sticky w
9282 ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
9283 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
9284 grid $w.f.hard -sticky w
9285 pack $w.f -side top -fill x -padx 4
9286 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
9287 pack $w.ok -side left -fill x -padx 20 -pady 20
9288 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
9289 bind $w <Key-Escape> [list destroy $w]
9290 pack $w.cancel -side right -fill x -padx 20 -pady 20
9291 bind $w <Visibility> "grab $w; focus $w"
9292 tkwait window $w
9293 if {!$confirm_ok} return
9294 if {[catch {set fd [open \
9295 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
9296 error_popup $err
9297 } else {
9298 dohidelocalchanges
9299 filerun $fd [list readresetstat $fd]
9300 nowbusy reset [mc "Resetting"]
9301 selbyid $rowmenuid
9305 proc readresetstat {fd} {
9306 global mainhead mainheadid showlocalchanges rprogcoord
9308 if {[gets $fd line] >= 0} {
9309 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9310 set rprogcoord [expr {1.0 * $m / $n}]
9311 adjustprogress
9313 return 1
9315 set rprogcoord 0
9316 adjustprogress
9317 notbusy reset
9318 if {[catch {close $fd} err]} {
9319 error_popup $err
9321 set oldhead $mainheadid
9322 set newhead [exec git rev-parse HEAD]
9323 if {$newhead ne $oldhead} {
9324 movehead $newhead $mainhead
9325 movedhead $newhead $mainhead
9326 set mainheadid $newhead
9327 redrawtags $oldhead
9328 redrawtags $newhead
9330 if {$showlocalchanges} {
9331 doshowlocalchanges
9333 return 0
9336 # context menu for a head
9337 proc headmenu {x y id head} {
9338 global headmenuid headmenuhead headctxmenu mainhead
9340 stopfinding
9341 set headmenuid $id
9342 set headmenuhead $head
9343 set state normal
9344 if {[string match "remotes/*" $head]} {
9345 set state disabled
9347 if {$head eq $mainhead} {
9348 set state disabled
9350 $headctxmenu entryconfigure 0 -state $state
9351 $headctxmenu entryconfigure 1 -state $state
9352 tk_popup $headctxmenu $x $y
9355 proc cobranch {} {
9356 global headmenuid headmenuhead headids
9357 global showlocalchanges
9359 # check the tree is clean first??
9360 nowbusy checkout [mc "Checking out"]
9361 update
9362 dohidelocalchanges
9363 if {[catch {
9364 set fd [open [list | git checkout $headmenuhead 2>@1] r]
9365 } err]} {
9366 notbusy checkout
9367 error_popup $err
9368 if {$showlocalchanges} {
9369 dodiffindex
9371 } else {
9372 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
9376 proc readcheckoutstat {fd newhead newheadid} {
9377 global mainhead mainheadid headids showlocalchanges progresscoords
9378 global viewmainheadid curview
9380 if {[gets $fd line] >= 0} {
9381 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9382 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
9383 adjustprogress
9385 return 1
9387 set progresscoords {0 0}
9388 adjustprogress
9389 notbusy checkout
9390 if {[catch {close $fd} err]} {
9391 error_popup $err
9393 set oldmainid $mainheadid
9394 set mainhead $newhead
9395 set mainheadid $newheadid
9396 set viewmainheadid($curview) $newheadid
9397 redrawtags $oldmainid
9398 redrawtags $newheadid
9399 selbyid $newheadid
9400 if {$showlocalchanges} {
9401 dodiffindex
9405 proc rmbranch {} {
9406 global headmenuid headmenuhead mainhead
9407 global idheads
9409 set head $headmenuhead
9410 set id $headmenuid
9411 # this check shouldn't be needed any more...
9412 if {$head eq $mainhead} {
9413 error_popup [mc "Cannot delete the currently checked-out branch"]
9414 return
9416 set dheads [descheads $id]
9417 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
9418 # the stuff on this branch isn't on any other branch
9419 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
9420 branch.\nReally delete branch %s?" $head $head]]} return
9422 nowbusy rmbranch
9423 update
9424 if {[catch {exec git branch -D $head} err]} {
9425 notbusy rmbranch
9426 error_popup $err
9427 return
9429 removehead $id $head
9430 removedhead $id $head
9431 redrawtags $id
9432 notbusy rmbranch
9433 dispneartags 0
9434 run refill_reflist
9437 # Display a list of tags and heads
9438 proc showrefs {} {
9439 global showrefstop bgcolor fgcolor selectbgcolor NS
9440 global bglist fglist reflistfilter reflist maincursor
9442 set top .showrefs
9443 set showrefstop $top
9444 if {[winfo exists $top]} {
9445 raise $top
9446 refill_reflist
9447 return
9449 ttk_toplevel $top
9450 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
9451 make_transient $top .
9452 text $top.list -background $bgcolor -foreground $fgcolor \
9453 -selectbackground $selectbgcolor -font mainfont \
9454 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
9455 -width 30 -height 20 -cursor $maincursor \
9456 -spacing1 1 -spacing3 1 -state disabled
9457 $top.list tag configure highlight -background $selectbgcolor
9458 lappend bglist $top.list
9459 lappend fglist $top.list
9460 ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
9461 ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
9462 grid $top.list $top.ysb -sticky nsew
9463 grid $top.xsb x -sticky ew
9464 ${NS}::frame $top.f
9465 ${NS}::label $top.f.l -text "[mc "Filter"]: "
9466 ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
9467 set reflistfilter "*"
9468 trace add variable reflistfilter write reflistfilter_change
9469 pack $top.f.e -side right -fill x -expand 1
9470 pack $top.f.l -side left
9471 grid $top.f - -sticky ew -pady 2
9472 ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
9473 bind $top <Key-Escape> [list destroy $top]
9474 grid $top.close -
9475 grid columnconfigure $top 0 -weight 1
9476 grid rowconfigure $top 0 -weight 1
9477 bind $top.list <1> {break}
9478 bind $top.list <B1-Motion> {break}
9479 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
9480 set reflist {}
9481 refill_reflist
9484 proc sel_reflist {w x y} {
9485 global showrefstop reflist headids tagids otherrefids
9487 if {![winfo exists $showrefstop]} return
9488 set l [lindex [split [$w index "@$x,$y"] "."] 0]
9489 set ref [lindex $reflist [expr {$l-1}]]
9490 set n [lindex $ref 0]
9491 switch -- [lindex $ref 1] {
9492 "H" {selbyid $headids($n)}
9493 "T" {selbyid $tagids($n)}
9494 "o" {selbyid $otherrefids($n)}
9496 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
9499 proc unsel_reflist {} {
9500 global showrefstop
9502 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9503 $showrefstop.list tag remove highlight 0.0 end
9506 proc reflistfilter_change {n1 n2 op} {
9507 global reflistfilter
9509 after cancel refill_reflist
9510 after 200 refill_reflist
9513 proc refill_reflist {} {
9514 global reflist reflistfilter showrefstop headids tagids otherrefids
9515 global curview
9517 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9518 set refs {}
9519 foreach n [array names headids] {
9520 if {[string match $reflistfilter $n]} {
9521 if {[commitinview $headids($n) $curview]} {
9522 lappend refs [list $n H]
9523 } else {
9524 interestedin $headids($n) {run refill_reflist}
9528 foreach n [array names tagids] {
9529 if {[string match $reflistfilter $n]} {
9530 if {[commitinview $tagids($n) $curview]} {
9531 lappend refs [list $n T]
9532 } else {
9533 interestedin $tagids($n) {run refill_reflist}
9537 foreach n [array names otherrefids] {
9538 if {[string match $reflistfilter $n]} {
9539 if {[commitinview $otherrefids($n) $curview]} {
9540 lappend refs [list $n o]
9541 } else {
9542 interestedin $otherrefids($n) {run refill_reflist}
9546 set refs [lsort -index 0 $refs]
9547 if {$refs eq $reflist} return
9549 # Update the contents of $showrefstop.list according to the
9550 # differences between $reflist (old) and $refs (new)
9551 $showrefstop.list conf -state normal
9552 $showrefstop.list insert end "\n"
9553 set i 0
9554 set j 0
9555 while {$i < [llength $reflist] || $j < [llength $refs]} {
9556 if {$i < [llength $reflist]} {
9557 if {$j < [llength $refs]} {
9558 set cmp [string compare [lindex $reflist $i 0] \
9559 [lindex $refs $j 0]]
9560 if {$cmp == 0} {
9561 set cmp [string compare [lindex $reflist $i 1] \
9562 [lindex $refs $j 1]]
9564 } else {
9565 set cmp -1
9567 } else {
9568 set cmp 1
9570 switch -- $cmp {
9571 -1 {
9572 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
9573 incr i
9576 incr i
9577 incr j
9580 set l [expr {$j + 1}]
9581 $showrefstop.list image create $l.0 -align baseline \
9582 -image reficon-[lindex $refs $j 1] -padx 2
9583 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
9584 incr j
9588 set reflist $refs
9589 # delete last newline
9590 $showrefstop.list delete end-2c end-1c
9591 $showrefstop.list conf -state disabled
9594 # Stuff for finding nearby tags
9595 proc getallcommits {} {
9596 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
9597 global idheads idtags idotherrefs allparents tagobjid
9598 global gitdir
9600 if {![info exists allcommits]} {
9601 set nextarc 0
9602 set allcommits 0
9603 set seeds {}
9604 set allcwait 0
9605 set cachedarcs 0
9606 set allccache [file join $gitdir "gitk.cache"]
9607 if {![catch {
9608 set f [open $allccache r]
9609 set allcwait 1
9610 getcache $f
9611 }]} return
9614 if {$allcwait} {
9615 return
9617 set cmd [list | git rev-list --parents]
9618 set allcupdate [expr {$seeds ne {}}]
9619 if {!$allcupdate} {
9620 set ids "--all"
9621 } else {
9622 set refs [concat [array names idheads] [array names idtags] \
9623 [array names idotherrefs]]
9624 set ids {}
9625 set tagobjs {}
9626 foreach name [array names tagobjid] {
9627 lappend tagobjs $tagobjid($name)
9629 foreach id [lsort -unique $refs] {
9630 if {![info exists allparents($id)] &&
9631 [lsearch -exact $tagobjs $id] < 0} {
9632 lappend ids $id
9635 if {$ids ne {}} {
9636 foreach id $seeds {
9637 lappend ids "^$id"
9641 if {$ids ne {}} {
9642 set fd [open [concat $cmd $ids] r]
9643 fconfigure $fd -blocking 0
9644 incr allcommits
9645 nowbusy allcommits
9646 filerun $fd [list getallclines $fd]
9647 } else {
9648 dispneartags 0
9652 # Since most commits have 1 parent and 1 child, we group strings of
9653 # such commits into "arcs" joining branch/merge points (BMPs), which
9654 # are commits that either don't have 1 parent or don't have 1 child.
9656 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
9657 # arcout(id) - outgoing arcs for BMP
9658 # arcids(a) - list of IDs on arc including end but not start
9659 # arcstart(a) - BMP ID at start of arc
9660 # arcend(a) - BMP ID at end of arc
9661 # growing(a) - arc a is still growing
9662 # arctags(a) - IDs out of arcids (excluding end) that have tags
9663 # archeads(a) - IDs out of arcids (excluding end) that have heads
9664 # The start of an arc is at the descendent end, so "incoming" means
9665 # coming from descendents, and "outgoing" means going towards ancestors.
9667 proc getallclines {fd} {
9668 global allparents allchildren idtags idheads nextarc
9669 global arcnos arcids arctags arcout arcend arcstart archeads growing
9670 global seeds allcommits cachedarcs allcupdate
9672 set nid 0
9673 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
9674 set id [lindex $line 0]
9675 if {[info exists allparents($id)]} {
9676 # seen it already
9677 continue
9679 set cachedarcs 0
9680 set olds [lrange $line 1 end]
9681 set allparents($id) $olds
9682 if {![info exists allchildren($id)]} {
9683 set allchildren($id) {}
9684 set arcnos($id) {}
9685 lappend seeds $id
9686 } else {
9687 set a $arcnos($id)
9688 if {[llength $olds] == 1 && [llength $a] == 1} {
9689 lappend arcids($a) $id
9690 if {[info exists idtags($id)]} {
9691 lappend arctags($a) $id
9693 if {[info exists idheads($id)]} {
9694 lappend archeads($a) $id
9696 if {[info exists allparents($olds)]} {
9697 # seen parent already
9698 if {![info exists arcout($olds)]} {
9699 splitarc $olds
9701 lappend arcids($a) $olds
9702 set arcend($a) $olds
9703 unset growing($a)
9705 lappend allchildren($olds) $id
9706 lappend arcnos($olds) $a
9707 continue
9710 foreach a $arcnos($id) {
9711 lappend arcids($a) $id
9712 set arcend($a) $id
9713 unset growing($a)
9716 set ao {}
9717 foreach p $olds {
9718 lappend allchildren($p) $id
9719 set a [incr nextarc]
9720 set arcstart($a) $id
9721 set archeads($a) {}
9722 set arctags($a) {}
9723 set archeads($a) {}
9724 set arcids($a) {}
9725 lappend ao $a
9726 set growing($a) 1
9727 if {[info exists allparents($p)]} {
9728 # seen it already, may need to make a new branch
9729 if {![info exists arcout($p)]} {
9730 splitarc $p
9732 lappend arcids($a) $p
9733 set arcend($a) $p
9734 unset growing($a)
9736 lappend arcnos($p) $a
9738 set arcout($id) $ao
9740 if {$nid > 0} {
9741 global cached_dheads cached_dtags cached_atags
9742 catch {unset cached_dheads}
9743 catch {unset cached_dtags}
9744 catch {unset cached_atags}
9746 if {![eof $fd]} {
9747 return [expr {$nid >= 1000? 2: 1}]
9749 set cacheok 1
9750 if {[catch {
9751 fconfigure $fd -blocking 1
9752 close $fd
9753 } err]} {
9754 # got an error reading the list of commits
9755 # if we were updating, try rereading the whole thing again
9756 if {$allcupdate} {
9757 incr allcommits -1
9758 dropcache $err
9759 return
9761 error_popup "[mc "Error reading commit topology information;\
9762 branch and preceding/following tag information\
9763 will be incomplete."]\n($err)"
9764 set cacheok 0
9766 if {[incr allcommits -1] == 0} {
9767 notbusy allcommits
9768 if {$cacheok} {
9769 run savecache
9772 dispneartags 0
9773 return 0
9776 proc recalcarc {a} {
9777 global arctags archeads arcids idtags idheads
9779 set at {}
9780 set ah {}
9781 foreach id [lrange $arcids($a) 0 end-1] {
9782 if {[info exists idtags($id)]} {
9783 lappend at $id
9785 if {[info exists idheads($id)]} {
9786 lappend ah $id
9789 set arctags($a) $at
9790 set archeads($a) $ah
9793 proc splitarc {p} {
9794 global arcnos arcids nextarc arctags archeads idtags idheads
9795 global arcstart arcend arcout allparents growing
9797 set a $arcnos($p)
9798 if {[llength $a] != 1} {
9799 puts "oops splitarc called but [llength $a] arcs already"
9800 return
9802 set a [lindex $a 0]
9803 set i [lsearch -exact $arcids($a) $p]
9804 if {$i < 0} {
9805 puts "oops splitarc $p not in arc $a"
9806 return
9808 set na [incr nextarc]
9809 if {[info exists arcend($a)]} {
9810 set arcend($na) $arcend($a)
9811 } else {
9812 set l [lindex $allparents([lindex $arcids($a) end]) 0]
9813 set j [lsearch -exact $arcnos($l) $a]
9814 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
9816 set tail [lrange $arcids($a) [expr {$i+1}] end]
9817 set arcids($a) [lrange $arcids($a) 0 $i]
9818 set arcend($a) $p
9819 set arcstart($na) $p
9820 set arcout($p) $na
9821 set arcids($na) $tail
9822 if {[info exists growing($a)]} {
9823 set growing($na) 1
9824 unset growing($a)
9827 foreach id $tail {
9828 if {[llength $arcnos($id)] == 1} {
9829 set arcnos($id) $na
9830 } else {
9831 set j [lsearch -exact $arcnos($id) $a]
9832 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
9836 # reconstruct tags and heads lists
9837 if {$arctags($a) ne {} || $archeads($a) ne {}} {
9838 recalcarc $a
9839 recalcarc $na
9840 } else {
9841 set arctags($na) {}
9842 set archeads($na) {}
9846 # Update things for a new commit added that is a child of one
9847 # existing commit. Used when cherry-picking.
9848 proc addnewchild {id p} {
9849 global allparents allchildren idtags nextarc
9850 global arcnos arcids arctags arcout arcend arcstart archeads growing
9851 global seeds allcommits
9853 if {![info exists allcommits] || ![info exists arcnos($p)]} return
9854 set allparents($id) [list $p]
9855 set allchildren($id) {}
9856 set arcnos($id) {}
9857 lappend seeds $id
9858 lappend allchildren($p) $id
9859 set a [incr nextarc]
9860 set arcstart($a) $id
9861 set archeads($a) {}
9862 set arctags($a) {}
9863 set arcids($a) [list $p]
9864 set arcend($a) $p
9865 if {![info exists arcout($p)]} {
9866 splitarc $p
9868 lappend arcnos($p) $a
9869 set arcout($id) [list $a]
9872 # This implements a cache for the topology information.
9873 # The cache saves, for each arc, the start and end of the arc,
9874 # the ids on the arc, and the outgoing arcs from the end.
9875 proc readcache {f} {
9876 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
9877 global idtags idheads allparents cachedarcs possible_seeds seeds growing
9878 global allcwait
9880 set a $nextarc
9881 set lim $cachedarcs
9882 if {$lim - $a > 500} {
9883 set lim [expr {$a + 500}]
9885 if {[catch {
9886 if {$a == $lim} {
9887 # finish reading the cache and setting up arctags, etc.
9888 set line [gets $f]
9889 if {$line ne "1"} {error "bad final version"}
9890 close $f
9891 foreach id [array names idtags] {
9892 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9893 [llength $allparents($id)] == 1} {
9894 set a [lindex $arcnos($id) 0]
9895 if {$arctags($a) eq {}} {
9896 recalcarc $a
9900 foreach id [array names idheads] {
9901 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9902 [llength $allparents($id)] == 1} {
9903 set a [lindex $arcnos($id) 0]
9904 if {$archeads($a) eq {}} {
9905 recalcarc $a
9909 foreach id [lsort -unique $possible_seeds] {
9910 if {$arcnos($id) eq {}} {
9911 lappend seeds $id
9914 set allcwait 0
9915 } else {
9916 while {[incr a] <= $lim} {
9917 set line [gets $f]
9918 if {[llength $line] != 3} {error "bad line"}
9919 set s [lindex $line 0]
9920 set arcstart($a) $s
9921 lappend arcout($s) $a
9922 if {![info exists arcnos($s)]} {
9923 lappend possible_seeds $s
9924 set arcnos($s) {}
9926 set e [lindex $line 1]
9927 if {$e eq {}} {
9928 set growing($a) 1
9929 } else {
9930 set arcend($a) $e
9931 if {![info exists arcout($e)]} {
9932 set arcout($e) {}
9935 set arcids($a) [lindex $line 2]
9936 foreach id $arcids($a) {
9937 lappend allparents($s) $id
9938 set s $id
9939 lappend arcnos($id) $a
9941 if {![info exists allparents($s)]} {
9942 set allparents($s) {}
9944 set arctags($a) {}
9945 set archeads($a) {}
9947 set nextarc [expr {$a - 1}]
9949 } err]} {
9950 dropcache $err
9951 return 0
9953 if {!$allcwait} {
9954 getallcommits
9956 return $allcwait
9959 proc getcache {f} {
9960 global nextarc cachedarcs possible_seeds
9962 if {[catch {
9963 set line [gets $f]
9964 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9965 # make sure it's an integer
9966 set cachedarcs [expr {int([lindex $line 1])}]
9967 if {$cachedarcs < 0} {error "bad number of arcs"}
9968 set nextarc 0
9969 set possible_seeds {}
9970 run readcache $f
9971 } err]} {
9972 dropcache $err
9974 return 0
9977 proc dropcache {err} {
9978 global allcwait nextarc cachedarcs seeds
9980 #puts "dropping cache ($err)"
9981 foreach v {arcnos arcout arcids arcstart arcend growing \
9982 arctags archeads allparents allchildren} {
9983 global $v
9984 catch {unset $v}
9986 set allcwait 0
9987 set nextarc 0
9988 set cachedarcs 0
9989 set seeds {}
9990 getallcommits
9993 proc writecache {f} {
9994 global cachearc cachedarcs allccache
9995 global arcstart arcend arcnos arcids arcout
9997 set a $cachearc
9998 set lim $cachedarcs
9999 if {$lim - $a > 1000} {
10000 set lim [expr {$a + 1000}]
10002 if {[catch {
10003 while {[incr a] <= $lim} {
10004 if {[info exists arcend($a)]} {
10005 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
10006 } else {
10007 puts $f [list $arcstart($a) {} $arcids($a)]
10010 } err]} {
10011 catch {close $f}
10012 catch {file delete $allccache}
10013 #puts "writing cache failed ($err)"
10014 return 0
10016 set cachearc [expr {$a - 1}]
10017 if {$a > $cachedarcs} {
10018 puts $f "1"
10019 close $f
10020 return 0
10022 return 1
10025 proc savecache {} {
10026 global nextarc cachedarcs cachearc allccache
10028 if {$nextarc == $cachedarcs} return
10029 set cachearc 0
10030 set cachedarcs $nextarc
10031 catch {
10032 set f [open $allccache w]
10033 puts $f [list 1 $cachedarcs]
10034 run writecache $f
10038 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
10039 # or 0 if neither is true.
10040 proc anc_or_desc {a b} {
10041 global arcout arcstart arcend arcnos cached_isanc
10043 if {$arcnos($a) eq $arcnos($b)} {
10044 # Both are on the same arc(s); either both are the same BMP,
10045 # or if one is not a BMP, the other is also not a BMP or is
10046 # the BMP at end of the arc (and it only has 1 incoming arc).
10047 # Or both can be BMPs with no incoming arcs.
10048 if {$a eq $b || $arcnos($a) eq {}} {
10049 return 0
10051 # assert {[llength $arcnos($a)] == 1}
10052 set arc [lindex $arcnos($a) 0]
10053 set i [lsearch -exact $arcids($arc) $a]
10054 set j [lsearch -exact $arcids($arc) $b]
10055 if {$i < 0 || $i > $j} {
10056 return 1
10057 } else {
10058 return -1
10062 if {![info exists arcout($a)]} {
10063 set arc [lindex $arcnos($a) 0]
10064 if {[info exists arcend($arc)]} {
10065 set aend $arcend($arc)
10066 } else {
10067 set aend {}
10069 set a $arcstart($arc)
10070 } else {
10071 set aend $a
10073 if {![info exists arcout($b)]} {
10074 set arc [lindex $arcnos($b) 0]
10075 if {[info exists arcend($arc)]} {
10076 set bend $arcend($arc)
10077 } else {
10078 set bend {}
10080 set b $arcstart($arc)
10081 } else {
10082 set bend $b
10084 if {$a eq $bend} {
10085 return 1
10087 if {$b eq $aend} {
10088 return -1
10090 if {[info exists cached_isanc($a,$bend)]} {
10091 if {$cached_isanc($a,$bend)} {
10092 return 1
10095 if {[info exists cached_isanc($b,$aend)]} {
10096 if {$cached_isanc($b,$aend)} {
10097 return -1
10099 if {[info exists cached_isanc($a,$bend)]} {
10100 return 0
10104 set todo [list $a $b]
10105 set anc($a) a
10106 set anc($b) b
10107 for {set i 0} {$i < [llength $todo]} {incr i} {
10108 set x [lindex $todo $i]
10109 if {$anc($x) eq {}} {
10110 continue
10112 foreach arc $arcnos($x) {
10113 set xd $arcstart($arc)
10114 if {$xd eq $bend} {
10115 set cached_isanc($a,$bend) 1
10116 set cached_isanc($b,$aend) 0
10117 return 1
10118 } elseif {$xd eq $aend} {
10119 set cached_isanc($b,$aend) 1
10120 set cached_isanc($a,$bend) 0
10121 return -1
10123 if {![info exists anc($xd)]} {
10124 set anc($xd) $anc($x)
10125 lappend todo $xd
10126 } elseif {$anc($xd) ne $anc($x)} {
10127 set anc($xd) {}
10131 set cached_isanc($a,$bend) 0
10132 set cached_isanc($b,$aend) 0
10133 return 0
10136 # This identifies whether $desc has an ancestor that is
10137 # a growing tip of the graph and which is not an ancestor of $anc
10138 # and returns 0 if so and 1 if not.
10139 # If we subsequently discover a tag on such a growing tip, and that
10140 # turns out to be a descendent of $anc (which it could, since we
10141 # don't necessarily see children before parents), then $desc
10142 # isn't a good choice to display as a descendent tag of
10143 # $anc (since it is the descendent of another tag which is
10144 # a descendent of $anc). Similarly, $anc isn't a good choice to
10145 # display as a ancestor tag of $desc.
10147 proc is_certain {desc anc} {
10148 global arcnos arcout arcstart arcend growing problems
10150 set certain {}
10151 if {[llength $arcnos($anc)] == 1} {
10152 # tags on the same arc are certain
10153 if {$arcnos($desc) eq $arcnos($anc)} {
10154 return 1
10156 if {![info exists arcout($anc)]} {
10157 # if $anc is partway along an arc, use the start of the arc instead
10158 set a [lindex $arcnos($anc) 0]
10159 set anc $arcstart($a)
10162 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
10163 set x $desc
10164 } else {
10165 set a [lindex $arcnos($desc) 0]
10166 set x $arcend($a)
10168 if {$x == $anc} {
10169 return 1
10171 set anclist [list $x]
10172 set dl($x) 1
10173 set nnh 1
10174 set ngrowanc 0
10175 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
10176 set x [lindex $anclist $i]
10177 if {$dl($x)} {
10178 incr nnh -1
10180 set done($x) 1
10181 foreach a $arcout($x) {
10182 if {[info exists growing($a)]} {
10183 if {![info exists growanc($x)] && $dl($x)} {
10184 set growanc($x) 1
10185 incr ngrowanc
10187 } else {
10188 set y $arcend($a)
10189 if {[info exists dl($y)]} {
10190 if {$dl($y)} {
10191 if {!$dl($x)} {
10192 set dl($y) 0
10193 if {![info exists done($y)]} {
10194 incr nnh -1
10196 if {[info exists growanc($x)]} {
10197 incr ngrowanc -1
10199 set xl [list $y]
10200 for {set k 0} {$k < [llength $xl]} {incr k} {
10201 set z [lindex $xl $k]
10202 foreach c $arcout($z) {
10203 if {[info exists arcend($c)]} {
10204 set v $arcend($c)
10205 if {[info exists dl($v)] && $dl($v)} {
10206 set dl($v) 0
10207 if {![info exists done($v)]} {
10208 incr nnh -1
10210 if {[info exists growanc($v)]} {
10211 incr ngrowanc -1
10213 lappend xl $v
10220 } elseif {$y eq $anc || !$dl($x)} {
10221 set dl($y) 0
10222 lappend anclist $y
10223 } else {
10224 set dl($y) 1
10225 lappend anclist $y
10226 incr nnh
10231 foreach x [array names growanc] {
10232 if {$dl($x)} {
10233 return 0
10235 return 0
10237 return 1
10240 proc validate_arctags {a} {
10241 global arctags idtags
10243 set i -1
10244 set na $arctags($a)
10245 foreach id $arctags($a) {
10246 incr i
10247 if {![info exists idtags($id)]} {
10248 set na [lreplace $na $i $i]
10249 incr i -1
10252 set arctags($a) $na
10255 proc validate_archeads {a} {
10256 global archeads idheads
10258 set i -1
10259 set na $archeads($a)
10260 foreach id $archeads($a) {
10261 incr i
10262 if {![info exists idheads($id)]} {
10263 set na [lreplace $na $i $i]
10264 incr i -1
10267 set archeads($a) $na
10270 # Return the list of IDs that have tags that are descendents of id,
10271 # ignoring IDs that are descendents of IDs already reported.
10272 proc desctags {id} {
10273 global arcnos arcstart arcids arctags idtags allparents
10274 global growing cached_dtags
10276 if {![info exists allparents($id)]} {
10277 return {}
10279 set t1 [clock clicks -milliseconds]
10280 set argid $id
10281 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10282 # part-way along an arc; check that arc first
10283 set a [lindex $arcnos($id) 0]
10284 if {$arctags($a) ne {}} {
10285 validate_arctags $a
10286 set i [lsearch -exact $arcids($a) $id]
10287 set tid {}
10288 foreach t $arctags($a) {
10289 set j [lsearch -exact $arcids($a) $t]
10290 if {$j >= $i} break
10291 set tid $t
10293 if {$tid ne {}} {
10294 return $tid
10297 set id $arcstart($a)
10298 if {[info exists idtags($id)]} {
10299 return $id
10302 if {[info exists cached_dtags($id)]} {
10303 return $cached_dtags($id)
10306 set origid $id
10307 set todo [list $id]
10308 set queued($id) 1
10309 set nc 1
10310 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10311 set id [lindex $todo $i]
10312 set done($id) 1
10313 set ta [info exists hastaggedancestor($id)]
10314 if {!$ta} {
10315 incr nc -1
10317 # ignore tags on starting node
10318 if {!$ta && $i > 0} {
10319 if {[info exists idtags($id)]} {
10320 set tagloc($id) $id
10321 set ta 1
10322 } elseif {[info exists cached_dtags($id)]} {
10323 set tagloc($id) $cached_dtags($id)
10324 set ta 1
10327 foreach a $arcnos($id) {
10328 set d $arcstart($a)
10329 if {!$ta && $arctags($a) ne {}} {
10330 validate_arctags $a
10331 if {$arctags($a) ne {}} {
10332 lappend tagloc($id) [lindex $arctags($a) end]
10335 if {$ta || $arctags($a) ne {}} {
10336 set tomark [list $d]
10337 for {set j 0} {$j < [llength $tomark]} {incr j} {
10338 set dd [lindex $tomark $j]
10339 if {![info exists hastaggedancestor($dd)]} {
10340 if {[info exists done($dd)]} {
10341 foreach b $arcnos($dd) {
10342 lappend tomark $arcstart($b)
10344 if {[info exists tagloc($dd)]} {
10345 unset tagloc($dd)
10347 } elseif {[info exists queued($dd)]} {
10348 incr nc -1
10350 set hastaggedancestor($dd) 1
10354 if {![info exists queued($d)]} {
10355 lappend todo $d
10356 set queued($d) 1
10357 if {![info exists hastaggedancestor($d)]} {
10358 incr nc
10363 set tags {}
10364 foreach id [array names tagloc] {
10365 if {![info exists hastaggedancestor($id)]} {
10366 foreach t $tagloc($id) {
10367 if {[lsearch -exact $tags $t] < 0} {
10368 lappend tags $t
10373 set t2 [clock clicks -milliseconds]
10374 set loopix $i
10376 # remove tags that are descendents of other tags
10377 for {set i 0} {$i < [llength $tags]} {incr i} {
10378 set a [lindex $tags $i]
10379 for {set j 0} {$j < $i} {incr j} {
10380 set b [lindex $tags $j]
10381 set r [anc_or_desc $a $b]
10382 if {$r == 1} {
10383 set tags [lreplace $tags $j $j]
10384 incr j -1
10385 incr i -1
10386 } elseif {$r == -1} {
10387 set tags [lreplace $tags $i $i]
10388 incr i -1
10389 break
10394 if {[array names growing] ne {}} {
10395 # graph isn't finished, need to check if any tag could get
10396 # eclipsed by another tag coming later. Simply ignore any
10397 # tags that could later get eclipsed.
10398 set ctags {}
10399 foreach t $tags {
10400 if {[is_certain $t $origid]} {
10401 lappend ctags $t
10404 if {$tags eq $ctags} {
10405 set cached_dtags($origid) $tags
10406 } else {
10407 set tags $ctags
10409 } else {
10410 set cached_dtags($origid) $tags
10412 set t3 [clock clicks -milliseconds]
10413 if {0 && $t3 - $t1 >= 100} {
10414 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
10415 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10417 return $tags
10420 proc anctags {id} {
10421 global arcnos arcids arcout arcend arctags idtags allparents
10422 global growing cached_atags
10424 if {![info exists allparents($id)]} {
10425 return {}
10427 set t1 [clock clicks -milliseconds]
10428 set argid $id
10429 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10430 # part-way along an arc; check that arc first
10431 set a [lindex $arcnos($id) 0]
10432 if {$arctags($a) ne {}} {
10433 validate_arctags $a
10434 set i [lsearch -exact $arcids($a) $id]
10435 foreach t $arctags($a) {
10436 set j [lsearch -exact $arcids($a) $t]
10437 if {$j > $i} {
10438 return $t
10442 if {![info exists arcend($a)]} {
10443 return {}
10445 set id $arcend($a)
10446 if {[info exists idtags($id)]} {
10447 return $id
10450 if {[info exists cached_atags($id)]} {
10451 return $cached_atags($id)
10454 set origid $id
10455 set todo [list $id]
10456 set queued($id) 1
10457 set taglist {}
10458 set nc 1
10459 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10460 set id [lindex $todo $i]
10461 set done($id) 1
10462 set td [info exists hastaggeddescendent($id)]
10463 if {!$td} {
10464 incr nc -1
10466 # ignore tags on starting node
10467 if {!$td && $i > 0} {
10468 if {[info exists idtags($id)]} {
10469 set tagloc($id) $id
10470 set td 1
10471 } elseif {[info exists cached_atags($id)]} {
10472 set tagloc($id) $cached_atags($id)
10473 set td 1
10476 foreach a $arcout($id) {
10477 if {!$td && $arctags($a) ne {}} {
10478 validate_arctags $a
10479 if {$arctags($a) ne {}} {
10480 lappend tagloc($id) [lindex $arctags($a) 0]
10483 if {![info exists arcend($a)]} continue
10484 set d $arcend($a)
10485 if {$td || $arctags($a) ne {}} {
10486 set tomark [list $d]
10487 for {set j 0} {$j < [llength $tomark]} {incr j} {
10488 set dd [lindex $tomark $j]
10489 if {![info exists hastaggeddescendent($dd)]} {
10490 if {[info exists done($dd)]} {
10491 foreach b $arcout($dd) {
10492 if {[info exists arcend($b)]} {
10493 lappend tomark $arcend($b)
10496 if {[info exists tagloc($dd)]} {
10497 unset tagloc($dd)
10499 } elseif {[info exists queued($dd)]} {
10500 incr nc -1
10502 set hastaggeddescendent($dd) 1
10506 if {![info exists queued($d)]} {
10507 lappend todo $d
10508 set queued($d) 1
10509 if {![info exists hastaggeddescendent($d)]} {
10510 incr nc
10515 set t2 [clock clicks -milliseconds]
10516 set loopix $i
10517 set tags {}
10518 foreach id [array names tagloc] {
10519 if {![info exists hastaggeddescendent($id)]} {
10520 foreach t $tagloc($id) {
10521 if {[lsearch -exact $tags $t] < 0} {
10522 lappend tags $t
10528 # remove tags that are ancestors of other tags
10529 for {set i 0} {$i < [llength $tags]} {incr i} {
10530 set a [lindex $tags $i]
10531 for {set j 0} {$j < $i} {incr j} {
10532 set b [lindex $tags $j]
10533 set r [anc_or_desc $a $b]
10534 if {$r == -1} {
10535 set tags [lreplace $tags $j $j]
10536 incr j -1
10537 incr i -1
10538 } elseif {$r == 1} {
10539 set tags [lreplace $tags $i $i]
10540 incr i -1
10541 break
10546 if {[array names growing] ne {}} {
10547 # graph isn't finished, need to check if any tag could get
10548 # eclipsed by another tag coming later. Simply ignore any
10549 # tags that could later get eclipsed.
10550 set ctags {}
10551 foreach t $tags {
10552 if {[is_certain $origid $t]} {
10553 lappend ctags $t
10556 if {$tags eq $ctags} {
10557 set cached_atags($origid) $tags
10558 } else {
10559 set tags $ctags
10561 } else {
10562 set cached_atags($origid) $tags
10564 set t3 [clock clicks -milliseconds]
10565 if {0 && $t3 - $t1 >= 100} {
10566 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
10567 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10569 return $tags
10572 # Return the list of IDs that have heads that are descendents of id,
10573 # including id itself if it has a head.
10574 proc descheads {id} {
10575 global arcnos arcstart arcids archeads idheads cached_dheads
10576 global allparents
10578 if {![info exists allparents($id)]} {
10579 return {}
10581 set aret {}
10582 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10583 # part-way along an arc; check it first
10584 set a [lindex $arcnos($id) 0]
10585 if {$archeads($a) ne {}} {
10586 validate_archeads $a
10587 set i [lsearch -exact $arcids($a) $id]
10588 foreach t $archeads($a) {
10589 set j [lsearch -exact $arcids($a) $t]
10590 if {$j > $i} break
10591 lappend aret $t
10594 set id $arcstart($a)
10596 set origid $id
10597 set todo [list $id]
10598 set seen($id) 1
10599 set ret {}
10600 for {set i 0} {$i < [llength $todo]} {incr i} {
10601 set id [lindex $todo $i]
10602 if {[info exists cached_dheads($id)]} {
10603 set ret [concat $ret $cached_dheads($id)]
10604 } else {
10605 if {[info exists idheads($id)]} {
10606 lappend ret $id
10608 foreach a $arcnos($id) {
10609 if {$archeads($a) ne {}} {
10610 validate_archeads $a
10611 if {$archeads($a) ne {}} {
10612 set ret [concat $ret $archeads($a)]
10615 set d $arcstart($a)
10616 if {![info exists seen($d)]} {
10617 lappend todo $d
10618 set seen($d) 1
10623 set ret [lsort -unique $ret]
10624 set cached_dheads($origid) $ret
10625 return [concat $ret $aret]
10628 proc addedtag {id} {
10629 global arcnos arcout cached_dtags cached_atags
10631 if {![info exists arcnos($id)]} return
10632 if {![info exists arcout($id)]} {
10633 recalcarc [lindex $arcnos($id) 0]
10635 catch {unset cached_dtags}
10636 catch {unset cached_atags}
10639 proc addedhead {hid head} {
10640 global arcnos arcout cached_dheads
10642 if {![info exists arcnos($hid)]} return
10643 if {![info exists arcout($hid)]} {
10644 recalcarc [lindex $arcnos($hid) 0]
10646 catch {unset cached_dheads}
10649 proc removedhead {hid head} {
10650 global cached_dheads
10652 catch {unset cached_dheads}
10655 proc movedhead {hid head} {
10656 global arcnos arcout cached_dheads
10658 if {![info exists arcnos($hid)]} return
10659 if {![info exists arcout($hid)]} {
10660 recalcarc [lindex $arcnos($hid) 0]
10662 catch {unset cached_dheads}
10665 proc changedrefs {} {
10666 global cached_dheads cached_dtags cached_atags cached_tagcontent
10667 global arctags archeads arcnos arcout idheads idtags
10669 foreach id [concat [array names idheads] [array names idtags]] {
10670 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
10671 set a [lindex $arcnos($id) 0]
10672 if {![info exists donearc($a)]} {
10673 recalcarc $a
10674 set donearc($a) 1
10678 catch {unset cached_tagcontent}
10679 catch {unset cached_dtags}
10680 catch {unset cached_atags}
10681 catch {unset cached_dheads}
10684 proc rereadrefs {} {
10685 global idtags idheads idotherrefs mainheadid
10687 set refids [concat [array names idtags] \
10688 [array names idheads] [array names idotherrefs]]
10689 foreach id $refids {
10690 if {![info exists ref($id)]} {
10691 set ref($id) [listrefs $id]
10694 set oldmainhead $mainheadid
10695 readrefs
10696 changedrefs
10697 set refids [lsort -unique [concat $refids [array names idtags] \
10698 [array names idheads] [array names idotherrefs]]]
10699 foreach id $refids {
10700 set v [listrefs $id]
10701 if {![info exists ref($id)] || $ref($id) != $v} {
10702 redrawtags $id
10705 if {$oldmainhead ne $mainheadid} {
10706 redrawtags $oldmainhead
10707 redrawtags $mainheadid
10709 run refill_reflist
10712 proc listrefs {id} {
10713 global idtags idheads idotherrefs
10715 set x {}
10716 if {[info exists idtags($id)]} {
10717 set x $idtags($id)
10719 set y {}
10720 if {[info exists idheads($id)]} {
10721 set y $idheads($id)
10723 set z {}
10724 if {[info exists idotherrefs($id)]} {
10725 set z $idotherrefs($id)
10727 return [list $x $y $z]
10730 proc showtag {tag isnew} {
10731 global ctext cached_tagcontent tagids linknum tagobjid
10733 if {$isnew} {
10734 addtohistory [list showtag $tag 0] savectextpos
10736 $ctext conf -state normal
10737 clear_ctext
10738 settabs 0
10739 set linknum 0
10740 if {![info exists cached_tagcontent($tag)]} {
10741 catch {
10742 set cached_tagcontent($tag) [exec git cat-file tag $tag]
10745 if {[info exists cached_tagcontent($tag)]} {
10746 set text $cached_tagcontent($tag)
10747 } else {
10748 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
10750 appendwithlinks $text {}
10751 maybe_scroll_ctext 1
10752 $ctext conf -state disabled
10753 init_flist {}
10756 proc doquit {} {
10757 global stopped
10758 global gitktmpdir
10760 set stopped 100
10761 savestuff .
10762 destroy .
10764 if {[info exists gitktmpdir]} {
10765 catch {file delete -force $gitktmpdir}
10769 proc mkfontdisp {font top which} {
10770 global fontattr fontpref $font NS use_ttk
10772 set fontpref($font) [set $font]
10773 ${NS}::button $top.${font}but -text $which \
10774 -command [list choosefont $font $which]
10775 ${NS}::label $top.$font -relief flat -font $font \
10776 -text $fontattr($font,family) -justify left
10777 grid x $top.${font}but $top.$font -sticky w
10780 proc choosefont {font which} {
10781 global fontparam fontlist fonttop fontattr
10782 global prefstop NS
10784 set fontparam(which) $which
10785 set fontparam(font) $font
10786 set fontparam(family) [font actual $font -family]
10787 set fontparam(size) $fontattr($font,size)
10788 set fontparam(weight) $fontattr($font,weight)
10789 set fontparam(slant) $fontattr($font,slant)
10790 set top .gitkfont
10791 set fonttop $top
10792 if {![winfo exists $top]} {
10793 font create sample
10794 eval font config sample [font actual $font]
10795 ttk_toplevel $top
10796 make_transient $top $prefstop
10797 wm title $top [mc "Gitk font chooser"]
10798 ${NS}::label $top.l -textvariable fontparam(which)
10799 pack $top.l -side top
10800 set fontlist [lsort [font families]]
10801 ${NS}::frame $top.f
10802 listbox $top.f.fam -listvariable fontlist \
10803 -yscrollcommand [list $top.f.sb set]
10804 bind $top.f.fam <<ListboxSelect>> selfontfam
10805 ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
10806 pack $top.f.sb -side right -fill y
10807 pack $top.f.fam -side left -fill both -expand 1
10808 pack $top.f -side top -fill both -expand 1
10809 ${NS}::frame $top.g
10810 spinbox $top.g.size -from 4 -to 40 -width 4 \
10811 -textvariable fontparam(size) \
10812 -validatecommand {string is integer -strict %s}
10813 checkbutton $top.g.bold -padx 5 \
10814 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
10815 -variable fontparam(weight) -onvalue bold -offvalue normal
10816 checkbutton $top.g.ital -padx 5 \
10817 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
10818 -variable fontparam(slant) -onvalue italic -offvalue roman
10819 pack $top.g.size $top.g.bold $top.g.ital -side left
10820 pack $top.g -side top
10821 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
10822 -background white
10823 $top.c create text 100 25 -anchor center -text $which -font sample \
10824 -fill black -tags text
10825 bind $top.c <Configure> [list centertext $top.c]
10826 pack $top.c -side top -fill x
10827 ${NS}::frame $top.buts
10828 ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
10829 ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
10830 bind $top <Key-Return> fontok
10831 bind $top <Key-Escape> fontcan
10832 grid $top.buts.ok $top.buts.can
10833 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10834 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10835 pack $top.buts -side bottom -fill x
10836 trace add variable fontparam write chg_fontparam
10837 } else {
10838 raise $top
10839 $top.c itemconf text -text $which
10841 set i [lsearch -exact $fontlist $fontparam(family)]
10842 if {$i >= 0} {
10843 $top.f.fam selection set $i
10844 $top.f.fam see $i
10848 proc centertext {w} {
10849 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
10852 proc fontok {} {
10853 global fontparam fontpref prefstop
10855 set f $fontparam(font)
10856 set fontpref($f) [list $fontparam(family) $fontparam(size)]
10857 if {$fontparam(weight) eq "bold"} {
10858 lappend fontpref($f) "bold"
10860 if {$fontparam(slant) eq "italic"} {
10861 lappend fontpref($f) "italic"
10863 set w $prefstop.notebook.fonts.$f
10864 $w conf -text $fontparam(family) -font $fontpref($f)
10866 fontcan
10869 proc fontcan {} {
10870 global fonttop fontparam
10872 if {[info exists fonttop]} {
10873 catch {destroy $fonttop}
10874 catch {font delete sample}
10875 unset fonttop
10876 unset fontparam
10880 if {[package vsatisfies [package provide Tk] 8.6]} {
10881 # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
10882 # function to make use of it.
10883 proc choosefont {font which} {
10884 tk fontchooser configure -title $which -font $font \
10885 -command [list on_choosefont $font $which]
10886 tk fontchooser show
10888 proc on_choosefont {font which newfont} {
10889 global fontparam
10890 puts stderr "$font $newfont"
10891 array set f [font actual $newfont]
10892 set fontparam(which) $which
10893 set fontparam(font) $font
10894 set fontparam(family) $f(-family)
10895 set fontparam(size) $f(-size)
10896 set fontparam(weight) $f(-weight)
10897 set fontparam(slant) $f(-slant)
10898 fontok
10902 proc selfontfam {} {
10903 global fonttop fontparam
10905 set i [$fonttop.f.fam curselection]
10906 if {$i ne {}} {
10907 set fontparam(family) [$fonttop.f.fam get $i]
10911 proc chg_fontparam {v sub op} {
10912 global fontparam
10914 font config sample -$sub $fontparam($sub)
10917 # Create a property sheet tab page
10918 proc create_prefs_page {w} {
10919 global NS
10920 set parent [join [lrange [split $w .] 0 end-1] .]
10921 if {[winfo class $parent] eq "TNotebook"} {
10922 ${NS}::frame $w
10923 } else {
10924 ${NS}::labelframe $w
10928 proc prefspage_general {notebook} {
10929 global NS maxwidth maxgraphpct showneartags showlocalchanges
10930 global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
10931 global hideremotes want_ttk have_ttk
10933 set page [create_prefs_page $notebook.general]
10935 ${NS}::label $page.ldisp -text [mc "Commit list display options"]
10936 grid $page.ldisp - -sticky w -pady 10
10937 ${NS}::label $page.spacer -text " "
10938 ${NS}::label $page.maxwidthl -text [mc "Maximum graph width (lines)"]
10939 spinbox $page.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10940 grid $page.spacer $page.maxwidthl $page.maxwidth -sticky w
10941 ${NS}::label $page.maxpctl -text [mc "Maximum graph width (% of pane)"]
10942 spinbox $page.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10943 grid x $page.maxpctl $page.maxpct -sticky w
10944 ${NS}::checkbutton $page.showlocal -text [mc "Show local changes"] \
10945 -variable showlocalchanges
10946 grid x $page.showlocal -sticky w
10947 ${NS}::checkbutton $page.autoselect -text [mc "Auto-select SHA1 (length)"] \
10948 -variable autoselect
10949 spinbox $page.autosellen -from 1 -to 40 -width 4 -textvariable autosellen
10950 grid x $page.autoselect $page.autosellen -sticky w
10951 ${NS}::checkbutton $page.hideremotes -text [mc "Hide remote refs"] \
10952 -variable hideremotes
10953 grid x $page.hideremotes -sticky w
10955 ${NS}::label $page.ddisp -text [mc "Diff display options"]
10956 grid $page.ddisp - -sticky w -pady 10
10957 ${NS}::label $page.tabstopl -text [mc "Tab spacing"]
10958 spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10959 grid x $page.tabstopl $page.tabstop -sticky w
10960 ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags"] \
10961 -variable showneartags
10962 grid x $page.ntag -sticky w
10963 ${NS}::checkbutton $page.ldiff -text [mc "Limit diffs to listed paths"] \
10964 -variable limitdiffs
10965 grid x $page.ldiff -sticky w
10966 ${NS}::checkbutton $page.lattr -text [mc "Support per-file encodings"] \
10967 -variable perfile_attrs
10968 grid x $page.lattr -sticky w
10970 ${NS}::entry $page.extdifft -textvariable extdifftool
10971 ${NS}::frame $page.extdifff
10972 ${NS}::label $page.extdifff.l -text [mc "External diff tool" ]
10973 ${NS}::button $page.extdifff.b -text [mc "Choose..."] -command choose_extdiff
10974 pack $page.extdifff.l $page.extdifff.b -side left
10975 pack configure $page.extdifff.l -padx 10
10976 grid x $page.extdifff $page.extdifft -sticky ew
10978 ${NS}::label $page.lgen -text [mc "General options"]
10979 grid $page.lgen - -sticky w -pady 10
10980 ${NS}::checkbutton $page.want_ttk -variable want_ttk \
10981 -text [mc "Use themed widgets"]
10982 if {$have_ttk} {
10983 ${NS}::label $page.ttk_note -text [mc "(change requires restart)"]
10984 } else {
10985 ${NS}::label $page.ttk_note -text [mc "(currently unavailable)"]
10987 grid x $page.want_ttk $page.ttk_note -sticky w
10988 return $page
10991 proc prefspage_colors {notebook} {
10992 global NS uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
10994 set page [create_prefs_page $notebook.colors]
10996 ${NS}::label $page.cdisp -text [mc "Colors: press to choose"]
10997 grid $page.cdisp - -sticky w -pady 10
10998 label $page.ui -padx 40 -relief sunk -background $uicolor
10999 ${NS}::button $page.uibut -text [mc "Interface"] \
11000 -command [list choosecolor uicolor {} $page.ui [mc "interface"] setui]
11001 grid x $page.uibut $page.ui -sticky w
11002 label $page.bg -padx 40 -relief sunk -background $bgcolor
11003 ${NS}::button $page.bgbut -text [mc "Background"] \
11004 -command [list choosecolor bgcolor {} $page.bg [mc "background"] setbg]
11005 grid x $page.bgbut $page.bg -sticky w
11006 label $page.fg -padx 40 -relief sunk -background $fgcolor
11007 ${NS}::button $page.fgbut -text [mc "Foreground"] \
11008 -command [list choosecolor fgcolor {} $page.fg [mc "foreground"] setfg]
11009 grid x $page.fgbut $page.fg -sticky w
11010 label $page.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
11011 ${NS}::button $page.diffoldbut -text [mc "Diff: old lines"] \
11012 -command [list choosecolor diffcolors 0 $page.diffold [mc "diff old lines"] \
11013 [list $ctext tag conf d0 -foreground]]
11014 grid x $page.diffoldbut $page.diffold -sticky w
11015 label $page.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
11016 ${NS}::button $page.diffnewbut -text [mc "Diff: new lines"] \
11017 -command [list choosecolor diffcolors 1 $page.diffnew [mc "diff new lines"] \
11018 [list $ctext tag conf dresult -foreground]]
11019 grid x $page.diffnewbut $page.diffnew -sticky w
11020 label $page.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
11021 ${NS}::button $page.hunksepbut -text [mc "Diff: hunk header"] \
11022 -command [list choosecolor diffcolors 2 $page.hunksep \
11023 [mc "diff hunk header"] \
11024 [list $ctext tag conf hunksep -foreground]]
11025 grid x $page.hunksepbut $page.hunksep -sticky w
11026 label $page.markbgsep -padx 40 -relief sunk -background $markbgcolor
11027 ${NS}::button $page.markbgbut -text [mc "Marked line bg"] \
11028 -command [list choosecolor markbgcolor {} $page.markbgsep \
11029 [mc "marked line background"] \
11030 [list $ctext tag conf omark -background]]
11031 grid x $page.markbgbut $page.markbgsep -sticky w
11032 label $page.selbgsep -padx 40 -relief sunk -background $selectbgcolor
11033 ${NS}::button $page.selbgbut -text [mc "Select bg"] \
11034 -command [list choosecolor selectbgcolor {} $page.selbgsep [mc "background"] setselbg]
11035 grid x $page.selbgbut $page.selbgsep -sticky w
11036 return $page
11039 proc prefspage_fonts {notebook} {
11040 global NS
11041 set page [create_prefs_page $notebook.fonts]
11042 ${NS}::label $page.cfont -text [mc "Fonts: press to choose"]
11043 grid $page.cfont - -sticky w -pady 10
11044 mkfontdisp mainfont $page [mc "Main font"]
11045 mkfontdisp textfont $page [mc "Diff display font"]
11046 mkfontdisp uifont $page [mc "User interface font"]
11047 return $page
11050 proc doprefs {} {
11051 global maxwidth maxgraphpct use_ttk NS
11052 global oldprefs prefstop showneartags showlocalchanges
11053 global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
11054 global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
11055 global hideremotes want_ttk have_ttk
11057 set top .gitkprefs
11058 set prefstop $top
11059 if {[winfo exists $top]} {
11060 raise $top
11061 return
11063 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
11064 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
11065 set oldprefs($v) [set $v]
11067 ttk_toplevel $top
11068 wm title $top [mc "Gitk preferences"]
11069 make_transient $top .
11071 if {[set use_notebook [expr {$use_ttk && [info command ::ttk::notebook] ne ""}]]} {
11072 set notebook [ttk::notebook $top.notebook]
11073 } else {
11074 set notebook [${NS}::frame $top.notebook -borderwidth 0 -relief flat]
11077 lappend pages [prefspage_general $notebook] [mc "General"]
11078 lappend pages [prefspage_colors $notebook] [mc "Colors"]
11079 lappend pages [prefspage_fonts $notebook] [mc "Fonts"]
11080 set col 0
11081 foreach {page title} $pages {
11082 if {$use_notebook} {
11083 $notebook add $page -text $title
11084 } else {
11085 set btn [${NS}::button $notebook.b_[string map {. X} $page] \
11086 -text $title -command [list raise $page]]
11087 $page configure -text $title
11088 grid $btn -row 0 -column [incr col] -sticky w
11089 grid $page -row 1 -column 0 -sticky news -columnspan 100
11093 if {!$use_notebook} {
11094 grid columnconfigure $notebook 0 -weight 1
11095 grid rowconfigure $notebook 1 -weight 1
11096 raise [lindex $pages 0]
11099 grid $notebook -sticky news -padx 2 -pady 2
11100 grid rowconfigure $top 0 -weight 1
11101 grid columnconfigure $top 0 -weight 1
11103 ${NS}::frame $top.buts
11104 ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
11105 ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
11106 bind $top <Key-Return> prefsok
11107 bind $top <Key-Escape> prefscan
11108 grid $top.buts.ok $top.buts.can
11109 grid columnconfigure $top.buts 0 -weight 1 -uniform a
11110 grid columnconfigure $top.buts 1 -weight 1 -uniform a
11111 grid $top.buts - - -pady 10 -sticky ew
11112 grid columnconfigure $top 2 -weight 1
11113 bind $top <Visibility> [list focus $top.buts.ok]
11116 proc choose_extdiff {} {
11117 global extdifftool
11119 set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false]
11120 if {$prog ne {}} {
11121 set extdifftool $prog
11125 proc choosecolor {v vi w x cmd} {
11126 global $v
11128 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
11129 -title [mc "Gitk: choose color for %s" $x]]
11130 if {$c eq {}} return
11131 $w conf -background $c
11132 lset $v $vi $c
11133 eval $cmd $c
11136 proc setselbg {c} {
11137 global bglist cflist
11138 foreach w $bglist {
11139 $w configure -selectbackground $c
11141 $cflist tag configure highlight \
11142 -background [$cflist cget -selectbackground]
11143 allcanvs itemconf secsel -fill $c
11146 # This sets the background color and the color scheme for the whole UI.
11147 # For some reason, tk_setPalette chooses a nasty dark red for selectColor
11148 # if we don't specify one ourselves, which makes the checkbuttons and
11149 # radiobuttons look bad. This chooses white for selectColor if the
11150 # background color is light, or black if it is dark.
11151 proc setui {c} {
11152 if {[tk windowingsystem] eq "win32"} { return }
11153 set bg [winfo rgb . $c]
11154 set selc black
11155 if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} {
11156 set selc white
11158 tk_setPalette background $c selectColor $selc
11161 proc setbg {c} {
11162 global bglist
11164 foreach w $bglist {
11165 $w conf -background $c
11169 proc setfg {c} {
11170 global fglist canv
11172 foreach w $fglist {
11173 $w conf -foreground $c
11175 allcanvs itemconf text -fill $c
11176 $canv itemconf circle -outline $c
11177 $canv itemconf markid -outline $c
11180 proc prefscan {} {
11181 global oldprefs prefstop
11183 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
11184 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
11185 global $v
11186 set $v $oldprefs($v)
11188 catch {destroy $prefstop}
11189 unset prefstop
11190 fontcan
11193 proc prefsok {} {
11194 global maxwidth maxgraphpct
11195 global oldprefs prefstop showneartags showlocalchanges
11196 global fontpref mainfont textfont uifont
11197 global limitdiffs treediffs perfile_attrs
11198 global hideremotes
11200 catch {destroy $prefstop}
11201 unset prefstop
11202 fontcan
11203 set fontchanged 0
11204 if {$mainfont ne $fontpref(mainfont)} {
11205 set mainfont $fontpref(mainfont)
11206 parsefont mainfont $mainfont
11207 eval font configure mainfont [fontflags mainfont]
11208 eval font configure mainfontbold [fontflags mainfont 1]
11209 setcoords
11210 set fontchanged 1
11212 if {$textfont ne $fontpref(textfont)} {
11213 set textfont $fontpref(textfont)
11214 parsefont textfont $textfont
11215 eval font configure textfont [fontflags textfont]
11216 eval font configure textfontbold [fontflags textfont 1]
11218 if {$uifont ne $fontpref(uifont)} {
11219 set uifont $fontpref(uifont)
11220 parsefont uifont $uifont
11221 eval font configure uifont [fontflags uifont]
11223 settabs
11224 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
11225 if {$showlocalchanges} {
11226 doshowlocalchanges
11227 } else {
11228 dohidelocalchanges
11231 if {$limitdiffs != $oldprefs(limitdiffs) ||
11232 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
11233 # treediffs elements are limited by path;
11234 # won't have encodings cached if perfile_attrs was just turned on
11235 catch {unset treediffs}
11237 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
11238 || $maxgraphpct != $oldprefs(maxgraphpct)} {
11239 redisplay
11240 } elseif {$showneartags != $oldprefs(showneartags) ||
11241 $limitdiffs != $oldprefs(limitdiffs)} {
11242 reselectline
11244 if {$hideremotes != $oldprefs(hideremotes)} {
11245 rereadrefs
11249 proc formatdate {d} {
11250 global datetimeformat
11251 if {$d ne {}} {
11252 set d [clock format [lindex $d 0] -format $datetimeformat]
11254 return $d
11257 # This list of encoding names and aliases is distilled from
11258 # http://www.iana.org/assignments/character-sets.
11259 # Not all of them are supported by Tcl.
11260 set encoding_aliases {
11261 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
11262 ISO646-US US-ASCII us IBM367 cp367 csASCII }
11263 { ISO-10646-UTF-1 csISO10646UTF1 }
11264 { ISO_646.basic:1983 ref csISO646basic1983 }
11265 { INVARIANT csINVARIANT }
11266 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
11267 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
11268 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
11269 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
11270 { NATS-DANO iso-ir-9-1 csNATSDANO }
11271 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
11272 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
11273 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
11274 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
11275 { ISO-2022-KR csISO2022KR }
11276 { EUC-KR csEUCKR }
11277 { ISO-2022-JP csISO2022JP }
11278 { ISO-2022-JP-2 csISO2022JP2 }
11279 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
11280 csISO13JISC6220jp }
11281 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
11282 { IT iso-ir-15 ISO646-IT csISO15Italian }
11283 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
11284 { ES iso-ir-17 ISO646-ES csISO17Spanish }
11285 { greek7-old iso-ir-18 csISO18Greek7Old }
11286 { latin-greek iso-ir-19 csISO19LatinGreek }
11287 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
11288 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
11289 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
11290 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
11291 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
11292 { BS_viewdata iso-ir-47 csISO47BSViewdata }
11293 { INIS iso-ir-49 csISO49INIS }
11294 { INIS-8 iso-ir-50 csISO50INIS8 }
11295 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
11296 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
11297 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
11298 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
11299 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
11300 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
11301 csISO60Norwegian1 }
11302 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
11303 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
11304 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
11305 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
11306 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
11307 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
11308 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
11309 { greek7 iso-ir-88 csISO88Greek7 }
11310 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
11311 { iso-ir-90 csISO90 }
11312 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
11313 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
11314 csISO92JISC62991984b }
11315 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
11316 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
11317 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
11318 csISO95JIS62291984handadd }
11319 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
11320 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
11321 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
11322 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
11323 CP819 csISOLatin1 }
11324 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
11325 { T.61-7bit iso-ir-102 csISO102T617bit }
11326 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
11327 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
11328 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
11329 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
11330 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
11331 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
11332 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
11333 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
11334 arabic csISOLatinArabic }
11335 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
11336 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
11337 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
11338 greek greek8 csISOLatinGreek }
11339 { T.101-G2 iso-ir-128 csISO128T101G2 }
11340 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
11341 csISOLatinHebrew }
11342 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
11343 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
11344 { CSN_369103 iso-ir-139 csISO139CSN369103 }
11345 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
11346 { ISO_6937-2-add iso-ir-142 csISOTextComm }
11347 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
11348 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
11349 csISOLatinCyrillic }
11350 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
11351 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
11352 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
11353 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
11354 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
11355 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
11356 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
11357 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
11358 { ISO_10367-box iso-ir-155 csISO10367Box }
11359 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
11360 { latin-lap lap iso-ir-158 csISO158Lap }
11361 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
11362 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
11363 { us-dk csUSDK }
11364 { dk-us csDKUS }
11365 { JIS_X0201 X0201 csHalfWidthKatakana }
11366 { KSC5636 ISO646-KR csKSC5636 }
11367 { ISO-10646-UCS-2 csUnicode }
11368 { ISO-10646-UCS-4 csUCS4 }
11369 { DEC-MCS dec csDECMCS }
11370 { hp-roman8 roman8 r8 csHPRoman8 }
11371 { macintosh mac csMacintosh }
11372 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
11373 csIBM037 }
11374 { IBM038 EBCDIC-INT cp038 csIBM038 }
11375 { IBM273 CP273 csIBM273 }
11376 { IBM274 EBCDIC-BE CP274 csIBM274 }
11377 { IBM275 EBCDIC-BR cp275 csIBM275 }
11378 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
11379 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
11380 { IBM280 CP280 ebcdic-cp-it csIBM280 }
11381 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
11382 { IBM284 CP284 ebcdic-cp-es csIBM284 }
11383 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
11384 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
11385 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
11386 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
11387 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
11388 { IBM424 cp424 ebcdic-cp-he csIBM424 }
11389 { IBM437 cp437 437 csPC8CodePage437 }
11390 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
11391 { IBM775 cp775 csPC775Baltic }
11392 { IBM850 cp850 850 csPC850Multilingual }
11393 { IBM851 cp851 851 csIBM851 }
11394 { IBM852 cp852 852 csPCp852 }
11395 { IBM855 cp855 855 csIBM855 }
11396 { IBM857 cp857 857 csIBM857 }
11397 { IBM860 cp860 860 csIBM860 }
11398 { IBM861 cp861 861 cp-is csIBM861 }
11399 { IBM862 cp862 862 csPC862LatinHebrew }
11400 { IBM863 cp863 863 csIBM863 }
11401 { IBM864 cp864 csIBM864 }
11402 { IBM865 cp865 865 csIBM865 }
11403 { IBM866 cp866 866 csIBM866 }
11404 { IBM868 CP868 cp-ar csIBM868 }
11405 { IBM869 cp869 869 cp-gr csIBM869 }
11406 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
11407 { IBM871 CP871 ebcdic-cp-is csIBM871 }
11408 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
11409 { IBM891 cp891 csIBM891 }
11410 { IBM903 cp903 csIBM903 }
11411 { IBM904 cp904 904 csIBBM904 }
11412 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
11413 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
11414 { IBM1026 CP1026 csIBM1026 }
11415 { EBCDIC-AT-DE csIBMEBCDICATDE }
11416 { EBCDIC-AT-DE-A csEBCDICATDEA }
11417 { EBCDIC-CA-FR csEBCDICCAFR }
11418 { EBCDIC-DK-NO csEBCDICDKNO }
11419 { EBCDIC-DK-NO-A csEBCDICDKNOA }
11420 { EBCDIC-FI-SE csEBCDICFISE }
11421 { EBCDIC-FI-SE-A csEBCDICFISEA }
11422 { EBCDIC-FR csEBCDICFR }
11423 { EBCDIC-IT csEBCDICIT }
11424 { EBCDIC-PT csEBCDICPT }
11425 { EBCDIC-ES csEBCDICES }
11426 { EBCDIC-ES-A csEBCDICESA }
11427 { EBCDIC-ES-S csEBCDICESS }
11428 { EBCDIC-UK csEBCDICUK }
11429 { EBCDIC-US csEBCDICUS }
11430 { UNKNOWN-8BIT csUnknown8BiT }
11431 { MNEMONIC csMnemonic }
11432 { MNEM csMnem }
11433 { VISCII csVISCII }
11434 { VIQR csVIQR }
11435 { KOI8-R csKOI8R }
11436 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
11437 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
11438 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
11439 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
11440 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
11441 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
11442 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
11443 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
11444 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
11445 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
11446 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
11447 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
11448 { IBM1047 IBM-1047 }
11449 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
11450 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
11451 { UNICODE-1-1 csUnicode11 }
11452 { CESU-8 csCESU-8 }
11453 { BOCU-1 csBOCU-1 }
11454 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
11455 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
11456 l8 }
11457 { ISO-8859-15 ISO_8859-15 Latin-9 }
11458 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
11459 { GBK CP936 MS936 windows-936 }
11460 { JIS_Encoding csJISEncoding }
11461 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
11462 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
11463 EUC-JP }
11464 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
11465 { ISO-10646-UCS-Basic csUnicodeASCII }
11466 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
11467 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
11468 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
11469 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
11470 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
11471 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
11472 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
11473 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
11474 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
11475 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
11476 { Adobe-Standard-Encoding csAdobeStandardEncoding }
11477 { Ventura-US csVenturaUS }
11478 { Ventura-International csVenturaInternational }
11479 { PC8-Danish-Norwegian csPC8DanishNorwegian }
11480 { PC8-Turkish csPC8Turkish }
11481 { IBM-Symbols csIBMSymbols }
11482 { IBM-Thai csIBMThai }
11483 { HP-Legal csHPLegal }
11484 { HP-Pi-font csHPPiFont }
11485 { HP-Math8 csHPMath8 }
11486 { Adobe-Symbol-Encoding csHPPSMath }
11487 { HP-DeskTop csHPDesktop }
11488 { Ventura-Math csVenturaMath }
11489 { Microsoft-Publishing csMicrosoftPublishing }
11490 { Windows-31J csWindows31J }
11491 { GB2312 csGB2312 }
11492 { Big5 csBig5 }
11495 proc tcl_encoding {enc} {
11496 global encoding_aliases tcl_encoding_cache
11497 if {[info exists tcl_encoding_cache($enc)]} {
11498 return $tcl_encoding_cache($enc)
11500 set names [encoding names]
11501 set lcnames [string tolower $names]
11502 set enc [string tolower $enc]
11503 set i [lsearch -exact $lcnames $enc]
11504 if {$i < 0} {
11505 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
11506 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
11507 set i [lsearch -exact $lcnames $encx]
11510 if {$i < 0} {
11511 foreach l $encoding_aliases {
11512 set ll [string tolower $l]
11513 if {[lsearch -exact $ll $enc] < 0} continue
11514 # look through the aliases for one that tcl knows about
11515 foreach e $ll {
11516 set i [lsearch -exact $lcnames $e]
11517 if {$i < 0} {
11518 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
11519 set i [lsearch -exact $lcnames $ex]
11522 if {$i >= 0} break
11524 break
11527 set tclenc {}
11528 if {$i >= 0} {
11529 set tclenc [lindex $names $i]
11531 set tcl_encoding_cache($enc) $tclenc
11532 return $tclenc
11535 proc gitattr {path attr default} {
11536 global path_attr_cache
11537 if {[info exists path_attr_cache($attr,$path)]} {
11538 set r $path_attr_cache($attr,$path)
11539 } else {
11540 set r "unspecified"
11541 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
11542 regexp "(.*): $attr: (.*)" $line m f r
11544 set path_attr_cache($attr,$path) $r
11546 if {$r eq "unspecified"} {
11547 return $default
11549 return $r
11552 proc cache_gitattr {attr pathlist} {
11553 global path_attr_cache
11554 set newlist {}
11555 foreach path $pathlist {
11556 if {![info exists path_attr_cache($attr,$path)]} {
11557 lappend newlist $path
11560 set lim 1000
11561 if {[tk windowingsystem] == "win32"} {
11562 # windows has a 32k limit on the arguments to a command...
11563 set lim 30
11565 while {$newlist ne {}} {
11566 set head [lrange $newlist 0 [expr {$lim - 1}]]
11567 set newlist [lrange $newlist $lim end]
11568 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
11569 foreach row [split $rlist "\n"] {
11570 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
11571 if {[string index $path 0] eq "\""} {
11572 set path [encoding convertfrom [lindex $path 0]]
11574 set path_attr_cache($attr,$path) $value
11581 proc get_path_encoding {path} {
11582 global gui_encoding perfile_attrs
11583 set tcl_enc $gui_encoding
11584 if {$path ne {} && $perfile_attrs} {
11585 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
11586 if {$enc2 ne {}} {
11587 set tcl_enc $enc2
11590 return $tcl_enc
11593 # First check that Tcl/Tk is recent enough
11594 if {[catch {package require Tk 8.4} err]} {
11595 show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
11596 Gitk requires at least Tcl/Tk 8.4." list
11597 exit 1
11600 # Unset GIT_TRACE var if set
11601 if { [info exists ::env(GIT_TRACE)] } {
11602 unset ::env(GIT_TRACE)
11605 # defaults...
11606 set wrcomcmd "git diff-tree --stdin -p --pretty"
11608 set gitencoding {}
11609 catch {
11610 set gitencoding [exec git config --get i18n.commitencoding]
11612 catch {
11613 set gitencoding [exec git config --get i18n.logoutputencoding]
11615 if {$gitencoding == ""} {
11616 set gitencoding "utf-8"
11618 set tclencoding [tcl_encoding $gitencoding]
11619 if {$tclencoding == {}} {
11620 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
11623 set gui_encoding [encoding system]
11624 catch {
11625 set enc [exec git config --get gui.encoding]
11626 if {$enc ne {}} {
11627 set tclenc [tcl_encoding $enc]
11628 if {$tclenc ne {}} {
11629 set gui_encoding $tclenc
11630 } else {
11631 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
11636 set log_showroot true
11637 catch {
11638 set log_showroot [exec git config --bool --get log.showroot]
11641 if {[tk windowingsystem] eq "aqua"} {
11642 set mainfont {{Lucida Grande} 9}
11643 set textfont {Monaco 9}
11644 set uifont {{Lucida Grande} 9 bold}
11645 } elseif {![catch {::tk::pkgconfig get fontsystem} xft] && $xft eq "xft"} {
11646 # fontconfig!
11647 set mainfont {sans 9}
11648 set textfont {monospace 9}
11649 set uifont {sans 9 bold}
11650 } else {
11651 set mainfont {Helvetica 9}
11652 set textfont {Courier 9}
11653 set uifont {Helvetica 9 bold}
11655 set tabstop 8
11656 set findmergefiles 0
11657 set maxgraphpct 50
11658 set maxwidth 16
11659 set revlistorder 0
11660 set fastdate 0
11661 set uparrowlen 5
11662 set downarrowlen 5
11663 set mingaplen 100
11664 set cmitmode "patch"
11665 set wrapcomment "none"
11666 set showneartags 1
11667 set hideremotes 0
11668 set maxrefs 20
11669 set maxlinelen 200
11670 set showlocalchanges 1
11671 set limitdiffs 1
11672 set datetimeformat "%Y-%m-%d %H:%M:%S"
11673 set autoselect 1
11674 set autosellen 40
11675 set perfile_attrs 0
11676 set want_ttk 1
11678 if {[tk windowingsystem] eq "aqua"} {
11679 set extdifftool "opendiff"
11680 } else {
11681 set extdifftool "meld"
11684 set colors {green red blue magenta darkgrey brown orange}
11685 if {[tk windowingsystem] eq "win32"} {
11686 set uicolor SystemButtonFace
11687 set bgcolor SystemWindow
11688 set fgcolor SystemButtonText
11689 set selectbgcolor SystemHighlight
11690 } else {
11691 set uicolor grey85
11692 set bgcolor white
11693 set fgcolor black
11694 set selectbgcolor gray85
11696 set diffcolors {red "#00a000" blue}
11697 set diffcontext 3
11698 set ignorespace 0
11699 set worddiff ""
11700 set markbgcolor "#e0e0ff"
11702 set circlecolors {white blue gray blue blue}
11704 # button for popping up context menus
11705 if {[tk windowingsystem] eq "aqua"} {
11706 set ctxbut <Button-2>
11707 } else {
11708 set ctxbut <Button-3>
11711 ## For msgcat loading, first locate the installation location.
11712 if { [info exists ::env(GITK_MSGSDIR)] } {
11713 ## Msgsdir was manually set in the environment.
11714 set gitk_msgsdir $::env(GITK_MSGSDIR)
11715 } else {
11716 ## Let's guess the prefix from argv0.
11717 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
11718 set gitk_libdir [file join $gitk_prefix share gitk lib]
11719 set gitk_msgsdir [file join $gitk_libdir msgs]
11720 unset gitk_prefix
11723 ## Internationalization (i18n) through msgcat and gettext. See
11724 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
11725 package require msgcat
11726 namespace import ::msgcat::mc
11727 ## And eventually load the actual message catalog
11728 ::msgcat::mcload $gitk_msgsdir
11730 catch {source ~/.gitk}
11732 parsefont mainfont $mainfont
11733 eval font create mainfont [fontflags mainfont]
11734 eval font create mainfontbold [fontflags mainfont 1]
11736 parsefont textfont $textfont
11737 eval font create textfont [fontflags textfont]
11738 eval font create textfontbold [fontflags textfont 1]
11740 parsefont uifont $uifont
11741 eval font create uifont [fontflags uifont]
11743 setui $uicolor
11745 setoptions
11747 # check that we can find a .git directory somewhere...
11748 if {[catch {set gitdir [exec git rev-parse --git-dir]}]} {
11749 show_error {} . [mc "Cannot find a git repository here."]
11750 exit 1
11753 set selecthead {}
11754 set selectheadid {}
11756 set revtreeargs {}
11757 set cmdline_files {}
11758 set i 0
11759 set revtreeargscmd {}
11760 foreach arg $argv {
11761 switch -glob -- $arg {
11762 "" { }
11763 "--" {
11764 set cmdline_files [lrange $argv [expr {$i + 1}] end]
11765 break
11767 "--select-commit=*" {
11768 set selecthead [string range $arg 16 end]
11770 "--argscmd=*" {
11771 set revtreeargscmd [string range $arg 10 end]
11773 default {
11774 lappend revtreeargs $arg
11777 incr i
11780 if {$selecthead eq "HEAD"} {
11781 set selecthead {}
11784 if {$i >= [llength $argv] && $revtreeargs ne {}} {
11785 # no -- on command line, but some arguments (other than --argscmd)
11786 if {[catch {
11787 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
11788 set cmdline_files [split $f "\n"]
11789 set n [llength $cmdline_files]
11790 set revtreeargs [lrange $revtreeargs 0 end-$n]
11791 # Unfortunately git rev-parse doesn't produce an error when
11792 # something is both a revision and a filename. To be consistent
11793 # with git log and git rev-list, check revtreeargs for filenames.
11794 foreach arg $revtreeargs {
11795 if {[file exists $arg]} {
11796 show_error {} . [mc "Ambiguous argument '%s': both revision\
11797 and filename" $arg]
11798 exit 1
11801 } err]} {
11802 # unfortunately we get both stdout and stderr in $err,
11803 # so look for "fatal:".
11804 set i [string first "fatal:" $err]
11805 if {$i > 0} {
11806 set err [string range $err [expr {$i + 6}] end]
11808 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
11809 exit 1
11813 set nullid "0000000000000000000000000000000000000000"
11814 set nullid2 "0000000000000000000000000000000000000001"
11815 set nullfile "/dev/null"
11817 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
11818 if {![info exists have_ttk]} {
11819 set have_ttk [llength [info commands ::ttk::style]]
11821 set use_ttk [expr {$have_ttk && $want_ttk}]
11822 set NS [expr {$use_ttk ? "ttk" : ""}]
11824 regexp {^git version ([\d.]*\d)} [exec git version] _ git_version
11826 set show_notes {}
11827 if {[package vcompare $git_version "1.6.6.2"] >= 0} {
11828 set show_notes "--show-notes"
11831 set appname "gitk"
11833 set runq {}
11834 set history {}
11835 set historyindex 0
11836 set fh_serial 0
11837 set nhl_names {}
11838 set highlight_paths {}
11839 set findpattern {}
11840 set searchdirn -forwards
11841 set boldids {}
11842 set boldnameids {}
11843 set diffelide {0 0}
11844 set markingmatches 0
11845 set linkentercount 0
11846 set need_redisplay 0
11847 set nrows_drawn 0
11848 set firsttabstop 0
11850 set nextviewnum 1
11851 set curview 0
11852 set selectedview 0
11853 set selectedhlview [mc "None"]
11854 set highlight_related [mc "None"]
11855 set highlight_files {}
11856 set viewfiles(0) {}
11857 set viewperm(0) 0
11858 set viewargs(0) {}
11859 set viewargscmd(0) {}
11861 set selectedline {}
11862 set numcommits 0
11863 set loginstance 0
11864 set cmdlineok 0
11865 set stopped 0
11866 set stuffsaved 0
11867 set patchnum 0
11868 set lserial 0
11869 set hasworktree [hasworktree]
11870 set cdup {}
11871 if {[expr {[exec git rev-parse --is-inside-work-tree] == "true"}]} {
11872 set cdup [exec git rev-parse --show-cdup]
11874 set worktree [exec git rev-parse --show-toplevel]
11875 setcoords
11876 makewindow
11877 catch {
11878 image create photo gitlogo -width 16 -height 16
11880 image create photo gitlogominus -width 4 -height 2
11881 gitlogominus put #C00000 -to 0 0 4 2
11882 gitlogo copy gitlogominus -to 1 5
11883 gitlogo copy gitlogominus -to 6 5
11884 gitlogo copy gitlogominus -to 11 5
11885 image delete gitlogominus
11887 image create photo gitlogoplus -width 4 -height 4
11888 gitlogoplus put #008000 -to 1 0 3 4
11889 gitlogoplus put #008000 -to 0 1 4 3
11890 gitlogo copy gitlogoplus -to 1 9
11891 gitlogo copy gitlogoplus -to 6 9
11892 gitlogo copy gitlogoplus -to 11 9
11893 image delete gitlogoplus
11895 image create photo gitlogo32 -width 32 -height 32
11896 gitlogo32 copy gitlogo -zoom 2 2
11898 wm iconphoto . -default gitlogo gitlogo32
11900 # wait for the window to become visible
11901 tkwait visibility .
11902 wm title . "$appname: [reponame]"
11903 update
11904 readrefs
11906 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
11907 # create a view for the files/dirs specified on the command line
11908 set curview 1
11909 set selectedview 1
11910 set nextviewnum 2
11911 set viewname(1) [mc "Command line"]
11912 set viewfiles(1) $cmdline_files
11913 set viewargs(1) $revtreeargs
11914 set viewargscmd(1) $revtreeargscmd
11915 set viewperm(1) 0
11916 set vdatemode(1) 0
11917 addviewmenu 1
11918 .bar.view entryconf [mca "Edit view..."] -state normal
11919 .bar.view entryconf [mca "Delete view"] -state normal
11922 if {[info exists permviews]} {
11923 foreach v $permviews {
11924 set n $nextviewnum
11925 incr nextviewnum
11926 set viewname($n) [lindex $v 0]
11927 set viewfiles($n) [lindex $v 1]
11928 set viewargs($n) [lindex $v 2]
11929 set viewargscmd($n) [lindex $v 3]
11930 set viewperm($n) 1
11931 addviewmenu $n
11935 if {[tk windowingsystem] eq "win32"} {
11936 focus -force .
11939 getcommits {}
11941 # Local variables:
11942 # mode: tcl
11943 # indent-tabs-mode: t
11944 # tab-width: 8
11945 # End: