gitk: Index line[hnd]tag arrays by id rather than row number
[git/dscho.git] / gitk
blob26ff80284952c281f622795d64141af120e32c98
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright © 2005-2008 Paul Mackerras. All rights reserved.
6 # This program is free software; it may be used, copied, modified
7 # and distributed under the terms of the GNU General Public Licence,
8 # either version 2, or (at your option) any later version.
10 proc gitdir {} {
11 global env
12 if {[info exists env(GIT_DIR)]} {
13 return $env(GIT_DIR)
14 } else {
15 return [exec git rev-parse --git-dir]
19 # A simple scheduler for compute-intensive stuff.
20 # The aim is to make sure that event handlers for GUI actions can
21 # run at least every 50-100 ms. Unfortunately fileevent handlers are
22 # run before X event handlers, so reading from a fast source can
23 # make the GUI completely unresponsive.
24 proc run args {
25 global isonrunq runq currunq
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {} && ![info exists currunq]} {
30 after idle dorunq
32 lappend runq [list {} $script]
33 set isonrunq($script) 1
36 proc filerun {fd script} {
37 fileevent $fd readable [list filereadable $fd $script]
40 proc filereadable {fd script} {
41 global runq currunq
43 fileevent $fd readable {}
44 if {$runq eq {} && ![info exists currunq]} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc nukefile {fd} {
51 global runq
53 for {set i 0} {$i < [llength $runq]} {} {
54 if {[lindex $runq $i 0] eq $fd} {
55 set runq [lreplace $runq $i $i]
56 } else {
57 incr i
62 proc dorunq {} {
63 global isonrunq runq currunq
65 set tstart [clock clicks -milliseconds]
66 set t0 $tstart
67 while {[llength $runq] > 0} {
68 set fd [lindex $runq 0 0]
69 set script [lindex $runq 0 1]
70 set currunq [lindex $runq 0]
71 set runq [lrange $runq 1 end]
72 set repeat [eval $script]
73 unset currunq
74 set t1 [clock clicks -milliseconds]
75 set t [expr {$t1 - $t0}]
76 if {$repeat ne {} && $repeat} {
77 if {$fd eq {} || $repeat == 2} {
78 # script returns 1 if it wants to be readded
79 # file readers return 2 if they could do more straight away
80 lappend runq [list $fd $script]
81 } else {
82 fileevent $fd readable [list filereadable $fd $script]
84 } elseif {$fd eq {}} {
85 unset isonrunq($script)
87 set t0 $t1
88 if {$t1 - $tstart >= 80} break
90 if {$runq ne {}} {
91 after idle dorunq
95 proc reg_instance {fd} {
96 global commfd leftover loginstance
98 set i [incr loginstance]
99 set commfd($i) $fd
100 set leftover($i) {}
101 return $i
104 proc unmerged_files {files} {
105 global nr_unmerged
107 # find the list of unmerged files
108 set mlist {}
109 set nr_unmerged 0
110 if {[catch {
111 set fd [open "| git ls-files -u" r]
112 } err]} {
113 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
114 exit 1
116 while {[gets $fd line] >= 0} {
117 set i [string first "\t" $line]
118 if {$i < 0} continue
119 set fname [string range $line [expr {$i+1}] end]
120 if {[lsearch -exact $mlist $fname] >= 0} continue
121 incr nr_unmerged
122 if {$files eq {} || [path_filter $files $fname]} {
123 lappend mlist $fname
126 catch {close $fd}
127 return $mlist
130 proc parseviewargs {n arglist} {
131 global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
133 set vdatemode($n) 0
134 set vmergeonly($n) 0
135 set glflags {}
136 set diffargs {}
137 set nextisval 0
138 set revargs {}
139 set origargs $arglist
140 set allknown 1
141 set filtered 0
142 set i -1
143 foreach arg $arglist {
144 incr i
145 if {$nextisval} {
146 lappend glflags $arg
147 set nextisval 0
148 continue
150 switch -glob -- $arg {
151 "-d" -
152 "--date-order" {
153 set vdatemode($n) 1
154 # remove from origargs in case we hit an unknown option
155 set origargs [lreplace $origargs $i $i]
156 incr i -1
158 # These request or affect diff output, which we don't want.
159 # Some could be used to set our defaults for diff display.
160 "-[puabwcrRBMC]" -
161 "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
162 "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
163 "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
164 "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
165 "--ignore-space-change" - "-U*" - "--unified=*" {
166 lappend diffargs $arg
168 # These cause our parsing of git log's output to fail, or else
169 # they're options we want to set ourselves, so ignore them.
170 "--raw" - "--patch-with-raw" - "--patch-with-stat" -
171 "--name-only" - "--name-status" - "--color" - "--color-words" -
172 "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
173 "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
174 "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
175 "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
176 "--objects" - "--objects-edge" - "--reverse" {
178 # These are harmless, and some are even useful
179 "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
180 "--check" - "--exit-code" - "--quiet" - "--topo-order" -
181 "--full-history" - "--dense" - "--sparse" -
182 "--follow" - "--left-right" - "--encoding=*" {
183 lappend glflags $arg
185 # These mean that we get a subset of the commits
186 "--diff-filter=*" - "--no-merges" - "--unpacked" -
187 "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
188 "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
189 "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
190 "--remove-empty" - "--first-parent" - "--cherry-pick" -
191 "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
192 set filtered 1
193 lappend glflags $arg
195 # This appears to be the only one that has a value as a
196 # separate word following it
197 "-n" {
198 set filtered 1
199 set nextisval 1
200 lappend glflags $arg
202 "--not" {
203 set notflag [expr {!$notflag}]
204 lappend revargs $arg
206 "--all" {
207 lappend revargs $arg
209 "--merge" {
210 set vmergeonly($n) 1
211 # git rev-parse doesn't understand --merge
212 lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
214 # Other flag arguments including -<n>
215 "-*" {
216 if {[string is digit -strict [string range $arg 1 end]]} {
217 set filtered 1
218 } else {
219 # a flag argument that we don't recognize;
220 # that means we can't optimize
221 set allknown 0
223 lappend glflags $arg
225 # Non-flag arguments specify commits or ranges of commits
226 default {
227 if {[string match "*...*" $arg]} {
228 lappend revargs --gitk-symmetric-diff-marker
230 lappend revargs $arg
234 set vdflags($n) $diffargs
235 set vflags($n) $glflags
236 set vrevs($n) $revargs
237 set vfiltered($n) $filtered
238 set vorigargs($n) $origargs
239 return $allknown
242 proc parseviewrevs {view revs} {
243 global vposids vnegids
245 if {$revs eq {}} {
246 set revs HEAD
248 if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
249 # we get stdout followed by stderr in $err
250 # for an unknown rev, git rev-parse echoes it and then errors out
251 set errlines [split $err "\n"]
252 set badrev {}
253 for {set l 0} {$l < [llength $errlines]} {incr l} {
254 set line [lindex $errlines $l]
255 if {!([string length $line] == 40 && [string is xdigit $line])} {
256 if {[string match "fatal:*" $line]} {
257 if {[string match "fatal: ambiguous argument*" $line]
258 && $badrev ne {}} {
259 if {[llength $badrev] == 1} {
260 set err "unknown revision $badrev"
261 } else {
262 set err "unknown revisions: [join $badrev ", "]"
264 } else {
265 set err [join [lrange $errlines $l end] "\n"]
267 break
269 lappend badrev $line
272 error_popup "[mc "Error parsing revisions:"] $err"
273 return {}
275 set ret {}
276 set pos {}
277 set neg {}
278 set sdm 0
279 foreach id [split $ids "\n"] {
280 if {$id eq "--gitk-symmetric-diff-marker"} {
281 set sdm 4
282 } elseif {[string match "^*" $id]} {
283 if {$sdm != 1} {
284 lappend ret $id
285 if {$sdm == 3} {
286 set sdm 0
289 lappend neg [string range $id 1 end]
290 } else {
291 if {$sdm != 2} {
292 lappend ret $id
293 } else {
294 lset ret end [lindex $ret end]...$id
296 lappend pos $id
298 incr sdm -1
300 set vposids($view) $pos
301 set vnegids($view) $neg
302 return $ret
305 # Start off a git log process and arrange to read its output
306 proc start_rev_list {view} {
307 global startmsecs commitidx viewcomplete curview
308 global tclencoding
309 global viewargs viewargscmd viewfiles vfilelimit
310 global showlocalchanges
311 global viewactive viewinstances vmergeonly
312 global mainheadid
313 global vcanopt vflags vrevs vorigargs
315 set startmsecs [clock clicks -milliseconds]
316 set commitidx($view) 0
317 # these are set this way for the error exits
318 set viewcomplete($view) 1
319 set viewactive($view) 0
320 varcinit $view
322 set args $viewargs($view)
323 if {$viewargscmd($view) ne {}} {
324 if {[catch {
325 set str [exec sh -c $viewargscmd($view)]
326 } err]} {
327 error_popup "[mc "Error executing --argscmd command:"] $err"
328 return 0
330 set args [concat $args [split $str "\n"]]
332 set vcanopt($view) [parseviewargs $view $args]
334 set files $viewfiles($view)
335 if {$vmergeonly($view)} {
336 set files [unmerged_files $files]
337 if {$files eq {}} {
338 global nr_unmerged
339 if {$nr_unmerged == 0} {
340 error_popup [mc "No files selected: --merge specified but\
341 no files are unmerged."]
342 } else {
343 error_popup [mc "No files selected: --merge specified but\
344 no unmerged files are within file limit."]
346 return 0
349 set vfilelimit($view) $files
351 if {$vcanopt($view)} {
352 set revs [parseviewrevs $view $vrevs($view)]
353 if {$revs eq {}} {
354 return 0
356 set args [concat $vflags($view) $revs]
357 } else {
358 set args $vorigargs($view)
361 if {[catch {
362 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
363 --boundary $args "--" $files] r]
364 } err]} {
365 error_popup "[mc "Error executing git log:"] $err"
366 return 0
368 set i [reg_instance $fd]
369 set viewinstances($view) [list $i]
370 if {$showlocalchanges && $mainheadid ne {}} {
371 interestedin $mainheadid dodiffindex
373 fconfigure $fd -blocking 0 -translation lf -eofchar {}
374 if {$tclencoding != {}} {
375 fconfigure $fd -encoding $tclencoding
377 filerun $fd [list getcommitlines $fd $i $view 0]
378 nowbusy $view [mc "Reading"]
379 set viewcomplete($view) 0
380 set viewactive($view) 1
381 return 1
384 proc stop_instance {inst} {
385 global commfd leftover
387 set fd $commfd($inst)
388 catch {
389 set pid [pid $fd]
391 if {$::tcl_platform(platform) eq {windows}} {
392 exec kill -f $pid
393 } else {
394 exec kill $pid
397 catch {close $fd}
398 nukefile $fd
399 unset commfd($inst)
400 unset leftover($inst)
403 proc stop_backends {} {
404 global commfd
406 foreach inst [array names commfd] {
407 stop_instance $inst
411 proc stop_rev_list {view} {
412 global viewinstances
414 foreach inst $viewinstances($view) {
415 stop_instance $inst
417 set viewinstances($view) {}
420 proc reset_pending_select {selid} {
421 global pending_select mainheadid selectheadid
423 if {$selid ne {}} {
424 set pending_select $selid
425 } elseif {$selectheadid ne {}} {
426 set pending_select $selectheadid
427 } else {
428 set pending_select $mainheadid
432 proc getcommits {selid} {
433 global canv curview need_redisplay viewactive
435 initlayout
436 if {[start_rev_list $curview]} {
437 reset_pending_select $selid
438 show_status [mc "Reading commits..."]
439 set need_redisplay 1
440 } else {
441 show_status [mc "No commits selected"]
445 proc updatecommits {} {
446 global curview vcanopt vorigargs vfilelimit viewinstances
447 global viewactive viewcomplete tclencoding
448 global startmsecs showneartags showlocalchanges
449 global mainheadid pending_select
450 global isworktree
451 global varcid vposids vnegids vflags vrevs
453 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
454 set oldmainid $mainheadid
455 rereadrefs
456 if {$showlocalchanges} {
457 if {$mainheadid ne $oldmainid} {
458 dohidelocalchanges
460 if {[commitinview $mainheadid $curview]} {
461 dodiffindex
464 set view $curview
465 if {$vcanopt($view)} {
466 set oldpos $vposids($view)
467 set oldneg $vnegids($view)
468 set revs [parseviewrevs $view $vrevs($view)]
469 if {$revs eq {}} {
470 return
472 # note: getting the delta when negative refs change is hard,
473 # and could require multiple git log invocations, so in that
474 # case we ask git log for all the commits (not just the delta)
475 if {$oldneg eq $vnegids($view)} {
476 set newrevs {}
477 set npos 0
478 # take out positive refs that we asked for before or
479 # that we have already seen
480 foreach rev $revs {
481 if {[string length $rev] == 40} {
482 if {[lsearch -exact $oldpos $rev] < 0
483 && ![info exists varcid($view,$rev)]} {
484 lappend newrevs $rev
485 incr npos
487 } else {
488 lappend $newrevs $rev
491 if {$npos == 0} return
492 set revs $newrevs
493 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
495 set args [concat $vflags($view) $revs --not $oldpos]
496 } else {
497 set args $vorigargs($view)
499 if {[catch {
500 set fd [open [concat | git log --no-color -z --pretty=raw --parents \
501 --boundary $args "--" $vfilelimit($view)] r]
502 } err]} {
503 error_popup "[mc "Error executing git log:"] $err"
504 return
506 if {$viewactive($view) == 0} {
507 set startmsecs [clock clicks -milliseconds]
509 set i [reg_instance $fd]
510 lappend viewinstances($view) $i
511 fconfigure $fd -blocking 0 -translation lf -eofchar {}
512 if {$tclencoding != {}} {
513 fconfigure $fd -encoding $tclencoding
515 filerun $fd [list getcommitlines $fd $i $view 1]
516 incr viewactive($view)
517 set viewcomplete($view) 0
518 reset_pending_select {}
519 nowbusy $view "Reading"
520 if {$showneartags} {
521 getallcommits
525 proc reloadcommits {} {
526 global curview viewcomplete selectedline currentid thickerline
527 global showneartags treediffs commitinterest cached_commitrow
528 global targetid
530 set selid {}
531 if {$selectedline ne {}} {
532 set selid $currentid
535 if {!$viewcomplete($curview)} {
536 stop_rev_list $curview
538 resetvarcs $curview
539 set selectedline {}
540 catch {unset currentid}
541 catch {unset thickerline}
542 catch {unset treediffs}
543 readrefs
544 changedrefs
545 if {$showneartags} {
546 getallcommits
548 clear_display
549 catch {unset commitinterest}
550 catch {unset cached_commitrow}
551 catch {unset targetid}
552 setcanvscroll
553 getcommits $selid
554 return 0
557 # This makes a string representation of a positive integer which
558 # sorts as a string in numerical order
559 proc strrep {n} {
560 if {$n < 16} {
561 return [format "%x" $n]
562 } elseif {$n < 256} {
563 return [format "x%.2x" $n]
564 } elseif {$n < 65536} {
565 return [format "y%.4x" $n]
567 return [format "z%.8x" $n]
570 # Procedures used in reordering commits from git log (without
571 # --topo-order) into the order for display.
573 proc varcinit {view} {
574 global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
575 global vtokmod varcmod vrowmod varcix vlastins
577 set varcstart($view) {{}}
578 set vupptr($view) {0}
579 set vdownptr($view) {0}
580 set vleftptr($view) {0}
581 set vbackptr($view) {0}
582 set varctok($view) {{}}
583 set varcrow($view) {{}}
584 set vtokmod($view) {}
585 set varcmod($view) 0
586 set vrowmod($view) 0
587 set varcix($view) {{}}
588 set vlastins($view) {0}
591 proc resetvarcs {view} {
592 global varcid varccommits parents children vseedcount ordertok
594 foreach vid [array names varcid $view,*] {
595 unset varcid($vid)
596 unset children($vid)
597 unset parents($vid)
599 # some commits might have children but haven't been seen yet
600 foreach vid [array names children $view,*] {
601 unset children($vid)
603 foreach va [array names varccommits $view,*] {
604 unset varccommits($va)
606 foreach vd [array names vseedcount $view,*] {
607 unset vseedcount($vd)
609 catch {unset ordertok}
612 # returns a list of the commits with no children
613 proc seeds {v} {
614 global vdownptr vleftptr varcstart
616 set ret {}
617 set a [lindex $vdownptr($v) 0]
618 while {$a != 0} {
619 lappend ret [lindex $varcstart($v) $a]
620 set a [lindex $vleftptr($v) $a]
622 return $ret
625 proc newvarc {view id} {
626 global varcid varctok parents children vdatemode
627 global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
628 global commitdata commitinfo vseedcount varccommits vlastins
630 set a [llength $varctok($view)]
631 set vid $view,$id
632 if {[llength $children($vid)] == 0 || $vdatemode($view)} {
633 if {![info exists commitinfo($id)]} {
634 parsecommit $id $commitdata($id) 1
636 set cdate [lindex $commitinfo($id) 4]
637 if {![string is integer -strict $cdate]} {
638 set cdate 0
640 if {![info exists vseedcount($view,$cdate)]} {
641 set vseedcount($view,$cdate) -1
643 set c [incr vseedcount($view,$cdate)]
644 set cdate [expr {$cdate ^ 0xffffffff}]
645 set tok "s[strrep $cdate][strrep $c]"
646 } else {
647 set tok {}
649 set ka 0
650 if {[llength $children($vid)] > 0} {
651 set kid [lindex $children($vid) end]
652 set k $varcid($view,$kid)
653 if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
654 set ki $kid
655 set ka $k
656 set tok [lindex $varctok($view) $k]
659 if {$ka != 0} {
660 set i [lsearch -exact $parents($view,$ki) $id]
661 set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
662 append tok [strrep $j]
664 set c [lindex $vlastins($view) $ka]
665 if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
666 set c $ka
667 set b [lindex $vdownptr($view) $ka]
668 } else {
669 set b [lindex $vleftptr($view) $c]
671 while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
672 set c $b
673 set b [lindex $vleftptr($view) $c]
675 if {$c == $ka} {
676 lset vdownptr($view) $ka $a
677 lappend vbackptr($view) 0
678 } else {
679 lset vleftptr($view) $c $a
680 lappend vbackptr($view) $c
682 lset vlastins($view) $ka $a
683 lappend vupptr($view) $ka
684 lappend vleftptr($view) $b
685 if {$b != 0} {
686 lset vbackptr($view) $b $a
688 lappend varctok($view) $tok
689 lappend varcstart($view) $id
690 lappend vdownptr($view) 0
691 lappend varcrow($view) {}
692 lappend varcix($view) {}
693 set varccommits($view,$a) {}
694 lappend vlastins($view) 0
695 return $a
698 proc splitvarc {p v} {
699 global varcid varcstart varccommits varctok
700 global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
702 set oa $varcid($v,$p)
703 set ac $varccommits($v,$oa)
704 set i [lsearch -exact $varccommits($v,$oa) $p]
705 if {$i <= 0} return
706 set na [llength $varctok($v)]
707 # "%" sorts before "0"...
708 set tok "[lindex $varctok($v) $oa]%[strrep $i]"
709 lappend varctok($v) $tok
710 lappend varcrow($v) {}
711 lappend varcix($v) {}
712 set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
713 set varccommits($v,$na) [lrange $ac $i end]
714 lappend varcstart($v) $p
715 foreach id $varccommits($v,$na) {
716 set varcid($v,$id) $na
718 lappend vdownptr($v) [lindex $vdownptr($v) $oa]
719 lappend vlastins($v) [lindex $vlastins($v) $oa]
720 lset vdownptr($v) $oa $na
721 lset vlastins($v) $oa 0
722 lappend vupptr($v) $oa
723 lappend vleftptr($v) 0
724 lappend vbackptr($v) 0
725 for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
726 lset vupptr($v) $b $na
730 proc renumbervarc {a v} {
731 global parents children varctok varcstart varccommits
732 global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
734 set t1 [clock clicks -milliseconds]
735 set todo {}
736 set isrelated($a) 1
737 set kidchanged($a) 1
738 set ntot 0
739 while {$a != 0} {
740 if {[info exists isrelated($a)]} {
741 lappend todo $a
742 set id [lindex $varccommits($v,$a) end]
743 foreach p $parents($v,$id) {
744 if {[info exists varcid($v,$p)]} {
745 set isrelated($varcid($v,$p)) 1
749 incr ntot
750 set b [lindex $vdownptr($v) $a]
751 if {$b == 0} {
752 while {$a != 0} {
753 set b [lindex $vleftptr($v) $a]
754 if {$b != 0} break
755 set a [lindex $vupptr($v) $a]
758 set a $b
760 foreach a $todo {
761 if {![info exists kidchanged($a)]} continue
762 set id [lindex $varcstart($v) $a]
763 if {[llength $children($v,$id)] > 1} {
764 set children($v,$id) [lsort -command [list vtokcmp $v] \
765 $children($v,$id)]
767 set oldtok [lindex $varctok($v) $a]
768 if {!$vdatemode($v)} {
769 set tok {}
770 } else {
771 set tok $oldtok
773 set ka 0
774 set kid [last_real_child $v,$id]
775 if {$kid ne {}} {
776 set k $varcid($v,$kid)
777 if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
778 set ki $kid
779 set ka $k
780 set tok [lindex $varctok($v) $k]
783 if {$ka != 0} {
784 set i [lsearch -exact $parents($v,$ki) $id]
785 set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
786 append tok [strrep $j]
788 if {$tok eq $oldtok} {
789 continue
791 set id [lindex $varccommits($v,$a) end]
792 foreach p $parents($v,$id) {
793 if {[info exists varcid($v,$p)]} {
794 set kidchanged($varcid($v,$p)) 1
795 } else {
796 set sortkids($p) 1
799 lset varctok($v) $a $tok
800 set b [lindex $vupptr($v) $a]
801 if {$b != $ka} {
802 if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
803 modify_arc $v $ka
805 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
806 modify_arc $v $b
808 set c [lindex $vbackptr($v) $a]
809 set d [lindex $vleftptr($v) $a]
810 if {$c == 0} {
811 lset vdownptr($v) $b $d
812 } else {
813 lset vleftptr($v) $c $d
815 if {$d != 0} {
816 lset vbackptr($v) $d $c
818 if {[lindex $vlastins($v) $b] == $a} {
819 lset vlastins($v) $b $c
821 lset vupptr($v) $a $ka
822 set c [lindex $vlastins($v) $ka]
823 if {$c == 0 || \
824 [string compare $tok [lindex $varctok($v) $c]] < 0} {
825 set c $ka
826 set b [lindex $vdownptr($v) $ka]
827 } else {
828 set b [lindex $vleftptr($v) $c]
830 while {$b != 0 && \
831 [string compare $tok [lindex $varctok($v) $b]] >= 0} {
832 set c $b
833 set b [lindex $vleftptr($v) $c]
835 if {$c == $ka} {
836 lset vdownptr($v) $ka $a
837 lset vbackptr($v) $a 0
838 } else {
839 lset vleftptr($v) $c $a
840 lset vbackptr($v) $a $c
842 lset vleftptr($v) $a $b
843 if {$b != 0} {
844 lset vbackptr($v) $b $a
846 lset vlastins($v) $ka $a
849 foreach id [array names sortkids] {
850 if {[llength $children($v,$id)] > 1} {
851 set children($v,$id) [lsort -command [list vtokcmp $v] \
852 $children($v,$id)]
855 set t2 [clock clicks -milliseconds]
856 #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
859 # Fix up the graph after we have found out that in view $v,
860 # $p (a commit that we have already seen) is actually the parent
861 # of the last commit in arc $a.
862 proc fix_reversal {p a v} {
863 global varcid varcstart varctok vupptr
865 set pa $varcid($v,$p)
866 if {$p ne [lindex $varcstart($v) $pa]} {
867 splitvarc $p $v
868 set pa $varcid($v,$p)
870 # seeds always need to be renumbered
871 if {[lindex $vupptr($v) $pa] == 0 ||
872 [string compare [lindex $varctok($v) $a] \
873 [lindex $varctok($v) $pa]] > 0} {
874 renumbervarc $pa $v
878 proc insertrow {id p v} {
879 global cmitlisted children parents varcid varctok vtokmod
880 global varccommits ordertok commitidx numcommits curview
881 global targetid targetrow
883 readcommit $id
884 set vid $v,$id
885 set cmitlisted($vid) 1
886 set children($vid) {}
887 set parents($vid) [list $p]
888 set a [newvarc $v $id]
889 set varcid($vid) $a
890 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
891 modify_arc $v $a
893 lappend varccommits($v,$a) $id
894 set vp $v,$p
895 if {[llength [lappend children($vp) $id]] > 1} {
896 set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
897 catch {unset ordertok}
899 fix_reversal $p $a $v
900 incr commitidx($v)
901 if {$v == $curview} {
902 set numcommits $commitidx($v)
903 setcanvscroll
904 if {[info exists targetid]} {
905 if {![comes_before $targetid $p]} {
906 incr targetrow
912 proc insertfakerow {id p} {
913 global varcid varccommits parents children cmitlisted
914 global commitidx varctok vtokmod targetid targetrow curview numcommits
916 set v $curview
917 set a $varcid($v,$p)
918 set i [lsearch -exact $varccommits($v,$a) $p]
919 if {$i < 0} {
920 puts "oops: insertfakerow can't find [shortids $p] on arc $a"
921 return
923 set children($v,$id) {}
924 set parents($v,$id) [list $p]
925 set varcid($v,$id) $a
926 lappend children($v,$p) $id
927 set cmitlisted($v,$id) 1
928 set numcommits [incr commitidx($v)]
929 # note we deliberately don't update varcstart($v) even if $i == 0
930 set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
931 modify_arc $v $a $i
932 if {[info exists targetid]} {
933 if {![comes_before $targetid $p]} {
934 incr targetrow
937 setcanvscroll
938 drawvisible
941 proc removefakerow {id} {
942 global varcid varccommits parents children commitidx
943 global varctok vtokmod cmitlisted currentid selectedline
944 global targetid curview numcommits
946 set v $curview
947 if {[llength $parents($v,$id)] != 1} {
948 puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
949 return
951 set p [lindex $parents($v,$id) 0]
952 set a $varcid($v,$id)
953 set i [lsearch -exact $varccommits($v,$a) $id]
954 if {$i < 0} {
955 puts "oops: removefakerow can't find [shortids $id] on arc $a"
956 return
958 unset varcid($v,$id)
959 set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
960 unset parents($v,$id)
961 unset children($v,$id)
962 unset cmitlisted($v,$id)
963 set numcommits [incr commitidx($v) -1]
964 set j [lsearch -exact $children($v,$p) $id]
965 if {$j >= 0} {
966 set children($v,$p) [lreplace $children($v,$p) $j $j]
968 modify_arc $v $a $i
969 if {[info exist currentid] && $id eq $currentid} {
970 unset currentid
971 set selectedline {}
973 if {[info exists targetid] && $targetid eq $id} {
974 set targetid $p
976 setcanvscroll
977 drawvisible
980 proc first_real_child {vp} {
981 global children nullid nullid2
983 foreach id $children($vp) {
984 if {$id ne $nullid && $id ne $nullid2} {
985 return $id
988 return {}
991 proc last_real_child {vp} {
992 global children nullid nullid2
994 set kids $children($vp)
995 for {set i [llength $kids]} {[incr i -1] >= 0} {} {
996 set id [lindex $kids $i]
997 if {$id ne $nullid && $id ne $nullid2} {
998 return $id
1001 return {}
1004 proc vtokcmp {v a b} {
1005 global varctok varcid
1007 return [string compare [lindex $varctok($v) $varcid($v,$a)] \
1008 [lindex $varctok($v) $varcid($v,$b)]]
1011 # This assumes that if lim is not given, the caller has checked that
1012 # arc a's token is less than $vtokmod($v)
1013 proc modify_arc {v a {lim {}}} {
1014 global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
1016 if {$lim ne {}} {
1017 set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
1018 if {$c > 0} return
1019 if {$c == 0} {
1020 set r [lindex $varcrow($v) $a]
1021 if {$r ne {} && $vrowmod($v) <= $r + $lim} return
1024 set vtokmod($v) [lindex $varctok($v) $a]
1025 set varcmod($v) $a
1026 if {$v == $curview} {
1027 while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
1028 set a [lindex $vupptr($v) $a]
1029 set lim {}
1031 set r 0
1032 if {$a != 0} {
1033 if {$lim eq {}} {
1034 set lim [llength $varccommits($v,$a)]
1036 set r [expr {[lindex $varcrow($v) $a] + $lim}]
1038 set vrowmod($v) $r
1039 undolayout $r
1043 proc update_arcrows {v} {
1044 global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
1045 global varcid vrownum varcorder varcix varccommits
1046 global vupptr vdownptr vleftptr varctok
1047 global displayorder parentlist curview cached_commitrow
1049 if {$vrowmod($v) == $commitidx($v)} return
1050 if {$v == $curview} {
1051 if {[llength $displayorder] > $vrowmod($v)} {
1052 set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
1053 set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
1055 catch {unset cached_commitrow}
1057 set narctot [expr {[llength $varctok($v)] - 1}]
1058 set a $varcmod($v)
1059 while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
1060 # go up the tree until we find something that has a row number,
1061 # or we get to a seed
1062 set a [lindex $vupptr($v) $a]
1064 if {$a == 0} {
1065 set a [lindex $vdownptr($v) 0]
1066 if {$a == 0} return
1067 set vrownum($v) {0}
1068 set varcorder($v) [list $a]
1069 lset varcix($v) $a 0
1070 lset varcrow($v) $a 0
1071 set arcn 0
1072 set row 0
1073 } else {
1074 set arcn [lindex $varcix($v) $a]
1075 if {[llength $vrownum($v)] > $arcn + 1} {
1076 set vrownum($v) [lrange $vrownum($v) 0 $arcn]
1077 set varcorder($v) [lrange $varcorder($v) 0 $arcn]
1079 set row [lindex $varcrow($v) $a]
1081 while {1} {
1082 set p $a
1083 incr row [llength $varccommits($v,$a)]
1084 # go down if possible
1085 set b [lindex $vdownptr($v) $a]
1086 if {$b == 0} {
1087 # if not, go left, or go up until we can go left
1088 while {$a != 0} {
1089 set b [lindex $vleftptr($v) $a]
1090 if {$b != 0} break
1091 set a [lindex $vupptr($v) $a]
1093 if {$a == 0} break
1095 set a $b
1096 incr arcn
1097 lappend vrownum($v) $row
1098 lappend varcorder($v) $a
1099 lset varcix($v) $a $arcn
1100 lset varcrow($v) $a $row
1102 set vtokmod($v) [lindex $varctok($v) $p]
1103 set varcmod($v) $p
1104 set vrowmod($v) $row
1105 if {[info exists currentid]} {
1106 set selectedline [rowofcommit $currentid]
1110 # Test whether view $v contains commit $id
1111 proc commitinview {id v} {
1112 global varcid
1114 return [info exists varcid($v,$id)]
1117 # Return the row number for commit $id in the current view
1118 proc rowofcommit {id} {
1119 global varcid varccommits varcrow curview cached_commitrow
1120 global varctok vtokmod
1122 set v $curview
1123 if {![info exists varcid($v,$id)]} {
1124 puts "oops rowofcommit no arc for [shortids $id]"
1125 return {}
1127 set a $varcid($v,$id)
1128 if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
1129 update_arcrows $v
1131 if {[info exists cached_commitrow($id)]} {
1132 return $cached_commitrow($id)
1134 set i [lsearch -exact $varccommits($v,$a) $id]
1135 if {$i < 0} {
1136 puts "oops didn't find commit [shortids $id] in arc $a"
1137 return {}
1139 incr i [lindex $varcrow($v) $a]
1140 set cached_commitrow($id) $i
1141 return $i
1144 # Returns 1 if a is on an earlier row than b, otherwise 0
1145 proc comes_before {a b} {
1146 global varcid varctok curview
1148 set v $curview
1149 if {$a eq $b || ![info exists varcid($v,$a)] || \
1150 ![info exists varcid($v,$b)]} {
1151 return 0
1153 if {$varcid($v,$a) != $varcid($v,$b)} {
1154 return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
1155 [lindex $varctok($v) $varcid($v,$b)]] < 0}]
1157 return [expr {[rowofcommit $a] < [rowofcommit $b]}]
1160 proc bsearch {l elt} {
1161 if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
1162 return 0
1164 set lo 0
1165 set hi [llength $l]
1166 while {$hi - $lo > 1} {
1167 set mid [expr {int(($lo + $hi) / 2)}]
1168 set t [lindex $l $mid]
1169 if {$elt < $t} {
1170 set hi $mid
1171 } elseif {$elt > $t} {
1172 set lo $mid
1173 } else {
1174 return $mid
1177 return $lo
1180 # Make sure rows $start..$end-1 are valid in displayorder and parentlist
1181 proc make_disporder {start end} {
1182 global vrownum curview commitidx displayorder parentlist
1183 global varccommits varcorder parents vrowmod varcrow
1184 global d_valid_start d_valid_end
1186 if {$end > $vrowmod($curview)} {
1187 update_arcrows $curview
1189 set ai [bsearch $vrownum($curview) $start]
1190 set start [lindex $vrownum($curview) $ai]
1191 set narc [llength $vrownum($curview)]
1192 for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
1193 set a [lindex $varcorder($curview) $ai]
1194 set l [llength $displayorder]
1195 set al [llength $varccommits($curview,$a)]
1196 if {$l < $r + $al} {
1197 if {$l < $r} {
1198 set pad [ntimes [expr {$r - $l}] {}]
1199 set displayorder [concat $displayorder $pad]
1200 set parentlist [concat $parentlist $pad]
1201 } elseif {$l > $r} {
1202 set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
1203 set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
1205 foreach id $varccommits($curview,$a) {
1206 lappend displayorder $id
1207 lappend parentlist $parents($curview,$id)
1209 } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
1210 set i $r
1211 foreach id $varccommits($curview,$a) {
1212 lset displayorder $i $id
1213 lset parentlist $i $parents($curview,$id)
1214 incr i
1217 incr r $al
1221 proc commitonrow {row} {
1222 global displayorder
1224 set id [lindex $displayorder $row]
1225 if {$id eq {}} {
1226 make_disporder $row [expr {$row + 1}]
1227 set id [lindex $displayorder $row]
1229 return $id
1232 proc closevarcs {v} {
1233 global varctok varccommits varcid parents children
1234 global cmitlisted commitidx vtokmod
1236 set missing_parents 0
1237 set scripts {}
1238 set narcs [llength $varctok($v)]
1239 for {set a 1} {$a < $narcs} {incr a} {
1240 set id [lindex $varccommits($v,$a) end]
1241 foreach p $parents($v,$id) {
1242 if {[info exists varcid($v,$p)]} continue
1243 # add p as a new commit
1244 incr missing_parents
1245 set cmitlisted($v,$p) 0
1246 set parents($v,$p) {}
1247 if {[llength $children($v,$p)] == 1 &&
1248 [llength $parents($v,$id)] == 1} {
1249 set b $a
1250 } else {
1251 set b [newvarc $v $p]
1253 set varcid($v,$p) $b
1254 if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
1255 modify_arc $v $b
1257 lappend varccommits($v,$b) $p
1258 incr commitidx($v)
1259 set scripts [check_interest $p $scripts]
1262 if {$missing_parents > 0} {
1263 foreach s $scripts {
1264 eval $s
1269 # Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
1270 # Assumes we already have an arc for $rwid.
1271 proc rewrite_commit {v id rwid} {
1272 global children parents varcid varctok vtokmod varccommits
1274 foreach ch $children($v,$id) {
1275 # make $rwid be $ch's parent in place of $id
1276 set i [lsearch -exact $parents($v,$ch) $id]
1277 if {$i < 0} {
1278 puts "oops rewrite_commit didn't find $id in parent list for $ch"
1280 set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
1281 # add $ch to $rwid's children and sort the list if necessary
1282 if {[llength [lappend children($v,$rwid) $ch]] > 1} {
1283 set children($v,$rwid) [lsort -command [list vtokcmp $v] \
1284 $children($v,$rwid)]
1286 # fix the graph after joining $id to $rwid
1287 set a $varcid($v,$ch)
1288 fix_reversal $rwid $a $v
1289 # parentlist is wrong for the last element of arc $a
1290 # even if displayorder is right, hence the 3rd arg here
1291 modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
1295 # Mechanism for registering a command to be executed when we come
1296 # across a particular commit. To handle the case when only the
1297 # prefix of the commit is known, the commitinterest array is now
1298 # indexed by the first 4 characters of the ID. Each element is a
1299 # list of id, cmd pairs.
1300 proc interestedin {id cmd} {
1301 global commitinterest
1303 lappend commitinterest([string range $id 0 3]) $id $cmd
1306 proc check_interest {id scripts} {
1307 global commitinterest
1309 set prefix [string range $id 0 3]
1310 if {[info exists commitinterest($prefix)]} {
1311 set newlist {}
1312 foreach {i script} $commitinterest($prefix) {
1313 if {[string match "$i*" $id]} {
1314 lappend scripts [string map [list "%I" $id "%P" $i] $script]
1315 } else {
1316 lappend newlist $i $script
1319 if {$newlist ne {}} {
1320 set commitinterest($prefix) $newlist
1321 } else {
1322 unset commitinterest($prefix)
1325 return $scripts
1328 proc getcommitlines {fd inst view updating} {
1329 global cmitlisted leftover
1330 global commitidx commitdata vdatemode
1331 global parents children curview hlview
1332 global idpending ordertok
1333 global varccommits varcid varctok vtokmod vfilelimit
1335 set stuff [read $fd 500000]
1336 # git log doesn't terminate the last commit with a null...
1337 if {$stuff == {} && $leftover($inst) ne {} && [eof $fd]} {
1338 set stuff "\0"
1340 if {$stuff == {}} {
1341 if {![eof $fd]} {
1342 return 1
1344 global commfd viewcomplete viewactive viewname
1345 global viewinstances
1346 unset commfd($inst)
1347 set i [lsearch -exact $viewinstances($view) $inst]
1348 if {$i >= 0} {
1349 set viewinstances($view) [lreplace $viewinstances($view) $i $i]
1351 # set it blocking so we wait for the process to terminate
1352 fconfigure $fd -blocking 1
1353 if {[catch {close $fd} err]} {
1354 set fv {}
1355 if {$view != $curview} {
1356 set fv " for the \"$viewname($view)\" view"
1358 if {[string range $err 0 4] == "usage"} {
1359 set err "Gitk: error reading commits$fv:\
1360 bad arguments to git log."
1361 if {$viewname($view) eq "Command line"} {
1362 append err \
1363 " (Note: arguments to gitk are passed to git log\
1364 to allow selection of commits to be displayed.)"
1366 } else {
1367 set err "Error reading commits$fv: $err"
1369 error_popup $err
1371 if {[incr viewactive($view) -1] <= 0} {
1372 set viewcomplete($view) 1
1373 # Check if we have seen any ids listed as parents that haven't
1374 # appeared in the list
1375 closevarcs $view
1376 notbusy $view
1378 if {$view == $curview} {
1379 run chewcommits
1381 return 0
1383 set start 0
1384 set gotsome 0
1385 set scripts {}
1386 while 1 {
1387 set i [string first "\0" $stuff $start]
1388 if {$i < 0} {
1389 append leftover($inst) [string range $stuff $start end]
1390 break
1392 if {$start == 0} {
1393 set cmit $leftover($inst)
1394 append cmit [string range $stuff 0 [expr {$i - 1}]]
1395 set leftover($inst) {}
1396 } else {
1397 set cmit [string range $stuff $start [expr {$i - 1}]]
1399 set start [expr {$i + 1}]
1400 set j [string first "\n" $cmit]
1401 set ok 0
1402 set listed 1
1403 if {$j >= 0 && [string match "commit *" $cmit]} {
1404 set ids [string range $cmit 7 [expr {$j - 1}]]
1405 if {[string match {[-^<>]*} $ids]} {
1406 switch -- [string index $ids 0] {
1407 "-" {set listed 0}
1408 "^" {set listed 2}
1409 "<" {set listed 3}
1410 ">" {set listed 4}
1412 set ids [string range $ids 1 end]
1414 set ok 1
1415 foreach id $ids {
1416 if {[string length $id] != 40} {
1417 set ok 0
1418 break
1422 if {!$ok} {
1423 set shortcmit $cmit
1424 if {[string length $shortcmit] > 80} {
1425 set shortcmit "[string range $shortcmit 0 80]..."
1427 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
1428 exit 1
1430 set id [lindex $ids 0]
1431 set vid $view,$id
1433 if {!$listed && $updating && ![info exists varcid($vid)] &&
1434 $vfilelimit($view) ne {}} {
1435 # git log doesn't rewrite parents for unlisted commits
1436 # when doing path limiting, so work around that here
1437 # by working out the rewritten parent with git rev-list
1438 # and if we already know about it, using the rewritten
1439 # parent as a substitute parent for $id's children.
1440 if {![catch {
1441 set rwid [exec git rev-list --first-parent --max-count=1 \
1442 $id -- $vfilelimit($view)]
1443 }]} {
1444 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1445 # use $rwid in place of $id
1446 rewrite_commit $view $id $rwid
1447 continue
1452 set a 0
1453 if {[info exists varcid($vid)]} {
1454 if {$cmitlisted($vid) || !$listed} continue
1455 set a $varcid($vid)
1457 if {$listed} {
1458 set olds [lrange $ids 1 end]
1459 } else {
1460 set olds {}
1462 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
1463 set cmitlisted($vid) $listed
1464 set parents($vid) $olds
1465 if {![info exists children($vid)]} {
1466 set children($vid) {}
1467 } elseif {$a == 0 && [llength $children($vid)] == 1} {
1468 set k [lindex $children($vid) 0]
1469 if {[llength $parents($view,$k)] == 1 &&
1470 (!$vdatemode($view) ||
1471 $varcid($view,$k) == [llength $varctok($view)] - 1)} {
1472 set a $varcid($view,$k)
1475 if {$a == 0} {
1476 # new arc
1477 set a [newvarc $view $id]
1479 if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
1480 modify_arc $view $a
1482 if {![info exists varcid($vid)]} {
1483 set varcid($vid) $a
1484 lappend varccommits($view,$a) $id
1485 incr commitidx($view)
1488 set i 0
1489 foreach p $olds {
1490 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
1491 set vp $view,$p
1492 if {[llength [lappend children($vp) $id]] > 1 &&
1493 [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
1494 set children($vp) [lsort -command [list vtokcmp $view] \
1495 $children($vp)]
1496 catch {unset ordertok}
1498 if {[info exists varcid($view,$p)]} {
1499 fix_reversal $p $a $view
1502 incr i
1505 set scripts [check_interest $id $scripts]
1506 set gotsome 1
1508 if {$gotsome} {
1509 global numcommits hlview
1511 if {$view == $curview} {
1512 set numcommits $commitidx($view)
1513 run chewcommits
1515 if {[info exists hlview] && $view == $hlview} {
1516 # we never actually get here...
1517 run vhighlightmore
1519 foreach s $scripts {
1520 eval $s
1523 return 2
1526 proc chewcommits {} {
1527 global curview hlview viewcomplete
1528 global pending_select
1530 layoutmore
1531 if {$viewcomplete($curview)} {
1532 global commitidx varctok
1533 global numcommits startmsecs
1535 if {[info exists pending_select]} {
1536 update
1537 reset_pending_select {}
1539 if {[commitinview $pending_select $curview]} {
1540 selectline [rowofcommit $pending_select] 1
1541 } else {
1542 set row [first_real_row]
1543 selectline $row 1
1546 if {$commitidx($curview) > 0} {
1547 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
1548 #puts "overall $ms ms for $numcommits commits"
1549 #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
1550 } else {
1551 show_status [mc "No commits selected"]
1553 notbusy layout
1555 return 0
1558 proc do_readcommit {id} {
1559 global tclencoding
1561 # Invoke git-log to handle automatic encoding conversion
1562 set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1563 # Read the results using i18n.logoutputencoding
1564 fconfigure $fd -translation lf -eofchar {}
1565 if {$tclencoding != {}} {
1566 fconfigure $fd -encoding $tclencoding
1568 set contents [read $fd]
1569 close $fd
1570 # Remove the heading line
1571 regsub {^commit [0-9a-f]+\n} $contents {} contents
1573 return $contents
1576 proc readcommit {id} {
1577 if {[catch {set contents [do_readcommit $id]}]} return
1578 parsecommit $id $contents 1
1581 proc parsecommit {id contents listed} {
1582 global commitinfo cdate
1584 set inhdr 1
1585 set comment {}
1586 set headline {}
1587 set auname {}
1588 set audate {}
1589 set comname {}
1590 set comdate {}
1591 set hdrend [string first "\n\n" $contents]
1592 if {$hdrend < 0} {
1593 # should never happen...
1594 set hdrend [string length $contents]
1596 set header [string range $contents 0 [expr {$hdrend - 1}]]
1597 set comment [string range $contents [expr {$hdrend + 2}] end]
1598 foreach line [split $header "\n"] {
1599 set tag [lindex $line 0]
1600 if {$tag == "author"} {
1601 set audate [lindex $line end-1]
1602 set auname [lrange $line 1 end-2]
1603 } elseif {$tag == "committer"} {
1604 set comdate [lindex $line end-1]
1605 set comname [lrange $line 1 end-2]
1608 set headline {}
1609 # take the first non-blank line of the comment as the headline
1610 set headline [string trimleft $comment]
1611 set i [string first "\n" $headline]
1612 if {$i >= 0} {
1613 set headline [string range $headline 0 $i]
1615 set headline [string trimright $headline]
1616 set i [string first "\r" $headline]
1617 if {$i >= 0} {
1618 set headline [string trimright [string range $headline 0 $i]]
1620 if {!$listed} {
1621 # git log indents the comment by 4 spaces;
1622 # if we got this via git cat-file, add the indentation
1623 set newcomment {}
1624 foreach line [split $comment "\n"] {
1625 append newcomment " "
1626 append newcomment $line
1627 append newcomment "\n"
1629 set comment $newcomment
1631 if {$comdate != {}} {
1632 set cdate($id) $comdate
1634 set commitinfo($id) [list $headline $auname $audate \
1635 $comname $comdate $comment]
1638 proc getcommit {id} {
1639 global commitdata commitinfo
1641 if {[info exists commitdata($id)]} {
1642 parsecommit $id $commitdata($id) 1
1643 } else {
1644 readcommit $id
1645 if {![info exists commitinfo($id)]} {
1646 set commitinfo($id) [list [mc "No commit information available"]]
1649 return 1
1652 # Expand an abbreviated commit ID to a list of full 40-char IDs that match
1653 # and are present in the current view.
1654 # This is fairly slow...
1655 proc longid {prefix} {
1656 global varcid curview
1658 set ids {}
1659 foreach match [array names varcid "$curview,$prefix*"] {
1660 lappend ids [lindex [split $match ","] 1]
1662 return $ids
1665 proc readrefs {} {
1666 global tagids idtags headids idheads tagobjid
1667 global otherrefids idotherrefs mainhead mainheadid
1668 global selecthead selectheadid
1670 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1671 catch {unset $v}
1673 set refd [open [list | git show-ref -d] r]
1674 while {[gets $refd line] >= 0} {
1675 if {[string index $line 40] ne " "} continue
1676 set id [string range $line 0 39]
1677 set ref [string range $line 41 end]
1678 if {![string match "refs/*" $ref]} continue
1679 set name [string range $ref 5 end]
1680 if {[string match "remotes/*" $name]} {
1681 if {![string match "*/HEAD" $name]} {
1682 set headids($name) $id
1683 lappend idheads($id) $name
1685 } elseif {[string match "heads/*" $name]} {
1686 set name [string range $name 6 end]
1687 set headids($name) $id
1688 lappend idheads($id) $name
1689 } elseif {[string match "tags/*" $name]} {
1690 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
1691 # which is what we want since the former is the commit ID
1692 set name [string range $name 5 end]
1693 if {[string match "*^{}" $name]} {
1694 set name [string range $name 0 end-3]
1695 } else {
1696 set tagobjid($name) $id
1698 set tagids($name) $id
1699 lappend idtags($id) $name
1700 } else {
1701 set otherrefids($name) $id
1702 lappend idotherrefs($id) $name
1705 catch {close $refd}
1706 set mainhead {}
1707 set mainheadid {}
1708 catch {
1709 set mainheadid [exec git rev-parse HEAD]
1710 set thehead [exec git symbolic-ref HEAD]
1711 if {[string match "refs/heads/*" $thehead]} {
1712 set mainhead [string range $thehead 11 end]
1715 set selectheadid {}
1716 if {$selecthead ne {}} {
1717 catch {
1718 set selectheadid [exec git rev-parse --verify $selecthead]
1723 # skip over fake commits
1724 proc first_real_row {} {
1725 global nullid nullid2 numcommits
1727 for {set row 0} {$row < $numcommits} {incr row} {
1728 set id [commitonrow $row]
1729 if {$id ne $nullid && $id ne $nullid2} {
1730 break
1733 return $row
1736 # update things for a head moved to a child of its previous location
1737 proc movehead {id name} {
1738 global headids idheads
1740 removehead $headids($name) $name
1741 set headids($name) $id
1742 lappend idheads($id) $name
1745 # update things when a head has been removed
1746 proc removehead {id name} {
1747 global headids idheads
1749 if {$idheads($id) eq $name} {
1750 unset idheads($id)
1751 } else {
1752 set i [lsearch -exact $idheads($id) $name]
1753 if {$i >= 0} {
1754 set idheads($id) [lreplace $idheads($id) $i $i]
1757 unset headids($name)
1760 proc make_transient {window origin} {
1761 global have_tk85
1763 # In MacOS Tk 8.4 transient appears to work by setting
1764 # overrideredirect, which is utterly useless, since the
1765 # windows get no border, and are not even kept above
1766 # the parent.
1767 if {!$have_tk85 && [tk windowingsystem] eq {aqua}} return
1769 wm transient $window $origin
1771 # Windows fails to place transient windows normally, so
1772 # schedule a callback to center them on the parent.
1773 if {[tk windowingsystem] eq {win32}} {
1774 after idle [list tk::PlaceWindow $window widget $origin]
1778 proc show_error {w top msg} {
1779 message $w.m -text $msg -justify center -aspect 400
1780 pack $w.m -side top -fill x -padx 20 -pady 20
1781 button $w.ok -text [mc OK] -command "destroy $top"
1782 pack $w.ok -side bottom -fill x
1783 bind $top <Visibility> "grab $top; focus $top"
1784 bind $top <Key-Return> "destroy $top"
1785 bind $top <Key-space> "destroy $top"
1786 bind $top <Key-Escape> "destroy $top"
1787 tkwait window $top
1790 proc error_popup {msg {owner .}} {
1791 set w .error
1792 toplevel $w
1793 make_transient $w $owner
1794 show_error $w $w $msg
1797 proc confirm_popup {msg {owner .}} {
1798 global confirm_ok
1799 set confirm_ok 0
1800 set w .confirm
1801 toplevel $w
1802 make_transient $w $owner
1803 message $w.m -text $msg -justify center -aspect 400
1804 pack $w.m -side top -fill x -padx 20 -pady 20
1805 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
1806 pack $w.ok -side left -fill x
1807 button $w.cancel -text [mc Cancel] -command "destroy $w"
1808 pack $w.cancel -side right -fill x
1809 bind $w <Visibility> "grab $w; focus $w"
1810 bind $w <Key-Return> "set confirm_ok 1; destroy $w"
1811 bind $w <Key-space> "set confirm_ok 1; destroy $w"
1812 bind $w <Key-Escape> "destroy $w"
1813 tkwait window $w
1814 return $confirm_ok
1817 proc setoptions {} {
1818 option add *Panedwindow.showHandle 1 startupFile
1819 option add *Panedwindow.sashRelief raised startupFile
1820 option add *Button.font uifont startupFile
1821 option add *Checkbutton.font uifont startupFile
1822 option add *Radiobutton.font uifont startupFile
1823 option add *Menu.font uifont startupFile
1824 option add *Menubutton.font uifont startupFile
1825 option add *Label.font uifont startupFile
1826 option add *Message.font uifont startupFile
1827 option add *Entry.font uifont startupFile
1830 # Make a menu and submenus.
1831 # m is the window name for the menu, items is the list of menu items to add.
1832 # Each item is a list {mc label type description options...}
1833 # mc is ignored; it's so we can put mc there to alert xgettext
1834 # label is the string that appears in the menu
1835 # type is cascade, command or radiobutton (should add checkbutton)
1836 # description depends on type; it's the sublist for cascade, the
1837 # command to invoke for command, or {variable value} for radiobutton
1838 proc makemenu {m items} {
1839 menu $m
1840 if {[tk windowingsystem] eq {aqua}} {
1841 set Meta1 Cmd
1842 } else {
1843 set Meta1 Ctrl
1845 foreach i $items {
1846 set name [mc [lindex $i 1]]
1847 set type [lindex $i 2]
1848 set thing [lindex $i 3]
1849 set params [list $type]
1850 if {$name ne {}} {
1851 set u [string first "&" [string map {&& x} $name]]
1852 lappend params -label [string map {&& & & {}} $name]
1853 if {$u >= 0} {
1854 lappend params -underline $u
1857 switch -- $type {
1858 "cascade" {
1859 set submenu [string tolower [string map {& ""} [lindex $i 1]]]
1860 lappend params -menu $m.$submenu
1862 "command" {
1863 lappend params -command $thing
1865 "radiobutton" {
1866 lappend params -variable [lindex $thing 0] \
1867 -value [lindex $thing 1]
1870 set tail [lrange $i 4 end]
1871 regsub -all {\yMeta1\y} $tail $Meta1 tail
1872 eval $m add $params $tail
1873 if {$type eq "cascade"} {
1874 makemenu $m.$submenu $thing
1879 # translate string and remove ampersands
1880 proc mca {str} {
1881 return [string map {&& & & {}} [mc $str]]
1884 proc makewindow {} {
1885 global canv canv2 canv3 linespc charspc ctext cflist cscroll
1886 global tabstop
1887 global findtype findtypemenu findloc findstring fstring geometry
1888 global entries sha1entry sha1string sha1but
1889 global diffcontextstring diffcontext
1890 global ignorespace
1891 global maincursor textcursor curtextcursor
1892 global rowctxmenu fakerowmenu mergemax wrapcomment
1893 global highlight_files gdttype
1894 global searchstring sstring
1895 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
1896 global headctxmenu progresscanv progressitem progresscoords statusw
1897 global fprogitem fprogcoord lastprogupdate progupdatepending
1898 global rprogitem rprogcoord rownumsel numcommits
1899 global have_tk85
1901 # The "mc" arguments here are purely so that xgettext
1902 # sees the following string as needing to be translated
1903 makemenu .bar {
1904 {mc "File" cascade {
1905 {mc "Update" command updatecommits -accelerator F5}
1906 {mc "Reload" command reloadcommits -accelerator Meta1-F5}
1907 {mc "Reread references" command rereadrefs}
1908 {mc "List references" command showrefs -accelerator F2}
1909 {mc "Quit" command doquit -accelerator Meta1-Q}
1911 {mc "Edit" cascade {
1912 {mc "Preferences" command doprefs}
1914 {mc "View" cascade {
1915 {mc "New view..." command {newview 0} -accelerator Shift-F4}
1916 {mc "Edit view..." command editview -state disabled -accelerator F4}
1917 {mc "Delete view" command delview -state disabled}
1918 {xx "" separator}
1919 {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
1921 {mc "Help" cascade {
1922 {mc "About gitk" command about}
1923 {mc "Key bindings" command keys}
1926 . configure -menu .bar
1928 # the gui has upper and lower half, parts of a paned window.
1929 panedwindow .ctop -orient vertical
1931 # possibly use assumed geometry
1932 if {![info exists geometry(pwsash0)]} {
1933 set geometry(topheight) [expr {15 * $linespc}]
1934 set geometry(topwidth) [expr {80 * $charspc}]
1935 set geometry(botheight) [expr {15 * $linespc}]
1936 set geometry(botwidth) [expr {50 * $charspc}]
1937 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
1938 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
1941 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
1942 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
1943 frame .tf.histframe
1944 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
1946 # create three canvases
1947 set cscroll .tf.histframe.csb
1948 set canv .tf.histframe.pwclist.canv
1949 canvas $canv \
1950 -selectbackground $selectbgcolor \
1951 -background $bgcolor -bd 0 \
1952 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
1953 .tf.histframe.pwclist add $canv
1954 set canv2 .tf.histframe.pwclist.canv2
1955 canvas $canv2 \
1956 -selectbackground $selectbgcolor \
1957 -background $bgcolor -bd 0 -yscrollincr $linespc
1958 .tf.histframe.pwclist add $canv2
1959 set canv3 .tf.histframe.pwclist.canv3
1960 canvas $canv3 \
1961 -selectbackground $selectbgcolor \
1962 -background $bgcolor -bd 0 -yscrollincr $linespc
1963 .tf.histframe.pwclist add $canv3
1964 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
1965 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
1967 # a scroll bar to rule them
1968 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
1969 pack $cscroll -side right -fill y
1970 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
1971 lappend bglist $canv $canv2 $canv3
1972 pack .tf.histframe.pwclist -fill both -expand 1 -side left
1974 # we have two button bars at bottom of top frame. Bar 1
1975 frame .tf.bar
1976 frame .tf.lbar -height 15
1978 set sha1entry .tf.bar.sha1
1979 set entries $sha1entry
1980 set sha1but .tf.bar.sha1label
1981 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
1982 -command gotocommit -width 8
1983 $sha1but conf -disabledforeground [$sha1but cget -foreground]
1984 pack .tf.bar.sha1label -side left
1985 entry $sha1entry -width 40 -font textfont -textvariable sha1string
1986 trace add variable sha1string write sha1change
1987 pack $sha1entry -side left -pady 2
1989 image create bitmap bm-left -data {
1990 #define left_width 16
1991 #define left_height 16
1992 static unsigned char left_bits[] = {
1993 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
1994 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
1995 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
1997 image create bitmap bm-right -data {
1998 #define right_width 16
1999 #define right_height 16
2000 static unsigned char right_bits[] = {
2001 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
2002 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
2003 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
2005 button .tf.bar.leftbut -image bm-left -command goback \
2006 -state disabled -width 26
2007 pack .tf.bar.leftbut -side left -fill y
2008 button .tf.bar.rightbut -image bm-right -command goforw \
2009 -state disabled -width 26
2010 pack .tf.bar.rightbut -side left -fill y
2012 label .tf.bar.rowlabel -text [mc "Row"]
2013 set rownumsel {}
2014 label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
2015 -relief sunken -anchor e
2016 label .tf.bar.rowlabel2 -text "/"
2017 label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
2018 -relief sunken -anchor e
2019 pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
2020 -side left
2021 global selectedline
2022 trace add variable selectedline write selectedline_change
2024 # Status label and progress bar
2025 set statusw .tf.bar.status
2026 label $statusw -width 15 -relief sunken
2027 pack $statusw -side left -padx 5
2028 set h [expr {[font metrics uifont -linespace] + 2}]
2029 set progresscanv .tf.bar.progress
2030 canvas $progresscanv -relief sunken -height $h -borderwidth 2
2031 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
2032 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
2033 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
2034 pack $progresscanv -side right -expand 1 -fill x
2035 set progresscoords {0 0}
2036 set fprogcoord 0
2037 set rprogcoord 0
2038 bind $progresscanv <Configure> adjustprogress
2039 set lastprogupdate [clock clicks -milliseconds]
2040 set progupdatepending 0
2042 # build up the bottom bar of upper window
2043 label .tf.lbar.flabel -text "[mc "Find"] "
2044 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
2045 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
2046 label .tf.lbar.flab2 -text " [mc "commit"] "
2047 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
2048 -side left -fill y
2049 set gdttype [mc "containing:"]
2050 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
2051 [mc "containing:"] \
2052 [mc "touching paths:"] \
2053 [mc "adding/removing string:"]]
2054 trace add variable gdttype write gdttype_change
2055 pack .tf.lbar.gdttype -side left -fill y
2057 set findstring {}
2058 set fstring .tf.lbar.findstring
2059 lappend entries $fstring
2060 entry $fstring -width 30 -font textfont -textvariable findstring
2061 trace add variable findstring write find_change
2062 set findtype [mc "Exact"]
2063 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
2064 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
2065 trace add variable findtype write findcom_change
2066 set findloc [mc "All fields"]
2067 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
2068 [mc "Comments"] [mc "Author"] [mc "Committer"]
2069 trace add variable findloc write find_change
2070 pack .tf.lbar.findloc -side right
2071 pack .tf.lbar.findtype -side right
2072 pack $fstring -side left -expand 1 -fill x
2074 # Finish putting the upper half of the viewer together
2075 pack .tf.lbar -in .tf -side bottom -fill x
2076 pack .tf.bar -in .tf -side bottom -fill x
2077 pack .tf.histframe -fill both -side top -expand 1
2078 .ctop add .tf
2079 .ctop paneconfigure .tf -height $geometry(topheight)
2080 .ctop paneconfigure .tf -width $geometry(topwidth)
2082 # now build up the bottom
2083 panedwindow .pwbottom -orient horizontal
2085 # lower left, a text box over search bar, scroll bar to the right
2086 # if we know window height, then that will set the lower text height, otherwise
2087 # we set lower text height which will drive window height
2088 if {[info exists geometry(main)]} {
2089 frame .bleft -width $geometry(botwidth)
2090 } else {
2091 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
2093 frame .bleft.top
2094 frame .bleft.mid
2095 frame .bleft.bottom
2097 button .bleft.top.search -text [mc "Search"] -command dosearch
2098 pack .bleft.top.search -side left -padx 5
2099 set sstring .bleft.top.sstring
2100 entry $sstring -width 20 -font textfont -textvariable searchstring
2101 lappend entries $sstring
2102 trace add variable searchstring write incrsearch
2103 pack $sstring -side left -expand 1 -fill x
2104 radiobutton .bleft.mid.diff -text [mc "Diff"] \
2105 -command changediffdisp -variable diffelide -value {0 0}
2106 radiobutton .bleft.mid.old -text [mc "Old version"] \
2107 -command changediffdisp -variable diffelide -value {0 1}
2108 radiobutton .bleft.mid.new -text [mc "New version"] \
2109 -command changediffdisp -variable diffelide -value {1 0}
2110 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
2111 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
2112 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
2113 -from 1 -increment 1 -to 10000000 \
2114 -validate all -validatecommand "diffcontextvalidate %P" \
2115 -textvariable diffcontextstring
2116 .bleft.mid.diffcontext set $diffcontext
2117 trace add variable diffcontextstring write diffcontextchange
2118 lappend entries .bleft.mid.diffcontext
2119 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
2120 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
2121 -command changeignorespace -variable ignorespace
2122 pack .bleft.mid.ignspace -side left -padx 5
2123 set ctext .bleft.bottom.ctext
2124 text $ctext -background $bgcolor -foreground $fgcolor \
2125 -state disabled -font textfont \
2126 -yscrollcommand scrolltext -wrap none \
2127 -xscrollcommand ".bleft.bottom.sbhorizontal set"
2128 if {$have_tk85} {
2129 $ctext conf -tabstyle wordprocessor
2131 scrollbar .bleft.bottom.sb -command "$ctext yview"
2132 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
2133 -width 10
2134 pack .bleft.top -side top -fill x
2135 pack .bleft.mid -side top -fill x
2136 grid $ctext .bleft.bottom.sb -sticky nsew
2137 grid .bleft.bottom.sbhorizontal -sticky ew
2138 grid columnconfigure .bleft.bottom 0 -weight 1
2139 grid rowconfigure .bleft.bottom 0 -weight 1
2140 grid rowconfigure .bleft.bottom 1 -weight 0
2141 pack .bleft.bottom -side top -fill both -expand 1
2142 lappend bglist $ctext
2143 lappend fglist $ctext
2145 $ctext tag conf comment -wrap $wrapcomment
2146 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
2147 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
2148 $ctext tag conf d0 -fore [lindex $diffcolors 0]
2149 $ctext tag conf dresult -fore [lindex $diffcolors 1]
2150 $ctext tag conf m0 -fore red
2151 $ctext tag conf m1 -fore blue
2152 $ctext tag conf m2 -fore green
2153 $ctext tag conf m3 -fore purple
2154 $ctext tag conf m4 -fore brown
2155 $ctext tag conf m5 -fore "#009090"
2156 $ctext tag conf m6 -fore magenta
2157 $ctext tag conf m7 -fore "#808000"
2158 $ctext tag conf m8 -fore "#009000"
2159 $ctext tag conf m9 -fore "#ff0080"
2160 $ctext tag conf m10 -fore cyan
2161 $ctext tag conf m11 -fore "#b07070"
2162 $ctext tag conf m12 -fore "#70b0f0"
2163 $ctext tag conf m13 -fore "#70f0b0"
2164 $ctext tag conf m14 -fore "#f0b070"
2165 $ctext tag conf m15 -fore "#ff70b0"
2166 $ctext tag conf mmax -fore darkgrey
2167 set mergemax 16
2168 $ctext tag conf mresult -font textfontbold
2169 $ctext tag conf msep -font textfontbold
2170 $ctext tag conf found -back yellow
2172 .pwbottom add .bleft
2173 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
2175 # lower right
2176 frame .bright
2177 frame .bright.mode
2178 radiobutton .bright.mode.patch -text [mc "Patch"] \
2179 -command reselectline -variable cmitmode -value "patch"
2180 radiobutton .bright.mode.tree -text [mc "Tree"] \
2181 -command reselectline -variable cmitmode -value "tree"
2182 grid .bright.mode.patch .bright.mode.tree -sticky ew
2183 pack .bright.mode -side top -fill x
2184 set cflist .bright.cfiles
2185 set indent [font measure mainfont "nn"]
2186 text $cflist \
2187 -selectbackground $selectbgcolor \
2188 -background $bgcolor -foreground $fgcolor \
2189 -font mainfont \
2190 -tabs [list $indent [expr {2 * $indent}]] \
2191 -yscrollcommand ".bright.sb set" \
2192 -cursor [. cget -cursor] \
2193 -spacing1 1 -spacing3 1
2194 lappend bglist $cflist
2195 lappend fglist $cflist
2196 scrollbar .bright.sb -command "$cflist yview"
2197 pack .bright.sb -side right -fill y
2198 pack $cflist -side left -fill both -expand 1
2199 $cflist tag configure highlight \
2200 -background [$cflist cget -selectbackground]
2201 $cflist tag configure bold -font mainfontbold
2203 .pwbottom add .bright
2204 .ctop add .pwbottom
2206 # restore window width & height if known
2207 if {[info exists geometry(main)]} {
2208 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
2209 if {$w > [winfo screenwidth .]} {
2210 set w [winfo screenwidth .]
2212 if {$h > [winfo screenheight .]} {
2213 set h [winfo screenheight .]
2215 wm geometry . "${w}x$h"
2219 if {[tk windowingsystem] eq {aqua}} {
2220 set M1B M1
2221 } else {
2222 set M1B Control
2225 bind .pwbottom <Configure> {resizecdetpanes %W %w}
2226 pack .ctop -fill both -expand 1
2227 bindall <1> {selcanvline %W %x %y}
2228 #bindall <B1-Motion> {selcanvline %W %x %y}
2229 if {[tk windowingsystem] == "win32"} {
2230 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
2231 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
2232 } else {
2233 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
2234 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
2235 if {[tk windowingsystem] eq "aqua"} {
2236 bindall <MouseWheel> {
2237 set delta [expr {- (%D)}]
2238 allcanvs yview scroll $delta units
2242 bindall <2> "canvscan mark %W %x %y"
2243 bindall <B2-Motion> "canvscan dragto %W %x %y"
2244 bindkey <Home> selfirstline
2245 bindkey <End> sellastline
2246 bind . <Key-Up> "selnextline -1"
2247 bind . <Key-Down> "selnextline 1"
2248 bind . <Shift-Key-Up> "dofind -1 0"
2249 bind . <Shift-Key-Down> "dofind 1 0"
2250 bindkey <Key-Right> "goforw"
2251 bindkey <Key-Left> "goback"
2252 bind . <Key-Prior> "selnextpage -1"
2253 bind . <Key-Next> "selnextpage 1"
2254 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
2255 bind . <$M1B-End> "allcanvs yview moveto 1.0"
2256 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
2257 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
2258 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
2259 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
2260 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
2261 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
2262 bindkey <Key-space> "$ctext yview scroll 1 pages"
2263 bindkey p "selnextline -1"
2264 bindkey n "selnextline 1"
2265 bindkey z "goback"
2266 bindkey x "goforw"
2267 bindkey i "selnextline -1"
2268 bindkey k "selnextline 1"
2269 bindkey j "goback"
2270 bindkey l "goforw"
2271 bindkey b prevfile
2272 bindkey d "$ctext yview scroll 18 units"
2273 bindkey u "$ctext yview scroll -18 units"
2274 bindkey / {dofind 1 1}
2275 bindkey <Key-Return> {dofind 1 1}
2276 bindkey ? {dofind -1 1}
2277 bindkey f nextfile
2278 bind . <F5> updatecommits
2279 bind . <$M1B-F5> reloadcommits
2280 bind . <F2> showrefs
2281 bind . <Shift-F4> {newview 0}
2282 catch { bind . <Shift-Key-XF86_Switch_VT_4> {newview 0} }
2283 bind . <F4> edit_or_newview
2284 bind . <$M1B-q> doquit
2285 bind . <$M1B-f> {dofind 1 1}
2286 bind . <$M1B-g> {dofind 1 0}
2287 bind . <$M1B-r> dosearchback
2288 bind . <$M1B-s> dosearch
2289 bind . <$M1B-equal> {incrfont 1}
2290 bind . <$M1B-plus> {incrfont 1}
2291 bind . <$M1B-KP_Add> {incrfont 1}
2292 bind . <$M1B-minus> {incrfont -1}
2293 bind . <$M1B-KP_Subtract> {incrfont -1}
2294 wm protocol . WM_DELETE_WINDOW doquit
2295 bind . <Destroy> {stop_backends}
2296 bind . <Button-1> "click %W"
2297 bind $fstring <Key-Return> {dofind 1 1}
2298 bind $sha1entry <Key-Return> {gotocommit; break}
2299 bind $sha1entry <<PasteSelection>> clearsha1
2300 bind $cflist <1> {sel_flist %W %x %y; break}
2301 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
2302 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
2303 global ctxbut
2304 bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
2305 bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
2307 set maincursor [. cget -cursor]
2308 set textcursor [$ctext cget -cursor]
2309 set curtextcursor $textcursor
2311 set rowctxmenu .rowctxmenu
2312 makemenu $rowctxmenu {
2313 {mc "Diff this -> selected" command {diffvssel 0}}
2314 {mc "Diff selected -> this" command {diffvssel 1}}
2315 {mc "Make patch" command mkpatch}
2316 {mc "Create tag" command mktag}
2317 {mc "Write commit to file" command writecommit}
2318 {mc "Create new branch" command mkbranch}
2319 {mc "Cherry-pick this commit" command cherrypick}
2320 {mc "Reset HEAD branch to here" command resethead}
2322 $rowctxmenu configure -tearoff 0
2324 set fakerowmenu .fakerowmenu
2325 makemenu $fakerowmenu {
2326 {mc "Diff this -> selected" command {diffvssel 0}}
2327 {mc "Diff selected -> this" command {diffvssel 1}}
2328 {mc "Make patch" command mkpatch}
2330 $fakerowmenu configure -tearoff 0
2332 set headctxmenu .headctxmenu
2333 makemenu $headctxmenu {
2334 {mc "Check out this branch" command cobranch}
2335 {mc "Remove this branch" command rmbranch}
2337 $headctxmenu configure -tearoff 0
2339 global flist_menu
2340 set flist_menu .flistctxmenu
2341 makemenu $flist_menu {
2342 {mc "Highlight this too" command {flist_hl 0}}
2343 {mc "Highlight this only" command {flist_hl 1}}
2344 {mc "External diff" command {external_diff}}
2345 {mc "Blame parent commit" command {external_blame 1}}
2347 $flist_menu configure -tearoff 0
2349 global diff_menu
2350 set diff_menu .diffctxmenu
2351 makemenu $diff_menu {
2352 {mc "Show origin of this line" command show_line_source}
2353 {mc "Run git gui blame on this line" command {external_blame_diff}}
2355 $diff_menu configure -tearoff 0
2358 # Windows sends all mouse wheel events to the current focused window, not
2359 # the one where the mouse hovers, so bind those events here and redirect
2360 # to the correct window
2361 proc windows_mousewheel_redirector {W X Y D} {
2362 global canv canv2 canv3
2363 set w [winfo containing -displayof $W $X $Y]
2364 if {$w ne ""} {
2365 set u [expr {$D < 0 ? 5 : -5}]
2366 if {$w == $canv || $w == $canv2 || $w == $canv3} {
2367 allcanvs yview scroll $u units
2368 } else {
2369 catch {
2370 $w yview scroll $u units
2376 # Update row number label when selectedline changes
2377 proc selectedline_change {n1 n2 op} {
2378 global selectedline rownumsel
2380 if {$selectedline eq {}} {
2381 set rownumsel {}
2382 } else {
2383 set rownumsel [expr {$selectedline + 1}]
2387 # mouse-2 makes all windows scan vertically, but only the one
2388 # the cursor is in scans horizontally
2389 proc canvscan {op w x y} {
2390 global canv canv2 canv3
2391 foreach c [list $canv $canv2 $canv3] {
2392 if {$c == $w} {
2393 $c scan $op $x $y
2394 } else {
2395 $c scan $op 0 $y
2400 proc scrollcanv {cscroll f0 f1} {
2401 $cscroll set $f0 $f1
2402 drawvisible
2403 flushhighlights
2406 # when we make a key binding for the toplevel, make sure
2407 # it doesn't get triggered when that key is pressed in the
2408 # find string entry widget.
2409 proc bindkey {ev script} {
2410 global entries
2411 bind . $ev $script
2412 set escript [bind Entry $ev]
2413 if {$escript == {}} {
2414 set escript [bind Entry <Key>]
2416 foreach e $entries {
2417 bind $e $ev "$escript; break"
2421 # set the focus back to the toplevel for any click outside
2422 # the entry widgets
2423 proc click {w} {
2424 global ctext entries
2425 foreach e [concat $entries $ctext] {
2426 if {$w == $e} return
2428 focus .
2431 # Adjust the progress bar for a change in requested extent or canvas size
2432 proc adjustprogress {} {
2433 global progresscanv progressitem progresscoords
2434 global fprogitem fprogcoord lastprogupdate progupdatepending
2435 global rprogitem rprogcoord
2437 set w [expr {[winfo width $progresscanv] - 4}]
2438 set x0 [expr {$w * [lindex $progresscoords 0]}]
2439 set x1 [expr {$w * [lindex $progresscoords 1]}]
2440 set h [winfo height $progresscanv]
2441 $progresscanv coords $progressitem $x0 0 $x1 $h
2442 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
2443 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
2444 set now [clock clicks -milliseconds]
2445 if {$now >= $lastprogupdate + 100} {
2446 set progupdatepending 0
2447 update
2448 } elseif {!$progupdatepending} {
2449 set progupdatepending 1
2450 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
2454 proc doprogupdate {} {
2455 global lastprogupdate progupdatepending
2457 if {$progupdatepending} {
2458 set progupdatepending 0
2459 set lastprogupdate [clock clicks -milliseconds]
2460 update
2464 proc savestuff {w} {
2465 global canv canv2 canv3 mainfont textfont uifont tabstop
2466 global stuffsaved findmergefiles maxgraphpct
2467 global maxwidth showneartags showlocalchanges
2468 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
2469 global cmitmode wrapcomment datetimeformat limitdiffs
2470 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
2471 global autoselect extdifftool perfile_attrs markbgcolor
2473 if {$stuffsaved} return
2474 if {![winfo viewable .]} return
2475 catch {
2476 set f [open "~/.gitk-new" w]
2477 puts $f [list set mainfont $mainfont]
2478 puts $f [list set textfont $textfont]
2479 puts $f [list set uifont $uifont]
2480 puts $f [list set tabstop $tabstop]
2481 puts $f [list set findmergefiles $findmergefiles]
2482 puts $f [list set maxgraphpct $maxgraphpct]
2483 puts $f [list set maxwidth $maxwidth]
2484 puts $f [list set cmitmode $cmitmode]
2485 puts $f [list set wrapcomment $wrapcomment]
2486 puts $f [list set autoselect $autoselect]
2487 puts $f [list set showneartags $showneartags]
2488 puts $f [list set showlocalchanges $showlocalchanges]
2489 puts $f [list set datetimeformat $datetimeformat]
2490 puts $f [list set limitdiffs $limitdiffs]
2491 puts $f [list set bgcolor $bgcolor]
2492 puts $f [list set fgcolor $fgcolor]
2493 puts $f [list set colors $colors]
2494 puts $f [list set diffcolors $diffcolors]
2495 puts $f [list set markbgcolor $markbgcolor]
2496 puts $f [list set diffcontext $diffcontext]
2497 puts $f [list set selectbgcolor $selectbgcolor]
2498 puts $f [list set extdifftool $extdifftool]
2499 puts $f [list set perfile_attrs $perfile_attrs]
2501 puts $f "set geometry(main) [wm geometry .]"
2502 puts $f "set geometry(topwidth) [winfo width .tf]"
2503 puts $f "set geometry(topheight) [winfo height .tf]"
2504 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
2505 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
2506 puts $f "set geometry(botwidth) [winfo width .bleft]"
2507 puts $f "set geometry(botheight) [winfo height .bleft]"
2509 puts -nonewline $f "set permviews {"
2510 for {set v 0} {$v < $nextviewnum} {incr v} {
2511 if {$viewperm($v)} {
2512 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
2515 puts $f "}"
2516 close $f
2517 file rename -force "~/.gitk-new" "~/.gitk"
2519 set stuffsaved 1
2522 proc resizeclistpanes {win w} {
2523 global oldwidth
2524 if {[info exists oldwidth($win)]} {
2525 set s0 [$win sash coord 0]
2526 set s1 [$win sash coord 1]
2527 if {$w < 60} {
2528 set sash0 [expr {int($w/2 - 2)}]
2529 set sash1 [expr {int($w*5/6 - 2)}]
2530 } else {
2531 set factor [expr {1.0 * $w / $oldwidth($win)}]
2532 set sash0 [expr {int($factor * [lindex $s0 0])}]
2533 set sash1 [expr {int($factor * [lindex $s1 0])}]
2534 if {$sash0 < 30} {
2535 set sash0 30
2537 if {$sash1 < $sash0 + 20} {
2538 set sash1 [expr {$sash0 + 20}]
2540 if {$sash1 > $w - 10} {
2541 set sash1 [expr {$w - 10}]
2542 if {$sash0 > $sash1 - 20} {
2543 set sash0 [expr {$sash1 - 20}]
2547 $win sash place 0 $sash0 [lindex $s0 1]
2548 $win sash place 1 $sash1 [lindex $s1 1]
2550 set oldwidth($win) $w
2553 proc resizecdetpanes {win w} {
2554 global oldwidth
2555 if {[info exists oldwidth($win)]} {
2556 set s0 [$win sash coord 0]
2557 if {$w < 60} {
2558 set sash0 [expr {int($w*3/4 - 2)}]
2559 } else {
2560 set factor [expr {1.0 * $w / $oldwidth($win)}]
2561 set sash0 [expr {int($factor * [lindex $s0 0])}]
2562 if {$sash0 < 45} {
2563 set sash0 45
2565 if {$sash0 > $w - 15} {
2566 set sash0 [expr {$w - 15}]
2569 $win sash place 0 $sash0 [lindex $s0 1]
2571 set oldwidth($win) $w
2574 proc allcanvs args {
2575 global canv canv2 canv3
2576 eval $canv $args
2577 eval $canv2 $args
2578 eval $canv3 $args
2581 proc bindall {event action} {
2582 global canv canv2 canv3
2583 bind $canv $event $action
2584 bind $canv2 $event $action
2585 bind $canv3 $event $action
2588 proc about {} {
2589 global uifont
2590 set w .about
2591 if {[winfo exists $w]} {
2592 raise $w
2593 return
2595 toplevel $w
2596 wm title $w [mc "About gitk"]
2597 make_transient $w .
2598 message $w.m -text [mc "
2599 Gitk - a commit viewer for git
2601 Copyright © 2005-2008 Paul Mackerras
2603 Use and redistribute under the terms of the GNU General Public License"] \
2604 -justify center -aspect 400 -border 2 -bg white -relief groove
2605 pack $w.m -side top -fill x -padx 2 -pady 2
2606 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2607 pack $w.ok -side bottom
2608 bind $w <Visibility> "focus $w.ok"
2609 bind $w <Key-Escape> "destroy $w"
2610 bind $w <Key-Return> "destroy $w"
2613 proc keys {} {
2614 set w .keys
2615 if {[winfo exists $w]} {
2616 raise $w
2617 return
2619 if {[tk windowingsystem] eq {aqua}} {
2620 set M1T Cmd
2621 } else {
2622 set M1T Ctrl
2624 toplevel $w
2625 wm title $w [mc "Gitk key bindings"]
2626 make_transient $w .
2627 message $w.m -text "
2628 [mc "Gitk key bindings:"]
2630 [mc "<%s-Q> Quit" $M1T]
2631 [mc "<Home> Move to first commit"]
2632 [mc "<End> Move to last commit"]
2633 [mc "<Up>, p, i Move up one commit"]
2634 [mc "<Down>, n, k Move down one commit"]
2635 [mc "<Left>, z, j Go back in history list"]
2636 [mc "<Right>, x, l Go forward in history list"]
2637 [mc "<PageUp> Move up one page in commit list"]
2638 [mc "<PageDown> Move down one page in commit list"]
2639 [mc "<%s-Home> Scroll to top of commit list" $M1T]
2640 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
2641 [mc "<%s-Up> Scroll commit list up one line" $M1T]
2642 [mc "<%s-Down> Scroll commit list down one line" $M1T]
2643 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
2644 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
2645 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
2646 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
2647 [mc "<Delete>, b Scroll diff view up one page"]
2648 [mc "<Backspace> Scroll diff view up one page"]
2649 [mc "<Space> Scroll diff view down one page"]
2650 [mc "u Scroll diff view up 18 lines"]
2651 [mc "d Scroll diff view down 18 lines"]
2652 [mc "<%s-F> Find" $M1T]
2653 [mc "<%s-G> Move to next find hit" $M1T]
2654 [mc "<Return> Move to next find hit"]
2655 [mc "/ Move to next find hit, or redo find"]
2656 [mc "? Move to previous find hit"]
2657 [mc "f Scroll diff view to next file"]
2658 [mc "<%s-S> Search for next hit in diff view" $M1T]
2659 [mc "<%s-R> Search for previous hit in diff view" $M1T]
2660 [mc "<%s-KP+> Increase font size" $M1T]
2661 [mc "<%s-plus> Increase font size" $M1T]
2662 [mc "<%s-KP-> Decrease font size" $M1T]
2663 [mc "<%s-minus> Decrease font size" $M1T]
2664 [mc "<F5> Update"]
2666 -justify left -bg white -border 2 -relief groove
2667 pack $w.m -side top -fill both -padx 2 -pady 2
2668 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
2669 bind $w <Key-Escape> [list destroy $w]
2670 pack $w.ok -side bottom
2671 bind $w <Visibility> "focus $w.ok"
2672 bind $w <Key-Escape> "destroy $w"
2673 bind $w <Key-Return> "destroy $w"
2676 # Procedures for manipulating the file list window at the
2677 # bottom right of the overall window.
2679 proc treeview {w l openlevs} {
2680 global treecontents treediropen treeheight treeparent treeindex
2682 set ix 0
2683 set treeindex() 0
2684 set lev 0
2685 set prefix {}
2686 set prefixend -1
2687 set prefendstack {}
2688 set htstack {}
2689 set ht 0
2690 set treecontents() {}
2691 $w conf -state normal
2692 foreach f $l {
2693 while {[string range $f 0 $prefixend] ne $prefix} {
2694 if {$lev <= $openlevs} {
2695 $w mark set e:$treeindex($prefix) "end -1c"
2696 $w mark gravity e:$treeindex($prefix) left
2698 set treeheight($prefix) $ht
2699 incr ht [lindex $htstack end]
2700 set htstack [lreplace $htstack end end]
2701 set prefixend [lindex $prefendstack end]
2702 set prefendstack [lreplace $prefendstack end end]
2703 set prefix [string range $prefix 0 $prefixend]
2704 incr lev -1
2706 set tail [string range $f [expr {$prefixend+1}] end]
2707 while {[set slash [string first "/" $tail]] >= 0} {
2708 lappend htstack $ht
2709 set ht 0
2710 lappend prefendstack $prefixend
2711 incr prefixend [expr {$slash + 1}]
2712 set d [string range $tail 0 $slash]
2713 lappend treecontents($prefix) $d
2714 set oldprefix $prefix
2715 append prefix $d
2716 set treecontents($prefix) {}
2717 set treeindex($prefix) [incr ix]
2718 set treeparent($prefix) $oldprefix
2719 set tail [string range $tail [expr {$slash+1}] end]
2720 if {$lev <= $openlevs} {
2721 set ht 1
2722 set treediropen($prefix) [expr {$lev < $openlevs}]
2723 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
2724 $w mark set d:$ix "end -1c"
2725 $w mark gravity d:$ix left
2726 set str "\n"
2727 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2728 $w insert end $str
2729 $w image create end -align center -image $bm -padx 1 \
2730 -name a:$ix
2731 $w insert end $d [highlight_tag $prefix]
2732 $w mark set s:$ix "end -1c"
2733 $w mark gravity s:$ix left
2735 incr lev
2737 if {$tail ne {}} {
2738 if {$lev <= $openlevs} {
2739 incr ht
2740 set str "\n"
2741 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
2742 $w insert end $str
2743 $w insert end $tail [highlight_tag $f]
2745 lappend treecontents($prefix) $tail
2748 while {$htstack ne {}} {
2749 set treeheight($prefix) $ht
2750 incr ht [lindex $htstack end]
2751 set htstack [lreplace $htstack end end]
2752 set prefixend [lindex $prefendstack end]
2753 set prefendstack [lreplace $prefendstack end end]
2754 set prefix [string range $prefix 0 $prefixend]
2756 $w conf -state disabled
2759 proc linetoelt {l} {
2760 global treeheight treecontents
2762 set y 2
2763 set prefix {}
2764 while {1} {
2765 foreach e $treecontents($prefix) {
2766 if {$y == $l} {
2767 return "$prefix$e"
2769 set n 1
2770 if {[string index $e end] eq "/"} {
2771 set n $treeheight($prefix$e)
2772 if {$y + $n > $l} {
2773 append prefix $e
2774 incr y
2775 break
2778 incr y $n
2783 proc highlight_tree {y prefix} {
2784 global treeheight treecontents cflist
2786 foreach e $treecontents($prefix) {
2787 set path $prefix$e
2788 if {[highlight_tag $path] ne {}} {
2789 $cflist tag add bold $y.0 "$y.0 lineend"
2791 incr y
2792 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
2793 set y [highlight_tree $y $path]
2796 return $y
2799 proc treeclosedir {w dir} {
2800 global treediropen treeheight treeparent treeindex
2802 set ix $treeindex($dir)
2803 $w conf -state normal
2804 $w delete s:$ix e:$ix
2805 set treediropen($dir) 0
2806 $w image configure a:$ix -image tri-rt
2807 $w conf -state disabled
2808 set n [expr {1 - $treeheight($dir)}]
2809 while {$dir ne {}} {
2810 incr treeheight($dir) $n
2811 set dir $treeparent($dir)
2815 proc treeopendir {w dir} {
2816 global treediropen treeheight treeparent treecontents treeindex
2818 set ix $treeindex($dir)
2819 $w conf -state normal
2820 $w image configure a:$ix -image tri-dn
2821 $w mark set e:$ix s:$ix
2822 $w mark gravity e:$ix right
2823 set lev 0
2824 set str "\n"
2825 set n [llength $treecontents($dir)]
2826 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
2827 incr lev
2828 append str "\t"
2829 incr treeheight($x) $n
2831 foreach e $treecontents($dir) {
2832 set de $dir$e
2833 if {[string index $e end] eq "/"} {
2834 set iy $treeindex($de)
2835 $w mark set d:$iy e:$ix
2836 $w mark gravity d:$iy left
2837 $w insert e:$ix $str
2838 set treediropen($de) 0
2839 $w image create e:$ix -align center -image tri-rt -padx 1 \
2840 -name a:$iy
2841 $w insert e:$ix $e [highlight_tag $de]
2842 $w mark set s:$iy e:$ix
2843 $w mark gravity s:$iy left
2844 set treeheight($de) 1
2845 } else {
2846 $w insert e:$ix $str
2847 $w insert e:$ix $e [highlight_tag $de]
2850 $w mark gravity e:$ix right
2851 $w conf -state disabled
2852 set treediropen($dir) 1
2853 set top [lindex [split [$w index @0,0] .] 0]
2854 set ht [$w cget -height]
2855 set l [lindex [split [$w index s:$ix] .] 0]
2856 if {$l < $top} {
2857 $w yview $l.0
2858 } elseif {$l + $n + 1 > $top + $ht} {
2859 set top [expr {$l + $n + 2 - $ht}]
2860 if {$l < $top} {
2861 set top $l
2863 $w yview $top.0
2867 proc treeclick {w x y} {
2868 global treediropen cmitmode ctext cflist cflist_top
2870 if {$cmitmode ne "tree"} return
2871 if {![info exists cflist_top]} return
2872 set l [lindex [split [$w index "@$x,$y"] "."] 0]
2873 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
2874 $cflist tag add highlight $l.0 "$l.0 lineend"
2875 set cflist_top $l
2876 if {$l == 1} {
2877 $ctext yview 1.0
2878 return
2880 set e [linetoelt $l]
2881 if {[string index $e end] ne "/"} {
2882 showfile $e
2883 } elseif {$treediropen($e)} {
2884 treeclosedir $w $e
2885 } else {
2886 treeopendir $w $e
2890 proc setfilelist {id} {
2891 global treefilelist cflist jump_to_here
2893 treeview $cflist $treefilelist($id) 0
2894 if {$jump_to_here ne {}} {
2895 set f [lindex $jump_to_here 0]
2896 if {[lsearch -exact $treefilelist($id) $f] >= 0} {
2897 showfile $f
2902 image create bitmap tri-rt -background black -foreground blue -data {
2903 #define tri-rt_width 13
2904 #define tri-rt_height 13
2905 static unsigned char tri-rt_bits[] = {
2906 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
2907 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
2908 0x00, 0x00};
2909 } -maskdata {
2910 #define tri-rt-mask_width 13
2911 #define tri-rt-mask_height 13
2912 static unsigned char tri-rt-mask_bits[] = {
2913 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
2914 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
2915 0x08, 0x00};
2917 image create bitmap tri-dn -background black -foreground blue -data {
2918 #define tri-dn_width 13
2919 #define tri-dn_height 13
2920 static unsigned char tri-dn_bits[] = {
2921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
2922 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2923 0x00, 0x00};
2924 } -maskdata {
2925 #define tri-dn-mask_width 13
2926 #define tri-dn-mask_height 13
2927 static unsigned char tri-dn-mask_bits[] = {
2928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
2929 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
2930 0x00, 0x00};
2933 image create bitmap reficon-T -background black -foreground yellow -data {
2934 #define tagicon_width 13
2935 #define tagicon_height 9
2936 static unsigned char tagicon_bits[] = {
2937 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
2938 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
2939 } -maskdata {
2940 #define tagicon-mask_width 13
2941 #define tagicon-mask_height 9
2942 static unsigned char tagicon-mask_bits[] = {
2943 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
2944 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
2946 set rectdata {
2947 #define headicon_width 13
2948 #define headicon_height 9
2949 static unsigned char headicon_bits[] = {
2950 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
2951 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
2953 set rectmask {
2954 #define headicon-mask_width 13
2955 #define headicon-mask_height 9
2956 static unsigned char headicon-mask_bits[] = {
2957 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
2958 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
2960 image create bitmap reficon-H -background black -foreground green \
2961 -data $rectdata -maskdata $rectmask
2962 image create bitmap reficon-o -background black -foreground "#ddddff" \
2963 -data $rectdata -maskdata $rectmask
2965 proc init_flist {first} {
2966 global cflist cflist_top difffilestart
2968 $cflist conf -state normal
2969 $cflist delete 0.0 end
2970 if {$first ne {}} {
2971 $cflist insert end $first
2972 set cflist_top 1
2973 $cflist tag add highlight 1.0 "1.0 lineend"
2974 } else {
2975 catch {unset cflist_top}
2977 $cflist conf -state disabled
2978 set difffilestart {}
2981 proc highlight_tag {f} {
2982 global highlight_paths
2984 foreach p $highlight_paths {
2985 if {[string match $p $f]} {
2986 return "bold"
2989 return {}
2992 proc highlight_filelist {} {
2993 global cmitmode cflist
2995 $cflist conf -state normal
2996 if {$cmitmode ne "tree"} {
2997 set end [lindex [split [$cflist index end] .] 0]
2998 for {set l 2} {$l < $end} {incr l} {
2999 set line [$cflist get $l.0 "$l.0 lineend"]
3000 if {[highlight_tag $line] ne {}} {
3001 $cflist tag add bold $l.0 "$l.0 lineend"
3004 } else {
3005 highlight_tree 2 {}
3007 $cflist conf -state disabled
3010 proc unhighlight_filelist {} {
3011 global cflist
3013 $cflist conf -state normal
3014 $cflist tag remove bold 1.0 end
3015 $cflist conf -state disabled
3018 proc add_flist {fl} {
3019 global cflist
3021 $cflist conf -state normal
3022 foreach f $fl {
3023 $cflist insert end "\n"
3024 $cflist insert end $f [highlight_tag $f]
3026 $cflist conf -state disabled
3029 proc sel_flist {w x y} {
3030 global ctext difffilestart cflist cflist_top cmitmode
3032 if {$cmitmode eq "tree"} return
3033 if {![info exists cflist_top]} return
3034 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3035 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
3036 $cflist tag add highlight $l.0 "$l.0 lineend"
3037 set cflist_top $l
3038 if {$l == 1} {
3039 $ctext yview 1.0
3040 } else {
3041 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
3045 proc pop_flist_menu {w X Y x y} {
3046 global ctext cflist cmitmode flist_menu flist_menu_file
3047 global treediffs diffids
3049 stopfinding
3050 set l [lindex [split [$w index "@$x,$y"] "."] 0]
3051 if {$l <= 1} return
3052 if {$cmitmode eq "tree"} {
3053 set e [linetoelt $l]
3054 if {[string index $e end] eq "/"} return
3055 } else {
3056 set e [lindex $treediffs($diffids) [expr {$l-2}]]
3058 set flist_menu_file $e
3059 set xdiffstate "normal"
3060 if {$cmitmode eq "tree"} {
3061 set xdiffstate "disabled"
3063 # Disable "External diff" item in tree mode
3064 $flist_menu entryconf 2 -state $xdiffstate
3065 tk_popup $flist_menu $X $Y
3068 proc find_ctext_fileinfo {line} {
3069 global ctext_file_names ctext_file_lines
3071 set ok [bsearch $ctext_file_lines $line]
3072 set tline [lindex $ctext_file_lines $ok]
3074 if {$ok >= [llength $ctext_file_lines] || $line < $tline} {
3075 return {}
3076 } else {
3077 return [list [lindex $ctext_file_names $ok] $tline]
3081 proc pop_diff_menu {w X Y x y} {
3082 global ctext diff_menu flist_menu_file
3083 global diff_menu_txtpos diff_menu_line
3084 global diff_menu_filebase
3086 set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
3087 set diff_menu_line [lindex $diff_menu_txtpos 0]
3088 # don't pop up the menu on hunk-separator or file-separator lines
3089 if {[lsearch -glob [$ctext tag names $diff_menu_line.0] "*sep"] >= 0} {
3090 return
3092 stopfinding
3093 set f [find_ctext_fileinfo $diff_menu_line]
3094 if {$f eq {}} return
3095 set flist_menu_file [lindex $f 0]
3096 set diff_menu_filebase [lindex $f 1]
3097 tk_popup $diff_menu $X $Y
3100 proc flist_hl {only} {
3101 global flist_menu_file findstring gdttype
3103 set x [shellquote $flist_menu_file]
3104 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
3105 set findstring $x
3106 } else {
3107 append findstring " " $x
3109 set gdttype [mc "touching paths:"]
3112 proc save_file_from_commit {filename output what} {
3113 global nullfile
3115 if {[catch {exec git show $filename -- > $output} err]} {
3116 if {[string match "fatal: bad revision *" $err]} {
3117 return $nullfile
3119 error_popup "[mc "Error getting \"%s\" from %s:" $filename $what] $err"
3120 return {}
3122 return $output
3125 proc external_diff_get_one_file {diffid filename diffdir} {
3126 global nullid nullid2 nullfile
3127 global gitdir
3129 if {$diffid == $nullid} {
3130 set difffile [file join [file dirname $gitdir] $filename]
3131 if {[file exists $difffile]} {
3132 return $difffile
3134 return $nullfile
3136 if {$diffid == $nullid2} {
3137 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
3138 return [save_file_from_commit :$filename $difffile index]
3140 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
3141 return [save_file_from_commit $diffid:$filename $difffile \
3142 "revision $diffid"]
3145 proc external_diff {} {
3146 global gitktmpdir nullid nullid2
3147 global flist_menu_file
3148 global diffids
3149 global diffnum
3150 global gitdir extdifftool
3152 if {[llength $diffids] == 1} {
3153 # no reference commit given
3154 set diffidto [lindex $diffids 0]
3155 if {$diffidto eq $nullid} {
3156 # diffing working copy with index
3157 set diffidfrom $nullid2
3158 } elseif {$diffidto eq $nullid2} {
3159 # diffing index with HEAD
3160 set diffidfrom "HEAD"
3161 } else {
3162 # use first parent commit
3163 global parentlist selectedline
3164 set diffidfrom [lindex $parentlist $selectedline 0]
3166 } else {
3167 set diffidfrom [lindex $diffids 0]
3168 set diffidto [lindex $diffids 1]
3171 # make sure that several diffs wont collide
3172 if {![info exists gitktmpdir]} {
3173 set gitktmpdir [file join [file dirname $gitdir] \
3174 [format ".gitk-tmp.%s" [pid]]]
3175 if {[catch {file mkdir $gitktmpdir} err]} {
3176 error_popup "[mc "Error creating temporary directory %s:" $gitktmpdir] $err"
3177 unset gitktmpdir
3178 return
3180 set diffnum 0
3182 incr diffnum
3183 set diffdir [file join $gitktmpdir $diffnum]
3184 if {[catch {file mkdir $diffdir} err]} {
3185 error_popup "[mc "Error creating temporary directory %s:" $diffdir] $err"
3186 return
3189 # gather files to diff
3190 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
3191 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
3193 if {$difffromfile ne {} && $difftofile ne {}} {
3194 set cmd [concat | [shellsplit $extdifftool] \
3195 [list $difffromfile $difftofile]]
3196 if {[catch {set fl [open $cmd r]} err]} {
3197 file delete -force $diffdir
3198 error_popup "$extdifftool: [mc "command failed:"] $err"
3199 } else {
3200 fconfigure $fl -blocking 0
3201 filerun $fl [list delete_at_eof $fl $diffdir]
3206 proc find_hunk_blamespec {base line} {
3207 global ctext
3209 # Find and parse the hunk header
3210 set s_lix [$ctext search -backwards -regexp ^@@ "$line.0 lineend" $base.0]
3211 if {$s_lix eq {}} return
3213 set s_line [$ctext get $s_lix "$s_lix + 1 lines"]
3214 if {![regexp {^@@@*(( -\d+(,\d+)?)+) \+(\d+)(,\d+)? @@} $s_line \
3215 s_line old_specs osz osz1 new_line nsz]} {
3216 return
3219 # base lines for the parents
3220 set base_lines [list $new_line]
3221 foreach old_spec [lrange [split $old_specs " "] 1 end] {
3222 if {![regexp -- {-(\d+)(,\d+)?} $old_spec \
3223 old_spec old_line osz]} {
3224 return
3226 lappend base_lines $old_line
3229 # Now scan the lines to determine offset within the hunk
3230 set max_parent [expr {[llength $base_lines]-2}]
3231 set dline 0
3232 set s_lno [lindex [split $s_lix "."] 0]
3234 # Determine if the line is removed
3235 set chunk [$ctext get $line.0 "$line.1 + $max_parent chars"]
3236 if {[string match {[-+ ]*} $chunk]} {
3237 set removed_idx [string first "-" $chunk]
3238 # Choose a parent index
3239 if {$removed_idx >= 0} {
3240 set parent $removed_idx
3241 } else {
3242 set unchanged_idx [string first " " $chunk]
3243 if {$unchanged_idx >= 0} {
3244 set parent $unchanged_idx
3245 } else {
3246 # blame the current commit
3247 set parent -1
3250 # then count other lines that belong to it
3251 for {set i $line} {[incr i -1] > $s_lno} {} {
3252 set chunk [$ctext get $i.0 "$i.1 + $max_parent chars"]
3253 # Determine if the line is removed
3254 set removed_idx [string first "-" $chunk]
3255 if {$parent >= 0} {
3256 set code [string index $chunk $parent]
3257 if {$code eq "-" || ($removed_idx < 0 && $code ne "+")} {
3258 incr dline
3260 } else {
3261 if {$removed_idx < 0} {
3262 incr dline
3266 incr parent
3267 } else {
3268 set parent 0
3271 incr dline [lindex $base_lines $parent]
3272 return [list $parent $dline]
3275 proc external_blame_diff {} {
3276 global currentid cmitmode
3277 global diff_menu_txtpos diff_menu_line
3278 global diff_menu_filebase flist_menu_file
3280 if {$cmitmode eq "tree"} {
3281 set parent_idx 0
3282 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3283 } else {
3284 set hinfo [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3285 if {$hinfo ne {}} {
3286 set parent_idx [lindex $hinfo 0]
3287 set line [lindex $hinfo 1]
3288 } else {
3289 set parent_idx 0
3290 set line 0
3294 external_blame $parent_idx $line
3297 # Find the SHA1 ID of the blob for file $fname in the index
3298 # at stage 0 or 2
3299 proc index_sha1 {fname} {
3300 set f [open [list | git ls-files -s $fname] r]
3301 while {[gets $f line] >= 0} {
3302 set info [lindex [split $line "\t"] 0]
3303 set stage [lindex $info 2]
3304 if {$stage eq "0" || $stage eq "2"} {
3305 close $f
3306 return [lindex $info 1]
3309 close $f
3310 return {}
3313 proc external_blame {parent_idx {line {}}} {
3314 global flist_menu_file
3315 global nullid nullid2
3316 global parentlist selectedline currentid
3318 if {$parent_idx > 0} {
3319 set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]]
3320 } else {
3321 set base_commit $currentid
3324 if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} {
3325 error_popup [mc "No such commit"]
3326 return
3329 set cmdline [list git gui blame]
3330 if {$line ne {} && $line > 1} {
3331 lappend cmdline "--line=$line"
3333 lappend cmdline $base_commit $flist_menu_file
3334 if {[catch {eval exec $cmdline &} err]} {
3335 error_popup "[mc "git gui blame: command failed:"] $err"
3339 proc show_line_source {} {
3340 global cmitmode currentid parents curview blamestuff blameinst
3341 global diff_menu_line diff_menu_filebase flist_menu_file
3342 global nullid nullid2 gitdir
3344 set from_index {}
3345 if {$cmitmode eq "tree"} {
3346 set id $currentid
3347 set line [expr {$diff_menu_line - $diff_menu_filebase}]
3348 } else {
3349 set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
3350 if {$h eq {}} return
3351 set pi [lindex $h 0]
3352 if {$pi == 0} {
3353 mark_ctext_line $diff_menu_line
3354 return
3356 incr pi -1
3357 if {$currentid eq $nullid} {
3358 if {$pi > 0} {
3359 # must be a merge in progress...
3360 if {[catch {
3361 # get the last line from .git/MERGE_HEAD
3362 set f [open [file join $gitdir MERGE_HEAD] r]
3363 set id [lindex [split [read $f] "\n"] end-1]
3364 close $f
3365 } err]} {
3366 error_popup [mc "Couldn't read merge head: %s" $err]
3367 return
3369 } elseif {$parents($curview,$currentid) eq $nullid2} {
3370 # need to do the blame from the index
3371 if {[catch {
3372 set from_index [index_sha1 $flist_menu_file]
3373 } err]} {
3374 error_popup [mc "Error reading index: %s" $err]
3375 return
3378 } else {
3379 set id [lindex $parents($curview,$currentid) $pi]
3381 set line [lindex $h 1]
3383 set blameargs {}
3384 if {$from_index ne {}} {
3385 lappend blameargs | git cat-file blob $from_index
3387 lappend blameargs | git blame -p -L$line,+1
3388 if {$from_index ne {}} {
3389 lappend blameargs --contents -
3390 } else {
3391 lappend blameargs $id
3393 lappend blameargs -- $flist_menu_file
3394 if {[catch {
3395 set f [open $blameargs r]
3396 } err]} {
3397 error_popup [mc "Couldn't start git blame: %s" $err]
3398 return
3400 fconfigure $f -blocking 0
3401 set i [reg_instance $f]
3402 set blamestuff($i) {}
3403 set blameinst $i
3404 filerun $f [list read_line_source $f $i]
3407 proc stopblaming {} {
3408 global blameinst
3410 if {[info exists blameinst]} {
3411 stop_instance $blameinst
3412 unset blameinst
3416 proc read_line_source {fd inst} {
3417 global blamestuff curview commfd blameinst nullid nullid2
3419 while {[gets $fd line] >= 0} {
3420 lappend blamestuff($inst) $line
3422 if {![eof $fd]} {
3423 return 1
3425 unset commfd($inst)
3426 unset blameinst
3427 fconfigure $fd -blocking 1
3428 if {[catch {close $fd} err]} {
3429 error_popup [mc "Error running git blame: %s" $err]
3430 return 0
3433 set fname {}
3434 set line [split [lindex $blamestuff($inst) 0] " "]
3435 set id [lindex $line 0]
3436 set lnum [lindex $line 1]
3437 if {[string length $id] == 40 && [string is xdigit $id] &&
3438 [string is digit -strict $lnum]} {
3439 # look for "filename" line
3440 foreach l $blamestuff($inst) {
3441 if {[string match "filename *" $l]} {
3442 set fname [string range $l 9 end]
3443 break
3447 if {$fname ne {}} {
3448 # all looks good, select it
3449 if {$id eq $nullid} {
3450 # blame uses all-zeroes to mean not committed,
3451 # which would mean a change in the index
3452 set id $nullid2
3454 if {[commitinview $id $curview]} {
3455 selectline [rowofcommit $id] 1 [list $fname $lnum]
3456 } else {
3457 error_popup [mc "That line comes from commit %s, \
3458 which is not in this view" [shortids $id]]
3460 } else {
3461 puts "oops couldn't parse git blame output"
3463 return 0
3466 # delete $dir when we see eof on $f (presumably because the child has exited)
3467 proc delete_at_eof {f dir} {
3468 while {[gets $f line] >= 0} {}
3469 if {[eof $f]} {
3470 if {[catch {close $f} err]} {
3471 error_popup "[mc "External diff viewer failed:"] $err"
3473 file delete -force $dir
3474 return 0
3476 return 1
3479 # Functions for adding and removing shell-type quoting
3481 proc shellquote {str} {
3482 if {![string match "*\['\"\\ \t]*" $str]} {
3483 return $str
3485 if {![string match "*\['\"\\]*" $str]} {
3486 return "\"$str\""
3488 if {![string match "*'*" $str]} {
3489 return "'$str'"
3491 return "\"[string map {\" \\\" \\ \\\\} $str]\""
3494 proc shellarglist {l} {
3495 set str {}
3496 foreach a $l {
3497 if {$str ne {}} {
3498 append str " "
3500 append str [shellquote $a]
3502 return $str
3505 proc shelldequote {str} {
3506 set ret {}
3507 set used -1
3508 while {1} {
3509 incr used
3510 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
3511 append ret [string range $str $used end]
3512 set used [string length $str]
3513 break
3515 set first [lindex $first 0]
3516 set ch [string index $str $first]
3517 if {$first > $used} {
3518 append ret [string range $str $used [expr {$first - 1}]]
3519 set used $first
3521 if {$ch eq " " || $ch eq "\t"} break
3522 incr used
3523 if {$ch eq "'"} {
3524 set first [string first "'" $str $used]
3525 if {$first < 0} {
3526 error "unmatched single-quote"
3528 append ret [string range $str $used [expr {$first - 1}]]
3529 set used $first
3530 continue
3532 if {$ch eq "\\"} {
3533 if {$used >= [string length $str]} {
3534 error "trailing backslash"
3536 append ret [string index $str $used]
3537 continue
3539 # here ch == "\""
3540 while {1} {
3541 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
3542 error "unmatched double-quote"
3544 set first [lindex $first 0]
3545 set ch [string index $str $first]
3546 if {$first > $used} {
3547 append ret [string range $str $used [expr {$first - 1}]]
3548 set used $first
3550 if {$ch eq "\""} break
3551 incr used
3552 append ret [string index $str $used]
3553 incr used
3556 return [list $used $ret]
3559 proc shellsplit {str} {
3560 set l {}
3561 while {1} {
3562 set str [string trimleft $str]
3563 if {$str eq {}} break
3564 set dq [shelldequote $str]
3565 set n [lindex $dq 0]
3566 set word [lindex $dq 1]
3567 set str [string range $str $n end]
3568 lappend l $word
3570 return $l
3573 # Code to implement multiple views
3575 proc newview {ishighlight} {
3576 global nextviewnum newviewname newishighlight
3577 global revtreeargs viewargscmd newviewopts curview
3579 set newishighlight $ishighlight
3580 set top .gitkview
3581 if {[winfo exists $top]} {
3582 raise $top
3583 return
3585 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
3586 set newviewopts($nextviewnum,perm) 0
3587 set newviewopts($nextviewnum,cmd) $viewargscmd($curview)
3588 decode_view_opts $nextviewnum $revtreeargs
3589 vieweditor $top $nextviewnum [mc "Gitk view definition"]
3592 set known_view_options {
3593 {perm b . {} {mc "Remember this view"}}
3594 {args t50= + {} {mc "Commits to include (arguments to git log):"}}
3595 {all b * "--all" {mc "Use all refs"}}
3596 {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}}
3597 {lright b . "--left-right" {mc "Mark branch sides"}}
3598 {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}}
3599 {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}}
3600 {limit t10 + "--max-count=*" {mc "Max count:"}}
3601 {skip t10 . "--skip=*" {mc "Skip:"}}
3602 {first b . "--first-parent" {mc "Limit to first parent"}}
3603 {cmd t50= + {} {mc "Command to generate more commits to include:"}}
3606 proc encode_view_opts {n} {
3607 global known_view_options newviewopts
3609 set rargs [list]
3610 foreach opt $known_view_options {
3611 set patterns [lindex $opt 3]
3612 if {$patterns eq {}} continue
3613 set pattern [lindex $patterns 0]
3615 set val $newviewopts($n,[lindex $opt 0])
3617 if {[lindex $opt 1] eq "b"} {
3618 if {$val} {
3619 lappend rargs $pattern
3621 } else {
3622 set val [string trim $val]
3623 if {$val ne {}} {
3624 set pfix [string range $pattern 0 end-1]
3625 lappend rargs $pfix$val
3629 return [concat $rargs [shellsplit $newviewopts($n,args)]]
3632 proc decode_view_opts {n view_args} {
3633 global known_view_options newviewopts
3635 foreach opt $known_view_options {
3636 if {[lindex $opt 1] eq "b"} {
3637 set val 0
3638 } else {
3639 set val {}
3641 set newviewopts($n,[lindex $opt 0]) $val
3643 set oargs [list]
3644 foreach arg $view_args {
3645 if {[regexp -- {^-([0-9]+)$} $arg arg cnt]
3646 && ![info exists found(limit)]} {
3647 set newviewopts($n,limit) $cnt
3648 set found(limit) 1
3649 continue
3651 catch { unset val }
3652 foreach opt $known_view_options {
3653 set id [lindex $opt 0]
3654 if {[info exists found($id)]} continue
3655 foreach pattern [lindex $opt 3] {
3656 if {![string match $pattern $arg]} continue
3657 if {[lindex $opt 1] ne "b"} {
3658 set size [string length $pattern]
3659 set val [string range $arg [expr {$size-1}] end]
3660 } else {
3661 set val 1
3663 set newviewopts($n,$id) $val
3664 set found($id) 1
3665 break
3667 if {[info exists val]} break
3669 if {[info exists val]} continue
3670 lappend oargs $arg
3672 set newviewopts($n,args) [shellarglist $oargs]
3675 proc edit_or_newview {} {
3676 global curview
3678 if {$curview > 0} {
3679 editview
3680 } else {
3681 newview 0
3685 proc editview {} {
3686 global curview
3687 global viewname viewperm newviewname newviewopts
3688 global viewargs viewargscmd
3690 set top .gitkvedit-$curview
3691 if {[winfo exists $top]} {
3692 raise $top
3693 return
3695 set newviewname($curview) $viewname($curview)
3696 set newviewopts($curview,perm) $viewperm($curview)
3697 set newviewopts($curview,cmd) $viewargscmd($curview)
3698 decode_view_opts $curview $viewargs($curview)
3699 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
3702 proc vieweditor {top n title} {
3703 global newviewname newviewopts viewfiles bgcolor
3704 global known_view_options
3706 toplevel $top
3707 wm title $top $title
3708 make_transient $top .
3710 # View name
3711 frame $top.nfr
3712 label $top.nl -text [mc "Name"]
3713 entry $top.name -width 20 -textvariable newviewname($n)
3714 pack $top.nfr -in $top -fill x -pady 5 -padx 3
3715 pack $top.nl -in $top.nfr -side left -padx {0 30}
3716 pack $top.name -in $top.nfr -side left
3718 # View options
3719 set cframe $top.nfr
3720 set cexpand 0
3721 set cnt 0
3722 foreach opt $known_view_options {
3723 set id [lindex $opt 0]
3724 set type [lindex $opt 1]
3725 set flags [lindex $opt 2]
3726 set title [eval [lindex $opt 4]]
3727 set lxpad 0
3729 if {$flags eq "+" || $flags eq "*"} {
3730 set cframe $top.fr$cnt
3731 incr cnt
3732 frame $cframe
3733 pack $cframe -in $top -fill x -pady 3 -padx 3
3734 set cexpand [expr {$flags eq "*"}]
3735 } else {
3736 set lxpad 5
3739 if {$type eq "b"} {
3740 checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
3741 pack $cframe.c_$id -in $cframe -side left \
3742 -padx [list $lxpad 0] -expand $cexpand -anchor w
3743 } elseif {[regexp {^t(\d+)$} $type type sz]} {
3744 message $cframe.l_$id -aspect 1500 -text $title
3745 entry $cframe.e_$id -width $sz -background $bgcolor \
3746 -textvariable newviewopts($n,$id)
3747 pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
3748 pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
3749 } elseif {[regexp {^t(\d+)=$} $type type sz]} {
3750 message $cframe.l_$id -aspect 1500 -text $title
3751 entry $cframe.e_$id -width $sz -background $bgcolor \
3752 -textvariable newviewopts($n,$id)
3753 pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
3754 pack $cframe.e_$id -in $cframe -side top -fill x
3758 # Path list
3759 message $top.l -aspect 1500 \
3760 -text [mc "Enter files and directories to include, one per line:"]
3761 pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3
3762 text $top.t -width 40 -height 5 -background $bgcolor -font uifont
3763 if {[info exists viewfiles($n)]} {
3764 foreach f $viewfiles($n) {
3765 $top.t insert end $f
3766 $top.t insert end "\n"
3768 $top.t delete {end - 1c} end
3769 $top.t mark set insert 0.0
3771 pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3
3772 frame $top.buts
3773 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
3774 button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
3775 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
3776 bind $top <Control-Return> [list newviewok $top $n]
3777 bind $top <F5> [list newviewok $top $n 1]
3778 bind $top <Escape> [list destroy $top]
3779 grid $top.buts.ok $top.buts.apply $top.buts.can
3780 grid columnconfigure $top.buts 0 -weight 1 -uniform a
3781 grid columnconfigure $top.buts 1 -weight 1 -uniform a
3782 grid columnconfigure $top.buts 2 -weight 1 -uniform a
3783 pack $top.buts -in $top -side top -fill x
3784 focus $top.t
3787 proc doviewmenu {m first cmd op argv} {
3788 set nmenu [$m index end]
3789 for {set i $first} {$i <= $nmenu} {incr i} {
3790 if {[$m entrycget $i -command] eq $cmd} {
3791 eval $m $op $i $argv
3792 break
3797 proc allviewmenus {n op args} {
3798 # global viewhlmenu
3800 doviewmenu .bar.view 5 [list showview $n] $op $args
3801 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
3804 proc newviewok {top n {apply 0}} {
3805 global nextviewnum newviewperm newviewname newishighlight
3806 global viewname viewfiles viewperm selectedview curview
3807 global viewargs viewargscmd newviewopts viewhlmenu
3809 if {[catch {
3810 set newargs [encode_view_opts $n]
3811 } err]} {
3812 error_popup "[mc "Error in commit selection arguments:"] $err" $top
3813 return
3815 set files {}
3816 foreach f [split [$top.t get 0.0 end] "\n"] {
3817 set ft [string trim $f]
3818 if {$ft ne {}} {
3819 lappend files $ft
3822 if {![info exists viewfiles($n)]} {
3823 # creating a new view
3824 incr nextviewnum
3825 set viewname($n) $newviewname($n)
3826 set viewperm($n) $newviewopts($n,perm)
3827 set viewfiles($n) $files
3828 set viewargs($n) $newargs
3829 set viewargscmd($n) $newviewopts($n,cmd)
3830 addviewmenu $n
3831 if {!$newishighlight} {
3832 run showview $n
3833 } else {
3834 run addvhighlight $n
3836 } else {
3837 # editing an existing view
3838 set viewperm($n) $newviewopts($n,perm)
3839 if {$newviewname($n) ne $viewname($n)} {
3840 set viewname($n) $newviewname($n)
3841 doviewmenu .bar.view 5 [list showview $n] \
3842 entryconf [list -label $viewname($n)]
3843 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
3844 # entryconf [list -label $viewname($n) -value $viewname($n)]
3846 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
3847 $newviewopts($n,cmd) ne $viewargscmd($n)} {
3848 set viewfiles($n) $files
3849 set viewargs($n) $newargs
3850 set viewargscmd($n) $newviewopts($n,cmd)
3851 if {$curview == $n} {
3852 run reloadcommits
3856 if {$apply} return
3857 catch {destroy $top}
3860 proc delview {} {
3861 global curview viewperm hlview selectedhlview
3863 if {$curview == 0} return
3864 if {[info exists hlview] && $hlview == $curview} {
3865 set selectedhlview [mc "None"]
3866 unset hlview
3868 allviewmenus $curview delete
3869 set viewperm($curview) 0
3870 showview 0
3873 proc addviewmenu {n} {
3874 global viewname viewhlmenu
3876 .bar.view add radiobutton -label $viewname($n) \
3877 -command [list showview $n] -variable selectedview -value $n
3878 #$viewhlmenu add radiobutton -label $viewname($n) \
3879 # -command [list addvhighlight $n] -variable selectedhlview
3882 proc showview {n} {
3883 global curview cached_commitrow ordertok
3884 global displayorder parentlist rowidlist rowisopt rowfinal
3885 global colormap rowtextx nextcolor canvxmax
3886 global numcommits viewcomplete
3887 global selectedline currentid canv canvy0
3888 global treediffs
3889 global pending_select mainheadid
3890 global commitidx
3891 global selectedview
3892 global hlview selectedhlview commitinterest
3894 if {$n == $curview} return
3895 set selid {}
3896 set ymax [lindex [$canv cget -scrollregion] 3]
3897 set span [$canv yview]
3898 set ytop [expr {[lindex $span 0] * $ymax}]
3899 set ybot [expr {[lindex $span 1] * $ymax}]
3900 set yscreen [expr {($ybot - $ytop) / 2}]
3901 if {$selectedline ne {}} {
3902 set selid $currentid
3903 set y [yc $selectedline]
3904 if {$ytop < $y && $y < $ybot} {
3905 set yscreen [expr {$y - $ytop}]
3907 } elseif {[info exists pending_select]} {
3908 set selid $pending_select
3909 unset pending_select
3911 unselectline
3912 normalline
3913 catch {unset treediffs}
3914 clear_display
3915 if {[info exists hlview] && $hlview == $n} {
3916 unset hlview
3917 set selectedhlview [mc "None"]
3919 catch {unset commitinterest}
3920 catch {unset cached_commitrow}
3921 catch {unset ordertok}
3923 set curview $n
3924 set selectedview $n
3925 .bar.view entryconf [mca "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
3926 .bar.view entryconf [mca "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
3928 run refill_reflist
3929 if {![info exists viewcomplete($n)]} {
3930 getcommits $selid
3931 return
3934 set displayorder {}
3935 set parentlist {}
3936 set rowidlist {}
3937 set rowisopt {}
3938 set rowfinal {}
3939 set numcommits $commitidx($n)
3941 catch {unset colormap}
3942 catch {unset rowtextx}
3943 set nextcolor 0
3944 set canvxmax [$canv cget -width]
3945 set curview $n
3946 set row 0
3947 setcanvscroll
3948 set yf 0
3949 set row {}
3950 if {$selid ne {} && [commitinview $selid $n]} {
3951 set row [rowofcommit $selid]
3952 # try to get the selected row in the same position on the screen
3953 set ymax [lindex [$canv cget -scrollregion] 3]
3954 set ytop [expr {[yc $row] - $yscreen}]
3955 if {$ytop < 0} {
3956 set ytop 0
3958 set yf [expr {$ytop * 1.0 / $ymax}]
3960 allcanvs yview moveto $yf
3961 drawvisible
3962 if {$row ne {}} {
3963 selectline $row 0
3964 } elseif {!$viewcomplete($n)} {
3965 reset_pending_select $selid
3966 } else {
3967 reset_pending_select {}
3969 if {[commitinview $pending_select $curview]} {
3970 selectline [rowofcommit $pending_select] 1
3971 } else {
3972 set row [first_real_row]
3973 if {$row < $numcommits} {
3974 selectline $row 0
3978 if {!$viewcomplete($n)} {
3979 if {$numcommits == 0} {
3980 show_status [mc "Reading commits..."]
3982 } elseif {$numcommits == 0} {
3983 show_status [mc "No commits selected"]
3987 # Stuff relating to the highlighting facility
3989 proc ishighlighted {id} {
3990 global vhighlights fhighlights nhighlights rhighlights
3992 if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
3993 return $nhighlights($id)
3995 if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
3996 return $vhighlights($id)
3998 if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
3999 return $fhighlights($id)
4001 if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
4002 return $rhighlights($id)
4004 return 0
4007 proc bolden {id font} {
4008 global canv linehtag currentid boldids need_redisplay
4010 # need_redisplay = 1 means the display is stale and about to be redrawn
4011 if {$need_redisplay} return
4012 lappend boldids $id
4013 $canv itemconf $linehtag($id) -font $font
4014 if {[info exists currentid] && $id eq $currentid} {
4015 $canv delete secsel
4016 set t [eval $canv create rect [$canv bbox $linehtag($id)] \
4017 -outline {{}} -tags secsel \
4018 -fill [$canv cget -selectbackground]]
4019 $canv lower $t
4023 proc bolden_name {id font} {
4024 global canv2 linentag currentid boldnameids need_redisplay
4026 if {$need_redisplay} return
4027 lappend boldnameids $id
4028 $canv2 itemconf $linentag($id) -font $font
4029 if {[info exists currentid] && $id eq $currentid} {
4030 $canv2 delete secsel
4031 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] \
4032 -outline {{}} -tags secsel \
4033 -fill [$canv2 cget -selectbackground]]
4034 $canv2 lower $t
4038 proc unbolden {} {
4039 global boldids
4041 set stillbold {}
4042 foreach id $boldids {
4043 if {![ishighlighted $id]} {
4044 bolden $id mainfont
4045 } else {
4046 lappend stillbold $id
4049 set boldids $stillbold
4052 proc addvhighlight {n} {
4053 global hlview viewcomplete curview vhl_done commitidx
4055 if {[info exists hlview]} {
4056 delvhighlight
4058 set hlview $n
4059 if {$n != $curview && ![info exists viewcomplete($n)]} {
4060 start_rev_list $n
4062 set vhl_done $commitidx($hlview)
4063 if {$vhl_done > 0} {
4064 drawvisible
4068 proc delvhighlight {} {
4069 global hlview vhighlights
4071 if {![info exists hlview]} return
4072 unset hlview
4073 catch {unset vhighlights}
4074 unbolden
4077 proc vhighlightmore {} {
4078 global hlview vhl_done commitidx vhighlights curview
4080 set max $commitidx($hlview)
4081 set vr [visiblerows]
4082 set r0 [lindex $vr 0]
4083 set r1 [lindex $vr 1]
4084 for {set i $vhl_done} {$i < $max} {incr i} {
4085 set id [commitonrow $i $hlview]
4086 if {[commitinview $id $curview]} {
4087 set row [rowofcommit $id]
4088 if {$r0 <= $row && $row <= $r1} {
4089 if {![highlighted $row]} {
4090 bolden $id mainfontbold
4092 set vhighlights($id) 1
4096 set vhl_done $max
4097 return 0
4100 proc askvhighlight {row id} {
4101 global hlview vhighlights iddrawn
4103 if {[commitinview $id $hlview]} {
4104 if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
4105 bolden $id mainfontbold
4107 set vhighlights($id) 1
4108 } else {
4109 set vhighlights($id) 0
4113 proc hfiles_change {} {
4114 global highlight_files filehighlight fhighlights fh_serial
4115 global highlight_paths gdttype
4117 if {[info exists filehighlight]} {
4118 # delete previous highlights
4119 catch {close $filehighlight}
4120 unset filehighlight
4121 catch {unset fhighlights}
4122 unbolden
4123 unhighlight_filelist
4125 set highlight_paths {}
4126 after cancel do_file_hl $fh_serial
4127 incr fh_serial
4128 if {$highlight_files ne {}} {
4129 after 300 do_file_hl $fh_serial
4133 proc gdttype_change {name ix op} {
4134 global gdttype highlight_files findstring findpattern
4136 stopfinding
4137 if {$findstring ne {}} {
4138 if {$gdttype eq [mc "containing:"]} {
4139 if {$highlight_files ne {}} {
4140 set highlight_files {}
4141 hfiles_change
4143 findcom_change
4144 } else {
4145 if {$findpattern ne {}} {
4146 set findpattern {}
4147 findcom_change
4149 set highlight_files $findstring
4150 hfiles_change
4152 drawvisible
4154 # enable/disable findtype/findloc menus too
4157 proc find_change {name ix op} {
4158 global gdttype findstring highlight_files
4160 stopfinding
4161 if {$gdttype eq [mc "containing:"]} {
4162 findcom_change
4163 } else {
4164 if {$highlight_files ne $findstring} {
4165 set highlight_files $findstring
4166 hfiles_change
4169 drawvisible
4172 proc findcom_change args {
4173 global nhighlights boldnameids
4174 global findpattern findtype findstring gdttype
4176 stopfinding
4177 # delete previous highlights, if any
4178 foreach id $boldnameids {
4179 bolden_name $id mainfont
4181 set boldnameids {}
4182 catch {unset nhighlights}
4183 unbolden
4184 unmarkmatches
4185 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
4186 set findpattern {}
4187 } elseif {$findtype eq [mc "Regexp"]} {
4188 set findpattern $findstring
4189 } else {
4190 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
4191 $findstring]
4192 set findpattern "*$e*"
4196 proc makepatterns {l} {
4197 set ret {}
4198 foreach e $l {
4199 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
4200 if {[string index $ee end] eq "/"} {
4201 lappend ret "$ee*"
4202 } else {
4203 lappend ret $ee
4204 lappend ret "$ee/*"
4207 return $ret
4210 proc do_file_hl {serial} {
4211 global highlight_files filehighlight highlight_paths gdttype fhl_list
4213 if {$gdttype eq [mc "touching paths:"]} {
4214 if {[catch {set paths [shellsplit $highlight_files]}]} return
4215 set highlight_paths [makepatterns $paths]
4216 highlight_filelist
4217 set gdtargs [concat -- $paths]
4218 } elseif {$gdttype eq [mc "adding/removing string:"]} {
4219 set gdtargs [list "-S$highlight_files"]
4220 } else {
4221 # must be "containing:", i.e. we're searching commit info
4222 return
4224 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
4225 set filehighlight [open $cmd r+]
4226 fconfigure $filehighlight -blocking 0
4227 filerun $filehighlight readfhighlight
4228 set fhl_list {}
4229 drawvisible
4230 flushhighlights
4233 proc flushhighlights {} {
4234 global filehighlight fhl_list
4236 if {[info exists filehighlight]} {
4237 lappend fhl_list {}
4238 puts $filehighlight ""
4239 flush $filehighlight
4243 proc askfilehighlight {row id} {
4244 global filehighlight fhighlights fhl_list
4246 lappend fhl_list $id
4247 set fhighlights($id) -1
4248 puts $filehighlight $id
4251 proc readfhighlight {} {
4252 global filehighlight fhighlights curview iddrawn
4253 global fhl_list find_dirn
4255 if {![info exists filehighlight]} {
4256 return 0
4258 set nr 0
4259 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
4260 set line [string trim $line]
4261 set i [lsearch -exact $fhl_list $line]
4262 if {$i < 0} continue
4263 for {set j 0} {$j < $i} {incr j} {
4264 set id [lindex $fhl_list $j]
4265 set fhighlights($id) 0
4267 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
4268 if {$line eq {}} continue
4269 if {![commitinview $line $curview]} continue
4270 if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
4271 bolden $line mainfontbold
4273 set fhighlights($line) 1
4275 if {[eof $filehighlight]} {
4276 # strange...
4277 puts "oops, git diff-tree died"
4278 catch {close $filehighlight}
4279 unset filehighlight
4280 return 0
4282 if {[info exists find_dirn]} {
4283 run findmore
4285 return 1
4288 proc doesmatch {f} {
4289 global findtype findpattern
4291 if {$findtype eq [mc "Regexp"]} {
4292 return [regexp $findpattern $f]
4293 } elseif {$findtype eq [mc "IgnCase"]} {
4294 return [string match -nocase $findpattern $f]
4295 } else {
4296 return [string match $findpattern $f]
4300 proc askfindhighlight {row id} {
4301 global nhighlights commitinfo iddrawn
4302 global findloc
4303 global markingmatches
4305 if {![info exists commitinfo($id)]} {
4306 getcommit $id
4308 set info $commitinfo($id)
4309 set isbold 0
4310 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
4311 foreach f $info ty $fldtypes {
4312 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4313 [doesmatch $f]} {
4314 if {$ty eq [mc "Author"]} {
4315 set isbold 2
4316 break
4318 set isbold 1
4321 if {$isbold && [info exists iddrawn($id)]} {
4322 if {![ishighlighted $id]} {
4323 bolden $id mainfontbold
4324 if {$isbold > 1} {
4325 bolden_name $id mainfontbold
4328 if {$markingmatches} {
4329 markrowmatches $row $id
4332 set nhighlights($id) $isbold
4335 proc markrowmatches {row id} {
4336 global canv canv2 linehtag linentag commitinfo findloc
4338 set headline [lindex $commitinfo($id) 0]
4339 set author [lindex $commitinfo($id) 1]
4340 $canv delete match$row
4341 $canv2 delete match$row
4342 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
4343 set m [findmatches $headline]
4344 if {$m ne {}} {
4345 markmatches $canv $row $headline $linehtag($id) $m \
4346 [$canv itemcget $linehtag($id) -font] $row
4349 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
4350 set m [findmatches $author]
4351 if {$m ne {}} {
4352 markmatches $canv2 $row $author $linentag($id) $m \
4353 [$canv2 itemcget $linentag($id) -font] $row
4358 proc vrel_change {name ix op} {
4359 global highlight_related
4361 rhighlight_none
4362 if {$highlight_related ne [mc "None"]} {
4363 run drawvisible
4367 # prepare for testing whether commits are descendents or ancestors of a
4368 proc rhighlight_sel {a} {
4369 global descendent desc_todo ancestor anc_todo
4370 global highlight_related
4372 catch {unset descendent}
4373 set desc_todo [list $a]
4374 catch {unset ancestor}
4375 set anc_todo [list $a]
4376 if {$highlight_related ne [mc "None"]} {
4377 rhighlight_none
4378 run drawvisible
4382 proc rhighlight_none {} {
4383 global rhighlights
4385 catch {unset rhighlights}
4386 unbolden
4389 proc is_descendent {a} {
4390 global curview children descendent desc_todo
4392 set v $curview
4393 set la [rowofcommit $a]
4394 set todo $desc_todo
4395 set leftover {}
4396 set done 0
4397 for {set i 0} {$i < [llength $todo]} {incr i} {
4398 set do [lindex $todo $i]
4399 if {[rowofcommit $do] < $la} {
4400 lappend leftover $do
4401 continue
4403 foreach nk $children($v,$do) {
4404 if {![info exists descendent($nk)]} {
4405 set descendent($nk) 1
4406 lappend todo $nk
4407 if {$nk eq $a} {
4408 set done 1
4412 if {$done} {
4413 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4414 return
4417 set descendent($a) 0
4418 set desc_todo $leftover
4421 proc is_ancestor {a} {
4422 global curview parents ancestor anc_todo
4424 set v $curview
4425 set la [rowofcommit $a]
4426 set todo $anc_todo
4427 set leftover {}
4428 set done 0
4429 for {set i 0} {$i < [llength $todo]} {incr i} {
4430 set do [lindex $todo $i]
4431 if {![commitinview $do $v] || [rowofcommit $do] > $la} {
4432 lappend leftover $do
4433 continue
4435 foreach np $parents($v,$do) {
4436 if {![info exists ancestor($np)]} {
4437 set ancestor($np) 1
4438 lappend todo $np
4439 if {$np eq $a} {
4440 set done 1
4444 if {$done} {
4445 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
4446 return
4449 set ancestor($a) 0
4450 set anc_todo $leftover
4453 proc askrelhighlight {row id} {
4454 global descendent highlight_related iddrawn rhighlights
4455 global selectedline ancestor
4457 if {$selectedline eq {}} return
4458 set isbold 0
4459 if {$highlight_related eq [mc "Descendant"] ||
4460 $highlight_related eq [mc "Not descendant"]} {
4461 if {![info exists descendent($id)]} {
4462 is_descendent $id
4464 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
4465 set isbold 1
4467 } elseif {$highlight_related eq [mc "Ancestor"] ||
4468 $highlight_related eq [mc "Not ancestor"]} {
4469 if {![info exists ancestor($id)]} {
4470 is_ancestor $id
4472 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
4473 set isbold 1
4476 if {[info exists iddrawn($id)]} {
4477 if {$isbold && ![ishighlighted $id]} {
4478 bolden $id mainfontbold
4481 set rhighlights($id) $isbold
4484 # Graph layout functions
4486 proc shortids {ids} {
4487 set res {}
4488 foreach id $ids {
4489 if {[llength $id] > 1} {
4490 lappend res [shortids $id]
4491 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
4492 lappend res [string range $id 0 7]
4493 } else {
4494 lappend res $id
4497 return $res
4500 proc ntimes {n o} {
4501 set ret {}
4502 set o [list $o]
4503 for {set mask 1} {$mask <= $n} {incr mask $mask} {
4504 if {($n & $mask) != 0} {
4505 set ret [concat $ret $o]
4507 set o [concat $o $o]
4509 return $ret
4512 proc ordertoken {id} {
4513 global ordertok curview varcid varcstart varctok curview parents children
4514 global nullid nullid2
4516 if {[info exists ordertok($id)]} {
4517 return $ordertok($id)
4519 set origid $id
4520 set todo {}
4521 while {1} {
4522 if {[info exists varcid($curview,$id)]} {
4523 set a $varcid($curview,$id)
4524 set p [lindex $varcstart($curview) $a]
4525 } else {
4526 set p [lindex $children($curview,$id) 0]
4528 if {[info exists ordertok($p)]} {
4529 set tok $ordertok($p)
4530 break
4532 set id [first_real_child $curview,$p]
4533 if {$id eq {}} {
4534 # it's a root
4535 set tok [lindex $varctok($curview) $varcid($curview,$p)]
4536 break
4538 if {[llength $parents($curview,$id)] == 1} {
4539 lappend todo [list $p {}]
4540 } else {
4541 set j [lsearch -exact $parents($curview,$id) $p]
4542 if {$j < 0} {
4543 puts "oops didn't find [shortids $p] in parents of [shortids $id]"
4545 lappend todo [list $p [strrep $j]]
4548 for {set i [llength $todo]} {[incr i -1] >= 0} {} {
4549 set p [lindex $todo $i 0]
4550 append tok [lindex $todo $i 1]
4551 set ordertok($p) $tok
4553 set ordertok($origid) $tok
4554 return $tok
4557 # Work out where id should go in idlist so that order-token
4558 # values increase from left to right
4559 proc idcol {idlist id {i 0}} {
4560 set t [ordertoken $id]
4561 if {$i < 0} {
4562 set i 0
4564 if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
4565 if {$i > [llength $idlist]} {
4566 set i [llength $idlist]
4568 while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
4569 incr i
4570 } else {
4571 if {$t > [ordertoken [lindex $idlist $i]]} {
4572 while {[incr i] < [llength $idlist] &&
4573 $t >= [ordertoken [lindex $idlist $i]]} {}
4576 return $i
4579 proc initlayout {} {
4580 global rowidlist rowisopt rowfinal displayorder parentlist
4581 global numcommits canvxmax canv
4582 global nextcolor
4583 global colormap rowtextx
4585 set numcommits 0
4586 set displayorder {}
4587 set parentlist {}
4588 set nextcolor 0
4589 set rowidlist {}
4590 set rowisopt {}
4591 set rowfinal {}
4592 set canvxmax [$canv cget -width]
4593 catch {unset colormap}
4594 catch {unset rowtextx}
4595 setcanvscroll
4598 proc setcanvscroll {} {
4599 global canv canv2 canv3 numcommits linespc canvxmax canvy0
4600 global lastscrollset lastscrollrows
4602 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
4603 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
4604 $canv2 conf -scrollregion [list 0 0 0 $ymax]
4605 $canv3 conf -scrollregion [list 0 0 0 $ymax]
4606 set lastscrollset [clock clicks -milliseconds]
4607 set lastscrollrows $numcommits
4610 proc visiblerows {} {
4611 global canv numcommits linespc
4613 set ymax [lindex [$canv cget -scrollregion] 3]
4614 if {$ymax eq {} || $ymax == 0} return
4615 set f [$canv yview]
4616 set y0 [expr {int([lindex $f 0] * $ymax)}]
4617 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
4618 if {$r0 < 0} {
4619 set r0 0
4621 set y1 [expr {int([lindex $f 1] * $ymax)}]
4622 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
4623 if {$r1 >= $numcommits} {
4624 set r1 [expr {$numcommits - 1}]
4626 return [list $r0 $r1]
4629 proc layoutmore {} {
4630 global commitidx viewcomplete curview
4631 global numcommits pending_select curview
4632 global lastscrollset lastscrollrows
4634 if {$lastscrollrows < 100 || $viewcomplete($curview) ||
4635 [clock clicks -milliseconds] - $lastscrollset > 500} {
4636 setcanvscroll
4638 if {[info exists pending_select] &&
4639 [commitinview $pending_select $curview]} {
4640 update
4641 selectline [rowofcommit $pending_select] 1
4643 drawvisible
4646 proc doshowlocalchanges {} {
4647 global curview mainheadid
4649 if {$mainheadid eq {}} return
4650 if {[commitinview $mainheadid $curview]} {
4651 dodiffindex
4652 } else {
4653 interestedin $mainheadid dodiffindex
4657 proc dohidelocalchanges {} {
4658 global nullid nullid2 lserial curview
4660 if {[commitinview $nullid $curview]} {
4661 removefakerow $nullid
4663 if {[commitinview $nullid2 $curview]} {
4664 removefakerow $nullid2
4666 incr lserial
4669 # spawn off a process to do git diff-index --cached HEAD
4670 proc dodiffindex {} {
4671 global lserial showlocalchanges
4672 global isworktree
4674 if {!$showlocalchanges || !$isworktree} return
4675 incr lserial
4676 set fd [open "|git diff-index --cached HEAD" r]
4677 fconfigure $fd -blocking 0
4678 set i [reg_instance $fd]
4679 filerun $fd [list readdiffindex $fd $lserial $i]
4682 proc readdiffindex {fd serial inst} {
4683 global mainheadid nullid nullid2 curview commitinfo commitdata lserial
4685 set isdiff 1
4686 if {[gets $fd line] < 0} {
4687 if {![eof $fd]} {
4688 return 1
4690 set isdiff 0
4692 # we only need to see one line and we don't really care what it says...
4693 stop_instance $inst
4695 if {$serial != $lserial} {
4696 return 0
4699 # now see if there are any local changes not checked in to the index
4700 set fd [open "|git diff-files" r]
4701 fconfigure $fd -blocking 0
4702 set i [reg_instance $fd]
4703 filerun $fd [list readdifffiles $fd $serial $i]
4705 if {$isdiff && ![commitinview $nullid2 $curview]} {
4706 # add the line for the changes in the index to the graph
4707 set hl [mc "Local changes checked in to index but not committed"]
4708 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
4709 set commitdata($nullid2) "\n $hl\n"
4710 if {[commitinview $nullid $curview]} {
4711 removefakerow $nullid
4713 insertfakerow $nullid2 $mainheadid
4714 } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
4715 removefakerow $nullid2
4717 return 0
4720 proc readdifffiles {fd serial inst} {
4721 global mainheadid nullid nullid2 curview
4722 global commitinfo commitdata lserial
4724 set isdiff 1
4725 if {[gets $fd line] < 0} {
4726 if {![eof $fd]} {
4727 return 1
4729 set isdiff 0
4731 # we only need to see one line and we don't really care what it says...
4732 stop_instance $inst
4734 if {$serial != $lserial} {
4735 return 0
4738 if {$isdiff && ![commitinview $nullid $curview]} {
4739 # add the line for the local diff to the graph
4740 set hl [mc "Local uncommitted changes, not checked in to index"]
4741 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
4742 set commitdata($nullid) "\n $hl\n"
4743 if {[commitinview $nullid2 $curview]} {
4744 set p $nullid2
4745 } else {
4746 set p $mainheadid
4748 insertfakerow $nullid $p
4749 } elseif {!$isdiff && [commitinview $nullid $curview]} {
4750 removefakerow $nullid
4752 return 0
4755 proc nextuse {id row} {
4756 global curview children
4758 if {[info exists children($curview,$id)]} {
4759 foreach kid $children($curview,$id) {
4760 if {![commitinview $kid $curview]} {
4761 return -1
4763 if {[rowofcommit $kid] > $row} {
4764 return [rowofcommit $kid]
4768 if {[commitinview $id $curview]} {
4769 return [rowofcommit $id]
4771 return -1
4774 proc prevuse {id row} {
4775 global curview children
4777 set ret -1
4778 if {[info exists children($curview,$id)]} {
4779 foreach kid $children($curview,$id) {
4780 if {![commitinview $kid $curview]} break
4781 if {[rowofcommit $kid] < $row} {
4782 set ret [rowofcommit $kid]
4786 return $ret
4789 proc make_idlist {row} {
4790 global displayorder parentlist uparrowlen downarrowlen mingaplen
4791 global commitidx curview children
4793 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
4794 if {$r < 0} {
4795 set r 0
4797 set ra [expr {$row - $downarrowlen}]
4798 if {$ra < 0} {
4799 set ra 0
4801 set rb [expr {$row + $uparrowlen}]
4802 if {$rb > $commitidx($curview)} {
4803 set rb $commitidx($curview)
4805 make_disporder $r [expr {$rb + 1}]
4806 set ids {}
4807 for {} {$r < $ra} {incr r} {
4808 set nextid [lindex $displayorder [expr {$r + 1}]]
4809 foreach p [lindex $parentlist $r] {
4810 if {$p eq $nextid} continue
4811 set rn [nextuse $p $r]
4812 if {$rn >= $row &&
4813 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
4814 lappend ids [list [ordertoken $p] $p]
4818 for {} {$r < $row} {incr r} {
4819 set nextid [lindex $displayorder [expr {$r + 1}]]
4820 foreach p [lindex $parentlist $r] {
4821 if {$p eq $nextid} continue
4822 set rn [nextuse $p $r]
4823 if {$rn < 0 || $rn >= $row} {
4824 lappend ids [list [ordertoken $p] $p]
4828 set id [lindex $displayorder $row]
4829 lappend ids [list [ordertoken $id] $id]
4830 while {$r < $rb} {
4831 foreach p [lindex $parentlist $r] {
4832 set firstkid [lindex $children($curview,$p) 0]
4833 if {[rowofcommit $firstkid] < $row} {
4834 lappend ids [list [ordertoken $p] $p]
4837 incr r
4838 set id [lindex $displayorder $r]
4839 if {$id ne {}} {
4840 set firstkid [lindex $children($curview,$id) 0]
4841 if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
4842 lappend ids [list [ordertoken $id] $id]
4846 set idlist {}
4847 foreach idx [lsort -unique $ids] {
4848 lappend idlist [lindex $idx 1]
4850 return $idlist
4853 proc rowsequal {a b} {
4854 while {[set i [lsearch -exact $a {}]] >= 0} {
4855 set a [lreplace $a $i $i]
4857 while {[set i [lsearch -exact $b {}]] >= 0} {
4858 set b [lreplace $b $i $i]
4860 return [expr {$a eq $b}]
4863 proc makeupline {id row rend col} {
4864 global rowidlist uparrowlen downarrowlen mingaplen
4866 for {set r $rend} {1} {set r $rstart} {
4867 set rstart [prevuse $id $r]
4868 if {$rstart < 0} return
4869 if {$rstart < $row} break
4871 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
4872 set rstart [expr {$rend - $uparrowlen - 1}]
4874 for {set r $rstart} {[incr r] <= $row} {} {
4875 set idlist [lindex $rowidlist $r]
4876 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
4877 set col [idcol $idlist $id $col]
4878 lset rowidlist $r [linsert $idlist $col $id]
4879 changedrow $r
4884 proc layoutrows {row endrow} {
4885 global rowidlist rowisopt rowfinal displayorder
4886 global uparrowlen downarrowlen maxwidth mingaplen
4887 global children parentlist
4888 global commitidx viewcomplete curview
4890 make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
4891 set idlist {}
4892 if {$row > 0} {
4893 set rm1 [expr {$row - 1}]
4894 foreach id [lindex $rowidlist $rm1] {
4895 if {$id ne {}} {
4896 lappend idlist $id
4899 set final [lindex $rowfinal $rm1]
4901 for {} {$row < $endrow} {incr row} {
4902 set rm1 [expr {$row - 1}]
4903 if {$rm1 < 0 || $idlist eq {}} {
4904 set idlist [make_idlist $row]
4905 set final 1
4906 } else {
4907 set id [lindex $displayorder $rm1]
4908 set col [lsearch -exact $idlist $id]
4909 set idlist [lreplace $idlist $col $col]
4910 foreach p [lindex $parentlist $rm1] {
4911 if {[lsearch -exact $idlist $p] < 0} {
4912 set col [idcol $idlist $p $col]
4913 set idlist [linsert $idlist $col $p]
4914 # if not the first child, we have to insert a line going up
4915 if {$id ne [lindex $children($curview,$p) 0]} {
4916 makeupline $p $rm1 $row $col
4920 set id [lindex $displayorder $row]
4921 if {$row > $downarrowlen} {
4922 set termrow [expr {$row - $downarrowlen - 1}]
4923 foreach p [lindex $parentlist $termrow] {
4924 set i [lsearch -exact $idlist $p]
4925 if {$i < 0} continue
4926 set nr [nextuse $p $termrow]
4927 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
4928 set idlist [lreplace $idlist $i $i]
4932 set col [lsearch -exact $idlist $id]
4933 if {$col < 0} {
4934 set col [idcol $idlist $id]
4935 set idlist [linsert $idlist $col $id]
4936 if {$children($curview,$id) ne {}} {
4937 makeupline $id $rm1 $row $col
4940 set r [expr {$row + $uparrowlen - 1}]
4941 if {$r < $commitidx($curview)} {
4942 set x $col
4943 foreach p [lindex $parentlist $r] {
4944 if {[lsearch -exact $idlist $p] >= 0} continue
4945 set fk [lindex $children($curview,$p) 0]
4946 if {[rowofcommit $fk] < $row} {
4947 set x [idcol $idlist $p $x]
4948 set idlist [linsert $idlist $x $p]
4951 if {[incr r] < $commitidx($curview)} {
4952 set p [lindex $displayorder $r]
4953 if {[lsearch -exact $idlist $p] < 0} {
4954 set fk [lindex $children($curview,$p) 0]
4955 if {$fk ne {} && [rowofcommit $fk] < $row} {
4956 set x [idcol $idlist $p $x]
4957 set idlist [linsert $idlist $x $p]
4963 if {$final && !$viewcomplete($curview) &&
4964 $row + $uparrowlen + $mingaplen + $downarrowlen
4965 >= $commitidx($curview)} {
4966 set final 0
4968 set l [llength $rowidlist]
4969 if {$row == $l} {
4970 lappend rowidlist $idlist
4971 lappend rowisopt 0
4972 lappend rowfinal $final
4973 } elseif {$row < $l} {
4974 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
4975 lset rowidlist $row $idlist
4976 changedrow $row
4978 lset rowfinal $row $final
4979 } else {
4980 set pad [ntimes [expr {$row - $l}] {}]
4981 set rowidlist [concat $rowidlist $pad]
4982 lappend rowidlist $idlist
4983 set rowfinal [concat $rowfinal $pad]
4984 lappend rowfinal $final
4985 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
4988 return $row
4991 proc changedrow {row} {
4992 global displayorder iddrawn rowisopt need_redisplay
4994 set l [llength $rowisopt]
4995 if {$row < $l} {
4996 lset rowisopt $row 0
4997 if {$row + 1 < $l} {
4998 lset rowisopt [expr {$row + 1}] 0
4999 if {$row + 2 < $l} {
5000 lset rowisopt [expr {$row + 2}] 0
5004 set id [lindex $displayorder $row]
5005 if {[info exists iddrawn($id)]} {
5006 set need_redisplay 1
5010 proc insert_pad {row col npad} {
5011 global rowidlist
5013 set pad [ntimes $npad {}]
5014 set idlist [lindex $rowidlist $row]
5015 set bef [lrange $idlist 0 [expr {$col - 1}]]
5016 set aft [lrange $idlist $col end]
5017 set i [lsearch -exact $aft {}]
5018 if {$i > 0} {
5019 set aft [lreplace $aft $i $i]
5021 lset rowidlist $row [concat $bef $pad $aft]
5022 changedrow $row
5025 proc optimize_rows {row col endrow} {
5026 global rowidlist rowisopt displayorder curview children
5028 if {$row < 1} {
5029 set row 1
5031 for {} {$row < $endrow} {incr row; set col 0} {
5032 if {[lindex $rowisopt $row]} continue
5033 set haspad 0
5034 set y0 [expr {$row - 1}]
5035 set ym [expr {$row - 2}]
5036 set idlist [lindex $rowidlist $row]
5037 set previdlist [lindex $rowidlist $y0]
5038 if {$idlist eq {} || $previdlist eq {}} continue
5039 if {$ym >= 0} {
5040 set pprevidlist [lindex $rowidlist $ym]
5041 if {$pprevidlist eq {}} continue
5042 } else {
5043 set pprevidlist {}
5045 set x0 -1
5046 set xm -1
5047 for {} {$col < [llength $idlist]} {incr col} {
5048 set id [lindex $idlist $col]
5049 if {[lindex $previdlist $col] eq $id} continue
5050 if {$id eq {}} {
5051 set haspad 1
5052 continue
5054 set x0 [lsearch -exact $previdlist $id]
5055 if {$x0 < 0} continue
5056 set z [expr {$x0 - $col}]
5057 set isarrow 0
5058 set z0 {}
5059 if {$ym >= 0} {
5060 set xm [lsearch -exact $pprevidlist $id]
5061 if {$xm >= 0} {
5062 set z0 [expr {$xm - $x0}]
5065 if {$z0 eq {}} {
5066 # if row y0 is the first child of $id then it's not an arrow
5067 if {[lindex $children($curview,$id) 0] ne
5068 [lindex $displayorder $y0]} {
5069 set isarrow 1
5072 if {!$isarrow && $id ne [lindex $displayorder $row] &&
5073 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
5074 set isarrow 1
5076 # Looking at lines from this row to the previous row,
5077 # make them go straight up if they end in an arrow on
5078 # the previous row; otherwise make them go straight up
5079 # or at 45 degrees.
5080 if {$z < -1 || ($z < 0 && $isarrow)} {
5081 # Line currently goes left too much;
5082 # insert pads in the previous row, then optimize it
5083 set npad [expr {-1 - $z + $isarrow}]
5084 insert_pad $y0 $x0 $npad
5085 if {$y0 > 0} {
5086 optimize_rows $y0 $x0 $row
5088 set previdlist [lindex $rowidlist $y0]
5089 set x0 [lsearch -exact $previdlist $id]
5090 set z [expr {$x0 - $col}]
5091 if {$z0 ne {}} {
5092 set pprevidlist [lindex $rowidlist $ym]
5093 set xm [lsearch -exact $pprevidlist $id]
5094 set z0 [expr {$xm - $x0}]
5096 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
5097 # Line currently goes right too much;
5098 # insert pads in this line
5099 set npad [expr {$z - 1 + $isarrow}]
5100 insert_pad $row $col $npad
5101 set idlist [lindex $rowidlist $row]
5102 incr col $npad
5103 set z [expr {$x0 - $col}]
5104 set haspad 1
5106 if {$z0 eq {} && !$isarrow && $ym >= 0} {
5107 # this line links to its first child on row $row-2
5108 set id [lindex $displayorder $ym]
5109 set xc [lsearch -exact $pprevidlist $id]
5110 if {$xc >= 0} {
5111 set z0 [expr {$xc - $x0}]
5114 # avoid lines jigging left then immediately right
5115 if {$z0 ne {} && $z < 0 && $z0 > 0} {
5116 insert_pad $y0 $x0 1
5117 incr x0
5118 optimize_rows $y0 $x0 $row
5119 set previdlist [lindex $rowidlist $y0]
5122 if {!$haspad} {
5123 # Find the first column that doesn't have a line going right
5124 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
5125 set id [lindex $idlist $col]
5126 if {$id eq {}} break
5127 set x0 [lsearch -exact $previdlist $id]
5128 if {$x0 < 0} {
5129 # check if this is the link to the first child
5130 set kid [lindex $displayorder $y0]
5131 if {[lindex $children($curview,$id) 0] eq $kid} {
5132 # it is, work out offset to child
5133 set x0 [lsearch -exact $previdlist $kid]
5136 if {$x0 <= $col} break
5138 # Insert a pad at that column as long as it has a line and
5139 # isn't the last column
5140 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
5141 set idlist [linsert $idlist $col {}]
5142 lset rowidlist $row $idlist
5143 changedrow $row
5149 proc xc {row col} {
5150 global canvx0 linespc
5151 return [expr {$canvx0 + $col * $linespc}]
5154 proc yc {row} {
5155 global canvy0 linespc
5156 return [expr {$canvy0 + $row * $linespc}]
5159 proc linewidth {id} {
5160 global thickerline lthickness
5162 set wid $lthickness
5163 if {[info exists thickerline] && $id eq $thickerline} {
5164 set wid [expr {2 * $lthickness}]
5166 return $wid
5169 proc rowranges {id} {
5170 global curview children uparrowlen downarrowlen
5171 global rowidlist
5173 set kids $children($curview,$id)
5174 if {$kids eq {}} {
5175 return {}
5177 set ret {}
5178 lappend kids $id
5179 foreach child $kids {
5180 if {![commitinview $child $curview]} break
5181 set row [rowofcommit $child]
5182 if {![info exists prev]} {
5183 lappend ret [expr {$row + 1}]
5184 } else {
5185 if {$row <= $prevrow} {
5186 puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
5188 # see if the line extends the whole way from prevrow to row
5189 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
5190 [lsearch -exact [lindex $rowidlist \
5191 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
5192 # it doesn't, see where it ends
5193 set r [expr {$prevrow + $downarrowlen}]
5194 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5195 while {[incr r -1] > $prevrow &&
5196 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5197 } else {
5198 while {[incr r] <= $row &&
5199 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5200 incr r -1
5202 lappend ret $r
5203 # see where it starts up again
5204 set r [expr {$row - $uparrowlen}]
5205 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
5206 while {[incr r] < $row &&
5207 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
5208 } else {
5209 while {[incr r -1] >= $prevrow &&
5210 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
5211 incr r
5213 lappend ret $r
5216 if {$child eq $id} {
5217 lappend ret $row
5219 set prev $child
5220 set prevrow $row
5222 return $ret
5225 proc drawlineseg {id row endrow arrowlow} {
5226 global rowidlist displayorder iddrawn linesegs
5227 global canv colormap linespc curview maxlinelen parentlist
5229 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
5230 set le [expr {$row + 1}]
5231 set arrowhigh 1
5232 while {1} {
5233 set c [lsearch -exact [lindex $rowidlist $le] $id]
5234 if {$c < 0} {
5235 incr le -1
5236 break
5238 lappend cols $c
5239 set x [lindex $displayorder $le]
5240 if {$x eq $id} {
5241 set arrowhigh 0
5242 break
5244 if {[info exists iddrawn($x)] || $le == $endrow} {
5245 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
5246 if {$c >= 0} {
5247 lappend cols $c
5248 set arrowhigh 0
5250 break
5252 incr le
5254 if {$le <= $row} {
5255 return $row
5258 set lines {}
5259 set i 0
5260 set joinhigh 0
5261 if {[info exists linesegs($id)]} {
5262 set lines $linesegs($id)
5263 foreach li $lines {
5264 set r0 [lindex $li 0]
5265 if {$r0 > $row} {
5266 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
5267 set joinhigh 1
5269 break
5271 incr i
5274 set joinlow 0
5275 if {$i > 0} {
5276 set li [lindex $lines [expr {$i-1}]]
5277 set r1 [lindex $li 1]
5278 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
5279 set joinlow 1
5283 set x [lindex $cols [expr {$le - $row}]]
5284 set xp [lindex $cols [expr {$le - 1 - $row}]]
5285 set dir [expr {$xp - $x}]
5286 if {$joinhigh} {
5287 set ith [lindex $lines $i 2]
5288 set coords [$canv coords $ith]
5289 set ah [$canv itemcget $ith -arrow]
5290 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
5291 set x2 [lindex $cols [expr {$le + 1 - $row}]]
5292 if {$x2 ne {} && $x - $x2 == $dir} {
5293 set coords [lrange $coords 0 end-2]
5295 } else {
5296 set coords [list [xc $le $x] [yc $le]]
5298 if {$joinlow} {
5299 set itl [lindex $lines [expr {$i-1}] 2]
5300 set al [$canv itemcget $itl -arrow]
5301 set arrowlow [expr {$al eq "last" || $al eq "both"}]
5302 } elseif {$arrowlow} {
5303 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
5304 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
5305 set arrowlow 0
5308 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
5309 for {set y $le} {[incr y -1] > $row} {} {
5310 set x $xp
5311 set xp [lindex $cols [expr {$y - 1 - $row}]]
5312 set ndir [expr {$xp - $x}]
5313 if {$dir != $ndir || $xp < 0} {
5314 lappend coords [xc $y $x] [yc $y]
5316 set dir $ndir
5318 if {!$joinlow} {
5319 if {$xp < 0} {
5320 # join parent line to first child
5321 set ch [lindex $displayorder $row]
5322 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
5323 if {$xc < 0} {
5324 puts "oops: drawlineseg: child $ch not on row $row"
5325 } elseif {$xc != $x} {
5326 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
5327 set d [expr {int(0.5 * $linespc)}]
5328 set x1 [xc $row $x]
5329 if {$xc < $x} {
5330 set x2 [expr {$x1 - $d}]
5331 } else {
5332 set x2 [expr {$x1 + $d}]
5334 set y2 [yc $row]
5335 set y1 [expr {$y2 + $d}]
5336 lappend coords $x1 $y1 $x2 $y2
5337 } elseif {$xc < $x - 1} {
5338 lappend coords [xc $row [expr {$x-1}]] [yc $row]
5339 } elseif {$xc > $x + 1} {
5340 lappend coords [xc $row [expr {$x+1}]] [yc $row]
5342 set x $xc
5344 lappend coords [xc $row $x] [yc $row]
5345 } else {
5346 set xn [xc $row $xp]
5347 set yn [yc $row]
5348 lappend coords $xn $yn
5350 if {!$joinhigh} {
5351 assigncolor $id
5352 set t [$canv create line $coords -width [linewidth $id] \
5353 -fill $colormap($id) -tags lines.$id -arrow $arrow]
5354 $canv lower $t
5355 bindline $t $id
5356 set lines [linsert $lines $i [list $row $le $t]]
5357 } else {
5358 $canv coords $ith $coords
5359 if {$arrow ne $ah} {
5360 $canv itemconf $ith -arrow $arrow
5362 lset lines $i 0 $row
5364 } else {
5365 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
5366 set ndir [expr {$xo - $xp}]
5367 set clow [$canv coords $itl]
5368 if {$dir == $ndir} {
5369 set clow [lrange $clow 2 end]
5371 set coords [concat $coords $clow]
5372 if {!$joinhigh} {
5373 lset lines [expr {$i-1}] 1 $le
5374 } else {
5375 # coalesce two pieces
5376 $canv delete $ith
5377 set b [lindex $lines [expr {$i-1}] 0]
5378 set e [lindex $lines $i 1]
5379 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
5381 $canv coords $itl $coords
5382 if {$arrow ne $al} {
5383 $canv itemconf $itl -arrow $arrow
5387 set linesegs($id) $lines
5388 return $le
5391 proc drawparentlinks {id row} {
5392 global rowidlist canv colormap curview parentlist
5393 global idpos linespc
5395 set rowids [lindex $rowidlist $row]
5396 set col [lsearch -exact $rowids $id]
5397 if {$col < 0} return
5398 set olds [lindex $parentlist $row]
5399 set row2 [expr {$row + 1}]
5400 set x [xc $row $col]
5401 set y [yc $row]
5402 set y2 [yc $row2]
5403 set d [expr {int(0.5 * $linespc)}]
5404 set ymid [expr {$y + $d}]
5405 set ids [lindex $rowidlist $row2]
5406 # rmx = right-most X coord used
5407 set rmx 0
5408 foreach p $olds {
5409 set i [lsearch -exact $ids $p]
5410 if {$i < 0} {
5411 puts "oops, parent $p of $id not in list"
5412 continue
5414 set x2 [xc $row2 $i]
5415 if {$x2 > $rmx} {
5416 set rmx $x2
5418 set j [lsearch -exact $rowids $p]
5419 if {$j < 0} {
5420 # drawlineseg will do this one for us
5421 continue
5423 assigncolor $p
5424 # should handle duplicated parents here...
5425 set coords [list $x $y]
5426 if {$i != $col} {
5427 # if attaching to a vertical segment, draw a smaller
5428 # slant for visual distinctness
5429 if {$i == $j} {
5430 if {$i < $col} {
5431 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
5432 } else {
5433 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
5435 } elseif {$i < $col && $i < $j} {
5436 # segment slants towards us already
5437 lappend coords [xc $row $j] $y
5438 } else {
5439 if {$i < $col - 1} {
5440 lappend coords [expr {$x2 + $linespc}] $y
5441 } elseif {$i > $col + 1} {
5442 lappend coords [expr {$x2 - $linespc}] $y
5444 lappend coords $x2 $y2
5446 } else {
5447 lappend coords $x2 $y2
5449 set t [$canv create line $coords -width [linewidth $p] \
5450 -fill $colormap($p) -tags lines.$p]
5451 $canv lower $t
5452 bindline $t $p
5454 if {$rmx > [lindex $idpos($id) 1]} {
5455 lset idpos($id) 1 $rmx
5456 redrawtags $id
5460 proc drawlines {id} {
5461 global canv
5463 $canv itemconf lines.$id -width [linewidth $id]
5466 proc drawcmittext {id row col} {
5467 global linespc canv canv2 canv3 fgcolor curview
5468 global cmitlisted commitinfo rowidlist parentlist
5469 global rowtextx idpos idtags idheads idotherrefs
5470 global linehtag linentag linedtag selectedline
5471 global canvxmax boldids boldnameids fgcolor
5472 global mainheadid nullid nullid2 circleitem circlecolors ctxbut
5474 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
5475 set listed $cmitlisted($curview,$id)
5476 if {$id eq $nullid} {
5477 set ofill red
5478 } elseif {$id eq $nullid2} {
5479 set ofill green
5480 } elseif {$id eq $mainheadid} {
5481 set ofill yellow
5482 } else {
5483 set ofill [lindex $circlecolors $listed]
5485 set x [xc $row $col]
5486 set y [yc $row]
5487 set orad [expr {$linespc / 3}]
5488 if {$listed <= 2} {
5489 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
5490 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5491 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5492 } elseif {$listed == 3} {
5493 # triangle pointing left for left-side commits
5494 set t [$canv create polygon \
5495 [expr {$x - $orad}] $y \
5496 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
5497 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
5498 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5499 } else {
5500 # triangle pointing right for right-side commits
5501 set t [$canv create polygon \
5502 [expr {$x + $orad - 1}] $y \
5503 [expr {$x - $orad}] [expr {$y - $orad}] \
5504 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
5505 -fill $ofill -outline $fgcolor -width 1 -tags circle]
5507 set circleitem($row) $t
5508 $canv raise $t
5509 $canv bind $t <1> {selcanvline {} %x %y}
5510 set rmx [llength [lindex $rowidlist $row]]
5511 set olds [lindex $parentlist $row]
5512 if {$olds ne {}} {
5513 set nextids [lindex $rowidlist [expr {$row + 1}]]
5514 foreach p $olds {
5515 set i [lsearch -exact $nextids $p]
5516 if {$i > $rmx} {
5517 set rmx $i
5521 set xt [xc $row $rmx]
5522 set rowtextx($row) $xt
5523 set idpos($id) [list $x $xt $y]
5524 if {[info exists idtags($id)] || [info exists idheads($id)]
5525 || [info exists idotherrefs($id)]} {
5526 set xt [drawtags $id $x $xt $y]
5528 set headline [lindex $commitinfo($id) 0]
5529 set name [lindex $commitinfo($id) 1]
5530 set date [lindex $commitinfo($id) 2]
5531 set date [formatdate $date]
5532 set font mainfont
5533 set nfont mainfont
5534 set isbold [ishighlighted $id]
5535 if {$isbold > 0} {
5536 lappend boldids $id
5537 set font mainfontbold
5538 if {$isbold > 1} {
5539 lappend boldnameids $id
5540 set nfont mainfontbold
5543 set linehtag($id) [$canv create text $xt $y -anchor w -fill $fgcolor \
5544 -text $headline -font $font -tags text]
5545 $canv bind $linehtag($id) $ctxbut "rowmenu %X %Y $id"
5546 set linentag($id) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
5547 -text $name -font $nfont -tags text]
5548 set linedtag($id) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
5549 -text $date -font mainfont -tags text]
5550 if {$selectedline == $row} {
5551 make_secsel $id
5553 set xr [expr {$xt + [font measure $font $headline]}]
5554 if {$xr > $canvxmax} {
5555 set canvxmax $xr
5556 setcanvscroll
5560 proc drawcmitrow {row} {
5561 global displayorder rowidlist nrows_drawn
5562 global iddrawn markingmatches
5563 global commitinfo numcommits
5564 global filehighlight fhighlights findpattern nhighlights
5565 global hlview vhighlights
5566 global highlight_related rhighlights
5568 if {$row >= $numcommits} return
5570 set id [lindex $displayorder $row]
5571 if {[info exists hlview] && ![info exists vhighlights($id)]} {
5572 askvhighlight $row $id
5574 if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
5575 askfilehighlight $row $id
5577 if {$findpattern ne {} && ![info exists nhighlights($id)]} {
5578 askfindhighlight $row $id
5580 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
5581 askrelhighlight $row $id
5583 if {![info exists iddrawn($id)]} {
5584 set col [lsearch -exact [lindex $rowidlist $row] $id]
5585 if {$col < 0} {
5586 puts "oops, row $row id $id not in list"
5587 return
5589 if {![info exists commitinfo($id)]} {
5590 getcommit $id
5592 assigncolor $id
5593 drawcmittext $id $row $col
5594 set iddrawn($id) 1
5595 incr nrows_drawn
5597 if {$markingmatches} {
5598 markrowmatches $row $id
5602 proc drawcommits {row {endrow {}}} {
5603 global numcommits iddrawn displayorder curview need_redisplay
5604 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
5606 if {$row < 0} {
5607 set row 0
5609 if {$endrow eq {}} {
5610 set endrow $row
5612 if {$endrow >= $numcommits} {
5613 set endrow [expr {$numcommits - 1}]
5616 set rl1 [expr {$row - $downarrowlen - 3}]
5617 if {$rl1 < 0} {
5618 set rl1 0
5620 set ro1 [expr {$row - 3}]
5621 if {$ro1 < 0} {
5622 set ro1 0
5624 set r2 [expr {$endrow + $uparrowlen + 3}]
5625 if {$r2 > $numcommits} {
5626 set r2 $numcommits
5628 for {set r $rl1} {$r < $r2} {incr r} {
5629 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
5630 if {$rl1 < $r} {
5631 layoutrows $rl1 $r
5633 set rl1 [expr {$r + 1}]
5636 if {$rl1 < $r} {
5637 layoutrows $rl1 $r
5639 optimize_rows $ro1 0 $r2
5640 if {$need_redisplay || $nrows_drawn > 2000} {
5641 clear_display
5642 drawvisible
5645 # make the lines join to already-drawn rows either side
5646 set r [expr {$row - 1}]
5647 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
5648 set r $row
5650 set er [expr {$endrow + 1}]
5651 if {$er >= $numcommits ||
5652 ![info exists iddrawn([lindex $displayorder $er])]} {
5653 set er $endrow
5655 for {} {$r <= $er} {incr r} {
5656 set id [lindex $displayorder $r]
5657 set wasdrawn [info exists iddrawn($id)]
5658 drawcmitrow $r
5659 if {$r == $er} break
5660 set nextid [lindex $displayorder [expr {$r + 1}]]
5661 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
5662 drawparentlinks $id $r
5664 set rowids [lindex $rowidlist $r]
5665 foreach lid $rowids {
5666 if {$lid eq {}} continue
5667 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
5668 if {$lid eq $id} {
5669 # see if this is the first child of any of its parents
5670 foreach p [lindex $parentlist $r] {
5671 if {[lsearch -exact $rowids $p] < 0} {
5672 # make this line extend up to the child
5673 set lineend($p) [drawlineseg $p $r $er 0]
5676 } else {
5677 set lineend($lid) [drawlineseg $lid $r $er 1]
5683 proc undolayout {row} {
5684 global uparrowlen mingaplen downarrowlen
5685 global rowidlist rowisopt rowfinal need_redisplay
5687 set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
5688 if {$r < 0} {
5689 set r 0
5691 if {[llength $rowidlist] > $r} {
5692 incr r -1
5693 set rowidlist [lrange $rowidlist 0 $r]
5694 set rowfinal [lrange $rowfinal 0 $r]
5695 set rowisopt [lrange $rowisopt 0 $r]
5696 set need_redisplay 1
5697 run drawvisible
5701 proc drawvisible {} {
5702 global canv linespc curview vrowmod selectedline targetrow targetid
5703 global need_redisplay cscroll numcommits
5705 set fs [$canv yview]
5706 set ymax [lindex [$canv cget -scrollregion] 3]
5707 if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
5708 set f0 [lindex $fs 0]
5709 set f1 [lindex $fs 1]
5710 set y0 [expr {int($f0 * $ymax)}]
5711 set y1 [expr {int($f1 * $ymax)}]
5713 if {[info exists targetid]} {
5714 if {[commitinview $targetid $curview]} {
5715 set r [rowofcommit $targetid]
5716 if {$r != $targetrow} {
5717 # Fix up the scrollregion and change the scrolling position
5718 # now that our target row has moved.
5719 set diff [expr {($r - $targetrow) * $linespc}]
5720 set targetrow $r
5721 setcanvscroll
5722 set ymax [lindex [$canv cget -scrollregion] 3]
5723 incr y0 $diff
5724 incr y1 $diff
5725 set f0 [expr {$y0 / $ymax}]
5726 set f1 [expr {$y1 / $ymax}]
5727 allcanvs yview moveto $f0
5728 $cscroll set $f0 $f1
5729 set need_redisplay 1
5731 } else {
5732 unset targetid
5736 set row [expr {int(($y0 - 3) / $linespc) - 1}]
5737 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
5738 if {$endrow >= $vrowmod($curview)} {
5739 update_arcrows $curview
5741 if {$selectedline ne {} &&
5742 $row <= $selectedline && $selectedline <= $endrow} {
5743 set targetrow $selectedline
5744 } elseif {[info exists targetid]} {
5745 set targetrow [expr {int(($row + $endrow) / 2)}]
5747 if {[info exists targetrow]} {
5748 if {$targetrow >= $numcommits} {
5749 set targetrow [expr {$numcommits - 1}]
5751 set targetid [commitonrow $targetrow]
5753 drawcommits $row $endrow
5756 proc clear_display {} {
5757 global iddrawn linesegs need_redisplay nrows_drawn
5758 global vhighlights fhighlights nhighlights rhighlights
5759 global linehtag linentag linedtag boldids boldnameids
5761 allcanvs delete all
5762 catch {unset iddrawn}
5763 catch {unset linesegs}
5764 catch {unset linehtag}
5765 catch {unset linentag}
5766 catch {unset linedtag}
5767 set boldids {}
5768 set boldnameids {}
5769 catch {unset vhighlights}
5770 catch {unset fhighlights}
5771 catch {unset nhighlights}
5772 catch {unset rhighlights}
5773 set need_redisplay 0
5774 set nrows_drawn 0
5777 proc findcrossings {id} {
5778 global rowidlist parentlist numcommits displayorder
5780 set cross {}
5781 set ccross {}
5782 foreach {s e} [rowranges $id] {
5783 if {$e >= $numcommits} {
5784 set e [expr {$numcommits - 1}]
5786 if {$e <= $s} continue
5787 for {set row $e} {[incr row -1] >= $s} {} {
5788 set x [lsearch -exact [lindex $rowidlist $row] $id]
5789 if {$x < 0} break
5790 set olds [lindex $parentlist $row]
5791 set kid [lindex $displayorder $row]
5792 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
5793 if {$kidx < 0} continue
5794 set nextrow [lindex $rowidlist [expr {$row + 1}]]
5795 foreach p $olds {
5796 set px [lsearch -exact $nextrow $p]
5797 if {$px < 0} continue
5798 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
5799 if {[lsearch -exact $ccross $p] >= 0} continue
5800 if {$x == $px + ($kidx < $px? -1: 1)} {
5801 lappend ccross $p
5802 } elseif {[lsearch -exact $cross $p] < 0} {
5803 lappend cross $p
5809 return [concat $ccross {{}} $cross]
5812 proc assigncolor {id} {
5813 global colormap colors nextcolor
5814 global parents children children curview
5816 if {[info exists colormap($id)]} return
5817 set ncolors [llength $colors]
5818 if {[info exists children($curview,$id)]} {
5819 set kids $children($curview,$id)
5820 } else {
5821 set kids {}
5823 if {[llength $kids] == 1} {
5824 set child [lindex $kids 0]
5825 if {[info exists colormap($child)]
5826 && [llength $parents($curview,$child)] == 1} {
5827 set colormap($id) $colormap($child)
5828 return
5831 set badcolors {}
5832 set origbad {}
5833 foreach x [findcrossings $id] {
5834 if {$x eq {}} {
5835 # delimiter between corner crossings and other crossings
5836 if {[llength $badcolors] >= $ncolors - 1} break
5837 set origbad $badcolors
5839 if {[info exists colormap($x)]
5840 && [lsearch -exact $badcolors $colormap($x)] < 0} {
5841 lappend badcolors $colormap($x)
5844 if {[llength $badcolors] >= $ncolors} {
5845 set badcolors $origbad
5847 set origbad $badcolors
5848 if {[llength $badcolors] < $ncolors - 1} {
5849 foreach child $kids {
5850 if {[info exists colormap($child)]
5851 && [lsearch -exact $badcolors $colormap($child)] < 0} {
5852 lappend badcolors $colormap($child)
5854 foreach p $parents($curview,$child) {
5855 if {[info exists colormap($p)]
5856 && [lsearch -exact $badcolors $colormap($p)] < 0} {
5857 lappend badcolors $colormap($p)
5861 if {[llength $badcolors] >= $ncolors} {
5862 set badcolors $origbad
5865 for {set i 0} {$i <= $ncolors} {incr i} {
5866 set c [lindex $colors $nextcolor]
5867 if {[incr nextcolor] >= $ncolors} {
5868 set nextcolor 0
5870 if {[lsearch -exact $badcolors $c]} break
5872 set colormap($id) $c
5875 proc bindline {t id} {
5876 global canv
5878 $canv bind $t <Enter> "lineenter %x %y $id"
5879 $canv bind $t <Motion> "linemotion %x %y $id"
5880 $canv bind $t <Leave> "lineleave $id"
5881 $canv bind $t <Button-1> "lineclick %x %y $id 1"
5884 proc drawtags {id x xt y1} {
5885 global idtags idheads idotherrefs mainhead
5886 global linespc lthickness
5887 global canv rowtextx curview fgcolor bgcolor ctxbut
5889 set marks {}
5890 set ntags 0
5891 set nheads 0
5892 if {[info exists idtags($id)]} {
5893 set marks $idtags($id)
5894 set ntags [llength $marks]
5896 if {[info exists idheads($id)]} {
5897 set marks [concat $marks $idheads($id)]
5898 set nheads [llength $idheads($id)]
5900 if {[info exists idotherrefs($id)]} {
5901 set marks [concat $marks $idotherrefs($id)]
5903 if {$marks eq {}} {
5904 return $xt
5907 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
5908 set yt [expr {$y1 - 0.5 * $linespc}]
5909 set yb [expr {$yt + $linespc - 1}]
5910 set xvals {}
5911 set wvals {}
5912 set i -1
5913 foreach tag $marks {
5914 incr i
5915 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
5916 set wid [font measure mainfontbold $tag]
5917 } else {
5918 set wid [font measure mainfont $tag]
5920 lappend xvals $xt
5921 lappend wvals $wid
5922 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
5924 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
5925 -width $lthickness -fill black -tags tag.$id]
5926 $canv lower $t
5927 foreach tag $marks x $xvals wid $wvals {
5928 set xl [expr {$x + $delta}]
5929 set xr [expr {$x + $delta + $wid + $lthickness}]
5930 set font mainfont
5931 if {[incr ntags -1] >= 0} {
5932 # draw a tag
5933 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
5934 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
5935 -width 1 -outline black -fill yellow -tags tag.$id]
5936 $canv bind $t <1> [list showtag $tag 1]
5937 set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
5938 } else {
5939 # draw a head or other ref
5940 if {[incr nheads -1] >= 0} {
5941 set col green
5942 if {$tag eq $mainhead} {
5943 set font mainfontbold
5945 } else {
5946 set col "#ddddff"
5948 set xl [expr {$xl - $delta/2}]
5949 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
5950 -width 1 -outline black -fill $col -tags tag.$id
5951 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
5952 set rwid [font measure mainfont $remoteprefix]
5953 set xi [expr {$x + 1}]
5954 set yti [expr {$yt + 1}]
5955 set xri [expr {$x + $rwid}]
5956 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
5957 -width 0 -fill "#ffddaa" -tags tag.$id
5960 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
5961 -font $font -tags [list tag.$id text]]
5962 if {$ntags >= 0} {
5963 $canv bind $t <1> [list showtag $tag 1]
5964 } elseif {$nheads >= 0} {
5965 $canv bind $t $ctxbut [list headmenu %X %Y $id $tag]
5968 return $xt
5971 proc xcoord {i level ln} {
5972 global canvx0 xspc1 xspc2
5974 set x [expr {$canvx0 + $i * $xspc1($ln)}]
5975 if {$i > 0 && $i == $level} {
5976 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
5977 } elseif {$i > $level} {
5978 set x [expr {$x + $xspc2 - $xspc1($ln)}]
5980 return $x
5983 proc show_status {msg} {
5984 global canv fgcolor
5986 clear_display
5987 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
5988 -tags text -fill $fgcolor
5991 # Don't change the text pane cursor if it is currently the hand cursor,
5992 # showing that we are over a sha1 ID link.
5993 proc settextcursor {c} {
5994 global ctext curtextcursor
5996 if {[$ctext cget -cursor] == $curtextcursor} {
5997 $ctext config -cursor $c
5999 set curtextcursor $c
6002 proc nowbusy {what {name {}}} {
6003 global isbusy busyname statusw
6005 if {[array names isbusy] eq {}} {
6006 . config -cursor watch
6007 settextcursor watch
6009 set isbusy($what) 1
6010 set busyname($what) $name
6011 if {$name ne {}} {
6012 $statusw conf -text $name
6016 proc notbusy {what} {
6017 global isbusy maincursor textcursor busyname statusw
6019 catch {
6020 unset isbusy($what)
6021 if {$busyname($what) ne {} &&
6022 [$statusw cget -text] eq $busyname($what)} {
6023 $statusw conf -text {}
6026 if {[array names isbusy] eq {}} {
6027 . config -cursor $maincursor
6028 settextcursor $textcursor
6032 proc findmatches {f} {
6033 global findtype findstring
6034 if {$findtype == [mc "Regexp"]} {
6035 set matches [regexp -indices -all -inline $findstring $f]
6036 } else {
6037 set fs $findstring
6038 if {$findtype == [mc "IgnCase"]} {
6039 set f [string tolower $f]
6040 set fs [string tolower $fs]
6042 set matches {}
6043 set i 0
6044 set l [string length $fs]
6045 while {[set j [string first $fs $f $i]] >= 0} {
6046 lappend matches [list $j [expr {$j+$l-1}]]
6047 set i [expr {$j + $l}]
6050 return $matches
6053 proc dofind {{dirn 1} {wrap 1}} {
6054 global findstring findstartline findcurline selectedline numcommits
6055 global gdttype filehighlight fh_serial find_dirn findallowwrap
6057 if {[info exists find_dirn]} {
6058 if {$find_dirn == $dirn} return
6059 stopfinding
6061 focus .
6062 if {$findstring eq {} || $numcommits == 0} return
6063 if {$selectedline eq {}} {
6064 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
6065 } else {
6066 set findstartline $selectedline
6068 set findcurline $findstartline
6069 nowbusy finding [mc "Searching"]
6070 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
6071 after cancel do_file_hl $fh_serial
6072 do_file_hl $fh_serial
6074 set find_dirn $dirn
6075 set findallowwrap $wrap
6076 run findmore
6079 proc stopfinding {} {
6080 global find_dirn findcurline fprogcoord
6082 if {[info exists find_dirn]} {
6083 unset find_dirn
6084 unset findcurline
6085 notbusy finding
6086 set fprogcoord 0
6087 adjustprogress
6089 stopblaming
6092 proc findmore {} {
6093 global commitdata commitinfo numcommits findpattern findloc
6094 global findstartline findcurline findallowwrap
6095 global find_dirn gdttype fhighlights fprogcoord
6096 global curview varcorder vrownum varccommits vrowmod
6098 if {![info exists find_dirn]} {
6099 return 0
6101 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
6102 set l $findcurline
6103 set moretodo 0
6104 if {$find_dirn > 0} {
6105 incr l
6106 if {$l >= $numcommits} {
6107 set l 0
6109 if {$l <= $findstartline} {
6110 set lim [expr {$findstartline + 1}]
6111 } else {
6112 set lim $numcommits
6113 set moretodo $findallowwrap
6115 } else {
6116 if {$l == 0} {
6117 set l $numcommits
6119 incr l -1
6120 if {$l >= $findstartline} {
6121 set lim [expr {$findstartline - 1}]
6122 } else {
6123 set lim -1
6124 set moretodo $findallowwrap
6127 set n [expr {($lim - $l) * $find_dirn}]
6128 if {$n > 500} {
6129 set n 500
6130 set moretodo 1
6132 if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
6133 update_arcrows $curview
6135 set found 0
6136 set domore 1
6137 set ai [bsearch $vrownum($curview) $l]
6138 set a [lindex $varcorder($curview) $ai]
6139 set arow [lindex $vrownum($curview) $ai]
6140 set ids [lindex $varccommits($curview,$a)]
6141 set arowend [expr {$arow + [llength $ids]}]
6142 if {$gdttype eq [mc "containing:"]} {
6143 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6144 if {$l < $arow || $l >= $arowend} {
6145 incr ai $find_dirn
6146 set a [lindex $varcorder($curview) $ai]
6147 set arow [lindex $vrownum($curview) $ai]
6148 set ids [lindex $varccommits($curview,$a)]
6149 set arowend [expr {$arow + [llength $ids]}]
6151 set id [lindex $ids [expr {$l - $arow}]]
6152 # shouldn't happen unless git log doesn't give all the commits...
6153 if {![info exists commitdata($id)] ||
6154 ![doesmatch $commitdata($id)]} {
6155 continue
6157 if {![info exists commitinfo($id)]} {
6158 getcommit $id
6160 set info $commitinfo($id)
6161 foreach f $info ty $fldtypes {
6162 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
6163 [doesmatch $f]} {
6164 set found 1
6165 break
6168 if {$found} break
6170 } else {
6171 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
6172 if {$l < $arow || $l >= $arowend} {
6173 incr ai $find_dirn
6174 set a [lindex $varcorder($curview) $ai]
6175 set arow [lindex $vrownum($curview) $ai]
6176 set ids [lindex $varccommits($curview,$a)]
6177 set arowend [expr {$arow + [llength $ids]}]
6179 set id [lindex $ids [expr {$l - $arow}]]
6180 if {![info exists fhighlights($id)]} {
6181 # this sets fhighlights($id) to -1
6182 askfilehighlight $l $id
6184 if {$fhighlights($id) > 0} {
6185 set found $domore
6186 break
6188 if {$fhighlights($id) < 0} {
6189 if {$domore} {
6190 set domore 0
6191 set findcurline [expr {$l - $find_dirn}]
6196 if {$found || ($domore && !$moretodo)} {
6197 unset findcurline
6198 unset find_dirn
6199 notbusy finding
6200 set fprogcoord 0
6201 adjustprogress
6202 if {$found} {
6203 findselectline $l
6204 } else {
6205 bell
6207 return 0
6209 if {!$domore} {
6210 flushhighlights
6211 } else {
6212 set findcurline [expr {$l - $find_dirn}]
6214 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
6215 if {$n < 0} {
6216 incr n $numcommits
6218 set fprogcoord [expr {$n * 1.0 / $numcommits}]
6219 adjustprogress
6220 return $domore
6223 proc findselectline {l} {
6224 global findloc commentend ctext findcurline markingmatches gdttype
6226 set markingmatches 1
6227 set findcurline $l
6228 selectline $l 1
6229 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
6230 # highlight the matches in the comments
6231 set f [$ctext get 1.0 $commentend]
6232 set matches [findmatches $f]
6233 foreach match $matches {
6234 set start [lindex $match 0]
6235 set end [expr {[lindex $match 1] + 1}]
6236 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
6239 drawvisible
6242 # mark the bits of a headline or author that match a find string
6243 proc markmatches {canv l str tag matches font row} {
6244 global selectedline
6246 set bbox [$canv bbox $tag]
6247 set x0 [lindex $bbox 0]
6248 set y0 [lindex $bbox 1]
6249 set y1 [lindex $bbox 3]
6250 foreach match $matches {
6251 set start [lindex $match 0]
6252 set end [lindex $match 1]
6253 if {$start > $end} continue
6254 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
6255 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
6256 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
6257 [expr {$x0+$xlen+2}] $y1 \
6258 -outline {} -tags [list match$l matches] -fill yellow]
6259 $canv lower $t
6260 if {$row == $selectedline} {
6261 $canv raise $t secsel
6266 proc unmarkmatches {} {
6267 global markingmatches
6269 allcanvs delete matches
6270 set markingmatches 0
6271 stopfinding
6274 proc selcanvline {w x y} {
6275 global canv canvy0 ctext linespc
6276 global rowtextx
6277 set ymax [lindex [$canv cget -scrollregion] 3]
6278 if {$ymax == {}} return
6279 set yfrac [lindex [$canv yview] 0]
6280 set y [expr {$y + $yfrac * $ymax}]
6281 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
6282 if {$l < 0} {
6283 set l 0
6285 if {$w eq $canv} {
6286 set xmax [lindex [$canv cget -scrollregion] 2]
6287 set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
6288 if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
6290 unmarkmatches
6291 selectline $l 1
6294 proc commit_descriptor {p} {
6295 global commitinfo
6296 if {![info exists commitinfo($p)]} {
6297 getcommit $p
6299 set l "..."
6300 if {[llength $commitinfo($p)] > 1} {
6301 set l [lindex $commitinfo($p) 0]
6303 return "$p ($l)\n"
6306 # append some text to the ctext widget, and make any SHA1 ID
6307 # that we know about be a clickable link.
6308 proc appendwithlinks {text tags} {
6309 global ctext linknum curview
6311 set start [$ctext index "end - 1c"]
6312 $ctext insert end $text $tags
6313 set links [regexp -indices -all -inline {\m[0-9a-f]{6,40}\M} $text]
6314 foreach l $links {
6315 set s [lindex $l 0]
6316 set e [lindex $l 1]
6317 set linkid [string range $text $s $e]
6318 incr e
6319 $ctext tag delete link$linknum
6320 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
6321 setlink $linkid link$linknum
6322 incr linknum
6326 proc setlink {id lk} {
6327 global curview ctext pendinglinks
6329 set known 0
6330 if {[string length $id] < 40} {
6331 set matches [longid $id]
6332 if {[llength $matches] > 0} {
6333 if {[llength $matches] > 1} return
6334 set known 1
6335 set id [lindex $matches 0]
6337 } else {
6338 set known [commitinview $id $curview]
6340 if {$known} {
6341 $ctext tag conf $lk -foreground blue -underline 1
6342 $ctext tag bind $lk <1> [list selbyid $id]
6343 $ctext tag bind $lk <Enter> {linkcursor %W 1}
6344 $ctext tag bind $lk <Leave> {linkcursor %W -1}
6345 } else {
6346 lappend pendinglinks($id) $lk
6347 interestedin $id {makelink %P}
6351 proc makelink {id} {
6352 global pendinglinks
6354 if {![info exists pendinglinks($id)]} return
6355 foreach lk $pendinglinks($id) {
6356 setlink $id $lk
6358 unset pendinglinks($id)
6361 proc linkcursor {w inc} {
6362 global linkentercount curtextcursor
6364 if {[incr linkentercount $inc] > 0} {
6365 $w configure -cursor hand2
6366 } else {
6367 $w configure -cursor $curtextcursor
6368 if {$linkentercount < 0} {
6369 set linkentercount 0
6374 proc viewnextline {dir} {
6375 global canv linespc
6377 $canv delete hover
6378 set ymax [lindex [$canv cget -scrollregion] 3]
6379 set wnow [$canv yview]
6380 set wtop [expr {[lindex $wnow 0] * $ymax}]
6381 set newtop [expr {$wtop + $dir * $linespc}]
6382 if {$newtop < 0} {
6383 set newtop 0
6384 } elseif {$newtop > $ymax} {
6385 set newtop $ymax
6387 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6390 # add a list of tag or branch names at position pos
6391 # returns the number of names inserted
6392 proc appendrefs {pos ids var} {
6393 global ctext linknum curview $var maxrefs
6395 if {[catch {$ctext index $pos}]} {
6396 return 0
6398 $ctext conf -state normal
6399 $ctext delete $pos "$pos lineend"
6400 set tags {}
6401 foreach id $ids {
6402 foreach tag [set $var\($id\)] {
6403 lappend tags [list $tag $id]
6406 if {[llength $tags] > $maxrefs} {
6407 $ctext insert $pos "many ([llength $tags])"
6408 } else {
6409 set tags [lsort -index 0 -decreasing $tags]
6410 set sep {}
6411 foreach ti $tags {
6412 set id [lindex $ti 1]
6413 set lk link$linknum
6414 incr linknum
6415 $ctext tag delete $lk
6416 $ctext insert $pos $sep
6417 $ctext insert $pos [lindex $ti 0] $lk
6418 setlink $id $lk
6419 set sep ", "
6422 $ctext conf -state disabled
6423 return [llength $tags]
6426 # called when we have finished computing the nearby tags
6427 proc dispneartags {delay} {
6428 global selectedline currentid showneartags tagphase
6430 if {$selectedline eq {} || !$showneartags} return
6431 after cancel dispnexttag
6432 if {$delay} {
6433 after 200 dispnexttag
6434 set tagphase -1
6435 } else {
6436 after idle dispnexttag
6437 set tagphase 0
6441 proc dispnexttag {} {
6442 global selectedline currentid showneartags tagphase ctext
6444 if {$selectedline eq {} || !$showneartags} return
6445 switch -- $tagphase {
6447 set dtags [desctags $currentid]
6448 if {$dtags ne {}} {
6449 appendrefs precedes $dtags idtags
6453 set atags [anctags $currentid]
6454 if {$atags ne {}} {
6455 appendrefs follows $atags idtags
6459 set dheads [descheads $currentid]
6460 if {$dheads ne {}} {
6461 if {[appendrefs branch $dheads idheads] > 1
6462 && [$ctext get "branch -3c"] eq "h"} {
6463 # turn "Branch" into "Branches"
6464 $ctext conf -state normal
6465 $ctext insert "branch -2c" "es"
6466 $ctext conf -state disabled
6471 if {[incr tagphase] <= 2} {
6472 after idle dispnexttag
6476 proc make_secsel {id} {
6477 global linehtag linentag linedtag canv canv2 canv3
6479 if {![info exists linehtag($id)]} return
6480 $canv delete secsel
6481 set t [eval $canv create rect [$canv bbox $linehtag($id)] -outline {{}} \
6482 -tags secsel -fill [$canv cget -selectbackground]]
6483 $canv lower $t
6484 $canv2 delete secsel
6485 set t [eval $canv2 create rect [$canv2 bbox $linentag($id)] -outline {{}} \
6486 -tags secsel -fill [$canv2 cget -selectbackground]]
6487 $canv2 lower $t
6488 $canv3 delete secsel
6489 set t [eval $canv3 create rect [$canv3 bbox $linedtag($id)] -outline {{}} \
6490 -tags secsel -fill [$canv3 cget -selectbackground]]
6491 $canv3 lower $t
6494 proc selectline {l isnew {desired_loc {}}} {
6495 global canv ctext commitinfo selectedline
6496 global canvy0 linespc parents children curview
6497 global currentid sha1entry
6498 global commentend idtags linknum
6499 global mergemax numcommits pending_select
6500 global cmitmode showneartags allcommits
6501 global targetrow targetid lastscrollrows
6502 global autoselect jump_to_here
6504 catch {unset pending_select}
6505 $canv delete hover
6506 normalline
6507 unsel_reflist
6508 stopfinding
6509 if {$l < 0 || $l >= $numcommits} return
6510 set id [commitonrow $l]
6511 set targetid $id
6512 set targetrow $l
6513 set selectedline $l
6514 set currentid $id
6515 if {$lastscrollrows < $numcommits} {
6516 setcanvscroll
6519 set y [expr {$canvy0 + $l * $linespc}]
6520 set ymax [lindex [$canv cget -scrollregion] 3]
6521 set ytop [expr {$y - $linespc - 1}]
6522 set ybot [expr {$y + $linespc + 1}]
6523 set wnow [$canv yview]
6524 set wtop [expr {[lindex $wnow 0] * $ymax}]
6525 set wbot [expr {[lindex $wnow 1] * $ymax}]
6526 set wh [expr {$wbot - $wtop}]
6527 set newtop $wtop
6528 if {$ytop < $wtop} {
6529 if {$ybot < $wtop} {
6530 set newtop [expr {$y - $wh / 2.0}]
6531 } else {
6532 set newtop $ytop
6533 if {$newtop > $wtop - $linespc} {
6534 set newtop [expr {$wtop - $linespc}]
6537 } elseif {$ybot > $wbot} {
6538 if {$ytop > $wbot} {
6539 set newtop [expr {$y - $wh / 2.0}]
6540 } else {
6541 set newtop [expr {$ybot - $wh}]
6542 if {$newtop < $wtop + $linespc} {
6543 set newtop [expr {$wtop + $linespc}]
6547 if {$newtop != $wtop} {
6548 if {$newtop < 0} {
6549 set newtop 0
6551 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
6552 drawvisible
6555 make_secsel $id
6557 if {$isnew} {
6558 addtohistory [list selbyid $id]
6561 $sha1entry delete 0 end
6562 $sha1entry insert 0 $id
6563 if {$autoselect} {
6564 $sha1entry selection from 0
6565 $sha1entry selection to end
6567 rhighlight_sel $id
6569 $ctext conf -state normal
6570 clear_ctext
6571 set linknum 0
6572 if {![info exists commitinfo($id)]} {
6573 getcommit $id
6575 set info $commitinfo($id)
6576 set date [formatdate [lindex $info 2]]
6577 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
6578 set date [formatdate [lindex $info 4]]
6579 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
6580 if {[info exists idtags($id)]} {
6581 $ctext insert end [mc "Tags:"]
6582 foreach tag $idtags($id) {
6583 $ctext insert end " $tag"
6585 $ctext insert end "\n"
6588 set headers {}
6589 set olds $parents($curview,$id)
6590 if {[llength $olds] > 1} {
6591 set np 0
6592 foreach p $olds {
6593 if {$np >= $mergemax} {
6594 set tag mmax
6595 } else {
6596 set tag m$np
6598 $ctext insert end "[mc "Parent"]: " $tag
6599 appendwithlinks [commit_descriptor $p] {}
6600 incr np
6602 } else {
6603 foreach p $olds {
6604 append headers "[mc "Parent"]: [commit_descriptor $p]"
6608 foreach c $children($curview,$id) {
6609 append headers "[mc "Child"]: [commit_descriptor $c]"
6612 # make anything that looks like a SHA1 ID be a clickable link
6613 appendwithlinks $headers {}
6614 if {$showneartags} {
6615 if {![info exists allcommits]} {
6616 getallcommits
6618 $ctext insert end "[mc "Branch"]: "
6619 $ctext mark set branch "end -1c"
6620 $ctext mark gravity branch left
6621 $ctext insert end "\n[mc "Follows"]: "
6622 $ctext mark set follows "end -1c"
6623 $ctext mark gravity follows left
6624 $ctext insert end "\n[mc "Precedes"]: "
6625 $ctext mark set precedes "end -1c"
6626 $ctext mark gravity precedes left
6627 $ctext insert end "\n"
6628 dispneartags 1
6630 $ctext insert end "\n"
6631 set comment [lindex $info 5]
6632 if {[string first "\r" $comment] >= 0} {
6633 set comment [string map {"\r" "\n "} $comment]
6635 appendwithlinks $comment {comment}
6637 $ctext tag remove found 1.0 end
6638 $ctext conf -state disabled
6639 set commentend [$ctext index "end - 1c"]
6641 set jump_to_here $desired_loc
6642 init_flist [mc "Comments"]
6643 if {$cmitmode eq "tree"} {
6644 gettree $id
6645 } elseif {[llength $olds] <= 1} {
6646 startdiff $id
6647 } else {
6648 mergediff $id
6652 proc selfirstline {} {
6653 unmarkmatches
6654 selectline 0 1
6657 proc sellastline {} {
6658 global numcommits
6659 unmarkmatches
6660 set l [expr {$numcommits - 1}]
6661 selectline $l 1
6664 proc selnextline {dir} {
6665 global selectedline
6666 focus .
6667 if {$selectedline eq {}} return
6668 set l [expr {$selectedline + $dir}]
6669 unmarkmatches
6670 selectline $l 1
6673 proc selnextpage {dir} {
6674 global canv linespc selectedline numcommits
6676 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
6677 if {$lpp < 1} {
6678 set lpp 1
6680 allcanvs yview scroll [expr {$dir * $lpp}] units
6681 drawvisible
6682 if {$selectedline eq {}} return
6683 set l [expr {$selectedline + $dir * $lpp}]
6684 if {$l < 0} {
6685 set l 0
6686 } elseif {$l >= $numcommits} {
6687 set l [expr $numcommits - 1]
6689 unmarkmatches
6690 selectline $l 1
6693 proc unselectline {} {
6694 global selectedline currentid
6696 set selectedline {}
6697 catch {unset currentid}
6698 allcanvs delete secsel
6699 rhighlight_none
6702 proc reselectline {} {
6703 global selectedline
6705 if {$selectedline ne {}} {
6706 selectline $selectedline 0
6710 proc addtohistory {cmd} {
6711 global history historyindex curview
6713 set elt [list $curview $cmd]
6714 if {$historyindex > 0
6715 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
6716 return
6719 if {$historyindex < [llength $history]} {
6720 set history [lreplace $history $historyindex end $elt]
6721 } else {
6722 lappend history $elt
6724 incr historyindex
6725 if {$historyindex > 1} {
6726 .tf.bar.leftbut conf -state normal
6727 } else {
6728 .tf.bar.leftbut conf -state disabled
6730 .tf.bar.rightbut conf -state disabled
6733 proc godo {elt} {
6734 global curview
6736 set view [lindex $elt 0]
6737 set cmd [lindex $elt 1]
6738 if {$curview != $view} {
6739 showview $view
6741 eval $cmd
6744 proc goback {} {
6745 global history historyindex
6746 focus .
6748 if {$historyindex > 1} {
6749 incr historyindex -1
6750 godo [lindex $history [expr {$historyindex - 1}]]
6751 .tf.bar.rightbut conf -state normal
6753 if {$historyindex <= 1} {
6754 .tf.bar.leftbut conf -state disabled
6758 proc goforw {} {
6759 global history historyindex
6760 focus .
6762 if {$historyindex < [llength $history]} {
6763 set cmd [lindex $history $historyindex]
6764 incr historyindex
6765 godo $cmd
6766 .tf.bar.leftbut conf -state normal
6768 if {$historyindex >= [llength $history]} {
6769 .tf.bar.rightbut conf -state disabled
6773 proc gettree {id} {
6774 global treefilelist treeidlist diffids diffmergeid treepending
6775 global nullid nullid2
6777 set diffids $id
6778 catch {unset diffmergeid}
6779 if {![info exists treefilelist($id)]} {
6780 if {![info exists treepending]} {
6781 if {$id eq $nullid} {
6782 set cmd [list | git ls-files]
6783 } elseif {$id eq $nullid2} {
6784 set cmd [list | git ls-files --stage -t]
6785 } else {
6786 set cmd [list | git ls-tree -r $id]
6788 if {[catch {set gtf [open $cmd r]}]} {
6789 return
6791 set treepending $id
6792 set treefilelist($id) {}
6793 set treeidlist($id) {}
6794 fconfigure $gtf -blocking 0 -encoding binary
6795 filerun $gtf [list gettreeline $gtf $id]
6797 } else {
6798 setfilelist $id
6802 proc gettreeline {gtf id} {
6803 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
6805 set nl 0
6806 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
6807 if {$diffids eq $nullid} {
6808 set fname $line
6809 } else {
6810 set i [string first "\t" $line]
6811 if {$i < 0} continue
6812 set fname [string range $line [expr {$i+1}] end]
6813 set line [string range $line 0 [expr {$i-1}]]
6814 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
6815 set sha1 [lindex $line 2]
6816 lappend treeidlist($id) $sha1
6818 if {[string index $fname 0] eq "\""} {
6819 set fname [lindex $fname 0]
6821 set fname [encoding convertfrom $fname]
6822 lappend treefilelist($id) $fname
6824 if {![eof $gtf]} {
6825 return [expr {$nl >= 1000? 2: 1}]
6827 close $gtf
6828 unset treepending
6829 if {$cmitmode ne "tree"} {
6830 if {![info exists diffmergeid]} {
6831 gettreediffs $diffids
6833 } elseif {$id ne $diffids} {
6834 gettree $diffids
6835 } else {
6836 setfilelist $id
6838 return 0
6841 proc showfile {f} {
6842 global treefilelist treeidlist diffids nullid nullid2
6843 global ctext_file_names ctext_file_lines
6844 global ctext commentend
6846 set i [lsearch -exact $treefilelist($diffids) $f]
6847 if {$i < 0} {
6848 puts "oops, $f not in list for id $diffids"
6849 return
6851 if {$diffids eq $nullid} {
6852 if {[catch {set bf [open $f r]} err]} {
6853 puts "oops, can't read $f: $err"
6854 return
6856 } else {
6857 set blob [lindex $treeidlist($diffids) $i]
6858 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
6859 puts "oops, error reading blob $blob: $err"
6860 return
6863 fconfigure $bf -blocking 0 -encoding [get_path_encoding $f]
6864 filerun $bf [list getblobline $bf $diffids]
6865 $ctext config -state normal
6866 clear_ctext $commentend
6867 lappend ctext_file_names $f
6868 lappend ctext_file_lines [lindex [split $commentend "."] 0]
6869 $ctext insert end "\n"
6870 $ctext insert end "$f\n" filesep
6871 $ctext config -state disabled
6872 $ctext yview $commentend
6873 settabs 0
6876 proc getblobline {bf id} {
6877 global diffids cmitmode ctext
6879 if {$id ne $diffids || $cmitmode ne "tree"} {
6880 catch {close $bf}
6881 return 0
6883 $ctext config -state normal
6884 set nl 0
6885 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
6886 $ctext insert end "$line\n"
6888 if {[eof $bf]} {
6889 global jump_to_here ctext_file_names commentend
6891 # delete last newline
6892 $ctext delete "end - 2c" "end - 1c"
6893 close $bf
6894 if {$jump_to_here ne {} &&
6895 [lindex $jump_to_here 0] eq [lindex $ctext_file_names 0]} {
6896 set lnum [expr {[lindex $jump_to_here 1] +
6897 [lindex [split $commentend .] 0]}]
6898 mark_ctext_line $lnum
6900 return 0
6902 $ctext config -state disabled
6903 return [expr {$nl >= 1000? 2: 1}]
6906 proc mark_ctext_line {lnum} {
6907 global ctext markbgcolor
6909 $ctext tag delete omark
6910 $ctext tag add omark $lnum.0 "$lnum.0 + 1 line"
6911 $ctext tag conf omark -background $markbgcolor
6912 $ctext see $lnum.0
6915 proc mergediff {id} {
6916 global diffmergeid
6917 global diffids treediffs
6918 global parents curview
6920 set diffmergeid $id
6921 set diffids $id
6922 set treediffs($id) {}
6923 set np [llength $parents($curview,$id)]
6924 settabs $np
6925 getblobdiffs $id
6928 proc startdiff {ids} {
6929 global treediffs diffids treepending diffmergeid nullid nullid2
6931 settabs 1
6932 set diffids $ids
6933 catch {unset diffmergeid}
6934 if {![info exists treediffs($ids)] ||
6935 [lsearch -exact $ids $nullid] >= 0 ||
6936 [lsearch -exact $ids $nullid2] >= 0} {
6937 if {![info exists treepending]} {
6938 gettreediffs $ids
6940 } else {
6941 addtocflist $ids
6945 proc path_filter {filter name} {
6946 foreach p $filter {
6947 set l [string length $p]
6948 if {[string index $p end] eq "/"} {
6949 if {[string compare -length $l $p $name] == 0} {
6950 return 1
6952 } else {
6953 if {[string compare -length $l $p $name] == 0 &&
6954 ([string length $name] == $l ||
6955 [string index $name $l] eq "/")} {
6956 return 1
6960 return 0
6963 proc addtocflist {ids} {
6964 global treediffs
6966 add_flist $treediffs($ids)
6967 getblobdiffs $ids
6970 proc diffcmd {ids flags} {
6971 global nullid nullid2
6973 set i [lsearch -exact $ids $nullid]
6974 set j [lsearch -exact $ids $nullid2]
6975 if {$i >= 0} {
6976 if {[llength $ids] > 1 && $j < 0} {
6977 # comparing working directory with some specific revision
6978 set cmd [concat | git diff-index $flags]
6979 if {$i == 0} {
6980 lappend cmd -R [lindex $ids 1]
6981 } else {
6982 lappend cmd [lindex $ids 0]
6984 } else {
6985 # comparing working directory with index
6986 set cmd [concat | git diff-files $flags]
6987 if {$j == 1} {
6988 lappend cmd -R
6991 } elseif {$j >= 0} {
6992 set cmd [concat | git diff-index --cached $flags]
6993 if {[llength $ids] > 1} {
6994 # comparing index with specific revision
6995 if {$i == 0} {
6996 lappend cmd -R [lindex $ids 1]
6997 } else {
6998 lappend cmd [lindex $ids 0]
7000 } else {
7001 # comparing index with HEAD
7002 lappend cmd HEAD
7004 } else {
7005 set cmd [concat | git diff-tree -r $flags $ids]
7007 return $cmd
7010 proc gettreediffs {ids} {
7011 global treediff treepending
7013 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
7015 set treepending $ids
7016 set treediff {}
7017 fconfigure $gdtf -blocking 0 -encoding binary
7018 filerun $gdtf [list gettreediffline $gdtf $ids]
7021 proc gettreediffline {gdtf ids} {
7022 global treediff treediffs treepending diffids diffmergeid
7023 global cmitmode vfilelimit curview limitdiffs perfile_attrs
7025 set nr 0
7026 set sublist {}
7027 set max 1000
7028 if {$perfile_attrs} {
7029 # cache_gitattr is slow, and even slower on win32 where we
7030 # have to invoke it for only about 30 paths at a time
7031 set max 500
7032 if {[tk windowingsystem] == "win32"} {
7033 set max 120
7036 while {[incr nr] <= $max && [gets $gdtf line] >= 0} {
7037 set i [string first "\t" $line]
7038 if {$i >= 0} {
7039 set file [string range $line [expr {$i+1}] end]
7040 if {[string index $file 0] eq "\""} {
7041 set file [lindex $file 0]
7043 set file [encoding convertfrom $file]
7044 if {$file ne [lindex $treediff end]} {
7045 lappend treediff $file
7046 lappend sublist $file
7050 if {$perfile_attrs} {
7051 cache_gitattr encoding $sublist
7053 if {![eof $gdtf]} {
7054 return [expr {$nr >= $max? 2: 1}]
7056 close $gdtf
7057 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7058 set flist {}
7059 foreach f $treediff {
7060 if {[path_filter $vfilelimit($curview) $f]} {
7061 lappend flist $f
7064 set treediffs($ids) $flist
7065 } else {
7066 set treediffs($ids) $treediff
7068 unset treepending
7069 if {$cmitmode eq "tree"} {
7070 gettree $diffids
7071 } elseif {$ids != $diffids} {
7072 if {![info exists diffmergeid]} {
7073 gettreediffs $diffids
7075 } else {
7076 addtocflist $ids
7078 return 0
7081 # empty string or positive integer
7082 proc diffcontextvalidate {v} {
7083 return [regexp {^(|[1-9][0-9]*)$} $v]
7086 proc diffcontextchange {n1 n2 op} {
7087 global diffcontextstring diffcontext
7089 if {[string is integer -strict $diffcontextstring]} {
7090 if {$diffcontextstring > 0} {
7091 set diffcontext $diffcontextstring
7092 reselectline
7097 proc changeignorespace {} {
7098 reselectline
7101 proc getblobdiffs {ids} {
7102 global blobdifffd diffids env
7103 global diffinhdr treediffs
7104 global diffcontext
7105 global ignorespace
7106 global limitdiffs vfilelimit curview
7107 global diffencoding targetline diffnparents
7109 set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"]
7110 if {$ignorespace} {
7111 append cmd " -w"
7113 if {$limitdiffs && $vfilelimit($curview) ne {}} {
7114 set cmd [concat $cmd -- $vfilelimit($curview)]
7116 if {[catch {set bdf [open $cmd r]} err]} {
7117 error_popup [mc "Error getting diffs: %s" $err]
7118 return
7120 set targetline {}
7121 set diffnparents 0
7122 set diffinhdr 0
7123 set diffencoding [get_path_encoding {}]
7124 fconfigure $bdf -blocking 0 -encoding binary
7125 set blobdifffd($ids) $bdf
7126 filerun $bdf [list getblobdiffline $bdf $diffids]
7129 proc setinlist {var i val} {
7130 global $var
7132 while {[llength [set $var]] < $i} {
7133 lappend $var {}
7135 if {[llength [set $var]] == $i} {
7136 lappend $var $val
7137 } else {
7138 lset $var $i $val
7142 proc makediffhdr {fname ids} {
7143 global ctext curdiffstart treediffs diffencoding
7144 global ctext_file_names jump_to_here targetline diffline
7146 set fname [encoding convertfrom $fname]
7147 set diffencoding [get_path_encoding $fname]
7148 set i [lsearch -exact $treediffs($ids) $fname]
7149 if {$i >= 0} {
7150 setinlist difffilestart $i $curdiffstart
7152 lset ctext_file_names end $fname
7153 set l [expr {(78 - [string length $fname]) / 2}]
7154 set pad [string range "----------------------------------------" 1 $l]
7155 $ctext insert $curdiffstart "$pad $fname $pad" filesep
7156 set targetline {}
7157 if {$jump_to_here ne {} && [lindex $jump_to_here 0] eq $fname} {
7158 set targetline [lindex $jump_to_here 1]
7160 set diffline 0
7163 proc getblobdiffline {bdf ids} {
7164 global diffids blobdifffd ctext curdiffstart
7165 global diffnexthead diffnextnote difffilestart
7166 global ctext_file_names ctext_file_lines
7167 global diffinhdr treediffs mergemax diffnparents
7168 global diffencoding jump_to_here targetline diffline
7170 set nr 0
7171 $ctext conf -state normal
7172 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
7173 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
7174 close $bdf
7175 return 0
7177 if {![string compare -length 5 "diff " $line]} {
7178 if {![regexp {^diff (--cc|--git) } $line m type]} {
7179 set line [encoding convertfrom $line]
7180 $ctext insert end "$line\n" hunksep
7181 continue
7183 # start of a new file
7184 set diffinhdr 1
7185 $ctext insert end "\n"
7186 set curdiffstart [$ctext index "end - 1c"]
7187 lappend ctext_file_names ""
7188 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7189 $ctext insert end "\n" filesep
7191 if {$type eq "--cc"} {
7192 # start of a new file in a merge diff
7193 set fname [string range $line 10 end]
7194 if {[lsearch -exact $treediffs($ids) $fname] < 0} {
7195 lappend treediffs($ids) $fname
7196 add_flist [list $fname]
7199 } else {
7200 set line [string range $line 11 end]
7201 # If the name hasn't changed the length will be odd,
7202 # the middle char will be a space, and the two bits either
7203 # side will be a/name and b/name, or "a/name" and "b/name".
7204 # If the name has changed we'll get "rename from" and
7205 # "rename to" or "copy from" and "copy to" lines following
7206 # this, and we'll use them to get the filenames.
7207 # This complexity is necessary because spaces in the
7208 # filename(s) don't get escaped.
7209 set l [string length $line]
7210 set i [expr {$l / 2}]
7211 if {!(($l & 1) && [string index $line $i] eq " " &&
7212 [string range $line 2 [expr {$i - 1}]] eq \
7213 [string range $line [expr {$i + 3}] end])} {
7214 continue
7216 # unescape if quoted and chop off the a/ from the front
7217 if {[string index $line 0] eq "\""} {
7218 set fname [string range [lindex $line 0] 2 end]
7219 } else {
7220 set fname [string range $line 2 [expr {$i - 1}]]
7223 makediffhdr $fname $ids
7225 } elseif {![string compare -length 16 "* Unmerged path " $line]} {
7226 set fname [encoding convertfrom [string range $line 16 end]]
7227 $ctext insert end "\n"
7228 set curdiffstart [$ctext index "end - 1c"]
7229 lappend ctext_file_names $fname
7230 lappend ctext_file_lines [lindex [split $curdiffstart "."] 0]
7231 $ctext insert end "$line\n" filesep
7232 set i [lsearch -exact $treediffs($ids) $fname]
7233 if {$i >= 0} {
7234 setinlist difffilestart $i $curdiffstart
7237 } elseif {![string compare -length 2 "@@" $line]} {
7238 regexp {^@@+} $line ats
7239 set line [encoding convertfrom $diffencoding $line]
7240 $ctext insert end "$line\n" hunksep
7241 if {[regexp { \+(\d+),\d+ @@} $line m nl]} {
7242 set diffline $nl
7244 set diffnparents [expr {[string length $ats] - 1}]
7245 set diffinhdr 0
7247 } elseif {$diffinhdr} {
7248 if {![string compare -length 12 "rename from " $line]} {
7249 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
7250 if {[string index $fname 0] eq "\""} {
7251 set fname [lindex $fname 0]
7253 set fname [encoding convertfrom $fname]
7254 set i [lsearch -exact $treediffs($ids) $fname]
7255 if {$i >= 0} {
7256 setinlist difffilestart $i $curdiffstart
7258 } elseif {![string compare -length 10 $line "rename to "] ||
7259 ![string compare -length 8 $line "copy to "]} {
7260 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
7261 if {[string index $fname 0] eq "\""} {
7262 set fname [lindex $fname 0]
7264 makediffhdr $fname $ids
7265 } elseif {[string compare -length 3 $line "---"] == 0} {
7266 # do nothing
7267 continue
7268 } elseif {[string compare -length 3 $line "+++"] == 0} {
7269 set diffinhdr 0
7270 continue
7272 $ctext insert end "$line\n" filesep
7274 } else {
7275 set line [encoding convertfrom $diffencoding $line]
7276 # parse the prefix - one ' ', '-' or '+' for each parent
7277 set prefix [string range $line 0 [expr {$diffnparents - 1}]]
7278 set tag [expr {$diffnparents > 1? "m": "d"}]
7279 if {[string trim $prefix " -+"] eq {}} {
7280 # prefix only has " ", "-" and "+" in it: normal diff line
7281 set num [string first "-" $prefix]
7282 if {$num >= 0} {
7283 # removed line, first parent with line is $num
7284 if {$num >= $mergemax} {
7285 set num "max"
7287 $ctext insert end "$line\n" $tag$num
7288 } else {
7289 set tags {}
7290 if {[string first "+" $prefix] >= 0} {
7291 # added line
7292 lappend tags ${tag}result
7293 if {$diffnparents > 1} {
7294 set num [string first " " $prefix]
7295 if {$num >= 0} {
7296 if {$num >= $mergemax} {
7297 set num "max"
7299 lappend tags m$num
7303 if {$targetline ne {}} {
7304 if {$diffline == $targetline} {
7305 set seehere [$ctext index "end - 1 chars"]
7306 set targetline {}
7307 } else {
7308 incr diffline
7311 $ctext insert end "$line\n" $tags
7313 } else {
7314 # "\ No newline at end of file",
7315 # or something else we don't recognize
7316 $ctext insert end "$line\n" hunksep
7320 if {[info exists seehere]} {
7321 mark_ctext_line [lindex [split $seehere .] 0]
7323 $ctext conf -state disabled
7324 if {[eof $bdf]} {
7325 close $bdf
7326 return 0
7328 return [expr {$nr >= 1000? 2: 1}]
7331 proc changediffdisp {} {
7332 global ctext diffelide
7334 $ctext tag conf d0 -elide [lindex $diffelide 0]
7335 $ctext tag conf dresult -elide [lindex $diffelide 1]
7338 proc highlightfile {loc cline} {
7339 global ctext cflist cflist_top
7341 $ctext yview $loc
7342 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
7343 $cflist tag add highlight $cline.0 "$cline.0 lineend"
7344 $cflist see $cline.0
7345 set cflist_top $cline
7348 proc prevfile {} {
7349 global difffilestart ctext cmitmode
7351 if {$cmitmode eq "tree"} return
7352 set prev 0.0
7353 set prevline 1
7354 set here [$ctext index @0,0]
7355 foreach loc $difffilestart {
7356 if {[$ctext compare $loc >= $here]} {
7357 highlightfile $prev $prevline
7358 return
7360 set prev $loc
7361 incr prevline
7363 highlightfile $prev $prevline
7366 proc nextfile {} {
7367 global difffilestart ctext cmitmode
7369 if {$cmitmode eq "tree"} return
7370 set here [$ctext index @0,0]
7371 set line 1
7372 foreach loc $difffilestart {
7373 incr line
7374 if {[$ctext compare $loc > $here]} {
7375 highlightfile $loc $line
7376 return
7381 proc clear_ctext {{first 1.0}} {
7382 global ctext smarktop smarkbot
7383 global ctext_file_names ctext_file_lines
7384 global pendinglinks
7386 set l [lindex [split $first .] 0]
7387 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
7388 set smarktop $l
7390 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
7391 set smarkbot $l
7393 $ctext delete $first end
7394 if {$first eq "1.0"} {
7395 catch {unset pendinglinks}
7397 set ctext_file_names {}
7398 set ctext_file_lines {}
7401 proc settabs {{firstab {}}} {
7402 global firsttabstop tabstop ctext have_tk85
7404 if {$firstab ne {} && $have_tk85} {
7405 set firsttabstop $firstab
7407 set w [font measure textfont "0"]
7408 if {$firsttabstop != 0} {
7409 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
7410 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
7411 } elseif {$have_tk85 || $tabstop != 8} {
7412 $ctext conf -tabs [expr {$tabstop * $w}]
7413 } else {
7414 $ctext conf -tabs {}
7418 proc incrsearch {name ix op} {
7419 global ctext searchstring searchdirn
7421 $ctext tag remove found 1.0 end
7422 if {[catch {$ctext index anchor}]} {
7423 # no anchor set, use start of selection, or of visible area
7424 set sel [$ctext tag ranges sel]
7425 if {$sel ne {}} {
7426 $ctext mark set anchor [lindex $sel 0]
7427 } elseif {$searchdirn eq "-forwards"} {
7428 $ctext mark set anchor @0,0
7429 } else {
7430 $ctext mark set anchor @0,[winfo height $ctext]
7433 if {$searchstring ne {}} {
7434 set here [$ctext search $searchdirn -- $searchstring anchor]
7435 if {$here ne {}} {
7436 $ctext see $here
7438 searchmarkvisible 1
7442 proc dosearch {} {
7443 global sstring ctext searchstring searchdirn
7445 focus $sstring
7446 $sstring icursor end
7447 set searchdirn -forwards
7448 if {$searchstring ne {}} {
7449 set sel [$ctext tag ranges sel]
7450 if {$sel ne {}} {
7451 set start "[lindex $sel 0] + 1c"
7452 } elseif {[catch {set start [$ctext index anchor]}]} {
7453 set start "@0,0"
7455 set match [$ctext search -count mlen -- $searchstring $start]
7456 $ctext tag remove sel 1.0 end
7457 if {$match eq {}} {
7458 bell
7459 return
7461 $ctext see $match
7462 set mend "$match + $mlen c"
7463 $ctext tag add sel $match $mend
7464 $ctext mark unset anchor
7468 proc dosearchback {} {
7469 global sstring ctext searchstring searchdirn
7471 focus $sstring
7472 $sstring icursor end
7473 set searchdirn -backwards
7474 if {$searchstring ne {}} {
7475 set sel [$ctext tag ranges sel]
7476 if {$sel ne {}} {
7477 set start [lindex $sel 0]
7478 } elseif {[catch {set start [$ctext index anchor]}]} {
7479 set start @0,[winfo height $ctext]
7481 set match [$ctext search -backwards -count ml -- $searchstring $start]
7482 $ctext tag remove sel 1.0 end
7483 if {$match eq {}} {
7484 bell
7485 return
7487 $ctext see $match
7488 set mend "$match + $ml c"
7489 $ctext tag add sel $match $mend
7490 $ctext mark unset anchor
7494 proc searchmark {first last} {
7495 global ctext searchstring
7497 set mend $first.0
7498 while {1} {
7499 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
7500 if {$match eq {}} break
7501 set mend "$match + $mlen c"
7502 $ctext tag add found $match $mend
7506 proc searchmarkvisible {doall} {
7507 global ctext smarktop smarkbot
7509 set topline [lindex [split [$ctext index @0,0] .] 0]
7510 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
7511 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
7512 # no overlap with previous
7513 searchmark $topline $botline
7514 set smarktop $topline
7515 set smarkbot $botline
7516 } else {
7517 if {$topline < $smarktop} {
7518 searchmark $topline [expr {$smarktop-1}]
7519 set smarktop $topline
7521 if {$botline > $smarkbot} {
7522 searchmark [expr {$smarkbot+1}] $botline
7523 set smarkbot $botline
7528 proc scrolltext {f0 f1} {
7529 global searchstring
7531 .bleft.bottom.sb set $f0 $f1
7532 if {$searchstring ne {}} {
7533 searchmarkvisible 0
7537 proc setcoords {} {
7538 global linespc charspc canvx0 canvy0
7539 global xspc1 xspc2 lthickness
7541 set linespc [font metrics mainfont -linespace]
7542 set charspc [font measure mainfont "m"]
7543 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
7544 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
7545 set lthickness [expr {int($linespc / 9) + 1}]
7546 set xspc1(0) $linespc
7547 set xspc2 $linespc
7550 proc redisplay {} {
7551 global canv
7552 global selectedline
7554 set ymax [lindex [$canv cget -scrollregion] 3]
7555 if {$ymax eq {} || $ymax == 0} return
7556 set span [$canv yview]
7557 clear_display
7558 setcanvscroll
7559 allcanvs yview moveto [lindex $span 0]
7560 drawvisible
7561 if {$selectedline ne {}} {
7562 selectline $selectedline 0
7563 allcanvs yview moveto [lindex $span 0]
7567 proc parsefont {f n} {
7568 global fontattr
7570 set fontattr($f,family) [lindex $n 0]
7571 set s [lindex $n 1]
7572 if {$s eq {} || $s == 0} {
7573 set s 10
7574 } elseif {$s < 0} {
7575 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
7577 set fontattr($f,size) $s
7578 set fontattr($f,weight) normal
7579 set fontattr($f,slant) roman
7580 foreach style [lrange $n 2 end] {
7581 switch -- $style {
7582 "normal" -
7583 "bold" {set fontattr($f,weight) $style}
7584 "roman" -
7585 "italic" {set fontattr($f,slant) $style}
7590 proc fontflags {f {isbold 0}} {
7591 global fontattr
7593 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
7594 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
7595 -slant $fontattr($f,slant)]
7598 proc fontname {f} {
7599 global fontattr
7601 set n [list $fontattr($f,family) $fontattr($f,size)]
7602 if {$fontattr($f,weight) eq "bold"} {
7603 lappend n "bold"
7605 if {$fontattr($f,slant) eq "italic"} {
7606 lappend n "italic"
7608 return $n
7611 proc incrfont {inc} {
7612 global mainfont textfont ctext canv cflist showrefstop
7613 global stopped entries fontattr
7615 unmarkmatches
7616 set s $fontattr(mainfont,size)
7617 incr s $inc
7618 if {$s < 1} {
7619 set s 1
7621 set fontattr(mainfont,size) $s
7622 font config mainfont -size $s
7623 font config mainfontbold -size $s
7624 set mainfont [fontname mainfont]
7625 set s $fontattr(textfont,size)
7626 incr s $inc
7627 if {$s < 1} {
7628 set s 1
7630 set fontattr(textfont,size) $s
7631 font config textfont -size $s
7632 font config textfontbold -size $s
7633 set textfont [fontname textfont]
7634 setcoords
7635 settabs
7636 redisplay
7639 proc clearsha1 {} {
7640 global sha1entry sha1string
7641 if {[string length $sha1string] == 40} {
7642 $sha1entry delete 0 end
7646 proc sha1change {n1 n2 op} {
7647 global sha1string currentid sha1but
7648 if {$sha1string == {}
7649 || ([info exists currentid] && $sha1string == $currentid)} {
7650 set state disabled
7651 } else {
7652 set state normal
7654 if {[$sha1but cget -state] == $state} return
7655 if {$state == "normal"} {
7656 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
7657 } else {
7658 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
7662 proc gotocommit {} {
7663 global sha1string tagids headids curview varcid
7665 if {$sha1string == {}
7666 || ([info exists currentid] && $sha1string == $currentid)} return
7667 if {[info exists tagids($sha1string)]} {
7668 set id $tagids($sha1string)
7669 } elseif {[info exists headids($sha1string)]} {
7670 set id $headids($sha1string)
7671 } else {
7672 set id [string tolower $sha1string]
7673 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
7674 set matches [longid $id]
7675 if {$matches ne {}} {
7676 if {[llength $matches] > 1} {
7677 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
7678 return
7680 set id [lindex $matches 0]
7684 if {[commitinview $id $curview]} {
7685 selectline [rowofcommit $id] 1
7686 return
7688 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
7689 set msg [mc "SHA1 id %s is not known" $sha1string]
7690 } else {
7691 set msg [mc "Tag/Head %s is not known" $sha1string]
7693 error_popup $msg
7696 proc lineenter {x y id} {
7697 global hoverx hovery hoverid hovertimer
7698 global commitinfo canv
7700 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7701 set hoverx $x
7702 set hovery $y
7703 set hoverid $id
7704 if {[info exists hovertimer]} {
7705 after cancel $hovertimer
7707 set hovertimer [after 500 linehover]
7708 $canv delete hover
7711 proc linemotion {x y id} {
7712 global hoverx hovery hoverid hovertimer
7714 if {[info exists hoverid] && $id == $hoverid} {
7715 set hoverx $x
7716 set hovery $y
7717 if {[info exists hovertimer]} {
7718 after cancel $hovertimer
7720 set hovertimer [after 500 linehover]
7724 proc lineleave {id} {
7725 global hoverid hovertimer canv
7727 if {[info exists hoverid] && $id == $hoverid} {
7728 $canv delete hover
7729 if {[info exists hovertimer]} {
7730 after cancel $hovertimer
7731 unset hovertimer
7733 unset hoverid
7737 proc linehover {} {
7738 global hoverx hovery hoverid hovertimer
7739 global canv linespc lthickness
7740 global commitinfo
7742 set text [lindex $commitinfo($hoverid) 0]
7743 set ymax [lindex [$canv cget -scrollregion] 3]
7744 if {$ymax == {}} return
7745 set yfrac [lindex [$canv yview] 0]
7746 set x [expr {$hoverx + 2 * $linespc}]
7747 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
7748 set x0 [expr {$x - 2 * $lthickness}]
7749 set y0 [expr {$y - 2 * $lthickness}]
7750 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
7751 set y1 [expr {$y + $linespc + 2 * $lthickness}]
7752 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
7753 -fill \#ffff80 -outline black -width 1 -tags hover]
7754 $canv raise $t
7755 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
7756 -font mainfont]
7757 $canv raise $t
7760 proc clickisonarrow {id y} {
7761 global lthickness
7763 set ranges [rowranges $id]
7764 set thresh [expr {2 * $lthickness + 6}]
7765 set n [expr {[llength $ranges] - 1}]
7766 for {set i 1} {$i < $n} {incr i} {
7767 set row [lindex $ranges $i]
7768 if {abs([yc $row] - $y) < $thresh} {
7769 return $i
7772 return {}
7775 proc arrowjump {id n y} {
7776 global canv
7778 # 1 <-> 2, 3 <-> 4, etc...
7779 set n [expr {(($n - 1) ^ 1) + 1}]
7780 set row [lindex [rowranges $id] $n]
7781 set yt [yc $row]
7782 set ymax [lindex [$canv cget -scrollregion] 3]
7783 if {$ymax eq {} || $ymax <= 0} return
7784 set view [$canv yview]
7785 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
7786 set yfrac [expr {$yt / $ymax - $yspan / 2}]
7787 if {$yfrac < 0} {
7788 set yfrac 0
7790 allcanvs yview moveto $yfrac
7793 proc lineclick {x y id isnew} {
7794 global ctext commitinfo children canv thickerline curview
7796 if {![info exists commitinfo($id)] && ![getcommit $id]} return
7797 unmarkmatches
7798 unselectline
7799 normalline
7800 $canv delete hover
7801 # draw this line thicker than normal
7802 set thickerline $id
7803 drawlines $id
7804 if {$isnew} {
7805 set ymax [lindex [$canv cget -scrollregion] 3]
7806 if {$ymax eq {}} return
7807 set yfrac [lindex [$canv yview] 0]
7808 set y [expr {$y + $yfrac * $ymax}]
7810 set dirn [clickisonarrow $id $y]
7811 if {$dirn ne {}} {
7812 arrowjump $id $dirn $y
7813 return
7816 if {$isnew} {
7817 addtohistory [list lineclick $x $y $id 0]
7819 # fill the details pane with info about this line
7820 $ctext conf -state normal
7821 clear_ctext
7822 settabs 0
7823 $ctext insert end "[mc "Parent"]:\t"
7824 $ctext insert end $id link0
7825 setlink $id link0
7826 set info $commitinfo($id)
7827 $ctext insert end "\n\t[lindex $info 0]\n"
7828 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
7829 set date [formatdate [lindex $info 2]]
7830 $ctext insert end "\t[mc "Date"]:\t$date\n"
7831 set kids $children($curview,$id)
7832 if {$kids ne {}} {
7833 $ctext insert end "\n[mc "Children"]:"
7834 set i 0
7835 foreach child $kids {
7836 incr i
7837 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
7838 set info $commitinfo($child)
7839 $ctext insert end "\n\t"
7840 $ctext insert end $child link$i
7841 setlink $child link$i
7842 $ctext insert end "\n\t[lindex $info 0]"
7843 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
7844 set date [formatdate [lindex $info 2]]
7845 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
7848 $ctext conf -state disabled
7849 init_flist {}
7852 proc normalline {} {
7853 global thickerline
7854 if {[info exists thickerline]} {
7855 set id $thickerline
7856 unset thickerline
7857 drawlines $id
7861 proc selbyid {id} {
7862 global curview
7863 if {[commitinview $id $curview]} {
7864 selectline [rowofcommit $id] 1
7868 proc mstime {} {
7869 global startmstime
7870 if {![info exists startmstime]} {
7871 set startmstime [clock clicks -milliseconds]
7873 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
7876 proc rowmenu {x y id} {
7877 global rowctxmenu selectedline rowmenuid curview
7878 global nullid nullid2 fakerowmenu mainhead
7880 stopfinding
7881 set rowmenuid $id
7882 if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
7883 set state disabled
7884 } else {
7885 set state normal
7887 if {$id ne $nullid && $id ne $nullid2} {
7888 set menu $rowctxmenu
7889 if {$mainhead ne {}} {
7890 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
7891 } else {
7892 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
7894 } else {
7895 set menu $fakerowmenu
7897 $menu entryconfigure [mca "Diff this -> selected"] -state $state
7898 $menu entryconfigure [mca "Diff selected -> this"] -state $state
7899 $menu entryconfigure [mca "Make patch"] -state $state
7900 tk_popup $menu $x $y
7903 proc diffvssel {dirn} {
7904 global rowmenuid selectedline
7906 if {$selectedline eq {}} return
7907 if {$dirn} {
7908 set oldid [commitonrow $selectedline]
7909 set newid $rowmenuid
7910 } else {
7911 set oldid $rowmenuid
7912 set newid [commitonrow $selectedline]
7914 addtohistory [list doseldiff $oldid $newid]
7915 doseldiff $oldid $newid
7918 proc doseldiff {oldid newid} {
7919 global ctext
7920 global commitinfo
7922 $ctext conf -state normal
7923 clear_ctext
7924 init_flist [mc "Top"]
7925 $ctext insert end "[mc "From"] "
7926 $ctext insert end $oldid link0
7927 setlink $oldid link0
7928 $ctext insert end "\n "
7929 $ctext insert end [lindex $commitinfo($oldid) 0]
7930 $ctext insert end "\n\n[mc "To"] "
7931 $ctext insert end $newid link1
7932 setlink $newid link1
7933 $ctext insert end "\n "
7934 $ctext insert end [lindex $commitinfo($newid) 0]
7935 $ctext insert end "\n"
7936 $ctext conf -state disabled
7937 $ctext tag remove found 1.0 end
7938 startdiff [list $oldid $newid]
7941 proc mkpatch {} {
7942 global rowmenuid currentid commitinfo patchtop patchnum
7944 if {![info exists currentid]} return
7945 set oldid $currentid
7946 set oldhead [lindex $commitinfo($oldid) 0]
7947 set newid $rowmenuid
7948 set newhead [lindex $commitinfo($newid) 0]
7949 set top .patch
7950 set patchtop $top
7951 catch {destroy $top}
7952 toplevel $top
7953 make_transient $top .
7954 label $top.title -text [mc "Generate patch"]
7955 grid $top.title - -pady 10
7956 label $top.from -text [mc "From:"]
7957 entry $top.fromsha1 -width 40 -relief flat
7958 $top.fromsha1 insert 0 $oldid
7959 $top.fromsha1 conf -state readonly
7960 grid $top.from $top.fromsha1 -sticky w
7961 entry $top.fromhead -width 60 -relief flat
7962 $top.fromhead insert 0 $oldhead
7963 $top.fromhead conf -state readonly
7964 grid x $top.fromhead -sticky w
7965 label $top.to -text [mc "To:"]
7966 entry $top.tosha1 -width 40 -relief flat
7967 $top.tosha1 insert 0 $newid
7968 $top.tosha1 conf -state readonly
7969 grid $top.to $top.tosha1 -sticky w
7970 entry $top.tohead -width 60 -relief flat
7971 $top.tohead insert 0 $newhead
7972 $top.tohead conf -state readonly
7973 grid x $top.tohead -sticky w
7974 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
7975 grid $top.rev x -pady 10
7976 label $top.flab -text [mc "Output file:"]
7977 entry $top.fname -width 60
7978 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
7979 incr patchnum
7980 grid $top.flab $top.fname -sticky w
7981 frame $top.buts
7982 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
7983 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
7984 bind $top <Key-Return> mkpatchgo
7985 bind $top <Key-Escape> mkpatchcan
7986 grid $top.buts.gen $top.buts.can
7987 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7988 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7989 grid $top.buts - -pady 10 -sticky ew
7990 focus $top.fname
7993 proc mkpatchrev {} {
7994 global patchtop
7996 set oldid [$patchtop.fromsha1 get]
7997 set oldhead [$patchtop.fromhead get]
7998 set newid [$patchtop.tosha1 get]
7999 set newhead [$patchtop.tohead get]
8000 foreach e [list fromsha1 fromhead tosha1 tohead] \
8001 v [list $newid $newhead $oldid $oldhead] {
8002 $patchtop.$e conf -state normal
8003 $patchtop.$e delete 0 end
8004 $patchtop.$e insert 0 $v
8005 $patchtop.$e conf -state readonly
8009 proc mkpatchgo {} {
8010 global patchtop nullid nullid2
8012 set oldid [$patchtop.fromsha1 get]
8013 set newid [$patchtop.tosha1 get]
8014 set fname [$patchtop.fname get]
8015 set cmd [diffcmd [list $oldid $newid] -p]
8016 # trim off the initial "|"
8017 set cmd [lrange $cmd 1 end]
8018 lappend cmd >$fname &
8019 if {[catch {eval exec $cmd} err]} {
8020 error_popup "[mc "Error creating patch:"] $err" $patchtop
8022 catch {destroy $patchtop}
8023 unset patchtop
8026 proc mkpatchcan {} {
8027 global patchtop
8029 catch {destroy $patchtop}
8030 unset patchtop
8033 proc mktag {} {
8034 global rowmenuid mktagtop commitinfo
8036 set top .maketag
8037 set mktagtop $top
8038 catch {destroy $top}
8039 toplevel $top
8040 make_transient $top .
8041 label $top.title -text [mc "Create tag"]
8042 grid $top.title - -pady 10
8043 label $top.id -text [mc "ID:"]
8044 entry $top.sha1 -width 40 -relief flat
8045 $top.sha1 insert 0 $rowmenuid
8046 $top.sha1 conf -state readonly
8047 grid $top.id $top.sha1 -sticky w
8048 entry $top.head -width 60 -relief flat
8049 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8050 $top.head conf -state readonly
8051 grid x $top.head -sticky w
8052 label $top.tlab -text [mc "Tag name:"]
8053 entry $top.tag -width 60
8054 grid $top.tlab $top.tag -sticky w
8055 frame $top.buts
8056 button $top.buts.gen -text [mc "Create"] -command mktaggo
8057 button $top.buts.can -text [mc "Cancel"] -command mktagcan
8058 bind $top <Key-Return> mktaggo
8059 bind $top <Key-Escape> mktagcan
8060 grid $top.buts.gen $top.buts.can
8061 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8062 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8063 grid $top.buts - -pady 10 -sticky ew
8064 focus $top.tag
8067 proc domktag {} {
8068 global mktagtop env tagids idtags
8070 set id [$mktagtop.sha1 get]
8071 set tag [$mktagtop.tag get]
8072 if {$tag == {}} {
8073 error_popup [mc "No tag name specified"] $mktagtop
8074 return 0
8076 if {[info exists tagids($tag)]} {
8077 error_popup [mc "Tag \"%s\" already exists" $tag] $mktagtop
8078 return 0
8080 if {[catch {
8081 exec git tag $tag $id
8082 } err]} {
8083 error_popup "[mc "Error creating tag:"] $err" $mktagtop
8084 return 0
8087 set tagids($tag) $id
8088 lappend idtags($id) $tag
8089 redrawtags $id
8090 addedtag $id
8091 dispneartags 0
8092 run refill_reflist
8093 return 1
8096 proc redrawtags {id} {
8097 global canv linehtag idpos currentid curview cmitlisted
8098 global canvxmax iddrawn circleitem mainheadid circlecolors
8100 if {![commitinview $id $curview]} return
8101 if {![info exists iddrawn($id)]} return
8102 set row [rowofcommit $id]
8103 if {$id eq $mainheadid} {
8104 set ofill yellow
8105 } else {
8106 set ofill [lindex $circlecolors $cmitlisted($curview,$id)]
8108 $canv itemconf $circleitem($row) -fill $ofill
8109 $canv delete tag.$id
8110 set xt [eval drawtags $id $idpos($id)]
8111 $canv coords $linehtag($id) $xt [lindex $idpos($id) 2]
8112 set text [$canv itemcget $linehtag($id) -text]
8113 set font [$canv itemcget $linehtag($id) -font]
8114 set xr [expr {$xt + [font measure $font $text]}]
8115 if {$xr > $canvxmax} {
8116 set canvxmax $xr
8117 setcanvscroll
8119 if {[info exists currentid] && $currentid == $id} {
8120 make_secsel $id
8124 proc mktagcan {} {
8125 global mktagtop
8127 catch {destroy $mktagtop}
8128 unset mktagtop
8131 proc mktaggo {} {
8132 if {![domktag]} return
8133 mktagcan
8136 proc writecommit {} {
8137 global rowmenuid wrcomtop commitinfo wrcomcmd
8139 set top .writecommit
8140 set wrcomtop $top
8141 catch {destroy $top}
8142 toplevel $top
8143 make_transient $top .
8144 label $top.title -text [mc "Write commit to file"]
8145 grid $top.title - -pady 10
8146 label $top.id -text [mc "ID:"]
8147 entry $top.sha1 -width 40 -relief flat
8148 $top.sha1 insert 0 $rowmenuid
8149 $top.sha1 conf -state readonly
8150 grid $top.id $top.sha1 -sticky w
8151 entry $top.head -width 60 -relief flat
8152 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
8153 $top.head conf -state readonly
8154 grid x $top.head -sticky w
8155 label $top.clab -text [mc "Command:"]
8156 entry $top.cmd -width 60 -textvariable wrcomcmd
8157 grid $top.clab $top.cmd -sticky w -pady 10
8158 label $top.flab -text [mc "Output file:"]
8159 entry $top.fname -width 60
8160 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
8161 grid $top.flab $top.fname -sticky w
8162 frame $top.buts
8163 button $top.buts.gen -text [mc "Write"] -command wrcomgo
8164 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
8165 bind $top <Key-Return> wrcomgo
8166 bind $top <Key-Escape> wrcomcan
8167 grid $top.buts.gen $top.buts.can
8168 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8169 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8170 grid $top.buts - -pady 10 -sticky ew
8171 focus $top.fname
8174 proc wrcomgo {} {
8175 global wrcomtop
8177 set id [$wrcomtop.sha1 get]
8178 set cmd "echo $id | [$wrcomtop.cmd get]"
8179 set fname [$wrcomtop.fname get]
8180 if {[catch {exec sh -c $cmd >$fname &} err]} {
8181 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
8183 catch {destroy $wrcomtop}
8184 unset wrcomtop
8187 proc wrcomcan {} {
8188 global wrcomtop
8190 catch {destroy $wrcomtop}
8191 unset wrcomtop
8194 proc mkbranch {} {
8195 global rowmenuid mkbrtop
8197 set top .makebranch
8198 catch {destroy $top}
8199 toplevel $top
8200 make_transient $top .
8201 label $top.title -text [mc "Create new branch"]
8202 grid $top.title - -pady 10
8203 label $top.id -text [mc "ID:"]
8204 entry $top.sha1 -width 40 -relief flat
8205 $top.sha1 insert 0 $rowmenuid
8206 $top.sha1 conf -state readonly
8207 grid $top.id $top.sha1 -sticky w
8208 label $top.nlab -text [mc "Name:"]
8209 entry $top.name -width 40
8210 bind $top.name <Key-Return> "[list mkbrgo $top]"
8211 grid $top.nlab $top.name -sticky w
8212 frame $top.buts
8213 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
8214 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
8215 bind $top <Key-Return> [list mkbrgo $top]
8216 bind $top <Key-Escape> "catch {destroy $top}"
8217 grid $top.buts.go $top.buts.can
8218 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8219 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8220 grid $top.buts - -pady 10 -sticky ew
8221 focus $top.name
8224 proc mkbrgo {top} {
8225 global headids idheads
8227 set name [$top.name get]
8228 set id [$top.sha1 get]
8229 set cmdargs {}
8230 set old_id {}
8231 if {$name eq {}} {
8232 error_popup [mc "Please specify a name for the new branch"] $top
8233 return
8235 if {[info exists headids($name)]} {
8236 if {![confirm_popup [mc \
8237 "Branch '%s' already exists. Overwrite?" $name] $top]} {
8238 return
8240 set old_id $headids($name)
8241 lappend cmdargs -f
8243 catch {destroy $top}
8244 lappend cmdargs $name $id
8245 nowbusy newbranch
8246 update
8247 if {[catch {
8248 eval exec git branch $cmdargs
8249 } err]} {
8250 notbusy newbranch
8251 error_popup $err
8252 } else {
8253 notbusy newbranch
8254 if {$old_id ne {}} {
8255 movehead $id $name
8256 movedhead $id $name
8257 redrawtags $old_id
8258 redrawtags $id
8259 } else {
8260 set headids($name) $id
8261 lappend idheads($id) $name
8262 addedhead $id $name
8263 redrawtags $id
8265 dispneartags 0
8266 run refill_reflist
8270 proc exec_citool {tool_args {baseid {}}} {
8271 global commitinfo env
8273 set save_env [array get env GIT_AUTHOR_*]
8275 if {$baseid ne {}} {
8276 if {![info exists commitinfo($baseid)]} {
8277 getcommit $baseid
8279 set author [lindex $commitinfo($baseid) 1]
8280 set date [lindex $commitinfo($baseid) 2]
8281 if {[regexp {^\s*(\S.*\S|\S)\s*<(.*)>\s*$} \
8282 $author author name email]
8283 && $date ne {}} {
8284 set env(GIT_AUTHOR_NAME) $name
8285 set env(GIT_AUTHOR_EMAIL) $email
8286 set env(GIT_AUTHOR_DATE) $date
8290 eval exec git citool $tool_args &
8292 array unset env GIT_AUTHOR_*
8293 array set env $save_env
8296 proc cherrypick {} {
8297 global rowmenuid curview
8298 global mainhead mainheadid
8300 set oldhead [exec git rev-parse HEAD]
8301 set dheads [descheads $rowmenuid]
8302 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
8303 set ok [confirm_popup [mc "Commit %s is already\
8304 included in branch %s -- really re-apply it?" \
8305 [string range $rowmenuid 0 7] $mainhead]]
8306 if {!$ok} return
8308 nowbusy cherrypick [mc "Cherry-picking"]
8309 update
8310 # Unfortunately git-cherry-pick writes stuff to stderr even when
8311 # no error occurs, and exec takes that as an indication of error...
8312 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
8313 notbusy cherrypick
8314 if {[regexp -line \
8315 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
8316 $err msg fname]} {
8317 error_popup [mc "Cherry-pick failed because of local changes\
8318 to file '%s'.\nPlease commit, reset or stash\
8319 your changes and try again." $fname]
8320 } elseif {[regexp -line \
8321 {^(CONFLICT \(.*\):|Automatic cherry-pick failed)} \
8322 $err]} {
8323 if {[confirm_popup [mc "Cherry-pick failed because of merge\
8324 conflict.\nDo you wish to run git citool to\
8325 resolve it?"]]} {
8326 # Force citool to read MERGE_MSG
8327 file delete [file join [gitdir] "GITGUI_MSG"]
8328 exec_citool {} $rowmenuid
8330 } else {
8331 error_popup $err
8333 run updatecommits
8334 return
8336 set newhead [exec git rev-parse HEAD]
8337 if {$newhead eq $oldhead} {
8338 notbusy cherrypick
8339 error_popup [mc "No changes committed"]
8340 return
8342 addnewchild $newhead $oldhead
8343 if {[commitinview $oldhead $curview]} {
8344 insertrow $newhead $oldhead $curview
8345 if {$mainhead ne {}} {
8346 movehead $newhead $mainhead
8347 movedhead $newhead $mainhead
8349 set mainheadid $newhead
8350 redrawtags $oldhead
8351 redrawtags $newhead
8352 selbyid $newhead
8354 notbusy cherrypick
8357 proc resethead {} {
8358 global mainhead rowmenuid confirm_ok resettype
8360 set confirm_ok 0
8361 set w ".confirmreset"
8362 toplevel $w
8363 make_transient $w .
8364 wm title $w [mc "Confirm reset"]
8365 message $w.m -text \
8366 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
8367 -justify center -aspect 1000
8368 pack $w.m -side top -fill x -padx 20 -pady 20
8369 frame $w.f -relief sunken -border 2
8370 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
8371 grid $w.f.rt -sticky w
8372 set resettype mixed
8373 radiobutton $w.f.soft -value soft -variable resettype -justify left \
8374 -text [mc "Soft: Leave working tree and index untouched"]
8375 grid $w.f.soft -sticky w
8376 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
8377 -text [mc "Mixed: Leave working tree untouched, reset index"]
8378 grid $w.f.mixed -sticky w
8379 radiobutton $w.f.hard -value hard -variable resettype -justify left \
8380 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
8381 grid $w.f.hard -sticky w
8382 pack $w.f -side top -fill x
8383 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
8384 pack $w.ok -side left -fill x -padx 20 -pady 20
8385 button $w.cancel -text [mc Cancel] -command "destroy $w"
8386 bind $w <Key-Escape> [list destroy $w]
8387 pack $w.cancel -side right -fill x -padx 20 -pady 20
8388 bind $w <Visibility> "grab $w; focus $w"
8389 tkwait window $w
8390 if {!$confirm_ok} return
8391 if {[catch {set fd [open \
8392 [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
8393 error_popup $err
8394 } else {
8395 dohidelocalchanges
8396 filerun $fd [list readresetstat $fd]
8397 nowbusy reset [mc "Resetting"]
8398 selbyid $rowmenuid
8402 proc readresetstat {fd} {
8403 global mainhead mainheadid showlocalchanges rprogcoord
8405 if {[gets $fd line] >= 0} {
8406 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8407 set rprogcoord [expr {1.0 * $m / $n}]
8408 adjustprogress
8410 return 1
8412 set rprogcoord 0
8413 adjustprogress
8414 notbusy reset
8415 if {[catch {close $fd} err]} {
8416 error_popup $err
8418 set oldhead $mainheadid
8419 set newhead [exec git rev-parse HEAD]
8420 if {$newhead ne $oldhead} {
8421 movehead $newhead $mainhead
8422 movedhead $newhead $mainhead
8423 set mainheadid $newhead
8424 redrawtags $oldhead
8425 redrawtags $newhead
8427 if {$showlocalchanges} {
8428 doshowlocalchanges
8430 return 0
8433 # context menu for a head
8434 proc headmenu {x y id head} {
8435 global headmenuid headmenuhead headctxmenu mainhead
8437 stopfinding
8438 set headmenuid $id
8439 set headmenuhead $head
8440 set state normal
8441 if {$head eq $mainhead} {
8442 set state disabled
8444 $headctxmenu entryconfigure 0 -state $state
8445 $headctxmenu entryconfigure 1 -state $state
8446 tk_popup $headctxmenu $x $y
8449 proc cobranch {} {
8450 global headmenuid headmenuhead headids
8451 global showlocalchanges mainheadid
8453 # check the tree is clean first??
8454 nowbusy checkout [mc "Checking out"]
8455 update
8456 dohidelocalchanges
8457 if {[catch {
8458 set fd [open [list | git checkout $headmenuhead 2>@1] r]
8459 } err]} {
8460 notbusy checkout
8461 error_popup $err
8462 if {$showlocalchanges} {
8463 dodiffindex
8465 } else {
8466 filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
8470 proc readcheckoutstat {fd newhead newheadid} {
8471 global mainhead mainheadid headids showlocalchanges progresscoords
8473 if {[gets $fd line] >= 0} {
8474 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
8475 set progresscoords [list 0 [expr {1.0 * $m / $n}]]
8476 adjustprogress
8478 return 1
8480 set progresscoords {0 0}
8481 adjustprogress
8482 notbusy checkout
8483 if {[catch {close $fd} err]} {
8484 error_popup $err
8486 set oldmainid $mainheadid
8487 set mainhead $newhead
8488 set mainheadid $newheadid
8489 redrawtags $oldmainid
8490 redrawtags $newheadid
8491 selbyid $newheadid
8492 if {$showlocalchanges} {
8493 dodiffindex
8497 proc rmbranch {} {
8498 global headmenuid headmenuhead mainhead
8499 global idheads
8501 set head $headmenuhead
8502 set id $headmenuid
8503 # this check shouldn't be needed any more...
8504 if {$head eq $mainhead} {
8505 error_popup [mc "Cannot delete the currently checked-out branch"]
8506 return
8508 set dheads [descheads $id]
8509 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
8510 # the stuff on this branch isn't on any other branch
8511 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
8512 branch.\nReally delete branch %s?" $head $head]]} return
8514 nowbusy rmbranch
8515 update
8516 if {[catch {exec git branch -D $head} err]} {
8517 notbusy rmbranch
8518 error_popup $err
8519 return
8521 removehead $id $head
8522 removedhead $id $head
8523 redrawtags $id
8524 notbusy rmbranch
8525 dispneartags 0
8526 run refill_reflist
8529 # Display a list of tags and heads
8530 proc showrefs {} {
8531 global showrefstop bgcolor fgcolor selectbgcolor
8532 global bglist fglist reflistfilter reflist maincursor
8534 set top .showrefs
8535 set showrefstop $top
8536 if {[winfo exists $top]} {
8537 raise $top
8538 refill_reflist
8539 return
8541 toplevel $top
8542 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
8543 make_transient $top .
8544 text $top.list -background $bgcolor -foreground $fgcolor \
8545 -selectbackground $selectbgcolor -font mainfont \
8546 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
8547 -width 30 -height 20 -cursor $maincursor \
8548 -spacing1 1 -spacing3 1 -state disabled
8549 $top.list tag configure highlight -background $selectbgcolor
8550 lappend bglist $top.list
8551 lappend fglist $top.list
8552 scrollbar $top.ysb -command "$top.list yview" -orient vertical
8553 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
8554 grid $top.list $top.ysb -sticky nsew
8555 grid $top.xsb x -sticky ew
8556 frame $top.f
8557 label $top.f.l -text "[mc "Filter"]: "
8558 entry $top.f.e -width 20 -textvariable reflistfilter
8559 set reflistfilter "*"
8560 trace add variable reflistfilter write reflistfilter_change
8561 pack $top.f.e -side right -fill x -expand 1
8562 pack $top.f.l -side left
8563 grid $top.f - -sticky ew -pady 2
8564 button $top.close -command [list destroy $top] -text [mc "Close"]
8565 bind $top <Key-Escape> [list destroy $top]
8566 grid $top.close -
8567 grid columnconfigure $top 0 -weight 1
8568 grid rowconfigure $top 0 -weight 1
8569 bind $top.list <1> {break}
8570 bind $top.list <B1-Motion> {break}
8571 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
8572 set reflist {}
8573 refill_reflist
8576 proc sel_reflist {w x y} {
8577 global showrefstop reflist headids tagids otherrefids
8579 if {![winfo exists $showrefstop]} return
8580 set l [lindex [split [$w index "@$x,$y"] "."] 0]
8581 set ref [lindex $reflist [expr {$l-1}]]
8582 set n [lindex $ref 0]
8583 switch -- [lindex $ref 1] {
8584 "H" {selbyid $headids($n)}
8585 "T" {selbyid $tagids($n)}
8586 "o" {selbyid $otherrefids($n)}
8588 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
8591 proc unsel_reflist {} {
8592 global showrefstop
8594 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8595 $showrefstop.list tag remove highlight 0.0 end
8598 proc reflistfilter_change {n1 n2 op} {
8599 global reflistfilter
8601 after cancel refill_reflist
8602 after 200 refill_reflist
8605 proc refill_reflist {} {
8606 global reflist reflistfilter showrefstop headids tagids otherrefids
8607 global curview
8609 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
8610 set refs {}
8611 foreach n [array names headids] {
8612 if {[string match $reflistfilter $n]} {
8613 if {[commitinview $headids($n) $curview]} {
8614 lappend refs [list $n H]
8615 } else {
8616 interestedin $headids($n) {run refill_reflist}
8620 foreach n [array names tagids] {
8621 if {[string match $reflistfilter $n]} {
8622 if {[commitinview $tagids($n) $curview]} {
8623 lappend refs [list $n T]
8624 } else {
8625 interestedin $tagids($n) {run refill_reflist}
8629 foreach n [array names otherrefids] {
8630 if {[string match $reflistfilter $n]} {
8631 if {[commitinview $otherrefids($n) $curview]} {
8632 lappend refs [list $n o]
8633 } else {
8634 interestedin $otherrefids($n) {run refill_reflist}
8638 set refs [lsort -index 0 $refs]
8639 if {$refs eq $reflist} return
8641 # Update the contents of $showrefstop.list according to the
8642 # differences between $reflist (old) and $refs (new)
8643 $showrefstop.list conf -state normal
8644 $showrefstop.list insert end "\n"
8645 set i 0
8646 set j 0
8647 while {$i < [llength $reflist] || $j < [llength $refs]} {
8648 if {$i < [llength $reflist]} {
8649 if {$j < [llength $refs]} {
8650 set cmp [string compare [lindex $reflist $i 0] \
8651 [lindex $refs $j 0]]
8652 if {$cmp == 0} {
8653 set cmp [string compare [lindex $reflist $i 1] \
8654 [lindex $refs $j 1]]
8656 } else {
8657 set cmp -1
8659 } else {
8660 set cmp 1
8662 switch -- $cmp {
8663 -1 {
8664 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
8665 incr i
8668 incr i
8669 incr j
8672 set l [expr {$j + 1}]
8673 $showrefstop.list image create $l.0 -align baseline \
8674 -image reficon-[lindex $refs $j 1] -padx 2
8675 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
8676 incr j
8680 set reflist $refs
8681 # delete last newline
8682 $showrefstop.list delete end-2c end-1c
8683 $showrefstop.list conf -state disabled
8686 # Stuff for finding nearby tags
8687 proc getallcommits {} {
8688 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
8689 global idheads idtags idotherrefs allparents tagobjid
8691 if {![info exists allcommits]} {
8692 set nextarc 0
8693 set allcommits 0
8694 set seeds {}
8695 set allcwait 0
8696 set cachedarcs 0
8697 set allccache [file join [gitdir] "gitk.cache"]
8698 if {![catch {
8699 set f [open $allccache r]
8700 set allcwait 1
8701 getcache $f
8702 }]} return
8705 if {$allcwait} {
8706 return
8708 set cmd [list | git rev-list --parents]
8709 set allcupdate [expr {$seeds ne {}}]
8710 if {!$allcupdate} {
8711 set ids "--all"
8712 } else {
8713 set refs [concat [array names idheads] [array names idtags] \
8714 [array names idotherrefs]]
8715 set ids {}
8716 set tagobjs {}
8717 foreach name [array names tagobjid] {
8718 lappend tagobjs $tagobjid($name)
8720 foreach id [lsort -unique $refs] {
8721 if {![info exists allparents($id)] &&
8722 [lsearch -exact $tagobjs $id] < 0} {
8723 lappend ids $id
8726 if {$ids ne {}} {
8727 foreach id $seeds {
8728 lappend ids "^$id"
8732 if {$ids ne {}} {
8733 set fd [open [concat $cmd $ids] r]
8734 fconfigure $fd -blocking 0
8735 incr allcommits
8736 nowbusy allcommits
8737 filerun $fd [list getallclines $fd]
8738 } else {
8739 dispneartags 0
8743 # Since most commits have 1 parent and 1 child, we group strings of
8744 # such commits into "arcs" joining branch/merge points (BMPs), which
8745 # are commits that either don't have 1 parent or don't have 1 child.
8747 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
8748 # arcout(id) - outgoing arcs for BMP
8749 # arcids(a) - list of IDs on arc including end but not start
8750 # arcstart(a) - BMP ID at start of arc
8751 # arcend(a) - BMP ID at end of arc
8752 # growing(a) - arc a is still growing
8753 # arctags(a) - IDs out of arcids (excluding end) that have tags
8754 # archeads(a) - IDs out of arcids (excluding end) that have heads
8755 # The start of an arc is at the descendent end, so "incoming" means
8756 # coming from descendents, and "outgoing" means going towards ancestors.
8758 proc getallclines {fd} {
8759 global allparents allchildren idtags idheads nextarc
8760 global arcnos arcids arctags arcout arcend arcstart archeads growing
8761 global seeds allcommits cachedarcs allcupdate
8763 set nid 0
8764 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
8765 set id [lindex $line 0]
8766 if {[info exists allparents($id)]} {
8767 # seen it already
8768 continue
8770 set cachedarcs 0
8771 set olds [lrange $line 1 end]
8772 set allparents($id) $olds
8773 if {![info exists allchildren($id)]} {
8774 set allchildren($id) {}
8775 set arcnos($id) {}
8776 lappend seeds $id
8777 } else {
8778 set a $arcnos($id)
8779 if {[llength $olds] == 1 && [llength $a] == 1} {
8780 lappend arcids($a) $id
8781 if {[info exists idtags($id)]} {
8782 lappend arctags($a) $id
8784 if {[info exists idheads($id)]} {
8785 lappend archeads($a) $id
8787 if {[info exists allparents($olds)]} {
8788 # seen parent already
8789 if {![info exists arcout($olds)]} {
8790 splitarc $olds
8792 lappend arcids($a) $olds
8793 set arcend($a) $olds
8794 unset growing($a)
8796 lappend allchildren($olds) $id
8797 lappend arcnos($olds) $a
8798 continue
8801 foreach a $arcnos($id) {
8802 lappend arcids($a) $id
8803 set arcend($a) $id
8804 unset growing($a)
8807 set ao {}
8808 foreach p $olds {
8809 lappend allchildren($p) $id
8810 set a [incr nextarc]
8811 set arcstart($a) $id
8812 set archeads($a) {}
8813 set arctags($a) {}
8814 set archeads($a) {}
8815 set arcids($a) {}
8816 lappend ao $a
8817 set growing($a) 1
8818 if {[info exists allparents($p)]} {
8819 # seen it already, may need to make a new branch
8820 if {![info exists arcout($p)]} {
8821 splitarc $p
8823 lappend arcids($a) $p
8824 set arcend($a) $p
8825 unset growing($a)
8827 lappend arcnos($p) $a
8829 set arcout($id) $ao
8831 if {$nid > 0} {
8832 global cached_dheads cached_dtags cached_atags
8833 catch {unset cached_dheads}
8834 catch {unset cached_dtags}
8835 catch {unset cached_atags}
8837 if {![eof $fd]} {
8838 return [expr {$nid >= 1000? 2: 1}]
8840 set cacheok 1
8841 if {[catch {
8842 fconfigure $fd -blocking 1
8843 close $fd
8844 } err]} {
8845 # got an error reading the list of commits
8846 # if we were updating, try rereading the whole thing again
8847 if {$allcupdate} {
8848 incr allcommits -1
8849 dropcache $err
8850 return
8852 error_popup "[mc "Error reading commit topology information;\
8853 branch and preceding/following tag information\
8854 will be incomplete."]\n($err)"
8855 set cacheok 0
8857 if {[incr allcommits -1] == 0} {
8858 notbusy allcommits
8859 if {$cacheok} {
8860 run savecache
8863 dispneartags 0
8864 return 0
8867 proc recalcarc {a} {
8868 global arctags archeads arcids idtags idheads
8870 set at {}
8871 set ah {}
8872 foreach id [lrange $arcids($a) 0 end-1] {
8873 if {[info exists idtags($id)]} {
8874 lappend at $id
8876 if {[info exists idheads($id)]} {
8877 lappend ah $id
8880 set arctags($a) $at
8881 set archeads($a) $ah
8884 proc splitarc {p} {
8885 global arcnos arcids nextarc arctags archeads idtags idheads
8886 global arcstart arcend arcout allparents growing
8888 set a $arcnos($p)
8889 if {[llength $a] != 1} {
8890 puts "oops splitarc called but [llength $a] arcs already"
8891 return
8893 set a [lindex $a 0]
8894 set i [lsearch -exact $arcids($a) $p]
8895 if {$i < 0} {
8896 puts "oops splitarc $p not in arc $a"
8897 return
8899 set na [incr nextarc]
8900 if {[info exists arcend($a)]} {
8901 set arcend($na) $arcend($a)
8902 } else {
8903 set l [lindex $allparents([lindex $arcids($a) end]) 0]
8904 set j [lsearch -exact $arcnos($l) $a]
8905 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
8907 set tail [lrange $arcids($a) [expr {$i+1}] end]
8908 set arcids($a) [lrange $arcids($a) 0 $i]
8909 set arcend($a) $p
8910 set arcstart($na) $p
8911 set arcout($p) $na
8912 set arcids($na) $tail
8913 if {[info exists growing($a)]} {
8914 set growing($na) 1
8915 unset growing($a)
8918 foreach id $tail {
8919 if {[llength $arcnos($id)] == 1} {
8920 set arcnos($id) $na
8921 } else {
8922 set j [lsearch -exact $arcnos($id) $a]
8923 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
8927 # reconstruct tags and heads lists
8928 if {$arctags($a) ne {} || $archeads($a) ne {}} {
8929 recalcarc $a
8930 recalcarc $na
8931 } else {
8932 set arctags($na) {}
8933 set archeads($na) {}
8937 # Update things for a new commit added that is a child of one
8938 # existing commit. Used when cherry-picking.
8939 proc addnewchild {id p} {
8940 global allparents allchildren idtags nextarc
8941 global arcnos arcids arctags arcout arcend arcstart archeads growing
8942 global seeds allcommits
8944 if {![info exists allcommits] || ![info exists arcnos($p)]} return
8945 set allparents($id) [list $p]
8946 set allchildren($id) {}
8947 set arcnos($id) {}
8948 lappend seeds $id
8949 lappend allchildren($p) $id
8950 set a [incr nextarc]
8951 set arcstart($a) $id
8952 set archeads($a) {}
8953 set arctags($a) {}
8954 set arcids($a) [list $p]
8955 set arcend($a) $p
8956 if {![info exists arcout($p)]} {
8957 splitarc $p
8959 lappend arcnos($p) $a
8960 set arcout($id) [list $a]
8963 # This implements a cache for the topology information.
8964 # The cache saves, for each arc, the start and end of the arc,
8965 # the ids on the arc, and the outgoing arcs from the end.
8966 proc readcache {f} {
8967 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
8968 global idtags idheads allparents cachedarcs possible_seeds seeds growing
8969 global allcwait
8971 set a $nextarc
8972 set lim $cachedarcs
8973 if {$lim - $a > 500} {
8974 set lim [expr {$a + 500}]
8976 if {[catch {
8977 if {$a == $lim} {
8978 # finish reading the cache and setting up arctags, etc.
8979 set line [gets $f]
8980 if {$line ne "1"} {error "bad final version"}
8981 close $f
8982 foreach id [array names idtags] {
8983 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8984 [llength $allparents($id)] == 1} {
8985 set a [lindex $arcnos($id) 0]
8986 if {$arctags($a) eq {}} {
8987 recalcarc $a
8991 foreach id [array names idheads] {
8992 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
8993 [llength $allparents($id)] == 1} {
8994 set a [lindex $arcnos($id) 0]
8995 if {$archeads($a) eq {}} {
8996 recalcarc $a
9000 foreach id [lsort -unique $possible_seeds] {
9001 if {$arcnos($id) eq {}} {
9002 lappend seeds $id
9005 set allcwait 0
9006 } else {
9007 while {[incr a] <= $lim} {
9008 set line [gets $f]
9009 if {[llength $line] != 3} {error "bad line"}
9010 set s [lindex $line 0]
9011 set arcstart($a) $s
9012 lappend arcout($s) $a
9013 if {![info exists arcnos($s)]} {
9014 lappend possible_seeds $s
9015 set arcnos($s) {}
9017 set e [lindex $line 1]
9018 if {$e eq {}} {
9019 set growing($a) 1
9020 } else {
9021 set arcend($a) $e
9022 if {![info exists arcout($e)]} {
9023 set arcout($e) {}
9026 set arcids($a) [lindex $line 2]
9027 foreach id $arcids($a) {
9028 lappend allparents($s) $id
9029 set s $id
9030 lappend arcnos($id) $a
9032 if {![info exists allparents($s)]} {
9033 set allparents($s) {}
9035 set arctags($a) {}
9036 set archeads($a) {}
9038 set nextarc [expr {$a - 1}]
9040 } err]} {
9041 dropcache $err
9042 return 0
9044 if {!$allcwait} {
9045 getallcommits
9047 return $allcwait
9050 proc getcache {f} {
9051 global nextarc cachedarcs possible_seeds
9053 if {[catch {
9054 set line [gets $f]
9055 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
9056 # make sure it's an integer
9057 set cachedarcs [expr {int([lindex $line 1])}]
9058 if {$cachedarcs < 0} {error "bad number of arcs"}
9059 set nextarc 0
9060 set possible_seeds {}
9061 run readcache $f
9062 } err]} {
9063 dropcache $err
9065 return 0
9068 proc dropcache {err} {
9069 global allcwait nextarc cachedarcs seeds
9071 #puts "dropping cache ($err)"
9072 foreach v {arcnos arcout arcids arcstart arcend growing \
9073 arctags archeads allparents allchildren} {
9074 global $v
9075 catch {unset $v}
9077 set allcwait 0
9078 set nextarc 0
9079 set cachedarcs 0
9080 set seeds {}
9081 getallcommits
9084 proc writecache {f} {
9085 global cachearc cachedarcs allccache
9086 global arcstart arcend arcnos arcids arcout
9088 set a $cachearc
9089 set lim $cachedarcs
9090 if {$lim - $a > 1000} {
9091 set lim [expr {$a + 1000}]
9093 if {[catch {
9094 while {[incr a] <= $lim} {
9095 if {[info exists arcend($a)]} {
9096 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
9097 } else {
9098 puts $f [list $arcstart($a) {} $arcids($a)]
9101 } err]} {
9102 catch {close $f}
9103 catch {file delete $allccache}
9104 #puts "writing cache failed ($err)"
9105 return 0
9107 set cachearc [expr {$a - 1}]
9108 if {$a > $cachedarcs} {
9109 puts $f "1"
9110 close $f
9111 return 0
9113 return 1
9116 proc savecache {} {
9117 global nextarc cachedarcs cachearc allccache
9119 if {$nextarc == $cachedarcs} return
9120 set cachearc 0
9121 set cachedarcs $nextarc
9122 catch {
9123 set f [open $allccache w]
9124 puts $f [list 1 $cachedarcs]
9125 run writecache $f
9129 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
9130 # or 0 if neither is true.
9131 proc anc_or_desc {a b} {
9132 global arcout arcstart arcend arcnos cached_isanc
9134 if {$arcnos($a) eq $arcnos($b)} {
9135 # Both are on the same arc(s); either both are the same BMP,
9136 # or if one is not a BMP, the other is also not a BMP or is
9137 # the BMP at end of the arc (and it only has 1 incoming arc).
9138 # Or both can be BMPs with no incoming arcs.
9139 if {$a eq $b || $arcnos($a) eq {}} {
9140 return 0
9142 # assert {[llength $arcnos($a)] == 1}
9143 set arc [lindex $arcnos($a) 0]
9144 set i [lsearch -exact $arcids($arc) $a]
9145 set j [lsearch -exact $arcids($arc) $b]
9146 if {$i < 0 || $i > $j} {
9147 return 1
9148 } else {
9149 return -1
9153 if {![info exists arcout($a)]} {
9154 set arc [lindex $arcnos($a) 0]
9155 if {[info exists arcend($arc)]} {
9156 set aend $arcend($arc)
9157 } else {
9158 set aend {}
9160 set a $arcstart($arc)
9161 } else {
9162 set aend $a
9164 if {![info exists arcout($b)]} {
9165 set arc [lindex $arcnos($b) 0]
9166 if {[info exists arcend($arc)]} {
9167 set bend $arcend($arc)
9168 } else {
9169 set bend {}
9171 set b $arcstart($arc)
9172 } else {
9173 set bend $b
9175 if {$a eq $bend} {
9176 return 1
9178 if {$b eq $aend} {
9179 return -1
9181 if {[info exists cached_isanc($a,$bend)]} {
9182 if {$cached_isanc($a,$bend)} {
9183 return 1
9186 if {[info exists cached_isanc($b,$aend)]} {
9187 if {$cached_isanc($b,$aend)} {
9188 return -1
9190 if {[info exists cached_isanc($a,$bend)]} {
9191 return 0
9195 set todo [list $a $b]
9196 set anc($a) a
9197 set anc($b) b
9198 for {set i 0} {$i < [llength $todo]} {incr i} {
9199 set x [lindex $todo $i]
9200 if {$anc($x) eq {}} {
9201 continue
9203 foreach arc $arcnos($x) {
9204 set xd $arcstart($arc)
9205 if {$xd eq $bend} {
9206 set cached_isanc($a,$bend) 1
9207 set cached_isanc($b,$aend) 0
9208 return 1
9209 } elseif {$xd eq $aend} {
9210 set cached_isanc($b,$aend) 1
9211 set cached_isanc($a,$bend) 0
9212 return -1
9214 if {![info exists anc($xd)]} {
9215 set anc($xd) $anc($x)
9216 lappend todo $xd
9217 } elseif {$anc($xd) ne $anc($x)} {
9218 set anc($xd) {}
9222 set cached_isanc($a,$bend) 0
9223 set cached_isanc($b,$aend) 0
9224 return 0
9227 # This identifies whether $desc has an ancestor that is
9228 # a growing tip of the graph and which is not an ancestor of $anc
9229 # and returns 0 if so and 1 if not.
9230 # If we subsequently discover a tag on such a growing tip, and that
9231 # turns out to be a descendent of $anc (which it could, since we
9232 # don't necessarily see children before parents), then $desc
9233 # isn't a good choice to display as a descendent tag of
9234 # $anc (since it is the descendent of another tag which is
9235 # a descendent of $anc). Similarly, $anc isn't a good choice to
9236 # display as a ancestor tag of $desc.
9238 proc is_certain {desc anc} {
9239 global arcnos arcout arcstart arcend growing problems
9241 set certain {}
9242 if {[llength $arcnos($anc)] == 1} {
9243 # tags on the same arc are certain
9244 if {$arcnos($desc) eq $arcnos($anc)} {
9245 return 1
9247 if {![info exists arcout($anc)]} {
9248 # if $anc is partway along an arc, use the start of the arc instead
9249 set a [lindex $arcnos($anc) 0]
9250 set anc $arcstart($a)
9253 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
9254 set x $desc
9255 } else {
9256 set a [lindex $arcnos($desc) 0]
9257 set x $arcend($a)
9259 if {$x == $anc} {
9260 return 1
9262 set anclist [list $x]
9263 set dl($x) 1
9264 set nnh 1
9265 set ngrowanc 0
9266 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
9267 set x [lindex $anclist $i]
9268 if {$dl($x)} {
9269 incr nnh -1
9271 set done($x) 1
9272 foreach a $arcout($x) {
9273 if {[info exists growing($a)]} {
9274 if {![info exists growanc($x)] && $dl($x)} {
9275 set growanc($x) 1
9276 incr ngrowanc
9278 } else {
9279 set y $arcend($a)
9280 if {[info exists dl($y)]} {
9281 if {$dl($y)} {
9282 if {!$dl($x)} {
9283 set dl($y) 0
9284 if {![info exists done($y)]} {
9285 incr nnh -1
9287 if {[info exists growanc($x)]} {
9288 incr ngrowanc -1
9290 set xl [list $y]
9291 for {set k 0} {$k < [llength $xl]} {incr k} {
9292 set z [lindex $xl $k]
9293 foreach c $arcout($z) {
9294 if {[info exists arcend($c)]} {
9295 set v $arcend($c)
9296 if {[info exists dl($v)] && $dl($v)} {
9297 set dl($v) 0
9298 if {![info exists done($v)]} {
9299 incr nnh -1
9301 if {[info exists growanc($v)]} {
9302 incr ngrowanc -1
9304 lappend xl $v
9311 } elseif {$y eq $anc || !$dl($x)} {
9312 set dl($y) 0
9313 lappend anclist $y
9314 } else {
9315 set dl($y) 1
9316 lappend anclist $y
9317 incr nnh
9322 foreach x [array names growanc] {
9323 if {$dl($x)} {
9324 return 0
9326 return 0
9328 return 1
9331 proc validate_arctags {a} {
9332 global arctags idtags
9334 set i -1
9335 set na $arctags($a)
9336 foreach id $arctags($a) {
9337 incr i
9338 if {![info exists idtags($id)]} {
9339 set na [lreplace $na $i $i]
9340 incr i -1
9343 set arctags($a) $na
9346 proc validate_archeads {a} {
9347 global archeads idheads
9349 set i -1
9350 set na $archeads($a)
9351 foreach id $archeads($a) {
9352 incr i
9353 if {![info exists idheads($id)]} {
9354 set na [lreplace $na $i $i]
9355 incr i -1
9358 set archeads($a) $na
9361 # Return the list of IDs that have tags that are descendents of id,
9362 # ignoring IDs that are descendents of IDs already reported.
9363 proc desctags {id} {
9364 global arcnos arcstart arcids arctags idtags allparents
9365 global growing cached_dtags
9367 if {![info exists allparents($id)]} {
9368 return {}
9370 set t1 [clock clicks -milliseconds]
9371 set argid $id
9372 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9373 # part-way along an arc; check that arc first
9374 set a [lindex $arcnos($id) 0]
9375 if {$arctags($a) ne {}} {
9376 validate_arctags $a
9377 set i [lsearch -exact $arcids($a) $id]
9378 set tid {}
9379 foreach t $arctags($a) {
9380 set j [lsearch -exact $arcids($a) $t]
9381 if {$j >= $i} break
9382 set tid $t
9384 if {$tid ne {}} {
9385 return $tid
9388 set id $arcstart($a)
9389 if {[info exists idtags($id)]} {
9390 return $id
9393 if {[info exists cached_dtags($id)]} {
9394 return $cached_dtags($id)
9397 set origid $id
9398 set todo [list $id]
9399 set queued($id) 1
9400 set nc 1
9401 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9402 set id [lindex $todo $i]
9403 set done($id) 1
9404 set ta [info exists hastaggedancestor($id)]
9405 if {!$ta} {
9406 incr nc -1
9408 # ignore tags on starting node
9409 if {!$ta && $i > 0} {
9410 if {[info exists idtags($id)]} {
9411 set tagloc($id) $id
9412 set ta 1
9413 } elseif {[info exists cached_dtags($id)]} {
9414 set tagloc($id) $cached_dtags($id)
9415 set ta 1
9418 foreach a $arcnos($id) {
9419 set d $arcstart($a)
9420 if {!$ta && $arctags($a) ne {}} {
9421 validate_arctags $a
9422 if {$arctags($a) ne {}} {
9423 lappend tagloc($id) [lindex $arctags($a) end]
9426 if {$ta || $arctags($a) ne {}} {
9427 set tomark [list $d]
9428 for {set j 0} {$j < [llength $tomark]} {incr j} {
9429 set dd [lindex $tomark $j]
9430 if {![info exists hastaggedancestor($dd)]} {
9431 if {[info exists done($dd)]} {
9432 foreach b $arcnos($dd) {
9433 lappend tomark $arcstart($b)
9435 if {[info exists tagloc($dd)]} {
9436 unset tagloc($dd)
9438 } elseif {[info exists queued($dd)]} {
9439 incr nc -1
9441 set hastaggedancestor($dd) 1
9445 if {![info exists queued($d)]} {
9446 lappend todo $d
9447 set queued($d) 1
9448 if {![info exists hastaggedancestor($d)]} {
9449 incr nc
9454 set tags {}
9455 foreach id [array names tagloc] {
9456 if {![info exists hastaggedancestor($id)]} {
9457 foreach t $tagloc($id) {
9458 if {[lsearch -exact $tags $t] < 0} {
9459 lappend tags $t
9464 set t2 [clock clicks -milliseconds]
9465 set loopix $i
9467 # remove tags that are descendents of other tags
9468 for {set i 0} {$i < [llength $tags]} {incr i} {
9469 set a [lindex $tags $i]
9470 for {set j 0} {$j < $i} {incr j} {
9471 set b [lindex $tags $j]
9472 set r [anc_or_desc $a $b]
9473 if {$r == 1} {
9474 set tags [lreplace $tags $j $j]
9475 incr j -1
9476 incr i -1
9477 } elseif {$r == -1} {
9478 set tags [lreplace $tags $i $i]
9479 incr i -1
9480 break
9485 if {[array names growing] ne {}} {
9486 # graph isn't finished, need to check if any tag could get
9487 # eclipsed by another tag coming later. Simply ignore any
9488 # tags that could later get eclipsed.
9489 set ctags {}
9490 foreach t $tags {
9491 if {[is_certain $t $origid]} {
9492 lappend ctags $t
9495 if {$tags eq $ctags} {
9496 set cached_dtags($origid) $tags
9497 } else {
9498 set tags $ctags
9500 } else {
9501 set cached_dtags($origid) $tags
9503 set t3 [clock clicks -milliseconds]
9504 if {0 && $t3 - $t1 >= 100} {
9505 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
9506 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9508 return $tags
9511 proc anctags {id} {
9512 global arcnos arcids arcout arcend arctags idtags allparents
9513 global growing cached_atags
9515 if {![info exists allparents($id)]} {
9516 return {}
9518 set t1 [clock clicks -milliseconds]
9519 set argid $id
9520 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9521 # part-way along an arc; check that arc first
9522 set a [lindex $arcnos($id) 0]
9523 if {$arctags($a) ne {}} {
9524 validate_arctags $a
9525 set i [lsearch -exact $arcids($a) $id]
9526 foreach t $arctags($a) {
9527 set j [lsearch -exact $arcids($a) $t]
9528 if {$j > $i} {
9529 return $t
9533 if {![info exists arcend($a)]} {
9534 return {}
9536 set id $arcend($a)
9537 if {[info exists idtags($id)]} {
9538 return $id
9541 if {[info exists cached_atags($id)]} {
9542 return $cached_atags($id)
9545 set origid $id
9546 set todo [list $id]
9547 set queued($id) 1
9548 set taglist {}
9549 set nc 1
9550 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
9551 set id [lindex $todo $i]
9552 set done($id) 1
9553 set td [info exists hastaggeddescendent($id)]
9554 if {!$td} {
9555 incr nc -1
9557 # ignore tags on starting node
9558 if {!$td && $i > 0} {
9559 if {[info exists idtags($id)]} {
9560 set tagloc($id) $id
9561 set td 1
9562 } elseif {[info exists cached_atags($id)]} {
9563 set tagloc($id) $cached_atags($id)
9564 set td 1
9567 foreach a $arcout($id) {
9568 if {!$td && $arctags($a) ne {}} {
9569 validate_arctags $a
9570 if {$arctags($a) ne {}} {
9571 lappend tagloc($id) [lindex $arctags($a) 0]
9574 if {![info exists arcend($a)]} continue
9575 set d $arcend($a)
9576 if {$td || $arctags($a) ne {}} {
9577 set tomark [list $d]
9578 for {set j 0} {$j < [llength $tomark]} {incr j} {
9579 set dd [lindex $tomark $j]
9580 if {![info exists hastaggeddescendent($dd)]} {
9581 if {[info exists done($dd)]} {
9582 foreach b $arcout($dd) {
9583 if {[info exists arcend($b)]} {
9584 lappend tomark $arcend($b)
9587 if {[info exists tagloc($dd)]} {
9588 unset tagloc($dd)
9590 } elseif {[info exists queued($dd)]} {
9591 incr nc -1
9593 set hastaggeddescendent($dd) 1
9597 if {![info exists queued($d)]} {
9598 lappend todo $d
9599 set queued($d) 1
9600 if {![info exists hastaggeddescendent($d)]} {
9601 incr nc
9606 set t2 [clock clicks -milliseconds]
9607 set loopix $i
9608 set tags {}
9609 foreach id [array names tagloc] {
9610 if {![info exists hastaggeddescendent($id)]} {
9611 foreach t $tagloc($id) {
9612 if {[lsearch -exact $tags $t] < 0} {
9613 lappend tags $t
9619 # remove tags that are ancestors of other tags
9620 for {set i 0} {$i < [llength $tags]} {incr i} {
9621 set a [lindex $tags $i]
9622 for {set j 0} {$j < $i} {incr j} {
9623 set b [lindex $tags $j]
9624 set r [anc_or_desc $a $b]
9625 if {$r == -1} {
9626 set tags [lreplace $tags $j $j]
9627 incr j -1
9628 incr i -1
9629 } elseif {$r == 1} {
9630 set tags [lreplace $tags $i $i]
9631 incr i -1
9632 break
9637 if {[array names growing] ne {}} {
9638 # graph isn't finished, need to check if any tag could get
9639 # eclipsed by another tag coming later. Simply ignore any
9640 # tags that could later get eclipsed.
9641 set ctags {}
9642 foreach t $tags {
9643 if {[is_certain $origid $t]} {
9644 lappend ctags $t
9647 if {$tags eq $ctags} {
9648 set cached_atags($origid) $tags
9649 } else {
9650 set tags $ctags
9652 } else {
9653 set cached_atags($origid) $tags
9655 set t3 [clock clicks -milliseconds]
9656 if {0 && $t3 - $t1 >= 100} {
9657 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
9658 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
9660 return $tags
9663 # Return the list of IDs that have heads that are descendents of id,
9664 # including id itself if it has a head.
9665 proc descheads {id} {
9666 global arcnos arcstart arcids archeads idheads cached_dheads
9667 global allparents
9669 if {![info exists allparents($id)]} {
9670 return {}
9672 set aret {}
9673 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
9674 # part-way along an arc; check it first
9675 set a [lindex $arcnos($id) 0]
9676 if {$archeads($a) ne {}} {
9677 validate_archeads $a
9678 set i [lsearch -exact $arcids($a) $id]
9679 foreach t $archeads($a) {
9680 set j [lsearch -exact $arcids($a) $t]
9681 if {$j > $i} break
9682 lappend aret $t
9685 set id $arcstart($a)
9687 set origid $id
9688 set todo [list $id]
9689 set seen($id) 1
9690 set ret {}
9691 for {set i 0} {$i < [llength $todo]} {incr i} {
9692 set id [lindex $todo $i]
9693 if {[info exists cached_dheads($id)]} {
9694 set ret [concat $ret $cached_dheads($id)]
9695 } else {
9696 if {[info exists idheads($id)]} {
9697 lappend ret $id
9699 foreach a $arcnos($id) {
9700 if {$archeads($a) ne {}} {
9701 validate_archeads $a
9702 if {$archeads($a) ne {}} {
9703 set ret [concat $ret $archeads($a)]
9706 set d $arcstart($a)
9707 if {![info exists seen($d)]} {
9708 lappend todo $d
9709 set seen($d) 1
9714 set ret [lsort -unique $ret]
9715 set cached_dheads($origid) $ret
9716 return [concat $ret $aret]
9719 proc addedtag {id} {
9720 global arcnos arcout cached_dtags cached_atags
9722 if {![info exists arcnos($id)]} return
9723 if {![info exists arcout($id)]} {
9724 recalcarc [lindex $arcnos($id) 0]
9726 catch {unset cached_dtags}
9727 catch {unset cached_atags}
9730 proc addedhead {hid head} {
9731 global arcnos arcout cached_dheads
9733 if {![info exists arcnos($hid)]} return
9734 if {![info exists arcout($hid)]} {
9735 recalcarc [lindex $arcnos($hid) 0]
9737 catch {unset cached_dheads}
9740 proc removedhead {hid head} {
9741 global cached_dheads
9743 catch {unset cached_dheads}
9746 proc movedhead {hid head} {
9747 global arcnos arcout cached_dheads
9749 if {![info exists arcnos($hid)]} return
9750 if {![info exists arcout($hid)]} {
9751 recalcarc [lindex $arcnos($hid) 0]
9753 catch {unset cached_dheads}
9756 proc changedrefs {} {
9757 global cached_dheads cached_dtags cached_atags
9758 global arctags archeads arcnos arcout idheads idtags
9760 foreach id [concat [array names idheads] [array names idtags]] {
9761 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
9762 set a [lindex $arcnos($id) 0]
9763 if {![info exists donearc($a)]} {
9764 recalcarc $a
9765 set donearc($a) 1
9769 catch {unset cached_dtags}
9770 catch {unset cached_atags}
9771 catch {unset cached_dheads}
9774 proc rereadrefs {} {
9775 global idtags idheads idotherrefs mainheadid
9777 set refids [concat [array names idtags] \
9778 [array names idheads] [array names idotherrefs]]
9779 foreach id $refids {
9780 if {![info exists ref($id)]} {
9781 set ref($id) [listrefs $id]
9784 set oldmainhead $mainheadid
9785 readrefs
9786 changedrefs
9787 set refids [lsort -unique [concat $refids [array names idtags] \
9788 [array names idheads] [array names idotherrefs]]]
9789 foreach id $refids {
9790 set v [listrefs $id]
9791 if {![info exists ref($id)] || $ref($id) != $v} {
9792 redrawtags $id
9795 if {$oldmainhead ne $mainheadid} {
9796 redrawtags $oldmainhead
9797 redrawtags $mainheadid
9799 run refill_reflist
9802 proc listrefs {id} {
9803 global idtags idheads idotherrefs
9805 set x {}
9806 if {[info exists idtags($id)]} {
9807 set x $idtags($id)
9809 set y {}
9810 if {[info exists idheads($id)]} {
9811 set y $idheads($id)
9813 set z {}
9814 if {[info exists idotherrefs($id)]} {
9815 set z $idotherrefs($id)
9817 return [list $x $y $z]
9820 proc showtag {tag isnew} {
9821 global ctext tagcontents tagids linknum tagobjid
9823 if {$isnew} {
9824 addtohistory [list showtag $tag 0]
9826 $ctext conf -state normal
9827 clear_ctext
9828 settabs 0
9829 set linknum 0
9830 if {![info exists tagcontents($tag)]} {
9831 catch {
9832 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
9835 if {[info exists tagcontents($tag)]} {
9836 set text $tagcontents($tag)
9837 } else {
9838 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
9840 appendwithlinks $text {}
9841 $ctext conf -state disabled
9842 init_flist {}
9845 proc doquit {} {
9846 global stopped
9847 global gitktmpdir
9849 set stopped 100
9850 savestuff .
9851 destroy .
9853 if {[info exists gitktmpdir]} {
9854 catch {file delete -force $gitktmpdir}
9858 proc mkfontdisp {font top which} {
9859 global fontattr fontpref $font
9861 set fontpref($font) [set $font]
9862 button $top.${font}but -text $which -font optionfont \
9863 -command [list choosefont $font $which]
9864 label $top.$font -relief flat -font $font \
9865 -text $fontattr($font,family) -justify left
9866 grid x $top.${font}but $top.$font -sticky w
9869 proc choosefont {font which} {
9870 global fontparam fontlist fonttop fontattr
9871 global prefstop
9873 set fontparam(which) $which
9874 set fontparam(font) $font
9875 set fontparam(family) [font actual $font -family]
9876 set fontparam(size) $fontattr($font,size)
9877 set fontparam(weight) $fontattr($font,weight)
9878 set fontparam(slant) $fontattr($font,slant)
9879 set top .gitkfont
9880 set fonttop $top
9881 if {![winfo exists $top]} {
9882 font create sample
9883 eval font config sample [font actual $font]
9884 toplevel $top
9885 make_transient $top $prefstop
9886 wm title $top [mc "Gitk font chooser"]
9887 label $top.l -textvariable fontparam(which)
9888 pack $top.l -side top
9889 set fontlist [lsort [font families]]
9890 frame $top.f
9891 listbox $top.f.fam -listvariable fontlist \
9892 -yscrollcommand [list $top.f.sb set]
9893 bind $top.f.fam <<ListboxSelect>> selfontfam
9894 scrollbar $top.f.sb -command [list $top.f.fam yview]
9895 pack $top.f.sb -side right -fill y
9896 pack $top.f.fam -side left -fill both -expand 1
9897 pack $top.f -side top -fill both -expand 1
9898 frame $top.g
9899 spinbox $top.g.size -from 4 -to 40 -width 4 \
9900 -textvariable fontparam(size) \
9901 -validatecommand {string is integer -strict %s}
9902 checkbutton $top.g.bold -padx 5 \
9903 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
9904 -variable fontparam(weight) -onvalue bold -offvalue normal
9905 checkbutton $top.g.ital -padx 5 \
9906 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
9907 -variable fontparam(slant) -onvalue italic -offvalue roman
9908 pack $top.g.size $top.g.bold $top.g.ital -side left
9909 pack $top.g -side top
9910 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
9911 -background white
9912 $top.c create text 100 25 -anchor center -text $which -font sample \
9913 -fill black -tags text
9914 bind $top.c <Configure> [list centertext $top.c]
9915 pack $top.c -side top -fill x
9916 frame $top.buts
9917 button $top.buts.ok -text [mc "OK"] -command fontok -default active
9918 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
9919 bind $top <Key-Return> fontok
9920 bind $top <Key-Escape> fontcan
9921 grid $top.buts.ok $top.buts.can
9922 grid columnconfigure $top.buts 0 -weight 1 -uniform a
9923 grid columnconfigure $top.buts 1 -weight 1 -uniform a
9924 pack $top.buts -side bottom -fill x
9925 trace add variable fontparam write chg_fontparam
9926 } else {
9927 raise $top
9928 $top.c itemconf text -text $which
9930 set i [lsearch -exact $fontlist $fontparam(family)]
9931 if {$i >= 0} {
9932 $top.f.fam selection set $i
9933 $top.f.fam see $i
9937 proc centertext {w} {
9938 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
9941 proc fontok {} {
9942 global fontparam fontpref prefstop
9944 set f $fontparam(font)
9945 set fontpref($f) [list $fontparam(family) $fontparam(size)]
9946 if {$fontparam(weight) eq "bold"} {
9947 lappend fontpref($f) "bold"
9949 if {$fontparam(slant) eq "italic"} {
9950 lappend fontpref($f) "italic"
9952 set w $prefstop.$f
9953 $w conf -text $fontparam(family) -font $fontpref($f)
9955 fontcan
9958 proc fontcan {} {
9959 global fonttop fontparam
9961 if {[info exists fonttop]} {
9962 catch {destroy $fonttop}
9963 catch {font delete sample}
9964 unset fonttop
9965 unset fontparam
9969 proc selfontfam {} {
9970 global fonttop fontparam
9972 set i [$fonttop.f.fam curselection]
9973 if {$i ne {}} {
9974 set fontparam(family) [$fonttop.f.fam get $i]
9978 proc chg_fontparam {v sub op} {
9979 global fontparam
9981 font config sample -$sub $fontparam($sub)
9984 proc doprefs {} {
9985 global maxwidth maxgraphpct
9986 global oldprefs prefstop showneartags showlocalchanges
9987 global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
9988 global tabstop limitdiffs autoselect extdifftool perfile_attrs
9990 set top .gitkprefs
9991 set prefstop $top
9992 if {[winfo exists $top]} {
9993 raise $top
9994 return
9996 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
9997 limitdiffs tabstop perfile_attrs} {
9998 set oldprefs($v) [set $v]
10000 toplevel $top
10001 wm title $top [mc "Gitk preferences"]
10002 make_transient $top .
10003 label $top.ldisp -text [mc "Commit list display options"]
10004 grid $top.ldisp - -sticky w -pady 10
10005 label $top.spacer -text " "
10006 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
10007 -font optionfont
10008 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
10009 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
10010 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
10011 -font optionfont
10012 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
10013 grid x $top.maxpctl $top.maxpct -sticky w
10014 frame $top.showlocal
10015 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
10016 checkbutton $top.showlocal.b -variable showlocalchanges
10017 pack $top.showlocal.b $top.showlocal.l -side left
10018 grid x $top.showlocal -sticky w
10019 frame $top.autoselect
10020 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
10021 checkbutton $top.autoselect.b -variable autoselect
10022 pack $top.autoselect.b $top.autoselect.l -side left
10023 grid x $top.autoselect -sticky w
10025 label $top.ddisp -text [mc "Diff display options"]
10026 grid $top.ddisp - -sticky w -pady 10
10027 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
10028 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
10029 grid x $top.tabstopl $top.tabstop -sticky w
10030 frame $top.ntag
10031 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
10032 checkbutton $top.ntag.b -variable showneartags
10033 pack $top.ntag.b $top.ntag.l -side left
10034 grid x $top.ntag -sticky w
10035 frame $top.ldiff
10036 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
10037 checkbutton $top.ldiff.b -variable limitdiffs
10038 pack $top.ldiff.b $top.ldiff.l -side left
10039 grid x $top.ldiff -sticky w
10040 frame $top.lattr
10041 label $top.lattr.l -text [mc "Support per-file encodings"] -font optionfont
10042 checkbutton $top.lattr.b -variable perfile_attrs
10043 pack $top.lattr.b $top.lattr.l -side left
10044 grid x $top.lattr -sticky w
10046 entry $top.extdifft -textvariable extdifftool
10047 frame $top.extdifff
10048 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
10049 -padx 10
10050 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
10051 -command choose_extdiff
10052 pack $top.extdifff.l $top.extdifff.b -side left
10053 grid x $top.extdifff $top.extdifft -sticky w
10055 label $top.cdisp -text [mc "Colors: press to choose"]
10056 grid $top.cdisp - -sticky w -pady 10
10057 label $top.bg -padx 40 -relief sunk -background $bgcolor
10058 button $top.bgbut -text [mc "Background"] -font optionfont \
10059 -command [list choosecolor bgcolor {} $top.bg background setbg]
10060 grid x $top.bgbut $top.bg -sticky w
10061 label $top.fg -padx 40 -relief sunk -background $fgcolor
10062 button $top.fgbut -text [mc "Foreground"] -font optionfont \
10063 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
10064 grid x $top.fgbut $top.fg -sticky w
10065 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
10066 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
10067 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
10068 [list $ctext tag conf d0 -foreground]]
10069 grid x $top.diffoldbut $top.diffold -sticky w
10070 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
10071 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
10072 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
10073 [list $ctext tag conf dresult -foreground]]
10074 grid x $top.diffnewbut $top.diffnew -sticky w
10075 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
10076 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
10077 -command [list choosecolor diffcolors 2 $top.hunksep \
10078 "diff hunk header" \
10079 [list $ctext tag conf hunksep -foreground]]
10080 grid x $top.hunksepbut $top.hunksep -sticky w
10081 label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
10082 button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
10083 -command [list choosecolor markbgcolor {} $top.markbgsep \
10084 [mc "marked line background"] \
10085 [list $ctext tag conf omark -background]]
10086 grid x $top.markbgbut $top.markbgsep -sticky w
10087 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
10088 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
10089 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
10090 grid x $top.selbgbut $top.selbgsep -sticky w
10092 label $top.cfont -text [mc "Fonts: press to choose"]
10093 grid $top.cfont - -sticky w -pady 10
10094 mkfontdisp mainfont $top [mc "Main font"]
10095 mkfontdisp textfont $top [mc "Diff display font"]
10096 mkfontdisp uifont $top [mc "User interface font"]
10098 frame $top.buts
10099 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
10100 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
10101 bind $top <Key-Return> prefsok
10102 bind $top <Key-Escape> prefscan
10103 grid $top.buts.ok $top.buts.can
10104 grid columnconfigure $top.buts 0 -weight 1 -uniform a
10105 grid columnconfigure $top.buts 1 -weight 1 -uniform a
10106 grid $top.buts - - -pady 10 -sticky ew
10107 bind $top <Visibility> "focus $top.buts.ok"
10110 proc choose_extdiff {} {
10111 global extdifftool
10113 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
10114 if {$prog ne {}} {
10115 set extdifftool $prog
10119 proc choosecolor {v vi w x cmd} {
10120 global $v
10122 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
10123 -title [mc "Gitk: choose color for %s" $x]]
10124 if {$c eq {}} return
10125 $w conf -background $c
10126 lset $v $vi $c
10127 eval $cmd $c
10130 proc setselbg {c} {
10131 global bglist cflist
10132 foreach w $bglist {
10133 $w configure -selectbackground $c
10135 $cflist tag configure highlight \
10136 -background [$cflist cget -selectbackground]
10137 allcanvs itemconf secsel -fill $c
10140 proc setbg {c} {
10141 global bglist
10143 foreach w $bglist {
10144 $w conf -background $c
10148 proc setfg {c} {
10149 global fglist canv
10151 foreach w $fglist {
10152 $w conf -foreground $c
10154 allcanvs itemconf text -fill $c
10155 $canv itemconf circle -outline $c
10158 proc prefscan {} {
10159 global oldprefs prefstop
10161 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
10162 limitdiffs tabstop perfile_attrs} {
10163 global $v
10164 set $v $oldprefs($v)
10166 catch {destroy $prefstop}
10167 unset prefstop
10168 fontcan
10171 proc prefsok {} {
10172 global maxwidth maxgraphpct
10173 global oldprefs prefstop showneartags showlocalchanges
10174 global fontpref mainfont textfont uifont
10175 global limitdiffs treediffs perfile_attrs
10177 catch {destroy $prefstop}
10178 unset prefstop
10179 fontcan
10180 set fontchanged 0
10181 if {$mainfont ne $fontpref(mainfont)} {
10182 set mainfont $fontpref(mainfont)
10183 parsefont mainfont $mainfont
10184 eval font configure mainfont [fontflags mainfont]
10185 eval font configure mainfontbold [fontflags mainfont 1]
10186 setcoords
10187 set fontchanged 1
10189 if {$textfont ne $fontpref(textfont)} {
10190 set textfont $fontpref(textfont)
10191 parsefont textfont $textfont
10192 eval font configure textfont [fontflags textfont]
10193 eval font configure textfontbold [fontflags textfont 1]
10195 if {$uifont ne $fontpref(uifont)} {
10196 set uifont $fontpref(uifont)
10197 parsefont uifont $uifont
10198 eval font configure uifont [fontflags uifont]
10200 settabs
10201 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
10202 if {$showlocalchanges} {
10203 doshowlocalchanges
10204 } else {
10205 dohidelocalchanges
10208 if {$limitdiffs != $oldprefs(limitdiffs) ||
10209 ($perfile_attrs && !$oldprefs(perfile_attrs))} {
10210 # treediffs elements are limited by path;
10211 # won't have encodings cached if perfile_attrs was just turned on
10212 catch {unset treediffs}
10214 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
10215 || $maxgraphpct != $oldprefs(maxgraphpct)} {
10216 redisplay
10217 } elseif {$showneartags != $oldprefs(showneartags) ||
10218 $limitdiffs != $oldprefs(limitdiffs)} {
10219 reselectline
10223 proc formatdate {d} {
10224 global datetimeformat
10225 if {$d ne {}} {
10226 set d [clock format $d -format $datetimeformat]
10228 return $d
10231 # This list of encoding names and aliases is distilled from
10232 # http://www.iana.org/assignments/character-sets.
10233 # Not all of them are supported by Tcl.
10234 set encoding_aliases {
10235 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
10236 ISO646-US US-ASCII us IBM367 cp367 csASCII }
10237 { ISO-10646-UTF-1 csISO10646UTF1 }
10238 { ISO_646.basic:1983 ref csISO646basic1983 }
10239 { INVARIANT csINVARIANT }
10240 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
10241 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
10242 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
10243 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
10244 { NATS-DANO iso-ir-9-1 csNATSDANO }
10245 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
10246 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
10247 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
10248 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
10249 { ISO-2022-KR csISO2022KR }
10250 { EUC-KR csEUCKR }
10251 { ISO-2022-JP csISO2022JP }
10252 { ISO-2022-JP-2 csISO2022JP2 }
10253 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
10254 csISO13JISC6220jp }
10255 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
10256 { IT iso-ir-15 ISO646-IT csISO15Italian }
10257 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
10258 { ES iso-ir-17 ISO646-ES csISO17Spanish }
10259 { greek7-old iso-ir-18 csISO18Greek7Old }
10260 { latin-greek iso-ir-19 csISO19LatinGreek }
10261 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
10262 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
10263 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
10264 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
10265 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
10266 { BS_viewdata iso-ir-47 csISO47BSViewdata }
10267 { INIS iso-ir-49 csISO49INIS }
10268 { INIS-8 iso-ir-50 csISO50INIS8 }
10269 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
10270 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
10271 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
10272 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
10273 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
10274 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
10275 csISO60Norwegian1 }
10276 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
10277 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
10278 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
10279 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
10280 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
10281 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
10282 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
10283 { greek7 iso-ir-88 csISO88Greek7 }
10284 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
10285 { iso-ir-90 csISO90 }
10286 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
10287 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
10288 csISO92JISC62991984b }
10289 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
10290 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
10291 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
10292 csISO95JIS62291984handadd }
10293 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
10294 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
10295 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
10296 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
10297 CP819 csISOLatin1 }
10298 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
10299 { T.61-7bit iso-ir-102 csISO102T617bit }
10300 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
10301 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
10302 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
10303 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
10304 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
10305 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
10306 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
10307 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
10308 arabic csISOLatinArabic }
10309 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
10310 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
10311 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
10312 greek greek8 csISOLatinGreek }
10313 { T.101-G2 iso-ir-128 csISO128T101G2 }
10314 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
10315 csISOLatinHebrew }
10316 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
10317 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
10318 { CSN_369103 iso-ir-139 csISO139CSN369103 }
10319 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
10320 { ISO_6937-2-add iso-ir-142 csISOTextComm }
10321 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
10322 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
10323 csISOLatinCyrillic }
10324 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
10325 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
10326 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
10327 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
10328 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
10329 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
10330 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
10331 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
10332 { ISO_10367-box iso-ir-155 csISO10367Box }
10333 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
10334 { latin-lap lap iso-ir-158 csISO158Lap }
10335 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
10336 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
10337 { us-dk csUSDK }
10338 { dk-us csDKUS }
10339 { JIS_X0201 X0201 csHalfWidthKatakana }
10340 { KSC5636 ISO646-KR csKSC5636 }
10341 { ISO-10646-UCS-2 csUnicode }
10342 { ISO-10646-UCS-4 csUCS4 }
10343 { DEC-MCS dec csDECMCS }
10344 { hp-roman8 roman8 r8 csHPRoman8 }
10345 { macintosh mac csMacintosh }
10346 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
10347 csIBM037 }
10348 { IBM038 EBCDIC-INT cp038 csIBM038 }
10349 { IBM273 CP273 csIBM273 }
10350 { IBM274 EBCDIC-BE CP274 csIBM274 }
10351 { IBM275 EBCDIC-BR cp275 csIBM275 }
10352 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
10353 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
10354 { IBM280 CP280 ebcdic-cp-it csIBM280 }
10355 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
10356 { IBM284 CP284 ebcdic-cp-es csIBM284 }
10357 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
10358 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
10359 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
10360 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
10361 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
10362 { IBM424 cp424 ebcdic-cp-he csIBM424 }
10363 { IBM437 cp437 437 csPC8CodePage437 }
10364 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
10365 { IBM775 cp775 csPC775Baltic }
10366 { IBM850 cp850 850 csPC850Multilingual }
10367 { IBM851 cp851 851 csIBM851 }
10368 { IBM852 cp852 852 csPCp852 }
10369 { IBM855 cp855 855 csIBM855 }
10370 { IBM857 cp857 857 csIBM857 }
10371 { IBM860 cp860 860 csIBM860 }
10372 { IBM861 cp861 861 cp-is csIBM861 }
10373 { IBM862 cp862 862 csPC862LatinHebrew }
10374 { IBM863 cp863 863 csIBM863 }
10375 { IBM864 cp864 csIBM864 }
10376 { IBM865 cp865 865 csIBM865 }
10377 { IBM866 cp866 866 csIBM866 }
10378 { IBM868 CP868 cp-ar csIBM868 }
10379 { IBM869 cp869 869 cp-gr csIBM869 }
10380 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
10381 { IBM871 CP871 ebcdic-cp-is csIBM871 }
10382 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
10383 { IBM891 cp891 csIBM891 }
10384 { IBM903 cp903 csIBM903 }
10385 { IBM904 cp904 904 csIBBM904 }
10386 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
10387 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
10388 { IBM1026 CP1026 csIBM1026 }
10389 { EBCDIC-AT-DE csIBMEBCDICATDE }
10390 { EBCDIC-AT-DE-A csEBCDICATDEA }
10391 { EBCDIC-CA-FR csEBCDICCAFR }
10392 { EBCDIC-DK-NO csEBCDICDKNO }
10393 { EBCDIC-DK-NO-A csEBCDICDKNOA }
10394 { EBCDIC-FI-SE csEBCDICFISE }
10395 { EBCDIC-FI-SE-A csEBCDICFISEA }
10396 { EBCDIC-FR csEBCDICFR }
10397 { EBCDIC-IT csEBCDICIT }
10398 { EBCDIC-PT csEBCDICPT }
10399 { EBCDIC-ES csEBCDICES }
10400 { EBCDIC-ES-A csEBCDICESA }
10401 { EBCDIC-ES-S csEBCDICESS }
10402 { EBCDIC-UK csEBCDICUK }
10403 { EBCDIC-US csEBCDICUS }
10404 { UNKNOWN-8BIT csUnknown8BiT }
10405 { MNEMONIC csMnemonic }
10406 { MNEM csMnem }
10407 { VISCII csVISCII }
10408 { VIQR csVIQR }
10409 { KOI8-R csKOI8R }
10410 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
10411 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
10412 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
10413 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
10414 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
10415 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
10416 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
10417 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
10418 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
10419 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
10420 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
10421 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
10422 { IBM1047 IBM-1047 }
10423 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
10424 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
10425 { UNICODE-1-1 csUnicode11 }
10426 { CESU-8 csCESU-8 }
10427 { BOCU-1 csBOCU-1 }
10428 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
10429 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
10430 l8 }
10431 { ISO-8859-15 ISO_8859-15 Latin-9 }
10432 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
10433 { GBK CP936 MS936 windows-936 }
10434 { JIS_Encoding csJISEncoding }
10435 { Shift_JIS MS_Kanji csShiftJIS ShiftJIS Shift-JIS }
10436 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
10437 EUC-JP }
10438 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
10439 { ISO-10646-UCS-Basic csUnicodeASCII }
10440 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
10441 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
10442 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
10443 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
10444 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
10445 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
10446 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
10447 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
10448 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
10449 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
10450 { Adobe-Standard-Encoding csAdobeStandardEncoding }
10451 { Ventura-US csVenturaUS }
10452 { Ventura-International csVenturaInternational }
10453 { PC8-Danish-Norwegian csPC8DanishNorwegian }
10454 { PC8-Turkish csPC8Turkish }
10455 { IBM-Symbols csIBMSymbols }
10456 { IBM-Thai csIBMThai }
10457 { HP-Legal csHPLegal }
10458 { HP-Pi-font csHPPiFont }
10459 { HP-Math8 csHPMath8 }
10460 { Adobe-Symbol-Encoding csHPPSMath }
10461 { HP-DeskTop csHPDesktop }
10462 { Ventura-Math csVenturaMath }
10463 { Microsoft-Publishing csMicrosoftPublishing }
10464 { Windows-31J csWindows31J }
10465 { GB2312 csGB2312 }
10466 { Big5 csBig5 }
10469 proc tcl_encoding {enc} {
10470 global encoding_aliases tcl_encoding_cache
10471 if {[info exists tcl_encoding_cache($enc)]} {
10472 return $tcl_encoding_cache($enc)
10474 set names [encoding names]
10475 set lcnames [string tolower $names]
10476 set enc [string tolower $enc]
10477 set i [lsearch -exact $lcnames $enc]
10478 if {$i < 0} {
10479 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
10480 if {[regsub {^(iso|cp|ibm|jis)[-_]} $enc {\1} encx]} {
10481 set i [lsearch -exact $lcnames $encx]
10484 if {$i < 0} {
10485 foreach l $encoding_aliases {
10486 set ll [string tolower $l]
10487 if {[lsearch -exact $ll $enc] < 0} continue
10488 # look through the aliases for one that tcl knows about
10489 foreach e $ll {
10490 set i [lsearch -exact $lcnames $e]
10491 if {$i < 0} {
10492 if {[regsub {^(iso|cp|ibm|jis)[-_]} $e {\1} ex]} {
10493 set i [lsearch -exact $lcnames $ex]
10496 if {$i >= 0} break
10498 break
10501 set tclenc {}
10502 if {$i >= 0} {
10503 set tclenc [lindex $names $i]
10505 set tcl_encoding_cache($enc) $tclenc
10506 return $tclenc
10509 proc gitattr {path attr default} {
10510 global path_attr_cache
10511 if {[info exists path_attr_cache($attr,$path)]} {
10512 set r $path_attr_cache($attr,$path)
10513 } else {
10514 set r "unspecified"
10515 if {![catch {set line [exec git check-attr $attr -- $path]}]} {
10516 regexp "(.*): encoding: (.*)" $line m f r
10518 set path_attr_cache($attr,$path) $r
10520 if {$r eq "unspecified"} {
10521 return $default
10523 return $r
10526 proc cache_gitattr {attr pathlist} {
10527 global path_attr_cache
10528 set newlist {}
10529 foreach path $pathlist {
10530 if {![info exists path_attr_cache($attr,$path)]} {
10531 lappend newlist $path
10534 set lim 1000
10535 if {[tk windowingsystem] == "win32"} {
10536 # windows has a 32k limit on the arguments to a command...
10537 set lim 30
10539 while {$newlist ne {}} {
10540 set head [lrange $newlist 0 [expr {$lim - 1}]]
10541 set newlist [lrange $newlist $lim end]
10542 if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
10543 foreach row [split $rlist "\n"] {
10544 if {[regexp "(.*): encoding: (.*)" $row m path value]} {
10545 if {[string index $path 0] eq "\""} {
10546 set path [encoding convertfrom [lindex $path 0]]
10548 set path_attr_cache($attr,$path) $value
10555 proc get_path_encoding {path} {
10556 global gui_encoding perfile_attrs
10557 set tcl_enc $gui_encoding
10558 if {$path ne {} && $perfile_attrs} {
10559 set enc2 [tcl_encoding [gitattr $path encoding $tcl_enc]]
10560 if {$enc2 ne {}} {
10561 set tcl_enc $enc2
10564 return $tcl_enc
10567 # First check that Tcl/Tk is recent enough
10568 if {[catch {package require Tk 8.4} err]} {
10569 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
10570 Gitk requires at least Tcl/Tk 8.4."]
10571 exit 1
10574 # defaults...
10575 set wrcomcmd "git diff-tree --stdin -p --pretty"
10577 set gitencoding {}
10578 catch {
10579 set gitencoding [exec git config --get i18n.commitencoding]
10581 catch {
10582 set gitencoding [exec git config --get i18n.logoutputencoding]
10584 if {$gitencoding == ""} {
10585 set gitencoding "utf-8"
10587 set tclencoding [tcl_encoding $gitencoding]
10588 if {$tclencoding == {}} {
10589 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
10592 set gui_encoding [encoding system]
10593 catch {
10594 set enc [exec git config --get gui.encoding]
10595 if {$enc ne {}} {
10596 set tclenc [tcl_encoding $enc]
10597 if {$tclenc ne {}} {
10598 set gui_encoding $tclenc
10599 } else {
10600 puts stderr "Warning: encoding $enc is not supported by Tcl/Tk"
10605 set mainfont {Helvetica 9}
10606 set textfont {Courier 9}
10607 set uifont {Helvetica 9 bold}
10608 set tabstop 8
10609 set findmergefiles 0
10610 set maxgraphpct 50
10611 set maxwidth 16
10612 set revlistorder 0
10613 set fastdate 0
10614 set uparrowlen 5
10615 set downarrowlen 5
10616 set mingaplen 100
10617 set cmitmode "patch"
10618 set wrapcomment "none"
10619 set showneartags 1
10620 set maxrefs 20
10621 set maxlinelen 200
10622 set showlocalchanges 1
10623 set limitdiffs 1
10624 set datetimeformat "%Y-%m-%d %H:%M:%S"
10625 set autoselect 1
10626 set perfile_attrs 0
10628 set extdifftool "meld"
10630 set colors {green red blue magenta darkgrey brown orange}
10631 set bgcolor white
10632 set fgcolor black
10633 set diffcolors {red "#00a000" blue}
10634 set diffcontext 3
10635 set ignorespace 0
10636 set selectbgcolor gray85
10637 set markbgcolor "#e0e0ff"
10639 set circlecolors {white blue gray blue blue}
10641 # button for popping up context menus
10642 if {[tk windowingsystem] eq "aqua"} {
10643 set ctxbut <Button-2>
10644 } else {
10645 set ctxbut <Button-3>
10648 ## For msgcat loading, first locate the installation location.
10649 if { [info exists ::env(GITK_MSGSDIR)] } {
10650 ## Msgsdir was manually set in the environment.
10651 set gitk_msgsdir $::env(GITK_MSGSDIR)
10652 } else {
10653 ## Let's guess the prefix from argv0.
10654 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
10655 set gitk_libdir [file join $gitk_prefix share gitk lib]
10656 set gitk_msgsdir [file join $gitk_libdir msgs]
10657 unset gitk_prefix
10660 ## Internationalization (i18n) through msgcat and gettext. See
10661 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
10662 package require msgcat
10663 namespace import ::msgcat::mc
10664 ## And eventually load the actual message catalog
10665 ::msgcat::mcload $gitk_msgsdir
10667 catch {source ~/.gitk}
10669 font create optionfont -family sans-serif -size -12
10671 parsefont mainfont $mainfont
10672 eval font create mainfont [fontflags mainfont]
10673 eval font create mainfontbold [fontflags mainfont 1]
10675 parsefont textfont $textfont
10676 eval font create textfont [fontflags textfont]
10677 eval font create textfontbold [fontflags textfont 1]
10679 parsefont uifont $uifont
10680 eval font create uifont [fontflags uifont]
10682 setoptions
10684 # check that we can find a .git directory somewhere...
10685 if {[catch {set gitdir [gitdir]}]} {
10686 show_error {} . [mc "Cannot find a git repository here."]
10687 exit 1
10689 if {![file isdirectory $gitdir]} {
10690 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
10691 exit 1
10694 set selecthead {}
10695 set selectheadid {}
10697 set revtreeargs {}
10698 set cmdline_files {}
10699 set i 0
10700 set revtreeargscmd {}
10701 foreach arg $argv {
10702 switch -glob -- $arg {
10703 "" { }
10704 "--" {
10705 set cmdline_files [lrange $argv [expr {$i + 1}] end]
10706 break
10708 "--select-commit=*" {
10709 set selecthead [string range $arg 16 end]
10711 "--argscmd=*" {
10712 set revtreeargscmd [string range $arg 10 end]
10714 default {
10715 lappend revtreeargs $arg
10718 incr i
10721 if {$selecthead eq "HEAD"} {
10722 set selecthead {}
10725 if {$i >= [llength $argv] && $revtreeargs ne {}} {
10726 # no -- on command line, but some arguments (other than --argscmd)
10727 if {[catch {
10728 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
10729 set cmdline_files [split $f "\n"]
10730 set n [llength $cmdline_files]
10731 set revtreeargs [lrange $revtreeargs 0 end-$n]
10732 # Unfortunately git rev-parse doesn't produce an error when
10733 # something is both a revision and a filename. To be consistent
10734 # with git log and git rev-list, check revtreeargs for filenames.
10735 foreach arg $revtreeargs {
10736 if {[file exists $arg]} {
10737 show_error {} . [mc "Ambiguous argument '%s': both revision\
10738 and filename" $arg]
10739 exit 1
10742 } err]} {
10743 # unfortunately we get both stdout and stderr in $err,
10744 # so look for "fatal:".
10745 set i [string first "fatal:" $err]
10746 if {$i > 0} {
10747 set err [string range $err [expr {$i + 6}] end]
10749 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
10750 exit 1
10754 set nullid "0000000000000000000000000000000000000000"
10755 set nullid2 "0000000000000000000000000000000000000001"
10756 set nullfile "/dev/null"
10758 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
10760 set runq {}
10761 set history {}
10762 set historyindex 0
10763 set fh_serial 0
10764 set nhl_names {}
10765 set highlight_paths {}
10766 set findpattern {}
10767 set searchdirn -forwards
10768 set boldids {}
10769 set boldnameids {}
10770 set diffelide {0 0}
10771 set markingmatches 0
10772 set linkentercount 0
10773 set need_redisplay 0
10774 set nrows_drawn 0
10775 set firsttabstop 0
10777 set nextviewnum 1
10778 set curview 0
10779 set selectedview 0
10780 set selectedhlview [mc "None"]
10781 set highlight_related [mc "None"]
10782 set highlight_files {}
10783 set viewfiles(0) {}
10784 set viewperm(0) 0
10785 set viewargs(0) {}
10786 set viewargscmd(0) {}
10788 set selectedline {}
10789 set numcommits 0
10790 set loginstance 0
10791 set cmdlineok 0
10792 set stopped 0
10793 set stuffsaved 0
10794 set patchnum 0
10795 set lserial 0
10796 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
10797 setcoords
10798 makewindow
10799 # wait for the window to become visible
10800 tkwait visibility .
10801 wm title . "[file tail $argv0]: [file tail [pwd]]"
10802 readrefs
10804 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
10805 # create a view for the files/dirs specified on the command line
10806 set curview 1
10807 set selectedview 1
10808 set nextviewnum 2
10809 set viewname(1) [mc "Command line"]
10810 set viewfiles(1) $cmdline_files
10811 set viewargs(1) $revtreeargs
10812 set viewargscmd(1) $revtreeargscmd
10813 set viewperm(1) 0
10814 set vdatemode(1) 0
10815 addviewmenu 1
10816 .bar.view entryconf [mca "Edit view..."] -state normal
10817 .bar.view entryconf [mca "Delete view"] -state normal
10820 if {[info exists permviews]} {
10821 foreach v $permviews {
10822 set n $nextviewnum
10823 incr nextviewnum
10824 set viewname($n) [lindex $v 0]
10825 set viewfiles($n) [lindex $v 1]
10826 set viewargs($n) [lindex $v 2]
10827 set viewargscmd($n) [lindex $v 3]
10828 set viewperm($n) 1
10829 addviewmenu $n
10832 getcommits {}