gitk: fix the display of files when filtered by path
[git/dscho.git] / gitk-git / gitk
blobb728345c4c8dbeb5ddcef9767ff775a8f79bfc38
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright © 2005-2009 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 package require Tk
12 proc gitdir {} {
13 global env
14 if {[info exists env(GIT_DIR)]} {
15 return $env(GIT_DIR)
16 } else {
17 return [exec git rev-parse --git-dir]
21 proc gitworktree {} {
22 variable _gitworktree
23 if {[info exists _gitworktree]} {
24 return $_gitworktree
26 # v1.7.0 introduced --show-toplevel to return the canonical work-tree
27 if {[catch {set _gitworktree [exec git rev-parse --show-toplevel]}]} {
28 # try to set work tree from environment, core.worktree or use
29 # cdup to obtain a relative path to the top of the worktree. If
30 # run from the top, the ./ prefix ensures normalize expands pwd.
31 if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
32 catch {set _gitworktree [exec git config --get core.worktree]}
33 if {$_gitworktree eq ""} {
34 set _gitworktree [file normalize ./[exec git rev-parse --show-cdup]]
38 return $_gitworktree
41 # A simple scheduler for compute-intensive stuff.
42 # The aim is to make sure that event handlers for GUI actions can
43 # run at least every 50-100 ms. Unfortunately fileevent handlers are
44 # run before X event handlers, so reading from a fast source can
45 # make the GUI completely unresponsive.
46 proc run args {
47 global isonrunq runq currunq
49 set script $args
50 if {[info exists isonrunq($script)]} return
51 if {$runq eq {} && ![info exists currunq]} {
52 after idle dorunq
54 lappend runq [list {} $script]
55 set isonrunq($script) 1
58 proc filerun {fd script} {
59 fileevent $fd readable [list filereadable $fd $script]
62 proc filereadable {fd script} {
63 global runq currunq
65 fileevent $fd readable {}
66 if {$runq eq {} && ![info exists currunq]} {
67 after idle dorunq
69 lappend runq [list $fd $script]
72 proc nukefile {fd} {
73 global runq
75 for {set i 0} {$i < [llength $runq]} {} {
76 if {[lindex $runq $i 0] eq $fd} {
77 set runq [lreplace $runq $i $i]
78 } else {
79 incr i
84 proc dorunq {} {
85 global isonrunq runq currunq
87 set tstart [clock clicks -milliseconds]
88 set t0 $tstart
89 while {[llength $runq] > 0} {
90 set fd [lindex $runq 0 0]
91 set script [lindex $runq 0 1]
92 set currunq [lindex $runq 0]
93 set runq [lrange $runq 1 end]
94 set repeat [eval $script]
95 unset currunq
96 set t1 [clock clicks -milliseconds]
97 set t [expr {$t1 - $t0}]
98 if {$repeat ne {} && $repeat} {
99 if {$fd eq {} || $repeat == 2} {
100 # script returns 1 if it wants to be readded
101 # file readers return 2 if they could do more straight away
102 lappend runq [list $fd $script]
103 } else {
104 fileevent $fd readable [list filereadable $fd $script]
106 } elseif {$fd eq {}} {
107 unset isonrunq($script)
109 set t0 $t1
110 if {$t1 - $tstart >= 80} break
112 if {$runq ne {}} {
113 after idle dorunq
117 proc reg_instance {fd} {
118 global commfd leftover loginstance
120 set i [incr loginstance]
121 set commfd($i) $fd
122 set leftover($i) {}
123 return $i
126 proc unmerged_files {files} {
127 global nr_unmerged
129 # find the list of unmerged files
130 set mlist {}
131 set nr_unmerged 0
132 if {[catch {
133 set fd [open "| git ls-files -u" r]
134 } err]} {
135 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
136 exit 1
138 while {[gets $fd line] >= 0} {
139 set i [string first "\t" $line]
140 if {$i < 0} continue
141 set fname [string range $line [expr {$i+1}] end]
142 if {[lsearch -exact $mlist $fname] >= 0} continue
143 incr nr_unmerged
144 if {$files eq {} || [path_filter $files $fname]} {
145 lappend mlist $fname
148 catch {close $fd}
149 return $mlist
152 proc parseviewargs {n arglist} {
153 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs env
154 global worddiff git_version
156 set vdatemode($n) 0
157 set vmergeonly($n) 0
158 set glflags {}
159 set diffargs {}
160 set nextisval 0
161 set revargs {}
162 set origargs $arglist
163 set allknown 1
164 set filtered 0
165 set i -1
166 foreach arg $arglist {
167 incr i
168 if {$nextisval} {
169 lappend glflags $arg
170 set nextisval 0
171 continue
173 switch -glob -- $arg {
174 "-d" -
175 "--date-order" {
176 set vdatemode($n) 1
177 # remove from origargs in case we hit an unknown option
178 set origargs [lreplace $origargs $i $i]
179 incr i -1
181 "-[puabwcrRBMC]" -
182 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
183 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
184 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
185 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
186 "--ignore-space-change" - "-U*" - "--unified=*" {
187 # These request or affect diff output, which we don't want.
188 # Some could be used to set our defaults for diff display.
189 lappend diffargs $arg
191 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
192 "--name-only" - "--name-status" - "--color" -
193 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
194 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
195 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
196 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
197 "--objects" - "--objects-edge" - "--reverse" {
198 # These cause our parsing of git log's output to fail, or else
199 # they're options we want to set ourselves, so ignore them.
201 "--color-words*" - "--word-diff=color" {
202 # These trigger a word diff in the console interface,
203 # so help the user by enabling our own support
204 if {[package vcompare $git_version "1.7.2"] >= 0} {
205 set worddiff [mc "Color words"]
208 "--word-diff*" {
209 if {[package vcompare $git_version "1.7.2"] >= 0} {
210 set worddiff [mc "Markup words"]
213 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
214 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
215 "--full-history" - "--dense" - "--sparse" -
216 "--follow" - "--left-right" - "--encoding=*" {
217 # These are harmless, and some are even useful
218 lappend glflags $arg
220 "--diff-filter=*" - "--no-merges" - "--unpacked" -
221 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
222 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
223 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
224 "--remove-empty" - "--first-parent" - "--cherry-pick" -
225 "-S*" - "--pickaxe-all" - "--pickaxe-regex" -
226 "--simplify-by-decoration" {
227 # These mean that we get a subset of the commits
228 set filtered 1
229 lappend glflags $arg
231 "-n" {
232 # This appears to be the only one that has a value as a
233 # separate word following it
234 set filtered 1
235 set nextisval 1
236 lappend glflags $arg
238 "--not" - "--all" {
239 lappend revargs $arg
241 "--merge" {
242 set vmergeonly($n) 1
243 # git rev-parse doesn't understand --merge
244 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
246 "--no-replace-objects" {
247 set env(GIT_NO_REPLACE_OBJECTS) "1"
249 "-*" {
250 # Other flag arguments including -<n>
251 if {[string is digit -strict [string range $arg 1 end]]} {
252 set filtered 1
253 } else {
254 # a flag argument that we don't recognize;
255 # that means we can't optimize
256 set allknown 0
258 lappend glflags $arg
260 default {
261 # Non-flag arguments specify commits or ranges of commits
262 if {[string match "*...*" $arg]} {
263 lappend revargs --gitk-symmetric-diff-marker
265 lappend revargs $arg
269 set vdflags($n) $diffargs
270 set vflags($n) $glflags
271 set vrevs($n) $revargs
272 set vfiltered($n) $filtered
273 set vorigargs($n) $origargs
274 return $allknown
277 proc parseviewrevs {view revs} {
278 global vposids vnegids
280 if {$revs eq {}} {
281 set revs HEAD
283 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
284 # we get stdout followed by stderr in $err
285 # for an unknown rev, git rev-parse echoes it and then errors out
286 set errlines [split $err "\n"]
287 set badrev {}
288 for {set l 0} {$l < [llength $errlines]} {incr l} {
289 set line [lindex $errlines $l]
290 if {!([string length $line] == 40 && [string is xdigit $line])} {
291 if {[string match "fatal:*" $line]} {
292 if {[string match "fatal: ambiguous argument*" $line]
293 && $badrev ne {}} {
294 if {[llength $badrev] == 1} {
295 set err "unknown revision $badrev"
296 } else {
297 set err "unknown revisions: [join $badrev ", "]"
299 } else {
300 set err [join [lrange $errlines $l end] "\n"]
302 break
304 lappend badrev $line
307 error_popup "[mc "Error parsing revisions:"] $err"
308 return {}
310 set ret {}
311 set pos {}
312 set neg {}
313 set sdm 0
314 foreach id [split $ids "\n"] {
315 if {$id eq "--gitk-symmetric-diff-marker"} {
316 set sdm 4
317 } elseif {[string match "^*" $id]} {
318 if {$sdm != 1} {
319 lappend ret $id
320 if {$sdm == 3} {
321 set sdm 0
324 lappend neg [string range $id 1 end]
325 } else {
326 if {$sdm != 2} {
327 lappend ret $id
328 } else {
329 lset ret end $id...[lindex $ret end]
331 lappend pos $id
333 incr sdm -1
335 set vposids($view) $pos
336 set vnegids($view) $neg
337 return $ret
340 # Start off a git log process and arrange to read its output
341 proc start_rev_list {view} {
342 global startmsecs commitidx viewcomplete curview
343 global tclencoding
344 global viewargs viewargscmd viewfiles vfilelimit
345 global showlocalchanges
346 global viewactive viewinstances vmergeonly
347 global mainheadid viewmainheadid viewmainheadid_orig
348 global vcanopt vflags vrevs vorigargs
349 global show_notes
351 set startmsecs [clock clicks -milliseconds]
352 set commitidx($view) 0
353 # these are set this way for the error exits
354 set viewcomplete($view) 1
355 set viewactive($view) 0
356 varcinit $view
358 set args $viewargs($view)
359 if {$viewargscmd($view) ne {}} {
360 if {[catch {
361 set str [exec sh -c $viewargscmd($view)]
362 } err]} {
363 error_popup "[mc "Error executing --argscmd command:"] $err"
364 return 0
366 set args [concat $args [split $str "\n"]]
368 set vcanopt($view) [parseviewargs $view $args]
370 set files $viewfiles($view)
371 if {$vmergeonly($view)} {
372 set files [unmerged_files $files]
373 if {$files eq {}} {
374 global nr_unmerged
375 if {$nr_unmerged == 0} {
376 error_popup [mc "No files selected: --merge specified but\
377 no files are unmerged."]
378 } else {
379 error_popup [mc "No files selected: --merge specified but\
380 no unmerged files are within file limit."]
382 return 0
385 set vfilelimit($view) $files
387 if {$vcanopt($view)} {
388 set revs [parseviewrevs $view $vrevs($view)]
389 if {$revs eq {}} {
390 return 0
392 set args [limit_arg_length [concat $vflags($view) $revs]]
393 } else {
394 set args $vorigargs($view)
397 if {[catch {
398 set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
399 --parents --boundary $args "--" $files] r]
400 } err]} {
401 error_popup "[mc "Error executing git log:"] $err"
402 return 0
404 set i [reg_instance $fd]
405 set viewinstances($view) [list $i]
406 set viewmainheadid($view) $mainheadid
407 set viewmainheadid_orig($view) $mainheadid
408 if {$files ne {} && $mainheadid ne {}} {
409 get_viewmainhead $view
411 if {$showlocalchanges && $viewmainheadid($view) ne {}} {
412 interestedin $viewmainheadid($view) dodiffindex
414 fconfigure $fd -blocking 0 -translation lf -eofchar {}
415 if {$tclencoding != {}} {
416 fconfigure $fd -encoding $tclencoding
418 filerun $fd [list getcommitlines $fd $i $view 0]
419 nowbusy $view [mc "Reading"]
420 set viewcomplete($view) 0
421 set viewactive($view) 1
422 return 1
425 proc stop_instance {inst} {
426 global commfd leftover
428 set fd $commfd($inst)
429 catch {
430 set pid [pid $fd]
432 if {$::tcl_platform(platform) eq {windows}} {
433 exec kill -f $pid
434 } else {
435 exec kill $pid
438 catch {close $fd}
439 nukefile $fd
440 unset commfd($inst)
441 unset leftover($inst)
444 proc stop_backends {} {
445 global commfd
447 foreach inst [array names commfd] {
448 stop_instance $inst
452 proc stop_rev_list {view} {
453 global viewinstances
455 foreach inst $viewinstances($view) {
456 stop_instance $inst
458 set viewinstances($view) {}
461 proc reset_pending_select {selid} {
462 global pending_select mainheadid selectheadid
464 if {$selid ne {}} {
465 set pending_select $selid
466 } elseif {$selectheadid ne {}} {
467 set pending_select $selectheadid
468 } else {
469 set pending_select $mainheadid
473 proc getcommits {selid} {
474 global canv curview need_redisplay viewactive
476 initlayout
477 if {[start_rev_list $curview]} {
478 reset_pending_select $selid
479 show_status [mc "Reading commits..."]
480 set need_redisplay 1
481 } else {
482 show_status [mc "No commits selected"]
486 proc updatecommits {} {
487 global curview vcanopt vorigargs vfilelimit viewinstances
488 global viewactive viewcomplete tclencoding
489 global startmsecs showneartags showlocalchanges
490 global mainheadid viewmainheadid viewmainheadid_orig pending_select
491 global isworktree
492 global varcid vposids vnegids vflags vrevs
493 global show_notes
495 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
496 rereadrefs
497 set view $curview
498 if {$mainheadid ne $viewmainheadid_orig($view)} {
499 if {$showlocalchanges} {
500 dohidelocalchanges
502 set viewmainheadid($view) $mainheadid
503 set viewmainheadid_orig($view) $mainheadid
504 if {$vfilelimit($view) ne {}} {
505 get_viewmainhead $view
508 if {$showlocalchanges} {
509 doshowlocalchanges
511 if {$vcanopt($view)} {
512 set oldpos $vposids($view)
513 set oldneg $vnegids($view)
514 set revs [parseviewrevs $view $vrevs($view)]
515 if {$revs eq {}} {
516 return
518 # note: getting the delta when negative refs change is hard,
519 # and could require multiple git log invocations, so in that
520 # case we ask git log for all the commits (not just the delta)
521 if {$oldneg eq $vnegids($view)} {
522 set newrevs {}
523 set npos 0
524 # take out positive refs that we asked for before or
525 # that we have already seen
526 foreach rev $revs {
527 if {[string length $rev] == 40} {
528 if {[lsearch -exact $oldpos $rev] < 0
529 && ![info exists varcid($view,$rev)]} {
530 lappend newrevs $rev
531 incr npos
533 } else {
534 lappend $newrevs $rev
537 if {$npos == 0} return
538 set revs $newrevs
539 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
541 set args [concat $vflags($view) $revs --not $oldpos]
542 } else {
543 set args $vorigargs($view)
545 if {[catch {
546 set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
547 --parents --boundary $args "--" $vfilelimit($view)] r]
548 } err]} {
549 error_popup "[mc "Error executing git log:"] $err"
550 return
552 if {$viewactive($view) == 0} {
553 set startmsecs [clock clicks -milliseconds]
555 set i [reg_instance $fd]
556 lappend viewinstances($view) $i
557 fconfigure $fd -blocking 0 -translation lf -eofchar {}
558 if {$tclencoding != {}} {
559 fconfigure $fd -encoding $tclencoding
561 filerun $fd [list getcommitlines $fd $i $view 1]
562 incr viewactive($view)
563 set viewcomplete($view) 0
564 reset_pending_select {}
565 nowbusy $view [mc "Reading"]
566 if {$showneartags} {
567 getallcommits
571 proc reloadcommits {} {
572 global curview viewcomplete selectedline currentid thickerline
573 global showneartags treediffs commitinterest cached_commitrow
574 global targetid
576 set selid {}
577 if {$selectedline ne {}} {
578 set selid $currentid
581 if {!$viewcomplete($curview)} {
582 stop_rev_list $curview
584 resetvarcs $curview
585 set selectedline {}
586 catch {unset currentid}
587 catch {unset thickerline}
588 catch {unset treediffs}
589 readrefs
590 changedrefs
591 if {$showneartags} {
592 getallcommits
594 clear_display
595 catch {unset commitinterest}
596 catch {unset cached_commitrow}
597 catch {unset targetid}
598 setcanvscroll
599 getcommits $selid
600 return 0
603 # This makes a string representation of a positive integer which
604 # sorts as a string in numerical order
605 proc strrep {n} {
606 if {$n < 16} {
607 return [format "%x" $n]
608 } elseif {$n < 256} {
609 return [format "x%.2x" $n]
610 } elseif {$n < 65536} {
611 return [format "y%.4x" $n]
613 return [format "z%.8x" $n]
616 # Procedures used in reordering commits from git log (without
617 # --topo-order) into the order for display.
619 proc varcinit {view} {
620 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
621 global vtokmod varcmod vrowmod varcix vlastins
623 set varcstart($view) {{}}
624 set vupptr($view) {0}
625 set vdownptr($view) {0}
626 set vleftptr($view) {0}
627 set vbackptr($view) {0}
628 set varctok($view) {{}}
629 set varcrow($view) {{}}
630 set vtokmod($view) {}
631 set varcmod($view) 0
632 set vrowmod($view) 0
633 set varcix($view) {{}}
634 set vlastins($view) {0}
637 proc resetvarcs {view} {
638 global varcid varccommits parents children vseedcount ordertok
640 foreach vid [array names varcid $view,*] {
641 unset varcid($vid)
642 unset children($vid)
643 unset parents($vid)
645 # some commits might have children but haven't been seen yet
646 foreach vid [array names children $view,*] {
647 unset children($vid)
649 foreach va [array names varccommits $view,*] {
650 unset varccommits($va)
652 foreach vd [array names vseedcount $view,*] {
653 unset vseedcount($vd)
655 catch {unset ordertok}
658 # returns a list of the commits with no children
659 proc seeds {v} {
660 global vdownptr vleftptr varcstart
662 set ret {}
663 set a [lindex $vdownptr($v) 0]
664 while {$a != 0} {
665 lappend ret [lindex $varcstart($v) $a]
666 set a [lindex $vleftptr($v) $a]
668 return $ret
671 proc newvarc {view id} {
672 global varcid varctok parents children vdatemode
673 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
674 global commitdata commitinfo vseedcount varccommits vlastins
676 set a [llength $varctok($view)]
677 set vid $view,$id
678 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
679 if {![info exists commitinfo($id)]} {
680 parsecommit $id $commitdata($id) 1
682 set cdate [lindex $commitinfo($id) 4]
683 if {![string is integer -strict $cdate]} {
684 set cdate 0
686 if {![info exists vseedcount($view,$cdate)]} {
687 set vseedcount($view,$cdate) -1
689 set c [incr vseedcount($view,$cdate)]
690 set cdate [expr {$cdate ^ 0xffffffff}]
691 set tok "s[strrep $cdate][strrep $c]"
692 } else {
693 set tok {}
695 set ka 0
696 if {[llength $children($vid)] > 0} {
697 set kid [lindex $children($vid) end]
698 set k $varcid($view,$kid)
699 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
700 set ki $kid
701 set ka $k
702 set tok [lindex $varctok($view) $k]
705 if {$ka != 0} {
706 set i [lsearch -exact $parents($view,$ki) $id]
707 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
708 append tok [strrep $j]
710 set c [lindex $vlastins($view) $ka]
711 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
712 set c $ka
713 set b [lindex $vdownptr($view) $ka]
714 } else {
715 set b [lindex $vleftptr($view) $c]
717 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
718 set c $b
719 set b [lindex $vleftptr($view) $c]
721 if {$c == $ka} {
722 lset vdownptr($view) $ka $a
723 lappend vbackptr($view) 0
724 } else {
725 lset vleftptr($view) $c $a
726 lappend vbackptr($view) $c
728 lset vlastins($view) $ka $a
729 lappend vupptr($view) $ka
730 lappend vleftptr($view) $b
731 if {$b != 0} {
732 lset vbackptr($view) $b $a
734 lappend varctok($view) $tok
735 lappend varcstart($view) $id
736 lappend vdownptr($view) 0
737 lappend varcrow($view) {}
738 lappend varcix($view) {}
739 set varccommits($view,$a) {}
740 lappend vlastins($view) 0
741 return $a
744 proc splitvarc {p v} {
745 global varcid varcstart varccommits varctok vtokmod
746 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
748 set oa $varcid($v,$p)
749 set otok [lindex $varctok($v) $oa]
750 set ac $varccommits($v,$oa)
751 set i [lsearch -exact $varccommits($v,$oa) $p]
752 if {$i <= 0} return
753 set na [llength $varctok($v)]
754 # "%" sorts before "0"...
755 set tok "$otok%[strrep $i]"
756 lappend varctok($v) $tok
757 lappend varcrow($v) {}
758 lappend varcix($v) {}
759 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
760 set varccommits($v,$na) [lrange $ac $i end]
761 lappend varcstart($v) $p
762 foreach id $varccommits($v,$na) {
763 set varcid($v,$id) $na
765 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
766 lappend vlastins($v) [lindex $vlastins($v) $oa]
767 lset vdownptr($v) $oa $na
768 lset vlastins($v) $oa 0
769 lappend vupptr($v) $oa
770 lappend vleftptr($v) 0
771 lappend vbackptr($v) 0
772 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
773 lset vupptr($v) $b $na
775 if {[string compare $otok $vtokmod($v)] <= 0} {
776 modify_arc $v $oa
780 proc renumbervarc {a v} {
781 global parents children varctok varcstart varccommits
782 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
784 set t1 [clock clicks -milliseconds]
785 set todo {}
786 set isrelated($a) 1
787 set kidchanged($a) 1
788 set ntot 0
789 while {$a != 0} {
790 if {[info exists isrelated($a)]} {
791 lappend todo $a
792 set id [lindex $varccommits($v,$a) end]
793 foreach p $parents($v,$id) {
794 if {[info exists varcid($v,$p)]} {
795 set isrelated($varcid($v,$p)) 1
799 incr ntot
800 set b [lindex $vdownptr($v) $a]
801 if {$b == 0} {
802 while {$a != 0} {
803 set b [lindex $vleftptr($v) $a]
804 if {$b != 0} break
805 set a [lindex $vupptr($v) $a]
808 set a $b
810 foreach a $todo {
811 if {![info exists kidchanged($a)]} continue
812 set id [lindex $varcstart($v) $a]
813 if {[llength $children($v,$id)] > 1} {
814 set children($v,$id) [lsort -command [list vtokcmp $v] \
815 $children($v,$id)]
817 set oldtok [lindex $varctok($v) $a]
818 if {!$vdatemode($v)} {
819 set tok {}
820 } else {
821 set tok $oldtok
823 set ka 0
824 set kid [last_real_child $v,$id]
825 if {$kid ne {}} {
826 set k $varcid($v,$kid)
827 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
828 set ki $kid
829 set ka $k
830 set tok [lindex $varctok($v) $k]
833 if {$ka != 0} {
834 set i [lsearch -exact $parents($v,$ki) $id]
835 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
836 append tok [strrep $j]
838 if {$tok eq $oldtok} {
839 continue
841 set id [lindex $varccommits($v,$a) end]
842 foreach p $parents($v,$id) {
843 if {[info exists varcid($v,$p)]} {
844 set kidchanged($varcid($v,$p)) 1
845 } else {
846 set sortkids($p) 1
849 lset varctok($v) $a $tok
850 set b [lindex $vupptr($v) $a]
851 if {$b != $ka} {
852 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
853 modify_arc $v $ka
855 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
856 modify_arc $v $b
858 set c [lindex $vbackptr($v) $a]
859 set d [lindex $vleftptr($v) $a]
860 if {$c == 0} {
861 lset vdownptr($v) $b $d
862 } else {
863 lset vleftptr($v) $c $d
865 if {$d != 0} {
866 lset vbackptr($v) $d $c
868 if {[lindex $vlastins($v) $b] == $a} {
869 lset vlastins($v) $b $c
871 lset vupptr($v) $a $ka
872 set c [lindex $vlastins($v) $ka]
873 if {$c == 0 || \
874 [string compare $tok [lindex $varctok($v) $c]] < 0} {
875 set c $ka
876 set b [lindex $vdownptr($v) $ka]
877 } else {
878 set b [lindex $vleftptr($v) $c]
880 while {$b != 0 && \
881 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
882 set c $b
883 set b [lindex $vleftptr($v) $c]
885 if {$c == $ka} {
886 lset vdownptr($v) $ka $a
887 lset vbackptr($v) $a 0
888 } else {
889 lset vleftptr($v) $c $a
890 lset vbackptr($v) $a $c
892 lset vleftptr($v) $a $b
893 if {$b != 0} {
894 lset vbackptr($v) $b $a
896 lset vlastins($v) $ka $a
899 foreach id [array names sortkids] {
900 if {[llength $children($v,$id)] > 1} {
901 set children($v,$id) [lsort -command [list vtokcmp $v] \
902 $children($v,$id)]
905 set t2 [clock clicks -milliseconds]
906 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
909 # Fix up the graph after we have found out that in view $v,
910 # $p (a commit that we have already seen) is actually the parent
911 # of the last commit in arc $a.
912 proc fix_reversal {p a v} {
913 global varcid varcstart varctok vupptr
915 set pa $varcid($v,$p)
916 if {$p ne [lindex $varcstart($v) $pa]} {
917 splitvarc $p $v
918 set pa $varcid($v,$p)
920 # seeds always need to be renumbered
921 if {[lindex $vupptr($v) $pa] == 0 ||
922 [string compare [lindex $varctok($v) $a] \
923 [lindex $varctok($v) $pa]] > 0} {
924 renumbervarc $pa $v
928 proc insertrow {id p v} {
929 global cmitlisted children parents varcid varctok vtokmod
930 global varccommits ordertok commitidx numcommits curview
931 global targetid targetrow
933 readcommit $id
934 set vid $v,$id
935 set cmitlisted($vid) 1
936 set children($vid) {}
937 set parents($vid) [list $p]
938 set a [newvarc $v $id]
939 set varcid($vid) $a
940 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
941 modify_arc $v $a
943 lappend varccommits($v,$a) $id
944 set vp $v,$p
945 if {[llength [lappend children($vp) $id]] > 1} {
946 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
947 catch {unset ordertok}
949 fix_reversal $p $a $v
950 incr commitidx($v)
951 if {$v == $curview} {
952 set numcommits $commitidx($v)
953 setcanvscroll
954 if {[info exists targetid]} {
955 if {![comes_before $targetid $p]} {
956 incr targetrow
962 proc insertfakerow {id p} {
963 global varcid varccommits parents children cmitlisted
964 global commitidx varctok vtokmod targetid targetrow curview numcommits
966 set v $curview
967 set a $varcid($v,$p)
968 set i [lsearch -exact $varccommits($v,$a) $p]
969 if {$i < 0} {
970 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
971 return
973 set children($v,$id) {}
974 set parents($v,$id) [list $p]
975 set varcid($v,$id) $a
976 lappend children($v,$p) $id
977 set cmitlisted($v,$id) 1
978 set numcommits [incr commitidx($v)]
979 # note we deliberately don't update varcstart($v) even if $i == 0
980 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
981 modify_arc $v $a $i
982 if {[info exists targetid]} {
983 if {![comes_before $targetid $p]} {
984 incr targetrow
987 setcanvscroll
988 drawvisible
991 proc removefakerow {id} {
992 global varcid varccommits parents children commitidx
993 global varctok vtokmod cmitlisted currentid selectedline
994 global targetid curview numcommits
996 set v $curview
997 if {[llength $parents($v,$id)] != 1} {
998 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
999 return
1001 set p [lindex $parents($v,$id) 0]
1002 set a $varcid($v,$id)
1003 set i [lsearch -exact $varccommits($v,$a) $id]
1004 if {$i < 0} {
1005 puts "oops: removefakerow can't find [shortids $id] on arc $a"
1006 return
1008 unset varcid($v,$id)
1009 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
1010 unset parents($v,$id)
1011 unset children($v,$id)
1012 unset cmitlisted($v,$id)
1013 set numcommits [incr commitidx($v) -1]
1014 set j [lsearch -exact $children($v,$p) $id]
1015 if {$j >= 0} {
1016 set children($v,$p) [lreplace $children($v,$p) $j $j]
1018 modify_arc $v $a $i
1019 if {[info exist currentid] && $id eq $currentid} {
1020 unset currentid
1021 set selectedline {}
1023 if {[info exists targetid] && $targetid eq $id} {
1024 set targetid $p
1026 setcanvscroll
1027 drawvisible
1030 proc real_children {vp} {
1031 global children nullid nullid2
1033 set kids {}
1034 foreach id $children($vp) {
1035 if {$id ne $nullid && $id ne $nullid2} {
1036 lappend kids $id
1039 return $kids
1042 proc first_real_child {vp} {
1043 global children nullid nullid2
1045 foreach id $children($vp) {
1046 if {$id ne $nullid && $id ne $nullid2} {
1047 return $id
1050 return {}
1053 proc last_real_child {vp} {
1054 global children nullid nullid2
1056 set kids $children($vp)
1057 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
1058 set id [lindex $kids $i]
1059 if {$id ne $nullid && $id ne $nullid2} {
1060 return $id
1063 return {}
1066 proc vtokcmp {v a b} {
1067 global varctok varcid
1069 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1070 [lindex $varctok($v) $varcid($v,$b)]]
1073 # This assumes that if lim is not given, the caller has checked that
1074 # arc a's token is less than $vtokmod($v)
1075 proc modify_arc {v a {lim {}}} {
1076 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1078 if {$lim ne {}} {
1079 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1080 if {$c > 0} return
1081 if {$c == 0} {
1082 set r [lindex $varcrow($v) $a]
1083 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1086 set vtokmod($v) [lindex $varctok($v) $a]
1087 set varcmod($v) $a
1088 if {$v == $curview} {
1089 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1090 set a [lindex $vupptr($v) $a]
1091 set lim {}
1093 set r 0
1094 if {$a != 0} {
1095 if {$lim eq {}} {
1096 set lim [llength $varccommits($v,$a)]
1098 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1100 set vrowmod($v) $r
1101 undolayout $r
1105 proc update_arcrows {v} {
1106 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1107 global varcid vrownum varcorder varcix varccommits
1108 global vupptr vdownptr vleftptr varctok
1109 global displayorder parentlist curview cached_commitrow
1111 if {$vrowmod($v) == $commitidx($v)} return
1112 if {$v == $curview} {
1113 if {[llength $displayorder] > $vrowmod($v)} {
1114 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1115 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1117 catch {unset cached_commitrow}
1119 set narctot [expr {[llength $varctok($v)] - 1}]
1120 set a $varcmod($v)
1121 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1122 # go up the tree until we find something that has a row number,
1123 # or we get to a seed
1124 set a [lindex $vupptr($v) $a]
1126 if {$a == 0} {
1127 set a [lindex $vdownptr($v) 0]
1128 if {$a == 0} return
1129 set vrownum($v) {0}
1130 set varcorder($v) [list $a]
1131 lset varcix($v) $a 0
1132 lset varcrow($v) $a 0
1133 set arcn 0
1134 set row 0
1135 } else {
1136 set arcn [lindex $varcix($v) $a]
1137 if {[llength $vrownum($v)] > $arcn + 1} {
1138 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1139 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1141 set row [lindex $varcrow($v) $a]
1143 while {1} {
1144 set p $a
1145 incr row [llength $varccommits($v,$a)]
1146 # go down if possible
1147 set b [lindex $vdownptr($v) $a]
1148 if {$b == 0} {
1149 # if not, go left, or go up until we can go left
1150 while {$a != 0} {
1151 set b [lindex $vleftptr($v) $a]
1152 if {$b != 0} break
1153 set a [lindex $vupptr($v) $a]
1155 if {$a == 0} break
1157 set a $b
1158 incr arcn
1159 lappend vrownum($v) $row
1160 lappend varcorder($v) $a
1161 lset varcix($v) $a $arcn
1162 lset varcrow($v) $a $row
1164 set vtokmod($v) [lindex $varctok($v) $p]
1165 set varcmod($v) $p
1166 set vrowmod($v) $row
1167 if {[info exists currentid]} {
1168 set selectedline [rowofcommit $currentid]
1172 # Test whether view $v contains commit $id
1173 proc commitinview {id v} {
1174 global varcid
1176 return [info exists varcid($v,$id)]
1179 # Return the row number for commit $id in the current view
1180 proc rowofcommit {id} {
1181 global varcid varccommits varcrow curview cached_commitrow
1182 global varctok vtokmod
1184 set v $curview
1185 if {![info exists varcid($v,$id)]} {
1186 puts "oops rowofcommit no arc for [shortids $id]"
1187 return {}
1189 set a $varcid($v,$id)
1190 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1191 update_arcrows $v
1193 if {[info exists cached_commitrow($id)]} {
1194 return $cached_commitrow($id)
1196 set i [lsearch -exact $varccommits($v,$a) $id]
1197 if {$i < 0} {
1198 puts "oops didn't find commit [shortids $id] in arc $a"
1199 return {}
1201 incr i [lindex $varcrow($v) $a]
1202 set cached_commitrow($id) $i
1203 return $i
1206 # Returns 1 if a is on an earlier row than b, otherwise 0
1207 proc comes_before {a b} {
1208 global varcid varctok curview
1210 set v $curview
1211 if {$a eq $b || ![info exists varcid($v,$a)] || \
1212 ![info exists varcid($v,$b)]} {
1213 return 0
1215 if {$varcid($v,$a) != $varcid($v,$b)} {
1216 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1217 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1219 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1222 proc bsearch {l elt} {
1223 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1224 return 0
1226 set lo 0
1227 set hi [llength $l]
1228 while {$hi - $lo > 1} {
1229 set mid [expr {int(($lo + $hi) / 2)}]
1230 set t [lindex $l $mid]
1231 if {$elt < $t} {
1232 set hi $mid
1233 } elseif {$elt > $t} {
1234 set lo $mid
1235 } else {
1236 return $mid
1239 return $lo
1242 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1243 proc make_disporder {start end} {
1244 global vrownum curview commitidx displayorder parentlist
1245 global varccommits varcorder parents vrowmod varcrow
1246 global d_valid_start d_valid_end
1248 if {$end > $vrowmod($curview)} {
1249 update_arcrows $curview
1251 set ai [bsearch $vrownum($curview) $start]
1252 set start [lindex $vrownum($curview) $ai]
1253 set narc [llength $vrownum($curview)]
1254 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1255 set a [lindex $varcorder($curview) $ai]
1256 set l [llength $displayorder]
1257 set al [llength $varccommits($curview,$a)]
1258 if {$l < $r + $al} {
1259 if {$l < $r} {
1260 set pad [ntimes [expr {$r - $l}] {}]
1261 set displayorder [concat $displayorder $pad]
1262 set parentlist [concat $parentlist $pad]
1263 } elseif {$l > $r} {
1264 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1265 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1267 foreach id $varccommits($curview,$a) {
1268 lappend displayorder $id
1269 lappend parentlist $parents($curview,$id)
1271 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1272 set i $r
1273 foreach id $varccommits($curview,$a) {
1274 lset displayorder $i $id
1275 lset parentlist $i $parents($curview,$id)
1276 incr i
1279 incr r $al
1283 proc commitonrow {row} {
1284 global displayorder
1286 set id [lindex $displayorder $row]
1287 if {$id eq {}} {
1288 make_disporder $row [expr {$row + 1}]
1289 set id [lindex $displayorder $row]
1291 return $id
1294 proc closevarcs {v} {
1295 global varctok varccommits varcid parents children
1296 global cmitlisted commitidx vtokmod
1298 set missing_parents 0
1299 set scripts {}
1300 set narcs [llength $varctok($v)]
1301 for {set a 1} {$a < $narcs} {incr a} {
1302 set id [lindex $varccommits($v,$a) end]
1303 foreach p $parents($v,$id) {
1304 if {[info exists varcid($v,$p)]} continue
1305 # add p as a new commit
1306 incr missing_parents
1307 set cmitlisted($v,$p) 0
1308 set parents($v,$p) {}
1309 if {[llength $children($v,$p)] == 1 &&
1310 [llength $parents($v,$id)] == 1} {
1311 set b $a
1312 } else {
1313 set b [newvarc $v $p]
1315 set varcid($v,$p) $b
1316 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1317 modify_arc $v $b
1319 lappend varccommits($v,$b) $p
1320 incr commitidx($v)
1321 set scripts [check_interest $p $scripts]
1324 if {$missing_parents > 0} {
1325 foreach s $scripts {
1326 eval $s
1331 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1332 # Assumes we already have an arc for $rwid.
1333 proc rewrite_commit {v id rwid} {
1334 global children parents varcid varctok vtokmod varccommits
1336 foreach ch $children($v,$id) {
1337 # make $rwid be $ch's parent in place of $id
1338 set i [lsearch -exact $parents($v,$ch) $id]
1339 if {$i < 0} {
1340 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1342 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1343 # add $ch to $rwid's children and sort the list if necessary
1344 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1345 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1346 $children($v,$rwid)]
1348 # fix the graph after joining $id to $rwid
1349 set a $varcid($v,$ch)
1350 fix_reversal $rwid $a $v
1351 # parentlist is wrong for the last element of arc $a
1352 # even if displayorder is right, hence the 3rd arg here
1353 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1357 # Mechanism for registering a command to be executed when we come
1358 # across a particular commit. To handle the case when only the
1359 # prefix of the commit is known, the commitinterest array is now
1360 # indexed by the first 4 characters of the ID. Each element is a
1361 # list of id, cmd pairs.
1362 proc interestedin {id cmd} {
1363 global commitinterest
1365 lappend commitinterest([string range $id 0 3]) $id $cmd
1368 proc check_interest {id scripts} {
1369 global commitinterest
1371 set prefix [string range $id 0 3]
1372 if {[info exists commitinterest($prefix)]} {
1373 set newlist {}
1374 foreach {i script} $commitinterest($prefix) {
1375 if {[string match "$i*" $id]} {
1376 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1377 } else {
1378 lappend newlist $i $script
1381 if {$newlist ne {}} {
1382 set commitinterest($prefix) $newlist
1383 } else {
1384 unset commitinterest($prefix)
1387 return $scripts
1390 proc getcommitlines {fd inst view updating} {
1391 global cmitlisted leftover
1392 global commitidx commitdata vdatemode
1393 global parents children curview hlview
1394 global idpending ordertok
1395 global varccommits varcid varctok vtokmod vfilelimit
1397 set stuff [read $fd 500000]
1398 # git log doesn't terminate the last commit with a null...
1399 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1400 set stuff "\0"
1402 if {$stuff == {}} {
1403 if {![eof $fd]} {
1404 return 1
1406 global commfd viewcomplete viewactive viewname
1407 global viewinstances
1408 unset commfd($inst)
1409 set i [lsearch -exact $viewinstances($view) $inst]
1410 if {$i >= 0} {
1411 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1413 # set it blocking so we wait for the process to terminate
1414 fconfigure $fd -blocking 1
1415 if {[catch {close $fd} err]} {
1416 set fv {}
1417 if {$view != $curview} {
1418 set fv " for the \"$viewname($view)\" view"
1420 if {[string range $err 0 4] == "usage"} {
1421 set err "Gitk: error reading commits$fv:\
1422 bad arguments to git log."
1423 if {$viewname($view) eq "Command line"} {
1424 append err \
1425 " (Note: arguments to gitk are passed to git log\
1426 to allow selection of commits to be displayed.)"
1428 } else {
1429 set err "Error reading commits$fv: $err"
1431 error_popup $err
1433 if {[incr viewactive($view) -1] <= 0} {
1434 set viewcomplete($view) 1
1435 # Check if we have seen any ids listed as parents that haven't
1436 # appeared in the list
1437 closevarcs $view
1438 notbusy $view
1440 if {$view == $curview} {
1441 run chewcommits
1443 return 0
1445 set start 0
1446 set gotsome 0
1447 set scripts {}
1448 while 1 {
1449 set i [string first "\0" $stuff $start]
1450 if {$i < 0} {
1451 append leftover($inst) [string range $stuff $start end]
1452 break
1454 if {$start == 0} {
1455 set cmit $leftover($inst)
1456 append cmit [string range $stuff 0 [expr {$i - 1}]]
1457 set leftover($inst) {}
1458 } else {
1459 set cmit [string range $stuff $start [expr {$i - 1}]]
1461 set start [expr {$i + 1}]
1462 set j [string first "\n" $cmit]
1463 set ok 0
1464 set listed 1
1465 if {$j >= 0 && [string match "commit *" $cmit]} {
1466 set ids [string range $cmit 7 [expr {$j - 1}]]
1467 if {[string match {[-^<>]*} $ids]} {
1468 switch -- [string index $ids 0] {
1469 "-" {set listed 0}
1470 "^" {set listed 2}
1471 "<" {set listed 3}
1472 ">" {set listed 4}
1474 set ids [string range $ids 1 end]
1476 set ok 1
1477 foreach id $ids {
1478 if {[string length $id] != 40} {
1479 set ok 0
1480 break
1484 if {!$ok} {
1485 set shortcmit $cmit
1486 if {[string length $shortcmit] > 80} {
1487 set shortcmit "[string range $shortcmit 0 80]..."
1489 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1490 exit 1
1492 set id [lindex $ids 0]
1493 set vid $view,$id
1495 if {!$listed && $updating && ![info exists varcid($vid)] &&
1496 $vfilelimit($view) ne {}} {
1497 # git log doesn't rewrite parents for unlisted commits
1498 # when doing path limiting, so work around that here
1499 # by working out the rewritten parent with git rev-list
1500 # and if we already know about it, using the rewritten
1501 # parent as a substitute parent for $id's children.
1502 if {![catch {
1503 set rwid [exec git rev-list --first-parent --max-count=1 \
1504 $id -- $vfilelimit($view)]
1505 }]} {
1506 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1507 # use $rwid in place of $id
1508 rewrite_commit $view $id $rwid
1509 continue
1514 set a 0
1515 if {[info exists varcid($vid)]} {
1516 if {$cmitlisted($vid) || !$listed} continue
1517 set a $varcid($vid)
1519 if {$listed} {
1520 set olds [lrange $ids 1 end]
1521 } else {
1522 set olds {}
1524 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1525 set cmitlisted($vid) $listed
1526 set parents($vid) $olds
1527 if {![info exists children($vid)]} {
1528 set children($vid) {}
1529 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1530 set k [lindex $children($vid) 0]
1531 if {[llength $parents($view,$k)] == 1 &&
1532 (!$vdatemode($view) ||
1533 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1534 set a $varcid($view,$k)
1537 if {$a == 0} {
1538 # new arc
1539 set a [newvarc $view $id]
1541 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1542 modify_arc $view $a
1544 if {![info exists varcid($vid)]} {
1545 set varcid($vid) $a
1546 lappend varccommits($view,$a) $id
1547 incr commitidx($view)
1550 set i 0
1551 foreach p $olds {
1552 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1553 set vp $view,$p
1554 if {[llength [lappend children($vp) $id]] > 1 &&
1555 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1556 set children($vp) [lsort -command [list vtokcmp $view] \
1557 $children($vp)]
1558 catch {unset ordertok}
1560 if {[info exists varcid($view,$p)]} {
1561 fix_reversal $p $a $view
1564 incr i
1567 set scripts [check_interest $id $scripts]
1568 set gotsome 1
1570 if {$gotsome} {
1571 global numcommits hlview
1573 if {$view == $curview} {
1574 set numcommits $commitidx($view)
1575 run chewcommits
1577 if {[info exists hlview] && $view == $hlview} {
1578 # we never actually get here...
1579 run vhighlightmore
1581 foreach s $scripts {
1582 eval $s
1585 return 2
1588 proc chewcommits {} {
1589 global curview hlview viewcomplete
1590 global pending_select
1592 layoutmore
1593 if {$viewcomplete($curview)} {
1594 global commitidx varctok
1595 global numcommits startmsecs
1597 if {[info exists pending_select]} {
1598 update
1599 reset_pending_select {}
1601 if {[commitinview $pending_select $curview]} {
1602 selectline [rowofcommit $pending_select] 1
1603 } else {
1604 set row [first_real_row]
1605 selectline $row 1
1608 if {$commitidx($curview) > 0} {
1609 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1610 #puts "overall $ms ms for $numcommits commits"
1611 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1612 } else {
1613 show_status [mc "No commits selected"]
1615 notbusy layout
1617 return 0
1620 proc do_readcommit {id} {
1621 global tclencoding
1623 # Invoke git-log to handle automatic encoding conversion
1624 set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1625 # Read the results using i18n.logoutputencoding
1626 fconfigure $fd -translation lf -eofchar {}
1627 if {$tclencoding != {}} {
1628 fconfigure $fd -encoding $tclencoding
1630 set contents [read $fd]
1631 close $fd
1632 # Remove the heading line
1633 regsub {^commit [0-9a-f]+\n} $contents {} contents
1635 return $contents
1638 proc readcommit {id} {
1639 if {[catch {set contents [do_readcommit $id]}]} return
1640 parsecommit $id $contents 1
1643 proc parsecommit {id contents listed} {
1644 global commitinfo cdate
1646 set inhdr 1
1647 set comment {}
1648 set headline {}
1649 set auname {}
1650 set audate {}
1651 set comname {}
1652 set comdate {}
1653 set hdrend [string first "\n\n" $contents]
1654 if {$hdrend < 0} {
1655 # should never happen...
1656 set hdrend [string length $contents]
1658 set header [string range $contents 0 [expr {$hdrend - 1}]]
1659 set comment [string range $contents [expr {$hdrend + 2}] end]
1660 foreach line [split $header "\n"] {
1661 set line [split $line " "]
1662 set tag [lindex $line 0]
1663 if {$tag == "author"} {
1664 set audate [lindex $line end-1]
1665 set auname [join [lrange $line 1 end-2] " "]
1666 } elseif {$tag == "committer"} {
1667 set comdate [lindex $line end-1]
1668 set comname [join [lrange $line 1 end-2] " "]
1671 set headline {}
1672 # take the first non-blank line of the comment as the headline
1673 set headline [string trimleft $comment]
1674 set i [string first "\n" $headline]
1675 if {$i >= 0} {
1676 set headline [string range $headline 0 $i]
1678 set headline [string trimright $headline]
1679 set i [string first "\r" $headline]
1680 if {$i >= 0} {
1681 set headline [string trimright [string range $headline 0 $i]]
1683 if {!$listed} {
1684 # git log indents the comment by 4 spaces;
1685 # if we got this via git cat-file, add the indentation
1686 set newcomment {}
1687 foreach line [split $comment "\n"] {
1688 append newcomment " "
1689 append newcomment $line
1690 append newcomment "\n"
1692 set comment $newcomment
1694 if {$comdate != {}} {
1695 set cdate($id) $comdate
1697 set commitinfo($id) [list $headline $auname $audate \
1698 $comname $comdate $comment]
1701 proc getcommit {id} {
1702 global commitdata commitinfo
1704 if {[info exists commitdata($id)]} {
1705 parsecommit $id $commitdata($id) 1
1706 } else {
1707 readcommit $id
1708 if {![info exists commitinfo($id)]} {
1709 set commitinfo($id) [list [mc "No commit information available"]]
1712 return 1
1715 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1716 # and are present in the current view.
1717 # This is fairly slow...
1718 proc longid {prefix} {
1719 global varcid curview
1721 set ids {}
1722 foreach match [array names varcid "$curview,$prefix*"] {
1723 lappend ids [lindex [split $match ","] 1]
1725 return $ids
1728 proc readrefs {} {
1729 global tagids idtags headids idheads tagobjid
1730 global otherrefids idotherrefs mainhead mainheadid
1731 global selecthead selectheadid
1732 global hideremotes
1734 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1735 catch {unset $v}
1737 set refd [open [list | git show-ref -d] r]
1738 while {[gets $refd line] >= 0} {
1739 if {[string index $line 40] ne " "} continue
1740 set id [string range $line 0 39]
1741 set ref [string range $line 41 end]
1742 if {![string match "refs/*" $ref]} continue
1743 set name [string range $ref 5 end]
1744 if {[string match "remotes/*" $name]} {
1745 if {![string match "*/HEAD" $name] && !$hideremotes} {
1746 set headids($name) $id
1747 lappend idheads($id) $name
1749 } elseif {[string match "heads/*" $name]} {
1750 set name [string range $name 6 end]
1751 set headids($name) $id
1752 lappend idheads($id) $name
1753 } elseif {[string match "tags/*" $name]} {
1754 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1755 # which is what we want since the former is the commit ID
1756 set name [string range $name 5 end]
1757 if {[string match "*^{}" $name]} {
1758 set name [string range $name 0 end-3]
1759 } else {
1760 set tagobjid($name) $id
1762 set tagids($name) $id
1763 lappend idtags($id) $name
1764 } else {
1765 set otherrefids($name) $id
1766 lappend idotherrefs($id) $name
1769 catch {close $refd}
1770 set mainhead {}
1771 set mainheadid {}
1772 catch {
1773 set mainheadid [exec git rev-parse HEAD]
1774 set thehead [exec git symbolic-ref HEAD]
1775 if {[string match "refs/heads/*" $thehead]} {
1776 set mainhead [string range $thehead 11 end]
1779 set selectheadid {}
1780 if {$selecthead ne {}} {
1781 catch {
1782 set selectheadid [exec git rev-parse --verify $selecthead]
1787 # skip over fake commits
1788 proc first_real_row {} {
1789 global nullid nullid2 numcommits
1791 for {set row 0} {$row < $numcommits} {incr row} {
1792 set id [commitonrow $row]
1793 if {$id ne $nullid && $id ne $nullid2} {
1794 break
1797 return $row
1800 # update things for a head moved to a child of its previous location
1801 proc movehead {id name} {
1802 global headids idheads
1804 removehead $headids($name) $name
1805 set headids($name) $id
1806 lappend idheads($id) $name
1809 # update things when a head has been removed
1810 proc removehead {id name} {
1811 global headids idheads
1813 if {$idheads($id) eq $name} {
1814 unset idheads($id)
1815 } else {
1816 set i [lsearch -exact $idheads($id) $name]
1817 if {$i >= 0} {
1818 set idheads($id) [lreplace $idheads($id) $i $i]
1821 unset headids($name)
1824 proc ttk_toplevel {w args} {
1825 global use_ttk
1826 eval [linsert $args 0 ::toplevel $w]
1827 if {$use_ttk} {
1828 place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
1830 return $w
1833 proc make_transient {window origin} {
1834 global have_tk85
1836 # In MacOS Tk 8.4 transient appears to work by setting
1837 # overrideredirect, which is utterly useless, since the
1838 # windows get no border, and are not even kept above
1839 # the parent.
1840 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1842 wm transient $window $origin
1844 # Windows fails to place transient windows normally, so
1845 # schedule a callback to center them on the parent.
1846 if {[tk windowingsystem] eq {win32}} {
1847 after idle [list tk::PlaceWindow $window widget $origin]
1851 proc show_error {w top msg {mc mc}} {
1852 global NS
1853 if {![info exists NS]} {set NS ""}
1854 if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
1855 message $w.m -text $msg -justify center -aspect 400
1856 pack $w.m -side top -fill x -padx 20 -pady 20
1857 ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top"
1858 pack $w.ok -side bottom -fill x
1859 bind $top <Visibility> "grab $top; focus $top"
1860 bind $top <Key-Return> "destroy $top"
1861 bind $top <Key-space> "destroy $top"
1862 bind $top <Key-Escape> "destroy $top"
1863 tkwait window $top
1866 proc error_popup {msg {owner .}} {
1867 if {[tk windowingsystem] eq "win32"} {
1868 tk_messageBox -icon error -type ok -title [wm title .] \
1869 -parent $owner -message $msg
1870 } else {
1871 set w .error
1872 ttk_toplevel $w
1873 make_transient $w $owner
1874 show_error $w $w $msg
1878 proc confirm_popup {msg {owner .}} {
1879 global confirm_ok NS
1880 set confirm_ok 0
1881 set w .confirm
1882 ttk_toplevel $w
1883 make_transient $w $owner
1884 message $w.m -text $msg -justify center -aspect 400
1885 pack $w.m -side top -fill x -padx 20 -pady 20
1886 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1887 pack $w.ok -side left -fill x
1888 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
1889 pack $w.cancel -side right -fill x
1890 bind $w <Visibility> "grab $w; focus $w"
1891 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1892 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1893 bind $w <Key-Escape> "destroy $w"
1894 tk::PlaceWindow $w widget $owner
1895 tkwait window $w
1896 return $confirm_ok
1899 proc setoptions {} {
1900 if {[tk windowingsystem] ne "win32"} {
1901 option add *Panedwindow.showHandle 1 startupFile
1902 option add *Panedwindow.sashRelief raised startupFile
1903 if {[tk windowingsystem] ne "aqua"} {
1904 option add *Menu.font uifont startupFile
1906 } else {
1907 option add *Menu.TearOff 0 startupFile
1909 option add *Button.font uifont startupFile
1910 option add *Checkbutton.font uifont startupFile
1911 option add *Radiobutton.font uifont startupFile
1912 option add *Menubutton.font uifont startupFile
1913 option add *Label.font uifont startupFile
1914 option add *Message.font uifont startupFile
1915 option add *Entry.font textfont startupFile
1916 option add *Text.font textfont startupFile
1917 option add *Labelframe.font uifont startupFile
1918 option add *Spinbox.font textfont startupFile
1919 option add *Listbox.font mainfont startupFile
1922 # Make a menu and submenus.
1923 # m is the window name for the menu, items is the list of menu items to add.
1924 # Each item is a list {mc label type description options...}
1925 # mc is ignored; it's so we can put mc there to alert xgettext
1926 # label is the string that appears in the menu
1927 # type is cascade, command or radiobutton (should add checkbutton)
1928 # description depends on type; it's the sublist for cascade, the
1929 # command to invoke for command, or {variable value} for radiobutton
1930 proc makemenu {m items} {
1931 menu $m
1932 if {[tk windowingsystem] eq {aqua}} {
1933 set Meta1 Cmd
1934 } else {
1935 set Meta1 Ctrl
1937 foreach i $items {
1938 set name [mc [lindex $i 1]]
1939 set type [lindex $i 2]
1940 set thing [lindex $i 3]
1941 set params [list $type]
1942 if {$name ne {}} {
1943 set u [string first "&" [string map {&& x} $name]]
1944 lappend params -label [string map {&& & & {}} $name]
1945 if {$u >= 0} {
1946 lappend params -underline $u
1949 switch -- $type {
1950 "cascade" {
1951 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1952 lappend params -menu $m.$submenu
1954 "command" {
1955 lappend params -command $thing
1957 "radiobutton" {
1958 lappend params -variable [lindex $thing 0] \
1959 -value [lindex $thing 1]
1962 set tail [lrange $i 4 end]
1963 regsub -all {\yMeta1\y} $tail $Meta1 tail
1964 eval $m add $params $tail
1965 if {$type eq "cascade"} {
1966 makemenu $m.$submenu $thing
1971 # translate string and remove ampersands
1972 proc mca {str} {
1973 return [string map {&& & & {}} [mc $str]]
1976 proc makedroplist {w varname args} {
1977 global use_ttk
1978 if {$use_ttk} {
1979 set width 0
1980 foreach label $args {
1981 set cx [string length $label]
1982 if {$cx > $width} {set width $cx}
1984 set gm [ttk::combobox $w -width $width -state readonly\
1985 -textvariable $varname -values $args]
1986 } else {
1987 set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
1989 return $gm
1992 proc makewindow {} {
1993 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1994 global tabstop
1995 global findtype findtypemenu findloc findstring fstring geometry
1996 global entries sha1entry sha1string sha1but
1997 global diffcontextstring diffcontext
1998 global ignorespace
1999 global maincursor textcursor curtextcursor
2000 global rowctxmenu fakerowmenu mergemax wrapcomment
2001 global highlight_files gdttype
2002 global searchstring sstring
2003 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
2004 global headctxmenu progresscanv progressitem progresscoords statusw
2005 global fprogitem fprogcoord lastprogupdate progupdatepending
2006 global rprogitem rprogcoord rownumsel numcommits
2007 global have_tk85 use_ttk NS
2008 global git_version
2009 global worddiff
2011 # The "mc" arguments here are purely so that xgettext
2012 # sees the following string as needing to be translated
2013 set file {
2014 mc "File" cascade {
2015 {mc "Update" command updatecommits -accelerator F5}
2016 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
2017 {mc "Reread references" command rereadrefs}
2018 {mc "List references" command showrefs -accelerator F2}
2019 {xx "" separator}
2020 {mc "Start git gui" command {exec git gui &}}
2021 {xx "" separator}
2022 {mc "Quit" command doquit -accelerator Meta1-Q}
2024 set edit {
2025 mc "Edit" cascade {
2026 {mc "Preferences" command doprefs}
2028 set view {
2029 mc "View" cascade {
2030 {mc "New view..." command {newview 0} -accelerator Shift-F4}
2031 {mc "Edit view..." command editview -state disabled -accelerator F4}
2032 {mc "Delete view" command delview -state disabled}
2033 {xx "" separator}
2034 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
2036 if {[tk windowingsystem] ne "aqua"} {
2037 set help {
2038 mc "Help" cascade {
2039 {mc "About gitk" command about}
2040 {mc "Key bindings" command keys}
2042 set bar [list $file $edit $view $help]
2043 } else {
2044 proc ::tk::mac::ShowPreferences {} {doprefs}
2045 proc ::tk::mac::Quit {} {doquit}
2046 lset file end [lreplace [lindex $file end] end-1 end]
2047 set apple {
2048 xx "Apple" cascade {
2049 {mc "About gitk" command about}
2050 {xx "" separator}
2052 set help {
2053 mc "Help" cascade {
2054 {mc "Key bindings" command keys}
2056 set bar [list $apple $file $view $help]
2058 makemenu .bar $bar
2059 . configure -menu .bar
2061 if {$use_ttk} {
2062 # cover the non-themed toplevel with a themed frame.
2063 place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
2066 # the gui has upper and lower half, parts of a paned window.
2067 ${NS}::panedwindow .ctop -orient vertical
2069 # possibly use assumed geometry
2070 if {![info exists geometry(pwsash0)]} {
2071 set geometry(topheight) [expr {15 * $linespc}]
2072 set geometry(topwidth) [expr {80 * $charspc}]
2073 set geometry(botheight) [expr {15 * $linespc}]
2074 set geometry(botwidth) [expr {50 * $charspc}]
2075 set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
2076 set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
2079 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
2080 ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
2081 ${NS}::frame .tf.histframe
2082 ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
2083 if {!$use_ttk} {
2084 .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
2087 # create three canvases
2088 set cscroll .tf.histframe.csb
2089 set canv .tf.histframe.pwclist.canv
2090 canvas $canv \
2091 -selectbackground $selectbgcolor \
2092 -background $bgcolor -bd 0 \
2093 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
2094 .tf.histframe.pwclist add $canv
2095 set canv2 .tf.histframe.pwclist.canv2
2096 canvas $canv2 \
2097 -selectbackground $selectbgcolor \
2098 -background $bgcolor -bd 0 -yscrollincr $linespc
2099 .tf.histframe.pwclist add $canv2
2100 set canv3 .tf.histframe.pwclist.canv3
2101 canvas $canv3 \
2102 -selectbackground $selectbgcolor \
2103 -background $bgcolor -bd 0 -yscrollincr $linespc
2104 .tf.histframe.pwclist add $canv3
2105 if {$use_ttk} {
2106 bind .tf.histframe.pwclist <Map> {
2107 bind %W <Map> {}
2108 .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
2109 .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
2111 } else {
2112 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
2113 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
2116 # a scroll bar to rule them
2117 ${NS}::scrollbar $cscroll -command {allcanvs yview}
2118 if {!$use_ttk} {$cscroll configure -highlightthickness 0}
2119 pack $cscroll -side right -fill y
2120 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
2121 lappend bglist $canv $canv2 $canv3
2122 pack .tf.histframe.pwclist -fill both -expand 1 -side left
2124 # we have two button bars at bottom of top frame. Bar 1
2125 ${NS}::frame .tf.bar
2126 ${NS}::frame .tf.lbar -height 15
2128 set sha1entry .tf.bar.sha1
2129 set entries $sha1entry
2130 set sha1but .tf.bar.sha1label
2131 button $sha1but -text "[mc "SHA1 ID:"] " -state disabled -relief flat \
2132 -command gotocommit -width 8
2133 $sha1but conf -disabledforeground [$sha1but cget -foreground]
2134 pack .tf.bar.sha1label -side left
2135 ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
2136 trace add variable sha1string write sha1change
2137 pack $sha1entry -side left -pady 2
2139 image create bitmap bm-left -data {
2140 #define left_width 16
2141 #define left_height 16
2142 static unsigned char left_bits[] = {
2143 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
2144 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
2145 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
2147 image create bitmap bm-right -data {
2148 #define right_width 16
2149 #define right_height 16
2150 static unsigned char right_bits[] = {
2151 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2152 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2153 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2155 ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
2156 -state disabled -width 26
2157 pack .tf.bar.leftbut -side left -fill y
2158 ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
2159 -state disabled -width 26
2160 pack .tf.bar.rightbut -side left -fill y
2162 ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
2163 set rownumsel {}
2164 ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
2165 -relief sunken -anchor e
2166 ${NS}::label .tf.bar.rowlabel2 -text "/"
2167 ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
2168 -relief sunken -anchor e
2169 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2170 -side left
2171 if {!$use_ttk} {
2172 foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
2174 global selectedline
2175 trace add variable selectedline write selectedline_change
2177 # Status label and progress bar
2178 set statusw .tf.bar.status
2179 ${NS}::label $statusw -width 15 -relief sunken
2180 pack $statusw -side left -padx 5
2181 if {$use_ttk} {
2182 set progresscanv [ttk::progressbar .tf.bar.progress]
2183 } else {
2184 set h [expr {[font metrics uifont -linespace] + 2}]
2185 set progresscanv .tf.bar.progress
2186 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2187 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2188 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2189 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2191 pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
2192 set progresscoords {0 0}
2193 set fprogcoord 0
2194 set rprogcoord 0
2195 bind $progresscanv <Configure> adjustprogress
2196 set lastprogupdate [clock clicks -milliseconds]
2197 set progupdatepending 0
2199 # build up the bottom bar of upper window
2200 ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
2201 ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2202 ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2203 ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
2204 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2205 -side left -fill y
2206 set gdttype [mc "containing:"]
2207 set gm [makedroplist .tf.lbar.gdttype gdttype \
2208 [mc "containing:"] \
2209 [mc "touching paths:"] \
2210 [mc "adding/removing string:"]]
2211 trace add variable gdttype write gdttype_change
2212 pack .tf.lbar.gdttype -side left -fill y
2214 set findstring {}
2215 set fstring .tf.lbar.findstring
2216 lappend entries $fstring
2217 ${NS}::entry $fstring -width 30 -textvariable findstring
2218 trace add variable findstring write find_change
2219 set findtype [mc "Exact"]
2220 set findtypemenu [makedroplist .tf.lbar.findtype \
2221 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2222 trace add variable findtype write findcom_change
2223 set findloc [mc "All fields"]
2224 makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2225 [mc "Comments"] [mc "Author"] [mc "Committer"]
2226 trace add variable findloc write find_change
2227 pack .tf.lbar.findloc -side right
2228 pack .tf.lbar.findtype -side right
2229 pack $fstring -side left -expand 1 -fill x
2231 # Finish putting the upper half of the viewer together
2232 pack .tf.lbar -in .tf -side bottom -fill x
2233 pack .tf.bar -in .tf -side bottom -fill x
2234 pack .tf.histframe -fill both -side top -expand 1
2235 .ctop add .tf
2236 if {!$use_ttk} {
2237 .ctop paneconfigure .tf -height $geometry(topheight)
2238 .ctop paneconfigure .tf -width $geometry(topwidth)
2241 # now build up the bottom
2242 ${NS}::panedwindow .pwbottom -orient horizontal
2244 # lower left, a text box over search bar, scroll bar to the right
2245 # if we know window height, then that will set the lower text height, otherwise
2246 # we set lower text height which will drive window height
2247 if {[info exists geometry(main)]} {
2248 ${NS}::frame .bleft -width $geometry(botwidth)
2249 } else {
2250 ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2252 ${NS}::frame .bleft.top
2253 ${NS}::frame .bleft.mid
2254 ${NS}::frame .bleft.bottom
2256 ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
2257 pack .bleft.top.search -side left -padx 5
2258 set sstring .bleft.top.sstring
2259 set searchstring ""
2260 ${NS}::entry $sstring -width 20 -textvariable searchstring
2261 lappend entries $sstring
2262 trace add variable searchstring write incrsearch
2263 pack $sstring -side left -expand 1 -fill x
2264 ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
2265 -command changediffdisp -variable diffelide -value {0 0}
2266 ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
2267 -command changediffdisp -variable diffelide -value {0 1}
2268 ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
2269 -command changediffdisp -variable diffelide -value {1 0}
2270 ${NS}::label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2271 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2272 spinbox .bleft.mid.diffcontext -width 5 \
2273 -from 0 -increment 1 -to 10000000 \
2274 -validate all -validatecommand "diffcontextvalidate %P" \
2275 -textvariable diffcontextstring
2276 .bleft.mid.diffcontext set $diffcontext
2277 trace add variable diffcontextstring write diffcontextchange
2278 lappend entries .bleft.mid.diffcontext
2279 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2280 ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2281 -command changeignorespace -variable ignorespace
2282 pack .bleft.mid.ignspace -side left -padx 5
2284 set worddiff [mc "Line diff"]
2285 if {[package vcompare $git_version "1.7.2"] >= 0} {
2286 makedroplist .bleft.mid.worddiff worddiff [mc "Line diff"] \
2287 [mc "Markup words"] [mc "Color words"]
2288 trace add variable worddiff write changeworddiff
2289 pack .bleft.mid.worddiff -side left -padx 5
2292 set ctext .bleft.bottom.ctext
2293 text $ctext -background $bgcolor -foreground $fgcolor \
2294 -state disabled -font textfont \
2295 -yscrollcommand scrolltext -wrap none \
2296 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2297 if {$have_tk85} {
2298 $ctext conf -tabstyle wordprocessor
2300 ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
2301 ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
2302 pack .bleft.top -side top -fill x
2303 pack .bleft.mid -side top -fill x
2304 grid $ctext .bleft.bottom.sb -sticky nsew
2305 grid .bleft.bottom.sbhorizontal -sticky ew
2306 grid columnconfigure .bleft.bottom 0 -weight 1
2307 grid rowconfigure .bleft.bottom 0 -weight 1
2308 grid rowconfigure .bleft.bottom 1 -weight 0
2309 pack .bleft.bottom -side top -fill both -expand 1
2310 lappend bglist $ctext
2311 lappend fglist $ctext
2313 $ctext tag conf comment -wrap $wrapcomment
2314 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2315 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2316 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2317 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2318 $ctext tag conf m0 -fore red
2319 $ctext tag conf m1 -fore blue
2320 $ctext tag conf m2 -fore green
2321 $ctext tag conf m3 -fore purple
2322 $ctext tag conf m4 -fore brown
2323 $ctext tag conf m5 -fore "#009090"
2324 $ctext tag conf m6 -fore magenta
2325 $ctext tag conf m7 -fore "#808000"
2326 $ctext tag conf m8 -fore "#009000"
2327 $ctext tag conf m9 -fore "#ff0080"
2328 $ctext tag conf m10 -fore cyan
2329 $ctext tag conf m11 -fore "#b07070"
2330 $ctext tag conf m12 -fore "#70b0f0"
2331 $ctext tag conf m13 -fore "#70f0b0"
2332 $ctext tag conf m14 -fore "#f0b070"
2333 $ctext tag conf m15 -fore "#ff70b0"
2334 $ctext tag conf mmax -fore darkgrey
2335 set mergemax 16
2336 $ctext tag conf mresult -font textfontbold
2337 $ctext tag conf msep -font textfontbold
2338 $ctext tag conf found -back yellow
2340 .pwbottom add .bleft
2341 if {!$use_ttk} {
2342 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2345 # lower right
2346 ${NS}::frame .bright
2347 ${NS}::frame .bright.mode
2348 ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
2349 -command reselectline -variable cmitmode -value "patch"
2350 ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
2351 -command reselectline -variable cmitmode -value "tree"
2352 grid .bright.mode.patch .bright.mode.tree -sticky ew
2353 pack .bright.mode -side top -fill x
2354 set cflist .bright.cfiles
2355 set indent [font measure mainfont "nn"]
2356 text $cflist \
2357 -selectbackground $selectbgcolor \
2358 -background $bgcolor -foreground $fgcolor \
2359 -font mainfont \
2360 -tabs [list $indent [expr {2 * $indent}]] \
2361 -yscrollcommand ".bright.sb set" \
2362 -cursor [. cget -cursor] \
2363 -spacing1 1 -spacing3 1
2364 lappend bglist $cflist
2365 lappend fglist $cflist
2366 ${NS}::scrollbar .bright.sb -command "$cflist yview"
2367 pack .bright.sb -side right -fill y
2368 pack $cflist -side left -fill both -expand 1
2369 $cflist tag configure highlight \
2370 -background [$cflist cget -selectbackground]
2371 $cflist tag configure bold -font mainfontbold
2373 .pwbottom add .bright
2374 .ctop add .pwbottom
2376 # restore window width & height if known
2377 if {[info exists geometry(main)]} {
2378 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2379 if {$w > [winfo screenwidth .]} {
2380 set w [winfo screenwidth .]
2382 if {$h > [winfo screenheight .]} {
2383 set h [winfo screenheight .]
2385 wm geometry . "${w}x$h"
2389 if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
2390 wm state . $geometry(state)
2393 if {[tk windowingsystem] eq {aqua}} {
2394 set M1B M1
2395 set ::BM "3"
2396 } else {
2397 set M1B Control
2398 set ::BM "2"
2401 if {$use_ttk} {
2402 bind .ctop <Map> {
2403 bind %W <Map> {}
2404 %W sashpos 0 $::geometry(topheight)
2406 bind .pwbottom <Map> {
2407 bind %W <Map> {}
2408 %W sashpos 0 $::geometry(botwidth)
2412 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2413 pack .ctop -fill both -expand 1
2414 bindall <1> {selcanvline %W %x %y}
2415 #bindall <B1-Motion> {selcanvline %W %x %y}
2416 if {[tk windowingsystem] == "win32"} {
2417 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2418 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2419 } else {
2420 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2421 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2422 if {[tk windowingsystem] eq "aqua"} {
2423 bindall <MouseWheel> {
2424 set delta [expr {- (%D)}]
2425 allcanvs yview scroll $delta units
2427 bindall <Shift-MouseWheel> {
2428 set delta [expr {- (%D)}]
2429 $canv xview scroll $delta units
2433 bindall <$::BM> "canvscan mark %W %x %y"
2434 bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
2435 bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
2436 bind . <$M1B-Key-w> doquit
2437 bindkey <Home> selfirstline
2438 bindkey <End> sellastline
2439 bind . <Key-Up> "selnextline -1"
2440 bind . <Key-Down> "selnextline 1"
2441 bind . <Shift-Key-Up> "dofind -1 0"
2442 bind . <Shift-Key-Down> "dofind 1 0"
2443 bindkey <Key-Right> "goforw"
2444 bindkey <Key-Left> "goback"
2445 bind . <Key-Prior> "selnextpage -1"
2446 bind . <Key-Next> "selnextpage 1"
2447 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2448 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2449 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2450 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2451 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2452 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2453 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2454 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2455 bindkey <Key-space> "$ctext yview scroll 1 pages"
2456 bindkey p "selnextline -1"
2457 bindkey n "selnextline 1"
2458 bindkey z "goback"
2459 bindkey x "goforw"
2460 bindkey i "selnextline -1"
2461 bindkey k "selnextline 1"
2462 bindkey j "goback"
2463 bindkey l "goforw"
2464 bindkey b prevfile
2465 bindkey d "$ctext yview scroll 18 units"
2466 bindkey u "$ctext yview scroll -18 units"
2467 bindkey / {focus $fstring}
2468 bindkey <Key-KP_Divide> {focus $fstring}
2469 bindkey <Key-Return> {dofind 1 1}
2470 bindkey ? {dofind -1 1}
2471 bindkey f nextfile
2472 bind . <F5> updatecommits
2473 bind . <$M1B-F5> reloadcommits
2474 bind . <F2> showrefs
2475 bind . <Shift-F4> {newview 0}
2476 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2477 bind . <F4> edit_or_newview
2478 bind . <$M1B-q> doquit
2479 bind . <$M1B-f> {dofind 1 1}
2480 bind . <$M1B-g> {dofind 1 0}
2481 bind . <$M1B-r> dosearchback
2482 bind . <$M1B-s> dosearch
2483 bind . <$M1B-equal> {incrfont 1}
2484 bind . <$M1B-plus> {incrfont 1}
2485 bind . <$M1B-KP_Add> {incrfont 1}
2486 bind . <$M1B-minus> {incrfont -1}
2487 bind . <$M1B-KP_Subtract> {incrfont -1}
2488 wm protocol . WM_DELETE_WINDOW doquit
2489 bind . <Destroy> {stop_backends}
2490 bind . <Button-1> "click %W"
2491 bind $fstring <Key-Return> {dofind 1 1}
2492 bind $sha1entry <Key-Return> {gotocommit; break}
2493 bind $sha1entry <<PasteSelection>> clearsha1
2494 bind $cflist <1> {sel_flist %W %x %y; break}
2495 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2496 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2497 global ctxbut
2498 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2499 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2500 bind $ctext <Button-1> {focus %W}
2502 set maincursor [. cget -cursor]
2503 set textcursor [$ctext cget -cursor]
2504 set curtextcursor $textcursor
2506 set rowctxmenu .rowctxmenu
2507 makemenu $rowctxmenu {
2508 {mc "Diff this -> selected" command {diffvssel 0}}
2509 {mc "Diff selected -> this" command {diffvssel 1}}
2510 {mc "Make patch" command mkpatch}
2511 {mc "Create tag" command mktag}
2512 {mc "Write commit to file" command writecommit}
2513 {mc "Create new branch" command mkbranch}
2514 {mc "Cherry-pick this commit" command cherrypick}
2515 {mc "Reset HEAD branch to here" command resethead}
2516 {mc "Mark this commit" command markhere}
2517 {mc "Return to mark" command gotomark}
2518 {mc "Find descendant of this and mark" command find_common_desc}
2519 {mc "Compare with marked commit" command compare_commits}
2521 $rowctxmenu configure -tearoff 0
2523 set fakerowmenu .fakerowmenu
2524 makemenu $fakerowmenu {
2525 {mc "Diff this -> selected" command {diffvssel 0}}
2526 {mc "Diff selected -> this" command {diffvssel 1}}
2527 {mc "Make patch" command mkpatch}
2529 $fakerowmenu configure -tearoff 0
2531 set headctxmenu .headctxmenu
2532 makemenu $headctxmenu {
2533 {mc "Check out this branch" command cobranch}
2534 {mc "Remove this branch" command rmbranch}
2536 $headctxmenu configure -tearoff 0
2538 global flist_menu
2539 set flist_menu .flistctxmenu
2540 makemenu $flist_menu {
2541 {mc "Highlight this too" command {flist_hl 0}}
2542 {mc "Highlight this only" command {flist_hl 1}}
2543 {mc "External diff" command {external_diff}}
2544 {mc "Blame parent commit" command {external_blame 1}}
2546 $flist_menu configure -tearoff 0
2548 global diff_menu
2549 set diff_menu .diffctxmenu
2550 makemenu $diff_menu {
2551 {mc "Show origin of this line" command show_line_source}
2552 {mc "Run git gui blame on this line" command {external_blame_diff}}
2554 $diff_menu configure -tearoff 0
2557 # Windows sends all mouse wheel events to the current focused window, not
2558 # the one where the mouse hovers, so bind those events here and redirect
2559 # to the correct window
2560 proc windows_mousewheel_redirector {W X Y D} {
2561 global canv canv2 canv3
2562 set w [winfo containing -displayof $W $X $Y]
2563 if {$w ne ""} {
2564 set u [expr {$D < 0 ? 5 : -5}]
2565 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2566 allcanvs yview scroll $u units
2567 } else {
2568 catch {
2569 $w yview scroll $u units
2575 # Update row number label when selectedline changes
2576 proc selectedline_change {n1 n2 op} {
2577 global selectedline rownumsel
2579 if {$selectedline eq {}} {
2580 set rownumsel {}
2581 } else {
2582 set rownumsel [expr {$selectedline + 1}]
2586 # mouse-2 makes all windows scan vertically, but only the one
2587 # the cursor is in scans horizontally
2588 proc canvscan {op w x y} {
2589 global canv canv2 canv3
2590 foreach c [list $canv $canv2 $canv3] {
2591 if {$c == $w} {
2592 $c scan $op $x $y
2593 } else {
2594 $c scan $op 0 $y
2599 proc scrollcanv {cscroll f0 f1} {
2600 $cscroll set $f0 $f1
2601 drawvisible
2602 flushhighlights
2605 # when we make a key binding for the toplevel, make sure
2606 # it doesn't get triggered when that key is pressed in the
2607 # find string entry widget.
2608 proc bindkey {ev script} {
2609 global entries
2610 bind . $ev $script
2611 set escript [bind Entry $ev]
2612 if {$escript == {}} {
2613 set escript [bind Entry <Key>]
2615 foreach e $entries {
2616 bind $e $ev "$escript; break"
2620 # set the focus back to the toplevel for any click outside
2621 # the entry widgets
2622 proc click {w} {
2623 global ctext entries
2624 foreach e [concat $entries $ctext] {
2625 if {$w == $e} return
2627 focus .
2630 # Adjust the progress bar for a change in requested extent or canvas size
2631 proc adjustprogress {} {
2632 global progresscanv progressitem progresscoords
2633 global fprogitem fprogcoord lastprogupdate progupdatepending
2634 global rprogitem rprogcoord use_ttk
2636 if {$use_ttk} {
2637 $progresscanv configure -value [expr {int($fprogcoord * 100)}]
2638 return
2641 set w [expr {[winfo width $progresscanv] - 4}]
2642 set x0 [expr {$w * [lindex $progresscoords 0]}]
2643 set x1 [expr {$w * [lindex $progresscoords 1]}]
2644 set h [winfo height $progresscanv]
2645 $progresscanv coords $progressitem $x0 0 $x1 $h
2646 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2647 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2648 set now [clock clicks -milliseconds]
2649 if {$now >= $lastprogupdate + 100} {
2650 set progupdatepending 0
2651 update
2652 } elseif {!$progupdatepending} {
2653 set progupdatepending 1
2654 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2658 proc doprogupdate {} {
2659 global lastprogupdate progupdatepending
2661 if {$progupdatepending} {
2662 set progupdatepending 0
2663 set lastprogupdate [clock clicks -milliseconds]
2664 update
2668 proc savestuff {w} {
2669 global canv canv2 canv3 mainfont textfont uifont tabstop
2670 global stuffsaved findmergefiles maxgraphpct
2671 global maxwidth showneartags showlocalchanges
2672 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2673 global cmitmode wrapcomment datetimeformat limitdiffs
2674 global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
2675 global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
2676 global hideremotes want_ttk
2678 if {$stuffsaved} return
2679 if {![winfo viewable .]} return
2680 catch {
2681 if {[file exists ~/.gitk-new]} {file delete -force ~/.gitk-new}
2682 set f [open "~/.gitk-new" w]
2683 if {$::tcl_platform(platform) eq {windows}} {
2684 file attributes "~/.gitk-new" -hidden true
2686 puts $f [list set mainfont $mainfont]
2687 puts $f [list set textfont $textfont]
2688 puts $f [list set uifont $uifont]
2689 puts $f [list set tabstop $tabstop]
2690 puts $f [list set findmergefiles $findmergefiles]
2691 puts $f [list set maxgraphpct $maxgraphpct]
2692 puts $f [list set maxwidth $maxwidth]
2693 puts $f [list set cmitmode $cmitmode]
2694 puts $f [list set wrapcomment $wrapcomment]
2695 puts $f [list set autoselect $autoselect]
2696 puts $f [list set autosellen $autosellen]
2697 puts $f [list set showneartags $showneartags]
2698 puts $f [list set hideremotes $hideremotes]
2699 puts $f [list set showlocalchanges $showlocalchanges]
2700 puts $f [list set datetimeformat $datetimeformat]
2701 puts $f [list set limitdiffs $limitdiffs]
2702 puts $f [list set uicolor $uicolor]
2703 puts $f [list set want_ttk $want_ttk]
2704 puts $f [list set bgcolor $bgcolor]
2705 puts $f [list set fgcolor $fgcolor]
2706 puts $f [list set colors $colors]
2707 puts $f [list set diffcolors $diffcolors]
2708 puts $f [list set markbgcolor $markbgcolor]
2709 puts $f [list set diffcontext $diffcontext]
2710 puts $f [list set selectbgcolor $selectbgcolor]
2711 puts $f [list set extdifftool $extdifftool]
2712 puts $f [list set perfile_attrs $perfile_attrs]
2714 puts $f "set geometry(main) [wm geometry .]"
2715 puts $f "set geometry(state) [wm state .]"
2716 puts $f "set geometry(topwidth) [winfo width .tf]"
2717 puts $f "set geometry(topheight) [winfo height .tf]"
2718 if {$use_ttk} {
2719 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
2720 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
2721 } else {
2722 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2723 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2725 puts $f "set geometry(botwidth) [winfo width .bleft]"
2726 puts $f "set geometry(botheight) [winfo height .bleft]"
2728 puts -nonewline $f "set permviews {"
2729 for {set v 0} {$v < $nextviewnum} {incr v} {
2730 if {$viewperm($v)} {
2731 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2734 puts $f "}"
2735 close $f
2736 file rename -force "~/.gitk-new" "~/.gitk"
2738 set stuffsaved 1
2741 proc resizeclistpanes {win w} {
2742 global oldwidth use_ttk
2743 if {[info exists oldwidth($win)]} {
2744 if {$use_ttk} {
2745 set s0 [$win sashpos 0]
2746 set s1 [$win sashpos 1]
2747 } else {
2748 set s0 [$win sash coord 0]
2749 set s1 [$win sash coord 1]
2751 if {$w < 60} {
2752 set sash0 [expr {int($w/2 - 2)}]
2753 set sash1 [expr {int($w*5/6 - 2)}]
2754 } else {
2755 set factor [expr {1.0 * $w / $oldwidth($win)}]
2756 set sash0 [expr {int($factor * [lindex $s0 0])}]
2757 set sash1 [expr {int($factor * [lindex $s1 0])}]
2758 if {$sash0 < 30} {
2759 set sash0 30
2761 if {$sash1 < $sash0 + 20} {
2762 set sash1 [expr {$sash0 + 20}]
2764 if {$sash1 > $w - 10} {
2765 set sash1 [expr {$w - 10}]
2766 if {$sash0 > $sash1 - 20} {
2767 set sash0 [expr {$sash1 - 20}]
2771 if {$use_ttk} {
2772 $win sashpos 0 $sash0
2773 $win sashpos 1 $sash1
2774 } else {
2775 $win sash place 0 $sash0 [lindex $s0 1]
2776 $win sash place 1 $sash1 [lindex $s1 1]
2779 set oldwidth($win) $w
2782 proc resizecdetpanes {win w} {
2783 global oldwidth use_ttk
2784 if {[info exists oldwidth($win)]} {
2785 if {$use_ttk} {
2786 set s0 [$win sashpos 0]
2787 } else {
2788 set s0 [$win sash coord 0]
2790 if {$w < 60} {
2791 set sash0 [expr {int($w*3/4 - 2)}]
2792 } else {
2793 set factor [expr {1.0 * $w / $oldwidth($win)}]
2794 set sash0 [expr {int($factor * [lindex $s0 0])}]
2795 if {$sash0 < 45} {
2796 set sash0 45
2798 if {$sash0 > $w - 15} {
2799 set sash0 [expr {$w - 15}]
2802 if {$use_ttk} {
2803 $win sashpos 0 $sash0
2804 } else {
2805 $win sash place 0 $sash0 [lindex $s0 1]
2808 set oldwidth($win) $w
2811 proc allcanvs args {
2812 global canv canv2 canv3
2813 eval $canv $args
2814 eval $canv2 $args
2815 eval $canv3 $args
2818 proc bindall {event action} {
2819 global canv canv2 canv3
2820 bind $canv $event $action
2821 bind $canv2 $event $action
2822 bind $canv3 $event $action
2825 proc about {} {
2826 global uifont NS
2827 set w .about
2828 if {[winfo exists $w]} {
2829 raise $w
2830 return
2832 ttk_toplevel $w
2833 wm title $w [mc "About gitk"]
2834 make_transient $w .
2835 message $w.m -text [mc "
2836 Gitk - a commit viewer for git
2838 Copyright \u00a9 2005-2010 Paul Mackerras
2840 Use and redistribute under the terms of the GNU General Public License"] \
2841 -justify center -aspect 400 -border 2 -bg white -relief groove
2842 pack $w.m -side top -fill x -padx 2 -pady 2
2843 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2844 pack $w.ok -side bottom
2845 bind $w <Visibility> "focus $w.ok"
2846 bind $w <Key-Escape> "destroy $w"
2847 bind $w <Key-Return> "destroy $w"
2848 tk::PlaceWindow $w widget .
2851 proc keys {} {
2852 global NS
2853 set w .keys
2854 if {[winfo exists $w]} {
2855 raise $w
2856 return
2858 if {[tk windowingsystem] eq {aqua}} {
2859 set M1T Cmd
2860 } else {
2861 set M1T Ctrl
2863 ttk_toplevel $w
2864 wm title $w [mc "Gitk key bindings"]
2865 make_transient $w .
2866 message $w.m -text "
2867 [mc "Gitk key bindings:"]
2869 [mc "<%s-Q> Quit" $M1T]
2870 [mc "<%s-W> Close window" $M1T]
2871 [mc "<Home> Move to first commit"]
2872 [mc "<End> Move to last commit"]
2873 [mc "<Up>, p, i Move up one commit"]
2874 [mc "<Down>, n, k Move down one commit"]
2875 [mc "<Left>, z, j Go back in history list"]
2876 [mc "<Right>, x, l Go forward in history list"]
2877 [mc "<PageUp> Move up one page in commit list"]
2878 [mc "<PageDown> Move down one page in commit list"]
2879 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2880 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2881 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2882 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2883 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2884 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2885 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2886 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2887 [mc "<Delete>, b Scroll diff view up one page"]
2888 [mc "<Backspace> Scroll diff view up one page"]
2889 [mc "<Space> Scroll diff view down one page"]
2890 [mc "u Scroll diff view up 18 lines"]
2891 [mc "d Scroll diff view down 18 lines"]
2892 [mc "<%s-F> Find" $M1T]
2893 [mc "<%s-G> Move to next find hit" $M1T]
2894 [mc "<Return> Move to next find hit"]
2895 [mc "/ Focus the search box"]
2896 [mc "? Move to previous find hit"]
2897 [mc "f Scroll diff view to next file"]
2898 [mc "<%s-S> Search for next hit in diff view" $M1T]
2899 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2900 [mc "<%s-KP+> Increase font size" $M1T]
2901 [mc "<%s-plus> Increase font size" $M1T]
2902 [mc "<%s-KP-> Decrease font size" $M1T]
2903 [mc "<%s-minus> Decrease font size" $M1T]
2904 [mc "<F5> Update"]
2906 -justify left -bg white -border 2 -relief groove
2907 pack $w.m -side top -fill both -padx 2 -pady 2
2908 ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2909 bind $w <Key-Escape> [list destroy $w]
2910 pack $w.ok -side bottom
2911 bind $w <Visibility> "focus $w.ok"
2912 bind $w <Key-Escape> "destroy $w"
2913 bind $w <Key-Return> "destroy $w"
2916 # Procedures for manipulating the file list window at the
2917 # bottom right of the overall window.
2919 proc treeview {w l openlevs} {
2920 global treecontents treediropen treeheight treeparent treeindex
2922 set ix 0
2923 set treeindex() 0
2924 set lev 0
2925 set prefix {}
2926 set prefixend -1
2927 set prefendstack {}
2928 set htstack {}
2929 set ht 0
2930 set treecontents() {}
2931 $w conf -state normal
2932 foreach f $l {
2933 while {[string range $f 0 $prefixend] ne $prefix} {
2934 if {$lev <= $openlevs} {
2935 $w mark set e:$treeindex($prefix) "end -1c"
2936 $w mark gravity e:$treeindex($prefix) left
2938 set treeheight($prefix) $ht
2939 incr ht [lindex $htstack end]
2940 set htstack [lreplace $htstack end end]
2941 set prefixend [lindex $prefendstack end]
2942 set prefendstack [lreplace $prefendstack end end]
2943 set prefix [string range $prefix 0 $prefixend]
2944 incr lev -1
2946 set tail [string range $f [expr {$prefixend+1}] end]
2947 while {[set slash [string first "/" $tail]] >= 0} {
2948 lappend htstack $ht
2949 set ht 0
2950 lappend prefendstack $prefixend
2951 incr prefixend [expr {$slash + 1}]
2952 set d [string range $tail 0 $slash]
2953 lappend treecontents($prefix) $d
2954 set oldprefix $prefix
2955 append prefix $d
2956 set treecontents($prefix) {}
2957 set treeindex($prefix) [incr ix]
2958 set treeparent($prefix) $oldprefix
2959 set tail [string range $tail [expr {$slash+1}] end]
2960 if {$lev <= $openlevs} {
2961 set ht 1
2962 set treediropen($prefix) [expr {$lev < $openlevs}]
2963 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2964 $w mark set d:$ix "end -1c"
2965 $w mark gravity d:$ix left
2966 set str "\n"
2967 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2968 $w insert end $str
2969 $w image create end -align center -image $bm -padx 1 \
2970 -name a:$ix
2971 $w insert end $d [highlight_tag $prefix]
2972 $w mark set s:$ix "end -1c"
2973 $w mark gravity s:$ix left
2975 incr lev
2977 if {$tail ne {}} {
2978 if {$lev <= $openlevs} {
2979 incr ht
2980 set str "\n"
2981 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2982 $w insert end $str
2983 $w insert end $tail [highlight_tag $f]
2985 lappend treecontents($prefix) $tail
2988 while {$htstack ne {}} {
2989 set treeheight($prefix) $ht
2990 incr ht [lindex $htstack end]
2991 set htstack [lreplace $htstack end end]
2992 set prefixend [lindex $prefendstack end]
2993 set prefendstack [lreplace $prefendstack end end]
2994 set prefix [string range $prefix 0 $prefixend]
2996 $w conf -state disabled
2999 proc linetoelt {l} {
3000 global treeheight treecontents
3002 set y 2
3003 set prefix {}
3004 while {1} {
3005 foreach e $treecontents($prefix) {
3006 if {$y == $l} {
3007 return "$prefix$e"
3009 set n 1
3010 if {[string index $e end] eq "/"} {
3011 set n $treeheight($prefix$e)
3012 if {$y + $n > $l} {
3013 append prefix $e
3014 incr y
3015 break
3018 incr y $n
3023 proc highlight_tree {y prefix} {
3024 global treeheight treecontents cflist
3026 foreach e $treecontents($prefix) {
3027 set path $prefix$e
3028 if {[highlight_tag $path] ne {}} {
3029 $cflist tag add bold $y.0 "$y.0 lineend"
3031 incr y
3032 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
3033 set y [highlight_tree $y $path]
3036 return $y
3039 proc treeclosedir {w dir} {
3040 global treediropen treeheight treeparent treeindex
3042 set ix $treeindex($dir)
3043 $w conf -state normal
3044 $w delete s:$ix e:$ix
3045 set treediropen($dir) 0
3046 $w image configure a:$ix -image tri-rt
3047 $w conf -state disabled
3048 set n [expr {1 - $treeheight($dir)}]
3049 while {$dir ne {}} {
3050 incr treeheight($dir) $n
3051 set dir $treeparent($dir)
3055 proc treeopendir {w dir} {
3056 global treediropen treeheight treeparent treecontents treeindex
3058 set ix $treeindex($dir)
3059 $w conf -state normal
3060 $w image configure a:$ix -image tri-dn
3061 $w mark set e:$ix s:$ix
3062 $w mark gravity e:$ix right
3063 set lev 0
3064 set str "\n"
3065 set n [llength $treecontents($dir)]
3066 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
3067 incr lev
3068 append str "\t"
3069 incr treeheight($x) $n
3071 foreach e $treecontents($dir) {
3072 set de $dir$e
3073 if {[string index $e end] eq "/"} {
3074 set iy $treeindex($de)
3075 $w mark set d:$iy e:$ix
3076 $w mark gravity d:$iy left
3077 $w insert e:$ix $str
3078 set treediropen($de) 0
3079 $w image create e:$ix -align center -image tri-rt -padx 1 \
3080 -name a:$iy
3081 $w insert e:$ix $e [highlight_tag $de]
3082 $w mark set s:$iy e:$ix
3083 $w mark gravity s:$iy left
3084 set treeheight($de) 1
3085 } else {
3086 $w insert e:$ix $str
3087 $w insert e:$ix $e [highlight_tag $de]
3090 $w mark gravity e:$ix right
3091 $w conf -state disabled
3092 set treediropen($dir) 1
3093 set top [lindex [split [$w index @0,0] .] 0]
3094 set ht [$w cget -height]
3095 set l [lindex [split [$w index s:$ix] .] 0]
3096 if {$l < $top} {
3097 $w yview $l.0
3098 } elseif {$l + $n + 1 > $top + $ht} {
3099 set top [expr {$l + $n + 2 - $ht}]
3100 if {$l < $top} {
3101 set top $l
3103 $w yview $top.0
3107 proc treeclick {w x y} {
3108 global treediropen cmitmode ctext cflist cflist_top
3110 if {$cmitmode ne "tree"} return
3111 if {![info exists cflist_top]} return
3112 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3113 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3114 $cflist tag add highlight $l.0 "$l.0 lineend"
3115 set cflist_top $l
3116 if {$l == 1} {
3117 $ctext yview 1.0
3118 return
3120 set e [linetoelt $l]
3121 if {[string index $e end] ne "/"} {
3122 showfile $e
3123 } elseif {$treediropen($e)} {
3124 treeclosedir $w $e
3125 } else {
3126 treeopendir $w $e
3130 proc setfilelist {id} {
3131 global treefilelist cflist jump_to_here
3133 treeview $cflist $treefilelist($id) 0
3134 if {$jump_to_here ne {}} {
3135 set f [lindex $jump_to_here 0]
3136 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
3137 showfile $f
3142 image create bitmap tri-rt -background black -foreground blue -data {
3143 #define tri-rt_width 13
3144 #define tri-rt_height 13
3145 static unsigned char tri-rt_bits[] = {
3146 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
3147 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
3148 0x00, 0x00};
3149 } -maskdata {
3150 #define tri-rt-mask_width 13
3151 #define tri-rt-mask_height 13
3152 static unsigned char tri-rt-mask_bits[] = {
3153 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
3154 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
3155 0x08, 0x00};
3157 image create bitmap tri-dn -background black -foreground blue -data {
3158 #define tri-dn_width 13
3159 #define tri-dn_height 13
3160 static unsigned char tri-dn_bits[] = {
3161 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
3162 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3163 0x00, 0x00};
3164 } -maskdata {
3165 #define tri-dn-mask_width 13
3166 #define tri-dn-mask_height 13
3167 static unsigned char tri-dn-mask_bits[] = {
3168 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
3169 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
3170 0x00, 0x00};
3173 image create bitmap reficon-T -background black -foreground yellow -data {
3174 #define tagicon_width 13
3175 #define tagicon_height 9
3176 static unsigned char tagicon_bits[] = {
3177 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
3178 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
3179 } -maskdata {
3180 #define tagicon-mask_width 13
3181 #define tagicon-mask_height 9
3182 static unsigned char tagicon-mask_bits[] = {
3183 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
3184 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
3186 set rectdata {
3187 #define headicon_width 13
3188 #define headicon_height 9
3189 static unsigned char headicon_bits[] = {
3190 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
3191 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
3193 set rectmask {
3194 #define headicon-mask_width 13
3195 #define headicon-mask_height 9
3196 static unsigned char headicon-mask_bits[] = {
3197 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
3198 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
3200 image create bitmap reficon-H -background black -foreground green \
3201 -data $rectdata -maskdata $rectmask
3202 image create bitmap reficon-o -background black -foreground "#ddddff" \
3203 -data $rectdata -maskdata $rectmask
3205 proc init_flist {first} {
3206 global cflist cflist_top difffilestart
3208 $cflist conf -state normal
3209 $cflist delete 0.0 end
3210 if {$first ne {}} {
3211 $cflist insert end $first
3212 set cflist_top 1
3213 $cflist tag add highlight 1.0 "1.0 lineend"
3214 } else {
3215 catch {unset cflist_top}
3217 $cflist conf -state disabled
3218 set difffilestart {}
3221 proc highlight_tag {f} {
3222 global highlight_paths
3224 foreach p $highlight_paths {
3225 if {[string match $p $f]} {
3226 return "bold"
3229 return {}
3232 proc highlight_filelist {} {
3233 global cmitmode cflist
3235 $cflist conf -state normal
3236 if {$cmitmode ne "tree"} {
3237 set end [lindex [split [$cflist index end] .] 0]
3238 for {set l 2} {$l < $end} {incr l} {
3239 set line [$cflist get $l.0 "$l.0 lineend"]
3240 if {[highlight_tag $line] ne {}} {
3241 $cflist tag add bold $l.0 "$l.0 lineend"
3244 } else {
3245 highlight_tree 2 {}
3247 $cflist conf -state disabled
3250 proc unhighlight_filelist {} {
3251 global cflist
3253 $cflist conf -state normal
3254 $cflist tag remove bold 1.0 end
3255 $cflist conf -state disabled
3258 proc add_flist {fl} {
3259 global cflist
3261 $cflist conf -state normal
3262 foreach f $fl {
3263 $cflist insert end "\n"
3264 $cflist insert end $f [highlight_tag $f]
3266 $cflist conf -state disabled
3269 proc sel_flist {w x y} {
3270 global ctext difffilestart cflist cflist_top cmitmode
3272 if {$cmitmode eq "tree"} return
3273 if {![info exists cflist_top]} return
3274 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3275 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3276 $cflist tag add highlight $l.0 "$l.0 lineend"
3277 set cflist_top $l
3278 if {$l == 1} {
3279 $ctext yview 1.0
3280 } else {
3281 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3285 proc pop_flist_menu {w X Y x y} {
3286 global ctext cflist cmitmode flist_menu flist_menu_file
3287 global treediffs diffids
3289 stopfinding
3290 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3291 if {$l <= 1} return
3292 if {$cmitmode eq "tree"} {
3293 set e [linetoelt $l]
3294 if {[string index $e end] eq "/"} return
3295 } else {
3296 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3298 set flist_menu_file $e
3299 set xdiffstate "normal"
3300 if {$cmitmode eq "tree"} {
3301 set xdiffstate "disabled"
3303 # Disable "External diff" item in tree mode
3304 $flist_menu entryconf 2 -state $xdiffstate
3305 tk_popup $flist_menu $X $Y
3308 proc find_ctext_fileinfo {line} {
3309 global ctext_file_names ctext_file_lines
3311 set ok [bsearch $ctext_file_lines $line]
3312 set tline [lindex $ctext_file_lines $ok]
3314 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3315 return {}
3316 } else {
3317 return [list [lindex $ctext_file_names $ok] $tline]
3321 proc pop_diff_menu {w X Y x y} {
3322 global ctext diff_menu flist_menu_file
3323 global diff_menu_txtpos diff_menu_line
3324 global diff_menu_filebase
3326 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3327 set diff_menu_line [lindex $diff_menu_txtpos 0]
3328 # don't pop up the menu on hunk-separator or file-separator lines
3329 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3330 return
3332 stopfinding
3333 set f [find_ctext_fileinfo $diff_menu_line]
3334 if {$f eq {}} return
3335 set flist_menu_file [lindex $f 0]
3336 set diff_menu_filebase [lindex $f 1]
3337 tk_popup $diff_menu $X $Y
3340 proc flist_hl {only} {
3341 global flist_menu_file findstring gdttype
3343 set x [shellquote $flist_menu_file]
3344 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3345 set findstring $x
3346 } else {
3347 append findstring " " $x
3349 set gdttype [mc "touching paths:"]
3352 proc gitknewtmpdir {} {
3353 global diffnum gitktmpdir gitdir
3355 if {![info exists gitktmpdir]} {
3356 set gitktmpdir [file join [file dirname $gitdir] \
3357 [format ".gitk-tmp.%s" [pid]]]
3358 if {[catch {file mkdir $gitktmpdir} err]} {
3359 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3360 unset gitktmpdir
3361 return {}
3363 set diffnum 0
3365 incr diffnum
3366 set diffdir [file join $gitktmpdir $diffnum]
3367 if {[catch {file mkdir $diffdir} err]} {
3368 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3369 return {}
3371 return $diffdir
3374 proc save_file_from_commit {filename output what} {
3375 global nullfile
3377 if {[catch {exec git show $filename -- > $output} err]} {
3378 if {[string match "fatal: bad revision *" $err]} {
3379 return $nullfile
3381 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3382 return {}
3384 return $output
3387 proc external_diff_get_one_file {diffid filename diffdir} {
3388 global nullid nullid2 nullfile
3389 global gitdir
3391 if {$diffid == $nullid} {
3392 set difffile [file join [file dirname $gitdir] $filename]
3393 if {[file exists $difffile]} {
3394 return $difffile
3396 return $nullfile
3398 if {$diffid == $nullid2} {
3399 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3400 return [save_file_from_commit :$filename $difffile index]
3402 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3403 return [save_file_from_commit $diffid:$filename $difffile \
3404 "revision $diffid"]
3407 proc external_diff {} {
3408 global nullid nullid2
3409 global flist_menu_file
3410 global diffids
3411 global extdifftool
3413 if {[llength $diffids] == 1} {
3414 # no reference commit given
3415 set diffidto [lindex $diffids 0]
3416 if {$diffidto eq $nullid} {
3417 # diffing working copy with index
3418 set diffidfrom $nullid2
3419 } elseif {$diffidto eq $nullid2} {
3420 # diffing index with HEAD
3421 set diffidfrom "HEAD"
3422 } else {
3423 # use first parent commit
3424 global parentlist selectedline
3425 set diffidfrom [lindex $parentlist $selectedline 0]
3427 } else {
3428 set diffidfrom [lindex $diffids 0]
3429 set diffidto [lindex $diffids 1]
3432 # make sure that several diffs wont collide
3433 set diffdir [gitknewtmpdir]
3434 if {$diffdir eq {}} return
3436 # gather files to diff
3437 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3438 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3440 if {$difffromfile ne {} && $difftofile ne {}} {
3441 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
3442 if {[catch {set fl [open |$cmd r]} err]} {
3443 file delete -force $diffdir
3444 error_popup "$extdifftool: [mc "command failed:"] $err"
3445 } else {
3446 fconfigure $fl -blocking 0
3447 filerun $fl [list delete_at_eof $fl $diffdir]
3452 proc find_hunk_blamespec {base line} {
3453 global ctext
3455 # Find and parse the hunk header
3456 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3457 if {$s_lix eq {}} return
3459 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3460 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3461 s_line old_specs osz osz1 new_line nsz]} {
3462 return
3465 # base lines for the parents
3466 set base_lines [list $new_line]
3467 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3468 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3469 old_spec old_line osz]} {
3470 return
3472 lappend base_lines $old_line
3475 # Now scan the lines to determine offset within the hunk
3476 set max_parent [expr {[llength $base_lines]-2}]
3477 set dline 0
3478 set s_lno [lindex [split $s_lix "."] 0]
3480 # Determine if the line is removed
3481 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3482 if {[string match {[-+ ]*} $chunk]} {
3483 set removed_idx [string first "-" $chunk]
3484 # Choose a parent index
3485 if {$removed_idx >= 0} {
3486 set parent $removed_idx
3487 } else {
3488 set unchanged_idx [string first " " $chunk]
3489 if {$unchanged_idx >= 0} {
3490 set parent $unchanged_idx
3491 } else {
3492 # blame the current commit
3493 set parent -1
3496 # then count other lines that belong to it
3497 for {set i $line} {[incr i -1] > $s_lno} {} {
3498 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3499 # Determine if the line is removed
3500 set removed_idx [string first "-" $chunk]
3501 if {$parent >= 0} {
3502 set code [string index $chunk $parent]
3503 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3504 incr dline
3506 } else {
3507 if {$removed_idx < 0} {
3508 incr dline
3512 incr parent
3513 } else {
3514 set parent 0
3517 incr dline [lindex $base_lines $parent]
3518 return [list $parent $dline]
3521 proc external_blame_diff {} {
3522 global currentid cmitmode
3523 global diff_menu_txtpos diff_menu_line
3524 global diff_menu_filebase flist_menu_file
3526 if {$cmitmode eq "tree"} {
3527 set parent_idx 0
3528 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3529 } else {
3530 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3531 if {$hinfo ne {}} {
3532 set parent_idx [lindex $hinfo 0]
3533 set line [lindex $hinfo 1]
3534 } else {
3535 set parent_idx 0
3536 set line 0
3540 external_blame $parent_idx $line
3543 # Find the SHA1 ID of the blob for file $fname in the index
3544 # at stage 0 or 2
3545 proc index_sha1 {fname} {
3546 set f [open [list | git ls-files -s $fname] r]
3547 while {[gets $f line] >= 0} {
3548 set info [lindex [split $line "\t"] 0]
3549 set stage [lindex $info 2]
3550 if {$stage eq "0" || $stage eq "2"} {
3551 close $f
3552 return [lindex $info 1]
3555 close $f
3556 return {}
3559 # Turn an absolute path into one relative to the current directory
3560 proc make_relative {f} {
3561 if {[file pathtype $f] eq "relative"} {
3562 return $f
3564 set elts [file split $f]
3565 set here [file split [pwd]]
3566 set ei 0
3567 set hi 0
3568 set res {}
3569 foreach d $here {
3570 if {$ei < $hi || $ei >= [llength $elts] || [lindex $elts $ei] ne $d} {
3571 lappend res ".."
3572 } else {
3573 incr ei
3575 incr hi
3577 set elts [concat $res [lrange $elts $ei end]]
3578 return [eval file join $elts]
3581 proc external_blame {parent_idx {line {}}} {
3582 global flist_menu_file gitdir
3583 global nullid nullid2
3584 global parentlist selectedline currentid
3586 if {$parent_idx > 0} {
3587 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3588 } else {
3589 set base_commit $currentid
3592 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3593 error_popup [mc "No such commit"]
3594 return
3597 set cmdline [list git gui blame]
3598 if {$line ne {} && $line > 1} {
3599 lappend cmdline "--line=$line"
3601 set f [file join [file dirname $gitdir] $flist_menu_file]
3602 # Unfortunately it seems git gui blame doesn't like
3603 # being given an absolute path...
3604 set f [make_relative $f]
3605 lappend cmdline $base_commit $f
3606 if {[catch {eval exec $cmdline &} err]} {
3607 error_popup "[mc "git gui blame: command failed:"] $err"
3611 proc show_line_source {} {
3612 global cmitmode currentid parents curview blamestuff blameinst
3613 global diff_menu_line diff_menu_filebase flist_menu_file
3614 global nullid nullid2 gitdir
3616 set from_index {}
3617 if {$cmitmode eq "tree"} {
3618 set id $currentid
3619 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3620 } else {
3621 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3622 if {$h eq {}} return
3623 set pi [lindex $h 0]
3624 if {$pi == 0} {
3625 mark_ctext_line $diff_menu_line
3626 return
3628 incr pi -1
3629 if {$currentid eq $nullid} {
3630 if {$pi > 0} {
3631 # must be a merge in progress...
3632 if {[catch {
3633 # get the last line from .git/MERGE_HEAD
3634 set f [open [file join $gitdir MERGE_HEAD] r]
3635 set id [lindex [split [read $f] "\n"] end-1]
3636 close $f
3637 } err]} {
3638 error_popup [mc "Couldn't read merge head: %s" $err]
3639 return
3641 } elseif {$parents($curview,$currentid) eq $nullid2} {
3642 # need to do the blame from the index
3643 if {[catch {
3644 set from_index [index_sha1 $flist_menu_file]
3645 } err]} {
3646 error_popup [mc "Error reading index: %s" $err]
3647 return
3649 } else {
3650 set id $parents($curview,$currentid)
3652 } else {
3653 set id [lindex $parents($curview,$currentid) $pi]
3655 set line [lindex $h 1]
3657 set blameargs {}
3658 if {$from_index ne {}} {
3659 lappend blameargs | git cat-file blob $from_index
3661 lappend blameargs | git blame -p -L$line,+1
3662 if {$from_index ne {}} {
3663 lappend blameargs --contents -
3664 } else {
3665 lappend blameargs $id
3667 lappend blameargs -- [file join [file dirname $gitdir] $flist_menu_file]
3668 if {[catch {
3669 set f [open $blameargs r]
3670 } err]} {
3671 error_popup [mc "Couldn't start git blame: %s" $err]
3672 return
3674 nowbusy blaming [mc "Searching"]
3675 fconfigure $f -blocking 0
3676 set i [reg_instance $f]
3677 set blamestuff($i) {}
3678 set blameinst $i
3679 filerun $f [list read_line_source $f $i]
3682 proc stopblaming {} {
3683 global blameinst
3685 if {[info exists blameinst]} {
3686 stop_instance $blameinst
3687 unset blameinst
3688 notbusy blaming
3692 proc read_line_source {fd inst} {
3693 global blamestuff curview commfd blameinst nullid nullid2
3695 while {[gets $fd line] >= 0} {
3696 lappend blamestuff($inst) $line
3698 if {![eof $fd]} {
3699 return 1
3701 unset commfd($inst)
3702 unset blameinst
3703 notbusy blaming
3704 fconfigure $fd -blocking 1
3705 if {[catch {close $fd} err]} {
3706 error_popup [mc "Error running git blame: %s" $err]
3707 return 0
3710 set fname {}
3711 set line [split [lindex $blamestuff($inst) 0] " "]
3712 set id [lindex $line 0]
3713 set lnum [lindex $line 1]
3714 if {[string length $id] == 40 && [string is xdigit $id] &&
3715 [string is digit -strict $lnum]} {
3716 # look for "filename" line
3717 foreach l $blamestuff($inst) {
3718 if {[string match "filename *" $l]} {
3719 set fname [string range $l 9 end]
3720 break
3724 if {$fname ne {}} {
3725 # all looks good, select it
3726 if {$id eq $nullid} {
3727 # blame uses all-zeroes to mean not committed,
3728 # which would mean a change in the index
3729 set id $nullid2
3731 if {[commitinview $id $curview]} {
3732 selectline [rowofcommit $id] 1 [list $fname $lnum]
3733 } else {
3734 error_popup [mc "That line comes from commit %s, \
3735 which is not in this view" [shortids $id]]
3737 } else {
3738 puts "oops couldn't parse git blame output"
3740 return 0
3743 # delete $dir when we see eof on $f (presumably because the child has exited)
3744 proc delete_at_eof {f dir} {
3745 while {[gets $f line] >= 0} {}
3746 if {[eof $f]} {
3747 if {[catch {close $f} err]} {
3748 error_popup "[mc "External diff viewer failed:"] $err"
3750 file delete -force $dir
3751 return 0
3753 return 1
3756 # Functions for adding and removing shell-type quoting
3758 proc shellquote {str} {
3759 if {![string match "*\['\"\\ \t]*" $str]} {
3760 return $str
3762 if {![string match "*\['\"\\]*" $str]} {
3763 return "\"$str\""
3765 if {![string match "*'*" $str]} {
3766 return "'$str'"
3768 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3771 proc shellarglist {l} {
3772 set str {}
3773 foreach a $l {
3774 if {$str ne {}} {
3775 append str " "
3777 append str [shellquote $a]
3779 return $str
3782 proc shelldequote {str} {
3783 set ret {}
3784 set used -1
3785 while {1} {
3786 incr used
3787 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3788 append ret [string range $str $used end]
3789 set used [string length $str]
3790 break
3792 set first [lindex $first 0]
3793 set ch [string index $str $first]
3794 if {$first > $used} {
3795 append ret [string range $str $used [expr {$first - 1}]]
3796 set used $first
3798 if {$ch eq " " || $ch eq "\t"} break
3799 incr used
3800 if {$ch eq "'"} {
3801 set first [string first "'" $str $used]
3802 if {$first < 0} {
3803 error "unmatched single-quote"
3805 append ret [string range $str $used [expr {$first - 1}]]
3806 set used $first
3807 continue
3809 if {$ch eq "\\"} {
3810 if {$used >= [string length $str]} {
3811 error "trailing backslash"
3813 append ret [string index $str $used]
3814 continue
3816 # here ch == "\""
3817 while {1} {
3818 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3819 error "unmatched double-quote"
3821 set first [lindex $first 0]
3822 set ch [string index $str $first]
3823 if {$first > $used} {
3824 append ret [string range $str $used [expr {$first - 1}]]
3825 set used $first
3827 if {$ch eq "\""} break
3828 incr used
3829 append ret [string index $str $used]
3830 incr used
3833 return [list $used $ret]
3836 proc shellsplit {str} {
3837 set l {}
3838 while {1} {
3839 set str [string trimleft $str]
3840 if {$str eq {}} break
3841 set dq [shelldequote $str]
3842 set n [lindex $dq 0]
3843 set word [lindex $dq 1]
3844 set str [string range $str $n end]
3845 lappend l $word
3847 return $l
3850 # Code to implement multiple views
3852 proc newview {ishighlight} {
3853 global nextviewnum newviewname newishighlight
3854 global revtreeargs viewargscmd newviewopts curview
3856 set newishighlight $ishighlight
3857 set top .gitkview
3858 if {[winfo exists $top]} {
3859 raise $top
3860 return
3862 decode_view_opts $nextviewnum $revtreeargs
3863 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3864 set newviewopts($nextviewnum,perm) 0
3865 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3866 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3869 set known_view_options {
3870 {perm b . {} {mc "Remember this view"}}
3871 {reflabel l + {} {mc "References (space separated list):"}}
3872 {refs t15 .. {} {mc "Branches & tags:"}}
3873 {allrefs b *. "--all" {mc "All refs"}}
3874 {branches b . "--branches" {mc "All (local) branches"}}
3875 {tags b . "--tags" {mc "All tags"}}
3876 {remotes b . "--remotes" {mc "All remote-tracking branches"}}
3877 {commitlbl l + {} {mc "Commit Info (regular expressions):"}}
3878 {author t15 .. "--author=*" {mc "Author:"}}
3879 {committer t15 . "--committer=*" {mc "Committer:"}}
3880 {loginfo t15 .. "--grep=*" {mc "Commit Message:"}}
3881 {allmatch b .. "--all-match" {mc "Matches all Commit Info criteria"}}
3882 {changes_l l + {} {mc "Changes to Files:"}}
3883 {pickaxe_s r0 . {} {mc "Fixed String"}}
3884 {pickaxe_t r1 . "--pickaxe-regex" {mc "Regular Expression"}}
3885 {pickaxe t15 .. "-S*" {mc "Search string:"}}
3886 {datelabel l + {} {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}}
3887 {since t15 .. {"--since=*" "--after=*"} {mc "Since:"}}
3888 {until t15 . {"--until=*" "--before=*"} {mc "Until:"}}
3889 {limit_lbl l + {} {mc "Limit and/or skip a number of revisions (positive integer):"}}
3890 {limit t10 *. "--max-count=*" {mc "Number to show:"}}
3891 {skip t10 . "--skip=*" {mc "Number to skip:"}}
3892 {misc_lbl l + {} {mc "Miscellaneous options:"}}
3893 {dorder b *. {"--date-order" "-d"} {mc "Strictly sort by date"}}
3894 {lright b . "--left-right" {mc "Mark branch sides"}}
3895 {first b . "--first-parent" {mc "Limit to first parent"}}
3896 {smplhst b . "--simplify-by-decoration" {mc "Simple history"}}
3897 {args t50 *. {} {mc "Additional arguments to git log:"}}
3898 {allpaths path + {} {mc "Enter files and directories to include, one per line:"}}
3899 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3902 # Convert $newviewopts($n, ...) into args for git log.
3903 proc encode_view_opts {n} {
3904 global known_view_options newviewopts
3906 set rargs [list]
3907 foreach opt $known_view_options {
3908 set patterns [lindex $opt 3]
3909 if {$patterns eq {}} continue
3910 set pattern [lindex $patterns 0]
3912 if {[lindex $opt 1] eq "b"} {
3913 set val $newviewopts($n,[lindex $opt 0])
3914 if {$val} {
3915 lappend rargs $pattern
3917 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} {
3918 regexp {^(.*_)} [lindex $opt 0] uselessvar button_id
3919 set val $newviewopts($n,$button_id)
3920 if {$val eq $value} {
3921 lappend rargs $pattern
3923 } else {
3924 set val $newviewopts($n,[lindex $opt 0])
3925 set val [string trim $val]
3926 if {$val ne {}} {
3927 set pfix [string range $pattern 0 end-1]
3928 lappend rargs $pfix$val
3932 set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]]
3933 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3936 # Fill $newviewopts($n, ...) based on args for git log.
3937 proc decode_view_opts {n view_args} {
3938 global known_view_options newviewopts
3940 foreach opt $known_view_options {
3941 set id [lindex $opt 0]
3942 if {[lindex $opt 1] eq "b"} {
3943 # Checkboxes
3944 set val 0
3945 } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} {
3946 # Radiobuttons
3947 regexp {^(.*_)} $id uselessvar id
3948 set val 0
3949 } else {
3950 # Text fields
3951 set val {}
3953 set newviewopts($n,$id) $val
3955 set oargs [list]
3956 set refargs [list]
3957 foreach arg $view_args {
3958 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3959 && ![info exists found(limit)]} {
3960 set newviewopts($n,limit) $cnt
3961 set found(limit) 1
3962 continue
3964 catch { unset val }
3965 foreach opt $known_view_options {
3966 set id [lindex $opt 0]
3967 if {[info exists found($id)]} continue
3968 foreach pattern [lindex $opt 3] {
3969 if {![string match $pattern $arg]} continue
3970 if {[lindex $opt 1] eq "b"} {
3971 # Check buttons
3972 set val 1
3973 } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} {
3974 # Radio buttons
3975 regexp {^(.*_)} $id uselessvar id
3976 set val $num
3977 } else {
3978 # Text input fields
3979 set size [string length $pattern]
3980 set val [string range $arg [expr {$size-1}] end]
3982 set newviewopts($n,$id) $val
3983 set found($id) 1
3984 break
3986 if {[info exists val]} break
3988 if {[info exists val]} continue
3989 if {[regexp {^-} $arg]} {
3990 lappend oargs $arg
3991 } else {
3992 lappend refargs $arg
3995 set newviewopts($n,refs) [shellarglist $refargs]
3996 set newviewopts($n,args) [shellarglist $oargs]
3999 proc edit_or_newview {} {
4000 global curview
4002 if {$curview > 0} {
4003 editview
4004 } else {
4005 newview 0
4009 proc editview {} {
4010 global curview
4011 global viewname viewperm newviewname newviewopts
4012 global viewargs viewargscmd
4014 set top .gitkvedit-$curview
4015 if {[winfo exists $top]} {
4016 raise $top
4017 return
4019 decode_view_opts $curview $viewargs($curview)
4020 set newviewname($curview) $viewname($curview)
4021 set newviewopts($curview,perm) $viewperm($curview)
4022 set newviewopts($curview,cmd) $viewargscmd($curview)
4023 vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
4026 proc vieweditor {top n title} {
4027 global newviewname newviewopts viewfiles bgcolor
4028 global known_view_options NS
4030 ttk_toplevel $top
4031 wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
4032 make_transient $top .
4034 # View name
4035 ${NS}::frame $top.nfr
4036 ${NS}::label $top.nl -text [mc "View Name"]
4037 ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
4038 pack $top.nfr -in $top -fill x -pady 5 -padx 3
4039 pack $top.nl -in $top.nfr -side left -padx {0 5}
4040 pack $top.name -in $top.nfr -side left -padx {0 25}
4042 # View options
4043 set cframe $top.nfr
4044 set cexpand 0
4045 set cnt 0
4046 foreach opt $known_view_options {
4047 set id [lindex $opt 0]
4048 set type [lindex $opt 1]
4049 set flags [lindex $opt 2]
4050 set title [eval [lindex $opt 4]]
4051 set lxpad 0
4053 if {$flags eq "+" || $flags eq "*"} {
4054 set cframe $top.fr$cnt
4055 incr cnt
4056 ${NS}::frame $cframe
4057 pack $cframe -in $top -fill x -pady 3 -padx 3
4058 set cexpand [expr {$flags eq "*"}]
4059 } elseif {$flags eq ".." || $flags eq "*."} {
4060 set cframe $top.fr$cnt
4061 incr cnt
4062 ${NS}::frame $cframe
4063 pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
4064 set cexpand [expr {$flags eq "*."}]
4065 } else {
4066 set lxpad 5
4069 if {$type eq "l"} {
4070 ${NS}::label $cframe.l_$id -text $title
4071 pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
4072 } elseif {$type eq "b"} {
4073 ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
4074 pack $cframe.c_$id -in $cframe -side left \
4075 -padx [list $lxpad 0] -expand $cexpand -anchor w
4076 } elseif {[regexp {^r(\d+)$} $type type sz]} {
4077 regexp {^(.*_)} $id uselessvar button_id
4078 ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
4079 pack $cframe.c_$id -in $cframe -side left \
4080 -padx [list $lxpad 0] -expand $cexpand -anchor w
4081 } elseif {[regexp {^t(\d+)$} $type type sz]} {
4082 ${NS}::label $cframe.l_$id -text $title
4083 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4084 -textvariable newviewopts($n,$id)
4085 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
4086 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
4087 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
4088 ${NS}::label $cframe.l_$id -text $title
4089 ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
4090 -textvariable newviewopts($n,$id)
4091 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
4092 pack $cframe.e_$id -in $cframe -side top -fill x
4093 } elseif {$type eq "path"} {
4094 ${NS}::label $top.l -text $title
4095 pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
4096 text $top.t -width 40 -height 5 -background $bgcolor
4097 if {[info exists viewfiles($n)]} {
4098 foreach f $viewfiles($n) {
4099 $top.t insert end $f
4100 $top.t insert end "\n"
4102 $top.t delete {end - 1c} end
4103 $top.t mark set insert 0.0
4105 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
4109 ${NS}::frame $top.buts
4110 ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
4111 ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
4112 ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
4113 bind $top <Control-Return> [list newviewok $top $n]
4114 bind $top <F5> [list newviewok $top $n 1]
4115 bind $top <Escape> [list destroy $top]
4116 grid $top.buts.ok $top.buts.apply $top.buts.can
4117 grid columnconfigure $top.buts 0 -weight 1 -uniform a
4118 grid columnconfigure $top.buts 1 -weight 1 -uniform a
4119 grid columnconfigure $top.buts 2 -weight 1 -uniform a
4120 pack $top.buts -in $top -side top -fill x
4121 focus $top.t
4124 proc doviewmenu {m first cmd op argv} {
4125 set nmenu [$m index end]
4126 for {set i $first} {$i <= $nmenu} {incr i} {
4127 if {[$m entrycget $i -command] eq $cmd} {
4128 eval $m $op $i $argv
4129 break
4134 proc allviewmenus {n op args} {
4135 # global viewhlmenu
4137 doviewmenu .bar.view 5 [list showview $n] $op $args
4138 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
4141 proc newviewok {top n {apply 0}} {
4142 global nextviewnum newviewperm newviewname newishighlight
4143 global viewname viewfiles viewperm selectedview curview
4144 global viewargs viewargscmd newviewopts viewhlmenu
4146 if {[catch {
4147 set newargs [encode_view_opts $n]
4148 } err]} {
4149 error_popup "[mc "Error in commit selection arguments:"] $err" $top
4150 return
4152 set files {}
4153 foreach f [split [$top.t get 0.0 end] "\n"] {
4154 set ft [string trim $f]
4155 if {$ft ne {}} {
4156 lappend files $ft
4159 if {![info exists viewfiles($n)]} {
4160 # creating a new view
4161 incr nextviewnum
4162 set viewname($n) $newviewname($n)
4163 set viewperm($n) $newviewopts($n,perm)
4164 set viewfiles($n) $files
4165 set viewargs($n) $newargs
4166 set viewargscmd($n) $newviewopts($n,cmd)
4167 addviewmenu $n
4168 if {!$newishighlight} {
4169 run showview $n
4170 } else {
4171 run addvhighlight $n
4173 } else {
4174 # editing an existing view
4175 set viewperm($n) $newviewopts($n,perm)
4176 if {$newviewname($n) ne $viewname($n)} {
4177 set viewname($n) $newviewname($n)
4178 doviewmenu .bar.view 5 [list showview $n] \
4179 entryconf [list -label $viewname($n)]
4180 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
4181 # entryconf [list -label $viewname($n) -value $viewname($n)]
4183 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
4184 $newviewopts($n,cmd) ne $viewargscmd($n)} {
4185 set viewfiles($n) $files
4186 set viewargs($n) $newargs
4187 set viewargscmd($n) $newviewopts($n,cmd)
4188 if {$curview == $n} {
4189 run reloadcommits
4193 if {$apply} return
4194 catch {destroy $top}
4197 proc delview {} {
4198 global curview viewperm hlview selectedhlview
4200 if {$curview == 0} return
4201 if {[info exists hlview] && $hlview == $curview} {
4202 set selectedhlview [mc "None"]
4203 unset hlview
4205 allviewmenus $curview delete
4206 set viewperm($curview) 0
4207 showview 0
4210 proc addviewmenu {n} {
4211 global viewname viewhlmenu
4213 .bar.view add radiobutton -label $viewname($n) \
4214 -command [list showview $n] -variable selectedview -value $n
4215 #$viewhlmenu add radiobutton -label $viewname($n) \
4216 # -command [list addvhighlight $n] -variable selectedhlview
4219 proc showview {n} {
4220 global curview cached_commitrow ordertok
4221 global displayorder parentlist rowidlist rowisopt rowfinal
4222 global colormap rowtextx nextcolor canvxmax
4223 global numcommits viewcomplete
4224 global selectedline currentid canv canvy0
4225 global treediffs
4226 global pending_select mainheadid
4227 global commitidx
4228 global selectedview
4229 global hlview selectedhlview commitinterest
4231 if {$n == $curview} return
4232 set selid {}
4233 set ymax [lindex [$canv cget -scrollregion] 3]
4234 set span [$canv yview]
4235 set ytop [expr {[lindex $span 0] * $ymax}]
4236 set ybot [expr {[lindex $span 1] * $ymax}]
4237 set yscreen [expr {($ybot - $ytop) / 2}]
4238 if {$selectedline ne {}} {
4239 set selid $currentid
4240 set y [yc $selectedline]
4241 if {$ytop < $y && $y < $ybot} {
4242 set yscreen [expr {$y - $ytop}]
4244 } elseif {[info exists pending_select]} {
4245 set selid $pending_select
4246 unset pending_select
4248 unselectline
4249 normalline
4250 catch {unset treediffs}
4251 clear_display
4252 if {[info exists hlview] && $hlview == $n} {
4253 unset hlview
4254 set selectedhlview [mc "None"]
4256 catch {unset commitinterest}
4257 catch {unset cached_commitrow}
4258 catch {unset ordertok}
4260 set curview $n
4261 set selectedview $n
4262 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
4263 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
4265 run refill_reflist
4266 if {![info exists viewcomplete($n)]} {
4267 getcommits $selid
4268 return
4271 set displayorder {}
4272 set parentlist {}
4273 set rowidlist {}
4274 set rowisopt {}
4275 set rowfinal {}
4276 set numcommits $commitidx($n)
4278 catch {unset colormap}
4279 catch {unset rowtextx}
4280 set nextcolor 0
4281 set canvxmax [$canv cget -width]
4282 set curview $n
4283 set row 0
4284 setcanvscroll
4285 set yf 0
4286 set row {}
4287 if {$selid ne {} && [commitinview $selid $n]} {
4288 set row [rowofcommit $selid]
4289 # try to get the selected row in the same position on the screen
4290 set ymax [lindex [$canv cget -scrollregion] 3]
4291 set ytop [expr {[yc $row] - $yscreen}]
4292 if {$ytop < 0} {
4293 set ytop 0
4295 set yf [expr {$ytop * 1.0 / $ymax}]
4297 allcanvs yview moveto $yf
4298 drawvisible
4299 if {$row ne {}} {
4300 selectline $row 0
4301 } elseif {!$viewcomplete($n)} {
4302 reset_pending_select $selid
4303 } else {
4304 reset_pending_select {}
4306 if {[commitinview $pending_select $curview]} {
4307 selectline [rowofcommit $pending_select] 1
4308 } else {
4309 set row [first_real_row]
4310 if {$row < $numcommits} {
4311 selectline $row 0
4315 if {!$viewcomplete($n)} {
4316 if {$numcommits == 0} {
4317 show_status [mc "Reading commits..."]
4319 } elseif {$numcommits == 0} {
4320 show_status [mc "No commits selected"]
4324 # Stuff relating to the highlighting facility
4326 proc ishighlighted {id} {
4327 global vhighlights fhighlights nhighlights rhighlights
4329 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
4330 return $nhighlights($id)
4332 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
4333 return $vhighlights($id)
4335 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
4336 return $fhighlights($id)
4338 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4339 return $rhighlights($id)
4341 return 0
4344 proc bolden {id font} {
4345 global canv linehtag currentid boldids need_redisplay markedid
4347 # need_redisplay = 1 means the display is stale and about to be redrawn
4348 if {$need_redisplay} return
4349 lappend boldids $id
4350 $canv itemconf $linehtag($id) -font $font
4351 if {[info exists currentid] && $id eq $currentid} {
4352 $canv delete secsel
4353 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4354 -outline {{}} -tags secsel \
4355 -fill [$canv cget -selectbackground]]
4356 $canv lower $t
4358 if {[info exists markedid] && $id eq $markedid} {
4359 make_idmark $id
4363 proc bolden_name {id font} {
4364 global canv2 linentag currentid boldnameids need_redisplay
4366 if {$need_redisplay} return
4367 lappend boldnameids $id
4368 $canv2 itemconf $linentag($id) -font $font
4369 if {[info exists currentid] && $id eq $currentid} {
4370 $canv2 delete secsel
4371 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4372 -outline {{}} -tags secsel \
4373 -fill [$canv2 cget -selectbackground]]
4374 $canv2 lower $t
4378 proc unbolden {} {
4379 global boldids
4381 set stillbold {}
4382 foreach id $boldids {
4383 if {![ishighlighted $id]} {
4384 bolden $id mainfont
4385 } else {
4386 lappend stillbold $id
4389 set boldids $stillbold
4392 proc addvhighlight {n} {
4393 global hlview viewcomplete curview vhl_done commitidx
4395 if {[info exists hlview]} {
4396 delvhighlight
4398 set hlview $n
4399 if {$n != $curview && ![info exists viewcomplete($n)]} {
4400 start_rev_list $n
4402 set vhl_done $commitidx($hlview)
4403 if {$vhl_done > 0} {
4404 drawvisible
4408 proc delvhighlight {} {
4409 global hlview vhighlights
4411 if {![info exists hlview]} return
4412 unset hlview
4413 catch {unset vhighlights}
4414 unbolden
4417 proc vhighlightmore {} {
4418 global hlview vhl_done commitidx vhighlights curview
4420 set max $commitidx($hlview)
4421 set vr [visiblerows]
4422 set r0 [lindex $vr 0]
4423 set r1 [lindex $vr 1]
4424 for {set i $vhl_done} {$i < $max} {incr i} {
4425 set id [commitonrow $i $hlview]
4426 if {[commitinview $id $curview]} {
4427 set row [rowofcommit $id]
4428 if {$r0 <= $row && $row <= $r1} {
4429 if {![highlighted $row]} {
4430 bolden $id mainfontbold
4432 set vhighlights($id) 1
4436 set vhl_done $max
4437 return 0
4440 proc askvhighlight {row id} {
4441 global hlview vhighlights iddrawn
4443 if {[commitinview $id $hlview]} {
4444 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4445 bolden $id mainfontbold
4447 set vhighlights($id) 1
4448 } else {
4449 set vhighlights($id) 0
4453 proc hfiles_change {} {
4454 global highlight_files filehighlight fhighlights fh_serial
4455 global highlight_paths
4457 if {[info exists filehighlight]} {
4458 # delete previous highlights
4459 catch {close $filehighlight}
4460 unset filehighlight
4461 catch {unset fhighlights}
4462 unbolden
4463 unhighlight_filelist
4465 set highlight_paths {}
4466 after cancel do_file_hl $fh_serial
4467 incr fh_serial
4468 if {$highlight_files ne {}} {
4469 after 300 do_file_hl $fh_serial
4473 proc gdttype_change {name ix op} {
4474 global gdttype highlight_files findstring findpattern
4476 stopfinding
4477 if {$findstring ne {}} {
4478 if {$gdttype eq [mc "containing:"]} {
4479 if {$highlight_files ne {}} {
4480 set highlight_files {}
4481 hfiles_change
4483 findcom_change
4484 } else {
4485 if {$findpattern ne {}} {
4486 set findpattern {}
4487 findcom_change
4489 set highlight_files $findstring
4490 hfiles_change
4492 drawvisible
4494 # enable/disable findtype/findloc menus too
4497 proc find_change {name ix op} {
4498 global gdttype findstring highlight_files
4500 stopfinding
4501 if {$gdttype eq [mc "containing:"]} {
4502 findcom_change
4503 } else {
4504 if {$highlight_files ne $findstring} {
4505 set highlight_files $findstring
4506 hfiles_change
4509 drawvisible
4512 proc findcom_change args {
4513 global nhighlights boldnameids
4514 global findpattern findtype findstring gdttype
4516 stopfinding
4517 # delete previous highlights, if any
4518 foreach id $boldnameids {
4519 bolden_name $id mainfont
4521 set boldnameids {}
4522 catch {unset nhighlights}
4523 unbolden
4524 unmarkmatches
4525 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4526 set findpattern {}
4527 } elseif {$findtype eq [mc "Regexp"]} {
4528 set findpattern $findstring
4529 } else {
4530 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4531 $findstring]
4532 set findpattern "*$e*"
4536 proc makepatterns {l} {
4537 set ret {}
4538 foreach e $l {
4539 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4540 if {[string index $ee end] eq "/"} {
4541 lappend ret "$ee*"
4542 } else {
4543 lappend ret $ee
4544 lappend ret "$ee/*"
4547 return $ret
4550 proc do_file_hl {serial} {
4551 global highlight_files filehighlight highlight_paths gdttype fhl_list
4553 if {$gdttype eq [mc "touching paths:"]} {
4554 if {[catch {set paths [shellsplit $highlight_files]}]} return
4555 set highlight_paths [makepatterns $paths]
4556 highlight_filelist
4557 set gdtargs [concat -- $paths]
4558 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4559 set gdtargs [list "-S$highlight_files"]
4560 } else {
4561 # must be "containing:", i.e. we're searching commit info
4562 return
4564 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4565 set filehighlight [open $cmd r+]
4566 fconfigure $filehighlight -blocking 0
4567 filerun $filehighlight readfhighlight
4568 set fhl_list {}
4569 drawvisible
4570 flushhighlights
4573 proc flushhighlights {} {
4574 global filehighlight fhl_list
4576 if {[info exists filehighlight]} {
4577 lappend fhl_list {}
4578 puts $filehighlight ""
4579 flush $filehighlight
4583 proc askfilehighlight {row id} {
4584 global filehighlight fhighlights fhl_list
4586 lappend fhl_list $id
4587 set fhighlights($id) -1
4588 puts $filehighlight $id
4591 proc readfhighlight {} {
4592 global filehighlight fhighlights curview iddrawn
4593 global fhl_list find_dirn
4595 if {![info exists filehighlight]} {
4596 return 0
4598 set nr 0
4599 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4600 set line [string trim $line]
4601 set i [lsearch -exact $fhl_list $line]
4602 if {$i < 0} continue
4603 for {set j 0} {$j < $i} {incr j} {
4604 set id [lindex $fhl_list $j]
4605 set fhighlights($id) 0
4607 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4608 if {$line eq {}} continue
4609 if {![commitinview $line $curview]} continue
4610 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4611 bolden $line mainfontbold
4613 set fhighlights($line) 1
4615 if {[eof $filehighlight]} {
4616 # strange...
4617 puts "oops, git diff-tree died"
4618 catch {close $filehighlight}
4619 unset filehighlight
4620 return 0
4622 if {[info exists find_dirn]} {
4623 run findmore
4625 return 1
4628 proc doesmatch {f} {
4629 global findtype findpattern
4631 if {$findtype eq [mc "Regexp"]} {
4632 return [regexp $findpattern $f]
4633 } elseif {$findtype eq [mc "IgnCase"]} {
4634 return [string match -nocase $findpattern $f]
4635 } else {
4636 return [string match $findpattern $f]
4640 proc askfindhighlight {row id} {
4641 global nhighlights commitinfo iddrawn
4642 global findloc
4643 global markingmatches
4645 if {![info exists commitinfo($id)]} {
4646 getcommit $id
4648 set info $commitinfo($id)
4649 set isbold 0
4650 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4651 foreach f $info ty $fldtypes {
4652 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4653 [doesmatch $f]} {
4654 if {$ty eq [mc "Author"]} {
4655 set isbold 2
4656 break
4658 set isbold 1
4661 if {$isbold && [info exists iddrawn($id)]} {
4662 if {![ishighlighted $id]} {
4663 bolden $id mainfontbold
4664 if {$isbold > 1} {
4665 bolden_name $id mainfontbold
4668 if {$markingmatches} {
4669 markrowmatches $row $id
4672 set nhighlights($id) $isbold
4675 proc markrowmatches {row id} {
4676 global canv canv2 linehtag linentag commitinfo findloc
4678 set headline [lindex $commitinfo($id) 0]
4679 set author [lindex $commitinfo($id) 1]
4680 $canv delete match$row
4681 $canv2 delete match$row
4682 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4683 set m [findmatches $headline]
4684 if {$m ne {}} {
4685 markmatches $canv $row $headline $linehtag($id) $m \
4686 [$canv itemcget $linehtag($id) -font] $row
4689 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4690 set m [findmatches $author]
4691 if {$m ne {}} {
4692 markmatches $canv2 $row $author $linentag($id) $m \
4693 [$canv2 itemcget $linentag($id) -font] $row
4698 proc vrel_change {name ix op} {
4699 global highlight_related
4701 rhighlight_none
4702 if {$highlight_related ne [mc "None"]} {
4703 run drawvisible
4707 # prepare for testing whether commits are descendents or ancestors of a
4708 proc rhighlight_sel {a} {
4709 global descendent desc_todo ancestor anc_todo
4710 global highlight_related
4712 catch {unset descendent}
4713 set desc_todo [list $a]
4714 catch {unset ancestor}
4715 set anc_todo [list $a]
4716 if {$highlight_related ne [mc "None"]} {
4717 rhighlight_none
4718 run drawvisible
4722 proc rhighlight_none {} {
4723 global rhighlights
4725 catch {unset rhighlights}
4726 unbolden
4729 proc is_descendent {a} {
4730 global curview children descendent desc_todo
4732 set v $curview
4733 set la [rowofcommit $a]
4734 set todo $desc_todo
4735 set leftover {}
4736 set done 0
4737 for {set i 0} {$i < [llength $todo]} {incr i} {
4738 set do [lindex $todo $i]
4739 if {[rowofcommit $do] < $la} {
4740 lappend leftover $do
4741 continue
4743 foreach nk $children($v,$do) {
4744 if {![info exists descendent($nk)]} {
4745 set descendent($nk) 1
4746 lappend todo $nk
4747 if {$nk eq $a} {
4748 set done 1
4752 if {$done} {
4753 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4754 return
4757 set descendent($a) 0
4758 set desc_todo $leftover
4761 proc is_ancestor {a} {
4762 global curview parents ancestor anc_todo
4764 set v $curview
4765 set la [rowofcommit $a]
4766 set todo $anc_todo
4767 set leftover {}
4768 set done 0
4769 for {set i 0} {$i < [llength $todo]} {incr i} {
4770 set do [lindex $todo $i]
4771 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4772 lappend leftover $do
4773 continue
4775 foreach np $parents($v,$do) {
4776 if {![info exists ancestor($np)]} {
4777 set ancestor($np) 1
4778 lappend todo $np
4779 if {$np eq $a} {
4780 set done 1
4784 if {$done} {
4785 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4786 return
4789 set ancestor($a) 0
4790 set anc_todo $leftover
4793 proc askrelhighlight {row id} {
4794 global descendent highlight_related iddrawn rhighlights
4795 global selectedline ancestor
4797 if {$selectedline eq {}} return
4798 set isbold 0
4799 if {$highlight_related eq [mc "Descendant"] ||
4800 $highlight_related eq [mc "Not descendant"]} {
4801 if {![info exists descendent($id)]} {
4802 is_descendent $id
4804 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4805 set isbold 1
4807 } elseif {$highlight_related eq [mc "Ancestor"] ||
4808 $highlight_related eq [mc "Not ancestor"]} {
4809 if {![info exists ancestor($id)]} {
4810 is_ancestor $id
4812 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4813 set isbold 1
4816 if {[info exists iddrawn($id)]} {
4817 if {$isbold && ![ishighlighted $id]} {
4818 bolden $id mainfontbold
4821 set rhighlights($id) $isbold
4824 # Graph layout functions
4826 proc shortids {ids} {
4827 set res {}
4828 foreach id $ids {
4829 if {[llength $id] > 1} {
4830 lappend res [shortids $id]
4831 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4832 lappend res [string range $id 0 7]
4833 } else {
4834 lappend res $id
4837 return $res
4840 proc ntimes {n o} {
4841 set ret {}
4842 set o [list $o]
4843 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4844 if {($n & $mask) != 0} {
4845 set ret [concat $ret $o]
4847 set o [concat $o $o]
4849 return $ret
4852 proc ordertoken {id} {
4853 global ordertok curview varcid varcstart varctok curview parents children
4854 global nullid nullid2
4856 if {[info exists ordertok($id)]} {
4857 return $ordertok($id)
4859 set origid $id
4860 set todo {}
4861 while {1} {
4862 if {[info exists varcid($curview,$id)]} {
4863 set a $varcid($curview,$id)
4864 set p [lindex $varcstart($curview) $a]
4865 } else {
4866 set p [lindex $children($curview,$id) 0]
4868 if {[info exists ordertok($p)]} {
4869 set tok $ordertok($p)
4870 break
4872 set id [first_real_child $curview,$p]
4873 if {$id eq {}} {
4874 # it's a root
4875 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4876 break
4878 if {[llength $parents($curview,$id)] == 1} {
4879 lappend todo [list $p {}]
4880 } else {
4881 set j [lsearch -exact $parents($curview,$id) $p]
4882 if {$j < 0} {
4883 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4885 lappend todo [list $p [strrep $j]]
4888 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4889 set p [lindex $todo $i 0]
4890 append tok [lindex $todo $i 1]
4891 set ordertok($p) $tok
4893 set ordertok($origid) $tok
4894 return $tok
4897 # Work out where id should go in idlist so that order-token
4898 # values increase from left to right
4899 proc idcol {idlist id {i 0}} {
4900 set t [ordertoken $id]
4901 if {$i < 0} {
4902 set i 0
4904 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4905 if {$i > [llength $idlist]} {
4906 set i [llength $idlist]
4908 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4909 incr i
4910 } else {
4911 if {$t > [ordertoken [lindex $idlist $i]]} {
4912 while {[incr i] < [llength $idlist] &&
4913 $t >= [ordertoken [lindex $idlist $i]]} {}
4916 return $i
4919 proc initlayout {} {
4920 global rowidlist rowisopt rowfinal displayorder parentlist
4921 global numcommits canvxmax canv
4922 global nextcolor
4923 global colormap rowtextx
4925 set numcommits 0
4926 set displayorder {}
4927 set parentlist {}
4928 set nextcolor 0
4929 set rowidlist {}
4930 set rowisopt {}
4931 set rowfinal {}
4932 set canvxmax [$canv cget -width]
4933 catch {unset colormap}
4934 catch {unset rowtextx}
4935 setcanvscroll
4938 proc setcanvscroll {} {
4939 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4940 global lastscrollset lastscrollrows
4942 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4943 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4944 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4945 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4946 set lastscrollset [clock clicks -milliseconds]
4947 set lastscrollrows $numcommits
4950 proc visiblerows {} {
4951 global canv numcommits linespc
4953 set ymax [lindex [$canv cget -scrollregion] 3]
4954 if {$ymax eq {} || $ymax == 0} return
4955 set f [$canv yview]
4956 set y0 [expr {int([lindex $f 0] * $ymax)}]
4957 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4958 if {$r0 < 0} {
4959 set r0 0
4961 set y1 [expr {int([lindex $f 1] * $ymax)}]
4962 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4963 if {$r1 >= $numcommits} {
4964 set r1 [expr {$numcommits - 1}]
4966 return [list $r0 $r1]
4969 proc layoutmore {} {
4970 global commitidx viewcomplete curview
4971 global numcommits pending_select curview
4972 global lastscrollset lastscrollrows
4974 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4975 [clock clicks -milliseconds] - $lastscrollset > 500} {
4976 setcanvscroll
4978 if {[info exists pending_select] &&
4979 [commitinview $pending_select $curview]} {
4980 update
4981 selectline [rowofcommit $pending_select] 1
4983 drawvisible
4986 # With path limiting, we mightn't get the actual HEAD commit,
4987 # so ask git rev-list what is the first ancestor of HEAD that
4988 # touches a file in the path limit.
4989 proc get_viewmainhead {view} {
4990 global viewmainheadid vfilelimit viewinstances mainheadid
4992 catch {
4993 set rfd [open [concat | git rev-list -1 $mainheadid \
4994 -- $vfilelimit($view)] r]
4995 set j [reg_instance $rfd]
4996 lappend viewinstances($view) $j
4997 fconfigure $rfd -blocking 0
4998 filerun $rfd [list getviewhead $rfd $j $view]
4999 set viewmainheadid($curview) {}
5003 # git rev-list should give us just 1 line to use as viewmainheadid($view)
5004 proc getviewhead {fd inst view} {
5005 global viewmainheadid commfd curview viewinstances showlocalchanges
5007 set id {}
5008 if {[gets $fd line] < 0} {
5009 if {![eof $fd]} {
5010 return 1
5012 } elseif {[string length $line] == 40 && [string is xdigit $line]} {
5013 set id $line
5015 set viewmainheadid($view) $id
5016 close $fd
5017 unset commfd($inst)
5018 set i [lsearch -exact $viewinstances($view) $inst]
5019 if {$i >= 0} {
5020 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
5022 if {$showlocalchanges && $id ne {} && $view == $curview} {
5023 doshowlocalchanges
5025 return 0
5028 proc doshowlocalchanges {} {
5029 global curview viewmainheadid
5031 if {$viewmainheadid($curview) eq {}} return
5032 if {[commitinview $viewmainheadid($curview) $curview]} {
5033 dodiffindex
5034 } else {
5035 interestedin $viewmainheadid($curview) dodiffindex
5039 proc dohidelocalchanges {} {
5040 global nullid nullid2 lserial curview
5042 if {[commitinview $nullid $curview]} {
5043 removefakerow $nullid
5045 if {[commitinview $nullid2 $curview]} {
5046 removefakerow $nullid2
5048 incr lserial
5051 # spawn off a process to do git diff-index --cached HEAD
5052 proc dodiffindex {} {
5053 global lserial showlocalchanges vfilelimit curview
5054 global isworktree
5056 if {!$showlocalchanges || !$isworktree} return
5057 incr lserial
5058 set cmd "|git diff-index --cached HEAD"
5059 if {$vfilelimit($curview) ne {}} {
5060 set cmd [concat $cmd -- $vfilelimit($curview)]
5062 set fd [open $cmd r]
5063 fconfigure $fd -blocking 0
5064 set i [reg_instance $fd]
5065 filerun $fd [list readdiffindex $fd $lserial $i]
5068 proc readdiffindex {fd serial inst} {
5069 global viewmainheadid nullid nullid2 curview commitinfo commitdata lserial
5070 global vfilelimit
5072 set isdiff 1
5073 if {[gets $fd line] < 0} {
5074 if {![eof $fd]} {
5075 return 1
5077 set isdiff 0
5079 # we only need to see one line and we don't really care what it says...
5080 stop_instance $inst
5082 if {$serial != $lserial} {
5083 return 0
5086 # now see if there are any local changes not checked in to the index
5087 set cmd "|git diff-files"
5088 if {$vfilelimit($curview) ne {}} {
5089 set cmd [concat $cmd -- $vfilelimit($curview)]
5091 set fd [open $cmd r]
5092 fconfigure $fd -blocking 0
5093 set i [reg_instance $fd]
5094 filerun $fd [list readdifffiles $fd $serial $i]
5096 if {$isdiff && ![commitinview $nullid2 $curview]} {
5097 # add the line for the changes in the index to the graph
5098 set hl [mc "Local changes checked in to index but not committed"]
5099 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
5100 set commitdata($nullid2) "\n $hl\n"
5101 if {[commitinview $nullid $curview]} {
5102 removefakerow $nullid
5104 insertfakerow $nullid2 $viewmainheadid($curview)
5105 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
5106 if {[commitinview $nullid $curview]} {
5107 removefakerow $nullid
5109 removefakerow $nullid2
5111 return 0
5114 proc readdifffiles {fd serial inst} {
5115 global viewmainheadid nullid nullid2 curview
5116 global commitinfo commitdata lserial
5118 set isdiff 1
5119 if {[gets $fd line] < 0} {
5120 if {![eof $fd]} {
5121 return 1
5123 set isdiff 0
5125 # we only need to see one line and we don't really care what it says...
5126 stop_instance $inst
5128 if {$serial != $lserial} {
5129 return 0
5132 if {$isdiff && ![commitinview $nullid $curview]} {
5133 # add the line for the local diff to the graph
5134 set hl [mc "Local uncommitted changes, not checked in to index"]
5135 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
5136 set commitdata($nullid) "\n $hl\n"
5137 if {[commitinview $nullid2 $curview]} {
5138 set p $nullid2
5139 } else {
5140 set p $viewmainheadid($curview)
5142 insertfakerow $nullid $p
5143 } elseif {!$isdiff && [commitinview $nullid $curview]} {
5144 removefakerow $nullid
5146 return 0
5149 proc nextuse {id row} {
5150 global curview children
5152 if {[info exists children($curview,$id)]} {
5153 foreach kid $children($curview,$id) {
5154 if {![commitinview $kid $curview]} {
5155 return -1
5157 if {[rowofcommit $kid] > $row} {
5158 return [rowofcommit $kid]
5162 if {[commitinview $id $curview]} {
5163 return [rowofcommit $id]
5165 return -1
5168 proc prevuse {id row} {
5169 global curview children
5171 set ret -1
5172 if {[info exists children($curview,$id)]} {
5173 foreach kid $children($curview,$id) {
5174 if {![commitinview $kid $curview]} break
5175 if {[rowofcommit $kid] < $row} {
5176 set ret [rowofcommit $kid]
5180 return $ret
5183 proc make_idlist {row} {
5184 global displayorder parentlist uparrowlen downarrowlen mingaplen
5185 global commitidx curview children
5187 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
5188 if {$r < 0} {
5189 set r 0
5191 set ra [expr {$row - $downarrowlen}]
5192 if {$ra < 0} {
5193 set ra 0
5195 set rb [expr {$row + $uparrowlen}]
5196 if {$rb > $commitidx($curview)} {
5197 set rb $commitidx($curview)
5199 make_disporder $r [expr {$rb + 1}]
5200 set ids {}
5201 for {} {$r < $ra} {incr r} {
5202 set nextid [lindex $displayorder [expr {$r + 1}]]
5203 foreach p [lindex $parentlist $r] {
5204 if {$p eq $nextid} continue
5205 set rn [nextuse $p $r]
5206 if {$rn >= $row &&
5207 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
5208 lappend ids [list [ordertoken $p] $p]
5212 for {} {$r < $row} {incr r} {
5213 set nextid [lindex $displayorder [expr {$r + 1}]]
5214 foreach p [lindex $parentlist $r] {
5215 if {$p eq $nextid} continue
5216 set rn [nextuse $p $r]
5217 if {$rn < 0 || $rn >= $row} {
5218 lappend ids [list [ordertoken $p] $p]
5222 set id [lindex $displayorder $row]
5223 lappend ids [list [ordertoken $id] $id]
5224 while {$r < $rb} {
5225 foreach p [lindex $parentlist $r] {
5226 set firstkid [lindex $children($curview,$p) 0]
5227 if {[rowofcommit $firstkid] < $row} {
5228 lappend ids [list [ordertoken $p] $p]
5231 incr r
5232 set id [lindex $displayorder $r]
5233 if {$id ne {}} {
5234 set firstkid [lindex $children($curview,$id) 0]
5235 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
5236 lappend ids [list [ordertoken $id] $id]
5240 set idlist {}
5241 foreach idx [lsort -unique $ids] {
5242 lappend idlist [lindex $idx 1]
5244 return $idlist
5247 proc rowsequal {a b} {
5248 while {[set i [lsearch -exact $a {}]] >= 0} {
5249 set a [lreplace $a $i $i]
5251 while {[set i [lsearch -exact $b {}]] >= 0} {
5252 set b [lreplace $b $i $i]
5254 return [expr {$a eq $b}]
5257 proc makeupline {id row rend col} {
5258 global rowidlist uparrowlen downarrowlen mingaplen
5260 for {set r $rend} {1} {set r $rstart} {
5261 set rstart [prevuse $id $r]
5262 if {$rstart < 0} return
5263 if {$rstart < $row} break
5265 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
5266 set rstart [expr {$rend - $uparrowlen - 1}]
5268 for {set r $rstart} {[incr r] <= $row} {} {
5269 set idlist [lindex $rowidlist $r]
5270 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
5271 set col [idcol $idlist $id $col]
5272 lset rowidlist $r [linsert $idlist $col $id]
5273 changedrow $r
5278 proc layoutrows {row endrow} {
5279 global rowidlist rowisopt rowfinal displayorder
5280 global uparrowlen downarrowlen maxwidth mingaplen
5281 global children parentlist
5282 global commitidx viewcomplete curview
5284 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
5285 set idlist {}
5286 if {$row > 0} {
5287 set rm1 [expr {$row - 1}]
5288 foreach id [lindex $rowidlist $rm1] {
5289 if {$id ne {}} {
5290 lappend idlist $id
5293 set final [lindex $rowfinal $rm1]
5295 for {} {$row < $endrow} {incr row} {
5296 set rm1 [expr {$row - 1}]
5297 if {$rm1 < 0 || $idlist eq {}} {
5298 set idlist [make_idlist $row]
5299 set final 1
5300 } else {
5301 set id [lindex $displayorder $rm1]
5302 set col [lsearch -exact $idlist $id]
5303 set idlist [lreplace $idlist $col $col]
5304 foreach p [lindex $parentlist $rm1] {
5305 if {[lsearch -exact $idlist $p] < 0} {
5306 set col [idcol $idlist $p $col]
5307 set idlist [linsert $idlist $col $p]
5308 # if not the first child, we have to insert a line going up
5309 if {$id ne [lindex $children($curview,$p) 0]} {
5310 makeupline $p $rm1 $row $col
5314 set id [lindex $displayorder $row]
5315 if {$row > $downarrowlen} {
5316 set termrow [expr {$row - $downarrowlen - 1}]
5317 foreach p [lindex $parentlist $termrow] {
5318 set i [lsearch -exact $idlist $p]
5319 if {$i < 0} continue
5320 set nr [nextuse $p $termrow]
5321 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
5322 set idlist [lreplace $idlist $i $i]
5326 set col [lsearch -exact $idlist $id]
5327 if {$col < 0} {
5328 set col [idcol $idlist $id]
5329 set idlist [linsert $idlist $col $id]
5330 if {$children($curview,$id) ne {}} {
5331 makeupline $id $rm1 $row $col
5334 set r [expr {$row + $uparrowlen - 1}]
5335 if {$r < $commitidx($curview)} {
5336 set x $col
5337 foreach p [lindex $parentlist $r] {
5338 if {[lsearch -exact $idlist $p] >= 0} continue
5339 set fk [lindex $children($curview,$p) 0]
5340 if {[rowofcommit $fk] < $row} {
5341 set x [idcol $idlist $p $x]
5342 set idlist [linsert $idlist $x $p]
5345 if {[incr r] < $commitidx($curview)} {
5346 set p [lindex $displayorder $r]
5347 if {[lsearch -exact $idlist $p] < 0} {
5348 set fk [lindex $children($curview,$p) 0]
5349 if {$fk ne {} && [rowofcommit $fk] < $row} {
5350 set x [idcol $idlist $p $x]
5351 set idlist [linsert $idlist $x $p]
5357 if {$final && !$viewcomplete($curview) &&
5358 $row + $uparrowlen + $mingaplen + $downarrowlen
5359 >= $commitidx($curview)} {
5360 set final 0
5362 set l [llength $rowidlist]
5363 if {$row == $l} {
5364 lappend rowidlist $idlist
5365 lappend rowisopt 0
5366 lappend rowfinal $final
5367 } elseif {$row < $l} {
5368 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
5369 lset rowidlist $row $idlist
5370 changedrow $row
5372 lset rowfinal $row $final
5373 } else {
5374 set pad [ntimes [expr {$row - $l}] {}]
5375 set rowidlist [concat $rowidlist $pad]
5376 lappend rowidlist $idlist
5377 set rowfinal [concat $rowfinal $pad]
5378 lappend rowfinal $final
5379 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
5382 return $row
5385 proc changedrow {row} {
5386 global displayorder iddrawn rowisopt need_redisplay
5388 set l [llength $rowisopt]
5389 if {$row < $l} {
5390 lset rowisopt $row 0
5391 if {$row + 1 < $l} {
5392 lset rowisopt [expr {$row + 1}] 0
5393 if {$row + 2 < $l} {
5394 lset rowisopt [expr {$row + 2}] 0
5398 set id [lindex $displayorder $row]
5399 if {[info exists iddrawn($id)]} {
5400 set need_redisplay 1
5404 proc insert_pad {row col npad} {
5405 global rowidlist
5407 set pad [ntimes $npad {}]
5408 set idlist [lindex $rowidlist $row]
5409 set bef [lrange $idlist 0 [expr {$col - 1}]]
5410 set aft [lrange $idlist $col end]
5411 set i [lsearch -exact $aft {}]
5412 if {$i > 0} {
5413 set aft [lreplace $aft $i $i]
5415 lset rowidlist $row [concat $bef $pad $aft]
5416 changedrow $row
5419 proc optimize_rows {row col endrow} {
5420 global rowidlist rowisopt displayorder curview children
5422 if {$row < 1} {
5423 set row 1
5425 for {} {$row < $endrow} {incr row; set col 0} {
5426 if {[lindex $rowisopt $row]} continue
5427 set haspad 0
5428 set y0 [expr {$row - 1}]
5429 set ym [expr {$row - 2}]
5430 set idlist [lindex $rowidlist $row]
5431 set previdlist [lindex $rowidlist $y0]
5432 if {$idlist eq {} || $previdlist eq {}} continue
5433 if {$ym >= 0} {
5434 set pprevidlist [lindex $rowidlist $ym]
5435 if {$pprevidlist eq {}} continue
5436 } else {
5437 set pprevidlist {}
5439 set x0 -1
5440 set xm -1
5441 for {} {$col < [llength $idlist]} {incr col} {
5442 set id [lindex $idlist $col]
5443 if {[lindex $previdlist $col] eq $id} continue
5444 if {$id eq {}} {
5445 set haspad 1
5446 continue
5448 set x0 [lsearch -exact $previdlist $id]
5449 if {$x0 < 0} continue
5450 set z [expr {$x0 - $col}]
5451 set isarrow 0
5452 set z0 {}
5453 if {$ym >= 0} {
5454 set xm [lsearch -exact $pprevidlist $id]
5455 if {$xm >= 0} {
5456 set z0 [expr {$xm - $x0}]
5459 if {$z0 eq {}} {
5460 # if row y0 is the first child of $id then it's not an arrow
5461 if {[lindex $children($curview,$id) 0] ne
5462 [lindex $displayorder $y0]} {
5463 set isarrow 1
5466 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5467 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5468 set isarrow 1
5470 # Looking at lines from this row to the previous row,
5471 # make them go straight up if they end in an arrow on
5472 # the previous row; otherwise make them go straight up
5473 # or at 45 degrees.
5474 if {$z < -1 || ($z < 0 && $isarrow)} {
5475 # Line currently goes left too much;
5476 # insert pads in the previous row, then optimize it
5477 set npad [expr {-1 - $z + $isarrow}]
5478 insert_pad $y0 $x0 $npad
5479 if {$y0 > 0} {
5480 optimize_rows $y0 $x0 $row
5482 set previdlist [lindex $rowidlist $y0]
5483 set x0 [lsearch -exact $previdlist $id]
5484 set z [expr {$x0 - $col}]
5485 if {$z0 ne {}} {
5486 set pprevidlist [lindex $rowidlist $ym]
5487 set xm [lsearch -exact $pprevidlist $id]
5488 set z0 [expr {$xm - $x0}]
5490 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5491 # Line currently goes right too much;
5492 # insert pads in this line
5493 set npad [expr {$z - 1 + $isarrow}]
5494 insert_pad $row $col $npad
5495 set idlist [lindex $rowidlist $row]
5496 incr col $npad
5497 set z [expr {$x0 - $col}]
5498 set haspad 1
5500 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5501 # this line links to its first child on row $row-2
5502 set id [lindex $displayorder $ym]
5503 set xc [lsearch -exact $pprevidlist $id]
5504 if {$xc >= 0} {
5505 set z0 [expr {$xc - $x0}]
5508 # avoid lines jigging left then immediately right
5509 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5510 insert_pad $y0 $x0 1
5511 incr x0
5512 optimize_rows $y0 $x0 $row
5513 set previdlist [lindex $rowidlist $y0]
5516 if {!$haspad} {
5517 # Find the first column that doesn't have a line going right
5518 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5519 set id [lindex $idlist $col]
5520 if {$id eq {}} break
5521 set x0 [lsearch -exact $previdlist $id]
5522 if {$x0 < 0} {
5523 # check if this is the link to the first child
5524 set kid [lindex $displayorder $y0]
5525 if {[lindex $children($curview,$id) 0] eq $kid} {
5526 # it is, work out offset to child
5527 set x0 [lsearch -exact $previdlist $kid]
5530 if {$x0 <= $col} break
5532 # Insert a pad at that column as long as it has a line and
5533 # isn't the last column
5534 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5535 set idlist [linsert $idlist $col {}]
5536 lset rowidlist $row $idlist
5537 changedrow $row
5543 proc xc {row col} {
5544 global canvx0 linespc
5545 return [expr {$canvx0 + $col * $linespc}]
5548 proc yc {row} {
5549 global canvy0 linespc
5550 return [expr {$canvy0 + $row * $linespc}]
5553 proc linewidth {id} {
5554 global thickerline lthickness
5556 set wid $lthickness
5557 if {[info exists thickerline] && $id eq $thickerline} {
5558 set wid [expr {2 * $lthickness}]
5560 return $wid
5563 proc rowranges {id} {
5564 global curview children uparrowlen downarrowlen
5565 global rowidlist
5567 set kids $children($curview,$id)
5568 if {$kids eq {}} {
5569 return {}
5571 set ret {}
5572 lappend kids $id
5573 foreach child $kids {
5574 if {![commitinview $child $curview]} break
5575 set row [rowofcommit $child]
5576 if {![info exists prev]} {
5577 lappend ret [expr {$row + 1}]
5578 } else {
5579 if {$row <= $prevrow} {
5580 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5582 # see if the line extends the whole way from prevrow to row
5583 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5584 [lsearch -exact [lindex $rowidlist \
5585 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5586 # it doesn't, see where it ends
5587 set r [expr {$prevrow + $downarrowlen}]
5588 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5589 while {[incr r -1] > $prevrow &&
5590 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5591 } else {
5592 while {[incr r] <= $row &&
5593 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5594 incr r -1
5596 lappend ret $r
5597 # see where it starts up again
5598 set r [expr {$row - $uparrowlen}]
5599 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5600 while {[incr r] < $row &&
5601 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5602 } else {
5603 while {[incr r -1] >= $prevrow &&
5604 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5605 incr r
5607 lappend ret $r
5610 if {$child eq $id} {
5611 lappend ret $row
5613 set prev $child
5614 set prevrow $row
5616 return $ret
5619 proc drawlineseg {id row endrow arrowlow} {
5620 global rowidlist displayorder iddrawn linesegs
5621 global canv colormap linespc curview maxlinelen parentlist
5623 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5624 set le [expr {$row + 1}]
5625 set arrowhigh 1
5626 while {1} {
5627 set c [lsearch -exact [lindex $rowidlist $le] $id]
5628 if {$c < 0} {
5629 incr le -1
5630 break
5632 lappend cols $c
5633 set x [lindex $displayorder $le]
5634 if {$x eq $id} {
5635 set arrowhigh 0
5636 break
5638 if {[info exists iddrawn($x)] || $le == $endrow} {
5639 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5640 if {$c >= 0} {
5641 lappend cols $c
5642 set arrowhigh 0
5644 break
5646 incr le
5648 if {$le <= $row} {
5649 return $row
5652 set lines {}
5653 set i 0
5654 set joinhigh 0
5655 if {[info exists linesegs($id)]} {
5656 set lines $linesegs($id)
5657 foreach li $lines {
5658 set r0 [lindex $li 0]
5659 if {$r0 > $row} {
5660 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5661 set joinhigh 1
5663 break
5665 incr i
5668 set joinlow 0
5669 if {$i > 0} {
5670 set li [lindex $lines [expr {$i-1}]]
5671 set r1 [lindex $li 1]
5672 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5673 set joinlow 1
5677 set x [lindex $cols [expr {$le - $row}]]
5678 set xp [lindex $cols [expr {$le - 1 - $row}]]
5679 set dir [expr {$xp - $x}]
5680 if {$joinhigh} {
5681 set ith [lindex $lines $i 2]
5682 set coords [$canv coords $ith]
5683 set ah [$canv itemcget $ith -arrow]
5684 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5685 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5686 if {$x2 ne {} && $x - $x2 == $dir} {
5687 set coords [lrange $coords 0 end-2]
5689 } else {
5690 set coords [list [xc $le $x] [yc $le]]
5692 if {$joinlow} {
5693 set itl [lindex $lines [expr {$i-1}] 2]
5694 set al [$canv itemcget $itl -arrow]
5695 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5696 } elseif {$arrowlow} {
5697 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5698 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5699 set arrowlow 0
5702 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5703 for {set y $le} {[incr y -1] > $row} {} {
5704 set x $xp
5705 set xp [lindex $cols [expr {$y - 1 - $row}]]
5706 set ndir [expr {$xp - $x}]
5707 if {$dir != $ndir || $xp < 0} {
5708 lappend coords [xc $y $x] [yc $y]
5710 set dir $ndir
5712 if {!$joinlow} {
5713 if {$xp < 0} {
5714 # join parent line to first child
5715 set ch [lindex $displayorder $row]
5716 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5717 if {$xc < 0} {
5718 puts "oops: drawlineseg: child $ch not on row $row"
5719 } elseif {$xc != $x} {
5720 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5721 set d [expr {int(0.5 * $linespc)}]
5722 set x1 [xc $row $x]
5723 if {$xc < $x} {
5724 set x2 [expr {$x1 - $d}]
5725 } else {
5726 set x2 [expr {$x1 + $d}]
5728 set y2 [yc $row]
5729 set y1 [expr {$y2 + $d}]
5730 lappend coords $x1 $y1 $x2 $y2
5731 } elseif {$xc < $x - 1} {
5732 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5733 } elseif {$xc > $x + 1} {
5734 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5736 set x $xc
5738 lappend coords [xc $row $x] [yc $row]
5739 } else {
5740 set xn [xc $row $xp]
5741 set yn [yc $row]
5742 lappend coords $xn $yn
5744 if {!$joinhigh} {
5745 assigncolor $id
5746 set t [$canv create line $coords -width [linewidth $id] \
5747 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5748 $canv lower $t
5749 bindline $t $id
5750 set lines [linsert $lines $i [list $row $le $t]]
5751 } else {
5752 $canv coords $ith $coords
5753 if {$arrow ne $ah} {
5754 $canv itemconf $ith -arrow $arrow
5756 lset lines $i 0 $row
5758 } else {
5759 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5760 set ndir [expr {$xo - $xp}]
5761 set clow [$canv coords $itl]
5762 if {$dir == $ndir} {
5763 set clow [lrange $clow 2 end]
5765 set coords [concat $coords $clow]
5766 if {!$joinhigh} {
5767 lset lines [expr {$i-1}] 1 $le
5768 } else {
5769 # coalesce two pieces
5770 $canv delete $ith
5771 set b [lindex $lines [expr {$i-1}] 0]
5772 set e [lindex $lines $i 1]
5773 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5775 $canv coords $itl $coords
5776 if {$arrow ne $al} {
5777 $canv itemconf $itl -arrow $arrow
5781 set linesegs($id) $lines
5782 return $le
5785 proc drawparentlinks {id row} {
5786 global rowidlist canv colormap curview parentlist
5787 global idpos linespc
5789 set rowids [lindex $rowidlist $row]
5790 set col [lsearch -exact $rowids $id]
5791 if {$col < 0} return
5792 set olds [lindex $parentlist $row]
5793 set row2 [expr {$row + 1}]
5794 set x [xc $row $col]
5795 set y [yc $row]
5796 set y2 [yc $row2]
5797 set d [expr {int(0.5 * $linespc)}]
5798 set ymid [expr {$y + $d}]
5799 set ids [lindex $rowidlist $row2]
5800 # rmx = right-most X coord used
5801 set rmx 0
5802 foreach p $olds {
5803 set i [lsearch -exact $ids $p]
5804 if {$i < 0} {
5805 puts "oops, parent $p of $id not in list"
5806 continue
5808 set x2 [xc $row2 $i]
5809 if {$x2 > $rmx} {
5810 set rmx $x2
5812 set j [lsearch -exact $rowids $p]
5813 if {$j < 0} {
5814 # drawlineseg will do this one for us
5815 continue
5817 assigncolor $p
5818 # should handle duplicated parents here...
5819 set coords [list $x $y]
5820 if {$i != $col} {
5821 # if attaching to a vertical segment, draw a smaller
5822 # slant for visual distinctness
5823 if {$i == $j} {
5824 if {$i < $col} {
5825 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5826 } else {
5827 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5829 } elseif {$i < $col && $i < $j} {
5830 # segment slants towards us already
5831 lappend coords [xc $row $j] $y
5832 } else {
5833 if {$i < $col - 1} {
5834 lappend coords [expr {$x2 + $linespc}] $y
5835 } elseif {$i > $col + 1} {
5836 lappend coords [expr {$x2 - $linespc}] $y
5838 lappend coords $x2 $y2
5840 } else {
5841 lappend coords $x2 $y2
5843 set t [$canv create line $coords -width [linewidth $p] \
5844 -fill $colormap($p) -tags lines.$p]
5845 $canv lower $t
5846 bindline $t $p
5848 if {$rmx > [lindex $idpos($id) 1]} {
5849 lset idpos($id) 1 $rmx
5850 redrawtags $id
5854 proc drawlines {id} {
5855 global canv
5857 $canv itemconf lines.$id -width [linewidth $id]
5860 proc drawcmittext {id row col} {
5861 global linespc canv canv2 canv3 fgcolor curview
5862 global cmitlisted commitinfo rowidlist parentlist
5863 global rowtextx idpos idtags idheads idotherrefs
5864 global linehtag linentag linedtag selectedline
5865 global canvxmax boldids boldnameids fgcolor markedid
5866 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5868 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5869 set listed $cmitlisted($curview,$id)
5870 if {$id eq $nullid} {
5871 set ofill red
5872 } elseif {$id eq $nullid2} {
5873 set ofill green
5874 } elseif {$id eq $mainheadid} {
5875 set ofill yellow
5876 } else {
5877 set ofill [lindex $circlecolors $listed]
5879 set x [xc $row $col]
5880 set y [yc $row]
5881 set orad [expr {$linespc / 3}]
5882 if {$listed <= 2} {
5883 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5884 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5885 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5886 } elseif {$listed == 3} {
5887 # triangle pointing left for left-side commits
5888 set t [$canv create polygon \
5889 [expr {$x - $orad}] $y \
5890 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5891 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5892 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5893 } else {
5894 # triangle pointing right for right-side commits
5895 set t [$canv create polygon \
5896 [expr {$x + $orad - 1}] $y \
5897 [expr {$x - $orad}] [expr {$y - $orad}] \
5898 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5899 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5901 set circleitem($row) $t
5902 $canv raise $t
5903 $canv bind $t <1> {selcanvline {} %x %y}
5904 set rmx [llength [lindex $rowidlist $row]]
5905 set olds [lindex $parentlist $row]
5906 if {$olds ne {}} {
5907 set nextids [lindex $rowidlist [expr {$row + 1}]]
5908 foreach p $olds {
5909 set i [lsearch -exact $nextids $p]
5910 if {$i > $rmx} {
5911 set rmx $i
5915 set xt [xc $row $rmx]
5916 set rowtextx($row) $xt
5917 set idpos($id) [list $x $xt $y]
5918 if {[info exists idtags($id)] || [info exists idheads($id)]
5919 || [info exists idotherrefs($id)]} {
5920 set xt [drawtags $id $x $xt $y]
5922 set headline [lindex $commitinfo($id) 0]
5923 set name [lindex $commitinfo($id) 1]
5924 set date [lindex $commitinfo($id) 2]
5925 set date [formatdate $date]
5926 set font mainfont
5927 set nfont mainfont
5928 set isbold [ishighlighted $id]
5929 if {$isbold > 0} {
5930 lappend boldids $id
5931 set font mainfontbold
5932 if {$isbold > 1} {
5933 lappend boldnameids $id
5934 set nfont mainfontbold
5937 set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
5938 -text $headline -font $font -tags text]
5939 $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
5940 set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5941 -text $name -font $nfont -tags text]
5942 set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5943 -text $date -font mainfont -tags text]
5944 if {$selectedline == $row} {
5945 make_secsel $id
5947 if {[info exists markedid] && $markedid eq $id} {
5948 make_idmark $id
5950 set xr [expr {$xt + [font measure $font $headline]}]
5951 if {$xr > $canvxmax} {
5952 set canvxmax $xr
5953 setcanvscroll
5957 proc drawcmitrow {row} {
5958 global displayorder rowidlist nrows_drawn
5959 global iddrawn markingmatches
5960 global commitinfo numcommits
5961 global filehighlight fhighlights findpattern nhighlights
5962 global hlview vhighlights
5963 global highlight_related rhighlights
5965 if {$row >= $numcommits} return
5967 set id [lindex $displayorder $row]
5968 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5969 askvhighlight $row $id
5971 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5972 askfilehighlight $row $id
5974 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5975 askfindhighlight $row $id
5977 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5978 askrelhighlight $row $id
5980 if {![info exists iddrawn($id)]} {
5981 set col [lsearch -exact [lindex $rowidlist $row] $id]
5982 if {$col < 0} {
5983 puts "oops, row $row id $id not in list"
5984 return
5986 if {![info exists commitinfo($id)]} {
5987 getcommit $id
5989 assigncolor $id
5990 drawcmittext $id $row $col
5991 set iddrawn($id) 1
5992 incr nrows_drawn
5994 if {$markingmatches} {
5995 markrowmatches $row $id
5999 proc drawcommits {row {endrow {}}} {
6000 global numcommits iddrawn displayorder curview need_redisplay
6001 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
6003 if {$row < 0} {
6004 set row 0
6006 if {$endrow eq {}} {
6007 set endrow $row
6009 if {$endrow >= $numcommits} {
6010 set endrow [expr {$numcommits - 1}]
6013 set rl1 [expr {$row - $downarrowlen - 3}]
6014 if {$rl1 < 0} {
6015 set rl1 0
6017 set ro1 [expr {$row - 3}]
6018 if {$ro1 < 0} {
6019 set ro1 0
6021 set r2 [expr {$endrow + $uparrowlen + 3}]
6022 if {$r2 > $numcommits} {
6023 set r2 $numcommits
6025 for {set r $rl1} {$r < $r2} {incr r} {
6026 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
6027 if {$rl1 < $r} {
6028 layoutrows $rl1 $r
6030 set rl1 [expr {$r + 1}]
6033 if {$rl1 < $r} {
6034 layoutrows $rl1 $r
6036 optimize_rows $ro1 0 $r2
6037 if {$need_redisplay || $nrows_drawn > 2000} {
6038 clear_display
6041 # make the lines join to already-drawn rows either side
6042 set r [expr {$row - 1}]
6043 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
6044 set r $row
6046 set er [expr {$endrow + 1}]
6047 if {$er >= $numcommits ||
6048 ![info exists iddrawn([lindex $displayorder $er])]} {
6049 set er $endrow
6051 for {} {$r <= $er} {incr r} {
6052 set id [lindex $displayorder $r]
6053 set wasdrawn [info exists iddrawn($id)]
6054 drawcmitrow $r
6055 if {$r == $er} break
6056 set nextid [lindex $displayorder [expr {$r + 1}]]
6057 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
6058 drawparentlinks $id $r
6060 set rowids [lindex $rowidlist $r]
6061 foreach lid $rowids {
6062 if {$lid eq {}} continue
6063 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
6064 if {$lid eq $id} {
6065 # see if this is the first child of any of its parents
6066 foreach p [lindex $parentlist $r] {
6067 if {[lsearch -exact $rowids $p] < 0} {
6068 # make this line extend up to the child
6069 set lineend($p) [drawlineseg $p $r $er 0]
6072 } else {
6073 set lineend($lid) [drawlineseg $lid $r $er 1]
6079 proc undolayout {row} {
6080 global uparrowlen mingaplen downarrowlen
6081 global rowidlist rowisopt rowfinal need_redisplay
6083 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
6084 if {$r < 0} {
6085 set r 0
6087 if {[llength $rowidlist] > $r} {
6088 incr r -1
6089 set rowidlist [lrange $rowidlist 0 $r]
6090 set rowfinal [lrange $rowfinal 0 $r]
6091 set rowisopt [lrange $rowisopt 0 $r]
6092 set need_redisplay 1
6093 run drawvisible
6097 proc drawvisible {} {
6098 global canv linespc curview vrowmod selectedline targetrow targetid
6099 global need_redisplay cscroll numcommits
6101 set fs [$canv yview]
6102 set ymax [lindex [$canv cget -scrollregion] 3]
6103 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
6104 set f0 [lindex $fs 0]
6105 set f1 [lindex $fs 1]
6106 set y0 [expr {int($f0 * $ymax)}]
6107 set y1 [expr {int($f1 * $ymax)}]
6109 if {[info exists targetid]} {
6110 if {[commitinview $targetid $curview]} {
6111 set r [rowofcommit $targetid]
6112 if {$r != $targetrow} {
6113 # Fix up the scrollregion and change the scrolling position
6114 # now that our target row has moved.
6115 set diff [expr {($r - $targetrow) * $linespc}]
6116 set targetrow $r
6117 setcanvscroll
6118 set ymax [lindex [$canv cget -scrollregion] 3]
6119 incr y0 $diff
6120 incr y1 $diff
6121 set f0 [expr {$y0 / $ymax}]
6122 set f1 [expr {$y1 / $ymax}]
6123 allcanvs yview moveto $f0
6124 $cscroll set $f0 $f1
6125 set need_redisplay 1
6127 } else {
6128 unset targetid
6132 set row [expr {int(($y0 - 3) / $linespc) - 1}]
6133 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
6134 if {$endrow >= $vrowmod($curview)} {
6135 update_arcrows $curview
6137 if {$selectedline ne {} &&
6138 $row <= $selectedline && $selectedline <= $endrow} {
6139 set targetrow $selectedline
6140 } elseif {[info exists targetid]} {
6141 set targetrow [expr {int(($row + $endrow) / 2)}]
6143 if {[info exists targetrow]} {
6144 if {$targetrow >= $numcommits} {
6145 set targetrow [expr {$numcommits - 1}]
6147 set targetid [commitonrow $targetrow]
6149 drawcommits $row $endrow
6152 proc clear_display {} {
6153 global iddrawn linesegs need_redisplay nrows_drawn
6154 global vhighlights fhighlights nhighlights rhighlights
6155 global linehtag linentag linedtag boldids boldnameids
6157 allcanvs delete all
6158 catch {unset iddrawn}
6159 catch {unset linesegs}
6160 catch {unset linehtag}
6161 catch {unset linentag}
6162 catch {unset linedtag}
6163 set boldids {}
6164 set boldnameids {}
6165 catch {unset vhighlights}
6166 catch {unset fhighlights}
6167 catch {unset nhighlights}
6168 catch {unset rhighlights}
6169 set need_redisplay 0
6170 set nrows_drawn 0
6173 proc findcrossings {id} {
6174 global rowidlist parentlist numcommits displayorder
6176 set cross {}
6177 set ccross {}
6178 foreach {s e} [rowranges $id] {
6179 if {$e >= $numcommits} {
6180 set e [expr {$numcommits - 1}]
6182 if {$e <= $s} continue
6183 for {set row $e} {[incr row -1] >= $s} {} {
6184 set x [lsearch -exact [lindex $rowidlist $row] $id]
6185 if {$x < 0} break
6186 set olds [lindex $parentlist $row]
6187 set kid [lindex $displayorder $row]
6188 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
6189 if {$kidx < 0} continue
6190 set nextrow [lindex $rowidlist [expr {$row + 1}]]
6191 foreach p $olds {
6192 set px [lsearch -exact $nextrow $p]
6193 if {$px < 0} continue
6194 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
6195 if {[lsearch -exact $ccross $p] >= 0} continue
6196 if {$x == $px + ($kidx < $px? -1: 1)} {
6197 lappend ccross $p
6198 } elseif {[lsearch -exact $cross $p] < 0} {
6199 lappend cross $p
6205 return [concat $ccross {{}} $cross]
6208 proc assigncolor {id} {
6209 global colormap colors nextcolor
6210 global parents children children curview
6212 if {[info exists colormap($id)]} return
6213 set ncolors [llength $colors]
6214 if {[info exists children($curview,$id)]} {
6215 set kids $children($curview,$id)
6216 } else {
6217 set kids {}
6219 if {[llength $kids] == 1} {
6220 set child [lindex $kids 0]
6221 if {[info exists colormap($child)]
6222 && [llength $parents($curview,$child)] == 1} {
6223 set colormap($id) $colormap($child)
6224 return
6227 set badcolors {}
6228 set origbad {}
6229 foreach x [findcrossings $id] {
6230 if {$x eq {}} {
6231 # delimiter between corner crossings and other crossings
6232 if {[llength $badcolors] >= $ncolors - 1} break
6233 set origbad $badcolors
6235 if {[info exists colormap($x)]
6236 && [lsearch -exact $badcolors $colormap($x)] < 0} {
6237 lappend badcolors $colormap($x)
6240 if {[llength $badcolors] >= $ncolors} {
6241 set badcolors $origbad
6243 set origbad $badcolors
6244 if {[llength $badcolors] < $ncolors - 1} {
6245 foreach child $kids {
6246 if {[info exists colormap($child)]
6247 && [lsearch -exact $badcolors $colormap($child)] < 0} {
6248 lappend badcolors $colormap($child)
6250 foreach p $parents($curview,$child) {
6251 if {[info exists colormap($p)]
6252 && [lsearch -exact $badcolors $colormap($p)] < 0} {
6253 lappend badcolors $colormap($p)
6257 if {[llength $badcolors] >= $ncolors} {
6258 set badcolors $origbad
6261 for {set i 0} {$i <= $ncolors} {incr i} {
6262 set c [lindex $colors $nextcolor]
6263 if {[incr nextcolor] >= $ncolors} {
6264 set nextcolor 0
6266 if {[lsearch -exact $badcolors $c]} break
6268 set colormap($id) $c
6271 proc bindline {t id} {
6272 global canv
6274 $canv bind $t <Enter> "lineenter %x %y $id"
6275 $canv bind $t <Motion> "linemotion %x %y $id"
6276 $canv bind $t <Leave> "lineleave $id"
6277 $canv bind $t <Button-1> "lineclick %x %y $id 1"
6280 proc drawtags {id x xt y1} {
6281 global idtags idheads idotherrefs mainhead
6282 global linespc lthickness
6283 global canv rowtextx curview fgcolor bgcolor ctxbut
6285 set marks {}
6286 set ntags 0
6287 set nheads 0
6288 if {[info exists idtags($id)]} {
6289 set marks $idtags($id)
6290 set ntags [llength $marks]
6292 if {[info exists idheads($id)]} {
6293 set marks [concat $marks $idheads($id)]
6294 set nheads [llength $idheads($id)]
6296 if {[info exists idotherrefs($id)]} {
6297 set marks [concat $marks $idotherrefs($id)]
6299 if {$marks eq {}} {
6300 return $xt
6303 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
6304 set yt [expr {$y1 - 0.5 * $linespc}]
6305 set yb [expr {$yt + $linespc - 1}]
6306 set xvals {}
6307 set wvals {}
6308 set i -1
6309 foreach tag $marks {
6310 incr i
6311 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
6312 set wid [font measure mainfontbold $tag]
6313 } else {
6314 set wid [font measure mainfont $tag]
6316 lappend xvals $xt
6317 lappend wvals $wid
6318 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
6320 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
6321 -width $lthickness -fill black -tags tag.$id]
6322 $canv lower $t
6323 foreach tag $marks x $xvals wid $wvals {
6324 set tag_quoted [string map {% %%} $tag]
6325 set xl [expr {$x + $delta}]
6326 set xr [expr {$x + $delta + $wid + $lthickness}]
6327 set font mainfont
6328 if {[incr ntags -1] >= 0} {
6329 # draw a tag
6330 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
6331 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
6332 -width 1 -outline black -fill yellow -tags tag.$id]
6333 $canv bind $t <1> [list showtag $tag_quoted 1]
6334 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
6335 } else {
6336 # draw a head or other ref
6337 if {[incr nheads -1] >= 0} {
6338 set col green
6339 if {$tag eq $mainhead} {
6340 set font mainfontbold
6342 } else {
6343 set col "#ddddff"
6345 set xl [expr {$xl - $delta/2}]
6346 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
6347 -width 1 -outline black -fill $col -tags tag.$id
6348 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
6349 set rwid [font measure mainfont $remoteprefix]
6350 set xi [expr {$x + 1}]
6351 set yti [expr {$yt + 1}]
6352 set xri [expr {$x + $rwid}]
6353 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
6354 -width 0 -fill "#ffddaa" -tags tag.$id
6357 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
6358 -font $font -tags [list tag.$id text]]
6359 if {$ntags >= 0} {
6360 $canv bind $t <1> [list showtag $tag_quoted 1]
6361 } elseif {$nheads >= 0} {
6362 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag_quoted]
6365 return $xt
6368 proc xcoord {i level ln} {
6369 global canvx0 xspc1 xspc2
6371 set x [expr {$canvx0 + $i * $xspc1($ln)}]
6372 if {$i > 0 && $i == $level} {
6373 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
6374 } elseif {$i > $level} {
6375 set x [expr {$x + $xspc2 - $xspc1($ln)}]
6377 return $x
6380 proc show_status {msg} {
6381 global canv fgcolor
6383 clear_display
6384 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
6385 -tags text -fill $fgcolor
6388 # Don't change the text pane cursor if it is currently the hand cursor,
6389 # showing that we are over a sha1 ID link.
6390 proc settextcursor {c} {
6391 global ctext curtextcursor
6393 if {[$ctext cget -cursor] == $curtextcursor} {
6394 $ctext config -cursor $c
6396 set curtextcursor $c
6399 proc nowbusy {what {name {}}} {
6400 global isbusy busyname statusw
6402 if {[array names isbusy] eq {}} {
6403 . config -cursor watch
6404 settextcursor watch
6406 set isbusy($what) 1
6407 set busyname($what) $name
6408 if {$name ne {}} {
6409 $statusw conf -text $name
6413 proc notbusy {what} {
6414 global isbusy maincursor textcursor busyname statusw
6416 catch {
6417 unset isbusy($what)
6418 if {$busyname($what) ne {} &&
6419 [$statusw cget -text] eq $busyname($what)} {
6420 $statusw conf -text {}
6423 if {[array names isbusy] eq {}} {
6424 . config -cursor $maincursor
6425 settextcursor $textcursor
6429 proc findmatches {f} {
6430 global findtype findstring
6431 if {$findtype == [mc "Regexp"]} {
6432 set matches [regexp -indices -all -inline $findstring $f]
6433 } else {
6434 set fs $findstring
6435 if {$findtype == [mc "IgnCase"]} {
6436 set f [string tolower $f]
6437 set fs [string tolower $fs]
6439 set matches {}
6440 set i 0
6441 set l [string length $fs]
6442 while {[set j [string first $fs $f $i]] >= 0} {
6443 lappend matches [list $j [expr {$j+$l-1}]]
6444 set i [expr {$j + $l}]
6447 return $matches
6450 proc dofind {{dirn 1} {wrap 1}} {
6451 global findstring findstartline findcurline selectedline numcommits
6452 global gdttype filehighlight fh_serial find_dirn findallowwrap
6454 if {[info exists find_dirn]} {
6455 if {$find_dirn == $dirn} return
6456 stopfinding
6458 focus .
6459 if {$findstring eq {} || $numcommits == 0} return
6460 if {$selectedline eq {}} {
6461 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6462 } else {
6463 set findstartline $selectedline
6465 set findcurline $findstartline
6466 nowbusy finding [mc "Searching"]
6467 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6468 after cancel do_file_hl $fh_serial
6469 do_file_hl $fh_serial
6471 set find_dirn $dirn
6472 set findallowwrap $wrap
6473 run findmore
6476 proc stopfinding {} {
6477 global find_dirn findcurline fprogcoord
6479 if {[info exists find_dirn]} {
6480 unset find_dirn
6481 unset findcurline
6482 notbusy finding
6483 set fprogcoord 0
6484 adjustprogress
6486 stopblaming
6489 proc findmore {} {
6490 global commitdata commitinfo numcommits findpattern findloc
6491 global findstartline findcurline findallowwrap
6492 global find_dirn gdttype fhighlights fprogcoord
6493 global curview varcorder vrownum varccommits vrowmod
6495 if {![info exists find_dirn]} {
6496 return 0
6498 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6499 set l $findcurline
6500 set moretodo 0
6501 if {$find_dirn > 0} {
6502 incr l
6503 if {$l >= $numcommits} {
6504 set l 0
6506 if {$l <= $findstartline} {
6507 set lim [expr {$findstartline + 1}]
6508 } else {
6509 set lim $numcommits
6510 set moretodo $findallowwrap
6512 } else {
6513 if {$l == 0} {
6514 set l $numcommits
6516 incr l -1
6517 if {$l >= $findstartline} {
6518 set lim [expr {$findstartline - 1}]
6519 } else {
6520 set lim -1
6521 set moretodo $findallowwrap
6524 set n [expr {($lim - $l) * $find_dirn}]
6525 if {$n > 500} {
6526 set n 500
6527 set moretodo 1
6529 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6530 update_arcrows $curview
6532 set found 0
6533 set domore 1
6534 set ai [bsearch $vrownum($curview) $l]
6535 set a [lindex $varcorder($curview) $ai]
6536 set arow [lindex $vrownum($curview) $ai]
6537 set ids [lindex $varccommits($curview,$a)]
6538 set arowend [expr {$arow + [llength $ids]}]
6539 if {$gdttype eq [mc "containing:"]} {
6540 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6541 if {$l < $arow || $l >= $arowend} {
6542 incr ai $find_dirn
6543 set a [lindex $varcorder($curview) $ai]
6544 set arow [lindex $vrownum($curview) $ai]
6545 set ids [lindex $varccommits($curview,$a)]
6546 set arowend [expr {$arow + [llength $ids]}]
6548 set id [lindex $ids [expr {$l - $arow}]]
6549 # shouldn't happen unless git log doesn't give all the commits...
6550 if {![info exists commitdata($id)] ||
6551 ![doesmatch $commitdata($id)]} {
6552 continue
6554 if {![info exists commitinfo($id)]} {
6555 getcommit $id
6557 set info $commitinfo($id)
6558 foreach f $info ty $fldtypes {
6559 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6560 [doesmatch $f]} {
6561 set found 1
6562 break
6565 if {$found} break
6567 } else {
6568 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6569 if {$l < $arow || $l >= $arowend} {
6570 incr ai $find_dirn
6571 set a [lindex $varcorder($curview) $ai]
6572 set arow [lindex $vrownum($curview) $ai]
6573 set ids [lindex $varccommits($curview,$a)]
6574 set arowend [expr {$arow + [llength $ids]}]
6576 set id [lindex $ids [expr {$l - $arow}]]
6577 if {![info exists fhighlights($id)]} {
6578 # this sets fhighlights($id) to -1
6579 askfilehighlight $l $id
6581 if {$fhighlights($id) > 0} {
6582 set found $domore
6583 break
6585 if {$fhighlights($id) < 0} {
6586 if {$domore} {
6587 set domore 0
6588 set findcurline [expr {$l - $find_dirn}]
6593 if {$found || ($domore && !$moretodo)} {
6594 unset findcurline
6595 unset find_dirn
6596 notbusy finding
6597 set fprogcoord 0
6598 adjustprogress
6599 if {$found} {
6600 findselectline $l
6601 } else {
6602 bell
6604 return 0
6606 if {!$domore} {
6607 flushhighlights
6608 } else {
6609 set findcurline [expr {$l - $find_dirn}]
6611 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6612 if {$n < 0} {
6613 incr n $numcommits
6615 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6616 adjustprogress
6617 return $domore
6620 proc findselectline {l} {
6621 global findloc commentend ctext findcurline markingmatches gdttype
6623 set markingmatches [expr {$gdttype eq [mc "containing:"]}]
6624 set findcurline $l
6625 selectline $l 1
6626 if {$markingmatches &&
6627 ($findloc eq [mc "All fields"] || $findloc eq [mc "Comments"])} {
6628 # highlight the matches in the comments
6629 set f [$ctext get 1.0 $commentend]
6630 set matches [findmatches $f]
6631 foreach match $matches {
6632 set start [lindex $match 0]
6633 set end [expr {[lindex $match 1] + 1}]
6634 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6637 drawvisible
6640 # mark the bits of a headline or author that match a find string
6641 proc markmatches {canv l str tag matches font row} {
6642 global selectedline
6644 set bbox [$canv bbox $tag]
6645 set x0 [lindex $bbox 0]
6646 set y0 [lindex $bbox 1]
6647 set y1 [lindex $bbox 3]
6648 foreach match $matches {
6649 set start [lindex $match 0]
6650 set end [lindex $match 1]
6651 if {$start > $end} continue
6652 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6653 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6654 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6655 [expr {$x0+$xlen+2}] $y1 \
6656 -outline {} -tags [list match$l matches] -fill yellow]
6657 $canv lower $t
6658 if {$row == $selectedline} {
6659 $canv raise $t secsel
6664 proc unmarkmatches {} {
6665 global markingmatches
6667 allcanvs delete matches
6668 set markingmatches 0
6669 stopfinding
6672 proc selcanvline {w x y} {
6673 global canv canvy0 ctext linespc
6674 global rowtextx
6675 set ymax [lindex [$canv cget -scrollregion] 3]
6676 if {$ymax == {}} return
6677 set yfrac [lindex [$canv yview] 0]
6678 set y [expr {$y + $yfrac * $ymax}]
6679 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6680 if {$l < 0} {
6681 set l 0
6683 if {$w eq $canv} {
6684 set xmax [lindex [$canv cget -scrollregion] 2]
6685 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6686 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6688 unmarkmatches
6689 selectline $l 1
6692 proc commit_descriptor {p} {
6693 global commitinfo
6694 if {![info exists commitinfo($p)]} {
6695 getcommit $p
6697 set l "..."
6698 if {[llength $commitinfo($p)] > 1} {
6699 set l [lindex $commitinfo($p) 0]
6701 return "$p ($l)\n"
6704 # append some text to the ctext widget, and make any SHA1 ID
6705 # that we know about be a clickable link.
6706 proc appendwithlinks {text tags} {
6707 global ctext linknum curview
6709 set start [$ctext index "end - 1c"]
6710 $ctext insert end $text $tags
6711 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6712 foreach l $links {
6713 set s [lindex $l 0]
6714 set e [lindex $l 1]
6715 set linkid [string range $text $s $e]
6716 incr e
6717 $ctext tag delete link$linknum
6718 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6719 setlink $linkid link$linknum
6720 incr linknum
6724 proc setlink {id lk} {
6725 global curview ctext pendinglinks
6727 set known 0
6728 if {[string length $id] < 40} {
6729 set matches [longid $id]
6730 if {[llength $matches] > 0} {
6731 if {[llength $matches] > 1} return
6732 set known 1
6733 set id [lindex $matches 0]
6735 } else {
6736 set known [commitinview $id $curview]
6738 if {$known} {
6739 $ctext tag conf $lk -foreground blue -underline 1
6740 $ctext tag bind $lk <1> [list selbyid $id]
6741 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6742 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6743 } else {
6744 lappend pendinglinks($id) $lk
6745 interestedin $id {makelink %P}
6749 proc appendshortlink {id {pre {}} {post {}}} {
6750 global ctext linknum
6752 $ctext insert end $pre
6753 $ctext tag delete link$linknum
6754 $ctext insert end [string range $id 0 7] link$linknum
6755 $ctext insert end $post
6756 setlink $id link$linknum
6757 incr linknum
6760 proc makelink {id} {
6761 global pendinglinks
6763 if {![info exists pendinglinks($id)]} return
6764 foreach lk $pendinglinks($id) {
6765 setlink $id $lk
6767 unset pendinglinks($id)
6770 proc linkcursor {w inc} {
6771 global linkentercount curtextcursor
6773 if {[incr linkentercount $inc] > 0} {
6774 $w configure -cursor hand2
6775 } else {
6776 $w configure -cursor $curtextcursor
6777 if {$linkentercount < 0} {
6778 set linkentercount 0
6783 proc viewnextline {dir} {
6784 global canv linespc
6786 $canv delete hover
6787 set ymax [lindex [$canv cget -scrollregion] 3]
6788 set wnow [$canv yview]
6789 set wtop [expr {[lindex $wnow 0] * $ymax}]
6790 set newtop [expr {$wtop + $dir * $linespc}]
6791 if {$newtop < 0} {
6792 set newtop 0
6793 } elseif {$newtop > $ymax} {
6794 set newtop $ymax
6796 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6799 # add a list of tag or branch names at position pos
6800 # returns the number of names inserted
6801 proc appendrefs {pos ids var} {
6802 global ctext linknum curview $var maxrefs
6804 if {[catch {$ctext index $pos}]} {
6805 return 0
6807 $ctext conf -state normal
6808 $ctext delete $pos "$pos lineend"
6809 set tags {}
6810 foreach id $ids {
6811 foreach tag [set $var\($id\)] {
6812 lappend tags [list $tag $id]
6815 if {[llength $tags] > $maxrefs} {
6816 $ctext insert $pos "[mc "many"] ([llength $tags])"
6817 } else {
6818 set tags [lsort -index 0 -decreasing $tags]
6819 set sep {}
6820 foreach ti $tags {
6821 set id [lindex $ti 1]
6822 set lk link$linknum
6823 incr linknum
6824 $ctext tag delete $lk
6825 $ctext insert $pos $sep
6826 $ctext insert $pos [lindex $ti 0] $lk
6827 setlink $id $lk
6828 set sep ", "
6831 $ctext conf -state disabled
6832 return [llength $tags]
6835 # called when we have finished computing the nearby tags
6836 proc dispneartags {delay} {
6837 global selectedline currentid showneartags tagphase
6839 if {$selectedline eq {} || !$showneartags} return
6840 after cancel dispnexttag
6841 if {$delay} {
6842 after 200 dispnexttag
6843 set tagphase -1
6844 } else {
6845 after idle dispnexttag
6846 set tagphase 0
6850 proc dispnexttag {} {
6851 global selectedline currentid showneartags tagphase ctext
6853 if {$selectedline eq {} || !$showneartags} return
6854 switch -- $tagphase {
6856 set dtags [desctags $currentid]
6857 if {$dtags ne {}} {
6858 appendrefs precedes $dtags idtags
6862 set atags [anctags $currentid]
6863 if {$atags ne {}} {
6864 appendrefs follows $atags idtags
6868 set dheads [descheads $currentid]
6869 if {$dheads ne {}} {
6870 if {[appendrefs branch $dheads idheads] > 1
6871 && [$ctext get "branch -3c"] eq "h"} {
6872 # turn "Branch" into "Branches"
6873 $ctext conf -state normal
6874 $ctext insert "branch -2c" "es"
6875 $ctext conf -state disabled
6880 if {[incr tagphase] <= 2} {
6881 after idle dispnexttag
6885 proc make_secsel {id} {
6886 global linehtag linentag linedtag canv canv2 canv3
6888 if {![info exists linehtag($id)]} return
6889 $canv delete secsel
6890 set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
6891 -tags secsel -fill [$canv cget -selectbackground]]
6892 $canv lower $t
6893 $canv2 delete secsel
6894 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
6895 -tags secsel -fill [$canv2 cget -selectbackground]]
6896 $canv2 lower $t
6897 $canv3 delete secsel
6898 set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
6899 -tags secsel -fill [$canv3 cget -selectbackground]]
6900 $canv3 lower $t
6903 proc make_idmark {id} {
6904 global linehtag canv fgcolor
6906 if {![info exists linehtag($id)]} return
6907 $canv delete markid
6908 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
6909 -tags markid -outline $fgcolor]
6910 $canv raise $t
6913 proc selectline {l isnew {desired_loc {}}} {
6914 global canv ctext commitinfo selectedline
6915 global canvy0 linespc parents children curview
6916 global currentid sha1entry
6917 global commentend idtags linknum
6918 global mergemax numcommits pending_select
6919 global cmitmode showneartags allcommits
6920 global targetrow targetid lastscrollrows
6921 global autoselect autosellen jump_to_here
6923 catch {unset pending_select}
6924 $canv delete hover
6925 normalline
6926 unsel_reflist
6927 stopfinding
6928 if {$l < 0 || $l >= $numcommits} return
6929 set id [commitonrow $l]
6930 set targetid $id
6931 set targetrow $l
6932 set selectedline $l
6933 set currentid $id
6934 if {$lastscrollrows < $numcommits} {
6935 setcanvscroll
6938 set y [expr {$canvy0 + $l * $linespc}]
6939 set ymax [lindex [$canv cget -scrollregion] 3]
6940 set ytop [expr {$y - $linespc - 1}]
6941 set ybot [expr {$y + $linespc + 1}]
6942 set wnow [$canv yview]
6943 set wtop [expr {[lindex $wnow 0] * $ymax}]
6944 set wbot [expr {[lindex $wnow 1] * $ymax}]
6945 set wh [expr {$wbot - $wtop}]
6946 set newtop $wtop
6947 if {$ytop < $wtop} {
6948 if {$ybot < $wtop} {
6949 set newtop [expr {$y - $wh / 2.0}]
6950 } else {
6951 set newtop $ytop
6952 if {$newtop > $wtop - $linespc} {
6953 set newtop [expr {$wtop - $linespc}]
6956 } elseif {$ybot > $wbot} {
6957 if {$ytop > $wbot} {
6958 set newtop [expr {$y - $wh / 2.0}]
6959 } else {
6960 set newtop [expr {$ybot - $wh}]
6961 if {$newtop < $wtop + $linespc} {
6962 set newtop [expr {$wtop + $linespc}]
6966 if {$newtop != $wtop} {
6967 if {$newtop < 0} {
6968 set newtop 0
6970 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6971 drawvisible
6974 make_secsel $id
6976 if {$isnew} {
6977 addtohistory [list selbyid $id 0] savecmitpos
6980 $sha1entry delete 0 end
6981 $sha1entry insert 0 $id
6982 if {$autoselect} {
6983 $sha1entry selection range 0 $autosellen
6985 rhighlight_sel $id
6987 $ctext conf -state normal
6988 clear_ctext
6989 set linknum 0
6990 if {![info exists commitinfo($id)]} {
6991 getcommit $id
6993 set info $commitinfo($id)
6994 set date [formatdate [lindex $info 2]]
6995 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6996 set date [formatdate [lindex $info 4]]
6997 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6998 if {[info exists idtags($id)]} {
6999 $ctext insert end [mc "Tags:"]
7000 foreach tag $idtags($id) {
7001 $ctext insert end " $tag"
7003 $ctext insert end "\n"
7006 set headers {}
7007 set olds $parents($curview,$id)
7008 if {[llength $olds] > 1} {
7009 set np 0
7010 foreach p $olds {
7011 if {$np >= $mergemax} {
7012 set tag mmax
7013 } else {
7014 set tag m$np
7016 $ctext insert end "[mc "Parent"]: " $tag
7017 appendwithlinks [commit_descriptor $p] {}
7018 incr np
7020 } else {
7021 foreach p $olds {
7022 append headers "[mc "Parent"]: [commit_descriptor $p]"
7026 foreach c $children($curview,$id) {
7027 append headers "[mc "Child"]: [commit_descriptor $c]"
7030 # make anything that looks like a SHA1 ID be a clickable link
7031 appendwithlinks $headers {}
7032 if {$showneartags} {
7033 if {![info exists allcommits]} {
7034 getallcommits
7036 $ctext insert end "[mc "Branch"]: "
7037 $ctext mark set branch "end -1c"
7038 $ctext mark gravity branch left
7039 $ctext insert end "\n[mc "Follows"]: "
7040 $ctext mark set follows "end -1c"
7041 $ctext mark gravity follows left
7042 $ctext insert end "\n[mc "Precedes"]: "
7043 $ctext mark set precedes "end -1c"
7044 $ctext mark gravity precedes left
7045 $ctext insert end "\n"
7046 dispneartags 1
7048 $ctext insert end "\n"
7049 set comment [lindex $info 5]
7050 if {[string first "\r" $comment] >= 0} {
7051 set comment [string map {"\r" "\n "} $comment]
7053 appendwithlinks $comment {comment}
7055 $ctext tag remove found 1.0 end
7056 $ctext conf -state disabled
7057 set commentend [$ctext index "end - 1c"]
7059 set jump_to_here $desired_loc
7060 init_flist [mc "Comments"]
7061 if {$cmitmode eq "tree"} {
7062 gettree $id
7063 } elseif {[llength $olds] <= 1} {
7064 startdiff $id
7065 } else {
7066 mergediff $id
7070 proc selfirstline {} {
7071 unmarkmatches
7072 selectline 0 1
7075 proc sellastline {} {
7076 global numcommits
7077 unmarkmatches
7078 set l [expr {$numcommits - 1}]
7079 selectline $l 1
7082 proc selnextline {dir} {
7083 global selectedline
7084 focus .
7085 if {$selectedline eq {}} return
7086 set l [expr {$selectedline + $dir}]
7087 unmarkmatches
7088 selectline $l 1
7091 proc selnextpage {dir} {
7092 global canv linespc selectedline numcommits
7094 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
7095 if {$lpp < 1} {
7096 set lpp 1
7098 allcanvs yview scroll [expr {$dir * $lpp}] units
7099 drawvisible
7100 if {$selectedline eq {}} return
7101 set l [expr {$selectedline + $dir * $lpp}]
7102 if {$l < 0} {
7103 set l 0
7104 } elseif {$l >= $numcommits} {
7105 set l [expr $numcommits - 1]
7107 unmarkmatches
7108 selectline $l 1
7111 proc unselectline {} {
7112 global selectedline currentid
7114 set selectedline {}
7115 catch {unset currentid}
7116 allcanvs delete secsel
7117 rhighlight_none
7120 proc reselectline {} {
7121 global selectedline
7123 if {$selectedline ne {}} {
7124 selectline $selectedline 0
7128 proc addtohistory {cmd {saveproc {}}} {
7129 global history historyindex curview
7131 unset_posvars
7132 save_position
7133 set elt [list $curview $cmd $saveproc {}]
7134 if {$historyindex > 0
7135 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
7136 return
7139 if {$historyindex < [llength $history]} {
7140 set history [lreplace $history $historyindex end $elt]
7141 } else {
7142 lappend history $elt
7144 incr historyindex
7145 if {$historyindex > 1} {
7146 .tf.bar.leftbut conf -state normal
7147 } else {
7148 .tf.bar.leftbut conf -state disabled
7150 .tf.bar.rightbut conf -state disabled
7153 # save the scrolling position of the diff display pane
7154 proc save_position {} {
7155 global historyindex history
7157 if {$historyindex < 1} return
7158 set hi [expr {$historyindex - 1}]
7159 set fn [lindex $history $hi 2]
7160 if {$fn ne {}} {
7161 lset history $hi 3 [eval $fn]
7165 proc unset_posvars {} {
7166 global last_posvars
7168 if {[info exists last_posvars]} {
7169 foreach {var val} $last_posvars {
7170 global $var
7171 catch {unset $var}
7173 unset last_posvars
7177 proc godo {elt} {
7178 global curview last_posvars
7180 set view [lindex $elt 0]
7181 set cmd [lindex $elt 1]
7182 set pv [lindex $elt 3]
7183 if {$curview != $view} {
7184 showview $view
7186 unset_posvars
7187 foreach {var val} $pv {
7188 global $var
7189 set $var $val
7191 set last_posvars $pv
7192 eval $cmd
7195 proc goback {} {
7196 global history historyindex
7197 focus .
7199 if {$historyindex > 1} {
7200 save_position
7201 incr historyindex -1
7202 godo [lindex $history [expr {$historyindex - 1}]]
7203 .tf.bar.rightbut conf -state normal
7205 if {$historyindex <= 1} {
7206 .tf.bar.leftbut conf -state disabled
7210 proc goforw {} {
7211 global history historyindex
7212 focus .
7214 if {$historyindex < [llength $history]} {
7215 save_position
7216 set cmd [lindex $history $historyindex]
7217 incr historyindex
7218 godo $cmd
7219 .tf.bar.leftbut conf -state normal
7221 if {$historyindex >= [llength $history]} {
7222 .tf.bar.rightbut conf -state disabled
7226 proc gettree {id} {
7227 global treefilelist treeidlist diffids diffmergeid treepending
7228 global nullid nullid2
7230 set diffids $id
7231 catch {unset diffmergeid}
7232 if {![info exists treefilelist($id)]} {
7233 if {![info exists treepending]} {
7234 if {$id eq $nullid} {
7235 set cmd [list | git ls-files]
7236 } elseif {$id eq $nullid2} {
7237 set cmd [list | git ls-files --stage -t]
7238 } else {
7239 set cmd [list | git ls-tree -r $id]
7241 if {[catch {set gtf [open $cmd r]}]} {
7242 return
7244 set treepending $id
7245 set treefilelist($id) {}
7246 set treeidlist($id) {}
7247 fconfigure $gtf -blocking 0 -encoding binary
7248 filerun $gtf [list gettreeline $gtf $id]
7250 } else {
7251 setfilelist $id
7255 proc gettreeline {gtf id} {
7256 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
7258 set nl 0
7259 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
7260 if {$diffids eq $nullid} {
7261 set fname $line
7262 } else {
7263 set i [string first "\t" $line]
7264 if {$i < 0} continue
7265 set fname [string range $line [expr {$i+1}] end]
7266 set line [string range $line 0 [expr {$i-1}]]
7267 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
7268 set sha1 [lindex $line 2]
7269 lappend treeidlist($id) $sha1
7271 if {[string index $fname 0] eq "\""} {
7272 set fname [lindex $fname 0]
7274 set fname [encoding convertfrom $fname]
7275 lappend treefilelist($id) $fname
7277 if {![eof $gtf]} {
7278 return [expr {$nl >= 1000? 2: 1}]
7280 close $gtf
7281 unset treepending
7282 if {$cmitmode ne "tree"} {
7283 if {![info exists diffmergeid]} {
7284 gettreediffs $diffids
7286 } elseif {$id ne $diffids} {
7287 gettree $diffids
7288 } else {
7289 setfilelist $id
7291 return 0
7294 proc showfile {f} {
7295 global treefilelist treeidlist diffids nullid nullid2
7296 global ctext_file_names ctext_file_lines
7297 global ctext commentend
7299 set i [lsearch -exact $treefilelist($diffids) $f]
7300 if {$i < 0} {
7301 puts "oops, $f not in list for id $diffids"
7302 return
7304 if {$diffids eq $nullid} {
7305 if {[catch {set bf [open $f r]} err]} {
7306 puts "oops, can't read $f: $err"
7307 return
7309 } else {
7310 set blob [lindex $treeidlist($diffids) $i]
7311 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
7312 puts "oops, error reading blob $blob: $err"
7313 return
7316 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
7317 filerun $bf [list getblobline $bf $diffids]
7318 $ctext config -state normal
7319 clear_ctext $commentend
7320 lappend ctext_file_names $f
7321 lappend ctext_file_lines [lindex [split $commentend "."] 0]
7322 $ctext insert end "\n"
7323 $ctext insert end "$f\n" filesep
7324 $ctext config -state disabled
7325 $ctext yview $commentend
7326 settabs 0
7329 proc getblobline {bf id} {
7330 global diffids cmitmode ctext
7332 if {$id ne $diffids || $cmitmode ne "tree"} {
7333 catch {close $bf}
7334 return 0
7336 $ctext config -state normal
7337 set nl 0
7338 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
7339 $ctext insert end "$line\n"
7341 if {[eof $bf]} {
7342 global jump_to_here ctext_file_names commentend
7344 # delete last newline
7345 $ctext delete "end - 2c" "end - 1c"
7346 close $bf
7347 if {$jump_to_here ne {} &&
7348 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
7349 set lnum [expr {[lindex $jump_to_here 1] +
7350 [lindex [split $commentend .] 0]}]
7351 mark_ctext_line $lnum
7353 $ctext config -state disabled
7354 return 0
7356 $ctext config -state disabled
7357 return [expr {$nl >= 1000? 2: 1}]
7360 proc mark_ctext_line {lnum} {
7361 global ctext markbgcolor
7363 $ctext tag delete omark
7364 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
7365 $ctext tag conf omark -background $markbgcolor
7366 $ctext see $lnum.0
7369 proc mergediff {id} {
7370 global diffmergeid
7371 global diffids treediffs
7372 global parents curview
7374 set diffmergeid $id
7375 set diffids $id
7376 set treediffs($id) {}
7377 set np [llength $parents($curview,$id)]
7378 settabs $np
7379 getblobdiffs $id
7382 proc startdiff {ids} {
7383 global treediffs diffids treepending diffmergeid nullid nullid2
7385 settabs 1
7386 set diffids $ids
7387 catch {unset diffmergeid}
7388 if {![info exists treediffs($ids)] ||
7389 [lsearch -exact $ids $nullid] >= 0 ||
7390 [lsearch -exact $ids $nullid2] >= 0} {
7391 if {![info exists treepending]} {
7392 gettreediffs $ids
7394 } else {
7395 addtocflist $ids
7399 # If the filename (name) is under any of the passed filter paths
7400 # then return true to include the file in the listing.
7401 proc path_filter {filter name} {
7402 set worktree [gitworktree]
7403 foreach p $filter {
7404 set fq_p [file normalize $p]
7405 set fq_n [file normalize [file join $worktree $name]]
7406 if {[string match [file normalize $fq_p]* $fq_n]} {
7407 return 1
7410 return 0
7413 proc addtocflist {ids} {
7414 global treediffs
7416 add_flist $treediffs($ids)
7417 getblobdiffs $ids
7420 proc diffcmd {ids flags} {
7421 global nullid nullid2
7423 set i [lsearch -exact $ids $nullid]
7424 set j [lsearch -exact $ids $nullid2]
7425 if {$i >= 0} {
7426 if {[llength $ids] > 1 && $j < 0} {
7427 # comparing working directory with some specific revision
7428 set cmd [concat | git diff-index $flags]
7429 if {$i == 0} {
7430 lappend cmd -R [lindex $ids 1]
7431 } else {
7432 lappend cmd [lindex $ids 0]
7434 } else {
7435 # comparing working directory with index
7436 set cmd [concat | git diff-files $flags]
7437 if {$j == 1} {
7438 lappend cmd -R
7441 } elseif {$j >= 0} {
7442 set cmd [concat | git diff-index --cached $flags]
7443 if {[llength $ids] > 1} {
7444 # comparing index with specific revision
7445 if {$j == 0} {
7446 lappend cmd -R [lindex $ids 1]
7447 } else {
7448 lappend cmd [lindex $ids 0]
7450 } else {
7451 # comparing index with HEAD
7452 lappend cmd HEAD
7454 } else {
7455 set cmd [concat | git diff-tree -r $flags $ids]
7457 return $cmd
7460 proc gettreediffs {ids} {
7461 global treediff treepending
7463 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7465 set treepending $ids
7466 set treediff {}
7467 fconfigure $gdtf -blocking 0 -encoding binary
7468 filerun $gdtf [list gettreediffline $gdtf $ids]
7471 proc gettreediffline {gdtf ids} {
7472 global treediff treediffs treepending diffids diffmergeid
7473 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7475 set nr 0
7476 set sublist {}
7477 set max 1000
7478 if {$perfile_attrs} {
7479 # cache_gitattr is slow, and even slower on win32 where we
7480 # have to invoke it for only about 30 paths at a time
7481 set max 500
7482 if {[tk windowingsystem] == "win32"} {
7483 set max 120
7486 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7487 set i [string first "\t" $line]
7488 if {$i >= 0} {
7489 set file [string range $line [expr {$i+1}] end]
7490 if {[string index $file 0] eq "\""} {
7491 set file [lindex $file 0]
7493 set file [encoding convertfrom $file]
7494 if {$file ne [lindex $treediff end]} {
7495 lappend treediff $file
7496 lappend sublist $file
7500 if {$perfile_attrs} {
7501 cache_gitattr encoding $sublist
7503 if {![eof $gdtf]} {
7504 return [expr {$nr >= $max? 2: 1}]
7506 close $gdtf
7507 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7508 set flist {}
7509 foreach f $treediff {
7510 if {[path_filter $vfilelimit($curview) $f]} {
7511 lappend flist $f
7514 set treediffs($ids) $flist
7515 } else {
7516 set treediffs($ids) $treediff
7518 unset treepending
7519 if {$cmitmode eq "tree" && [llength $diffids] == 1} {
7520 gettree $diffids
7521 } elseif {$ids != $diffids} {
7522 if {![info exists diffmergeid]} {
7523 gettreediffs $diffids
7525 } else {
7526 addtocflist $ids
7528 return 0
7531 # empty string or positive integer
7532 proc diffcontextvalidate {v} {
7533 return [regexp {^(|[1-9][0-9]*)$} $v]
7536 proc diffcontextchange {n1 n2 op} {
7537 global diffcontextstring diffcontext
7539 if {[string is integer -strict $diffcontextstring]} {
7540 if {$diffcontextstring >= 0} {
7541 set diffcontext $diffcontextstring
7542 reselectline
7547 proc changeignorespace {} {
7548 reselectline
7551 proc changeworddiff {name ix op} {
7552 reselectline
7555 proc getblobdiffs {ids} {
7556 global blobdifffd diffids env
7557 global diffinhdr treediffs
7558 global diffcontext
7559 global ignorespace
7560 global worddiff
7561 global limitdiffs vfilelimit curview
7562 global diffencoding targetline diffnparents
7563 global git_version currdiffsubmod
7565 set textconv {}
7566 if {[package vcompare $git_version "1.6.1"] >= 0} {
7567 set textconv "--textconv"
7569 set submodule {}
7570 if {[package vcompare $git_version "1.6.6"] >= 0} {
7571 set submodule "--submodule"
7573 set cmd [diffcmd $ids "-p $textconv $submodule -C --cc --no-commit-id -U$diffcontext"]
7574 if {$ignorespace} {
7575 append cmd " -w"
7577 if {$worddiff ne [mc "Line diff"]} {
7578 append cmd " --word-diff=porcelain"
7580 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7581 set cmd [concat $cmd -- $vfilelimit($curview)]
7583 if {[catch {set bdf [open $cmd r]} err]} {
7584 error_popup [mc "Error getting diffs: %s" $err]
7585 return
7587 set targetline {}
7588 set diffnparents 0
7589 set diffinhdr 0
7590 set diffencoding [get_path_encoding {}]
7591 fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
7592 set blobdifffd($ids) $bdf
7593 set currdiffsubmod ""
7594 filerun $bdf [list getblobdiffline $bdf $diffids]
7597 proc savecmitpos {} {
7598 global ctext cmitmode
7600 if {$cmitmode eq "tree"} {
7601 return {}
7603 return [list target_scrollpos [$ctext index @0,0]]
7606 proc savectextpos {} {
7607 global ctext
7609 return [list target_scrollpos [$ctext index @0,0]]
7612 proc maybe_scroll_ctext {ateof} {
7613 global ctext target_scrollpos
7615 if {![info exists target_scrollpos]} return
7616 if {!$ateof} {
7617 set nlines [expr {[winfo height $ctext]
7618 / [font metrics textfont -linespace]}]
7619 if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
7621 $ctext yview $target_scrollpos
7622 unset target_scrollpos
7625 proc setinlist {var i val} {
7626 global $var
7628 while {[llength [set $var]] < $i} {
7629 lappend $var {}
7631 if {[llength [set $var]] == $i} {
7632 lappend $var $val
7633 } else {
7634 lset $var $i $val
7638 proc makediffhdr {fname ids} {
7639 global ctext curdiffstart treediffs diffencoding
7640 global ctext_file_names jump_to_here targetline diffline
7642 set fname [encoding convertfrom $fname]
7643 set diffencoding [get_path_encoding $fname]
7644 set i [lsearch -exact $treediffs($ids) $fname]
7645 if {$i >= 0} {
7646 setinlist difffilestart $i $curdiffstart
7648 lset ctext_file_names end $fname
7649 set l [expr {(78 - [string length $fname]) / 2}]
7650 set pad [string range "----------------------------------------" 1 $l]
7651 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7652 set targetline {}
7653 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7654 set targetline [lindex $jump_to_here 1]
7656 set diffline 0
7659 proc getblobdiffline {bdf ids} {
7660 global diffids blobdifffd ctext curdiffstart
7661 global diffnexthead diffnextnote difffilestart
7662 global ctext_file_names ctext_file_lines
7663 global diffinhdr treediffs mergemax diffnparents
7664 global diffencoding jump_to_here targetline diffline currdiffsubmod
7665 global worddiff
7667 set nr 0
7668 $ctext conf -state normal
7669 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7670 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7671 catch {close $bdf}
7672 return 0
7674 if {![string compare -length 5 "diff " $line]} {
7675 if {![regexp {^diff (--cc|--git) } $line m type]} {
7676 set line [encoding convertfrom $line]
7677 $ctext insert end "$line\n" hunksep
7678 continue
7680 # start of a new file
7681 set diffinhdr 1
7682 $ctext insert end "\n"
7683 set curdiffstart [$ctext index "end - 1c"]
7684 lappend ctext_file_names ""
7685 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7686 $ctext insert end "\n" filesep
7688 if {$type eq "--cc"} {
7689 # start of a new file in a merge diff
7690 set fname [string range $line 10 end]
7691 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7692 lappend treediffs($ids) $fname
7693 add_flist [list $fname]
7696 } else {
7697 set line [string range $line 11 end]
7698 # If the name hasn't changed the length will be odd,
7699 # the middle char will be a space, and the two bits either
7700 # side will be a/name and b/name, or "a/name" and "b/name".
7701 # If the name has changed we'll get "rename from" and
7702 # "rename to" or "copy from" and "copy to" lines following
7703 # this, and we'll use them to get the filenames.
7704 # This complexity is necessary because spaces in the
7705 # filename(s) don't get escaped.
7706 set l [string length $line]
7707 set i [expr {$l / 2}]
7708 if {!(($l & 1) && [string index $line $i] eq " " &&
7709 [string range $line 2 [expr {$i - 1}]] eq \
7710 [string range $line [expr {$i + 3}] end])} {
7711 continue
7713 # unescape if quoted and chop off the a/ from the front
7714 if {[string index $line 0] eq "\""} {
7715 set fname [string range [lindex $line 0] 2 end]
7716 } else {
7717 set fname [string range $line 2 [expr {$i - 1}]]
7720 makediffhdr $fname $ids
7722 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7723 set fname [encoding convertfrom [string range $line 16 end]]
7724 $ctext insert end "\n"
7725 set curdiffstart [$ctext index "end - 1c"]
7726 lappend ctext_file_names $fname
7727 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7728 $ctext insert end "$line\n" filesep
7729 set i [lsearch -exact $treediffs($ids) $fname]
7730 if {$i >= 0} {
7731 setinlist difffilestart $i $curdiffstart
7734 } elseif {![string compare -length 2 "@@" $line]} {
7735 regexp {^@@+} $line ats
7736 set line [encoding convertfrom $diffencoding $line]
7737 $ctext insert end "$line\n" hunksep
7738 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7739 set diffline $nl
7741 set diffnparents [expr {[string length $ats] - 1}]
7742 set diffinhdr 0
7744 } elseif {![string compare -length 10 "Submodule " $line]} {
7745 # start of a new submodule
7746 if {[regexp -indices "\[0-9a-f\]+\\.\\." $line nameend]} {
7747 set fname [string range $line 10 [expr [lindex $nameend 0] - 2]]
7748 } else {
7749 set fname [string range $line 10 [expr [string first "contains " $line] - 2]]
7751 if {$currdiffsubmod != $fname} {
7752 $ctext insert end "\n"; # Add newline after commit message
7754 set curdiffstart [$ctext index "end - 1c"]
7755 lappend ctext_file_names ""
7756 if {$currdiffsubmod != $fname} {
7757 lappend ctext_file_lines $fname
7758 makediffhdr $fname $ids
7759 set currdiffsubmod $fname
7760 $ctext insert end "\n$line\n" filesep
7761 } else {
7762 $ctext insert end "$line\n" filesep
7764 } elseif {![string compare -length 3 " >" $line]} {
7765 set $currdiffsubmod ""
7766 set line [encoding convertfrom $diffencoding $line]
7767 $ctext insert end "$line\n" dresult
7768 } elseif {![string compare -length 3 " <" $line]} {
7769 set $currdiffsubmod ""
7770 set line [encoding convertfrom $diffencoding $line]
7771 $ctext insert end "$line\n" d0
7772 } elseif {$diffinhdr} {
7773 if {![string compare -length 12 "rename from " $line]} {
7774 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7775 if {[string index $fname 0] eq "\""} {
7776 set fname [lindex $fname 0]
7778 set fname [encoding convertfrom $fname]
7779 set i [lsearch -exact $treediffs($ids) $fname]
7780 if {$i >= 0} {
7781 setinlist difffilestart $i $curdiffstart
7783 } elseif {![string compare -length 10 $line "rename to "] ||
7784 ![string compare -length 8 $line "copy to "]} {
7785 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7786 if {[string index $fname 0] eq "\""} {
7787 set fname [lindex $fname 0]
7789 makediffhdr $fname $ids
7790 } elseif {[string compare -length 3 $line "---"] == 0} {
7791 # do nothing
7792 continue
7793 } elseif {[string compare -length 3 $line "+++"] == 0} {
7794 set diffinhdr 0
7795 continue
7797 $ctext insert end "$line\n" filesep
7799 } else {
7800 set line [string map {\x1A ^Z} \
7801 [encoding convertfrom $diffencoding $line]]
7802 # parse the prefix - one ' ', '-' or '+' for each parent
7803 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7804 set tag [expr {$diffnparents > 1? "m": "d"}]
7805 set dowords [expr {$worddiff ne [mc "Line diff"] && $diffnparents == 1}]
7806 set words_pre_markup ""
7807 set words_post_markup ""
7808 if {[string trim $prefix " -+"] eq {}} {
7809 # prefix only has " ", "-" and "+" in it: normal diff line
7810 set num [string first "-" $prefix]
7811 if {$dowords} {
7812 set line [string range $line 1 end]
7814 if {$num >= 0} {
7815 # removed line, first parent with line is $num
7816 if {$num >= $mergemax} {
7817 set num "max"
7819 if {$dowords && $worddiff eq [mc "Markup words"]} {
7820 $ctext insert end "\[-$line-\]" $tag$num
7821 } else {
7822 $ctext insert end "$line" $tag$num
7824 if {!$dowords} {
7825 $ctext insert end "\n" $tag$num
7827 } else {
7828 set tags {}
7829 if {[string first "+" $prefix] >= 0} {
7830 # added line
7831 lappend tags ${tag}result
7832 if {$diffnparents > 1} {
7833 set num [string first " " $prefix]
7834 if {$num >= 0} {
7835 if {$num >= $mergemax} {
7836 set num "max"
7838 lappend tags m$num
7841 set words_pre_markup "{+"
7842 set words_post_markup "+}"
7844 if {$targetline ne {}} {
7845 if {$diffline == $targetline} {
7846 set seehere [$ctext index "end - 1 chars"]
7847 set targetline {}
7848 } else {
7849 incr diffline
7852 if {$dowords && $worddiff eq [mc "Markup words"]} {
7853 $ctext insert end "$words_pre_markup$line$words_post_markup" $tags
7854 } else {
7855 $ctext insert end "$line" $tags
7857 if {!$dowords} {
7858 $ctext insert end "\n" $tags
7861 } elseif {$dowords && $prefix eq "~"} {
7862 $ctext insert end "\n" {}
7863 } else {
7864 # "\ No newline at end of file",
7865 # or something else we don't recognize
7866 $ctext insert end "$line\n" hunksep
7870 if {[info exists seehere]} {
7871 mark_ctext_line [lindex [split $seehere .] 0]
7873 maybe_scroll_ctext [eof $bdf]
7874 $ctext conf -state disabled
7875 if {[eof $bdf]} {
7876 catch {close $bdf}
7877 return 0
7879 return [expr {$nr >= 1000? 2: 1}]
7882 proc changediffdisp {} {
7883 global ctext diffelide
7885 $ctext tag conf d0 -elide [lindex $diffelide 0]
7886 $ctext tag conf dresult -elide [lindex $diffelide 1]
7889 proc highlightfile {loc cline} {
7890 global ctext cflist cflist_top
7892 $ctext yview $loc
7893 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7894 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7895 $cflist see $cline.0
7896 set cflist_top $cline
7899 proc prevfile {} {
7900 global difffilestart ctext cmitmode
7902 if {$cmitmode eq "tree"} return
7903 set prev 0.0
7904 set prevline 1
7905 set here [$ctext index @0,0]
7906 foreach loc $difffilestart {
7907 if {[$ctext compare $loc >= $here]} {
7908 highlightfile $prev $prevline
7909 return
7911 set prev $loc
7912 incr prevline
7914 highlightfile $prev $prevline
7917 proc nextfile {} {
7918 global difffilestart ctext cmitmode
7920 if {$cmitmode eq "tree"} return
7921 set here [$ctext index @0,0]
7922 set line 1
7923 foreach loc $difffilestart {
7924 incr line
7925 if {[$ctext compare $loc > $here]} {
7926 highlightfile $loc $line
7927 return
7932 proc clear_ctext {{first 1.0}} {
7933 global ctext smarktop smarkbot
7934 global ctext_file_names ctext_file_lines
7935 global pendinglinks
7937 set l [lindex [split $first .] 0]
7938 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7939 set smarktop $l
7941 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7942 set smarkbot $l
7944 $ctext delete $first end
7945 if {$first eq "1.0"} {
7946 catch {unset pendinglinks}
7948 set ctext_file_names {}
7949 set ctext_file_lines {}
7952 proc settabs {{firstab {}}} {
7953 global firsttabstop tabstop ctext have_tk85
7955 if {$firstab ne {} && $have_tk85} {
7956 set firsttabstop $firstab
7958 set w [font measure textfont "0"]
7959 if {$firsttabstop != 0} {
7960 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7961 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7962 } elseif {$have_tk85 || $tabstop != 8} {
7963 $ctext conf -tabs [expr {$tabstop * $w}]
7964 } else {
7965 $ctext conf -tabs {}
7969 proc incrsearch {name ix op} {
7970 global ctext searchstring searchdirn
7972 $ctext tag remove found 1.0 end
7973 if {[catch {$ctext index anchor}]} {
7974 # no anchor set, use start of selection, or of visible area
7975 set sel [$ctext tag ranges sel]
7976 if {$sel ne {}} {
7977 $ctext mark set anchor [lindex $sel 0]
7978 } elseif {$searchdirn eq "-forwards"} {
7979 $ctext mark set anchor @0,0
7980 } else {
7981 $ctext mark set anchor @0,[winfo height $ctext]
7984 if {$searchstring ne {}} {
7985 set here [$ctext search $searchdirn -- $searchstring anchor]
7986 if {$here ne {}} {
7987 $ctext see $here
7989 searchmarkvisible 1
7993 proc dosearch {} {
7994 global sstring ctext searchstring searchdirn
7996 focus $sstring
7997 $sstring icursor end
7998 set searchdirn -forwards
7999 if {$searchstring ne {}} {
8000 set sel [$ctext tag ranges sel]
8001 if {$sel ne {}} {
8002 set start "[lindex $sel 0] + 1c"
8003 } elseif {[catch {set start [$ctext index anchor]}]} {
8004 set start "@0,0"
8006 set match [$ctext search -count mlen -- $searchstring $start]
8007 $ctext tag remove sel 1.0 end
8008 if {$match eq {}} {
8009 bell
8010 return
8012 $ctext see $match
8013 set mend "$match + $mlen c"
8014 $ctext tag add sel $match $mend
8015 $ctext mark unset anchor
8019 proc dosearchback {} {
8020 global sstring ctext searchstring searchdirn
8022 focus $sstring
8023 $sstring icursor end
8024 set searchdirn -backwards
8025 if {$searchstring ne {}} {
8026 set sel [$ctext tag ranges sel]
8027 if {$sel ne {}} {
8028 set start [lindex $sel 0]
8029 } elseif {[catch {set start [$ctext index anchor]}]} {
8030 set start @0,[winfo height $ctext]
8032 set match [$ctext search -backwards -count ml -- $searchstring $start]
8033 $ctext tag remove sel 1.0 end
8034 if {$match eq {}} {
8035 bell
8036 return
8038 $ctext see $match
8039 set mend "$match + $ml c"
8040 $ctext tag add sel $match $mend
8041 $ctext mark unset anchor
8045 proc searchmark {first last} {
8046 global ctext searchstring
8048 set mend $first.0
8049 while {1} {
8050 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
8051 if {$match eq {}} break
8052 set mend "$match + $mlen c"
8053 $ctext tag add found $match $mend
8057 proc searchmarkvisible {doall} {
8058 global ctext smarktop smarkbot
8060 set topline [lindex [split [$ctext index @0,0] .] 0]
8061 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
8062 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
8063 # no overlap with previous
8064 searchmark $topline $botline
8065 set smarktop $topline
8066 set smarkbot $botline
8067 } else {
8068 if {$topline < $smarktop} {
8069 searchmark $topline [expr {$smarktop-1}]
8070 set smarktop $topline
8072 if {$botline > $smarkbot} {
8073 searchmark [expr {$smarkbot+1}] $botline
8074 set smarkbot $botline
8079 proc scrolltext {f0 f1} {
8080 global searchstring
8082 .bleft.bottom.sb set $f0 $f1
8083 if {$searchstring ne {}} {
8084 searchmarkvisible 0
8088 proc setcoords {} {
8089 global linespc charspc canvx0 canvy0
8090 global xspc1 xspc2 lthickness
8092 set linespc [font metrics mainfont -linespace]
8093 set charspc [font measure mainfont "m"]
8094 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
8095 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
8096 set lthickness [expr {int($linespc / 9) + 1}]
8097 set xspc1(0) $linespc
8098 set xspc2 $linespc
8101 proc redisplay {} {
8102 global canv
8103 global selectedline
8105 set ymax [lindex [$canv cget -scrollregion] 3]
8106 if {$ymax eq {} || $ymax == 0} return
8107 set span [$canv yview]
8108 clear_display
8109 setcanvscroll
8110 allcanvs yview moveto [lindex $span 0]
8111 drawvisible
8112 if {$selectedline ne {}} {
8113 selectline $selectedline 0
8114 allcanvs yview moveto [lindex $span 0]
8118 proc parsefont {f n} {
8119 global fontattr
8121 set fontattr($f,family) [lindex $n 0]
8122 set s [lindex $n 1]
8123 if {$s eq {} || $s == 0} {
8124 set s 10
8125 } elseif {$s < 0} {
8126 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
8128 set fontattr($f,size) $s
8129 set fontattr($f,weight) normal
8130 set fontattr($f,slant) roman
8131 foreach style [lrange $n 2 end] {
8132 switch -- $style {
8133 "normal" -
8134 "bold" {set fontattr($f,weight) $style}
8135 "roman" -
8136 "italic" {set fontattr($f,slant) $style}
8141 proc fontflags {f {isbold 0}} {
8142 global fontattr
8144 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
8145 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
8146 -slant $fontattr($f,slant)]
8149 proc fontname {f} {
8150 global fontattr
8152 set n [list $fontattr($f,family) $fontattr($f,size)]
8153 if {$fontattr($f,weight) eq "bold"} {
8154 lappend n "bold"
8156 if {$fontattr($f,slant) eq "italic"} {
8157 lappend n "italic"
8159 return $n
8162 proc incrfont {inc} {
8163 global mainfont textfont ctext canv cflist showrefstop
8164 global stopped entries fontattr
8166 unmarkmatches
8167 set s $fontattr(mainfont,size)
8168 incr s $inc
8169 if {$s < 1} {
8170 set s 1
8172 set fontattr(mainfont,size) $s
8173 font config mainfont -size $s
8174 font config mainfontbold -size $s
8175 set mainfont [fontname mainfont]
8176 set s $fontattr(textfont,size)
8177 incr s $inc
8178 if {$s < 1} {
8179 set s 1
8181 set fontattr(textfont,size) $s
8182 font config textfont -size $s
8183 font config textfontbold -size $s
8184 set textfont [fontname textfont]
8185 setcoords
8186 settabs
8187 redisplay
8190 proc clearsha1 {} {
8191 global sha1entry sha1string
8192 if {[string length $sha1string] == 40} {
8193 $sha1entry delete 0 end
8197 proc sha1change {n1 n2 op} {
8198 global sha1string currentid sha1but
8199 if {$sha1string == {}
8200 || ([info exists currentid] && $sha1string == $currentid)} {
8201 set state disabled
8202 } else {
8203 set state normal
8205 if {[$sha1but cget -state] == $state} return
8206 if {$state == "normal"} {
8207 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
8208 } else {
8209 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
8213 proc gotocommit {} {
8214 global sha1string tagids headids curview varcid
8216 if {$sha1string == {}
8217 || ([info exists currentid] && $sha1string == $currentid)} return
8218 if {[info exists tagids($sha1string)]} {
8219 set id $tagids($sha1string)
8220 } elseif {[info exists headids($sha1string)]} {
8221 set id $headids($sha1string)
8222 } else {
8223 set id [string tolower $sha1string]
8224 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
8225 set matches [longid $id]
8226 if {$matches ne {}} {
8227 if {[llength $matches] > 1} {
8228 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
8229 return
8231 set id [lindex $matches 0]
8233 } else {
8234 if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
8235 error_popup [mc "Revision %s is not known" $sha1string]
8236 return
8240 if {[commitinview $id $curview]} {
8241 selectline [rowofcommit $id] 1
8242 return
8244 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
8245 set msg [mc "SHA1 id %s is not known" $sha1string]
8246 } else {
8247 set msg [mc "Revision %s is not in the current view" $sha1string]
8249 error_popup $msg
8252 proc lineenter {x y id} {
8253 global hoverx hovery hoverid hovertimer
8254 global commitinfo canv
8256 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8257 set hoverx $x
8258 set hovery $y
8259 set hoverid $id
8260 if {[info exists hovertimer]} {
8261 after cancel $hovertimer
8263 set hovertimer [after 500 linehover]
8264 $canv delete hover
8267 proc linemotion {x y id} {
8268 global hoverx hovery hoverid hovertimer
8270 if {[info exists hoverid] && $id == $hoverid} {
8271 set hoverx $x
8272 set hovery $y
8273 if {[info exists hovertimer]} {
8274 after cancel $hovertimer
8276 set hovertimer [after 500 linehover]
8280 proc lineleave {id} {
8281 global hoverid hovertimer canv
8283 if {[info exists hoverid] && $id == $hoverid} {
8284 $canv delete hover
8285 if {[info exists hovertimer]} {
8286 after cancel $hovertimer
8287 unset hovertimer
8289 unset hoverid
8293 proc linehover {} {
8294 global hoverx hovery hoverid hovertimer
8295 global canv linespc lthickness
8296 global commitinfo
8298 set text [lindex $commitinfo($hoverid) 0]
8299 set ymax [lindex [$canv cget -scrollregion] 3]
8300 if {$ymax == {}} return
8301 set yfrac [lindex [$canv yview] 0]
8302 set x [expr {$hoverx + 2 * $linespc}]
8303 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
8304 set x0 [expr {$x - 2 * $lthickness}]
8305 set y0 [expr {$y - 2 * $lthickness}]
8306 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
8307 set y1 [expr {$y + $linespc + 2 * $lthickness}]
8308 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
8309 -fill \#ffff80 -outline black -width 1 -tags hover]
8310 $canv raise $t
8311 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
8312 -font mainfont]
8313 $canv raise $t
8316 proc clickisonarrow {id y} {
8317 global lthickness
8319 set ranges [rowranges $id]
8320 set thresh [expr {2 * $lthickness + 6}]
8321 set n [expr {[llength $ranges] - 1}]
8322 for {set i 1} {$i < $n} {incr i} {
8323 set row [lindex $ranges $i]
8324 if {abs([yc $row] - $y) < $thresh} {
8325 return $i
8328 return {}
8331 proc arrowjump {id n y} {
8332 global canv
8334 # 1 <-> 2, 3 <-> 4, etc...
8335 set n [expr {(($n - 1) ^ 1) + 1}]
8336 set row [lindex [rowranges $id] $n]
8337 set yt [yc $row]
8338 set ymax [lindex [$canv cget -scrollregion] 3]
8339 if {$ymax eq {} || $ymax <= 0} return
8340 set view [$canv yview]
8341 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
8342 set yfrac [expr {$yt / $ymax - $yspan / 2}]
8343 if {$yfrac < 0} {
8344 set yfrac 0
8346 allcanvs yview moveto $yfrac
8349 proc lineclick {x y id isnew} {
8350 global ctext commitinfo children canv thickerline curview
8352 if {![info exists commitinfo($id)] && ![getcommit $id]} return
8353 unmarkmatches
8354 unselectline
8355 normalline
8356 $canv delete hover
8357 # draw this line thicker than normal
8358 set thickerline $id
8359 drawlines $id
8360 if {$isnew} {
8361 set ymax [lindex [$canv cget -scrollregion] 3]
8362 if {$ymax eq {}} return
8363 set yfrac [lindex [$canv yview] 0]
8364 set y [expr {$y + $yfrac * $ymax}]
8366 set dirn [clickisonarrow $id $y]
8367 if {$dirn ne {}} {
8368 arrowjump $id $dirn $y
8369 return
8372 if {$isnew} {
8373 addtohistory [list lineclick $x $y $id 0] savectextpos
8375 # fill the details pane with info about this line
8376 $ctext conf -state normal
8377 clear_ctext
8378 settabs 0
8379 $ctext insert end "[mc "Parent"]:\t"
8380 $ctext insert end $id link0
8381 setlink $id link0
8382 set info $commitinfo($id)
8383 $ctext insert end "\n\t[lindex $info 0]\n"
8384 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
8385 set date [formatdate [lindex $info 2]]
8386 $ctext insert end "\t[mc "Date"]:\t$date\n"
8387 set kids $children($curview,$id)
8388 if {$kids ne {}} {
8389 $ctext insert end "\n[mc "Children"]:"
8390 set i 0
8391 foreach child $kids {
8392 incr i
8393 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
8394 set info $commitinfo($child)
8395 $ctext insert end "\n\t"
8396 $ctext insert end $child link$i
8397 setlink $child link$i
8398 $ctext insert end "\n\t[lindex $info 0]"
8399 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
8400 set date [formatdate [lindex $info 2]]
8401 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
8404 maybe_scroll_ctext 1
8405 $ctext conf -state disabled
8406 init_flist {}
8409 proc normalline {} {
8410 global thickerline
8411 if {[info exists thickerline]} {
8412 set id $thickerline
8413 unset thickerline
8414 drawlines $id
8418 proc selbyid {id {isnew 1}} {
8419 global curview
8420 if {[commitinview $id $curview]} {
8421 selectline [rowofcommit $id] $isnew
8425 proc mstime {} {
8426 global startmstime
8427 if {![info exists startmstime]} {
8428 set startmstime [clock clicks -milliseconds]
8430 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
8433 proc rowmenu {x y id} {
8434 global rowctxmenu selectedline rowmenuid curview
8435 global nullid nullid2 fakerowmenu mainhead markedid
8437 stopfinding
8438 set rowmenuid $id
8439 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
8440 set state disabled
8441 } else {
8442 set state normal
8444 if {$id ne $nullid && $id ne $nullid2} {
8445 set menu $rowctxmenu
8446 if {$mainhead ne {}} {
8447 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead] -state normal
8448 } else {
8449 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
8451 if {[info exists markedid] && $markedid ne $id} {
8452 $menu entryconfigure 9 -state normal
8453 $menu entryconfigure 10 -state normal
8454 $menu entryconfigure 11 -state normal
8455 } else {
8456 $menu entryconfigure 9 -state disabled
8457 $menu entryconfigure 10 -state disabled
8458 $menu entryconfigure 11 -state disabled
8460 } else {
8461 set menu $fakerowmenu
8463 $menu entryconfigure [mca "Diff this -> selected"] -state $state
8464 $menu entryconfigure [mca "Diff selected -> this"] -state $state
8465 $menu entryconfigure [mca "Make patch"] -state $state
8466 tk_popup $menu $x $y
8469 proc markhere {} {
8470 global rowmenuid markedid canv
8472 set markedid $rowmenuid
8473 make_idmark $markedid
8476 proc gotomark {} {
8477 global markedid
8479 if {[info exists markedid]} {
8480 selbyid $markedid
8484 proc replace_by_kids {l r} {
8485 global curview children
8487 set id [commitonrow $r]
8488 set l [lreplace $l 0 0]
8489 foreach kid $children($curview,$id) {
8490 lappend l [rowofcommit $kid]
8492 return [lsort -integer -decreasing -unique $l]
8495 proc find_common_desc {} {
8496 global markedid rowmenuid curview children
8498 if {![info exists markedid]} return
8499 if {![commitinview $markedid $curview] ||
8500 ![commitinview $rowmenuid $curview]} return
8501 #set t1 [clock clicks -milliseconds]
8502 set l1 [list [rowofcommit $markedid]]
8503 set l2 [list [rowofcommit $rowmenuid]]
8504 while 1 {
8505 set r1 [lindex $l1 0]
8506 set r2 [lindex $l2 0]
8507 if {$r1 eq {} || $r2 eq {}} break
8508 if {$r1 == $r2} {
8509 selectline $r1 1
8510 break
8512 if {$r1 > $r2} {
8513 set l1 [replace_by_kids $l1 $r1]
8514 } else {
8515 set l2 [replace_by_kids $l2 $r2]
8518 #set t2 [clock clicks -milliseconds]
8519 #puts "took [expr {$t2-$t1}]ms"
8522 proc compare_commits {} {
8523 global markedid rowmenuid curview children
8525 if {![info exists markedid]} return
8526 if {![commitinview $markedid $curview]} return
8527 addtohistory [list do_cmp_commits $markedid $rowmenuid]
8528 do_cmp_commits $markedid $rowmenuid
8531 proc getpatchid {id} {
8532 global patchids
8534 if {![info exists patchids($id)]} {
8535 set cmd [diffcmd [list $id] {-p --root}]
8536 # trim off the initial "|"
8537 set cmd [lrange $cmd 1 end]
8538 if {[catch {
8539 set x [eval exec $cmd | git patch-id]
8540 set patchids($id) [lindex $x 0]
8541 }]} {
8542 set patchids($id) "error"
8545 return $patchids($id)
8548 proc do_cmp_commits {a b} {
8549 global ctext curview parents children patchids commitinfo
8551 $ctext conf -state normal
8552 clear_ctext
8553 init_flist {}
8554 for {set i 0} {$i < 100} {incr i} {
8555 set skipa 0
8556 set skipb 0
8557 if {[llength $parents($curview,$a)] > 1} {
8558 appendshortlink $a [mc "Skipping merge commit "] "\n"
8559 set skipa 1
8560 } else {
8561 set patcha [getpatchid $a]
8563 if {[llength $parents($curview,$b)] > 1} {
8564 appendshortlink $b [mc "Skipping merge commit "] "\n"
8565 set skipb 1
8566 } else {
8567 set patchb [getpatchid $b]
8569 if {!$skipa && !$skipb} {
8570 set heada [lindex $commitinfo($a) 0]
8571 set headb [lindex $commitinfo($b) 0]
8572 if {$patcha eq "error"} {
8573 appendshortlink $a [mc "Error getting patch ID for "] \
8574 [mc " - stopping\n"]
8575 break
8577 if {$patchb eq "error"} {
8578 appendshortlink $b [mc "Error getting patch ID for "] \
8579 [mc " - stopping\n"]
8580 break
8582 if {$patcha eq $patchb} {
8583 if {$heada eq $headb} {
8584 appendshortlink $a [mc "Commit "]
8585 appendshortlink $b " == " " $heada\n"
8586 } else {
8587 appendshortlink $a [mc "Commit "] " $heada\n"
8588 appendshortlink $b [mc " is the same patch as\n "] \
8589 " $headb\n"
8591 set skipa 1
8592 set skipb 1
8593 } else {
8594 $ctext insert end "\n"
8595 appendshortlink $a [mc "Commit "] " $heada\n"
8596 appendshortlink $b [mc " differs from\n "] \
8597 " $headb\n"
8598 $ctext insert end [mc "Diff of commits:\n\n"]
8599 $ctext conf -state disabled
8600 update
8601 diffcommits $a $b
8602 return
8605 if {$skipa} {
8606 set kids [real_children $curview,$a]
8607 if {[llength $kids] != 1} {
8608 $ctext insert end "\n"
8609 appendshortlink $a [mc "Commit "] \
8610 [mc " has %s children - stopping\n" [llength $kids]]
8611 break
8613 set a [lindex $kids 0]
8615 if {$skipb} {
8616 set kids [real_children $curview,$b]
8617 if {[llength $kids] != 1} {
8618 appendshortlink $b [mc "Commit "] \
8619 [mc " has %s children - stopping\n" [llength $kids]]
8620 break
8622 set b [lindex $kids 0]
8625 $ctext conf -state disabled
8628 proc diffcommits {a b} {
8629 global diffcontext diffids blobdifffd diffinhdr currdiffsubmod
8631 set tmpdir [gitknewtmpdir]
8632 set fna [file join $tmpdir "commit-[string range $a 0 7]"]
8633 set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
8634 if {[catch {
8635 exec git diff-tree -p --pretty $a >$fna
8636 exec git diff-tree -p --pretty $b >$fnb
8637 } err]} {
8638 error_popup [mc "Error writing commit to file: %s" $err]
8639 return
8641 if {[catch {
8642 set fd [open "| diff -U$diffcontext $fna $fnb" r]
8643 } err]} {
8644 error_popup [mc "Error diffing commits: %s" $err]
8645 return
8647 set diffids [list commits $a $b]
8648 set blobdifffd($diffids) $fd
8649 set diffinhdr 0
8650 set currdiffsubmod ""
8651 filerun $fd [list getblobdiffline $fd $diffids]
8654 proc diffvssel {dirn} {
8655 global rowmenuid selectedline
8657 if {$selectedline eq {}} return
8658 if {$dirn} {
8659 set oldid [commitonrow $selectedline]
8660 set newid $rowmenuid
8661 } else {
8662 set oldid $rowmenuid
8663 set newid [commitonrow $selectedline]
8665 addtohistory [list doseldiff $oldid $newid] savectextpos
8666 doseldiff $oldid $newid
8669 proc doseldiff {oldid newid} {
8670 global ctext
8671 global commitinfo
8673 $ctext conf -state normal
8674 clear_ctext
8675 init_flist [mc "Top"]
8676 $ctext insert end "[mc "From"] "
8677 $ctext insert end $oldid link0
8678 setlink $oldid link0
8679 $ctext insert end "\n "
8680 $ctext insert end [lindex $commitinfo($oldid) 0]
8681 $ctext insert end "\n\n[mc "To"] "
8682 $ctext insert end $newid link1
8683 setlink $newid link1
8684 $ctext insert end "\n "
8685 $ctext insert end [lindex $commitinfo($newid) 0]
8686 $ctext insert end "\n"
8687 $ctext conf -state disabled
8688 $ctext tag remove found 1.0 end
8689 startdiff [list $oldid $newid]
8692 proc mkpatch {} {
8693 global rowmenuid currentid commitinfo patchtop patchnum NS
8695 if {![info exists currentid]} return
8696 set oldid $currentid
8697 set oldhead [lindex $commitinfo($oldid) 0]
8698 set newid $rowmenuid
8699 set newhead [lindex $commitinfo($newid) 0]
8700 set top .patch
8701 set patchtop $top
8702 catch {destroy $top}
8703 ttk_toplevel $top
8704 make_transient $top .
8705 ${NS}::label $top.title -text [mc "Generate patch"]
8706 grid $top.title - -pady 10
8707 ${NS}::label $top.from -text [mc "From:"]
8708 ${NS}::entry $top.fromsha1 -width 40
8709 $top.fromsha1 insert 0 $oldid
8710 $top.fromsha1 conf -state readonly
8711 grid $top.from $top.fromsha1 -sticky w
8712 ${NS}::entry $top.fromhead -width 60
8713 $top.fromhead insert 0 $oldhead
8714 $top.fromhead conf -state readonly
8715 grid x $top.fromhead -sticky w
8716 ${NS}::label $top.to -text [mc "To:"]
8717 ${NS}::entry $top.tosha1 -width 40
8718 $top.tosha1 insert 0 $newid
8719 $top.tosha1 conf -state readonly
8720 grid $top.to $top.tosha1 -sticky w
8721 ${NS}::entry $top.tohead -width 60
8722 $top.tohead insert 0 $newhead
8723 $top.tohead conf -state readonly
8724 grid x $top.tohead -sticky w
8725 ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
8726 grid $top.rev x -pady 10 -padx 5
8727 ${NS}::label $top.flab -text [mc "Output file:"]
8728 ${NS}::entry $top.fname -width 60
8729 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
8730 incr patchnum
8731 grid $top.flab $top.fname -sticky w
8732 ${NS}::frame $top.buts
8733 ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
8734 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
8735 bind $top <Key-Return> mkpatchgo
8736 bind $top <Key-Escape> mkpatchcan
8737 grid $top.buts.gen $top.buts.can
8738 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8739 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8740 grid $top.buts - -pady 10 -sticky ew
8741 focus $top.fname
8744 proc mkpatchrev {} {
8745 global patchtop
8747 set oldid [$patchtop.fromsha1 get]
8748 set oldhead [$patchtop.fromhead get]
8749 set newid [$patchtop.tosha1 get]
8750 set newhead [$patchtop.tohead get]
8751 foreach e [list fromsha1 fromhead tosha1 tohead] \
8752 v [list $newid $newhead $oldid $oldhead] {
8753 $patchtop.$e conf -state normal
8754 $patchtop.$e delete 0 end
8755 $patchtop.$e insert 0 $v
8756 $patchtop.$e conf -state readonly
8760 proc mkpatchgo {} {
8761 global patchtop nullid nullid2
8763 set oldid [$patchtop.fromsha1 get]
8764 set newid [$patchtop.tosha1 get]
8765 set fname [$patchtop.fname get]
8766 set cmd [diffcmd [list $oldid $newid] -p]
8767 # trim off the initial "|"
8768 set cmd [lrange $cmd 1 end]
8769 lappend cmd >$fname &
8770 if {[catch {eval exec $cmd} err]} {
8771 error_popup "[mc "Error creating patch:"] $err" $patchtop
8773 catch {destroy $patchtop}
8774 unset patchtop
8777 proc mkpatchcan {} {
8778 global patchtop
8780 catch {destroy $patchtop}
8781 unset patchtop
8784 proc mktag {} {
8785 global rowmenuid mktagtop commitinfo NS
8787 set top .maketag
8788 set mktagtop $top
8789 catch {destroy $top}
8790 ttk_toplevel $top
8791 make_transient $top .
8792 ${NS}::label $top.title -text [mc "Create tag"]
8793 grid $top.title - -pady 10
8794 ${NS}::label $top.id -text [mc "ID:"]
8795 ${NS}::entry $top.sha1 -width 40
8796 $top.sha1 insert 0 $rowmenuid
8797 $top.sha1 conf -state readonly
8798 grid $top.id $top.sha1 -sticky w
8799 ${NS}::entry $top.head -width 60
8800 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8801 $top.head conf -state readonly
8802 grid x $top.head -sticky w
8803 ${NS}::label $top.tlab -text [mc "Tag name:"]
8804 ${NS}::entry $top.tag -width 60
8805 grid $top.tlab $top.tag -sticky w
8806 ${NS}::label $top.op -text [mc "Tag message is optional"]
8807 grid $top.op -columnspan 2 -sticky we
8808 ${NS}::label $top.mlab -text [mc "Tag message:"]
8809 ${NS}::entry $top.msg -width 60
8810 grid $top.mlab $top.msg -sticky w
8811 ${NS}::frame $top.buts
8812 ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
8813 ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
8814 bind $top <Key-Return> mktaggo
8815 bind $top <Key-Escape> mktagcan
8816 grid $top.buts.gen $top.buts.can
8817 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8818 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8819 grid $top.buts - -pady 10 -sticky ew
8820 focus $top.tag
8823 proc domktag {} {
8824 global mktagtop env tagids idtags
8826 set id [$mktagtop.sha1 get]
8827 set tag [$mktagtop.tag get]
8828 set msg [$mktagtop.msg get]
8829 if {$tag == {}} {
8830 error_popup [mc "No tag name specified"] $mktagtop
8831 return 0
8833 if {[info exists tagids($tag)]} {
8834 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8835 return 0
8837 if {[catch {
8838 if {$msg != {}} {
8839 exec git tag -a -m $msg $tag $id
8840 } else {
8841 exec git tag $tag $id
8843 } err]} {
8844 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8845 return 0
8848 set tagids($tag) $id
8849 lappend idtags($id) $tag
8850 redrawtags $id
8851 addedtag $id
8852 dispneartags 0
8853 run refill_reflist
8854 return 1
8857 proc redrawtags {id} {
8858 global canv linehtag idpos currentid curview cmitlisted markedid
8859 global canvxmax iddrawn circleitem mainheadid circlecolors
8861 if {![commitinview $id $curview]} return
8862 if {![info exists iddrawn($id)]} return
8863 set row [rowofcommit $id]
8864 if {$id eq $mainheadid} {
8865 set ofill yellow
8866 } else {
8867 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8869 $canv itemconf $circleitem($row) -fill $ofill
8870 $canv delete tag.$id
8871 set xt [eval drawtags $id $idpos($id)]
8872 $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
8873 set text [$canv itemcget $linehtag($id) -text]
8874 set font [$canv itemcget $linehtag($id) -font]
8875 set xr [expr {$xt + [font measure $font $text]}]
8876 if {$xr > $canvxmax} {
8877 set canvxmax $xr
8878 setcanvscroll
8880 if {[info exists currentid] && $currentid == $id} {
8881 make_secsel $id
8883 if {[info exists markedid] && $markedid eq $id} {
8884 make_idmark $id
8888 proc mktagcan {} {
8889 global mktagtop
8891 catch {destroy $mktagtop}
8892 unset mktagtop
8895 proc mktaggo {} {
8896 if {![domktag]} return
8897 mktagcan
8900 proc writecommit {} {
8901 global rowmenuid wrcomtop commitinfo wrcomcmd NS
8903 set top .writecommit
8904 set wrcomtop $top
8905 catch {destroy $top}
8906 ttk_toplevel $top
8907 make_transient $top .
8908 ${NS}::label $top.title -text [mc "Write commit to file"]
8909 grid $top.title - -pady 10
8910 ${NS}::label $top.id -text [mc "ID:"]
8911 ${NS}::entry $top.sha1 -width 40
8912 $top.sha1 insert 0 $rowmenuid
8913 $top.sha1 conf -state readonly
8914 grid $top.id $top.sha1 -sticky w
8915 ${NS}::entry $top.head -width 60
8916 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8917 $top.head conf -state readonly
8918 grid x $top.head -sticky w
8919 ${NS}::label $top.clab -text [mc "Command:"]
8920 ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
8921 grid $top.clab $top.cmd -sticky w -pady 10
8922 ${NS}::label $top.flab -text [mc "Output file:"]
8923 ${NS}::entry $top.fname -width 60
8924 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8925 grid $top.flab $top.fname -sticky w
8926 ${NS}::frame $top.buts
8927 ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
8928 ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8929 bind $top <Key-Return> wrcomgo
8930 bind $top <Key-Escape> wrcomcan
8931 grid $top.buts.gen $top.buts.can
8932 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8933 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8934 grid $top.buts - -pady 10 -sticky ew
8935 focus $top.fname
8938 proc wrcomgo {} {
8939 global wrcomtop
8941 set id [$wrcomtop.sha1 get]
8942 set cmd "echo $id | [$wrcomtop.cmd get]"
8943 set fname [$wrcomtop.fname get]
8944 if {[catch {exec sh -c $cmd >$fname &} err]} {
8945 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8947 catch {destroy $wrcomtop}
8948 unset wrcomtop
8951 proc wrcomcan {} {
8952 global wrcomtop
8954 catch {destroy $wrcomtop}
8955 unset wrcomtop
8958 proc mkbranch {} {
8959 global rowmenuid mkbrtop NS
8961 set top .makebranch
8962 catch {destroy $top}
8963 ttk_toplevel $top
8964 make_transient $top .
8965 ${NS}::label $top.title -text [mc "Create new branch"]
8966 grid $top.title - -pady 10
8967 ${NS}::label $top.id -text [mc "ID:"]
8968 ${NS}::entry $top.sha1 -width 40
8969 $top.sha1 insert 0 $rowmenuid
8970 $top.sha1 conf -state readonly
8971 grid $top.id $top.sha1 -sticky w
8972 ${NS}::label $top.nlab -text [mc "Name:"]
8973 ${NS}::entry $top.name -width 40
8974 grid $top.nlab $top.name -sticky w
8975 ${NS}::frame $top.buts
8976 ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8977 ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8978 bind $top <Key-Return> [list mkbrgo $top]
8979 bind $top <Key-Escape> "catch {destroy $top}"
8980 grid $top.buts.go $top.buts.can
8981 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8982 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8983 grid $top.buts - -pady 10 -sticky ew
8984 focus $top.name
8987 proc mkbrgo {top} {
8988 global headids idheads
8990 set name [$top.name get]
8991 set id [$top.sha1 get]
8992 set cmdargs {}
8993 set old_id {}
8994 if {$name eq {}} {
8995 error_popup [mc "Please specify a name for the new branch"] $top
8996 return
8998 if {[info exists headids($name)]} {
8999 if {![confirm_popup [mc \
9000 "Branch '%s' already exists. Overwrite?" $name] $top]} {
9001 return
9003 set old_id $headids($name)
9004 lappend cmdargs -f
9006 catch {destroy $top}
9007 lappend cmdargs $name $id
9008 nowbusy newbranch
9009 update
9010 if {[catch {
9011 eval exec git branch $cmdargs
9012 } err]} {
9013 notbusy newbranch
9014 error_popup $err
9015 } else {
9016 notbusy newbranch
9017 if {$old_id ne {}} {
9018 movehead $id $name
9019 movedhead $id $name
9020 redrawtags $old_id
9021 redrawtags $id
9022 } else {
9023 set headids($name) $id
9024 lappend idheads($id) $name
9025 addedhead $id $name
9026 redrawtags $id
9028 dispneartags 0
9029 run refill_reflist
9033 proc exec_citool {tool_args {baseid {}}} {
9034 global commitinfo env
9036 set save_env [array get env GIT_AUTHOR_*]
9038 if {$baseid ne {}} {
9039 if {![info exists commitinfo($baseid)]} {
9040 getcommit $baseid
9042 set author [lindex $commitinfo($baseid) 1]
9043 set date [lindex $commitinfo($baseid) 2]
9044 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
9045 $author author name email]
9046 && $date ne {}} {
9047 set env(GIT_AUTHOR_NAME) $name
9048 set env(GIT_AUTHOR_EMAIL) $email
9049 set env(GIT_AUTHOR_DATE) $date
9053 eval exec git citool $tool_args &
9055 array unset env GIT_AUTHOR_*
9056 array set env $save_env
9059 proc cherrypick {} {
9060 global rowmenuid curview
9061 global mainhead mainheadid
9063 set oldhead [exec git rev-parse HEAD]
9064 set dheads [descheads $rowmenuid]
9065 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
9066 set ok [confirm_popup [mc "Commit %s is already\
9067 included in branch %s -- really re-apply it?" \
9068 [string range $rowmenuid 0 7] $mainhead]]
9069 if {!$ok} return
9071 nowbusy cherrypick [mc "Cherry-picking"]
9072 update
9073 # Unfortunately git-cherry-pick writes stuff to stderr even when
9074 # no error occurs, and exec takes that as an indication of error...
9075 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
9076 notbusy cherrypick
9077 if {[regexp -line \
9078 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
9079 $err msg fname]} {
9080 error_popup [mc "Cherry-pick failed because of local changes\
9081 to file '%s'.\nPlease commit, reset or stash\
9082 your changes and try again." $fname]
9083 } elseif {[regexp -line \
9084 {^(CONFLICT \(.*\):|Automatic cherry-pick failed|error: could not apply)} \
9085 $err]} {
9086 if {[confirm_popup [mc "Cherry-pick failed because of merge\
9087 conflict.\nDo you wish to run git citool to\
9088 resolve it?"]]} {
9089 # Force citool to read MERGE_MSG
9090 file delete [file join [gitdir] "GITGUI_MSG"]
9091 exec_citool {} $rowmenuid
9093 } else {
9094 error_popup $err
9096 run updatecommits
9097 return
9099 set newhead [exec git rev-parse HEAD]
9100 if {$newhead eq $oldhead} {
9101 notbusy cherrypick
9102 error_popup [mc "No changes committed"]
9103 return
9105 addnewchild $newhead $oldhead
9106 if {[commitinview $oldhead $curview]} {
9107 # XXX this isn't right if we have a path limit...
9108 insertrow $newhead $oldhead $curview
9109 if {$mainhead ne {}} {
9110 movehead $newhead $mainhead
9111 movedhead $newhead $mainhead
9113 set mainheadid $newhead
9114 redrawtags $oldhead
9115 redrawtags $newhead
9116 selbyid $newhead
9118 notbusy cherrypick
9121 proc resethead {} {
9122 global mainhead rowmenuid confirm_ok resettype NS
9124 set confirm_ok 0
9125 set w ".confirmreset"
9126 ttk_toplevel $w
9127 make_transient $w .
9128 wm title $w [mc "Confirm reset"]
9129 ${NS}::label $w.m -text \
9130 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
9131 pack $w.m -side top -fill x -padx 20 -pady 20
9132 ${NS}::labelframe $w.f -text [mc "Reset type:"]
9133 set resettype mixed
9134 ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
9135 -text [mc "Soft: Leave working tree and index untouched"]
9136 grid $w.f.soft -sticky w
9137 ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
9138 -text [mc "Mixed: Leave working tree untouched, reset index"]
9139 grid $w.f.mixed -sticky w
9140 ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
9141 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
9142 grid $w.f.hard -sticky w
9143 pack $w.f -side top -fill x -padx 4
9144 ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
9145 pack $w.ok -side left -fill x -padx 20 -pady 20
9146 ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
9147 bind $w <Key-Escape> [list destroy $w]
9148 pack $w.cancel -side right -fill x -padx 20 -pady 20
9149 bind $w <Visibility> "grab $w; focus $w"
9150 tkwait window $w
9151 if {!$confirm_ok} return
9152 if {[catch {set fd [open \
9153 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
9154 error_popup $err
9155 } else {
9156 dohidelocalchanges
9157 filerun $fd [list readresetstat $fd]
9158 nowbusy reset [mc "Resetting"]
9159 selbyid $rowmenuid
9163 proc readresetstat {fd} {
9164 global mainhead mainheadid showlocalchanges rprogcoord
9166 if {[gets $fd line] >= 0} {
9167 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9168 set rprogcoord [expr {1.0 * $m / $n}]
9169 adjustprogress
9171 return 1
9173 set rprogcoord 0
9174 adjustprogress
9175 notbusy reset
9176 if {[catch {close $fd} err]} {
9177 error_popup $err
9179 set oldhead $mainheadid
9180 set newhead [exec git rev-parse HEAD]
9181 if {$newhead ne $oldhead} {
9182 movehead $newhead $mainhead
9183 movedhead $newhead $mainhead
9184 set mainheadid $newhead
9185 redrawtags $oldhead
9186 redrawtags $newhead
9188 if {$showlocalchanges} {
9189 doshowlocalchanges
9191 return 0
9194 # context menu for a head
9195 proc headmenu {x y id head} {
9196 global headmenuid headmenuhead headctxmenu mainhead
9198 stopfinding
9199 set headmenuid $id
9200 set headmenuhead $head
9201 set state normal
9202 if {[string match "remotes/*" $head]} {
9203 set state disabled
9205 if {$head eq $mainhead} {
9206 set state disabled
9208 $headctxmenu entryconfigure 0 -state $state
9209 $headctxmenu entryconfigure 1 -state $state
9210 tk_popup $headctxmenu $x $y
9213 proc cobranch {} {
9214 global headmenuid headmenuhead headids
9215 global showlocalchanges
9217 # check the tree is clean first??
9218 nowbusy checkout [mc "Checking out"]
9219 update
9220 dohidelocalchanges
9221 if {[catch {
9222 set fd [open [list | git checkout $headmenuhead 2>@1] r]
9223 } err]} {
9224 notbusy checkout
9225 error_popup $err
9226 if {$showlocalchanges} {
9227 dodiffindex
9229 } else {
9230 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
9234 proc readcheckoutstat {fd newhead newheadid} {
9235 global mainhead mainheadid headids showlocalchanges progresscoords
9236 global viewmainheadid curview
9238 if {[gets $fd line] >= 0} {
9239 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
9240 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
9241 adjustprogress
9243 return 1
9245 set progresscoords {0 0}
9246 adjustprogress
9247 notbusy checkout
9248 if {[catch {close $fd} err]} {
9249 error_popup $err
9251 set oldmainid $mainheadid
9252 set mainhead $newhead
9253 set mainheadid $newheadid
9254 set viewmainheadid($curview) $newheadid
9255 redrawtags $oldmainid
9256 redrawtags $newheadid
9257 selbyid $newheadid
9258 if {$showlocalchanges} {
9259 dodiffindex
9263 proc rmbranch {} {
9264 global headmenuid headmenuhead mainhead
9265 global idheads
9267 set head $headmenuhead
9268 set id $headmenuid
9269 # this check shouldn't be needed any more...
9270 if {$head eq $mainhead} {
9271 error_popup [mc "Cannot delete the currently checked-out branch"]
9272 return
9274 set dheads [descheads $id]
9275 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
9276 # the stuff on this branch isn't on any other branch
9277 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
9278 branch.\nReally delete branch %s?" $head $head]]} return
9280 nowbusy rmbranch
9281 update
9282 if {[catch {exec git branch -D $head} err]} {
9283 notbusy rmbranch
9284 error_popup $err
9285 return
9287 removehead $id $head
9288 removedhead $id $head
9289 redrawtags $id
9290 notbusy rmbranch
9291 dispneartags 0
9292 run refill_reflist
9295 # Display a list of tags and heads
9296 proc showrefs {} {
9297 global showrefstop bgcolor fgcolor selectbgcolor NS
9298 global bglist fglist reflistfilter reflist maincursor
9300 set top .showrefs
9301 set showrefstop $top
9302 if {[winfo exists $top]} {
9303 raise $top
9304 refill_reflist
9305 return
9307 ttk_toplevel $top
9308 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
9309 make_transient $top .
9310 text $top.list -background $bgcolor -foreground $fgcolor \
9311 -selectbackground $selectbgcolor -font mainfont \
9312 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
9313 -width 30 -height 20 -cursor $maincursor \
9314 -spacing1 1 -spacing3 1 -state disabled
9315 $top.list tag configure highlight -background $selectbgcolor
9316 lappend bglist $top.list
9317 lappend fglist $top.list
9318 ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
9319 ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
9320 grid $top.list $top.ysb -sticky nsew
9321 grid $top.xsb x -sticky ew
9322 ${NS}::frame $top.f
9323 ${NS}::label $top.f.l -text "[mc "Filter"]: "
9324 ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
9325 set reflistfilter "*"
9326 trace add variable reflistfilter write reflistfilter_change
9327 pack $top.f.e -side right -fill x -expand 1
9328 pack $top.f.l -side left
9329 grid $top.f - -sticky ew -pady 2
9330 ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
9331 bind $top <Key-Escape> [list destroy $top]
9332 grid $top.close -
9333 grid columnconfigure $top 0 -weight 1
9334 grid rowconfigure $top 0 -weight 1
9335 bind $top.list <1> {break}
9336 bind $top.list <B1-Motion> {break}
9337 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
9338 set reflist {}
9339 refill_reflist
9342 proc sel_reflist {w x y} {
9343 global showrefstop reflist headids tagids otherrefids
9345 if {![winfo exists $showrefstop]} return
9346 set l [lindex [split [$w index "@$x,$y"] "."] 0]
9347 set ref [lindex $reflist [expr {$l-1}]]
9348 set n [lindex $ref 0]
9349 switch -- [lindex $ref 1] {
9350 "H" {selbyid $headids($n)}
9351 "T" {selbyid $tagids($n)}
9352 "o" {selbyid $otherrefids($n)}
9354 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
9357 proc unsel_reflist {} {
9358 global showrefstop
9360 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9361 $showrefstop.list tag remove highlight 0.0 end
9364 proc reflistfilter_change {n1 n2 op} {
9365 global reflistfilter
9367 after cancel refill_reflist
9368 after 200 refill_reflist
9371 proc refill_reflist {} {
9372 global reflist reflistfilter showrefstop headids tagids otherrefids
9373 global curview
9375 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
9376 set refs {}
9377 foreach n [array names headids] {
9378 if {[string match $reflistfilter $n]} {
9379 if {[commitinview $headids($n) $curview]} {
9380 lappend refs [list $n H]
9381 } else {
9382 interestedin $headids($n) {run refill_reflist}
9386 foreach n [array names tagids] {
9387 if {[string match $reflistfilter $n]} {
9388 if {[commitinview $tagids($n) $curview]} {
9389 lappend refs [list $n T]
9390 } else {
9391 interestedin $tagids($n) {run refill_reflist}
9395 foreach n [array names otherrefids] {
9396 if {[string match $reflistfilter $n]} {
9397 if {[commitinview $otherrefids($n) $curview]} {
9398 lappend refs [list $n o]
9399 } else {
9400 interestedin $otherrefids($n) {run refill_reflist}
9404 set refs [lsort -index 0 $refs]
9405 if {$refs eq $reflist} return
9407 # Update the contents of $showrefstop.list according to the
9408 # differences between $reflist (old) and $refs (new)
9409 $showrefstop.list conf -state normal
9410 $showrefstop.list insert end "\n"
9411 set i 0
9412 set j 0
9413 while {$i < [llength $reflist] || $j < [llength $refs]} {
9414 if {$i < [llength $reflist]} {
9415 if {$j < [llength $refs]} {
9416 set cmp [string compare [lindex $reflist $i 0] \
9417 [lindex $refs $j 0]]
9418 if {$cmp == 0} {
9419 set cmp [string compare [lindex $reflist $i 1] \
9420 [lindex $refs $j 1]]
9422 } else {
9423 set cmp -1
9425 } else {
9426 set cmp 1
9428 switch -- $cmp {
9429 -1 {
9430 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
9431 incr i
9434 incr i
9435 incr j
9438 set l [expr {$j + 1}]
9439 $showrefstop.list image create $l.0 -align baseline \
9440 -image reficon-[lindex $refs $j 1] -padx 2
9441 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
9442 incr j
9446 set reflist $refs
9447 # delete last newline
9448 $showrefstop.list delete end-2c end-1c
9449 $showrefstop.list conf -state disabled
9452 # Stuff for finding nearby tags
9453 proc getallcommits {} {
9454 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
9455 global idheads idtags idotherrefs allparents tagobjid
9457 if {![info exists allcommits]} {
9458 set nextarc 0
9459 set allcommits 0
9460 set seeds {}
9461 set allcwait 0
9462 set cachedarcs 0
9463 set allccache [file join [gitdir] "gitk.cache"]
9464 if {![catch {
9465 set f [open $allccache r]
9466 set allcwait 1
9467 getcache $f
9468 }]} return
9471 if {$allcwait} {
9472 return
9474 set cmd [list | git rev-list --parents]
9475 set allcupdate [expr {$seeds ne {}}]
9476 if {!$allcupdate} {
9477 set ids "--all"
9478 } else {
9479 set refs [concat [array names idheads] [array names idtags] \
9480 [array names idotherrefs]]
9481 set ids {}
9482 set tagobjs {}
9483 foreach name [array names tagobjid] {
9484 lappend tagobjs $tagobjid($name)
9486 foreach id [lsort -unique $refs] {
9487 if {![info exists allparents($id)] &&
9488 [lsearch -exact $tagobjs $id] < 0} {
9489 lappend ids $id
9492 if {$ids ne {}} {
9493 foreach id $seeds {
9494 lappend ids "^$id"
9498 if {$ids ne {}} {
9499 set cmd [limit_arg_length [concat $cmd $ids]]
9500 set fd [open $cmd r]
9501 fconfigure $fd -blocking 0
9502 incr allcommits
9503 nowbusy allcommits
9504 filerun $fd [list getallclines $fd]
9505 } else {
9506 dispneartags 0
9510 # The maximum command line length for the CreateProcess function is 32767 characters, see
9511 # http://blogs.msdn.com/oldnewthing/archive/2003/12/10/56028.aspx
9512 # Be a little conservative in case Tcl adds some more stuff to the command line we do not
9513 # know about and truncate the command line at a SHA1-boundary below 32000 characters.
9514 proc limit_arg_length {cmd} {
9515 if {[tk windowingsystem] == "win32" &&
9516 [string length $cmd] > 32000} {
9517 set ndx [string last " " $cmd 32000]
9518 if {$ndx != -1} {
9519 return [string range $cmd 0 $ndx]
9522 return $cmd
9525 # Since most commits have 1 parent and 1 child, we group strings of
9526 # such commits into "arcs" joining branch/merge points (BMPs), which
9527 # are commits that either don't have 1 parent or don't have 1 child.
9529 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
9530 # arcout(id) - outgoing arcs for BMP
9531 # arcids(a) - list of IDs on arc including end but not start
9532 # arcstart(a) - BMP ID at start of arc
9533 # arcend(a) - BMP ID at end of arc
9534 # growing(a) - arc a is still growing
9535 # arctags(a) - IDs out of arcids (excluding end) that have tags
9536 # archeads(a) - IDs out of arcids (excluding end) that have heads
9537 # The start of an arc is at the descendent end, so "incoming" means
9538 # coming from descendents, and "outgoing" means going towards ancestors.
9540 proc getallclines {fd} {
9541 global allparents allchildren idtags idheads nextarc
9542 global arcnos arcids arctags arcout arcend arcstart archeads growing
9543 global seeds allcommits cachedarcs allcupdate
9545 set nid 0
9546 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
9547 set id [lindex $line 0]
9548 if {[info exists allparents($id)]} {
9549 # seen it already
9550 continue
9552 set cachedarcs 0
9553 set olds [lrange $line 1 end]
9554 set allparents($id) $olds
9555 if {![info exists allchildren($id)]} {
9556 set allchildren($id) {}
9557 set arcnos($id) {}
9558 lappend seeds $id
9559 } else {
9560 set a $arcnos($id)
9561 if {[llength $olds] == 1 && [llength $a] == 1} {
9562 lappend arcids($a) $id
9563 if {[info exists idtags($id)]} {
9564 lappend arctags($a) $id
9566 if {[info exists idheads($id)]} {
9567 lappend archeads($a) $id
9569 if {[info exists allparents($olds)]} {
9570 # seen parent already
9571 if {![info exists arcout($olds)]} {
9572 splitarc $olds
9574 lappend arcids($a) $olds
9575 set arcend($a) $olds
9576 unset growing($a)
9578 lappend allchildren($olds) $id
9579 lappend arcnos($olds) $a
9580 continue
9583 foreach a $arcnos($id) {
9584 lappend arcids($a) $id
9585 set arcend($a) $id
9586 unset growing($a)
9589 set ao {}
9590 foreach p $olds {
9591 lappend allchildren($p) $id
9592 set a [incr nextarc]
9593 set arcstart($a) $id
9594 set archeads($a) {}
9595 set arctags($a) {}
9596 set archeads($a) {}
9597 set arcids($a) {}
9598 lappend ao $a
9599 set growing($a) 1
9600 if {[info exists allparents($p)]} {
9601 # seen it already, may need to make a new branch
9602 if {![info exists arcout($p)]} {
9603 splitarc $p
9605 lappend arcids($a) $p
9606 set arcend($a) $p
9607 unset growing($a)
9609 lappend arcnos($p) $a
9611 set arcout($id) $ao
9613 if {$nid > 0} {
9614 global cached_dheads cached_dtags cached_atags
9615 catch {unset cached_dheads}
9616 catch {unset cached_dtags}
9617 catch {unset cached_atags}
9619 if {![eof $fd]} {
9620 return [expr {$nid >= 1000? 2: 1}]
9622 set cacheok 1
9623 if {[catch {
9624 fconfigure $fd -blocking 1
9625 close $fd
9626 } err]} {
9627 # got an error reading the list of commits
9628 # if we were updating, try rereading the whole thing again
9629 if {$allcupdate} {
9630 incr allcommits -1
9631 dropcache $err
9632 return
9634 error_popup "[mc "Error reading commit topology information;\
9635 branch and preceding/following tag information\
9636 will be incomplete."]\n($err)"
9637 set cacheok 0
9639 if {[incr allcommits -1] == 0} {
9640 notbusy allcommits
9641 if {$cacheok} {
9642 run savecache
9645 dispneartags 0
9646 return 0
9649 proc recalcarc {a} {
9650 global arctags archeads arcids idtags idheads
9652 set at {}
9653 set ah {}
9654 foreach id [lrange $arcids($a) 0 end-1] {
9655 if {[info exists idtags($id)]} {
9656 lappend at $id
9658 if {[info exists idheads($id)]} {
9659 lappend ah $id
9662 set arctags($a) $at
9663 set archeads($a) $ah
9666 proc splitarc {p} {
9667 global arcnos arcids nextarc arctags archeads idtags idheads
9668 global arcstart arcend arcout allparents growing
9670 set a $arcnos($p)
9671 if {[llength $a] != 1} {
9672 puts "oops splitarc called but [llength $a] arcs already"
9673 return
9675 set a [lindex $a 0]
9676 set i [lsearch -exact $arcids($a) $p]
9677 if {$i < 0} {
9678 puts "oops splitarc $p not in arc $a"
9679 return
9681 set na [incr nextarc]
9682 if {[info exists arcend($a)]} {
9683 set arcend($na) $arcend($a)
9684 } else {
9685 set l [lindex $allparents([lindex $arcids($a) end]) 0]
9686 set j [lsearch -exact $arcnos($l) $a]
9687 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
9689 set tail [lrange $arcids($a) [expr {$i+1}] end]
9690 set arcids($a) [lrange $arcids($a) 0 $i]
9691 set arcend($a) $p
9692 set arcstart($na) $p
9693 set arcout($p) $na
9694 set arcids($na) $tail
9695 if {[info exists growing($a)]} {
9696 set growing($na) 1
9697 unset growing($a)
9700 foreach id $tail {
9701 if {[llength $arcnos($id)] == 1} {
9702 set arcnos($id) $na
9703 } else {
9704 set j [lsearch -exact $arcnos($id) $a]
9705 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
9709 # reconstruct tags and heads lists
9710 if {$arctags($a) ne {} || $archeads($a) ne {}} {
9711 recalcarc $a
9712 recalcarc $na
9713 } else {
9714 set arctags($na) {}
9715 set archeads($na) {}
9719 # Update things for a new commit added that is a child of one
9720 # existing commit. Used when cherry-picking.
9721 proc addnewchild {id p} {
9722 global allparents allchildren idtags nextarc
9723 global arcnos arcids arctags arcout arcend arcstart archeads growing
9724 global seeds allcommits
9726 if {![info exists allcommits] || ![info exists arcnos($p)]} return
9727 set allparents($id) [list $p]
9728 set allchildren($id) {}
9729 set arcnos($id) {}
9730 lappend seeds $id
9731 lappend allchildren($p) $id
9732 set a [incr nextarc]
9733 set arcstart($a) $id
9734 set archeads($a) {}
9735 set arctags($a) {}
9736 set arcids($a) [list $p]
9737 set arcend($a) $p
9738 if {![info exists arcout($p)]} {
9739 splitarc $p
9741 lappend arcnos($p) $a
9742 set arcout($id) [list $a]
9745 # This implements a cache for the topology information.
9746 # The cache saves, for each arc, the start and end of the arc,
9747 # the ids on the arc, and the outgoing arcs from the end.
9748 proc readcache {f} {
9749 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
9750 global idtags idheads allparents cachedarcs possible_seeds seeds growing
9751 global allcwait
9753 set a $nextarc
9754 set lim $cachedarcs
9755 if {$lim - $a > 500} {
9756 set lim [expr {$a + 500}]
9758 if {[catch {
9759 if {$a == $lim} {
9760 # finish reading the cache and setting up arctags, etc.
9761 set line [gets $f]
9762 if {$line ne "1"} {error "bad final version"}
9763 close $f
9764 foreach id [array names idtags] {
9765 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9766 [llength $allparents($id)] == 1} {
9767 set a [lindex $arcnos($id) 0]
9768 if {$arctags($a) eq {}} {
9769 recalcarc $a
9773 foreach id [array names idheads] {
9774 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
9775 [llength $allparents($id)] == 1} {
9776 set a [lindex $arcnos($id) 0]
9777 if {$archeads($a) eq {}} {
9778 recalcarc $a
9782 foreach id [lsort -unique $possible_seeds] {
9783 if {$arcnos($id) eq {}} {
9784 lappend seeds $id
9787 set allcwait 0
9788 } else {
9789 while {[incr a] <= $lim} {
9790 set line [gets $f]
9791 if {[llength $line] != 3} {error "bad line"}
9792 set s [lindex $line 0]
9793 set arcstart($a) $s
9794 lappend arcout($s) $a
9795 if {![info exists arcnos($s)]} {
9796 lappend possible_seeds $s
9797 set arcnos($s) {}
9799 set e [lindex $line 1]
9800 if {$e eq {}} {
9801 set growing($a) 1
9802 } else {
9803 set arcend($a) $e
9804 if {![info exists arcout($e)]} {
9805 set arcout($e) {}
9808 set arcids($a) [lindex $line 2]
9809 foreach id $arcids($a) {
9810 lappend allparents($s) $id
9811 set s $id
9812 lappend arcnos($id) $a
9814 if {![info exists allparents($s)]} {
9815 set allparents($s) {}
9817 set arctags($a) {}
9818 set archeads($a) {}
9820 set nextarc [expr {$a - 1}]
9822 } err]} {
9823 dropcache $err
9824 return 0
9826 if {!$allcwait} {
9827 getallcommits
9829 return $allcwait
9832 proc getcache {f} {
9833 global nextarc cachedarcs possible_seeds
9835 if {[catch {
9836 set line [gets $f]
9837 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9838 # make sure it's an integer
9839 set cachedarcs [expr {int([lindex $line 1])}]
9840 if {$cachedarcs < 0} {error "bad number of arcs"}
9841 set nextarc 0
9842 set possible_seeds {}
9843 run readcache $f
9844 } err]} {
9845 dropcache $err
9847 return 0
9850 proc dropcache {err} {
9851 global allcwait nextarc cachedarcs seeds
9853 #puts "dropping cache ($err)"
9854 foreach v {arcnos arcout arcids arcstart arcend growing \
9855 arctags archeads allparents allchildren} {
9856 global $v
9857 catch {unset $v}
9859 set allcwait 0
9860 set nextarc 0
9861 set cachedarcs 0
9862 set seeds {}
9863 getallcommits
9866 proc writecache {f} {
9867 global cachearc cachedarcs allccache
9868 global arcstart arcend arcnos arcids arcout
9870 set a $cachearc
9871 set lim $cachedarcs
9872 if {$lim - $a > 1000} {
9873 set lim [expr {$a + 1000}]
9875 if {[catch {
9876 while {[incr a] <= $lim} {
9877 if {[info exists arcend($a)]} {
9878 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9879 } else {
9880 puts $f [list $arcstart($a) {} $arcids($a)]
9883 } err]} {
9884 catch {close $f}
9885 catch {file delete $allccache}
9886 #puts "writing cache failed ($err)"
9887 return 0
9889 set cachearc [expr {$a - 1}]
9890 if {$a > $cachedarcs} {
9891 puts $f "1"
9892 close $f
9893 return 0
9895 return 1
9898 proc savecache {} {
9899 global nextarc cachedarcs cachearc allccache
9901 if {$nextarc == $cachedarcs} return
9902 set cachearc 0
9903 set cachedarcs $nextarc
9904 catch {
9905 set f [open $allccache w]
9906 puts $f [list 1 $cachedarcs]
9907 run writecache $f
9911 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9912 # or 0 if neither is true.
9913 proc anc_or_desc {a b} {
9914 global arcout arcstart arcend arcnos cached_isanc
9916 if {$arcnos($a) eq $arcnos($b)} {
9917 # Both are on the same arc(s); either both are the same BMP,
9918 # or if one is not a BMP, the other is also not a BMP or is
9919 # the BMP at end of the arc (and it only has 1 incoming arc).
9920 # Or both can be BMPs with no incoming arcs.
9921 if {$a eq $b || $arcnos($a) eq {}} {
9922 return 0
9924 # assert {[llength $arcnos($a)] == 1}
9925 set arc [lindex $arcnos($a) 0]
9926 set i [lsearch -exact $arcids($arc) $a]
9927 set j [lsearch -exact $arcids($arc) $b]
9928 if {$i < 0 || $i > $j} {
9929 return 1
9930 } else {
9931 return -1
9935 if {![info exists arcout($a)]} {
9936 set arc [lindex $arcnos($a) 0]
9937 if {[info exists arcend($arc)]} {
9938 set aend $arcend($arc)
9939 } else {
9940 set aend {}
9942 set a $arcstart($arc)
9943 } else {
9944 set aend $a
9946 if {![info exists arcout($b)]} {
9947 set arc [lindex $arcnos($b) 0]
9948 if {[info exists arcend($arc)]} {
9949 set bend $arcend($arc)
9950 } else {
9951 set bend {}
9953 set b $arcstart($arc)
9954 } else {
9955 set bend $b
9957 if {$a eq $bend} {
9958 return 1
9960 if {$b eq $aend} {
9961 return -1
9963 if {[info exists cached_isanc($a,$bend)]} {
9964 if {$cached_isanc($a,$bend)} {
9965 return 1
9968 if {[info exists cached_isanc($b,$aend)]} {
9969 if {$cached_isanc($b,$aend)} {
9970 return -1
9972 if {[info exists cached_isanc($a,$bend)]} {
9973 return 0
9977 set todo [list $a $b]
9978 set anc($a) a
9979 set anc($b) b
9980 for {set i 0} {$i < [llength $todo]} {incr i} {
9981 set x [lindex $todo $i]
9982 if {$anc($x) eq {}} {
9983 continue
9985 foreach arc $arcnos($x) {
9986 set xd $arcstart($arc)
9987 if {$xd eq $bend} {
9988 set cached_isanc($a,$bend) 1
9989 set cached_isanc($b,$aend) 0
9990 return 1
9991 } elseif {$xd eq $aend} {
9992 set cached_isanc($b,$aend) 1
9993 set cached_isanc($a,$bend) 0
9994 return -1
9996 if {![info exists anc($xd)]} {
9997 set anc($xd) $anc($x)
9998 lappend todo $xd
9999 } elseif {$anc($xd) ne $anc($x)} {
10000 set anc($xd) {}
10004 set cached_isanc($a,$bend) 0
10005 set cached_isanc($b,$aend) 0
10006 return 0
10009 # This identifies whether $desc has an ancestor that is
10010 # a growing tip of the graph and which is not an ancestor of $anc
10011 # and returns 0 if so and 1 if not.
10012 # If we subsequently discover a tag on such a growing tip, and that
10013 # turns out to be a descendent of $anc (which it could, since we
10014 # don't necessarily see children before parents), then $desc
10015 # isn't a good choice to display as a descendent tag of
10016 # $anc (since it is the descendent of another tag which is
10017 # a descendent of $anc). Similarly, $anc isn't a good choice to
10018 # display as a ancestor tag of $desc.
10020 proc is_certain {desc anc} {
10021 global arcnos arcout arcstart arcend growing problems
10023 set certain {}
10024 if {[llength $arcnos($anc)] == 1} {
10025 # tags on the same arc are certain
10026 if {$arcnos($desc) eq $arcnos($anc)} {
10027 return 1
10029 if {![info exists arcout($anc)]} {
10030 # if $anc is partway along an arc, use the start of the arc instead
10031 set a [lindex $arcnos($anc) 0]
10032 set anc $arcstart($a)
10035 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
10036 set x $desc
10037 } else {
10038 set a [lindex $arcnos($desc) 0]
10039 set x $arcend($a)
10041 if {$x == $anc} {
10042 return 1
10044 set anclist [list $x]
10045 set dl($x) 1
10046 set nnh 1
10047 set ngrowanc 0
10048 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
10049 set x [lindex $anclist $i]
10050 if {$dl($x)} {
10051 incr nnh -1
10053 set done($x) 1
10054 foreach a $arcout($x) {
10055 if {[info exists growing($a)]} {
10056 if {![info exists growanc($x)] && $dl($x)} {
10057 set growanc($x) 1
10058 incr ngrowanc
10060 } else {
10061 set y $arcend($a)
10062 if {[info exists dl($y)]} {
10063 if {$dl($y)} {
10064 if {!$dl($x)} {
10065 set dl($y) 0
10066 if {![info exists done($y)]} {
10067 incr nnh -1
10069 if {[info exists growanc($x)]} {
10070 incr ngrowanc -1
10072 set xl [list $y]
10073 for {set k 0} {$k < [llength $xl]} {incr k} {
10074 set z [lindex $xl $k]
10075 foreach c $arcout($z) {
10076 if {[info exists arcend($c)]} {
10077 set v $arcend($c)
10078 if {[info exists dl($v)] && $dl($v)} {
10079 set dl($v) 0
10080 if {![info exists done($v)]} {
10081 incr nnh -1
10083 if {[info exists growanc($v)]} {
10084 incr ngrowanc -1
10086 lappend xl $v
10093 } elseif {$y eq $anc || !$dl($x)} {
10094 set dl($y) 0
10095 lappend anclist $y
10096 } else {
10097 set dl($y) 1
10098 lappend anclist $y
10099 incr nnh
10104 foreach x [array names growanc] {
10105 if {$dl($x)} {
10106 return 0
10108 return 0
10110 return 1
10113 proc validate_arctags {a} {
10114 global arctags idtags
10116 set i -1
10117 set na $arctags($a)
10118 foreach id $arctags($a) {
10119 incr i
10120 if {![info exists idtags($id)]} {
10121 set na [lreplace $na $i $i]
10122 incr i -1
10125 set arctags($a) $na
10128 proc validate_archeads {a} {
10129 global archeads idheads
10131 set i -1
10132 set na $archeads($a)
10133 foreach id $archeads($a) {
10134 incr i
10135 if {![info exists idheads($id)]} {
10136 set na [lreplace $na $i $i]
10137 incr i -1
10140 set archeads($a) $na
10143 # Return the list of IDs that have tags that are descendents of id,
10144 # ignoring IDs that are descendents of IDs already reported.
10145 proc desctags {id} {
10146 global arcnos arcstart arcids arctags idtags allparents
10147 global growing cached_dtags
10149 if {![info exists allparents($id)]} {
10150 return {}
10152 set t1 [clock clicks -milliseconds]
10153 set argid $id
10154 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10155 # part-way along an arc; check that arc first
10156 set a [lindex $arcnos($id) 0]
10157 if {$arctags($a) ne {}} {
10158 validate_arctags $a
10159 set i [lsearch -exact $arcids($a) $id]
10160 set tid {}
10161 foreach t $arctags($a) {
10162 set j [lsearch -exact $arcids($a) $t]
10163 if {$j >= $i} break
10164 set tid $t
10166 if {$tid ne {}} {
10167 return $tid
10170 set id $arcstart($a)
10171 if {[info exists idtags($id)]} {
10172 return $id
10175 if {[info exists cached_dtags($id)]} {
10176 return $cached_dtags($id)
10179 set origid $id
10180 set todo [list $id]
10181 set queued($id) 1
10182 set nc 1
10183 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10184 set id [lindex $todo $i]
10185 set done($id) 1
10186 set ta [info exists hastaggedancestor($id)]
10187 if {!$ta} {
10188 incr nc -1
10190 # ignore tags on starting node
10191 if {!$ta && $i > 0} {
10192 if {[info exists idtags($id)]} {
10193 set tagloc($id) $id
10194 set ta 1
10195 } elseif {[info exists cached_dtags($id)]} {
10196 set tagloc($id) $cached_dtags($id)
10197 set ta 1
10200 foreach a $arcnos($id) {
10201 set d $arcstart($a)
10202 if {!$ta && $arctags($a) ne {}} {
10203 validate_arctags $a
10204 if {$arctags($a) ne {}} {
10205 lappend tagloc($id) [lindex $arctags($a) end]
10208 if {$ta || $arctags($a) ne {}} {
10209 set tomark [list $d]
10210 for {set j 0} {$j < [llength $tomark]} {incr j} {
10211 set dd [lindex $tomark $j]
10212 if {![info exists hastaggedancestor($dd)]} {
10213 if {[info exists done($dd)]} {
10214 foreach b $arcnos($dd) {
10215 lappend tomark $arcstart($b)
10217 if {[info exists tagloc($dd)]} {
10218 unset tagloc($dd)
10220 } elseif {[info exists queued($dd)]} {
10221 incr nc -1
10223 set hastaggedancestor($dd) 1
10227 if {![info exists queued($d)]} {
10228 lappend todo $d
10229 set queued($d) 1
10230 if {![info exists hastaggedancestor($d)]} {
10231 incr nc
10236 set tags {}
10237 foreach id [array names tagloc] {
10238 if {![info exists hastaggedancestor($id)]} {
10239 foreach t $tagloc($id) {
10240 if {[lsearch -exact $tags $t] < 0} {
10241 lappend tags $t
10246 set t2 [clock clicks -milliseconds]
10247 set loopix $i
10249 # remove tags that are descendents of other tags
10250 for {set i 0} {$i < [llength $tags]} {incr i} {
10251 set a [lindex $tags $i]
10252 for {set j 0} {$j < $i} {incr j} {
10253 set b [lindex $tags $j]
10254 set r [anc_or_desc $a $b]
10255 if {$r == 1} {
10256 set tags [lreplace $tags $j $j]
10257 incr j -1
10258 incr i -1
10259 } elseif {$r == -1} {
10260 set tags [lreplace $tags $i $i]
10261 incr i -1
10262 break
10267 if {[array names growing] ne {}} {
10268 # graph isn't finished, need to check if any tag could get
10269 # eclipsed by another tag coming later. Simply ignore any
10270 # tags that could later get eclipsed.
10271 set ctags {}
10272 foreach t $tags {
10273 if {[is_certain $t $origid]} {
10274 lappend ctags $t
10277 if {$tags eq $ctags} {
10278 set cached_dtags($origid) $tags
10279 } else {
10280 set tags $ctags
10282 } else {
10283 set cached_dtags($origid) $tags
10285 set t3 [clock clicks -milliseconds]
10286 if {0 && $t3 - $t1 >= 100} {
10287 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
10288 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10290 return $tags
10293 proc anctags {id} {
10294 global arcnos arcids arcout arcend arctags idtags allparents
10295 global growing cached_atags
10297 if {![info exists allparents($id)]} {
10298 return {}
10300 set t1 [clock clicks -milliseconds]
10301 set argid $id
10302 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10303 # part-way along an arc; check that arc first
10304 set a [lindex $arcnos($id) 0]
10305 if {$arctags($a) ne {}} {
10306 validate_arctags $a
10307 set i [lsearch -exact $arcids($a) $id]
10308 foreach t $arctags($a) {
10309 set j [lsearch -exact $arcids($a) $t]
10310 if {$j > $i} {
10311 return $t
10315 if {![info exists arcend($a)]} {
10316 return {}
10318 set id $arcend($a)
10319 if {[info exists idtags($id)]} {
10320 return $id
10323 if {[info exists cached_atags($id)]} {
10324 return $cached_atags($id)
10327 set origid $id
10328 set todo [list $id]
10329 set queued($id) 1
10330 set taglist {}
10331 set nc 1
10332 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
10333 set id [lindex $todo $i]
10334 set done($id) 1
10335 set td [info exists hastaggeddescendent($id)]
10336 if {!$td} {
10337 incr nc -1
10339 # ignore tags on starting node
10340 if {!$td && $i > 0} {
10341 if {[info exists idtags($id)]} {
10342 set tagloc($id) $id
10343 set td 1
10344 } elseif {[info exists cached_atags($id)]} {
10345 set tagloc($id) $cached_atags($id)
10346 set td 1
10349 foreach a $arcout($id) {
10350 if {!$td && $arctags($a) ne {}} {
10351 validate_arctags $a
10352 if {$arctags($a) ne {}} {
10353 lappend tagloc($id) [lindex $arctags($a) 0]
10356 if {![info exists arcend($a)]} continue
10357 set d $arcend($a)
10358 if {$td || $arctags($a) ne {}} {
10359 set tomark [list $d]
10360 for {set j 0} {$j < [llength $tomark]} {incr j} {
10361 set dd [lindex $tomark $j]
10362 if {![info exists hastaggeddescendent($dd)]} {
10363 if {[info exists done($dd)]} {
10364 foreach b $arcout($dd) {
10365 if {[info exists arcend($b)]} {
10366 lappend tomark $arcend($b)
10369 if {[info exists tagloc($dd)]} {
10370 unset tagloc($dd)
10372 } elseif {[info exists queued($dd)]} {
10373 incr nc -1
10375 set hastaggeddescendent($dd) 1
10379 if {![info exists queued($d)]} {
10380 lappend todo $d
10381 set queued($d) 1
10382 if {![info exists hastaggeddescendent($d)]} {
10383 incr nc
10388 set t2 [clock clicks -milliseconds]
10389 set loopix $i
10390 set tags {}
10391 foreach id [array names tagloc] {
10392 if {![info exists hastaggeddescendent($id)]} {
10393 foreach t $tagloc($id) {
10394 if {[lsearch -exact $tags $t] < 0} {
10395 lappend tags $t
10401 # remove tags that are ancestors of other tags
10402 for {set i 0} {$i < [llength $tags]} {incr i} {
10403 set a [lindex $tags $i]
10404 for {set j 0} {$j < $i} {incr j} {
10405 set b [lindex $tags $j]
10406 set r [anc_or_desc $a $b]
10407 if {$r == -1} {
10408 set tags [lreplace $tags $j $j]
10409 incr j -1
10410 incr i -1
10411 } elseif {$r == 1} {
10412 set tags [lreplace $tags $i $i]
10413 incr i -1
10414 break
10419 if {[array names growing] ne {}} {
10420 # graph isn't finished, need to check if any tag could get
10421 # eclipsed by another tag coming later. Simply ignore any
10422 # tags that could later get eclipsed.
10423 set ctags {}
10424 foreach t $tags {
10425 if {[is_certain $origid $t]} {
10426 lappend ctags $t
10429 if {$tags eq $ctags} {
10430 set cached_atags($origid) $tags
10431 } else {
10432 set tags $ctags
10434 } else {
10435 set cached_atags($origid) $tags
10437 set t3 [clock clicks -milliseconds]
10438 if {0 && $t3 - $t1 >= 100} {
10439 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
10440 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
10442 return $tags
10445 # Return the list of IDs that have heads that are descendents of id,
10446 # including id itself if it has a head.
10447 proc descheads {id} {
10448 global arcnos arcstart arcids archeads idheads cached_dheads
10449 global allparents
10451 if {![info exists allparents($id)]} {
10452 return {}
10454 set aret {}
10455 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
10456 # part-way along an arc; check it first
10457 set a [lindex $arcnos($id) 0]
10458 if {$archeads($a) ne {}} {
10459 validate_archeads $a
10460 set i [lsearch -exact $arcids($a) $id]
10461 foreach t $archeads($a) {
10462 set j [lsearch -exact $arcids($a) $t]
10463 if {$j > $i} break
10464 lappend aret $t
10467 set id $arcstart($a)
10469 set origid $id
10470 set todo [list $id]
10471 set seen($id) 1
10472 set ret {}
10473 for {set i 0} {$i < [llength $todo]} {incr i} {
10474 set id [lindex $todo $i]
10475 if {[info exists cached_dheads($id)]} {
10476 set ret [concat $ret $cached_dheads($id)]
10477 } else {
10478 if {[info exists idheads($id)]} {
10479 lappend ret $id
10481 foreach a $arcnos($id) {
10482 if {$archeads($a) ne {}} {
10483 validate_archeads $a
10484 if {$archeads($a) ne {}} {
10485 set ret [concat $ret $archeads($a)]
10488 set d $arcstart($a)
10489 if {![info exists seen($d)]} {
10490 lappend todo $d
10491 set seen($d) 1
10496 set ret [lsort -unique $ret]
10497 set cached_dheads($origid) $ret
10498 return [concat $ret $aret]
10501 proc addedtag {id} {
10502 global arcnos arcout cached_dtags cached_atags
10504 if {![info exists arcnos($id)]} return
10505 if {![info exists arcout($id)]} {
10506 recalcarc [lindex $arcnos($id) 0]
10508 catch {unset cached_dtags}
10509 catch {unset cached_atags}
10512 proc addedhead {hid head} {
10513 global arcnos arcout cached_dheads
10515 if {![info exists arcnos($hid)]} return
10516 if {![info exists arcout($hid)]} {
10517 recalcarc [lindex $arcnos($hid) 0]
10519 catch {unset cached_dheads}
10522 proc removedhead {hid head} {
10523 global cached_dheads
10525 catch {unset cached_dheads}
10528 proc movedhead {hid head} {
10529 global arcnos arcout cached_dheads
10531 if {![info exists arcnos($hid)]} return
10532 if {![info exists arcout($hid)]} {
10533 recalcarc [lindex $arcnos($hid) 0]
10535 catch {unset cached_dheads}
10538 proc changedrefs {} {
10539 global cached_dheads cached_dtags cached_atags
10540 global arctags archeads arcnos arcout idheads idtags
10542 foreach id [concat [array names idheads] [array names idtags]] {
10543 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
10544 set a [lindex $arcnos($id) 0]
10545 if {![info exists donearc($a)]} {
10546 recalcarc $a
10547 set donearc($a) 1
10551 catch {unset cached_dtags}
10552 catch {unset cached_atags}
10553 catch {unset cached_dheads}
10556 proc rereadrefs {} {
10557 global idtags idheads idotherrefs mainheadid
10559 set refids [concat [array names idtags] \
10560 [array names idheads] [array names idotherrefs]]
10561 foreach id $refids {
10562 if {![info exists ref($id)]} {
10563 set ref($id) [listrefs $id]
10566 set oldmainhead $mainheadid
10567 readrefs
10568 changedrefs
10569 set refids [lsort -unique [concat $refids [array names idtags] \
10570 [array names idheads] [array names idotherrefs]]]
10571 foreach id $refids {
10572 set v [listrefs $id]
10573 if {![info exists ref($id)] || $ref($id) != $v} {
10574 redrawtags $id
10577 if {$oldmainhead ne $mainheadid} {
10578 redrawtags $oldmainhead
10579 redrawtags $mainheadid
10581 run refill_reflist
10584 proc listrefs {id} {
10585 global idtags idheads idotherrefs
10587 set x {}
10588 if {[info exists idtags($id)]} {
10589 set x $idtags($id)
10591 set y {}
10592 if {[info exists idheads($id)]} {
10593 set y $idheads($id)
10595 set z {}
10596 if {[info exists idotherrefs($id)]} {
10597 set z $idotherrefs($id)
10599 return [list $x $y $z]
10602 proc showtag {tag isnew} {
10603 global ctext tagcontents tagids linknum tagobjid
10605 if {$isnew} {
10606 addtohistory [list showtag $tag 0] savectextpos
10608 $ctext conf -state normal
10609 clear_ctext
10610 settabs 0
10611 set linknum 0
10612 if {![info exists tagcontents($tag)]} {
10613 catch {
10614 set tagcontents($tag) [exec git cat-file tag $tag]
10617 if {[info exists tagcontents($tag)]} {
10618 set text $tagcontents($tag)
10619 } else {
10620 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
10622 appendwithlinks $text {}
10623 maybe_scroll_ctext 1
10624 $ctext conf -state disabled
10625 init_flist {}
10628 proc doquit {} {
10629 global stopped
10630 global gitktmpdir
10632 set stopped 100
10633 savestuff .
10634 destroy .
10636 if {[info exists gitktmpdir]} {
10637 catch {file delete -force $gitktmpdir}
10641 proc mkfontdisp {font top which} {
10642 global fontattr fontpref $font NS use_ttk
10644 set fontpref($font) [set $font]
10645 ${NS}::button $top.${font}but -text $which \
10646 -command [list choosefont $font $which]
10647 ${NS}::label $top.$font -relief flat -font $font \
10648 -text $fontattr($font,family) -justify left
10649 grid x $top.${font}but $top.$font -sticky w
10652 proc choosefont {font which} {
10653 global fontparam fontlist fonttop fontattr
10654 global prefstop NS
10656 set fontparam(which) $which
10657 set fontparam(font) $font
10658 set fontparam(family) [font actual $font -family]
10659 set fontparam(size) $fontattr($font,size)
10660 set fontparam(weight) $fontattr($font,weight)
10661 set fontparam(slant) $fontattr($font,slant)
10662 set top .gitkfont
10663 set fonttop $top
10664 if {![winfo exists $top]} {
10665 font create sample
10666 eval font config sample [font actual $font]
10667 ttk_toplevel $top
10668 make_transient $top $prefstop
10669 wm title $top [mc "Gitk font chooser"]
10670 ${NS}::label $top.l -textvariable fontparam(which)
10671 pack $top.l -side top
10672 set fontlist [lsort [font families]]
10673 ${NS}::frame $top.f
10674 listbox $top.f.fam -listvariable fontlist \
10675 -yscrollcommand [list $top.f.sb set]
10676 bind $top.f.fam <<ListboxSelect>> selfontfam
10677 ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
10678 pack $top.f.sb -side right -fill y
10679 pack $top.f.fam -side left -fill both -expand 1
10680 pack $top.f -side top -fill both -expand 1
10681 ${NS}::frame $top.g
10682 spinbox $top.g.size -from 4 -to 40 -width 4 \
10683 -textvariable fontparam(size) \
10684 -validatecommand {string is integer -strict %s}
10685 checkbutton $top.g.bold -padx 5 \
10686 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
10687 -variable fontparam(weight) -onvalue bold -offvalue normal
10688 checkbutton $top.g.ital -padx 5 \
10689 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
10690 -variable fontparam(slant) -onvalue italic -offvalue roman
10691 pack $top.g.size $top.g.bold $top.g.ital -side left
10692 pack $top.g -side top
10693 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
10694 -background white
10695 $top.c create text 100 25 -anchor center -text $which -font sample \
10696 -fill black -tags text
10697 bind $top.c <Configure> [list centertext $top.c]
10698 pack $top.c -side top -fill x
10699 ${NS}::frame $top.buts
10700 ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
10701 ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
10702 bind $top <Key-Return> fontok
10703 bind $top <Key-Escape> fontcan
10704 grid $top.buts.ok $top.buts.can
10705 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10706 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10707 pack $top.buts -side bottom -fill x
10708 trace add variable fontparam write chg_fontparam
10709 } else {
10710 raise $top
10711 $top.c itemconf text -text $which
10713 set i [lsearch -exact $fontlist $fontparam(family)]
10714 if {$i >= 0} {
10715 $top.f.fam selection set $i
10716 $top.f.fam see $i
10720 proc centertext {w} {
10721 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
10724 proc fontok {} {
10725 global fontparam fontpref prefstop
10727 set f $fontparam(font)
10728 set fontpref($f) [list $fontparam(family) $fontparam(size)]
10729 if {$fontparam(weight) eq "bold"} {
10730 lappend fontpref($f) "bold"
10732 if {$fontparam(slant) eq "italic"} {
10733 lappend fontpref($f) "italic"
10735 set w $prefstop.$f
10736 $w conf -text $fontparam(family) -font $fontpref($f)
10738 fontcan
10741 proc fontcan {} {
10742 global fonttop fontparam
10744 if {[info exists fonttop]} {
10745 catch {destroy $fonttop}
10746 catch {font delete sample}
10747 unset fonttop
10748 unset fontparam
10752 if {[package vsatisfies [package provide Tk] 8.6]} {
10753 # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
10754 # function to make use of it.
10755 proc choosefont {font which} {
10756 tk fontchooser configure -title $which -font $font \
10757 -command [list on_choosefont $font $which]
10758 tk fontchooser show
10760 proc on_choosefont {font which newfont} {
10761 global fontparam
10762 puts stderr "$font $newfont"
10763 array set f [font actual $newfont]
10764 set fontparam(which) $which
10765 set fontparam(font) $font
10766 set fontparam(family) $f(-family)
10767 set fontparam(size) $f(-size)
10768 set fontparam(weight) $f(-weight)
10769 set fontparam(slant) $f(-slant)
10770 fontok
10774 proc selfontfam {} {
10775 global fonttop fontparam
10777 set i [$fonttop.f.fam curselection]
10778 if {$i ne {}} {
10779 set fontparam(family) [$fonttop.f.fam get $i]
10783 proc chg_fontparam {v sub op} {
10784 global fontparam
10786 font config sample -$sub $fontparam($sub)
10789 proc doprefs {} {
10790 global maxwidth maxgraphpct use_ttk NS
10791 global oldprefs prefstop showneartags showlocalchanges
10792 global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
10793 global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
10794 global hideremotes want_ttk have_ttk
10796 set top .gitkprefs
10797 set prefstop $top
10798 if {[winfo exists $top]} {
10799 raise $top
10800 return
10802 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10803 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
10804 set oldprefs($v) [set $v]
10806 ttk_toplevel $top
10807 wm title $top [mc "Gitk preferences"]
10808 make_transient $top .
10809 ${NS}::label $top.ldisp -text [mc "Commit list display options"]
10810 grid $top.ldisp - -sticky w -pady 10
10811 ${NS}::label $top.spacer -text " "
10812 ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"]
10813 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10814 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
10815 ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"]
10816 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10817 grid x $top.maxpctl $top.maxpct -sticky w
10818 ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
10819 -variable showlocalchanges
10820 grid x $top.showlocal -sticky w
10821 ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1 (length)"] \
10822 -variable autoselect
10823 spinbox $top.autosellen -from 1 -to 40 -width 4 -textvariable autosellen
10824 grid x $top.autoselect $top.autosellen -sticky w
10825 ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
10826 -variable hideremotes
10827 grid x $top.hideremotes -sticky w
10829 ${NS}::label $top.ddisp -text [mc "Diff display options"]
10830 grid $top.ddisp - -sticky w -pady 10
10831 ${NS}::label $top.tabstopl -text [mc "Tab spacing"]
10832 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10833 grid x $top.tabstopl $top.tabstop -sticky w
10834 ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \
10835 -variable showneartags
10836 grid x $top.ntag -sticky w
10837 ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
10838 -variable limitdiffs
10839 grid x $top.ldiff -sticky w
10840 ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \
10841 -variable perfile_attrs
10842 grid x $top.lattr -sticky w
10844 ${NS}::entry $top.extdifft -textvariable extdifftool
10845 ${NS}::frame $top.extdifff
10846 ${NS}::label $top.extdifff.l -text [mc "External diff tool" ]
10847 ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff
10848 pack $top.extdifff.l $top.extdifff.b -side left
10849 pack configure $top.extdifff.l -padx 10
10850 grid x $top.extdifff $top.extdifft -sticky ew
10852 ${NS}::label $top.lgen -text [mc "General options"]
10853 grid $top.lgen - -sticky w -pady 10
10854 ${NS}::checkbutton $top.want_ttk -variable want_ttk \
10855 -text [mc "Use themed widgets"]
10856 if {$have_ttk} {
10857 ${NS}::label $top.ttk_note -text [mc "(change requires restart)"]
10858 } else {
10859 ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"]
10861 grid x $top.want_ttk $top.ttk_note -sticky w
10863 ${NS}::label $top.cdisp -text [mc "Colors: press to choose"]
10864 grid $top.cdisp - -sticky w -pady 10
10865 label $top.ui -padx 40 -relief sunk -background $uicolor
10866 ${NS}::button $top.uibut -text [mc "Interface"] \
10867 -command [list choosecolor uicolor {} $top.ui [mc "interface"] setui]
10868 grid x $top.uibut $top.ui -sticky w
10869 label $top.bg -padx 40 -relief sunk -background $bgcolor
10870 ${NS}::button $top.bgbut -text [mc "Background"] \
10871 -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg]
10872 grid x $top.bgbut $top.bg -sticky w
10873 label $top.fg -padx 40 -relief sunk -background $fgcolor
10874 ${NS}::button $top.fgbut -text [mc "Foreground"] \
10875 -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg]
10876 grid x $top.fgbut $top.fg -sticky w
10877 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10878 ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \
10879 -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \
10880 [list $ctext tag conf d0 -foreground]]
10881 grid x $top.diffoldbut $top.diffold -sticky w
10882 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10883 ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \
10884 -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
10885 [list $ctext tag conf dresult -foreground]]
10886 grid x $top.diffnewbut $top.diffnew -sticky w
10887 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10888 ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \
10889 -command [list choosecolor diffcolors 2 $top.hunksep \
10890 [mc "diff hunk header"] \
10891 [list $ctext tag conf hunksep -foreground]]
10892 grid x $top.hunksepbut $top.hunksep -sticky w
10893 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10894 ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \
10895 -command [list choosecolor markbgcolor {} $top.markbgsep \
10896 [mc "marked line background"] \
10897 [list $ctext tag conf omark -background]]
10898 grid x $top.markbgbut $top.markbgsep -sticky w
10899 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10900 ${NS}::button $top.selbgbut -text [mc "Select bg"] \
10901 -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
10902 grid x $top.selbgbut $top.selbgsep -sticky w
10904 ${NS}::label $top.cfont -text [mc "Fonts: press to choose"]
10905 grid $top.cfont - -sticky w -pady 10
10906 mkfontdisp mainfont $top [mc "Main font"]
10907 mkfontdisp textfont $top [mc "Diff display font"]
10908 mkfontdisp uifont $top [mc "User interface font"]
10910 ${NS}::frame $top.buts
10911 ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10912 ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10913 bind $top <Key-Return> prefsok
10914 bind $top <Key-Escape> prefscan
10915 grid $top.buts.ok $top.buts.can
10916 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10917 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10918 grid $top.buts - - -pady 10 -sticky ew
10919 grid columnconfigure $top 2 -weight 1
10920 bind $top <Visibility> "focus $top.buts.ok"
10923 proc choose_extdiff {} {
10924 global extdifftool
10926 set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false]
10927 if {$prog ne {}} {
10928 set extdifftool $prog
10932 proc choosecolor {v vi w x cmd} {
10933 global $v
10935 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10936 -title [mc "Gitk: choose color for %s" $x]]
10937 if {$c eq {}} return
10938 $w conf -background $c
10939 lset $v $vi $c
10940 eval $cmd $c
10943 proc setselbg {c} {
10944 global bglist cflist
10945 foreach w $bglist {
10946 $w configure -selectbackground $c
10948 $cflist tag configure highlight \
10949 -background [$cflist cget -selectbackground]
10950 allcanvs itemconf secsel -fill $c
10953 # This sets the background color and the color scheme for the whole UI.
10954 # For some reason, tk_setPalette chooses a nasty dark red for selectColor
10955 # if we don't specify one ourselves, which makes the checkbuttons and
10956 # radiobuttons look bad. This chooses white for selectColor if the
10957 # background color is light, or black if it is dark.
10958 proc setui {c} {
10959 if {[tk windowingsystem] eq "win32"} { return }
10960 set bg [winfo rgb . $c]
10961 set selc black
10962 if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} {
10963 set selc white
10965 tk_setPalette background $c selectColor $selc
10968 proc setbg {c} {
10969 global bglist
10971 foreach w $bglist {
10972 $w conf -background $c
10976 proc setfg {c} {
10977 global fglist canv
10979 foreach w $fglist {
10980 $w conf -foreground $c
10982 allcanvs itemconf text -fill $c
10983 $canv itemconf circle -outline $c
10984 $canv itemconf markid -outline $c
10987 proc prefscan {} {
10988 global oldprefs prefstop
10990 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10991 limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
10992 global $v
10993 set $v $oldprefs($v)
10995 catch {destroy $prefstop}
10996 unset prefstop
10997 fontcan
11000 proc prefsok {} {
11001 global maxwidth maxgraphpct
11002 global oldprefs prefstop showneartags showlocalchanges
11003 global fontpref mainfont textfont uifont
11004 global limitdiffs treediffs perfile_attrs
11005 global hideremotes
11007 catch {destroy $prefstop}
11008 unset prefstop
11009 fontcan
11010 set fontchanged 0
11011 if {$mainfont ne $fontpref(mainfont)} {
11012 set mainfont $fontpref(mainfont)
11013 parsefont mainfont $mainfont
11014 eval font configure mainfont [fontflags mainfont]
11015 eval font configure mainfontbold [fontflags mainfont 1]
11016 setcoords
11017 set fontchanged 1
11019 if {$textfont ne $fontpref(textfont)} {
11020 set textfont $fontpref(textfont)
11021 parsefont textfont $textfont
11022 eval font configure textfont [fontflags textfont]
11023 eval font configure textfontbold [fontflags textfont 1]
11025 if {$uifont ne $fontpref(uifont)} {
11026 set uifont $fontpref(uifont)
11027 parsefont uifont $uifont
11028 eval font configure uifont [fontflags uifont]
11030 settabs
11031 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
11032 if {$showlocalchanges} {
11033 doshowlocalchanges
11034 } else {
11035 dohidelocalchanges
11038 if {$limitdiffs != $oldprefs(limitdiffs) ||
11039 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
11040 # treediffs elements are limited by path;
11041 # won't have encodings cached if perfile_attrs was just turned on
11042 catch {unset treediffs}
11044 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
11045 || $maxgraphpct != $oldprefs(maxgraphpct)} {
11046 redisplay
11047 } elseif {$showneartags != $oldprefs(showneartags) ||
11048 $limitdiffs != $oldprefs(limitdiffs)} {
11049 reselectline
11051 if {$hideremotes != $oldprefs(hideremotes)} {
11052 rereadrefs
11056 proc formatdate {d} {
11057 global datetimeformat
11058 if {$d ne {}} {
11059 set d [clock format $d -format $datetimeformat]
11061 return $d
11064 # This list of encoding names and aliases is distilled from
11065 # http://www.iana.org/assignments/character-sets.
11066 # Not all of them are supported by Tcl.
11067 set encoding_aliases {
11068 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
11069 ISO646-US US-ASCII us IBM367 cp367 csASCII }
11070 { ISO-10646-UTF-1 csISO10646UTF1 }
11071 { ISO_646.basic:1983 ref csISO646basic1983 }
11072 { INVARIANT csINVARIANT }
11073 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
11074 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
11075 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
11076 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
11077 { NATS-DANO iso-ir-9-1 csNATSDANO }
11078 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
11079 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
11080 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
11081 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
11082 { ISO-2022-KR csISO2022KR }
11083 { EUC-KR csEUCKR }
11084 { ISO-2022-JP csISO2022JP }
11085 { ISO-2022-JP-2 csISO2022JP2 }
11086 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
11087 csISO13JISC6220jp }
11088 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
11089 { IT iso-ir-15 ISO646-IT csISO15Italian }
11090 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
11091 { ES iso-ir-17 ISO646-ES csISO17Spanish }
11092 { greek7-old iso-ir-18 csISO18Greek7Old }
11093 { latin-greek iso-ir-19 csISO19LatinGreek }
11094 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
11095 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
11096 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
11097 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
11098 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
11099 { BS_viewdata iso-ir-47 csISO47BSViewdata }
11100 { INIS iso-ir-49 csISO49INIS }
11101 { INIS-8 iso-ir-50 csISO50INIS8 }
11102 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
11103 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
11104 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
11105 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
11106 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
11107 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
11108 csISO60Norwegian1 }
11109 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
11110 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
11111 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
11112 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
11113 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
11114 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
11115 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
11116 { greek7 iso-ir-88 csISO88Greek7 }
11117 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
11118 { iso-ir-90 csISO90 }
11119 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
11120 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
11121 csISO92JISC62991984b }
11122 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
11123 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
11124 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
11125 csISO95JIS62291984handadd }
11126 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
11127 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
11128 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
11129 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
11130 CP819 csISOLatin1 }
11131 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
11132 { T.61-7bit iso-ir-102 csISO102T617bit }
11133 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
11134 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
11135 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
11136 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
11137 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
11138 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
11139 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
11140 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
11141 arabic csISOLatinArabic }
11142 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
11143 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
11144 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
11145 greek greek8 csISOLatinGreek }
11146 { T.101-G2 iso-ir-128 csISO128T101G2 }
11147 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
11148 csISOLatinHebrew }
11149 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
11150 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
11151 { CSN_369103 iso-ir-139 csISO139CSN369103 }
11152 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
11153 { ISO_6937-2-add iso-ir-142 csISOTextComm }
11154 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
11155 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
11156 csISOLatinCyrillic }
11157 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
11158 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
11159 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
11160 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
11161 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
11162 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
11163 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
11164 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
11165 { ISO_10367-box iso-ir-155 csISO10367Box }
11166 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
11167 { latin-lap lap iso-ir-158 csISO158Lap }
11168 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
11169 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
11170 { us-dk csUSDK }
11171 { dk-us csDKUS }
11172 { JIS_X0201 X0201 csHalfWidthKatakana }
11173 { KSC5636 ISO646-KR csKSC5636 }
11174 { ISO-10646-UCS-2 csUnicode }
11175 { ISO-10646-UCS-4 csUCS4 }
11176 { DEC-MCS dec csDECMCS }
11177 { hp-roman8 roman8 r8 csHPRoman8 }
11178 { macintosh mac csMacintosh }
11179 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
11180 csIBM037 }
11181 { IBM038 EBCDIC-INT cp038 csIBM038 }
11182 { IBM273 CP273 csIBM273 }
11183 { IBM274 EBCDIC-BE CP274 csIBM274 }
11184 { IBM275 EBCDIC-BR cp275 csIBM275 }
11185 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
11186 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
11187 { IBM280 CP280 ebcdic-cp-it csIBM280 }
11188 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
11189 { IBM284 CP284 ebcdic-cp-es csIBM284 }
11190 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
11191 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
11192 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
11193 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
11194 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
11195 { IBM424 cp424 ebcdic-cp-he csIBM424 }
11196 { IBM437 cp437 437 csPC8CodePage437 }
11197 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
11198 { IBM775 cp775 csPC775Baltic }
11199 { IBM850 cp850 850 csPC850Multilingual }
11200 { IBM851 cp851 851 csIBM851 }
11201 { IBM852 cp852 852 csPCp852 }
11202 { IBM855 cp855 855 csIBM855 }
11203 { IBM857 cp857 857 csIBM857 }
11204 { IBM860 cp860 860 csIBM860 }
11205 { IBM861 cp861 861 cp-is csIBM861 }
11206 { IBM862 cp862 862 csPC862LatinHebrew }
11207 { IBM863 cp863 863 csIBM863 }
11208 { IBM864 cp864 csIBM864 }
11209 { IBM865 cp865 865 csIBM865 }
11210 { IBM866 cp866 866 csIBM866 }
11211 { IBM868 CP868 cp-ar csIBM868 }
11212 { IBM869 cp869 869 cp-gr csIBM869 }
11213 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
11214 { IBM871 CP871 ebcdic-cp-is csIBM871 }
11215 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
11216 { IBM891 cp891 csIBM891 }
11217 { IBM903 cp903 csIBM903 }
11218 { IBM904 cp904 904 csIBBM904 }
11219 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
11220 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
11221 { IBM1026 CP1026 csIBM1026 }
11222 { EBCDIC-AT-DE csIBMEBCDICATDE }
11223 { EBCDIC-AT-DE-A csEBCDICATDEA }
11224 { EBCDIC-CA-FR csEBCDICCAFR }
11225 { EBCDIC-DK-NO csEBCDICDKNO }
11226 { EBCDIC-DK-NO-A csEBCDICDKNOA }
11227 { EBCDIC-FI-SE csEBCDICFISE }
11228 { EBCDIC-FI-SE-A csEBCDICFISEA }
11229 { EBCDIC-FR csEBCDICFR }
11230 { EBCDIC-IT csEBCDICIT }
11231 { EBCDIC-PT csEBCDICPT }
11232 { EBCDIC-ES csEBCDICES }
11233 { EBCDIC-ES-A csEBCDICESA }
11234 { EBCDIC-ES-S csEBCDICESS }
11235 { EBCDIC-UK csEBCDICUK }
11236 { EBCDIC-US csEBCDICUS }
11237 { UNKNOWN-8BIT csUnknown8BiT }
11238 { MNEMONIC csMnemonic }
11239 { MNEM csMnem }
11240 { VISCII csVISCII }
11241 { VIQR csVIQR }
11242 { KOI8-R csKOI8R }
11243 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
11244 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
11245 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
11246 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
11247 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
11248 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
11249 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
11250 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
11251 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
11252 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
11253 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
11254 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
11255 { IBM1047 IBM-1047 }
11256 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
11257 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
11258 { UNICODE-1-1 csUnicode11 }
11259 { CESU-8 csCESU-8 }
11260 { BOCU-1 csBOCU-1 }
11261 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
11262 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
11263 l8 }
11264 { ISO-8859-15 ISO_8859-15 Latin-9 }
11265 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
11266 { GBK CP936 MS936 windows-936 }
11267 { JIS_Encoding csJISEncoding }
11268 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
11269 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
11270 EUC-JP }
11271 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
11272 { ISO-10646-UCS-Basic csUnicodeASCII }
11273 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
11274 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
11275 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
11276 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
11277 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
11278 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
11279 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
11280 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
11281 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
11282 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
11283 { Adobe-Standard-Encoding csAdobeStandardEncoding }
11284 { Ventura-US csVenturaUS }
11285 { Ventura-International csVenturaInternational }
11286 { PC8-Danish-Norwegian csPC8DanishNorwegian }
11287 { PC8-Turkish csPC8Turkish }
11288 { IBM-Symbols csIBMSymbols }
11289 { IBM-Thai csIBMThai }
11290 { HP-Legal csHPLegal }
11291 { HP-Pi-font csHPPiFont }
11292 { HP-Math8 csHPMath8 }
11293 { Adobe-Symbol-Encoding csHPPSMath }
11294 { HP-DeskTop csHPDesktop }
11295 { Ventura-Math csVenturaMath }
11296 { Microsoft-Publishing csMicrosoftPublishing }
11297 { Windows-31J csWindows31J }
11298 { GB2312 csGB2312 }
11299 { Big5 csBig5 }
11302 proc tcl_encoding {enc} {
11303 global encoding_aliases tcl_encoding_cache
11304 if {[info exists tcl_encoding_cache($enc)]} {
11305 return $tcl_encoding_cache($enc)
11307 set names [encoding names]
11308 set lcnames [string tolower $names]
11309 set enc [string tolower $enc]
11310 set i [lsearch -exact $lcnames $enc]
11311 if {$i < 0} {
11312 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
11313 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
11314 set i [lsearch -exact $lcnames $encx]
11317 if {$i < 0} {
11318 foreach l $encoding_aliases {
11319 set ll [string tolower $l]
11320 if {[lsearch -exact $ll $enc] < 0} continue
11321 # look through the aliases for one that tcl knows about
11322 foreach e $ll {
11323 set i [lsearch -exact $lcnames $e]
11324 if {$i < 0} {
11325 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
11326 set i [lsearch -exact $lcnames $ex]
11329 if {$i >= 0} break
11331 break
11334 set tclenc {}
11335 if {$i >= 0} {
11336 set tclenc [lindex $names $i]
11338 set tcl_encoding_cache($enc) $tclenc
11339 return $tclenc
11342 proc gitattr {path attr default} {
11343 global path_attr_cache
11344 if {[info exists path_attr_cache($attr,$path)]} {
11345 set r $path_attr_cache($attr,$path)
11346 } else {
11347 set r "unspecified"
11348 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
11349 regexp "(.*): $attr: (.*)" $line m f r
11351 set path_attr_cache($attr,$path) $r
11353 if {$r eq "unspecified"} {
11354 return $default
11356 return $r
11359 proc cache_gitattr {attr pathlist} {
11360 global path_attr_cache
11361 set newlist {}
11362 foreach path $pathlist {
11363 if {![info exists path_attr_cache($attr,$path)]} {
11364 lappend newlist $path
11367 set lim 1000
11368 if {[tk windowingsystem] == "win32"} {
11369 # windows has a 32k limit on the arguments to a command...
11370 set lim 30
11372 while {$newlist ne {}} {
11373 set head [lrange $newlist 0 [expr {$lim - 1}]]
11374 set newlist [lrange $newlist $lim end]
11375 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
11376 foreach row [split $rlist "\n"] {
11377 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
11378 if {[string index $path 0] eq "\""} {
11379 set path [encoding convertfrom [lindex $path 0]]
11381 set path_attr_cache($attr,$path) $value
11388 proc get_path_encoding {path} {
11389 global gui_encoding perfile_attrs
11390 set tcl_enc $gui_encoding
11391 if {$path ne {} && $perfile_attrs} {
11392 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
11393 if {$enc2 ne {}} {
11394 set tcl_enc $enc2
11397 return $tcl_enc
11400 # First check that Tcl/Tk is recent enough
11401 if {[catch {package require Tk 8.4} err]} {
11402 show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
11403 Gitk requires at least Tcl/Tk 8.4." list
11404 exit 1
11407 # defaults...
11408 set wrcomcmd "git diff-tree --stdin -p --pretty"
11410 set gitencoding {}
11411 catch {
11412 set gitencoding [exec git config --get i18n.commitencoding]
11414 catch {
11415 set gitencoding [exec git config --get i18n.logoutputencoding]
11417 if {$gitencoding == ""} {
11418 set gitencoding "utf-8"
11420 set tclencoding [tcl_encoding $gitencoding]
11421 if {$tclencoding == {}} {
11422 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
11425 set gui_encoding [encoding system]
11426 catch {
11427 set enc [exec git config --get gui.encoding]
11428 if {$enc ne {}} {
11429 set tclenc [tcl_encoding $enc]
11430 if {$tclenc ne {}} {
11431 set gui_encoding $tclenc
11432 } else {
11433 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
11438 if {[tk windowingsystem] eq "aqua"} {
11439 set mainfont {{Lucida Grande} 9}
11440 set textfont {Monaco 9}
11441 set uifont {{Lucida Grande} 9 bold}
11442 } else {
11443 set mainfont {Helvetica 9}
11444 set textfont {Courier 9}
11445 set uifont {Helvetica 9 bold}
11447 set tabstop 8
11448 set findmergefiles 0
11449 set maxgraphpct 50
11450 set maxwidth 16
11451 set revlistorder 0
11452 set fastdate 0
11453 set uparrowlen 5
11454 set downarrowlen 5
11455 set mingaplen 100
11456 set cmitmode "patch"
11457 set wrapcomment "none"
11458 set showneartags 1
11459 set hideremotes 0
11460 set maxrefs 20
11461 set maxlinelen 200
11462 set showlocalchanges 1
11463 set limitdiffs 1
11464 set datetimeformat "%Y-%m-%d %H:%M:%S"
11465 set autoselect 1
11466 set autosellen 40
11467 set perfile_attrs 0
11468 set want_ttk 1
11470 if {[tk windowingsystem] eq "aqua"} {
11471 set extdifftool "opendiff"
11472 } else {
11473 set extdifftool "meld"
11476 set colors {green red blue magenta darkgrey brown orange}
11477 if {[tk windowingsystem] eq "win32"} {
11478 set uicolor SystemButtonFace
11479 set bgcolor SystemWindow
11480 set fgcolor SystemButtonText
11481 set selectbgcolor SystemHighlight
11482 } else {
11483 set uicolor grey85
11484 set bgcolor white
11485 set fgcolor black
11486 set selectbgcolor gray85
11488 set diffcolors {red "#00a000" blue}
11489 set diffcontext 3
11490 set ignorespace 0
11491 set worddiff ""
11492 set markbgcolor "#e0e0ff"
11494 set circlecolors {white blue gray blue blue}
11496 # button for popping up context menus
11497 if {[tk windowingsystem] eq "aqua"} {
11498 set ctxbut <Button-2>
11499 } else {
11500 set ctxbut <Button-3>
11503 ## For msgcat loading, first locate the installation location.
11504 if { [info exists ::env(GITK_MSGSDIR)] } {
11505 ## Msgsdir was manually set in the environment.
11506 set gitk_msgsdir $::env(GITK_MSGSDIR)
11507 } else {
11508 ## Let's guess the prefix from argv0.
11509 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
11510 set gitk_libdir [file join $gitk_prefix share gitk lib]
11511 set gitk_msgsdir [file join $gitk_libdir msgs]
11512 unset gitk_prefix
11515 ## Internationalization (i18n) through msgcat and gettext. See
11516 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
11517 package require msgcat
11518 namespace import ::msgcat::mc
11519 ## And eventually load the actual message catalog
11520 ::msgcat::mcload $gitk_msgsdir
11522 catch {source ~/.gitk}
11524 parsefont mainfont $mainfont
11525 eval font create mainfont [fontflags mainfont]
11526 eval font create mainfontbold [fontflags mainfont 1]
11528 parsefont textfont $textfont
11529 eval font create textfont [fontflags textfont]
11530 eval font create textfontbold [fontflags textfont 1]
11532 parsefont uifont $uifont
11533 eval font create uifont [fontflags uifont]
11535 setui $uicolor
11537 setoptions
11539 # check that we can find a .git directory somewhere...
11540 if {[catch {set gitdir [gitdir]}]} {
11541 show_error {} . [mc "Cannot find a git repository here."]
11542 exit 1
11544 if {![file isdirectory $gitdir]} {
11545 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
11546 exit 1
11549 set selecthead {}
11550 set selectheadid {}
11552 set revtreeargs {}
11553 set cmdline_files {}
11554 set i 0
11555 set revtreeargscmd {}
11556 foreach arg $argv {
11557 switch -glob -- $arg {
11558 "" { }
11559 "--" {
11560 set cmdline_files [lrange $argv [expr {$i + 1}] end]
11561 break
11563 "--select-commit=*" {
11564 set selecthead [string range $arg 16 end]
11566 "--argscmd=*" {
11567 set revtreeargscmd [string range $arg 10 end]
11569 default {
11570 lappend revtreeargs $arg
11573 incr i
11576 if {$selecthead eq "HEAD"} {
11577 set selecthead {}
11580 if {$i >= [llength $argv] && $revtreeargs ne {}} {
11581 # no -- on command line, but some arguments (other than --argscmd)
11582 if {[catch {
11583 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
11584 set cmdline_files [split $f "\n"]
11585 set n [llength $cmdline_files]
11586 set revtreeargs [lrange $revtreeargs 0 end-$n]
11587 # Unfortunately git rev-parse doesn't produce an error when
11588 # something is both a revision and a filename. To be consistent
11589 # with git log and git rev-list, check revtreeargs for filenames.
11590 foreach arg $revtreeargs {
11591 if {[file exists $arg]} {
11592 show_error {} . [mc "Ambiguous argument '%s': both revision\
11593 and filename" $arg]
11594 exit 1
11597 } err]} {
11598 # unfortunately we get both stdout and stderr in $err,
11599 # so look for "fatal:".
11600 set i [string first "fatal:" $err]
11601 if {$i > 0} {
11602 set err [string range $err [expr {$i + 6}] end]
11604 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
11605 exit 1
11609 set nullid "0000000000000000000000000000000000000000"
11610 set nullid2 "0000000000000000000000000000000000000001"
11611 set nullfile "/dev/null"
11613 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
11614 if {![info exists have_ttk]} {
11615 set have_ttk [llength [info commands ::ttk::style]]
11617 set use_ttk [expr {$have_ttk && $want_ttk}]
11618 set NS [expr {$use_ttk ? "ttk" : ""}]
11620 regexp {^git version ([\d.]*\d)} [exec git version] _ git_version
11622 set show_notes {}
11623 if {[package vcompare $git_version "1.6.6.2"] >= 0} {
11624 set show_notes "--show-notes"
11627 set runq {}
11628 set history {}
11629 set historyindex 0
11630 set fh_serial 0
11631 set nhl_names {}
11632 set highlight_paths {}
11633 set findpattern {}
11634 set searchdirn -forwards
11635 set boldids {}
11636 set boldnameids {}
11637 set diffelide {0 0}
11638 set markingmatches 0
11639 set linkentercount 0
11640 set need_redisplay 0
11641 set nrows_drawn 0
11642 set firsttabstop 0
11644 set nextviewnum 1
11645 set curview 0
11646 set selectedview 0
11647 set selectedhlview [mc "None"]
11648 set highlight_related [mc "None"]
11649 set highlight_files {}
11650 set viewfiles(0) {}
11651 set viewperm(0) 0
11652 set viewargs(0) {}
11653 set viewargscmd(0) {}
11655 set selectedline {}
11656 set numcommits 0
11657 set loginstance 0
11658 set cmdlineok 0
11659 set stopped 0
11660 set stuffsaved 0
11661 set patchnum 0
11662 set lserial 0
11663 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
11664 setcoords
11665 makewindow
11666 catch {
11667 image create photo gitlogo -width 16 -height 16
11669 image create photo gitlogominus -width 4 -height 2
11670 gitlogominus put #C00000 -to 0 0 4 2
11671 gitlogo copy gitlogominus -to 1 5
11672 gitlogo copy gitlogominus -to 6 5
11673 gitlogo copy gitlogominus -to 11 5
11674 image delete gitlogominus
11676 image create photo gitlogoplus -width 4 -height 4
11677 gitlogoplus put #008000 -to 1 0 3 4
11678 gitlogoplus put #008000 -to 0 1 4 3
11679 gitlogo copy gitlogoplus -to 1 9
11680 gitlogo copy gitlogoplus -to 6 9
11681 gitlogo copy gitlogoplus -to 11 9
11682 image delete gitlogoplus
11684 image create photo gitlogo32 -width 32 -height 32
11685 gitlogo32 copy gitlogo -zoom 2 2
11687 wm iconphoto . -default gitlogo gitlogo32
11689 # wait for the window to become visible
11690 tkwait visibility .
11691 wm title . "[file tail $argv0]: [file tail [pwd]]"
11692 update
11693 readrefs
11695 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
11696 # create a view for the files/dirs specified on the command line
11697 set curview 1
11698 set selectedview 1
11699 set nextviewnum 2
11700 set viewname(1) [mc "Command line"]
11701 set viewfiles(1) $cmdline_files
11702 set viewargs(1) $revtreeargs
11703 set viewargscmd(1) $revtreeargscmd
11704 set viewperm(1) 0
11705 set vdatemode(1) 0
11706 addviewmenu 1
11707 .bar.view entryconf [mca "Edit view..."] -state normal
11708 .bar.view entryconf [mca "Delete view"] -state normal
11711 if {[info exists permviews]} {
11712 foreach v $permviews {
11713 set n $nextviewnum
11714 incr nextviewnum
11715 set viewname($n) [lindex $v 0]
11716 set viewfiles($n) [lindex $v 1]
11717 set viewargs($n) [lindex $v 2]
11718 set viewargscmd($n) [lindex $v 3]
11719 set viewperm($n) 1
11720 addviewmenu $n
11724 if {[tk windowingsystem] eq "win32"} {
11725 focus -force .
11728 getcommits {}
11730 # Local variables:
11731 # mode: tcl
11732 # indent-tabs-mode: t
11733 # tab-width: 8
11734 # End: