gitk: Allow users to view diffs in external diff viewer
[git/jnareb-git.git] / gitk
bloba1eccfc6d278cc013b055256029f5056f81d3b24
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "$@"
5 # Copyright (C) 2005-2006 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
27 set script $args
28 if {[info exists isonrunq($script)]} return
29 if {$runq eq {}} {
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
43 fileevent $fd readable {}
44 if {$runq eq {}} {
45 after idle dorunq
47 lappend runq [list $fd $script]
50 proc dorunq {} {
51 global isonrunq runq
53 set tstart [clock clicks -milliseconds]
54 set t0 $tstart
55 while {$runq ne {}} {
56 set fd [lindex $runq 0 0]
57 set script [lindex $runq 0 1]
58 set repeat [eval $script]
59 set t1 [clock clicks -milliseconds]
60 set t [expr {$t1 - $t0}]
61 set runq [lrange $runq 1 end]
62 if {$repeat ne {} && $repeat} {
63 if {$fd eq {} || $repeat == 2} {
64 # script returns 1 if it wants to be readded
65 # file readers return 2 if they could do more straight away
66 lappend runq [list $fd $script]
67 } else {
68 fileevent $fd readable [list filereadable $fd $script]
70 } elseif {$fd eq {}} {
71 unset isonrunq($script)
73 set t0 $t1
74 if {$t1 - $tstart >= 80} break
76 if {$runq ne {}} {
77 after idle dorunq
81 # Start off a git rev-list process and arrange to read its output
82 proc start_rev_list {view} {
83 global startmsecs
84 global commfd leftover tclencoding datemode
85 global viewargs viewargscmd viewfiles commitidx viewcomplete vnextroot
86 global showlocalchanges commitinterest mainheadid
87 global progressdirn progresscoords proglastnc curview
89 set startmsecs [clock clicks -milliseconds]
90 set commitidx($view) 0
91 set viewcomplete($view) 0
92 set vnextroot($view) 0
93 set args $viewargs($view)
94 if {$viewargscmd($view) ne {}} {
95 if {[catch {
96 set str [exec sh -c $viewargscmd($view)]
97 } err]} {
98 error_popup "Error executing --argscmd command: $err"
99 exit 1
101 set args [concat $args [split $str "\n"]]
103 set order "--topo-order"
104 if {$datemode} {
105 set order "--date-order"
107 if {[catch {
108 set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \
109 --boundary $args "--" $viewfiles($view)] r]
110 } err]} {
111 error_popup "[mc "Error executing git rev-list:"] $err"
112 exit 1
114 set commfd($view) $fd
115 set leftover($view) {}
116 if {$showlocalchanges} {
117 lappend commitinterest($mainheadid) {dodiffindex}
119 fconfigure $fd -blocking 0 -translation lf -eofchar {}
120 if {$tclencoding != {}} {
121 fconfigure $fd -encoding $tclencoding
123 filerun $fd [list getcommitlines $fd $view]
124 nowbusy $view [mc "Reading"]
125 if {$view == $curview} {
126 set progressdirn 1
127 set progresscoords {0 0}
128 set proglastnc 0
132 proc stop_rev_list {} {
133 global commfd curview
135 if {![info exists commfd($curview)]} return
136 set fd $commfd($curview)
137 catch {
138 set pid [pid $fd]
139 exec kill $pid
141 catch {close $fd}
142 unset commfd($curview)
145 proc getcommits {} {
146 global phase canv curview
148 set phase getcommits
149 initlayout
150 start_rev_list $curview
151 show_status [mc "Reading commits..."]
154 # This makes a string representation of a positive integer which
155 # sorts as a string in numerical order
156 proc strrep {n} {
157 if {$n < 16} {
158 return [format "%x" $n]
159 } elseif {$n < 256} {
160 return [format "x%.2x" $n]
161 } elseif {$n < 65536} {
162 return [format "y%.4x" $n]
164 return [format "z%.8x" $n]
167 proc getcommitlines {fd view} {
168 global commitlisted commitinterest
169 global leftover commfd
170 global displayorder commitidx viewcomplete commitrow commitdata
171 global parentlist children curview hlview
172 global vparentlist vdisporder vcmitlisted
173 global ordertok vnextroot idpending
175 set stuff [read $fd 500000]
176 # git log doesn't terminate the last commit with a null...
177 if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
178 set stuff "\0"
180 if {$stuff == {}} {
181 if {![eof $fd]} {
182 return 1
184 # Check if we have seen any ids listed as parents that haven't
185 # appeared in the list
186 foreach vid [array names idpending "$view,*"] {
187 # should only get here if git log is buggy
188 set id [lindex [split $vid ","] 1]
189 set commitrow($vid) $commitidx($view)
190 incr commitidx($view)
191 if {$view == $curview} {
192 lappend parentlist {}
193 lappend displayorder $id
194 lappend commitlisted 0
195 } else {
196 lappend vparentlist($view) {}
197 lappend vdisporder($view) $id
198 lappend vcmitlisted($view) 0
201 set viewcomplete($view) 1
202 global viewname progresscoords
203 unset commfd($view)
204 notbusy $view
205 set progresscoords {0 0}
206 adjustprogress
207 # set it blocking so we wait for the process to terminate
208 fconfigure $fd -blocking 1
209 if {[catch {close $fd} err]} {
210 set fv {}
211 if {$view != $curview} {
212 set fv " for the \"$viewname($view)\" view"
214 if {[string range $err 0 4] == "usage"} {
215 set err "Gitk: error reading commits$fv:\
216 bad arguments to git rev-list."
217 if {$viewname($view) eq "Command line"} {
218 append err \
219 " (Note: arguments to gitk are passed to git rev-list\
220 to allow selection of commits to be displayed.)"
222 } else {
223 set err "Error reading commits$fv: $err"
225 error_popup $err
227 if {$view == $curview} {
228 run chewcommits $view
230 return 0
232 set start 0
233 set gotsome 0
234 while 1 {
235 set i [string first "\0" $stuff $start]
236 if {$i < 0} {
237 append leftover($view) [string range $stuff $start end]
238 break
240 if {$start == 0} {
241 set cmit $leftover($view)
242 append cmit [string range $stuff 0 [expr {$i - 1}]]
243 set leftover($view) {}
244 } else {
245 set cmit [string range $stuff $start [expr {$i - 1}]]
247 set start [expr {$i + 1}]
248 set j [string first "\n" $cmit]
249 set ok 0
250 set listed 1
251 if {$j >= 0 && [string match "commit *" $cmit]} {
252 set ids [string range $cmit 7 [expr {$j - 1}]]
253 if {[string match {[-^<>]*} $ids]} {
254 switch -- [string index $ids 0] {
255 "-" {set listed 0}
256 "^" {set listed 2}
257 "<" {set listed 3}
258 ">" {set listed 4}
260 set ids [string range $ids 1 end]
262 set ok 1
263 foreach id $ids {
264 if {[string length $id] != 40} {
265 set ok 0
266 break
270 if {!$ok} {
271 set shortcmit $cmit
272 if {[string length $shortcmit] > 80} {
273 set shortcmit "[string range $shortcmit 0 80]..."
275 error_popup "[mc "Can't parse git log output:"] {$shortcmit}"
276 exit 1
278 set id [lindex $ids 0]
279 if {![info exists ordertok($view,$id)]} {
280 set otok "o[strrep $vnextroot($view)]"
281 incr vnextroot($view)
282 set ordertok($view,$id) $otok
283 } else {
284 set otok $ordertok($view,$id)
285 unset idpending($view,$id)
287 if {$listed} {
288 set olds [lrange $ids 1 end]
289 if {[llength $olds] == 1} {
290 set p [lindex $olds 0]
291 lappend children($view,$p) $id
292 if {![info exists ordertok($view,$p)]} {
293 set ordertok($view,$p) $ordertok($view,$id)
294 set idpending($view,$p) 1
296 } else {
297 set i 0
298 foreach p $olds {
299 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
300 lappend children($view,$p) $id
302 if {![info exists ordertok($view,$p)]} {
303 set ordertok($view,$p) "$otok[strrep $i]]"
304 set idpending($view,$p) 1
306 incr i
309 } else {
310 set olds {}
312 if {![info exists children($view,$id)]} {
313 set children($view,$id) {}
315 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
316 set commitrow($view,$id) $commitidx($view)
317 incr commitidx($view)
318 if {$view == $curview} {
319 lappend parentlist $olds
320 lappend displayorder $id
321 lappend commitlisted $listed
322 } else {
323 lappend vparentlist($view) $olds
324 lappend vdisporder($view) $id
325 lappend vcmitlisted($view) $listed
327 if {[info exists commitinterest($id)]} {
328 foreach script $commitinterest($id) {
329 eval [string map [list "%I" $id] $script]
331 unset commitinterest($id)
333 set gotsome 1
335 if {$gotsome} {
336 run chewcommits $view
337 if {$view == $curview} {
338 # update progress bar
339 global progressdirn progresscoords proglastnc
340 set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
341 set proglastnc $commitidx($view)
342 set l [lindex $progresscoords 0]
343 set r [lindex $progresscoords 1]
344 if {$progressdirn} {
345 set r [expr {$r + $inc}]
346 if {$r >= 1.0} {
347 set r 1.0
348 set progressdirn 0
350 if {$r > 0.2} {
351 set l [expr {$r - 0.2}]
353 } else {
354 set l [expr {$l - $inc}]
355 if {$l <= 0.0} {
356 set l 0.0
357 set progressdirn 1
359 set r [expr {$l + 0.2}]
361 set progresscoords [list $l $r]
362 adjustprogress
365 return 2
368 proc chewcommits {view} {
369 global curview hlview viewcomplete
370 global selectedline pending_select
372 if {$view == $curview} {
373 layoutmore
374 if {$viewcomplete($view)} {
375 global displayorder commitidx phase
376 global numcommits startmsecs
378 if {[info exists pending_select]} {
379 set row [first_real_row]
380 selectline $row 1
382 if {$commitidx($curview) > 0} {
383 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
384 #puts "overall $ms ms for $numcommits commits"
385 } else {
386 show_status [mc "No commits selected"]
388 notbusy layout
389 set phase {}
392 if {[info exists hlview] && $view == $hlview} {
393 vhighlightmore
395 return 0
398 proc readcommit {id} {
399 if {[catch {set contents [exec git cat-file commit $id]}]} return
400 parsecommit $id $contents 0
403 proc updatecommits {} {
404 global viewdata curview phase displayorder ordertok idpending
405 global children commitrow selectedline thickerline showneartags
406 global isworktree
408 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
410 if {$phase ne {}} {
411 stop_rev_list
412 set phase {}
414 set n $curview
415 foreach id $displayorder {
416 catch {unset children($n,$id)}
417 catch {unset commitrow($n,$id)}
418 catch {unset ordertok($n,$id)}
420 foreach vid [array names idpending "$n,*"] {
421 unset idpending($vid)
423 set curview -1
424 catch {unset selectedline}
425 catch {unset thickerline}
426 catch {unset viewdata($n)}
427 readrefs
428 changedrefs
429 if {$showneartags} {
430 getallcommits
432 showview $n
435 proc parsecommit {id contents listed} {
436 global commitinfo cdate
438 set inhdr 1
439 set comment {}
440 set headline {}
441 set auname {}
442 set audate {}
443 set comname {}
444 set comdate {}
445 set hdrend [string first "\n\n" $contents]
446 if {$hdrend < 0} {
447 # should never happen...
448 set hdrend [string length $contents]
450 set header [string range $contents 0 [expr {$hdrend - 1}]]
451 set comment [string range $contents [expr {$hdrend + 2}] end]
452 foreach line [split $header "\n"] {
453 set tag [lindex $line 0]
454 if {$tag == "author"} {
455 set audate [lindex $line end-1]
456 set auname [lrange $line 1 end-2]
457 } elseif {$tag == "committer"} {
458 set comdate [lindex $line end-1]
459 set comname [lrange $line 1 end-2]
462 set headline {}
463 # take the first non-blank line of the comment as the headline
464 set headline [string trimleft $comment]
465 set i [string first "\n" $headline]
466 if {$i >= 0} {
467 set headline [string range $headline 0 $i]
469 set headline [string trimright $headline]
470 set i [string first "\r" $headline]
471 if {$i >= 0} {
472 set headline [string trimright [string range $headline 0 $i]]
474 if {!$listed} {
475 # git rev-list indents the comment by 4 spaces;
476 # if we got this via git cat-file, add the indentation
477 set newcomment {}
478 foreach line [split $comment "\n"] {
479 append newcomment " "
480 append newcomment $line
481 append newcomment "\n"
483 set comment $newcomment
485 if {$comdate != {}} {
486 set cdate($id) $comdate
488 set commitinfo($id) [list $headline $auname $audate \
489 $comname $comdate $comment]
492 proc getcommit {id} {
493 global commitdata commitinfo
495 if {[info exists commitdata($id)]} {
496 parsecommit $id $commitdata($id) 1
497 } else {
498 readcommit $id
499 if {![info exists commitinfo($id)]} {
500 set commitinfo($id) [list [mc "No commit information available"]]
503 return 1
506 proc readrefs {} {
507 global tagids idtags headids idheads tagobjid
508 global otherrefids idotherrefs mainhead mainheadid
510 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
511 catch {unset $v}
513 set refd [open [list | git show-ref -d] r]
514 while {[gets $refd line] >= 0} {
515 if {[string index $line 40] ne " "} continue
516 set id [string range $line 0 39]
517 set ref [string range $line 41 end]
518 if {![string match "refs/*" $ref]} continue
519 set name [string range $ref 5 end]
520 if {[string match "remotes/*" $name]} {
521 if {![string match "*/HEAD" $name]} {
522 set headids($name) $id
523 lappend idheads($id) $name
525 } elseif {[string match "heads/*" $name]} {
526 set name [string range $name 6 end]
527 set headids($name) $id
528 lappend idheads($id) $name
529 } elseif {[string match "tags/*" $name]} {
530 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
531 # which is what we want since the former is the commit ID
532 set name [string range $name 5 end]
533 if {[string match "*^{}" $name]} {
534 set name [string range $name 0 end-3]
535 } else {
536 set tagobjid($name) $id
538 set tagids($name) $id
539 lappend idtags($id) $name
540 } else {
541 set otherrefids($name) $id
542 lappend idotherrefs($id) $name
545 catch {close $refd}
546 set mainhead {}
547 set mainheadid {}
548 catch {
549 set thehead [exec git symbolic-ref HEAD]
550 if {[string match "refs/heads/*" $thehead]} {
551 set mainhead [string range $thehead 11 end]
552 if {[info exists headids($mainhead)]} {
553 set mainheadid $headids($mainhead)
559 # skip over fake commits
560 proc first_real_row {} {
561 global nullid nullid2 displayorder numcommits
563 for {set row 0} {$row < $numcommits} {incr row} {
564 set id [lindex $displayorder $row]
565 if {$id ne $nullid && $id ne $nullid2} {
566 break
569 return $row
572 # update things for a head moved to a child of its previous location
573 proc movehead {id name} {
574 global headids idheads
576 removehead $headids($name) $name
577 set headids($name) $id
578 lappend idheads($id) $name
581 # update things when a head has been removed
582 proc removehead {id name} {
583 global headids idheads
585 if {$idheads($id) eq $name} {
586 unset idheads($id)
587 } else {
588 set i [lsearch -exact $idheads($id) $name]
589 if {$i >= 0} {
590 set idheads($id) [lreplace $idheads($id) $i $i]
593 unset headids($name)
596 proc show_error {w top msg} {
597 message $w.m -text $msg -justify center -aspect 400
598 pack $w.m -side top -fill x -padx 20 -pady 20
599 button $w.ok -text [mc OK] -command "destroy $top"
600 pack $w.ok -side bottom -fill x
601 bind $top <Visibility> "grab $top; focus $top"
602 bind $top <Key-Return> "destroy $top"
603 tkwait window $top
606 proc error_popup msg {
607 set w .error
608 toplevel $w
609 wm transient $w .
610 show_error $w $w $msg
613 proc confirm_popup msg {
614 global confirm_ok
615 set confirm_ok 0
616 set w .confirm
617 toplevel $w
618 wm transient $w .
619 message $w.m -text $msg -justify center -aspect 400
620 pack $w.m -side top -fill x -padx 20 -pady 20
621 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
622 pack $w.ok -side left -fill x
623 button $w.cancel -text [mc Cancel] -command "destroy $w"
624 pack $w.cancel -side right -fill x
625 bind $w <Visibility> "grab $w; focus $w"
626 tkwait window $w
627 return $confirm_ok
630 proc setoptions {} {
631 option add *Panedwindow.showHandle 1 startupFile
632 option add *Panedwindow.sashRelief raised startupFile
633 option add *Button.font uifont startupFile
634 option add *Checkbutton.font uifont startupFile
635 option add *Radiobutton.font uifont startupFile
636 option add *Menu.font uifont startupFile
637 option add *Menubutton.font uifont startupFile
638 option add *Label.font uifont startupFile
639 option add *Message.font uifont startupFile
640 option add *Entry.font uifont startupFile
643 proc makewindow {} {
644 global canv canv2 canv3 linespc charspc ctext cflist
645 global tabstop
646 global findtype findtypemenu findloc findstring fstring geometry
647 global entries sha1entry sha1string sha1but
648 global diffcontextstring diffcontext
649 global ignorespace
650 global maincursor textcursor curtextcursor
651 global rowctxmenu fakerowmenu mergemax wrapcomment
652 global highlight_files gdttype
653 global searchstring sstring
654 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
655 global headctxmenu progresscanv progressitem progresscoords statusw
656 global fprogitem fprogcoord lastprogupdate progupdatepending
657 global rprogitem rprogcoord
658 global have_tk85
660 menu .bar
661 .bar add cascade -label [mc "File"] -menu .bar.file
662 menu .bar.file
663 .bar.file add command -label [mc "Update"] -command updatecommits
664 .bar.file add command -label [mc "Reread references"] -command rereadrefs
665 .bar.file add command -label [mc "List references"] -command showrefs
666 .bar.file add command -label [mc "Quit"] -command doquit
667 menu .bar.edit
668 .bar add cascade -label [mc "Edit"] -menu .bar.edit
669 .bar.edit add command -label [mc "Preferences"] -command doprefs
671 menu .bar.view
672 .bar add cascade -label [mc "View"] -menu .bar.view
673 .bar.view add command -label [mc "New view..."] -command {newview 0}
674 .bar.view add command -label [mc "Edit view..."] -command editview \
675 -state disabled
676 .bar.view add command -label [mc "Delete view"] -command delview -state disabled
677 .bar.view add separator
678 .bar.view add radiobutton -label [mc "All files"] -command {showview 0} \
679 -variable selectedview -value 0
681 menu .bar.help
682 .bar add cascade -label [mc "Help"] -menu .bar.help
683 .bar.help add command -label [mc "About gitk"] -command about
684 .bar.help add command -label [mc "Key bindings"] -command keys
685 .bar.help configure
686 . configure -menu .bar
688 # the gui has upper and lower half, parts of a paned window.
689 panedwindow .ctop -orient vertical
691 # possibly use assumed geometry
692 if {![info exists geometry(pwsash0)]} {
693 set geometry(topheight) [expr {15 * $linespc}]
694 set geometry(topwidth) [expr {80 * $charspc}]
695 set geometry(botheight) [expr {15 * $linespc}]
696 set geometry(botwidth) [expr {50 * $charspc}]
697 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
698 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
701 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
702 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
703 frame .tf.histframe
704 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
706 # create three canvases
707 set cscroll .tf.histframe.csb
708 set canv .tf.histframe.pwclist.canv
709 canvas $canv \
710 -selectbackground $selectbgcolor \
711 -background $bgcolor -bd 0 \
712 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
713 .tf.histframe.pwclist add $canv
714 set canv2 .tf.histframe.pwclist.canv2
715 canvas $canv2 \
716 -selectbackground $selectbgcolor \
717 -background $bgcolor -bd 0 -yscrollincr $linespc
718 .tf.histframe.pwclist add $canv2
719 set canv3 .tf.histframe.pwclist.canv3
720 canvas $canv3 \
721 -selectbackground $selectbgcolor \
722 -background $bgcolor -bd 0 -yscrollincr $linespc
723 .tf.histframe.pwclist add $canv3
724 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
725 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
727 # a scroll bar to rule them
728 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
729 pack $cscroll -side right -fill y
730 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
731 lappend bglist $canv $canv2 $canv3
732 pack .tf.histframe.pwclist -fill both -expand 1 -side left
734 # we have two button bars at bottom of top frame. Bar 1
735 frame .tf.bar
736 frame .tf.lbar -height 15
738 set sha1entry .tf.bar.sha1
739 set entries $sha1entry
740 set sha1but .tf.bar.sha1label
741 button $sha1but -text [mc "SHA1 ID: "] -state disabled -relief flat \
742 -command gotocommit -width 8
743 $sha1but conf -disabledforeground [$sha1but cget -foreground]
744 pack .tf.bar.sha1label -side left
745 entry $sha1entry -width 40 -font textfont -textvariable sha1string
746 trace add variable sha1string write sha1change
747 pack $sha1entry -side left -pady 2
749 image create bitmap bm-left -data {
750 #define left_width 16
751 #define left_height 16
752 static unsigned char left_bits[] = {
753 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
754 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
755 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
757 image create bitmap bm-right -data {
758 #define right_width 16
759 #define right_height 16
760 static unsigned char right_bits[] = {
761 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
762 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
763 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
765 button .tf.bar.leftbut -image bm-left -command goback \
766 -state disabled -width 26
767 pack .tf.bar.leftbut -side left -fill y
768 button .tf.bar.rightbut -image bm-right -command goforw \
769 -state disabled -width 26
770 pack .tf.bar.rightbut -side left -fill y
772 # Status label and progress bar
773 set statusw .tf.bar.status
774 label $statusw -width 15 -relief sunken
775 pack $statusw -side left -padx 5
776 set h [expr {[font metrics uifont -linespace] + 2}]
777 set progresscanv .tf.bar.progress
778 canvas $progresscanv -relief sunken -height $h -borderwidth 2
779 set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
780 set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
781 set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
782 pack $progresscanv -side right -expand 1 -fill x
783 set progresscoords {0 0}
784 set fprogcoord 0
785 set rprogcoord 0
786 bind $progresscanv <Configure> adjustprogress
787 set lastprogupdate [clock clicks -milliseconds]
788 set progupdatepending 0
790 # build up the bottom bar of upper window
791 label .tf.lbar.flabel -text "[mc "Find"] "
792 button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
793 button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
794 label .tf.lbar.flab2 -text " [mc "commit"] "
795 pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
796 -side left -fill y
797 set gdttype [mc "containing:"]
798 set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
799 [mc "containing:"] \
800 [mc "touching paths:"] \
801 [mc "adding/removing string:"]]
802 trace add variable gdttype write gdttype_change
803 pack .tf.lbar.gdttype -side left -fill y
805 set findstring {}
806 set fstring .tf.lbar.findstring
807 lappend entries $fstring
808 entry $fstring -width 30 -font textfont -textvariable findstring
809 trace add variable findstring write find_change
810 set findtype [mc "Exact"]
811 set findtypemenu [tk_optionMenu .tf.lbar.findtype \
812 findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
813 trace add variable findtype write findcom_change
814 set findloc [mc "All fields"]
815 tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
816 [mc "Comments"] [mc "Author"] [mc "Committer"]
817 trace add variable findloc write find_change
818 pack .tf.lbar.findloc -side right
819 pack .tf.lbar.findtype -side right
820 pack $fstring -side left -expand 1 -fill x
822 # Finish putting the upper half of the viewer together
823 pack .tf.lbar -in .tf -side bottom -fill x
824 pack .tf.bar -in .tf -side bottom -fill x
825 pack .tf.histframe -fill both -side top -expand 1
826 .ctop add .tf
827 .ctop paneconfigure .tf -height $geometry(topheight)
828 .ctop paneconfigure .tf -width $geometry(topwidth)
830 # now build up the bottom
831 panedwindow .pwbottom -orient horizontal
833 # lower left, a text box over search bar, scroll bar to the right
834 # if we know window height, then that will set the lower text height, otherwise
835 # we set lower text height which will drive window height
836 if {[info exists geometry(main)]} {
837 frame .bleft -width $geometry(botwidth)
838 } else {
839 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
841 frame .bleft.top
842 frame .bleft.mid
843 frame .bleft.bottom
845 button .bleft.top.search -text [mc "Search"] -command dosearch
846 pack .bleft.top.search -side left -padx 5
847 set sstring .bleft.top.sstring
848 entry $sstring -width 20 -font textfont -textvariable searchstring
849 lappend entries $sstring
850 trace add variable searchstring write incrsearch
851 pack $sstring -side left -expand 1 -fill x
852 radiobutton .bleft.mid.diff -text [mc "Diff"] \
853 -command changediffdisp -variable diffelide -value {0 0}
854 radiobutton .bleft.mid.old -text [mc "Old version"] \
855 -command changediffdisp -variable diffelide -value {0 1}
856 radiobutton .bleft.mid.new -text [mc "New version"] \
857 -command changediffdisp -variable diffelide -value {1 0}
858 label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: "
859 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
860 spinbox .bleft.mid.diffcontext -width 5 -font textfont \
861 -from 1 -increment 1 -to 10000000 \
862 -validate all -validatecommand "diffcontextvalidate %P" \
863 -textvariable diffcontextstring
864 .bleft.mid.diffcontext set $diffcontext
865 trace add variable diffcontextstring write diffcontextchange
866 lappend entries .bleft.mid.diffcontext
867 pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
868 checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
869 -command changeignorespace -variable ignorespace
870 pack .bleft.mid.ignspace -side left -padx 5
871 set ctext .bleft.bottom.ctext
872 text $ctext -background $bgcolor -foreground $fgcolor \
873 -state disabled -font textfont \
874 -yscrollcommand scrolltext -wrap none \
875 -xscrollcommand ".bleft.bottom.sbhorizontal set"
876 if {$have_tk85} {
877 $ctext conf -tabstyle wordprocessor
879 scrollbar .bleft.bottom.sb -command "$ctext yview"
880 scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
881 -width 10
882 pack .bleft.top -side top -fill x
883 pack .bleft.mid -side top -fill x
884 grid $ctext .bleft.bottom.sb -sticky nsew
885 grid .bleft.bottom.sbhorizontal -sticky ew
886 grid columnconfigure .bleft.bottom 0 -weight 1
887 grid rowconfigure .bleft.bottom 0 -weight 1
888 grid rowconfigure .bleft.bottom 1 -weight 0
889 pack .bleft.bottom -side top -fill both -expand 1
890 lappend bglist $ctext
891 lappend fglist $ctext
893 $ctext tag conf comment -wrap $wrapcomment
894 $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
895 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
896 $ctext tag conf d0 -fore [lindex $diffcolors 0]
897 $ctext tag conf d1 -fore [lindex $diffcolors 1]
898 $ctext tag conf m0 -fore red
899 $ctext tag conf m1 -fore blue
900 $ctext tag conf m2 -fore green
901 $ctext tag conf m3 -fore purple
902 $ctext tag conf m4 -fore brown
903 $ctext tag conf m5 -fore "#009090"
904 $ctext tag conf m6 -fore magenta
905 $ctext tag conf m7 -fore "#808000"
906 $ctext tag conf m8 -fore "#009000"
907 $ctext tag conf m9 -fore "#ff0080"
908 $ctext tag conf m10 -fore cyan
909 $ctext tag conf m11 -fore "#b07070"
910 $ctext tag conf m12 -fore "#70b0f0"
911 $ctext tag conf m13 -fore "#70f0b0"
912 $ctext tag conf m14 -fore "#f0b070"
913 $ctext tag conf m15 -fore "#ff70b0"
914 $ctext tag conf mmax -fore darkgrey
915 set mergemax 16
916 $ctext tag conf mresult -font textfontbold
917 $ctext tag conf msep -font textfontbold
918 $ctext tag conf found -back yellow
920 .pwbottom add .bleft
921 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
923 # lower right
924 frame .bright
925 frame .bright.mode
926 radiobutton .bright.mode.patch -text [mc "Patch"] \
927 -command reselectline -variable cmitmode -value "patch"
928 radiobutton .bright.mode.tree -text [mc "Tree"] \
929 -command reselectline -variable cmitmode -value "tree"
930 grid .bright.mode.patch .bright.mode.tree -sticky ew
931 pack .bright.mode -side top -fill x
932 set cflist .bright.cfiles
933 set indent [font measure mainfont "nn"]
934 text $cflist \
935 -selectbackground $selectbgcolor \
936 -background $bgcolor -foreground $fgcolor \
937 -font mainfont \
938 -tabs [list $indent [expr {2 * $indent}]] \
939 -yscrollcommand ".bright.sb set" \
940 -cursor [. cget -cursor] \
941 -spacing1 1 -spacing3 1
942 lappend bglist $cflist
943 lappend fglist $cflist
944 scrollbar .bright.sb -command "$cflist yview"
945 pack .bright.sb -side right -fill y
946 pack $cflist -side left -fill both -expand 1
947 $cflist tag configure highlight \
948 -background [$cflist cget -selectbackground]
949 $cflist tag configure bold -font mainfontbold
951 .pwbottom add .bright
952 .ctop add .pwbottom
954 # restore window width & height if known
955 if {[info exists geometry(main)]} {
956 if {[scan $geometry(main) "%dx%d" w h] >= 2} {
957 if {$w > [winfo screenwidth .]} {
958 set w [winfo screenwidth .]
960 if {$h > [winfo screenheight .]} {
961 set h [winfo screenheight .]
963 wm geometry . "${w}x$h"
967 if {[tk windowingsystem] eq {aqua}} {
968 set M1B M1
969 } else {
970 set M1B Control
973 bind .pwbottom <Configure> {resizecdetpanes %W %w}
974 pack .ctop -fill both -expand 1
975 bindall <1> {selcanvline %W %x %y}
976 #bindall <B1-Motion> {selcanvline %W %x %y}
977 if {[tk windowingsystem] == "win32"} {
978 bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
979 bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
980 } else {
981 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
982 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
983 if {[tk windowingsystem] eq "aqua"} {
984 bindall <MouseWheel> {
985 set delta [expr {- (%D)}]
986 allcanvs yview scroll $delta units
990 bindall <2> "canvscan mark %W %x %y"
991 bindall <B2-Motion> "canvscan dragto %W %x %y"
992 bindkey <Home> selfirstline
993 bindkey <End> sellastline
994 bind . <Key-Up> "selnextline -1"
995 bind . <Key-Down> "selnextline 1"
996 bind . <Shift-Key-Up> "dofind -1 0"
997 bind . <Shift-Key-Down> "dofind 1 0"
998 bindkey <Key-Right> "goforw"
999 bindkey <Key-Left> "goback"
1000 bind . <Key-Prior> "selnextpage -1"
1001 bind . <Key-Next> "selnextpage 1"
1002 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
1003 bind . <$M1B-End> "allcanvs yview moveto 1.0"
1004 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
1005 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
1006 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
1007 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
1008 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
1009 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
1010 bindkey <Key-space> "$ctext yview scroll 1 pages"
1011 bindkey p "selnextline -1"
1012 bindkey n "selnextline 1"
1013 bindkey z "goback"
1014 bindkey x "goforw"
1015 bindkey i "selnextline -1"
1016 bindkey k "selnextline 1"
1017 bindkey j "goback"
1018 bindkey l "goforw"
1019 bindkey b prevfile
1020 bindkey d "$ctext yview scroll 18 units"
1021 bindkey u "$ctext yview scroll -18 units"
1022 bindkey / {dofind 1 1}
1023 bindkey <Key-Return> {dofind 1 1}
1024 bindkey ? {dofind -1 1}
1025 bindkey f nextfile
1026 bindkey <F5> updatecommits
1027 bind . <$M1B-q> doquit
1028 bind . <$M1B-f> {dofind 1 1}
1029 bind . <$M1B-g> {dofind 1 0}
1030 bind . <$M1B-r> dosearchback
1031 bind . <$M1B-s> dosearch
1032 bind . <$M1B-equal> {incrfont 1}
1033 bind . <$M1B-plus> {incrfont 1}
1034 bind . <$M1B-KP_Add> {incrfont 1}
1035 bind . <$M1B-minus> {incrfont -1}
1036 bind . <$M1B-KP_Subtract> {incrfont -1}
1037 wm protocol . WM_DELETE_WINDOW doquit
1038 bind . <Button-1> "click %W"
1039 bind $fstring <Key-Return> {dofind 1 1}
1040 bind $sha1entry <Key-Return> gotocommit
1041 bind $sha1entry <<PasteSelection>> clearsha1
1042 bind $cflist <1> {sel_flist %W %x %y; break}
1043 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
1044 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
1045 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
1047 set maincursor [. cget -cursor]
1048 set textcursor [$ctext cget -cursor]
1049 set curtextcursor $textcursor
1051 set rowctxmenu .rowctxmenu
1052 menu $rowctxmenu -tearoff 0
1053 $rowctxmenu add command -label [mc "Diff this -> selected"] \
1054 -command {diffvssel 0}
1055 $rowctxmenu add command -label [mc "Diff selected -> this"] \
1056 -command {diffvssel 1}
1057 $rowctxmenu add command -label [mc "Make patch"] -command mkpatch
1058 $rowctxmenu add command -label [mc "Create tag"] -command mktag
1059 $rowctxmenu add command -label [mc "Write commit to file"] -command writecommit
1060 $rowctxmenu add command -label [mc "Create new branch"] -command mkbranch
1061 $rowctxmenu add command -label [mc "Cherry-pick this commit"] \
1062 -command cherrypick
1063 $rowctxmenu add command -label [mc "Reset HEAD branch to here"] \
1064 -command resethead
1066 set fakerowmenu .fakerowmenu
1067 menu $fakerowmenu -tearoff 0
1068 $fakerowmenu add command -label [mc "Diff this -> selected"] \
1069 -command {diffvssel 0}
1070 $fakerowmenu add command -label [mc "Diff selected -> this"] \
1071 -command {diffvssel 1}
1072 $fakerowmenu add command -label [mc "Make patch"] -command mkpatch
1073 # $fakerowmenu add command -label [mc "Commit"] -command {mkcommit 0}
1074 # $fakerowmenu add command -label [mc "Commit all"] -command {mkcommit 1}
1075 # $fakerowmenu add command -label [mc "Revert local changes"] -command revertlocal
1077 set headctxmenu .headctxmenu
1078 menu $headctxmenu -tearoff 0
1079 $headctxmenu add command -label [mc "Check out this branch"] \
1080 -command cobranch
1081 $headctxmenu add command -label [mc "Remove this branch"] \
1082 -command rmbranch
1084 global flist_menu
1085 set flist_menu .flistctxmenu
1086 menu $flist_menu -tearoff 0
1087 $flist_menu add command -label [mc "Highlight this too"] \
1088 -command {flist_hl 0}
1089 $flist_menu add command -label [mc "Highlight this only"] \
1090 -command {flist_hl 1}
1091 $flist_menu add command -label [mc "External diff"] \
1092 -command {external_diff}
1095 # Windows sends all mouse wheel events to the current focused window, not
1096 # the one where the mouse hovers, so bind those events here and redirect
1097 # to the correct window
1098 proc windows_mousewheel_redirector {W X Y D} {
1099 global canv canv2 canv3
1100 set w [winfo containing -displayof $W $X $Y]
1101 if {$w ne ""} {
1102 set u [expr {$D < 0 ? 5 : -5}]
1103 if {$w == $canv || $w == $canv2 || $w == $canv3} {
1104 allcanvs yview scroll $u units
1105 } else {
1106 catch {
1107 $w yview scroll $u units
1113 # mouse-2 makes all windows scan vertically, but only the one
1114 # the cursor is in scans horizontally
1115 proc canvscan {op w x y} {
1116 global canv canv2 canv3
1117 foreach c [list $canv $canv2 $canv3] {
1118 if {$c == $w} {
1119 $c scan $op $x $y
1120 } else {
1121 $c scan $op 0 $y
1126 proc scrollcanv {cscroll f0 f1} {
1127 $cscroll set $f0 $f1
1128 drawfrac $f0 $f1
1129 flushhighlights
1132 # when we make a key binding for the toplevel, make sure
1133 # it doesn't get triggered when that key is pressed in the
1134 # find string entry widget.
1135 proc bindkey {ev script} {
1136 global entries
1137 bind . $ev $script
1138 set escript [bind Entry $ev]
1139 if {$escript == {}} {
1140 set escript [bind Entry <Key>]
1142 foreach e $entries {
1143 bind $e $ev "$escript; break"
1147 # set the focus back to the toplevel for any click outside
1148 # the entry widgets
1149 proc click {w} {
1150 global ctext entries
1151 foreach e [concat $entries $ctext] {
1152 if {$w == $e} return
1154 focus .
1157 # Adjust the progress bar for a change in requested extent or canvas size
1158 proc adjustprogress {} {
1159 global progresscanv progressitem progresscoords
1160 global fprogitem fprogcoord lastprogupdate progupdatepending
1161 global rprogitem rprogcoord
1163 set w [expr {[winfo width $progresscanv] - 4}]
1164 set x0 [expr {$w * [lindex $progresscoords 0]}]
1165 set x1 [expr {$w * [lindex $progresscoords 1]}]
1166 set h [winfo height $progresscanv]
1167 $progresscanv coords $progressitem $x0 0 $x1 $h
1168 $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
1169 $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
1170 set now [clock clicks -milliseconds]
1171 if {$now >= $lastprogupdate + 100} {
1172 set progupdatepending 0
1173 update
1174 } elseif {!$progupdatepending} {
1175 set progupdatepending 1
1176 after [expr {$lastprogupdate + 100 - $now}] doprogupdate
1180 proc doprogupdate {} {
1181 global lastprogupdate progupdatepending
1183 if {$progupdatepending} {
1184 set progupdatepending 0
1185 set lastprogupdate [clock clicks -milliseconds]
1186 update
1190 proc savestuff {w} {
1191 global canv canv2 canv3 mainfont textfont uifont tabstop
1192 global stuffsaved findmergefiles maxgraphpct
1193 global maxwidth showneartags showlocalchanges
1194 global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
1195 global cmitmode wrapcomment datetimeformat limitdiffs
1196 global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
1197 global autoselect extdifftool
1199 if {$stuffsaved} return
1200 if {![winfo viewable .]} return
1201 catch {
1202 set f [open "~/.gitk-new" w]
1203 puts $f [list set mainfont $mainfont]
1204 puts $f [list set textfont $textfont]
1205 puts $f [list set uifont $uifont]
1206 puts $f [list set tabstop $tabstop]
1207 puts $f [list set findmergefiles $findmergefiles]
1208 puts $f [list set maxgraphpct $maxgraphpct]
1209 puts $f [list set maxwidth $maxwidth]
1210 puts $f [list set cmitmode $cmitmode]
1211 puts $f [list set wrapcomment $wrapcomment]
1212 puts $f [list set autoselect $autoselect]
1213 puts $f [list set showneartags $showneartags]
1214 puts $f [list set showlocalchanges $showlocalchanges]
1215 puts $f [list set datetimeformat $datetimeformat]
1216 puts $f [list set limitdiffs $limitdiffs]
1217 puts $f [list set bgcolor $bgcolor]
1218 puts $f [list set fgcolor $fgcolor]
1219 puts $f [list set colors $colors]
1220 puts $f [list set diffcolors $diffcolors]
1221 puts $f [list set diffcontext $diffcontext]
1222 puts $f [list set selectbgcolor $selectbgcolor]
1223 puts $f [list set extdifftool $extdifftool]
1225 puts $f "set geometry(main) [wm geometry .]"
1226 puts $f "set geometry(topwidth) [winfo width .tf]"
1227 puts $f "set geometry(topheight) [winfo height .tf]"
1228 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1229 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1230 puts $f "set geometry(botwidth) [winfo width .bleft]"
1231 puts $f "set geometry(botheight) [winfo height .bleft]"
1233 puts -nonewline $f "set permviews {"
1234 for {set v 0} {$v < $nextviewnum} {incr v} {
1235 if {$viewperm($v)} {
1236 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v) $viewargscmd($v)]}"
1239 puts $f "}"
1240 close $f
1241 file rename -force "~/.gitk-new" "~/.gitk"
1243 set stuffsaved 1
1246 proc resizeclistpanes {win w} {
1247 global oldwidth
1248 if {[info exists oldwidth($win)]} {
1249 set s0 [$win sash coord 0]
1250 set s1 [$win sash coord 1]
1251 if {$w < 60} {
1252 set sash0 [expr {int($w/2 - 2)}]
1253 set sash1 [expr {int($w*5/6 - 2)}]
1254 } else {
1255 set factor [expr {1.0 * $w / $oldwidth($win)}]
1256 set sash0 [expr {int($factor * [lindex $s0 0])}]
1257 set sash1 [expr {int($factor * [lindex $s1 0])}]
1258 if {$sash0 < 30} {
1259 set sash0 30
1261 if {$sash1 < $sash0 + 20} {
1262 set sash1 [expr {$sash0 + 20}]
1264 if {$sash1 > $w - 10} {
1265 set sash1 [expr {$w - 10}]
1266 if {$sash0 > $sash1 - 20} {
1267 set sash0 [expr {$sash1 - 20}]
1271 $win sash place 0 $sash0 [lindex $s0 1]
1272 $win sash place 1 $sash1 [lindex $s1 1]
1274 set oldwidth($win) $w
1277 proc resizecdetpanes {win w} {
1278 global oldwidth
1279 if {[info exists oldwidth($win)]} {
1280 set s0 [$win sash coord 0]
1281 if {$w < 60} {
1282 set sash0 [expr {int($w*3/4 - 2)}]
1283 } else {
1284 set factor [expr {1.0 * $w / $oldwidth($win)}]
1285 set sash0 [expr {int($factor * [lindex $s0 0])}]
1286 if {$sash0 < 45} {
1287 set sash0 45
1289 if {$sash0 > $w - 15} {
1290 set sash0 [expr {$w - 15}]
1293 $win sash place 0 $sash0 [lindex $s0 1]
1295 set oldwidth($win) $w
1298 proc allcanvs args {
1299 global canv canv2 canv3
1300 eval $canv $args
1301 eval $canv2 $args
1302 eval $canv3 $args
1305 proc bindall {event action} {
1306 global canv canv2 canv3
1307 bind $canv $event $action
1308 bind $canv2 $event $action
1309 bind $canv3 $event $action
1312 proc about {} {
1313 global uifont
1314 set w .about
1315 if {[winfo exists $w]} {
1316 raise $w
1317 return
1319 toplevel $w
1320 wm title $w [mc "About gitk"]
1321 message $w.m -text [mc "
1322 Gitk - a commit viewer for git
1324 Copyright © 2005-2006 Paul Mackerras
1326 Use and redistribute under the terms of the GNU General Public License"] \
1327 -justify center -aspect 400 -border 2 -bg white -relief groove
1328 pack $w.m -side top -fill x -padx 2 -pady 2
1329 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
1330 pack $w.ok -side bottom
1331 bind $w <Visibility> "focus $w.ok"
1332 bind $w <Key-Escape> "destroy $w"
1333 bind $w <Key-Return> "destroy $w"
1336 proc keys {} {
1337 set w .keys
1338 if {[winfo exists $w]} {
1339 raise $w
1340 return
1342 if {[tk windowingsystem] eq {aqua}} {
1343 set M1T Cmd
1344 } else {
1345 set M1T Ctrl
1347 toplevel $w
1348 wm title $w [mc "Gitk key bindings"]
1349 message $w.m -text "
1350 [mc "Gitk key bindings:"]
1352 [mc "<%s-Q> Quit" $M1T]
1353 [mc "<Home> Move to first commit"]
1354 [mc "<End> Move to last commit"]
1355 [mc "<Up>, p, i Move up one commit"]
1356 [mc "<Down>, n, k Move down one commit"]
1357 [mc "<Left>, z, j Go back in history list"]
1358 [mc "<Right>, x, l Go forward in history list"]
1359 [mc "<PageUp> Move up one page in commit list"]
1360 [mc "<PageDown> Move down one page in commit list"]
1361 [mc "<%s-Home> Scroll to top of commit list" $M1T]
1362 [mc "<%s-End> Scroll to bottom of commit list" $M1T]
1363 [mc "<%s-Up> Scroll commit list up one line" $M1T]
1364 [mc "<%s-Down> Scroll commit list down one line" $M1T]
1365 [mc "<%s-PageUp> Scroll commit list up one page" $M1T]
1366 [mc "<%s-PageDown> Scroll commit list down one page" $M1T]
1367 [mc "<Shift-Up> Find backwards (upwards, later commits)"]
1368 [mc "<Shift-Down> Find forwards (downwards, earlier commits)"]
1369 [mc "<Delete>, b Scroll diff view up one page"]
1370 [mc "<Backspace> Scroll diff view up one page"]
1371 [mc "<Space> Scroll diff view down one page"]
1372 [mc "u Scroll diff view up 18 lines"]
1373 [mc "d Scroll diff view down 18 lines"]
1374 [mc "<%s-F> Find" $M1T]
1375 [mc "<%s-G> Move to next find hit" $M1T]
1376 [mc "<Return> Move to next find hit"]
1377 [mc "/ Move to next find hit, or redo find"]
1378 [mc "? Move to previous find hit"]
1379 [mc "f Scroll diff view to next file"]
1380 [mc "<%s-S> Search for next hit in diff view" $M1T]
1381 [mc "<%s-R> Search for previous hit in diff view" $M1T]
1382 [mc "<%s-KP+> Increase font size" $M1T]
1383 [mc "<%s-plus> Increase font size" $M1T]
1384 [mc "<%s-KP-> Decrease font size" $M1T]
1385 [mc "<%s-minus> Decrease font size" $M1T]
1386 [mc "<F5> Update"]
1388 -justify left -bg white -border 2 -relief groove
1389 pack $w.m -side top -fill both -padx 2 -pady 2
1390 button $w.ok -text [mc "Close"] -command "destroy $w" -default active
1391 pack $w.ok -side bottom
1392 bind $w <Visibility> "focus $w.ok"
1393 bind $w <Key-Escape> "destroy $w"
1394 bind $w <Key-Return> "destroy $w"
1397 # Procedures for manipulating the file list window at the
1398 # bottom right of the overall window.
1400 proc treeview {w l openlevs} {
1401 global treecontents treediropen treeheight treeparent treeindex
1403 set ix 0
1404 set treeindex() 0
1405 set lev 0
1406 set prefix {}
1407 set prefixend -1
1408 set prefendstack {}
1409 set htstack {}
1410 set ht 0
1411 set treecontents() {}
1412 $w conf -state normal
1413 foreach f $l {
1414 while {[string range $f 0 $prefixend] ne $prefix} {
1415 if {$lev <= $openlevs} {
1416 $w mark set e:$treeindex($prefix) "end -1c"
1417 $w mark gravity e:$treeindex($prefix) left
1419 set treeheight($prefix) $ht
1420 incr ht [lindex $htstack end]
1421 set htstack [lreplace $htstack end end]
1422 set prefixend [lindex $prefendstack end]
1423 set prefendstack [lreplace $prefendstack end end]
1424 set prefix [string range $prefix 0 $prefixend]
1425 incr lev -1
1427 set tail [string range $f [expr {$prefixend+1}] end]
1428 while {[set slash [string first "/" $tail]] >= 0} {
1429 lappend htstack $ht
1430 set ht 0
1431 lappend prefendstack $prefixend
1432 incr prefixend [expr {$slash + 1}]
1433 set d [string range $tail 0 $slash]
1434 lappend treecontents($prefix) $d
1435 set oldprefix $prefix
1436 append prefix $d
1437 set treecontents($prefix) {}
1438 set treeindex($prefix) [incr ix]
1439 set treeparent($prefix) $oldprefix
1440 set tail [string range $tail [expr {$slash+1}] end]
1441 if {$lev <= $openlevs} {
1442 set ht 1
1443 set treediropen($prefix) [expr {$lev < $openlevs}]
1444 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1445 $w mark set d:$ix "end -1c"
1446 $w mark gravity d:$ix left
1447 set str "\n"
1448 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1449 $w insert end $str
1450 $w image create end -align center -image $bm -padx 1 \
1451 -name a:$ix
1452 $w insert end $d [highlight_tag $prefix]
1453 $w mark set s:$ix "end -1c"
1454 $w mark gravity s:$ix left
1456 incr lev
1458 if {$tail ne {}} {
1459 if {$lev <= $openlevs} {
1460 incr ht
1461 set str "\n"
1462 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1463 $w insert end $str
1464 $w insert end $tail [highlight_tag $f]
1466 lappend treecontents($prefix) $tail
1469 while {$htstack ne {}} {
1470 set treeheight($prefix) $ht
1471 incr ht [lindex $htstack end]
1472 set htstack [lreplace $htstack end end]
1473 set prefixend [lindex $prefendstack end]
1474 set prefendstack [lreplace $prefendstack end end]
1475 set prefix [string range $prefix 0 $prefixend]
1477 $w conf -state disabled
1480 proc linetoelt {l} {
1481 global treeheight treecontents
1483 set y 2
1484 set prefix {}
1485 while {1} {
1486 foreach e $treecontents($prefix) {
1487 if {$y == $l} {
1488 return "$prefix$e"
1490 set n 1
1491 if {[string index $e end] eq "/"} {
1492 set n $treeheight($prefix$e)
1493 if {$y + $n > $l} {
1494 append prefix $e
1495 incr y
1496 break
1499 incr y $n
1504 proc highlight_tree {y prefix} {
1505 global treeheight treecontents cflist
1507 foreach e $treecontents($prefix) {
1508 set path $prefix$e
1509 if {[highlight_tag $path] ne {}} {
1510 $cflist tag add bold $y.0 "$y.0 lineend"
1512 incr y
1513 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1514 set y [highlight_tree $y $path]
1517 return $y
1520 proc treeclosedir {w dir} {
1521 global treediropen treeheight treeparent treeindex
1523 set ix $treeindex($dir)
1524 $w conf -state normal
1525 $w delete s:$ix e:$ix
1526 set treediropen($dir) 0
1527 $w image configure a:$ix -image tri-rt
1528 $w conf -state disabled
1529 set n [expr {1 - $treeheight($dir)}]
1530 while {$dir ne {}} {
1531 incr treeheight($dir) $n
1532 set dir $treeparent($dir)
1536 proc treeopendir {w dir} {
1537 global treediropen treeheight treeparent treecontents treeindex
1539 set ix $treeindex($dir)
1540 $w conf -state normal
1541 $w image configure a:$ix -image tri-dn
1542 $w mark set e:$ix s:$ix
1543 $w mark gravity e:$ix right
1544 set lev 0
1545 set str "\n"
1546 set n [llength $treecontents($dir)]
1547 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1548 incr lev
1549 append str "\t"
1550 incr treeheight($x) $n
1552 foreach e $treecontents($dir) {
1553 set de $dir$e
1554 if {[string index $e end] eq "/"} {
1555 set iy $treeindex($de)
1556 $w mark set d:$iy e:$ix
1557 $w mark gravity d:$iy left
1558 $w insert e:$ix $str
1559 set treediropen($de) 0
1560 $w image create e:$ix -align center -image tri-rt -padx 1 \
1561 -name a:$iy
1562 $w insert e:$ix $e [highlight_tag $de]
1563 $w mark set s:$iy e:$ix
1564 $w mark gravity s:$iy left
1565 set treeheight($de) 1
1566 } else {
1567 $w insert e:$ix $str
1568 $w insert e:$ix $e [highlight_tag $de]
1571 $w mark gravity e:$ix left
1572 $w conf -state disabled
1573 set treediropen($dir) 1
1574 set top [lindex [split [$w index @0,0] .] 0]
1575 set ht [$w cget -height]
1576 set l [lindex [split [$w index s:$ix] .] 0]
1577 if {$l < $top} {
1578 $w yview $l.0
1579 } elseif {$l + $n + 1 > $top + $ht} {
1580 set top [expr {$l + $n + 2 - $ht}]
1581 if {$l < $top} {
1582 set top $l
1584 $w yview $top.0
1588 proc treeclick {w x y} {
1589 global treediropen cmitmode ctext cflist cflist_top
1591 if {$cmitmode ne "tree"} return
1592 if {![info exists cflist_top]} return
1593 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1594 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1595 $cflist tag add highlight $l.0 "$l.0 lineend"
1596 set cflist_top $l
1597 if {$l == 1} {
1598 $ctext yview 1.0
1599 return
1601 set e [linetoelt $l]
1602 if {[string index $e end] ne "/"} {
1603 showfile $e
1604 } elseif {$treediropen($e)} {
1605 treeclosedir $w $e
1606 } else {
1607 treeopendir $w $e
1611 proc setfilelist {id} {
1612 global treefilelist cflist
1614 treeview $cflist $treefilelist($id) 0
1617 image create bitmap tri-rt -background black -foreground blue -data {
1618 #define tri-rt_width 13
1619 #define tri-rt_height 13
1620 static unsigned char tri-rt_bits[] = {
1621 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1622 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1623 0x00, 0x00};
1624 } -maskdata {
1625 #define tri-rt-mask_width 13
1626 #define tri-rt-mask_height 13
1627 static unsigned char tri-rt-mask_bits[] = {
1628 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1629 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1630 0x08, 0x00};
1632 image create bitmap tri-dn -background black -foreground blue -data {
1633 #define tri-dn_width 13
1634 #define tri-dn_height 13
1635 static unsigned char tri-dn_bits[] = {
1636 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1637 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1638 0x00, 0x00};
1639 } -maskdata {
1640 #define tri-dn-mask_width 13
1641 #define tri-dn-mask_height 13
1642 static unsigned char tri-dn-mask_bits[] = {
1643 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1644 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1645 0x00, 0x00};
1648 image create bitmap reficon-T -background black -foreground yellow -data {
1649 #define tagicon_width 13
1650 #define tagicon_height 9
1651 static unsigned char tagicon_bits[] = {
1652 0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
1653 0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
1654 } -maskdata {
1655 #define tagicon-mask_width 13
1656 #define tagicon-mask_height 9
1657 static unsigned char tagicon-mask_bits[] = {
1658 0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
1659 0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
1661 set rectdata {
1662 #define headicon_width 13
1663 #define headicon_height 9
1664 static unsigned char headicon_bits[] = {
1665 0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
1666 0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
1668 set rectmask {
1669 #define headicon-mask_width 13
1670 #define headicon-mask_height 9
1671 static unsigned char headicon-mask_bits[] = {
1672 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
1673 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
1675 image create bitmap reficon-H -background black -foreground green \
1676 -data $rectdata -maskdata $rectmask
1677 image create bitmap reficon-o -background black -foreground "#ddddff" \
1678 -data $rectdata -maskdata $rectmask
1680 proc init_flist {first} {
1681 global cflist cflist_top selectedline difffilestart
1683 $cflist conf -state normal
1684 $cflist delete 0.0 end
1685 if {$first ne {}} {
1686 $cflist insert end $first
1687 set cflist_top 1
1688 $cflist tag add highlight 1.0 "1.0 lineend"
1689 } else {
1690 catch {unset cflist_top}
1692 $cflist conf -state disabled
1693 set difffilestart {}
1696 proc highlight_tag {f} {
1697 global highlight_paths
1699 foreach p $highlight_paths {
1700 if {[string match $p $f]} {
1701 return "bold"
1704 return {}
1707 proc highlight_filelist {} {
1708 global cmitmode cflist
1710 $cflist conf -state normal
1711 if {$cmitmode ne "tree"} {
1712 set end [lindex [split [$cflist index end] .] 0]
1713 for {set l 2} {$l < $end} {incr l} {
1714 set line [$cflist get $l.0 "$l.0 lineend"]
1715 if {[highlight_tag $line] ne {}} {
1716 $cflist tag add bold $l.0 "$l.0 lineend"
1719 } else {
1720 highlight_tree 2 {}
1722 $cflist conf -state disabled
1725 proc unhighlight_filelist {} {
1726 global cflist
1728 $cflist conf -state normal
1729 $cflist tag remove bold 1.0 end
1730 $cflist conf -state disabled
1733 proc add_flist {fl} {
1734 global cflist
1736 $cflist conf -state normal
1737 foreach f $fl {
1738 $cflist insert end "\n"
1739 $cflist insert end $f [highlight_tag $f]
1741 $cflist conf -state disabled
1744 proc sel_flist {w x y} {
1745 global ctext difffilestart cflist cflist_top cmitmode
1747 if {$cmitmode eq "tree"} return
1748 if {![info exists cflist_top]} return
1749 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1750 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1751 $cflist tag add highlight $l.0 "$l.0 lineend"
1752 set cflist_top $l
1753 if {$l == 1} {
1754 $ctext yview 1.0
1755 } else {
1756 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1760 proc pop_flist_menu {w X Y x y} {
1761 global ctext cflist cmitmode flist_menu flist_menu_file
1762 global treediffs diffids
1764 stopfinding
1765 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1766 if {$l <= 1} return
1767 if {$cmitmode eq "tree"} {
1768 set e [linetoelt $l]
1769 if {[string index $e end] eq "/"} return
1770 } else {
1771 set e [lindex $treediffs($diffids) [expr {$l-2}]]
1773 set flist_menu_file $e
1774 set xdiffstate "normal"
1775 if {$cmitmode eq "tree"} {
1776 set xdiffstate "disabled"
1778 # Disable "External diff" item in tree mode
1779 $flist_menu entryconf 2 -state $xdiffstate
1780 tk_popup $flist_menu $X $Y
1783 proc flist_hl {only} {
1784 global flist_menu_file findstring gdttype
1786 set x [shellquote $flist_menu_file]
1787 if {$only || $findstring eq {} || $gdttype ne [mc "touching paths:"]} {
1788 set findstring $x
1789 } else {
1790 append findstring " " $x
1792 set gdttype [mc "touching paths:"]
1795 proc save_file_from_commit {filename output what} {
1796 global nullfile
1798 if {[catch {exec git show $filename -- > $output} err]} {
1799 if {[string match "fatal: bad revision *" $err]} {
1800 return $nullfile
1802 error_popup "Error getting \"$filename\" from $what: $err"
1803 return {}
1805 return $output
1808 proc external_diff_get_one_file {diffid filename diffdir} {
1809 global nullid nullid2 nullfile
1810 global gitdir
1812 if {$diffid == $nullid} {
1813 set difffile [file join [file dirname $gitdir] $filename]
1814 if {[file exists $difffile]} {
1815 return $difffile
1817 return $nullfile
1819 if {$diffid == $nullid2} {
1820 set difffile [file join $diffdir "\[index\] [file tail $filename]"]
1821 return [save_file_from_commit :$filename $difffile index]
1823 set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
1824 return [save_file_from_commit $diffid:$filename $difffile \
1825 "revision $diffid"]
1828 proc external_diff {} {
1829 global gitktmpdir nullid nullid2
1830 global flist_menu_file
1831 global diffids
1832 global diffnum
1833 global gitdir extdifftool
1835 if {[llength $diffids] == 1} {
1836 # no reference commit given
1837 set diffidto [lindex $diffids 0]
1838 if {$diffidto eq $nullid} {
1839 # diffing working copy with index
1840 set diffidfrom $nullid2
1841 } elseif {$diffidto eq $nullid2} {
1842 # diffing index with HEAD
1843 set diffidfrom "HEAD"
1844 } else {
1845 # use first parent commit
1846 global parentlist selectedline
1847 set diffidfrom [lindex $parentlist $selectedline 0]
1849 } else {
1850 set diffidfrom [lindex $diffids 0]
1851 set diffidto [lindex $diffids 1]
1854 # make sure that several diffs wont collide
1855 if {![info exists gitktmpdir]} {
1856 set gitktmpdir [file join [file dirname $gitdir] \
1857 [format ".gitk-tmp.%s" [pid]]]
1858 if {[catch {file mkdir $gitktmpdir} err]} {
1859 error_popup "Error creating temporary directory $gitktmpdir: $err"
1860 unset gitktmpdir
1861 return
1863 set diffnum 0
1865 incr diffnum
1866 set diffdir [file join $gitktmpdir $diffnum]
1867 if {[catch {file mkdir $diffdir} err]} {
1868 error_popup "Error creating temporary directory $diffdir: $err"
1869 return
1872 # gather files to diff
1873 set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
1874 set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
1876 if {$difffromfile ne {} && $difftofile ne {}} {
1877 set cmd [concat | [shellsplit $extdifftool] \
1878 [list $difffromfile $difftofile]]
1879 if {[catch {set fl [open $cmd r]} err]} {
1880 file delete -force $diffdir
1881 error_popup [mc "$extdifftool: command failed: $err"]
1882 } else {
1883 fconfigure $fl -blocking 0
1884 filerun $fl [list delete_at_eof $fl $diffdir]
1889 # delete $dir when we see eof on $f (presumably because the child has exited)
1890 proc delete_at_eof {f dir} {
1891 while {[gets $f line] >= 0} {}
1892 if {[eof $f]} {
1893 if {[catch {close $f} err]} {
1894 error_popup "External diff viewer failed: $err"
1896 file delete -force $dir
1897 return 0
1899 return 1
1902 # Functions for adding and removing shell-type quoting
1904 proc shellquote {str} {
1905 if {![string match "*\['\"\\ \t]*" $str]} {
1906 return $str
1908 if {![string match "*\['\"\\]*" $str]} {
1909 return "\"$str\""
1911 if {![string match "*'*" $str]} {
1912 return "'$str'"
1914 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1917 proc shellarglist {l} {
1918 set str {}
1919 foreach a $l {
1920 if {$str ne {}} {
1921 append str " "
1923 append str [shellquote $a]
1925 return $str
1928 proc shelldequote {str} {
1929 set ret {}
1930 set used -1
1931 while {1} {
1932 incr used
1933 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1934 append ret [string range $str $used end]
1935 set used [string length $str]
1936 break
1938 set first [lindex $first 0]
1939 set ch [string index $str $first]
1940 if {$first > $used} {
1941 append ret [string range $str $used [expr {$first - 1}]]
1942 set used $first
1944 if {$ch eq " " || $ch eq "\t"} break
1945 incr used
1946 if {$ch eq "'"} {
1947 set first [string first "'" $str $used]
1948 if {$first < 0} {
1949 error "unmatched single-quote"
1951 append ret [string range $str $used [expr {$first - 1}]]
1952 set used $first
1953 continue
1955 if {$ch eq "\\"} {
1956 if {$used >= [string length $str]} {
1957 error "trailing backslash"
1959 append ret [string index $str $used]
1960 continue
1962 # here ch == "\""
1963 while {1} {
1964 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1965 error "unmatched double-quote"
1967 set first [lindex $first 0]
1968 set ch [string index $str $first]
1969 if {$first > $used} {
1970 append ret [string range $str $used [expr {$first - 1}]]
1971 set used $first
1973 if {$ch eq "\""} break
1974 incr used
1975 append ret [string index $str $used]
1976 incr used
1979 return [list $used $ret]
1982 proc shellsplit {str} {
1983 set l {}
1984 while {1} {
1985 set str [string trimleft $str]
1986 if {$str eq {}} break
1987 set dq [shelldequote $str]
1988 set n [lindex $dq 0]
1989 set word [lindex $dq 1]
1990 set str [string range $str $n end]
1991 lappend l $word
1993 return $l
1996 # Code to implement multiple views
1998 proc newview {ishighlight} {
1999 global nextviewnum newviewname newviewperm newishighlight
2000 global newviewargs revtreeargs viewargscmd newviewargscmd curview
2002 set newishighlight $ishighlight
2003 set top .gitkview
2004 if {[winfo exists $top]} {
2005 raise $top
2006 return
2008 set newviewname($nextviewnum) "[mc "View"] $nextviewnum"
2009 set newviewperm($nextviewnum) 0
2010 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
2011 set newviewargscmd($nextviewnum) $viewargscmd($curview)
2012 vieweditor $top $nextviewnum [mc "Gitk view definition"]
2015 proc editview {} {
2016 global curview
2017 global viewname viewperm newviewname newviewperm
2018 global viewargs newviewargs viewargscmd newviewargscmd
2020 set top .gitkvedit-$curview
2021 if {[winfo exists $top]} {
2022 raise $top
2023 return
2025 set newviewname($curview) $viewname($curview)
2026 set newviewperm($curview) $viewperm($curview)
2027 set newviewargs($curview) [shellarglist $viewargs($curview)]
2028 set newviewargscmd($curview) $viewargscmd($curview)
2029 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
2032 proc vieweditor {top n title} {
2033 global newviewname newviewperm viewfiles bgcolor
2035 toplevel $top
2036 wm title $top $title
2037 label $top.nl -text [mc "Name"]
2038 entry $top.name -width 20 -textvariable newviewname($n)
2039 grid $top.nl $top.name -sticky w -pady 5
2040 checkbutton $top.perm -text [mc "Remember this view"] \
2041 -variable newviewperm($n)
2042 grid $top.perm - -pady 5 -sticky w
2043 message $top.al -aspect 1000 \
2044 -text [mc "Commits to include (arguments to git rev-list):"]
2045 grid $top.al - -sticky w -pady 5
2046 entry $top.args -width 50 -textvariable newviewargs($n) \
2047 -background $bgcolor
2048 grid $top.args - -sticky ew -padx 5
2050 message $top.ac -aspect 1000 \
2051 -text [mc "Command to generate more commits to include:"]
2052 grid $top.ac - -sticky w -pady 5
2053 entry $top.argscmd -width 50 -textvariable newviewargscmd($n) \
2054 -background white
2055 grid $top.argscmd - -sticky ew -padx 5
2057 message $top.l -aspect 1000 \
2058 -text [mc "Enter files and directories to include, one per line:"]
2059 grid $top.l - -sticky w
2060 text $top.t -width 40 -height 10 -background $bgcolor -font uifont
2061 if {[info exists viewfiles($n)]} {
2062 foreach f $viewfiles($n) {
2063 $top.t insert end $f
2064 $top.t insert end "\n"
2066 $top.t delete {end - 1c} end
2067 $top.t mark set insert 0.0
2069 grid $top.t - -sticky ew -padx 5
2070 frame $top.buts
2071 button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
2072 button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
2073 grid $top.buts.ok $top.buts.can
2074 grid columnconfigure $top.buts 0 -weight 1 -uniform a
2075 grid columnconfigure $top.buts 1 -weight 1 -uniform a
2076 grid $top.buts - -pady 10 -sticky ew
2077 focus $top.t
2080 proc doviewmenu {m first cmd op argv} {
2081 set nmenu [$m index end]
2082 for {set i $first} {$i <= $nmenu} {incr i} {
2083 if {[$m entrycget $i -command] eq $cmd} {
2084 eval $m $op $i $argv
2085 break
2090 proc allviewmenus {n op args} {
2091 # global viewhlmenu
2093 doviewmenu .bar.view 5 [list showview $n] $op $args
2094 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
2097 proc newviewok {top n} {
2098 global nextviewnum newviewperm newviewname newishighlight
2099 global viewname viewfiles viewperm selectedview curview
2100 global viewargs newviewargs viewargscmd newviewargscmd viewhlmenu
2102 if {[catch {
2103 set newargs [shellsplit $newviewargs($n)]
2104 } err]} {
2105 error_popup "[mc "Error in commit selection arguments:"] $err"
2106 wm raise $top
2107 focus $top
2108 return
2110 set files {}
2111 foreach f [split [$top.t get 0.0 end] "\n"] {
2112 set ft [string trim $f]
2113 if {$ft ne {}} {
2114 lappend files $ft
2117 if {![info exists viewfiles($n)]} {
2118 # creating a new view
2119 incr nextviewnum
2120 set viewname($n) $newviewname($n)
2121 set viewperm($n) $newviewperm($n)
2122 set viewfiles($n) $files
2123 set viewargs($n) $newargs
2124 set viewargscmd($n) $newviewargscmd($n)
2125 addviewmenu $n
2126 if {!$newishighlight} {
2127 run showview $n
2128 } else {
2129 run addvhighlight $n
2131 } else {
2132 # editing an existing view
2133 set viewperm($n) $newviewperm($n)
2134 if {$newviewname($n) ne $viewname($n)} {
2135 set viewname($n) $newviewname($n)
2136 doviewmenu .bar.view 5 [list showview $n] \
2137 entryconf [list -label $viewname($n)]
2138 # doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
2139 # entryconf [list -label $viewname($n) -value $viewname($n)]
2141 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n) || \
2142 $newviewargscmd($n) ne $viewargscmd($n)} {
2143 set viewfiles($n) $files
2144 set viewargs($n) $newargs
2145 set viewargscmd($n) $newviewargscmd($n)
2146 if {$curview == $n} {
2147 run updatecommits
2151 catch {destroy $top}
2154 proc delview {} {
2155 global curview viewdata viewperm hlview selectedhlview
2157 if {$curview == 0} return
2158 if {[info exists hlview] && $hlview == $curview} {
2159 set selectedhlview [mc "None"]
2160 unset hlview
2162 allviewmenus $curview delete
2163 set viewdata($curview) {}
2164 set viewperm($curview) 0
2165 showview 0
2168 proc addviewmenu {n} {
2169 global viewname viewhlmenu
2171 .bar.view add radiobutton -label $viewname($n) \
2172 -command [list showview $n] -variable selectedview -value $n
2173 #$viewhlmenu add radiobutton -label $viewname($n) \
2174 # -command [list addvhighlight $n] -variable selectedhlview
2177 proc flatten {var} {
2178 global $var
2180 set ret {}
2181 foreach i [array names $var] {
2182 lappend ret $i [set $var\($i\)]
2184 return $ret
2187 proc unflatten {var l} {
2188 global $var
2190 catch {unset $var}
2191 foreach {i v} $l {
2192 set $var\($i\) $v
2196 proc showview {n} {
2197 global curview viewdata viewfiles
2198 global displayorder parentlist rowidlist rowisopt rowfinal
2199 global colormap rowtextx commitrow nextcolor canvxmax
2200 global numcommits commitlisted
2201 global selectedline currentid canv canvy0
2202 global treediffs
2203 global pending_select phase
2204 global commitidx
2205 global commfd
2206 global selectedview selectfirst
2207 global vparentlist vdisporder vcmitlisted
2208 global hlview selectedhlview commitinterest
2210 if {$n == $curview} return
2211 set selid {}
2212 if {[info exists selectedline]} {
2213 set selid $currentid
2214 set y [yc $selectedline]
2215 set ymax [lindex [$canv cget -scrollregion] 3]
2216 set span [$canv yview]
2217 set ytop [expr {[lindex $span 0] * $ymax}]
2218 set ybot [expr {[lindex $span 1] * $ymax}]
2219 if {$ytop < $y && $y < $ybot} {
2220 set yscreen [expr {$y - $ytop}]
2222 } elseif {[info exists pending_select]} {
2223 set selid $pending_select
2224 unset pending_select
2226 unselectline
2227 normalline
2228 if {$curview >= 0} {
2229 set vparentlist($curview) $parentlist
2230 set vdisporder($curview) $displayorder
2231 set vcmitlisted($curview) $commitlisted
2232 if {$phase ne {} ||
2233 ![info exists viewdata($curview)] ||
2234 [lindex $viewdata($curview) 0] ne {}} {
2235 set viewdata($curview) \
2236 [list $phase $rowidlist $rowisopt $rowfinal]
2239 catch {unset treediffs}
2240 clear_display
2241 if {[info exists hlview] && $hlview == $n} {
2242 unset hlview
2243 set selectedhlview [mc "None"]
2245 catch {unset commitinterest}
2247 set curview $n
2248 set selectedview $n
2249 .bar.view entryconf [mc "Edit view..."] -state [expr {$n == 0? "disabled": "normal"}]
2250 .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
2252 run refill_reflist
2253 if {![info exists viewdata($n)]} {
2254 if {$selid ne {}} {
2255 set pending_select $selid
2257 getcommits
2258 return
2261 set v $viewdata($n)
2262 set phase [lindex $v 0]
2263 set displayorder $vdisporder($n)
2264 set parentlist $vparentlist($n)
2265 set commitlisted $vcmitlisted($n)
2266 set rowidlist [lindex $v 1]
2267 set rowisopt [lindex $v 2]
2268 set rowfinal [lindex $v 3]
2269 set numcommits $commitidx($n)
2271 catch {unset colormap}
2272 catch {unset rowtextx}
2273 set nextcolor 0
2274 set canvxmax [$canv cget -width]
2275 set curview $n
2276 set row 0
2277 setcanvscroll
2278 set yf 0
2279 set row {}
2280 set selectfirst 0
2281 if {[info exists yscreen] && [info exists commitrow($n,$selid)]} {
2282 set row $commitrow($n,$selid)
2283 # try to get the selected row in the same position on the screen
2284 set ymax [lindex [$canv cget -scrollregion] 3]
2285 set ytop [expr {[yc $row] - $yscreen}]
2286 if {$ytop < 0} {
2287 set ytop 0
2289 set yf [expr {$ytop * 1.0 / $ymax}]
2291 allcanvs yview moveto $yf
2292 drawvisible
2293 if {$row ne {}} {
2294 selectline $row 0
2295 } elseif {$selid ne {}} {
2296 set pending_select $selid
2297 } else {
2298 set row [first_real_row]
2299 if {$row < $numcommits} {
2300 selectline $row 0
2301 } else {
2302 set selectfirst 1
2305 if {$phase ne {}} {
2306 if {$phase eq "getcommits"} {
2307 show_status [mc "Reading commits..."]
2309 run chewcommits $n
2310 } elseif {$numcommits == 0} {
2311 show_status [mc "No commits selected"]
2315 # Stuff relating to the highlighting facility
2317 proc ishighlighted {row} {
2318 global vhighlights fhighlights nhighlights rhighlights
2320 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
2321 return $nhighlights($row)
2323 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
2324 return $vhighlights($row)
2326 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
2327 return $fhighlights($row)
2329 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
2330 return $rhighlights($row)
2332 return 0
2335 proc bolden {row font} {
2336 global canv linehtag selectedline boldrows
2338 lappend boldrows $row
2339 $canv itemconf $linehtag($row) -font $font
2340 if {[info exists selectedline] && $row == $selectedline} {
2341 $canv delete secsel
2342 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
2343 -outline {{}} -tags secsel \
2344 -fill [$canv cget -selectbackground]]
2345 $canv lower $t
2349 proc bolden_name {row font} {
2350 global canv2 linentag selectedline boldnamerows
2352 lappend boldnamerows $row
2353 $canv2 itemconf $linentag($row) -font $font
2354 if {[info exists selectedline] && $row == $selectedline} {
2355 $canv2 delete secsel
2356 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
2357 -outline {{}} -tags secsel \
2358 -fill [$canv2 cget -selectbackground]]
2359 $canv2 lower $t
2363 proc unbolden {} {
2364 global boldrows
2366 set stillbold {}
2367 foreach row $boldrows {
2368 if {![ishighlighted $row]} {
2369 bolden $row mainfont
2370 } else {
2371 lappend stillbold $row
2374 set boldrows $stillbold
2377 proc addvhighlight {n} {
2378 global hlview curview viewdata vhl_done vhighlights commitidx
2380 if {[info exists hlview]} {
2381 delvhighlight
2383 set hlview $n
2384 if {$n != $curview && ![info exists viewdata($n)]} {
2385 set viewdata($n) [list getcommits {{}} 0 0 0]
2386 set vparentlist($n) {}
2387 set vdisporder($n) {}
2388 set vcmitlisted($n) {}
2389 start_rev_list $n
2391 set vhl_done $commitidx($hlview)
2392 if {$vhl_done > 0} {
2393 drawvisible
2397 proc delvhighlight {} {
2398 global hlview vhighlights
2400 if {![info exists hlview]} return
2401 unset hlview
2402 catch {unset vhighlights}
2403 unbolden
2406 proc vhighlightmore {} {
2407 global hlview vhl_done commitidx vhighlights
2408 global displayorder vdisporder curview
2410 set max $commitidx($hlview)
2411 if {$hlview == $curview} {
2412 set disp $displayorder
2413 } else {
2414 set disp $vdisporder($hlview)
2416 set vr [visiblerows]
2417 set r0 [lindex $vr 0]
2418 set r1 [lindex $vr 1]
2419 for {set i $vhl_done} {$i < $max} {incr i} {
2420 set id [lindex $disp $i]
2421 if {[info exists commitrow($curview,$id)]} {
2422 set row $commitrow($curview,$id)
2423 if {$r0 <= $row && $row <= $r1} {
2424 if {![highlighted $row]} {
2425 bolden $row mainfontbold
2427 set vhighlights($row) 1
2431 set vhl_done $max
2434 proc askvhighlight {row id} {
2435 global hlview vhighlights commitrow iddrawn
2437 if {[info exists commitrow($hlview,$id)]} {
2438 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2439 bolden $row mainfontbold
2441 set vhighlights($row) 1
2442 } else {
2443 set vhighlights($row) 0
2447 proc hfiles_change {} {
2448 global highlight_files filehighlight fhighlights fh_serial
2449 global highlight_paths gdttype
2451 if {[info exists filehighlight]} {
2452 # delete previous highlights
2453 catch {close $filehighlight}
2454 unset filehighlight
2455 catch {unset fhighlights}
2456 unbolden
2457 unhighlight_filelist
2459 set highlight_paths {}
2460 after cancel do_file_hl $fh_serial
2461 incr fh_serial
2462 if {$highlight_files ne {}} {
2463 after 300 do_file_hl $fh_serial
2467 proc gdttype_change {name ix op} {
2468 global gdttype highlight_files findstring findpattern
2470 stopfinding
2471 if {$findstring ne {}} {
2472 if {$gdttype eq [mc "containing:"]} {
2473 if {$highlight_files ne {}} {
2474 set highlight_files {}
2475 hfiles_change
2477 findcom_change
2478 } else {
2479 if {$findpattern ne {}} {
2480 set findpattern {}
2481 findcom_change
2483 set highlight_files $findstring
2484 hfiles_change
2486 drawvisible
2488 # enable/disable findtype/findloc menus too
2491 proc find_change {name ix op} {
2492 global gdttype findstring highlight_files
2494 stopfinding
2495 if {$gdttype eq [mc "containing:"]} {
2496 findcom_change
2497 } else {
2498 if {$highlight_files ne $findstring} {
2499 set highlight_files $findstring
2500 hfiles_change
2503 drawvisible
2506 proc findcom_change args {
2507 global nhighlights boldnamerows
2508 global findpattern findtype findstring gdttype
2510 stopfinding
2511 # delete previous highlights, if any
2512 foreach row $boldnamerows {
2513 bolden_name $row mainfont
2515 set boldnamerows {}
2516 catch {unset nhighlights}
2517 unbolden
2518 unmarkmatches
2519 if {$gdttype ne [mc "containing:"] || $findstring eq {}} {
2520 set findpattern {}
2521 } elseif {$findtype eq [mc "Regexp"]} {
2522 set findpattern $findstring
2523 } else {
2524 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2525 $findstring]
2526 set findpattern "*$e*"
2530 proc makepatterns {l} {
2531 set ret {}
2532 foreach e $l {
2533 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2534 if {[string index $ee end] eq "/"} {
2535 lappend ret "$ee*"
2536 } else {
2537 lappend ret $ee
2538 lappend ret "$ee/*"
2541 return $ret
2544 proc do_file_hl {serial} {
2545 global highlight_files filehighlight highlight_paths gdttype fhl_list
2547 if {$gdttype eq [mc "touching paths:"]} {
2548 if {[catch {set paths [shellsplit $highlight_files]}]} return
2549 set highlight_paths [makepatterns $paths]
2550 highlight_filelist
2551 set gdtargs [concat -- $paths]
2552 } elseif {$gdttype eq [mc "adding/removing string:"]} {
2553 set gdtargs [list "-S$highlight_files"]
2554 } else {
2555 # must be "containing:", i.e. we're searching commit info
2556 return
2558 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2559 set filehighlight [open $cmd r+]
2560 fconfigure $filehighlight -blocking 0
2561 filerun $filehighlight readfhighlight
2562 set fhl_list {}
2563 drawvisible
2564 flushhighlights
2567 proc flushhighlights {} {
2568 global filehighlight fhl_list
2570 if {[info exists filehighlight]} {
2571 lappend fhl_list {}
2572 puts $filehighlight ""
2573 flush $filehighlight
2577 proc askfilehighlight {row id} {
2578 global filehighlight fhighlights fhl_list
2580 lappend fhl_list $id
2581 set fhighlights($row) -1
2582 puts $filehighlight $id
2585 proc readfhighlight {} {
2586 global filehighlight fhighlights commitrow curview iddrawn
2587 global fhl_list find_dirn
2589 if {![info exists filehighlight]} {
2590 return 0
2592 set nr 0
2593 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2594 set line [string trim $line]
2595 set i [lsearch -exact $fhl_list $line]
2596 if {$i < 0} continue
2597 for {set j 0} {$j < $i} {incr j} {
2598 set id [lindex $fhl_list $j]
2599 if {[info exists commitrow($curview,$id)]} {
2600 set fhighlights($commitrow($curview,$id)) 0
2603 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2604 if {$line eq {}} continue
2605 if {![info exists commitrow($curview,$line)]} continue
2606 set row $commitrow($curview,$line)
2607 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2608 bolden $row mainfontbold
2610 set fhighlights($row) 1
2612 if {[eof $filehighlight]} {
2613 # strange...
2614 puts "oops, git diff-tree died"
2615 catch {close $filehighlight}
2616 unset filehighlight
2617 return 0
2619 if {[info exists find_dirn]} {
2620 run findmore
2622 return 1
2625 proc doesmatch {f} {
2626 global findtype findpattern
2628 if {$findtype eq [mc "Regexp"]} {
2629 return [regexp $findpattern $f]
2630 } elseif {$findtype eq [mc "IgnCase"]} {
2631 return [string match -nocase $findpattern $f]
2632 } else {
2633 return [string match $findpattern $f]
2637 proc askfindhighlight {row id} {
2638 global nhighlights commitinfo iddrawn
2639 global findloc
2640 global markingmatches
2642 if {![info exists commitinfo($id)]} {
2643 getcommit $id
2645 set info $commitinfo($id)
2646 set isbold 0
2647 set fldtypes [list [mc Headline] [mc Author] [mc Date] [mc Committer] [mc CDate] [mc Comments]]
2648 foreach f $info ty $fldtypes {
2649 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
2650 [doesmatch $f]} {
2651 if {$ty eq [mc "Author"]} {
2652 set isbold 2
2653 break
2655 set isbold 1
2658 if {$isbold && [info exists iddrawn($id)]} {
2659 if {![ishighlighted $row]} {
2660 bolden $row mainfontbold
2661 if {$isbold > 1} {
2662 bolden_name $row mainfontbold
2665 if {$markingmatches} {
2666 markrowmatches $row $id
2669 set nhighlights($row) $isbold
2672 proc markrowmatches {row id} {
2673 global canv canv2 linehtag linentag commitinfo findloc
2675 set headline [lindex $commitinfo($id) 0]
2676 set author [lindex $commitinfo($id) 1]
2677 $canv delete match$row
2678 $canv2 delete match$row
2679 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Headline"]} {
2680 set m [findmatches $headline]
2681 if {$m ne {}} {
2682 markmatches $canv $row $headline $linehtag($row) $m \
2683 [$canv itemcget $linehtag($row) -font] $row
2686 if {$findloc eq [mc "All fields"] || $findloc eq [mc "Author"]} {
2687 set m [findmatches $author]
2688 if {$m ne {}} {
2689 markmatches $canv2 $row $author $linentag($row) $m \
2690 [$canv2 itemcget $linentag($row) -font] $row
2695 proc vrel_change {name ix op} {
2696 global highlight_related
2698 rhighlight_none
2699 if {$highlight_related ne [mc "None"]} {
2700 run drawvisible
2704 # prepare for testing whether commits are descendents or ancestors of a
2705 proc rhighlight_sel {a} {
2706 global descendent desc_todo ancestor anc_todo
2707 global highlight_related rhighlights
2709 catch {unset descendent}
2710 set desc_todo [list $a]
2711 catch {unset ancestor}
2712 set anc_todo [list $a]
2713 if {$highlight_related ne [mc "None"]} {
2714 rhighlight_none
2715 run drawvisible
2719 proc rhighlight_none {} {
2720 global rhighlights
2722 catch {unset rhighlights}
2723 unbolden
2726 proc is_descendent {a} {
2727 global curview children commitrow descendent desc_todo
2729 set v $curview
2730 set la $commitrow($v,$a)
2731 set todo $desc_todo
2732 set leftover {}
2733 set done 0
2734 for {set i 0} {$i < [llength $todo]} {incr i} {
2735 set do [lindex $todo $i]
2736 if {$commitrow($v,$do) < $la} {
2737 lappend leftover $do
2738 continue
2740 foreach nk $children($v,$do) {
2741 if {![info exists descendent($nk)]} {
2742 set descendent($nk) 1
2743 lappend todo $nk
2744 if {$nk eq $a} {
2745 set done 1
2749 if {$done} {
2750 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2751 return
2754 set descendent($a) 0
2755 set desc_todo $leftover
2758 proc is_ancestor {a} {
2759 global curview parentlist commitrow ancestor anc_todo
2761 set v $curview
2762 set la $commitrow($v,$a)
2763 set todo $anc_todo
2764 set leftover {}
2765 set done 0
2766 for {set i 0} {$i < [llength $todo]} {incr i} {
2767 set do [lindex $todo $i]
2768 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2769 lappend leftover $do
2770 continue
2772 foreach np [lindex $parentlist $commitrow($v,$do)] {
2773 if {![info exists ancestor($np)]} {
2774 set ancestor($np) 1
2775 lappend todo $np
2776 if {$np eq $a} {
2777 set done 1
2781 if {$done} {
2782 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2783 return
2786 set ancestor($a) 0
2787 set anc_todo $leftover
2790 proc askrelhighlight {row id} {
2791 global descendent highlight_related iddrawn rhighlights
2792 global selectedline ancestor
2794 if {![info exists selectedline]} return
2795 set isbold 0
2796 if {$highlight_related eq [mc "Descendant"] ||
2797 $highlight_related eq [mc "Not descendant"]} {
2798 if {![info exists descendent($id)]} {
2799 is_descendent $id
2801 if {$descendent($id) == ($highlight_related eq [mc "Descendant"])} {
2802 set isbold 1
2804 } elseif {$highlight_related eq [mc "Ancestor"] ||
2805 $highlight_related eq [mc "Not ancestor"]} {
2806 if {![info exists ancestor($id)]} {
2807 is_ancestor $id
2809 if {$ancestor($id) == ($highlight_related eq [mc "Ancestor"])} {
2810 set isbold 1
2813 if {[info exists iddrawn($id)]} {
2814 if {$isbold && ![ishighlighted $row]} {
2815 bolden $row mainfontbold
2818 set rhighlights($row) $isbold
2821 # Graph layout functions
2823 proc shortids {ids} {
2824 set res {}
2825 foreach id $ids {
2826 if {[llength $id] > 1} {
2827 lappend res [shortids $id]
2828 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2829 lappend res [string range $id 0 7]
2830 } else {
2831 lappend res $id
2834 return $res
2837 proc ntimes {n o} {
2838 set ret {}
2839 set o [list $o]
2840 for {set mask 1} {$mask <= $n} {incr mask $mask} {
2841 if {($n & $mask) != 0} {
2842 set ret [concat $ret $o]
2844 set o [concat $o $o]
2846 return $ret
2849 # Work out where id should go in idlist so that order-token
2850 # values increase from left to right
2851 proc idcol {idlist id {i 0}} {
2852 global ordertok curview
2854 set t $ordertok($curview,$id)
2855 if {$i >= [llength $idlist] ||
2856 $t < $ordertok($curview,[lindex $idlist $i])} {
2857 if {$i > [llength $idlist]} {
2858 set i [llength $idlist]
2860 while {[incr i -1] >= 0 &&
2861 $t < $ordertok($curview,[lindex $idlist $i])} {}
2862 incr i
2863 } else {
2864 if {$t > $ordertok($curview,[lindex $idlist $i])} {
2865 while {[incr i] < [llength $idlist] &&
2866 $t >= $ordertok($curview,[lindex $idlist $i])} {}
2869 return $i
2872 proc initlayout {} {
2873 global rowidlist rowisopt rowfinal displayorder commitlisted
2874 global numcommits canvxmax canv
2875 global nextcolor
2876 global parentlist
2877 global colormap rowtextx
2878 global selectfirst
2880 set numcommits 0
2881 set displayorder {}
2882 set commitlisted {}
2883 set parentlist {}
2884 set nextcolor 0
2885 set rowidlist {}
2886 set rowisopt {}
2887 set rowfinal {}
2888 set canvxmax [$canv cget -width]
2889 catch {unset colormap}
2890 catch {unset rowtextx}
2891 set selectfirst 1
2894 proc setcanvscroll {} {
2895 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2897 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2898 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2899 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2900 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2903 proc visiblerows {} {
2904 global canv numcommits linespc
2906 set ymax [lindex [$canv cget -scrollregion] 3]
2907 if {$ymax eq {} || $ymax == 0} return
2908 set f [$canv yview]
2909 set y0 [expr {int([lindex $f 0] * $ymax)}]
2910 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2911 if {$r0 < 0} {
2912 set r0 0
2914 set y1 [expr {int([lindex $f 1] * $ymax)}]
2915 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2916 if {$r1 >= $numcommits} {
2917 set r1 [expr {$numcommits - 1}]
2919 return [list $r0 $r1]
2922 proc layoutmore {} {
2923 global commitidx viewcomplete numcommits
2924 global uparrowlen downarrowlen mingaplen curview
2926 set show $commitidx($curview)
2927 if {$show > $numcommits || $viewcomplete($curview)} {
2928 showstuff $show $viewcomplete($curview)
2932 proc showstuff {canshow last} {
2933 global numcommits commitrow pending_select selectedline curview
2934 global mainheadid displayorder selectfirst
2935 global lastscrollset commitinterest
2937 if {$numcommits == 0} {
2938 global phase
2939 set phase "incrdraw"
2940 allcanvs delete all
2942 set r0 $numcommits
2943 set prev $numcommits
2944 set numcommits $canshow
2945 set t [clock clicks -milliseconds]
2946 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2947 set lastscrollset $t
2948 setcanvscroll
2950 set rows [visiblerows]
2951 set r1 [lindex $rows 1]
2952 if {$r1 >= $canshow} {
2953 set r1 [expr {$canshow - 1}]
2955 if {$r0 <= $r1} {
2956 drawcommits $r0 $r1
2958 if {[info exists pending_select] &&
2959 [info exists commitrow($curview,$pending_select)] &&
2960 $commitrow($curview,$pending_select) < $numcommits} {
2961 selectline $commitrow($curview,$pending_select) 1
2963 if {$selectfirst} {
2964 if {[info exists selectedline] || [info exists pending_select]} {
2965 set selectfirst 0
2966 } else {
2967 set l [first_real_row]
2968 selectline $l 1
2969 set selectfirst 0
2974 proc doshowlocalchanges {} {
2975 global curview mainheadid phase commitrow
2977 if {[info exists commitrow($curview,$mainheadid)] &&
2978 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2979 dodiffindex
2980 } elseif {$phase ne {}} {
2981 lappend commitinterest($mainheadid) {}
2985 proc dohidelocalchanges {} {
2986 global localfrow localirow lserial
2988 if {$localfrow >= 0} {
2989 removerow $localfrow
2990 set localfrow -1
2991 if {$localirow > 0} {
2992 incr localirow -1
2995 if {$localirow >= 0} {
2996 removerow $localirow
2997 set localirow -1
2999 incr lserial
3002 # spawn off a process to do git diff-index --cached HEAD
3003 proc dodiffindex {} {
3004 global localirow localfrow lserial showlocalchanges
3005 global isworktree
3007 if {!$showlocalchanges || !$isworktree} return
3008 incr lserial
3009 set localfrow -1
3010 set localirow -1
3011 set fd [open "|git diff-index --cached HEAD" r]
3012 fconfigure $fd -blocking 0
3013 filerun $fd [list readdiffindex $fd $lserial]
3016 proc readdiffindex {fd serial} {
3017 global localirow commitrow mainheadid nullid2 curview
3018 global commitinfo commitdata lserial
3020 set isdiff 1
3021 if {[gets $fd line] < 0} {
3022 if {![eof $fd]} {
3023 return 1
3025 set isdiff 0
3027 # we only need to see one line and we don't really care what it says...
3028 close $fd
3030 # now see if there are any local changes not checked in to the index
3031 if {$serial == $lserial} {
3032 set fd [open "|git diff-files" r]
3033 fconfigure $fd -blocking 0
3034 filerun $fd [list readdifffiles $fd $serial]
3037 if {$isdiff && $serial == $lserial && $localirow == -1} {
3038 # add the line for the changes in the index to the graph
3039 set localirow $commitrow($curview,$mainheadid)
3040 set hl [mc "Local changes checked in to index but not committed"]
3041 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
3042 set commitdata($nullid2) "\n $hl\n"
3043 insertrow $localirow $nullid2
3045 return 0
3048 proc readdifffiles {fd serial} {
3049 global localirow localfrow commitrow mainheadid nullid curview
3050 global commitinfo commitdata lserial
3052 set isdiff 1
3053 if {[gets $fd line] < 0} {
3054 if {![eof $fd]} {
3055 return 1
3057 set isdiff 0
3059 # we only need to see one line and we don't really care what it says...
3060 close $fd
3062 if {$isdiff && $serial == $lserial && $localfrow == -1} {
3063 # add the line for the local diff to the graph
3064 if {$localirow >= 0} {
3065 set localfrow $localirow
3066 incr localirow
3067 } else {
3068 set localfrow $commitrow($curview,$mainheadid)
3070 set hl [mc "Local uncommitted changes, not checked in to index"]
3071 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
3072 set commitdata($nullid) "\n $hl\n"
3073 insertrow $localfrow $nullid
3075 return 0
3078 proc nextuse {id row} {
3079 global commitrow curview children
3081 if {[info exists children($curview,$id)]} {
3082 foreach kid $children($curview,$id) {
3083 if {![info exists commitrow($curview,$kid)]} {
3084 return -1
3086 if {$commitrow($curview,$kid) > $row} {
3087 return $commitrow($curview,$kid)
3091 if {[info exists commitrow($curview,$id)]} {
3092 return $commitrow($curview,$id)
3094 return -1
3097 proc prevuse {id row} {
3098 global commitrow curview children
3100 set ret -1
3101 if {[info exists children($curview,$id)]} {
3102 foreach kid $children($curview,$id) {
3103 if {![info exists commitrow($curview,$kid)]} break
3104 if {$commitrow($curview,$kid) < $row} {
3105 set ret $commitrow($curview,$kid)
3109 return $ret
3112 proc make_idlist {row} {
3113 global displayorder parentlist uparrowlen downarrowlen mingaplen
3114 global commitidx curview ordertok children commitrow
3116 set r [expr {$row - $mingaplen - $downarrowlen - 1}]
3117 if {$r < 0} {
3118 set r 0
3120 set ra [expr {$row - $downarrowlen}]
3121 if {$ra < 0} {
3122 set ra 0
3124 set rb [expr {$row + $uparrowlen}]
3125 if {$rb > $commitidx($curview)} {
3126 set rb $commitidx($curview)
3128 set ids {}
3129 for {} {$r < $ra} {incr r} {
3130 set nextid [lindex $displayorder [expr {$r + 1}]]
3131 foreach p [lindex $parentlist $r] {
3132 if {$p eq $nextid} continue
3133 set rn [nextuse $p $r]
3134 if {$rn >= $row &&
3135 $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
3136 lappend ids [list $ordertok($curview,$p) $p]
3140 for {} {$r < $row} {incr r} {
3141 set nextid [lindex $displayorder [expr {$r + 1}]]
3142 foreach p [lindex $parentlist $r] {
3143 if {$p eq $nextid} continue
3144 set rn [nextuse $p $r]
3145 if {$rn < 0 || $rn >= $row} {
3146 lappend ids [list $ordertok($curview,$p) $p]
3150 set id [lindex $displayorder $row]
3151 lappend ids [list $ordertok($curview,$id) $id]
3152 while {$r < $rb} {
3153 foreach p [lindex $parentlist $r] {
3154 set firstkid [lindex $children($curview,$p) 0]
3155 if {$commitrow($curview,$firstkid) < $row} {
3156 lappend ids [list $ordertok($curview,$p) $p]
3159 incr r
3160 set id [lindex $displayorder $r]
3161 if {$id ne {}} {
3162 set firstkid [lindex $children($curview,$id) 0]
3163 if {$firstkid ne {} && $commitrow($curview,$firstkid) < $row} {
3164 lappend ids [list $ordertok($curview,$id) $id]
3168 set idlist {}
3169 foreach idx [lsort -unique $ids] {
3170 lappend idlist [lindex $idx 1]
3172 return $idlist
3175 proc rowsequal {a b} {
3176 while {[set i [lsearch -exact $a {}]] >= 0} {
3177 set a [lreplace $a $i $i]
3179 while {[set i [lsearch -exact $b {}]] >= 0} {
3180 set b [lreplace $b $i $i]
3182 return [expr {$a eq $b}]
3185 proc makeupline {id row rend col} {
3186 global rowidlist uparrowlen downarrowlen mingaplen
3188 for {set r $rend} {1} {set r $rstart} {
3189 set rstart [prevuse $id $r]
3190 if {$rstart < 0} return
3191 if {$rstart < $row} break
3193 if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
3194 set rstart [expr {$rend - $uparrowlen - 1}]
3196 for {set r $rstart} {[incr r] <= $row} {} {
3197 set idlist [lindex $rowidlist $r]
3198 if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
3199 set col [idcol $idlist $id $col]
3200 lset rowidlist $r [linsert $idlist $col $id]
3201 changedrow $r
3206 proc layoutrows {row endrow} {
3207 global rowidlist rowisopt rowfinal displayorder
3208 global uparrowlen downarrowlen maxwidth mingaplen
3209 global children parentlist
3210 global commitidx viewcomplete curview commitrow
3212 set idlist {}
3213 if {$row > 0} {
3214 set rm1 [expr {$row - 1}]
3215 foreach id [lindex $rowidlist $rm1] {
3216 if {$id ne {}} {
3217 lappend idlist $id
3220 set final [lindex $rowfinal $rm1]
3222 for {} {$row < $endrow} {incr row} {
3223 set rm1 [expr {$row - 1}]
3224 if {$rm1 < 0 || $idlist eq {}} {
3225 set idlist [make_idlist $row]
3226 set final 1
3227 } else {
3228 set id [lindex $displayorder $rm1]
3229 set col [lsearch -exact $idlist $id]
3230 set idlist [lreplace $idlist $col $col]
3231 foreach p [lindex $parentlist $rm1] {
3232 if {[lsearch -exact $idlist $p] < 0} {
3233 set col [idcol $idlist $p $col]
3234 set idlist [linsert $idlist $col $p]
3235 # if not the first child, we have to insert a line going up
3236 if {$id ne [lindex $children($curview,$p) 0]} {
3237 makeupline $p $rm1 $row $col
3241 set id [lindex $displayorder $row]
3242 if {$row > $downarrowlen} {
3243 set termrow [expr {$row - $downarrowlen - 1}]
3244 foreach p [lindex $parentlist $termrow] {
3245 set i [lsearch -exact $idlist $p]
3246 if {$i < 0} continue
3247 set nr [nextuse $p $termrow]
3248 if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
3249 set idlist [lreplace $idlist $i $i]
3253 set col [lsearch -exact $idlist $id]
3254 if {$col < 0} {
3255 set col [idcol $idlist $id]
3256 set idlist [linsert $idlist $col $id]
3257 if {$children($curview,$id) ne {}} {
3258 makeupline $id $rm1 $row $col
3261 set r [expr {$row + $uparrowlen - 1}]
3262 if {$r < $commitidx($curview)} {
3263 set x $col
3264 foreach p [lindex $parentlist $r] {
3265 if {[lsearch -exact $idlist $p] >= 0} continue
3266 set fk [lindex $children($curview,$p) 0]
3267 if {$commitrow($curview,$fk) < $row} {
3268 set x [idcol $idlist $p $x]
3269 set idlist [linsert $idlist $x $p]
3272 if {[incr r] < $commitidx($curview)} {
3273 set p [lindex $displayorder $r]
3274 if {[lsearch -exact $idlist $p] < 0} {
3275 set fk [lindex $children($curview,$p) 0]
3276 if {$fk ne {} && $commitrow($curview,$fk) < $row} {
3277 set x [idcol $idlist $p $x]
3278 set idlist [linsert $idlist $x $p]
3284 if {$final && !$viewcomplete($curview) &&
3285 $row + $uparrowlen + $mingaplen + $downarrowlen
3286 >= $commitidx($curview)} {
3287 set final 0
3289 set l [llength $rowidlist]
3290 if {$row == $l} {
3291 lappend rowidlist $idlist
3292 lappend rowisopt 0
3293 lappend rowfinal $final
3294 } elseif {$row < $l} {
3295 if {![rowsequal $idlist [lindex $rowidlist $row]]} {
3296 lset rowidlist $row $idlist
3297 changedrow $row
3299 lset rowfinal $row $final
3300 } else {
3301 set pad [ntimes [expr {$row - $l}] {}]
3302 set rowidlist [concat $rowidlist $pad]
3303 lappend rowidlist $idlist
3304 set rowfinal [concat $rowfinal $pad]
3305 lappend rowfinal $final
3306 set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
3309 return $row
3312 proc changedrow {row} {
3313 global displayorder iddrawn rowisopt need_redisplay
3315 set l [llength $rowisopt]
3316 if {$row < $l} {
3317 lset rowisopt $row 0
3318 if {$row + 1 < $l} {
3319 lset rowisopt [expr {$row + 1}] 0
3320 if {$row + 2 < $l} {
3321 lset rowisopt [expr {$row + 2}] 0
3325 set id [lindex $displayorder $row]
3326 if {[info exists iddrawn($id)]} {
3327 set need_redisplay 1
3331 proc insert_pad {row col npad} {
3332 global rowidlist
3334 set pad [ntimes $npad {}]
3335 set idlist [lindex $rowidlist $row]
3336 set bef [lrange $idlist 0 [expr {$col - 1}]]
3337 set aft [lrange $idlist $col end]
3338 set i [lsearch -exact $aft {}]
3339 if {$i > 0} {
3340 set aft [lreplace $aft $i $i]
3342 lset rowidlist $row [concat $bef $pad $aft]
3343 changedrow $row
3346 proc optimize_rows {row col endrow} {
3347 global rowidlist rowisopt displayorder curview children
3349 if {$row < 1} {
3350 set row 1
3352 for {} {$row < $endrow} {incr row; set col 0} {
3353 if {[lindex $rowisopt $row]} continue
3354 set haspad 0
3355 set y0 [expr {$row - 1}]
3356 set ym [expr {$row - 2}]
3357 set idlist [lindex $rowidlist $row]
3358 set previdlist [lindex $rowidlist $y0]
3359 if {$idlist eq {} || $previdlist eq {}} continue
3360 if {$ym >= 0} {
3361 set pprevidlist [lindex $rowidlist $ym]
3362 if {$pprevidlist eq {}} continue
3363 } else {
3364 set pprevidlist {}
3366 set x0 -1
3367 set xm -1
3368 for {} {$col < [llength $idlist]} {incr col} {
3369 set id [lindex $idlist $col]
3370 if {[lindex $previdlist $col] eq $id} continue
3371 if {$id eq {}} {
3372 set haspad 1
3373 continue
3375 set x0 [lsearch -exact $previdlist $id]
3376 if {$x0 < 0} continue
3377 set z [expr {$x0 - $col}]
3378 set isarrow 0
3379 set z0 {}
3380 if {$ym >= 0} {
3381 set xm [lsearch -exact $pprevidlist $id]
3382 if {$xm >= 0} {
3383 set z0 [expr {$xm - $x0}]
3386 if {$z0 eq {}} {
3387 # if row y0 is the first child of $id then it's not an arrow
3388 if {[lindex $children($curview,$id) 0] ne
3389 [lindex $displayorder $y0]} {
3390 set isarrow 1
3393 if {!$isarrow && $id ne [lindex $displayorder $row] &&
3394 [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
3395 set isarrow 1
3397 # Looking at lines from this row to the previous row,
3398 # make them go straight up if they end in an arrow on
3399 # the previous row; otherwise make them go straight up
3400 # or at 45 degrees.
3401 if {$z < -1 || ($z < 0 && $isarrow)} {
3402 # Line currently goes left too much;
3403 # insert pads in the previous row, then optimize it
3404 set npad [expr {-1 - $z + $isarrow}]
3405 insert_pad $y0 $x0 $npad
3406 if {$y0 > 0} {
3407 optimize_rows $y0 $x0 $row
3409 set previdlist [lindex $rowidlist $y0]
3410 set x0 [lsearch -exact $previdlist $id]
3411 set z [expr {$x0 - $col}]
3412 if {$z0 ne {}} {
3413 set pprevidlist [lindex $rowidlist $ym]
3414 set xm [lsearch -exact $pprevidlist $id]
3415 set z0 [expr {$xm - $x0}]
3417 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3418 # Line currently goes right too much;
3419 # insert pads in this line
3420 set npad [expr {$z - 1 + $isarrow}]
3421 insert_pad $row $col $npad
3422 set idlist [lindex $rowidlist $row]
3423 incr col $npad
3424 set z [expr {$x0 - $col}]
3425 set haspad 1
3427 if {$z0 eq {} && !$isarrow && $ym >= 0} {
3428 # this line links to its first child on row $row-2
3429 set id [lindex $displayorder $ym]
3430 set xc [lsearch -exact $pprevidlist $id]
3431 if {$xc >= 0} {
3432 set z0 [expr {$xc - $x0}]
3435 # avoid lines jigging left then immediately right
3436 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3437 insert_pad $y0 $x0 1
3438 incr x0
3439 optimize_rows $y0 $x0 $row
3440 set previdlist [lindex $rowidlist $y0]
3443 if {!$haspad} {
3444 # Find the first column that doesn't have a line going right
3445 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3446 set id [lindex $idlist $col]
3447 if {$id eq {}} break
3448 set x0 [lsearch -exact $previdlist $id]
3449 if {$x0 < 0} {
3450 # check if this is the link to the first child
3451 set kid [lindex $displayorder $y0]
3452 if {[lindex $children($curview,$id) 0] eq $kid} {
3453 # it is, work out offset to child
3454 set x0 [lsearch -exact $previdlist $kid]
3457 if {$x0 <= $col} break
3459 # Insert a pad at that column as long as it has a line and
3460 # isn't the last column
3461 if {$x0 >= 0 && [incr col] < [llength $idlist]} {
3462 set idlist [linsert $idlist $col {}]
3463 lset rowidlist $row $idlist
3464 changedrow $row
3470 proc xc {row col} {
3471 global canvx0 linespc
3472 return [expr {$canvx0 + $col * $linespc}]
3475 proc yc {row} {
3476 global canvy0 linespc
3477 return [expr {$canvy0 + $row * $linespc}]
3480 proc linewidth {id} {
3481 global thickerline lthickness
3483 set wid $lthickness
3484 if {[info exists thickerline] && $id eq $thickerline} {
3485 set wid [expr {2 * $lthickness}]
3487 return $wid
3490 proc rowranges {id} {
3491 global commitrow curview children uparrowlen downarrowlen
3492 global rowidlist
3494 set kids $children($curview,$id)
3495 if {$kids eq {}} {
3496 return {}
3498 set ret {}
3499 lappend kids $id
3500 foreach child $kids {
3501 if {![info exists commitrow($curview,$child)]} break
3502 set row $commitrow($curview,$child)
3503 if {![info exists prev]} {
3504 lappend ret [expr {$row + 1}]
3505 } else {
3506 if {$row <= $prevrow} {
3507 puts "oops children out of order [shortids $id] $row < [shortids $prev] $prevrow"
3509 # see if the line extends the whole way from prevrow to row
3510 if {$row > $prevrow + $uparrowlen + $downarrowlen &&
3511 [lsearch -exact [lindex $rowidlist \
3512 [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
3513 # it doesn't, see where it ends
3514 set r [expr {$prevrow + $downarrowlen}]
3515 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
3516 while {[incr r -1] > $prevrow &&
3517 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
3518 } else {
3519 while {[incr r] <= $row &&
3520 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
3521 incr r -1
3523 lappend ret $r
3524 # see where it starts up again
3525 set r [expr {$row - $uparrowlen}]
3526 if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
3527 while {[incr r] < $row &&
3528 [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
3529 } else {
3530 while {[incr r -1] >= $prevrow &&
3531 [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
3532 incr r
3534 lappend ret $r
3537 if {$child eq $id} {
3538 lappend ret $row
3540 set prev $id
3541 set prevrow $row
3543 return $ret
3546 proc drawlineseg {id row endrow arrowlow} {
3547 global rowidlist displayorder iddrawn linesegs
3548 global canv colormap linespc curview maxlinelen parentlist
3550 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3551 set le [expr {$row + 1}]
3552 set arrowhigh 1
3553 while {1} {
3554 set c [lsearch -exact [lindex $rowidlist $le] $id]
3555 if {$c < 0} {
3556 incr le -1
3557 break
3559 lappend cols $c
3560 set x [lindex $displayorder $le]
3561 if {$x eq $id} {
3562 set arrowhigh 0
3563 break
3565 if {[info exists iddrawn($x)] || $le == $endrow} {
3566 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3567 if {$c >= 0} {
3568 lappend cols $c
3569 set arrowhigh 0
3571 break
3573 incr le
3575 if {$le <= $row} {
3576 return $row
3579 set lines {}
3580 set i 0
3581 set joinhigh 0
3582 if {[info exists linesegs($id)]} {
3583 set lines $linesegs($id)
3584 foreach li $lines {
3585 set r0 [lindex $li 0]
3586 if {$r0 > $row} {
3587 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3588 set joinhigh 1
3590 break
3592 incr i
3595 set joinlow 0
3596 if {$i > 0} {
3597 set li [lindex $lines [expr {$i-1}]]
3598 set r1 [lindex $li 1]
3599 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3600 set joinlow 1
3604 set x [lindex $cols [expr {$le - $row}]]
3605 set xp [lindex $cols [expr {$le - 1 - $row}]]
3606 set dir [expr {$xp - $x}]
3607 if {$joinhigh} {
3608 set ith [lindex $lines $i 2]
3609 set coords [$canv coords $ith]
3610 set ah [$canv itemcget $ith -arrow]
3611 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3612 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3613 if {$x2 ne {} && $x - $x2 == $dir} {
3614 set coords [lrange $coords 0 end-2]
3616 } else {
3617 set coords [list [xc $le $x] [yc $le]]
3619 if {$joinlow} {
3620 set itl [lindex $lines [expr {$i-1}] 2]
3621 set al [$canv itemcget $itl -arrow]
3622 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3623 } elseif {$arrowlow} {
3624 if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
3625 [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
3626 set arrowlow 0
3629 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3630 for {set y $le} {[incr y -1] > $row} {} {
3631 set x $xp
3632 set xp [lindex $cols [expr {$y - 1 - $row}]]
3633 set ndir [expr {$xp - $x}]
3634 if {$dir != $ndir || $xp < 0} {
3635 lappend coords [xc $y $x] [yc $y]
3637 set dir $ndir
3639 if {!$joinlow} {
3640 if {$xp < 0} {
3641 # join parent line to first child
3642 set ch [lindex $displayorder $row]
3643 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3644 if {$xc < 0} {
3645 puts "oops: drawlineseg: child $ch not on row $row"
3646 } elseif {$xc != $x} {
3647 if {($arrowhigh && $le == $row + 1) || $dir == 0} {
3648 set d [expr {int(0.5 * $linespc)}]
3649 set x1 [xc $row $x]
3650 if {$xc < $x} {
3651 set x2 [expr {$x1 - $d}]
3652 } else {
3653 set x2 [expr {$x1 + $d}]
3655 set y2 [yc $row]
3656 set y1 [expr {$y2 + $d}]
3657 lappend coords $x1 $y1 $x2 $y2
3658 } elseif {$xc < $x - 1} {
3659 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3660 } elseif {$xc > $x + 1} {
3661 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3663 set x $xc
3665 lappend coords [xc $row $x] [yc $row]
3666 } else {
3667 set xn [xc $row $xp]
3668 set yn [yc $row]
3669 lappend coords $xn $yn
3671 if {!$joinhigh} {
3672 assigncolor $id
3673 set t [$canv create line $coords -width [linewidth $id] \
3674 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3675 $canv lower $t
3676 bindline $t $id
3677 set lines [linsert $lines $i [list $row $le $t]]
3678 } else {
3679 $canv coords $ith $coords
3680 if {$arrow ne $ah} {
3681 $canv itemconf $ith -arrow $arrow
3683 lset lines $i 0 $row
3685 } else {
3686 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3687 set ndir [expr {$xo - $xp}]
3688 set clow [$canv coords $itl]
3689 if {$dir == $ndir} {
3690 set clow [lrange $clow 2 end]
3692 set coords [concat $coords $clow]
3693 if {!$joinhigh} {
3694 lset lines [expr {$i-1}] 1 $le
3695 } else {
3696 # coalesce two pieces
3697 $canv delete $ith
3698 set b [lindex $lines [expr {$i-1}] 0]
3699 set e [lindex $lines $i 1]
3700 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3702 $canv coords $itl $coords
3703 if {$arrow ne $al} {
3704 $canv itemconf $itl -arrow $arrow
3708 set linesegs($id) $lines
3709 return $le
3712 proc drawparentlinks {id row} {
3713 global rowidlist canv colormap curview parentlist
3714 global idpos linespc
3716 set rowids [lindex $rowidlist $row]
3717 set col [lsearch -exact $rowids $id]
3718 if {$col < 0} return
3719 set olds [lindex $parentlist $row]
3720 set row2 [expr {$row + 1}]
3721 set x [xc $row $col]
3722 set y [yc $row]
3723 set y2 [yc $row2]
3724 set d [expr {int(0.5 * $linespc)}]
3725 set ymid [expr {$y + $d}]
3726 set ids [lindex $rowidlist $row2]
3727 # rmx = right-most X coord used
3728 set rmx 0
3729 foreach p $olds {
3730 set i [lsearch -exact $ids $p]
3731 if {$i < 0} {
3732 puts "oops, parent $p of $id not in list"
3733 continue
3735 set x2 [xc $row2 $i]
3736 if {$x2 > $rmx} {
3737 set rmx $x2
3739 set j [lsearch -exact $rowids $p]
3740 if {$j < 0} {
3741 # drawlineseg will do this one for us
3742 continue
3744 assigncolor $p
3745 # should handle duplicated parents here...
3746 set coords [list $x $y]
3747 if {$i != $col} {
3748 # if attaching to a vertical segment, draw a smaller
3749 # slant for visual distinctness
3750 if {$i == $j} {
3751 if {$i < $col} {
3752 lappend coords [expr {$x2 + $d}] $y $x2 $ymid
3753 } else {
3754 lappend coords [expr {$x2 - $d}] $y $x2 $ymid
3756 } elseif {$i < $col && $i < $j} {
3757 # segment slants towards us already
3758 lappend coords [xc $row $j] $y
3759 } else {
3760 if {$i < $col - 1} {
3761 lappend coords [expr {$x2 + $linespc}] $y
3762 } elseif {$i > $col + 1} {
3763 lappend coords [expr {$x2 - $linespc}] $y
3765 lappend coords $x2 $y2
3767 } else {
3768 lappend coords $x2 $y2
3770 set t [$canv create line $coords -width [linewidth $p] \
3771 -fill $colormap($p) -tags lines.$p]
3772 $canv lower $t
3773 bindline $t $p
3775 if {$rmx > [lindex $idpos($id) 1]} {
3776 lset idpos($id) 1 $rmx
3777 redrawtags $id
3781 proc drawlines {id} {
3782 global canv
3784 $canv itemconf lines.$id -width [linewidth $id]
3787 proc drawcmittext {id row col} {
3788 global linespc canv canv2 canv3 canvy0 fgcolor curview
3789 global commitlisted commitinfo rowidlist parentlist
3790 global rowtextx idpos idtags idheads idotherrefs
3791 global linehtag linentag linedtag selectedline
3792 global canvxmax boldrows boldnamerows fgcolor nullid nullid2
3794 # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
3795 set listed [lindex $commitlisted $row]
3796 if {$id eq $nullid} {
3797 set ofill red
3798 } elseif {$id eq $nullid2} {
3799 set ofill green
3800 } else {
3801 set ofill [expr {$listed != 0 ? $listed == 2 ? "gray" : "blue" : "white"}]
3803 set x [xc $row $col]
3804 set y [yc $row]
3805 set orad [expr {$linespc / 3}]
3806 if {$listed <= 2} {
3807 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3808 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3809 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3810 } elseif {$listed == 3} {
3811 # triangle pointing left for left-side commits
3812 set t [$canv create polygon \
3813 [expr {$x - $orad}] $y \
3814 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3815 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3816 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3817 } else {
3818 # triangle pointing right for right-side commits
3819 set t [$canv create polygon \
3820 [expr {$x + $orad - 1}] $y \
3821 [expr {$x - $orad}] [expr {$y - $orad}] \
3822 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3823 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3825 $canv raise $t
3826 $canv bind $t <1> {selcanvline {} %x %y}
3827 set rmx [llength [lindex $rowidlist $row]]
3828 set olds [lindex $parentlist $row]
3829 if {$olds ne {}} {
3830 set nextids [lindex $rowidlist [expr {$row + 1}]]
3831 foreach p $olds {
3832 set i [lsearch -exact $nextids $p]
3833 if {$i > $rmx} {
3834 set rmx $i
3838 set xt [xc $row $rmx]
3839 set rowtextx($row) $xt
3840 set idpos($id) [list $x $xt $y]
3841 if {[info exists idtags($id)] || [info exists idheads($id)]
3842 || [info exists idotherrefs($id)]} {
3843 set xt [drawtags $id $x $xt $y]
3845 set headline [lindex $commitinfo($id) 0]
3846 set name [lindex $commitinfo($id) 1]
3847 set date [lindex $commitinfo($id) 2]
3848 set date [formatdate $date]
3849 set font mainfont
3850 set nfont mainfont
3851 set isbold [ishighlighted $row]
3852 if {$isbold > 0} {
3853 lappend boldrows $row
3854 set font mainfontbold
3855 if {$isbold > 1} {
3856 lappend boldnamerows $row
3857 set nfont mainfontbold
3860 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3861 -text $headline -font $font -tags text]
3862 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3863 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3864 -text $name -font $nfont -tags text]
3865 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3866 -text $date -font mainfont -tags text]
3867 if {[info exists selectedline] && $selectedline == $row} {
3868 make_secsel $row
3870 set xr [expr {$xt + [font measure $font $headline]}]
3871 if {$xr > $canvxmax} {
3872 set canvxmax $xr
3873 setcanvscroll
3877 proc drawcmitrow {row} {
3878 global displayorder rowidlist nrows_drawn
3879 global iddrawn markingmatches
3880 global commitinfo parentlist numcommits
3881 global filehighlight fhighlights findpattern nhighlights
3882 global hlview vhighlights
3883 global highlight_related rhighlights
3885 if {$row >= $numcommits} return
3887 set id [lindex $displayorder $row]
3888 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3889 askvhighlight $row $id
3891 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3892 askfilehighlight $row $id
3894 if {$findpattern ne {} && ![info exists nhighlights($row)]} {
3895 askfindhighlight $row $id
3897 if {$highlight_related ne [mc "None"] && ![info exists rhighlights($row)]} {
3898 askrelhighlight $row $id
3900 if {![info exists iddrawn($id)]} {
3901 set col [lsearch -exact [lindex $rowidlist $row] $id]
3902 if {$col < 0} {
3903 puts "oops, row $row id $id not in list"
3904 return
3906 if {![info exists commitinfo($id)]} {
3907 getcommit $id
3909 assigncolor $id
3910 drawcmittext $id $row $col
3911 set iddrawn($id) 1
3912 incr nrows_drawn
3914 if {$markingmatches} {
3915 markrowmatches $row $id
3919 proc drawcommits {row {endrow {}}} {
3920 global numcommits iddrawn displayorder curview need_redisplay
3921 global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
3923 if {$row < 0} {
3924 set row 0
3926 if {$endrow eq {}} {
3927 set endrow $row
3929 if {$endrow >= $numcommits} {
3930 set endrow [expr {$numcommits - 1}]
3933 set rl1 [expr {$row - $downarrowlen - 3}]
3934 if {$rl1 < 0} {
3935 set rl1 0
3937 set ro1 [expr {$row - 3}]
3938 if {$ro1 < 0} {
3939 set ro1 0
3941 set r2 [expr {$endrow + $uparrowlen + 3}]
3942 if {$r2 > $numcommits} {
3943 set r2 $numcommits
3945 for {set r $rl1} {$r < $r2} {incr r} {
3946 if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
3947 if {$rl1 < $r} {
3948 layoutrows $rl1 $r
3950 set rl1 [expr {$r + 1}]
3953 if {$rl1 < $r} {
3954 layoutrows $rl1 $r
3956 optimize_rows $ro1 0 $r2
3957 if {$need_redisplay || $nrows_drawn > 2000} {
3958 clear_display
3959 drawvisible
3962 # make the lines join to already-drawn rows either side
3963 set r [expr {$row - 1}]
3964 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3965 set r $row
3967 set er [expr {$endrow + 1}]
3968 if {$er >= $numcommits ||
3969 ![info exists iddrawn([lindex $displayorder $er])]} {
3970 set er $endrow
3972 for {} {$r <= $er} {incr r} {
3973 set id [lindex $displayorder $r]
3974 set wasdrawn [info exists iddrawn($id)]
3975 drawcmitrow $r
3976 if {$r == $er} break
3977 set nextid [lindex $displayorder [expr {$r + 1}]]
3978 if {$wasdrawn && [info exists iddrawn($nextid)]} continue
3979 drawparentlinks $id $r
3981 set rowids [lindex $rowidlist $r]
3982 foreach lid $rowids {
3983 if {$lid eq {}} continue
3984 if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
3985 if {$lid eq $id} {
3986 # see if this is the first child of any of its parents
3987 foreach p [lindex $parentlist $r] {
3988 if {[lsearch -exact $rowids $p] < 0} {
3989 # make this line extend up to the child
3990 set lineend($p) [drawlineseg $p $r $er 0]
3993 } else {
3994 set lineend($lid) [drawlineseg $lid $r $er 1]
4000 proc drawfrac {f0 f1} {
4001 global canv linespc
4003 set ymax [lindex [$canv cget -scrollregion] 3]
4004 if {$ymax eq {} || $ymax == 0} return
4005 set y0 [expr {int($f0 * $ymax)}]
4006 set row [expr {int(($y0 - 3) / $linespc) - 1}]
4007 set y1 [expr {int($f1 * $ymax)}]
4008 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
4009 drawcommits $row $endrow
4012 proc drawvisible {} {
4013 global canv
4014 eval drawfrac [$canv yview]
4017 proc clear_display {} {
4018 global iddrawn linesegs need_redisplay nrows_drawn
4019 global vhighlights fhighlights nhighlights rhighlights
4021 allcanvs delete all
4022 catch {unset iddrawn}
4023 catch {unset linesegs}
4024 catch {unset vhighlights}
4025 catch {unset fhighlights}
4026 catch {unset nhighlights}
4027 catch {unset rhighlights}
4028 set need_redisplay 0
4029 set nrows_drawn 0
4032 proc findcrossings {id} {
4033 global rowidlist parentlist numcommits displayorder
4035 set cross {}
4036 set ccross {}
4037 foreach {s e} [rowranges $id] {
4038 if {$e >= $numcommits} {
4039 set e [expr {$numcommits - 1}]
4041 if {$e <= $s} continue
4042 for {set row $e} {[incr row -1] >= $s} {} {
4043 set x [lsearch -exact [lindex $rowidlist $row] $id]
4044 if {$x < 0} break
4045 set olds [lindex $parentlist $row]
4046 set kid [lindex $displayorder $row]
4047 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
4048 if {$kidx < 0} continue
4049 set nextrow [lindex $rowidlist [expr {$row + 1}]]
4050 foreach p $olds {
4051 set px [lsearch -exact $nextrow $p]
4052 if {$px < 0} continue
4053 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
4054 if {[lsearch -exact $ccross $p] >= 0} continue
4055 if {$x == $px + ($kidx < $px? -1: 1)} {
4056 lappend ccross $p
4057 } elseif {[lsearch -exact $cross $p] < 0} {
4058 lappend cross $p
4064 return [concat $ccross {{}} $cross]
4067 proc assigncolor {id} {
4068 global colormap colors nextcolor
4069 global commitrow parentlist children children curview
4071 if {[info exists colormap($id)]} return
4072 set ncolors [llength $colors]
4073 if {[info exists children($curview,$id)]} {
4074 set kids $children($curview,$id)
4075 } else {
4076 set kids {}
4078 if {[llength $kids] == 1} {
4079 set child [lindex $kids 0]
4080 if {[info exists colormap($child)]
4081 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
4082 set colormap($id) $colormap($child)
4083 return
4086 set badcolors {}
4087 set origbad {}
4088 foreach x [findcrossings $id] {
4089 if {$x eq {}} {
4090 # delimiter between corner crossings and other crossings
4091 if {[llength $badcolors] >= $ncolors - 1} break
4092 set origbad $badcolors
4094 if {[info exists colormap($x)]
4095 && [lsearch -exact $badcolors $colormap($x)] < 0} {
4096 lappend badcolors $colormap($x)
4099 if {[llength $badcolors] >= $ncolors} {
4100 set badcolors $origbad
4102 set origbad $badcolors
4103 if {[llength $badcolors] < $ncolors - 1} {
4104 foreach child $kids {
4105 if {[info exists colormap($child)]
4106 && [lsearch -exact $badcolors $colormap($child)] < 0} {
4107 lappend badcolors $colormap($child)
4109 foreach p [lindex $parentlist $commitrow($curview,$child)] {
4110 if {[info exists colormap($p)]
4111 && [lsearch -exact $badcolors $colormap($p)] < 0} {
4112 lappend badcolors $colormap($p)
4116 if {[llength $badcolors] >= $ncolors} {
4117 set badcolors $origbad
4120 for {set i 0} {$i <= $ncolors} {incr i} {
4121 set c [lindex $colors $nextcolor]
4122 if {[incr nextcolor] >= $ncolors} {
4123 set nextcolor 0
4125 if {[lsearch -exact $badcolors $c]} break
4127 set colormap($id) $c
4130 proc bindline {t id} {
4131 global canv
4133 $canv bind $t <Enter> "lineenter %x %y $id"
4134 $canv bind $t <Motion> "linemotion %x %y $id"
4135 $canv bind $t <Leave> "lineleave $id"
4136 $canv bind $t <Button-1> "lineclick %x %y $id 1"
4139 proc drawtags {id x xt y1} {
4140 global idtags idheads idotherrefs mainhead
4141 global linespc lthickness
4142 global canv commitrow rowtextx curview fgcolor bgcolor
4144 set marks {}
4145 set ntags 0
4146 set nheads 0
4147 if {[info exists idtags($id)]} {
4148 set marks $idtags($id)
4149 set ntags [llength $marks]
4151 if {[info exists idheads($id)]} {
4152 set marks [concat $marks $idheads($id)]
4153 set nheads [llength $idheads($id)]
4155 if {[info exists idotherrefs($id)]} {
4156 set marks [concat $marks $idotherrefs($id)]
4158 if {$marks eq {}} {
4159 return $xt
4162 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
4163 set yt [expr {$y1 - 0.5 * $linespc}]
4164 set yb [expr {$yt + $linespc - 1}]
4165 set xvals {}
4166 set wvals {}
4167 set i -1
4168 foreach tag $marks {
4169 incr i
4170 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
4171 set wid [font measure mainfontbold $tag]
4172 } else {
4173 set wid [font measure mainfont $tag]
4175 lappend xvals $xt
4176 lappend wvals $wid
4177 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
4179 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
4180 -width $lthickness -fill black -tags tag.$id]
4181 $canv lower $t
4182 foreach tag $marks x $xvals wid $wvals {
4183 set xl [expr {$x + $delta}]
4184 set xr [expr {$x + $delta + $wid + $lthickness}]
4185 set font mainfont
4186 if {[incr ntags -1] >= 0} {
4187 # draw a tag
4188 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
4189 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
4190 -width 1 -outline black -fill yellow -tags tag.$id]
4191 $canv bind $t <1> [list showtag $tag 1]
4192 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
4193 } else {
4194 # draw a head or other ref
4195 if {[incr nheads -1] >= 0} {
4196 set col green
4197 if {$tag eq $mainhead} {
4198 set font mainfontbold
4200 } else {
4201 set col "#ddddff"
4203 set xl [expr {$xl - $delta/2}]
4204 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
4205 -width 1 -outline black -fill $col -tags tag.$id
4206 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
4207 set rwid [font measure mainfont $remoteprefix]
4208 set xi [expr {$x + 1}]
4209 set yti [expr {$yt + 1}]
4210 set xri [expr {$x + $rwid}]
4211 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
4212 -width 0 -fill "#ffddaa" -tags tag.$id
4215 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
4216 -font $font -tags [list tag.$id text]]
4217 if {$ntags >= 0} {
4218 $canv bind $t <1> [list showtag $tag 1]
4219 } elseif {$nheads >= 0} {
4220 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
4223 return $xt
4226 proc xcoord {i level ln} {
4227 global canvx0 xspc1 xspc2
4229 set x [expr {$canvx0 + $i * $xspc1($ln)}]
4230 if {$i > 0 && $i == $level} {
4231 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
4232 } elseif {$i > $level} {
4233 set x [expr {$x + $xspc2 - $xspc1($ln)}]
4235 return $x
4238 proc show_status {msg} {
4239 global canv fgcolor
4241 clear_display
4242 $canv create text 3 3 -anchor nw -text $msg -font mainfont \
4243 -tags text -fill $fgcolor
4246 # Insert a new commit as the child of the commit on row $row.
4247 # The new commit will be displayed on row $row and the commits
4248 # on that row and below will move down one row.
4249 proc insertrow {row newcmit} {
4250 global displayorder parentlist commitlisted children
4251 global commitrow curview rowidlist rowisopt rowfinal numcommits
4252 global numcommits
4253 global selectedline commitidx ordertok
4255 if {$row >= $numcommits} {
4256 puts "oops, inserting new row $row but only have $numcommits rows"
4257 return
4259 set p [lindex $displayorder $row]
4260 set displayorder [linsert $displayorder $row $newcmit]
4261 set parentlist [linsert $parentlist $row $p]
4262 set kids $children($curview,$p)
4263 lappend kids $newcmit
4264 set children($curview,$p) $kids
4265 set children($curview,$newcmit) {}
4266 set commitlisted [linsert $commitlisted $row 1]
4267 set l [llength $displayorder]
4268 for {set r $row} {$r < $l} {incr r} {
4269 set id [lindex $displayorder $r]
4270 set commitrow($curview,$id) $r
4272 incr commitidx($curview)
4273 set ordertok($curview,$newcmit) $ordertok($curview,$p)
4275 if {$row < [llength $rowidlist]} {
4276 set idlist [lindex $rowidlist $row]
4277 if {$idlist ne {}} {
4278 if {[llength $kids] == 1} {
4279 set col [lsearch -exact $idlist $p]
4280 lset idlist $col $newcmit
4281 } else {
4282 set col [llength $idlist]
4283 lappend idlist $newcmit
4286 set rowidlist [linsert $rowidlist $row $idlist]
4287 set rowisopt [linsert $rowisopt $row 0]
4288 set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]]
4291 incr numcommits
4293 if {[info exists selectedline] && $selectedline >= $row} {
4294 incr selectedline
4296 redisplay
4299 # Remove a commit that was inserted with insertrow on row $row.
4300 proc removerow {row} {
4301 global displayorder parentlist commitlisted children
4302 global commitrow curview rowidlist rowisopt rowfinal numcommits
4303 global numcommits
4304 global linesegends selectedline commitidx
4306 if {$row >= $numcommits} {
4307 puts "oops, removing row $row but only have $numcommits rows"
4308 return
4310 set rp1 [expr {$row + 1}]
4311 set id [lindex $displayorder $row]
4312 set p [lindex $parentlist $row]
4313 set displayorder [lreplace $displayorder $row $row]
4314 set parentlist [lreplace $parentlist $row $row]
4315 set commitlisted [lreplace $commitlisted $row $row]
4316 set kids $children($curview,$p)
4317 set i [lsearch -exact $kids $id]
4318 if {$i >= 0} {
4319 set kids [lreplace $kids $i $i]
4320 set children($curview,$p) $kids
4322 set l [llength $displayorder]
4323 for {set r $row} {$r < $l} {incr r} {
4324 set id [lindex $displayorder $r]
4325 set commitrow($curview,$id) $r
4327 incr commitidx($curview) -1
4329 if {$row < [llength $rowidlist]} {
4330 set rowidlist [lreplace $rowidlist $row $row]
4331 set rowisopt [lreplace $rowisopt $row $row]
4332 set rowfinal [lreplace $rowfinal $row $row]
4335 incr numcommits -1
4337 if {[info exists selectedline] && $selectedline > $row} {
4338 incr selectedline -1
4340 redisplay
4343 # Don't change the text pane cursor if it is currently the hand cursor,
4344 # showing that we are over a sha1 ID link.
4345 proc settextcursor {c} {
4346 global ctext curtextcursor
4348 if {[$ctext cget -cursor] == $curtextcursor} {
4349 $ctext config -cursor $c
4351 set curtextcursor $c
4354 proc nowbusy {what {name {}}} {
4355 global isbusy busyname statusw
4357 if {[array names isbusy] eq {}} {
4358 . config -cursor watch
4359 settextcursor watch
4361 set isbusy($what) 1
4362 set busyname($what) $name
4363 if {$name ne {}} {
4364 $statusw conf -text $name
4368 proc notbusy {what} {
4369 global isbusy maincursor textcursor busyname statusw
4371 catch {
4372 unset isbusy($what)
4373 if {$busyname($what) ne {} &&
4374 [$statusw cget -text] eq $busyname($what)} {
4375 $statusw conf -text {}
4378 if {[array names isbusy] eq {}} {
4379 . config -cursor $maincursor
4380 settextcursor $textcursor
4384 proc findmatches {f} {
4385 global findtype findstring
4386 if {$findtype == [mc "Regexp"]} {
4387 set matches [regexp -indices -all -inline $findstring $f]
4388 } else {
4389 set fs $findstring
4390 if {$findtype == [mc "IgnCase"]} {
4391 set f [string tolower $f]
4392 set fs [string tolower $fs]
4394 set matches {}
4395 set i 0
4396 set l [string length $fs]
4397 while {[set j [string first $fs $f $i]] >= 0} {
4398 lappend matches [list $j [expr {$j+$l-1}]]
4399 set i [expr {$j + $l}]
4402 return $matches
4405 proc dofind {{dirn 1} {wrap 1}} {
4406 global findstring findstartline findcurline selectedline numcommits
4407 global gdttype filehighlight fh_serial find_dirn findallowwrap
4409 if {[info exists find_dirn]} {
4410 if {$find_dirn == $dirn} return
4411 stopfinding
4413 focus .
4414 if {$findstring eq {} || $numcommits == 0} return
4415 if {![info exists selectedline]} {
4416 set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
4417 } else {
4418 set findstartline $selectedline
4420 set findcurline $findstartline
4421 nowbusy finding [mc "Searching"]
4422 if {$gdttype ne [mc "containing:"] && ![info exists filehighlight]} {
4423 after cancel do_file_hl $fh_serial
4424 do_file_hl $fh_serial
4426 set find_dirn $dirn
4427 set findallowwrap $wrap
4428 run findmore
4431 proc stopfinding {} {
4432 global find_dirn findcurline fprogcoord
4434 if {[info exists find_dirn]} {
4435 unset find_dirn
4436 unset findcurline
4437 notbusy finding
4438 set fprogcoord 0
4439 adjustprogress
4443 proc findmore {} {
4444 global commitdata commitinfo numcommits findpattern findloc
4445 global findstartline findcurline displayorder
4446 global find_dirn gdttype fhighlights fprogcoord
4447 global findallowwrap
4449 if {![info exists find_dirn]} {
4450 return 0
4452 set fldtypes [list [mc "Headline"] [mc "Author"] [mc "Date"] [mc "Committer"] [mc "CDate"] [mc "Comments"]]
4453 set l $findcurline
4454 set moretodo 0
4455 if {$find_dirn > 0} {
4456 incr l
4457 if {$l >= $numcommits} {
4458 set l 0
4460 if {$l <= $findstartline} {
4461 set lim [expr {$findstartline + 1}]
4462 } else {
4463 set lim $numcommits
4464 set moretodo $findallowwrap
4466 } else {
4467 if {$l == 0} {
4468 set l $numcommits
4470 incr l -1
4471 if {$l >= $findstartline} {
4472 set lim [expr {$findstartline - 1}]
4473 } else {
4474 set lim -1
4475 set moretodo $findallowwrap
4478 set n [expr {($lim - $l) * $find_dirn}]
4479 if {$n > 500} {
4480 set n 500
4481 set moretodo 1
4483 set found 0
4484 set domore 1
4485 if {$gdttype eq [mc "containing:"]} {
4486 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4487 set id [lindex $displayorder $l]
4488 # shouldn't happen unless git log doesn't give all the commits...
4489 if {![info exists commitdata($id)]} continue
4490 if {![doesmatch $commitdata($id)]} continue
4491 if {![info exists commitinfo($id)]} {
4492 getcommit $id
4494 set info $commitinfo($id)
4495 foreach f $info ty $fldtypes {
4496 if {($findloc eq [mc "All fields"] || $findloc eq $ty) &&
4497 [doesmatch $f]} {
4498 set found 1
4499 break
4502 if {$found} break
4504 } else {
4505 for {} {$n > 0} {incr n -1; incr l $find_dirn} {
4506 set id [lindex $displayorder $l]
4507 if {![info exists fhighlights($l)]} {
4508 askfilehighlight $l $id
4509 if {$domore} {
4510 set domore 0
4511 set findcurline [expr {$l - $find_dirn}]
4513 } elseif {$fhighlights($l)} {
4514 set found $domore
4515 break
4519 if {$found || ($domore && !$moretodo)} {
4520 unset findcurline
4521 unset find_dirn
4522 notbusy finding
4523 set fprogcoord 0
4524 adjustprogress
4525 if {$found} {
4526 findselectline $l
4527 } else {
4528 bell
4530 return 0
4532 if {!$domore} {
4533 flushhighlights
4534 } else {
4535 set findcurline [expr {$l - $find_dirn}]
4537 set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
4538 if {$n < 0} {
4539 incr n $numcommits
4541 set fprogcoord [expr {$n * 1.0 / $numcommits}]
4542 adjustprogress
4543 return $domore
4546 proc findselectline {l} {
4547 global findloc commentend ctext findcurline markingmatches gdttype
4549 set markingmatches 1
4550 set findcurline $l
4551 selectline $l 1
4552 if {$findloc == [mc "All fields"] || $findloc == [mc "Comments"]} {
4553 # highlight the matches in the comments
4554 set f [$ctext get 1.0 $commentend]
4555 set matches [findmatches $f]
4556 foreach match $matches {
4557 set start [lindex $match 0]
4558 set end [expr {[lindex $match 1] + 1}]
4559 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4562 drawvisible
4565 # mark the bits of a headline or author that match a find string
4566 proc markmatches {canv l str tag matches font row} {
4567 global selectedline
4569 set bbox [$canv bbox $tag]
4570 set x0 [lindex $bbox 0]
4571 set y0 [lindex $bbox 1]
4572 set y1 [lindex $bbox 3]
4573 foreach match $matches {
4574 set start [lindex $match 0]
4575 set end [lindex $match 1]
4576 if {$start > $end} continue
4577 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4578 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4579 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4580 [expr {$x0+$xlen+2}] $y1 \
4581 -outline {} -tags [list match$l matches] -fill yellow]
4582 $canv lower $t
4583 if {[info exists selectedline] && $row == $selectedline} {
4584 $canv raise $t secsel
4589 proc unmarkmatches {} {
4590 global markingmatches
4592 allcanvs delete matches
4593 set markingmatches 0
4594 stopfinding
4597 proc selcanvline {w x y} {
4598 global canv canvy0 ctext linespc
4599 global rowtextx
4600 set ymax [lindex [$canv cget -scrollregion] 3]
4601 if {$ymax == {}} return
4602 set yfrac [lindex [$canv yview] 0]
4603 set y [expr {$y + $yfrac * $ymax}]
4604 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4605 if {$l < 0} {
4606 set l 0
4608 if {$w eq $canv} {
4609 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4611 unmarkmatches
4612 selectline $l 1
4615 proc commit_descriptor {p} {
4616 global commitinfo
4617 if {![info exists commitinfo($p)]} {
4618 getcommit $p
4620 set l "..."
4621 if {[llength $commitinfo($p)] > 1} {
4622 set l [lindex $commitinfo($p) 0]
4624 return "$p ($l)\n"
4627 # append some text to the ctext widget, and make any SHA1 ID
4628 # that we know about be a clickable link.
4629 proc appendwithlinks {text tags} {
4630 global ctext commitrow linknum curview pendinglinks
4632 set start [$ctext index "end - 1c"]
4633 $ctext insert end $text $tags
4634 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4635 foreach l $links {
4636 set s [lindex $l 0]
4637 set e [lindex $l 1]
4638 set linkid [string range $text $s $e]
4639 incr e
4640 $ctext tag delete link$linknum
4641 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4642 setlink $linkid link$linknum
4643 incr linknum
4647 proc setlink {id lk} {
4648 global curview commitrow ctext pendinglinks commitinterest
4650 if {[info exists commitrow($curview,$id)]} {
4651 $ctext tag conf $lk -foreground blue -underline 1
4652 $ctext tag bind $lk <1> [list selectline $commitrow($curview,$id) 1]
4653 $ctext tag bind $lk <Enter> {linkcursor %W 1}
4654 $ctext tag bind $lk <Leave> {linkcursor %W -1}
4655 } else {
4656 lappend pendinglinks($id) $lk
4657 lappend commitinterest($id) {makelink %I}
4661 proc makelink {id} {
4662 global pendinglinks
4664 if {![info exists pendinglinks($id)]} return
4665 foreach lk $pendinglinks($id) {
4666 setlink $id $lk
4668 unset pendinglinks($id)
4671 proc linkcursor {w inc} {
4672 global linkentercount curtextcursor
4674 if {[incr linkentercount $inc] > 0} {
4675 $w configure -cursor hand2
4676 } else {
4677 $w configure -cursor $curtextcursor
4678 if {$linkentercount < 0} {
4679 set linkentercount 0
4684 proc viewnextline {dir} {
4685 global canv linespc
4687 $canv delete hover
4688 set ymax [lindex [$canv cget -scrollregion] 3]
4689 set wnow [$canv yview]
4690 set wtop [expr {[lindex $wnow 0] * $ymax}]
4691 set newtop [expr {$wtop + $dir * $linespc}]
4692 if {$newtop < 0} {
4693 set newtop 0
4694 } elseif {$newtop > $ymax} {
4695 set newtop $ymax
4697 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4700 # add a list of tag or branch names at position pos
4701 # returns the number of names inserted
4702 proc appendrefs {pos ids var} {
4703 global ctext commitrow linknum curview $var maxrefs
4705 if {[catch {$ctext index $pos}]} {
4706 return 0
4708 $ctext conf -state normal
4709 $ctext delete $pos "$pos lineend"
4710 set tags {}
4711 foreach id $ids {
4712 foreach tag [set $var\($id\)] {
4713 lappend tags [list $tag $id]
4716 if {[llength $tags] > $maxrefs} {
4717 $ctext insert $pos "many ([llength $tags])"
4718 } else {
4719 set tags [lsort -index 0 -decreasing $tags]
4720 set sep {}
4721 foreach ti $tags {
4722 set id [lindex $ti 1]
4723 set lk link$linknum
4724 incr linknum
4725 $ctext tag delete $lk
4726 $ctext insert $pos $sep
4727 $ctext insert $pos [lindex $ti 0] $lk
4728 setlink $id $lk
4729 set sep ", "
4732 $ctext conf -state disabled
4733 return [llength $tags]
4736 # called when we have finished computing the nearby tags
4737 proc dispneartags {delay} {
4738 global selectedline currentid showneartags tagphase
4740 if {![info exists selectedline] || !$showneartags} return
4741 after cancel dispnexttag
4742 if {$delay} {
4743 after 200 dispnexttag
4744 set tagphase -1
4745 } else {
4746 after idle dispnexttag
4747 set tagphase 0
4751 proc dispnexttag {} {
4752 global selectedline currentid showneartags tagphase ctext
4754 if {![info exists selectedline] || !$showneartags} return
4755 switch -- $tagphase {
4757 set dtags [desctags $currentid]
4758 if {$dtags ne {}} {
4759 appendrefs precedes $dtags idtags
4763 set atags [anctags $currentid]
4764 if {$atags ne {}} {
4765 appendrefs follows $atags idtags
4769 set dheads [descheads $currentid]
4770 if {$dheads ne {}} {
4771 if {[appendrefs branch $dheads idheads] > 1
4772 && [$ctext get "branch -3c"] eq "h"} {
4773 # turn "Branch" into "Branches"
4774 $ctext conf -state normal
4775 $ctext insert "branch -2c" "es"
4776 $ctext conf -state disabled
4781 if {[incr tagphase] <= 2} {
4782 after idle dispnexttag
4786 proc make_secsel {l} {
4787 global linehtag linentag linedtag canv canv2 canv3
4789 if {![info exists linehtag($l)]} return
4790 $canv delete secsel
4791 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4792 -tags secsel -fill [$canv cget -selectbackground]]
4793 $canv lower $t
4794 $canv2 delete secsel
4795 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4796 -tags secsel -fill [$canv2 cget -selectbackground]]
4797 $canv2 lower $t
4798 $canv3 delete secsel
4799 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4800 -tags secsel -fill [$canv3 cget -selectbackground]]
4801 $canv3 lower $t
4804 proc selectline {l isnew} {
4805 global canv ctext commitinfo selectedline
4806 global displayorder
4807 global canvy0 linespc parentlist children curview
4808 global currentid sha1entry
4809 global commentend idtags linknum
4810 global mergemax numcommits pending_select
4811 global cmitmode showneartags allcommits
4812 global autoselect
4814 catch {unset pending_select}
4815 $canv delete hover
4816 normalline
4817 unsel_reflist
4818 stopfinding
4819 if {$l < 0 || $l >= $numcommits} return
4820 set y [expr {$canvy0 + $l * $linespc}]
4821 set ymax [lindex [$canv cget -scrollregion] 3]
4822 set ytop [expr {$y - $linespc - 1}]
4823 set ybot [expr {$y + $linespc + 1}]
4824 set wnow [$canv yview]
4825 set wtop [expr {[lindex $wnow 0] * $ymax}]
4826 set wbot [expr {[lindex $wnow 1] * $ymax}]
4827 set wh [expr {$wbot - $wtop}]
4828 set newtop $wtop
4829 if {$ytop < $wtop} {
4830 if {$ybot < $wtop} {
4831 set newtop [expr {$y - $wh / 2.0}]
4832 } else {
4833 set newtop $ytop
4834 if {$newtop > $wtop - $linespc} {
4835 set newtop [expr {$wtop - $linespc}]
4838 } elseif {$ybot > $wbot} {
4839 if {$ytop > $wbot} {
4840 set newtop [expr {$y - $wh / 2.0}]
4841 } else {
4842 set newtop [expr {$ybot - $wh}]
4843 if {$newtop < $wtop + $linespc} {
4844 set newtop [expr {$wtop + $linespc}]
4848 if {$newtop != $wtop} {
4849 if {$newtop < 0} {
4850 set newtop 0
4852 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4853 drawvisible
4856 make_secsel $l
4858 if {$isnew} {
4859 addtohistory [list selectline $l 0]
4862 set selectedline $l
4864 set id [lindex $displayorder $l]
4865 set currentid $id
4866 $sha1entry delete 0 end
4867 $sha1entry insert 0 $id
4868 if {$autoselect} {
4869 $sha1entry selection from 0
4870 $sha1entry selection to end
4872 rhighlight_sel $id
4874 $ctext conf -state normal
4875 clear_ctext
4876 set linknum 0
4877 set info $commitinfo($id)
4878 set date [formatdate [lindex $info 2]]
4879 $ctext insert end "[mc "Author"]: [lindex $info 1] $date\n"
4880 set date [formatdate [lindex $info 4]]
4881 $ctext insert end "[mc "Committer"]: [lindex $info 3] $date\n"
4882 if {[info exists idtags($id)]} {
4883 $ctext insert end [mc "Tags:"]
4884 foreach tag $idtags($id) {
4885 $ctext insert end " $tag"
4887 $ctext insert end "\n"
4890 set headers {}
4891 set olds [lindex $parentlist $l]
4892 if {[llength $olds] > 1} {
4893 set np 0
4894 foreach p $olds {
4895 if {$np >= $mergemax} {
4896 set tag mmax
4897 } else {
4898 set tag m$np
4900 $ctext insert end "[mc "Parent"]: " $tag
4901 appendwithlinks [commit_descriptor $p] {}
4902 incr np
4904 } else {
4905 foreach p $olds {
4906 append headers "[mc "Parent"]: [commit_descriptor $p]"
4910 foreach c $children($curview,$id) {
4911 append headers "[mc "Child"]: [commit_descriptor $c]"
4914 # make anything that looks like a SHA1 ID be a clickable link
4915 appendwithlinks $headers {}
4916 if {$showneartags} {
4917 if {![info exists allcommits]} {
4918 getallcommits
4920 $ctext insert end "[mc "Branch"]: "
4921 $ctext mark set branch "end -1c"
4922 $ctext mark gravity branch left
4923 $ctext insert end "\n[mc "Follows"]: "
4924 $ctext mark set follows "end -1c"
4925 $ctext mark gravity follows left
4926 $ctext insert end "\n[mc "Precedes"]: "
4927 $ctext mark set precedes "end -1c"
4928 $ctext mark gravity precedes left
4929 $ctext insert end "\n"
4930 dispneartags 1
4932 $ctext insert end "\n"
4933 set comment [lindex $info 5]
4934 if {[string first "\r" $comment] >= 0} {
4935 set comment [string map {"\r" "\n "} $comment]
4937 appendwithlinks $comment {comment}
4939 $ctext tag remove found 1.0 end
4940 $ctext conf -state disabled
4941 set commentend [$ctext index "end - 1c"]
4943 init_flist [mc "Comments"]
4944 if {$cmitmode eq "tree"} {
4945 gettree $id
4946 } elseif {[llength $olds] <= 1} {
4947 startdiff $id
4948 } else {
4949 mergediff $id $l
4953 proc selfirstline {} {
4954 unmarkmatches
4955 selectline 0 1
4958 proc sellastline {} {
4959 global numcommits
4960 unmarkmatches
4961 set l [expr {$numcommits - 1}]
4962 selectline $l 1
4965 proc selnextline {dir} {
4966 global selectedline
4967 focus .
4968 if {![info exists selectedline]} return
4969 set l [expr {$selectedline + $dir}]
4970 unmarkmatches
4971 selectline $l 1
4974 proc selnextpage {dir} {
4975 global canv linespc selectedline numcommits
4977 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4978 if {$lpp < 1} {
4979 set lpp 1
4981 allcanvs yview scroll [expr {$dir * $lpp}] units
4982 drawvisible
4983 if {![info exists selectedline]} return
4984 set l [expr {$selectedline + $dir * $lpp}]
4985 if {$l < 0} {
4986 set l 0
4987 } elseif {$l >= $numcommits} {
4988 set l [expr $numcommits - 1]
4990 unmarkmatches
4991 selectline $l 1
4994 proc unselectline {} {
4995 global selectedline currentid
4997 catch {unset selectedline}
4998 catch {unset currentid}
4999 allcanvs delete secsel
5000 rhighlight_none
5003 proc reselectline {} {
5004 global selectedline
5006 if {[info exists selectedline]} {
5007 selectline $selectedline 0
5011 proc addtohistory {cmd} {
5012 global history historyindex curview
5014 set elt [list $curview $cmd]
5015 if {$historyindex > 0
5016 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
5017 return
5020 if {$historyindex < [llength $history]} {
5021 set history [lreplace $history $historyindex end $elt]
5022 } else {
5023 lappend history $elt
5025 incr historyindex
5026 if {$historyindex > 1} {
5027 .tf.bar.leftbut conf -state normal
5028 } else {
5029 .tf.bar.leftbut conf -state disabled
5031 .tf.bar.rightbut conf -state disabled
5034 proc godo {elt} {
5035 global curview
5037 set view [lindex $elt 0]
5038 set cmd [lindex $elt 1]
5039 if {$curview != $view} {
5040 showview $view
5042 eval $cmd
5045 proc goback {} {
5046 global history historyindex
5047 focus .
5049 if {$historyindex > 1} {
5050 incr historyindex -1
5051 godo [lindex $history [expr {$historyindex - 1}]]
5052 .tf.bar.rightbut conf -state normal
5054 if {$historyindex <= 1} {
5055 .tf.bar.leftbut conf -state disabled
5059 proc goforw {} {
5060 global history historyindex
5061 focus .
5063 if {$historyindex < [llength $history]} {
5064 set cmd [lindex $history $historyindex]
5065 incr historyindex
5066 godo $cmd
5067 .tf.bar.leftbut conf -state normal
5069 if {$historyindex >= [llength $history]} {
5070 .tf.bar.rightbut conf -state disabled
5074 proc gettree {id} {
5075 global treefilelist treeidlist diffids diffmergeid treepending
5076 global nullid nullid2
5078 set diffids $id
5079 catch {unset diffmergeid}
5080 if {![info exists treefilelist($id)]} {
5081 if {![info exists treepending]} {
5082 if {$id eq $nullid} {
5083 set cmd [list | git ls-files]
5084 } elseif {$id eq $nullid2} {
5085 set cmd [list | git ls-files --stage -t]
5086 } else {
5087 set cmd [list | git ls-tree -r $id]
5089 if {[catch {set gtf [open $cmd r]}]} {
5090 return
5092 set treepending $id
5093 set treefilelist($id) {}
5094 set treeidlist($id) {}
5095 fconfigure $gtf -blocking 0
5096 filerun $gtf [list gettreeline $gtf $id]
5098 } else {
5099 setfilelist $id
5103 proc gettreeline {gtf id} {
5104 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
5106 set nl 0
5107 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
5108 if {$diffids eq $nullid} {
5109 set fname $line
5110 } else {
5111 set i [string first "\t" $line]
5112 if {$i < 0} continue
5113 set fname [string range $line [expr {$i+1}] end]
5114 set line [string range $line 0 [expr {$i-1}]]
5115 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
5116 set sha1 [lindex $line 2]
5117 if {[string index $fname 0] eq "\""} {
5118 set fname [lindex $fname 0]
5120 lappend treeidlist($id) $sha1
5122 lappend treefilelist($id) $fname
5124 if {![eof $gtf]} {
5125 return [expr {$nl >= 1000? 2: 1}]
5127 close $gtf
5128 unset treepending
5129 if {$cmitmode ne "tree"} {
5130 if {![info exists diffmergeid]} {
5131 gettreediffs $diffids
5133 } elseif {$id ne $diffids} {
5134 gettree $diffids
5135 } else {
5136 setfilelist $id
5138 return 0
5141 proc showfile {f} {
5142 global treefilelist treeidlist diffids nullid nullid2
5143 global ctext commentend
5145 set i [lsearch -exact $treefilelist($diffids) $f]
5146 if {$i < 0} {
5147 puts "oops, $f not in list for id $diffids"
5148 return
5150 if {$diffids eq $nullid} {
5151 if {[catch {set bf [open $f r]} err]} {
5152 puts "oops, can't read $f: $err"
5153 return
5155 } else {
5156 set blob [lindex $treeidlist($diffids) $i]
5157 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
5158 puts "oops, error reading blob $blob: $err"
5159 return
5162 fconfigure $bf -blocking 0
5163 filerun $bf [list getblobline $bf $diffids]
5164 $ctext config -state normal
5165 clear_ctext $commentend
5166 $ctext insert end "\n"
5167 $ctext insert end "$f\n" filesep
5168 $ctext config -state disabled
5169 $ctext yview $commentend
5170 settabs 0
5173 proc getblobline {bf id} {
5174 global diffids cmitmode ctext
5176 if {$id ne $diffids || $cmitmode ne "tree"} {
5177 catch {close $bf}
5178 return 0
5180 $ctext config -state normal
5181 set nl 0
5182 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
5183 $ctext insert end "$line\n"
5185 if {[eof $bf]} {
5186 # delete last newline
5187 $ctext delete "end - 2c" "end - 1c"
5188 close $bf
5189 return 0
5191 $ctext config -state disabled
5192 return [expr {$nl >= 1000? 2: 1}]
5195 proc mergediff {id l} {
5196 global diffmergeid mdifffd
5197 global diffids
5198 global diffcontext
5199 global parentlist
5200 global limitdiffs viewfiles curview
5202 set diffmergeid $id
5203 set diffids $id
5204 # this doesn't seem to actually affect anything...
5205 set cmd [concat | git diff-tree --no-commit-id --cc -U$diffcontext $id]
5206 if {$limitdiffs && $viewfiles($curview) ne {}} {
5207 set cmd [concat $cmd -- $viewfiles($curview)]
5209 if {[catch {set mdf [open $cmd r]} err]} {
5210 error_popup "[mc "Error getting merge diffs:"] $err"
5211 return
5213 fconfigure $mdf -blocking 0
5214 set mdifffd($id) $mdf
5215 set np [llength [lindex $parentlist $l]]
5216 settabs $np
5217 filerun $mdf [list getmergediffline $mdf $id $np]
5220 proc getmergediffline {mdf id np} {
5221 global diffmergeid ctext cflist mergemax
5222 global difffilestart mdifffd
5224 $ctext conf -state normal
5225 set nr 0
5226 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
5227 if {![info exists diffmergeid] || $id != $diffmergeid
5228 || $mdf != $mdifffd($id)} {
5229 close $mdf
5230 return 0
5232 if {[regexp {^diff --cc (.*)} $line match fname]} {
5233 # start of a new file
5234 $ctext insert end "\n"
5235 set here [$ctext index "end - 1c"]
5236 lappend difffilestart $here
5237 add_flist [list $fname]
5238 set l [expr {(78 - [string length $fname]) / 2}]
5239 set pad [string range "----------------------------------------" 1 $l]
5240 $ctext insert end "$pad $fname $pad\n" filesep
5241 } elseif {[regexp {^@@} $line]} {
5242 $ctext insert end "$line\n" hunksep
5243 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
5244 # do nothing
5245 } else {
5246 # parse the prefix - one ' ', '-' or '+' for each parent
5247 set spaces {}
5248 set minuses {}
5249 set pluses {}
5250 set isbad 0
5251 for {set j 0} {$j < $np} {incr j} {
5252 set c [string range $line $j $j]
5253 if {$c == " "} {
5254 lappend spaces $j
5255 } elseif {$c == "-"} {
5256 lappend minuses $j
5257 } elseif {$c == "+"} {
5258 lappend pluses $j
5259 } else {
5260 set isbad 1
5261 break
5264 set tags {}
5265 set num {}
5266 if {!$isbad && $minuses ne {} && $pluses eq {}} {
5267 # line doesn't appear in result, parents in $minuses have the line
5268 set num [lindex $minuses 0]
5269 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
5270 # line appears in result, parents in $pluses don't have the line
5271 lappend tags mresult
5272 set num [lindex $spaces 0]
5274 if {$num ne {}} {
5275 if {$num >= $mergemax} {
5276 set num "max"
5278 lappend tags m$num
5280 $ctext insert end "$line\n" $tags
5283 $ctext conf -state disabled
5284 if {[eof $mdf]} {
5285 close $mdf
5286 return 0
5288 return [expr {$nr >= 1000? 2: 1}]
5291 proc startdiff {ids} {
5292 global treediffs diffids treepending diffmergeid nullid nullid2
5294 settabs 1
5295 set diffids $ids
5296 catch {unset diffmergeid}
5297 if {![info exists treediffs($ids)] ||
5298 [lsearch -exact $ids $nullid] >= 0 ||
5299 [lsearch -exact $ids $nullid2] >= 0} {
5300 if {![info exists treepending]} {
5301 gettreediffs $ids
5303 } else {
5304 addtocflist $ids
5308 proc path_filter {filter name} {
5309 foreach p $filter {
5310 set l [string length $p]
5311 if {[string index $p end] eq "/"} {
5312 if {[string compare -length $l $p $name] == 0} {
5313 return 1
5315 } else {
5316 if {[string compare -length $l $p $name] == 0 &&
5317 ([string length $name] == $l ||
5318 [string index $name $l] eq "/")} {
5319 return 1
5323 return 0
5326 proc addtocflist {ids} {
5327 global treediffs
5329 add_flist $treediffs($ids)
5330 getblobdiffs $ids
5333 proc diffcmd {ids flags} {
5334 global nullid nullid2
5336 set i [lsearch -exact $ids $nullid]
5337 set j [lsearch -exact $ids $nullid2]
5338 if {$i >= 0} {
5339 if {[llength $ids] > 1 && $j < 0} {
5340 # comparing working directory with some specific revision
5341 set cmd [concat | git diff-index $flags]
5342 if {$i == 0} {
5343 lappend cmd -R [lindex $ids 1]
5344 } else {
5345 lappend cmd [lindex $ids 0]
5347 } else {
5348 # comparing working directory with index
5349 set cmd [concat | git diff-files $flags]
5350 if {$j == 1} {
5351 lappend cmd -R
5354 } elseif {$j >= 0} {
5355 set cmd [concat | git diff-index --cached $flags]
5356 if {[llength $ids] > 1} {
5357 # comparing index with specific revision
5358 if {$i == 0} {
5359 lappend cmd -R [lindex $ids 1]
5360 } else {
5361 lappend cmd [lindex $ids 0]
5363 } else {
5364 # comparing index with HEAD
5365 lappend cmd HEAD
5367 } else {
5368 set cmd [concat | git diff-tree -r $flags $ids]
5370 return $cmd
5373 proc gettreediffs {ids} {
5374 global treediff treepending
5376 set treepending $ids
5377 set treediff {}
5378 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
5379 fconfigure $gdtf -blocking 0
5380 filerun $gdtf [list gettreediffline $gdtf $ids]
5383 proc gettreediffline {gdtf ids} {
5384 global treediff treediffs treepending diffids diffmergeid
5385 global cmitmode viewfiles curview limitdiffs
5387 set nr 0
5388 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
5389 set i [string first "\t" $line]
5390 if {$i >= 0} {
5391 set file [string range $line [expr {$i+1}] end]
5392 if {[string index $file 0] eq "\""} {
5393 set file [lindex $file 0]
5395 lappend treediff $file
5398 if {![eof $gdtf]} {
5399 return [expr {$nr >= 1000? 2: 1}]
5401 close $gdtf
5402 if {$limitdiffs && $viewfiles($curview) ne {}} {
5403 set flist {}
5404 foreach f $treediff {
5405 if {[path_filter $viewfiles($curview) $f]} {
5406 lappend flist $f
5409 set treediffs($ids) $flist
5410 } else {
5411 set treediffs($ids) $treediff
5413 unset treepending
5414 if {$cmitmode eq "tree"} {
5415 gettree $diffids
5416 } elseif {$ids != $diffids} {
5417 if {![info exists diffmergeid]} {
5418 gettreediffs $diffids
5420 } else {
5421 addtocflist $ids
5423 return 0
5426 # empty string or positive integer
5427 proc diffcontextvalidate {v} {
5428 return [regexp {^(|[1-9][0-9]*)$} $v]
5431 proc diffcontextchange {n1 n2 op} {
5432 global diffcontextstring diffcontext
5434 if {[string is integer -strict $diffcontextstring]} {
5435 if {$diffcontextstring > 0} {
5436 set diffcontext $diffcontextstring
5437 reselectline
5442 proc changeignorespace {} {
5443 reselectline
5446 proc getblobdiffs {ids} {
5447 global blobdifffd diffids env
5448 global diffinhdr treediffs
5449 global diffcontext
5450 global ignorespace
5451 global limitdiffs viewfiles curview
5453 set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
5454 if {$ignorespace} {
5455 append cmd " -w"
5457 if {$limitdiffs && $viewfiles($curview) ne {}} {
5458 set cmd [concat $cmd -- $viewfiles($curview)]
5460 if {[catch {set bdf [open $cmd r]} err]} {
5461 puts "error getting diffs: $err"
5462 return
5464 set diffinhdr 0
5465 fconfigure $bdf -blocking 0
5466 set blobdifffd($ids) $bdf
5467 filerun $bdf [list getblobdiffline $bdf $diffids]
5470 proc setinlist {var i val} {
5471 global $var
5473 while {[llength [set $var]] < $i} {
5474 lappend $var {}
5476 if {[llength [set $var]] == $i} {
5477 lappend $var $val
5478 } else {
5479 lset $var $i $val
5483 proc makediffhdr {fname ids} {
5484 global ctext curdiffstart treediffs
5486 set i [lsearch -exact $treediffs($ids) $fname]
5487 if {$i >= 0} {
5488 setinlist difffilestart $i $curdiffstart
5490 set l [expr {(78 - [string length $fname]) / 2}]
5491 set pad [string range "----------------------------------------" 1 $l]
5492 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5495 proc getblobdiffline {bdf ids} {
5496 global diffids blobdifffd ctext curdiffstart
5497 global diffnexthead diffnextnote difffilestart
5498 global diffinhdr treediffs
5500 set nr 0
5501 $ctext conf -state normal
5502 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5503 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5504 close $bdf
5505 return 0
5507 if {![string compare -length 11 "diff --git " $line]} {
5508 # trim off "diff --git "
5509 set line [string range $line 11 end]
5510 set diffinhdr 1
5511 # start of a new file
5512 $ctext insert end "\n"
5513 set curdiffstart [$ctext index "end - 1c"]
5514 $ctext insert end "\n" filesep
5515 # If the name hasn't changed the length will be odd,
5516 # the middle char will be a space, and the two bits either
5517 # side will be a/name and b/name, or "a/name" and "b/name".
5518 # If the name has changed we'll get "rename from" and
5519 # "rename to" or "copy from" and "copy to" lines following this,
5520 # and we'll use them to get the filenames.
5521 # This complexity is necessary because spaces in the filename(s)
5522 # don't get escaped.
5523 set l [string length $line]
5524 set i [expr {$l / 2}]
5525 if {!(($l & 1) && [string index $line $i] eq " " &&
5526 [string range $line 2 [expr {$i - 1}]] eq \
5527 [string range $line [expr {$i + 3}] end])} {
5528 continue
5530 # unescape if quoted and chop off the a/ from the front
5531 if {[string index $line 0] eq "\""} {
5532 set fname [string range [lindex $line 0] 2 end]
5533 } else {
5534 set fname [string range $line 2 [expr {$i - 1}]]
5536 makediffhdr $fname $ids
5538 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5539 $line match f1l f1c f2l f2c rest]} {
5540 $ctext insert end "$line\n" hunksep
5541 set diffinhdr 0
5543 } elseif {$diffinhdr} {
5544 if {![string compare -length 12 "rename from " $line]} {
5545 set fname [string range $line [expr 6 + [string first " from " $line] ] end]
5546 if {[string index $fname 0] eq "\""} {
5547 set fname [lindex $fname 0]
5549 set i [lsearch -exact $treediffs($ids) $fname]
5550 if {$i >= 0} {
5551 setinlist difffilestart $i $curdiffstart
5553 } elseif {![string compare -length 10 $line "rename to "] ||
5554 ![string compare -length 8 $line "copy to "]} {
5555 set fname [string range $line [expr 4 + [string first " to " $line] ] end]
5556 if {[string index $fname 0] eq "\""} {
5557 set fname [lindex $fname 0]
5559 makediffhdr $fname $ids
5560 } elseif {[string compare -length 3 $line "---"] == 0} {
5561 # do nothing
5562 continue
5563 } elseif {[string compare -length 3 $line "+++"] == 0} {
5564 set diffinhdr 0
5565 continue
5567 $ctext insert end "$line\n" filesep
5569 } else {
5570 set x [string range $line 0 0]
5571 if {$x == "-" || $x == "+"} {
5572 set tag [expr {$x == "+"}]
5573 $ctext insert end "$line\n" d$tag
5574 } elseif {$x == " "} {
5575 $ctext insert end "$line\n"
5576 } else {
5577 # "\ No newline at end of file",
5578 # or something else we don't recognize
5579 $ctext insert end "$line\n" hunksep
5583 $ctext conf -state disabled
5584 if {[eof $bdf]} {
5585 close $bdf
5586 return 0
5588 return [expr {$nr >= 1000? 2: 1}]
5591 proc changediffdisp {} {
5592 global ctext diffelide
5594 $ctext tag conf d0 -elide [lindex $diffelide 0]
5595 $ctext tag conf d1 -elide [lindex $diffelide 1]
5598 proc highlightfile {loc cline} {
5599 global ctext cflist cflist_top
5601 $ctext yview $loc
5602 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
5603 $cflist tag add highlight $cline.0 "$cline.0 lineend"
5604 $cflist see $cline.0
5605 set cflist_top $cline
5608 proc prevfile {} {
5609 global difffilestart ctext cmitmode
5611 if {$cmitmode eq "tree"} return
5612 set prev 0.0
5613 set prevline 1
5614 set here [$ctext index @0,0]
5615 foreach loc $difffilestart {
5616 if {[$ctext compare $loc >= $here]} {
5617 highlightfile $prev $prevline
5618 return
5620 set prev $loc
5621 incr prevline
5623 highlightfile $prev $prevline
5626 proc nextfile {} {
5627 global difffilestart ctext cmitmode
5629 if {$cmitmode eq "tree"} return
5630 set here [$ctext index @0,0]
5631 set line 1
5632 foreach loc $difffilestart {
5633 incr line
5634 if {[$ctext compare $loc > $here]} {
5635 highlightfile $loc $line
5636 return
5641 proc clear_ctext {{first 1.0}} {
5642 global ctext smarktop smarkbot
5643 global pendinglinks
5645 set l [lindex [split $first .] 0]
5646 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5647 set smarktop $l
5649 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5650 set smarkbot $l
5652 $ctext delete $first end
5653 if {$first eq "1.0"} {
5654 catch {unset pendinglinks}
5658 proc settabs {{firstab {}}} {
5659 global firsttabstop tabstop ctext have_tk85
5661 if {$firstab ne {} && $have_tk85} {
5662 set firsttabstop $firstab
5664 set w [font measure textfont "0"]
5665 if {$firsttabstop != 0} {
5666 $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
5667 [expr {($firsttabstop + 2 * $tabstop) * $w}]]
5668 } elseif {$have_tk85 || $tabstop != 8} {
5669 $ctext conf -tabs [expr {$tabstop * $w}]
5670 } else {
5671 $ctext conf -tabs {}
5675 proc incrsearch {name ix op} {
5676 global ctext searchstring searchdirn
5678 $ctext tag remove found 1.0 end
5679 if {[catch {$ctext index anchor}]} {
5680 # no anchor set, use start of selection, or of visible area
5681 set sel [$ctext tag ranges sel]
5682 if {$sel ne {}} {
5683 $ctext mark set anchor [lindex $sel 0]
5684 } elseif {$searchdirn eq "-forwards"} {
5685 $ctext mark set anchor @0,0
5686 } else {
5687 $ctext mark set anchor @0,[winfo height $ctext]
5690 if {$searchstring ne {}} {
5691 set here [$ctext search $searchdirn -- $searchstring anchor]
5692 if {$here ne {}} {
5693 $ctext see $here
5695 searchmarkvisible 1
5699 proc dosearch {} {
5700 global sstring ctext searchstring searchdirn
5702 focus $sstring
5703 $sstring icursor end
5704 set searchdirn -forwards
5705 if {$searchstring ne {}} {
5706 set sel [$ctext tag ranges sel]
5707 if {$sel ne {}} {
5708 set start "[lindex $sel 0] + 1c"
5709 } elseif {[catch {set start [$ctext index anchor]}]} {
5710 set start "@0,0"
5712 set match [$ctext search -count mlen -- $searchstring $start]
5713 $ctext tag remove sel 1.0 end
5714 if {$match eq {}} {
5715 bell
5716 return
5718 $ctext see $match
5719 set mend "$match + $mlen c"
5720 $ctext tag add sel $match $mend
5721 $ctext mark unset anchor
5725 proc dosearchback {} {
5726 global sstring ctext searchstring searchdirn
5728 focus $sstring
5729 $sstring icursor end
5730 set searchdirn -backwards
5731 if {$searchstring ne {}} {
5732 set sel [$ctext tag ranges sel]
5733 if {$sel ne {}} {
5734 set start [lindex $sel 0]
5735 } elseif {[catch {set start [$ctext index anchor]}]} {
5736 set start @0,[winfo height $ctext]
5738 set match [$ctext search -backwards -count ml -- $searchstring $start]
5739 $ctext tag remove sel 1.0 end
5740 if {$match eq {}} {
5741 bell
5742 return
5744 $ctext see $match
5745 set mend "$match + $ml c"
5746 $ctext tag add sel $match $mend
5747 $ctext mark unset anchor
5751 proc searchmark {first last} {
5752 global ctext searchstring
5754 set mend $first.0
5755 while {1} {
5756 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5757 if {$match eq {}} break
5758 set mend "$match + $mlen c"
5759 $ctext tag add found $match $mend
5763 proc searchmarkvisible {doall} {
5764 global ctext smarktop smarkbot
5766 set topline [lindex [split [$ctext index @0,0] .] 0]
5767 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5768 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5769 # no overlap with previous
5770 searchmark $topline $botline
5771 set smarktop $topline
5772 set smarkbot $botline
5773 } else {
5774 if {$topline < $smarktop} {
5775 searchmark $topline [expr {$smarktop-1}]
5776 set smarktop $topline
5778 if {$botline > $smarkbot} {
5779 searchmark [expr {$smarkbot+1}] $botline
5780 set smarkbot $botline
5785 proc scrolltext {f0 f1} {
5786 global searchstring
5788 .bleft.bottom.sb set $f0 $f1
5789 if {$searchstring ne {}} {
5790 searchmarkvisible 0
5794 proc setcoords {} {
5795 global linespc charspc canvx0 canvy0
5796 global xspc1 xspc2 lthickness
5798 set linespc [font metrics mainfont -linespace]
5799 set charspc [font measure mainfont "m"]
5800 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5801 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5802 set lthickness [expr {int($linespc / 9) + 1}]
5803 set xspc1(0) $linespc
5804 set xspc2 $linespc
5807 proc redisplay {} {
5808 global canv
5809 global selectedline
5811 set ymax [lindex [$canv cget -scrollregion] 3]
5812 if {$ymax eq {} || $ymax == 0} return
5813 set span [$canv yview]
5814 clear_display
5815 setcanvscroll
5816 allcanvs yview moveto [lindex $span 0]
5817 drawvisible
5818 if {[info exists selectedline]} {
5819 selectline $selectedline 0
5820 allcanvs yview moveto [lindex $span 0]
5824 proc parsefont {f n} {
5825 global fontattr
5827 set fontattr($f,family) [lindex $n 0]
5828 set s [lindex $n 1]
5829 if {$s eq {} || $s == 0} {
5830 set s 10
5831 } elseif {$s < 0} {
5832 set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
5834 set fontattr($f,size) $s
5835 set fontattr($f,weight) normal
5836 set fontattr($f,slant) roman
5837 foreach style [lrange $n 2 end] {
5838 switch -- $style {
5839 "normal" -
5840 "bold" {set fontattr($f,weight) $style}
5841 "roman" -
5842 "italic" {set fontattr($f,slant) $style}
5847 proc fontflags {f {isbold 0}} {
5848 global fontattr
5850 return [list -family $fontattr($f,family) -size $fontattr($f,size) \
5851 -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
5852 -slant $fontattr($f,slant)]
5855 proc fontname {f} {
5856 global fontattr
5858 set n [list $fontattr($f,family) $fontattr($f,size)]
5859 if {$fontattr($f,weight) eq "bold"} {
5860 lappend n "bold"
5862 if {$fontattr($f,slant) eq "italic"} {
5863 lappend n "italic"
5865 return $n
5868 proc incrfont {inc} {
5869 global mainfont textfont ctext canv phase cflist showrefstop
5870 global stopped entries fontattr
5872 unmarkmatches
5873 set s $fontattr(mainfont,size)
5874 incr s $inc
5875 if {$s < 1} {
5876 set s 1
5878 set fontattr(mainfont,size) $s
5879 font config mainfont -size $s
5880 font config mainfontbold -size $s
5881 set mainfont [fontname mainfont]
5882 set s $fontattr(textfont,size)
5883 incr s $inc
5884 if {$s < 1} {
5885 set s 1
5887 set fontattr(textfont,size) $s
5888 font config textfont -size $s
5889 font config textfontbold -size $s
5890 set textfont [fontname textfont]
5891 setcoords
5892 settabs
5893 redisplay
5896 proc clearsha1 {} {
5897 global sha1entry sha1string
5898 if {[string length $sha1string] == 40} {
5899 $sha1entry delete 0 end
5903 proc sha1change {n1 n2 op} {
5904 global sha1string currentid sha1but
5905 if {$sha1string == {}
5906 || ([info exists currentid] && $sha1string == $currentid)} {
5907 set state disabled
5908 } else {
5909 set state normal
5911 if {[$sha1but cget -state] == $state} return
5912 if {$state == "normal"} {
5913 $sha1but conf -state normal -relief raised -text "[mc "Goto:"] "
5914 } else {
5915 $sha1but conf -state disabled -relief flat -text "[mc "SHA1 ID:"] "
5919 proc gotocommit {} {
5920 global sha1string currentid commitrow tagids headids
5921 global displayorder numcommits curview
5923 if {$sha1string == {}
5924 || ([info exists currentid] && $sha1string == $currentid)} return
5925 if {[info exists tagids($sha1string)]} {
5926 set id $tagids($sha1string)
5927 } elseif {[info exists headids($sha1string)]} {
5928 set id $headids($sha1string)
5929 } else {
5930 set id [string tolower $sha1string]
5931 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5932 set matches {}
5933 foreach i $displayorder {
5934 if {[string match $id* $i]} {
5935 lappend matches $i
5938 if {$matches ne {}} {
5939 if {[llength $matches] > 1} {
5940 error_popup [mc "Short SHA1 id %s is ambiguous" $id]
5941 return
5943 set id [lindex $matches 0]
5947 if {[info exists commitrow($curview,$id)]} {
5948 selectline $commitrow($curview,$id) 1
5949 return
5951 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5952 set msg [mc "SHA1 id %s is not known" $sha1string]
5953 } else {
5954 set msg [mc "Tag/Head %s is not known" $sha1string]
5956 error_popup $msg
5959 proc lineenter {x y id} {
5960 global hoverx hovery hoverid hovertimer
5961 global commitinfo canv
5963 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5964 set hoverx $x
5965 set hovery $y
5966 set hoverid $id
5967 if {[info exists hovertimer]} {
5968 after cancel $hovertimer
5970 set hovertimer [after 500 linehover]
5971 $canv delete hover
5974 proc linemotion {x y id} {
5975 global hoverx hovery hoverid hovertimer
5977 if {[info exists hoverid] && $id == $hoverid} {
5978 set hoverx $x
5979 set hovery $y
5980 if {[info exists hovertimer]} {
5981 after cancel $hovertimer
5983 set hovertimer [after 500 linehover]
5987 proc lineleave {id} {
5988 global hoverid hovertimer canv
5990 if {[info exists hoverid] && $id == $hoverid} {
5991 $canv delete hover
5992 if {[info exists hovertimer]} {
5993 after cancel $hovertimer
5994 unset hovertimer
5996 unset hoverid
6000 proc linehover {} {
6001 global hoverx hovery hoverid hovertimer
6002 global canv linespc lthickness
6003 global commitinfo
6005 set text [lindex $commitinfo($hoverid) 0]
6006 set ymax [lindex [$canv cget -scrollregion] 3]
6007 if {$ymax == {}} return
6008 set yfrac [lindex [$canv yview] 0]
6009 set x [expr {$hoverx + 2 * $linespc}]
6010 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
6011 set x0 [expr {$x - 2 * $lthickness}]
6012 set y0 [expr {$y - 2 * $lthickness}]
6013 set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
6014 set y1 [expr {$y + $linespc + 2 * $lthickness}]
6015 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
6016 -fill \#ffff80 -outline black -width 1 -tags hover]
6017 $canv raise $t
6018 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
6019 -font mainfont]
6020 $canv raise $t
6023 proc clickisonarrow {id y} {
6024 global lthickness
6026 set ranges [rowranges $id]
6027 set thresh [expr {2 * $lthickness + 6}]
6028 set n [expr {[llength $ranges] - 1}]
6029 for {set i 1} {$i < $n} {incr i} {
6030 set row [lindex $ranges $i]
6031 if {abs([yc $row] - $y) < $thresh} {
6032 return $i
6035 return {}
6038 proc arrowjump {id n y} {
6039 global canv
6041 # 1 <-> 2, 3 <-> 4, etc...
6042 set n [expr {(($n - 1) ^ 1) + 1}]
6043 set row [lindex [rowranges $id] $n]
6044 set yt [yc $row]
6045 set ymax [lindex [$canv cget -scrollregion] 3]
6046 if {$ymax eq {} || $ymax <= 0} return
6047 set view [$canv yview]
6048 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
6049 set yfrac [expr {$yt / $ymax - $yspan / 2}]
6050 if {$yfrac < 0} {
6051 set yfrac 0
6053 allcanvs yview moveto $yfrac
6056 proc lineclick {x y id isnew} {
6057 global ctext commitinfo children canv thickerline curview commitrow
6059 if {![info exists commitinfo($id)] && ![getcommit $id]} return
6060 unmarkmatches
6061 unselectline
6062 normalline
6063 $canv delete hover
6064 # draw this line thicker than normal
6065 set thickerline $id
6066 drawlines $id
6067 if {$isnew} {
6068 set ymax [lindex [$canv cget -scrollregion] 3]
6069 if {$ymax eq {}} return
6070 set yfrac [lindex [$canv yview] 0]
6071 set y [expr {$y + $yfrac * $ymax}]
6073 set dirn [clickisonarrow $id $y]
6074 if {$dirn ne {}} {
6075 arrowjump $id $dirn $y
6076 return
6079 if {$isnew} {
6080 addtohistory [list lineclick $x $y $id 0]
6082 # fill the details pane with info about this line
6083 $ctext conf -state normal
6084 clear_ctext
6085 settabs 0
6086 $ctext insert end "[mc "Parent"]:\t"
6087 $ctext insert end $id link0
6088 setlink $id link0
6089 set info $commitinfo($id)
6090 $ctext insert end "\n\t[lindex $info 0]\n"
6091 $ctext insert end "\t[mc "Author"]:\t[lindex $info 1]\n"
6092 set date [formatdate [lindex $info 2]]
6093 $ctext insert end "\t[mc "Date"]:\t$date\n"
6094 set kids $children($curview,$id)
6095 if {$kids ne {}} {
6096 $ctext insert end "\n[mc "Children"]:"
6097 set i 0
6098 foreach child $kids {
6099 incr i
6100 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
6101 set info $commitinfo($child)
6102 $ctext insert end "\n\t"
6103 $ctext insert end $child link$i
6104 setlink $child link$i
6105 $ctext insert end "\n\t[lindex $info 0]"
6106 $ctext insert end "\n\t[mc "Author"]:\t[lindex $info 1]"
6107 set date [formatdate [lindex $info 2]]
6108 $ctext insert end "\n\t[mc "Date"]:\t$date\n"
6111 $ctext conf -state disabled
6112 init_flist {}
6115 proc normalline {} {
6116 global thickerline
6117 if {[info exists thickerline]} {
6118 set id $thickerline
6119 unset thickerline
6120 drawlines $id
6124 proc selbyid {id} {
6125 global commitrow curview
6126 if {[info exists commitrow($curview,$id)]} {
6127 selectline $commitrow($curview,$id) 1
6131 proc mstime {} {
6132 global startmstime
6133 if {![info exists startmstime]} {
6134 set startmstime [clock clicks -milliseconds]
6136 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
6139 proc rowmenu {x y id} {
6140 global rowctxmenu commitrow selectedline rowmenuid curview
6141 global nullid nullid2 fakerowmenu mainhead
6143 stopfinding
6144 set rowmenuid $id
6145 if {![info exists selectedline]
6146 || $commitrow($curview,$id) eq $selectedline} {
6147 set state disabled
6148 } else {
6149 set state normal
6151 if {$id ne $nullid && $id ne $nullid2} {
6152 set menu $rowctxmenu
6153 if {$mainhead ne {}} {
6154 $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
6155 } else {
6156 $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
6158 } else {
6159 set menu $fakerowmenu
6161 $menu entryconfigure [mc "Diff this -> selected"] -state $state
6162 $menu entryconfigure [mc "Diff selected -> this"] -state $state
6163 $menu entryconfigure [mc "Make patch"] -state $state
6164 tk_popup $menu $x $y
6167 proc diffvssel {dirn} {
6168 global rowmenuid selectedline displayorder
6170 if {![info exists selectedline]} return
6171 if {$dirn} {
6172 set oldid [lindex $displayorder $selectedline]
6173 set newid $rowmenuid
6174 } else {
6175 set oldid $rowmenuid
6176 set newid [lindex $displayorder $selectedline]
6178 addtohistory [list doseldiff $oldid $newid]
6179 doseldiff $oldid $newid
6182 proc doseldiff {oldid newid} {
6183 global ctext
6184 global commitinfo
6186 $ctext conf -state normal
6187 clear_ctext
6188 init_flist [mc "Top"]
6189 $ctext insert end "[mc "From"] "
6190 $ctext insert end $oldid link0
6191 setlink $oldid link0
6192 $ctext insert end "\n "
6193 $ctext insert end [lindex $commitinfo($oldid) 0]
6194 $ctext insert end "\n\n[mc "To"] "
6195 $ctext insert end $newid link1
6196 setlink $newid link1
6197 $ctext insert end "\n "
6198 $ctext insert end [lindex $commitinfo($newid) 0]
6199 $ctext insert end "\n"
6200 $ctext conf -state disabled
6201 $ctext tag remove found 1.0 end
6202 startdiff [list $oldid $newid]
6205 proc mkpatch {} {
6206 global rowmenuid currentid commitinfo patchtop patchnum
6208 if {![info exists currentid]} return
6209 set oldid $currentid
6210 set oldhead [lindex $commitinfo($oldid) 0]
6211 set newid $rowmenuid
6212 set newhead [lindex $commitinfo($newid) 0]
6213 set top .patch
6214 set patchtop $top
6215 catch {destroy $top}
6216 toplevel $top
6217 label $top.title -text [mc "Generate patch"]
6218 grid $top.title - -pady 10
6219 label $top.from -text [mc "From:"]
6220 entry $top.fromsha1 -width 40 -relief flat
6221 $top.fromsha1 insert 0 $oldid
6222 $top.fromsha1 conf -state readonly
6223 grid $top.from $top.fromsha1 -sticky w
6224 entry $top.fromhead -width 60 -relief flat
6225 $top.fromhead insert 0 $oldhead
6226 $top.fromhead conf -state readonly
6227 grid x $top.fromhead -sticky w
6228 label $top.to -text [mc "To:"]
6229 entry $top.tosha1 -width 40 -relief flat
6230 $top.tosha1 insert 0 $newid
6231 $top.tosha1 conf -state readonly
6232 grid $top.to $top.tosha1 -sticky w
6233 entry $top.tohead -width 60 -relief flat
6234 $top.tohead insert 0 $newhead
6235 $top.tohead conf -state readonly
6236 grid x $top.tohead -sticky w
6237 button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
6238 grid $top.rev x -pady 10
6239 label $top.flab -text [mc "Output file:"]
6240 entry $top.fname -width 60
6241 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
6242 incr patchnum
6243 grid $top.flab $top.fname -sticky w
6244 frame $top.buts
6245 button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
6246 button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
6247 grid $top.buts.gen $top.buts.can
6248 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6249 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6250 grid $top.buts - -pady 10 -sticky ew
6251 focus $top.fname
6254 proc mkpatchrev {} {
6255 global patchtop
6257 set oldid [$patchtop.fromsha1 get]
6258 set oldhead [$patchtop.fromhead get]
6259 set newid [$patchtop.tosha1 get]
6260 set newhead [$patchtop.tohead get]
6261 foreach e [list fromsha1 fromhead tosha1 tohead] \
6262 v [list $newid $newhead $oldid $oldhead] {
6263 $patchtop.$e conf -state normal
6264 $patchtop.$e delete 0 end
6265 $patchtop.$e insert 0 $v
6266 $patchtop.$e conf -state readonly
6270 proc mkpatchgo {} {
6271 global patchtop nullid nullid2
6273 set oldid [$patchtop.fromsha1 get]
6274 set newid [$patchtop.tosha1 get]
6275 set fname [$patchtop.fname get]
6276 set cmd [diffcmd [list $oldid $newid] -p]
6277 # trim off the initial "|"
6278 set cmd [lrange $cmd 1 end]
6279 lappend cmd >$fname &
6280 if {[catch {eval exec $cmd} err]} {
6281 error_popup "[mc "Error creating patch:"] $err"
6283 catch {destroy $patchtop}
6284 unset patchtop
6287 proc mkpatchcan {} {
6288 global patchtop
6290 catch {destroy $patchtop}
6291 unset patchtop
6294 proc mktag {} {
6295 global rowmenuid mktagtop commitinfo
6297 set top .maketag
6298 set mktagtop $top
6299 catch {destroy $top}
6300 toplevel $top
6301 label $top.title -text [mc "Create tag"]
6302 grid $top.title - -pady 10
6303 label $top.id -text [mc "ID:"]
6304 entry $top.sha1 -width 40 -relief flat
6305 $top.sha1 insert 0 $rowmenuid
6306 $top.sha1 conf -state readonly
6307 grid $top.id $top.sha1 -sticky w
6308 entry $top.head -width 60 -relief flat
6309 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6310 $top.head conf -state readonly
6311 grid x $top.head -sticky w
6312 label $top.tlab -text [mc "Tag name:"]
6313 entry $top.tag -width 60
6314 grid $top.tlab $top.tag -sticky w
6315 frame $top.buts
6316 button $top.buts.gen -text [mc "Create"] -command mktaggo
6317 button $top.buts.can -text [mc "Cancel"] -command mktagcan
6318 grid $top.buts.gen $top.buts.can
6319 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6320 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6321 grid $top.buts - -pady 10 -sticky ew
6322 focus $top.tag
6325 proc domktag {} {
6326 global mktagtop env tagids idtags
6328 set id [$mktagtop.sha1 get]
6329 set tag [$mktagtop.tag get]
6330 if {$tag == {}} {
6331 error_popup [mc "No tag name specified"]
6332 return
6334 if {[info exists tagids($tag)]} {
6335 error_popup [mc "Tag \"%s\" already exists" $tag]
6336 return
6338 if {[catch {
6339 exec git tag $tag $id
6340 } err]} {
6341 error_popup "[mc "Error creating tag:"] $err"
6342 return
6345 set tagids($tag) $id
6346 lappend idtags($id) $tag
6347 redrawtags $id
6348 addedtag $id
6349 dispneartags 0
6350 run refill_reflist
6353 proc redrawtags {id} {
6354 global canv linehtag commitrow idpos selectedline curview
6355 global canvxmax iddrawn
6357 if {![info exists commitrow($curview,$id)]} return
6358 if {![info exists iddrawn($id)]} return
6359 drawcommits $commitrow($curview,$id)
6360 $canv delete tag.$id
6361 set xt [eval drawtags $id $idpos($id)]
6362 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
6363 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
6364 set xr [expr {$xt + [font measure mainfont $text]}]
6365 if {$xr > $canvxmax} {
6366 set canvxmax $xr
6367 setcanvscroll
6369 if {[info exists selectedline]
6370 && $selectedline == $commitrow($curview,$id)} {
6371 selectline $selectedline 0
6375 proc mktagcan {} {
6376 global mktagtop
6378 catch {destroy $mktagtop}
6379 unset mktagtop
6382 proc mktaggo {} {
6383 domktag
6384 mktagcan
6387 proc writecommit {} {
6388 global rowmenuid wrcomtop commitinfo wrcomcmd
6390 set top .writecommit
6391 set wrcomtop $top
6392 catch {destroy $top}
6393 toplevel $top
6394 label $top.title -text [mc "Write commit to file"]
6395 grid $top.title - -pady 10
6396 label $top.id -text [mc "ID:"]
6397 entry $top.sha1 -width 40 -relief flat
6398 $top.sha1 insert 0 $rowmenuid
6399 $top.sha1 conf -state readonly
6400 grid $top.id $top.sha1 -sticky w
6401 entry $top.head -width 60 -relief flat
6402 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
6403 $top.head conf -state readonly
6404 grid x $top.head -sticky w
6405 label $top.clab -text [mc "Command:"]
6406 entry $top.cmd -width 60 -textvariable wrcomcmd
6407 grid $top.clab $top.cmd -sticky w -pady 10
6408 label $top.flab -text [mc "Output file:"]
6409 entry $top.fname -width 60
6410 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
6411 grid $top.flab $top.fname -sticky w
6412 frame $top.buts
6413 button $top.buts.gen -text [mc "Write"] -command wrcomgo
6414 button $top.buts.can -text [mc "Cancel"] -command wrcomcan
6415 grid $top.buts.gen $top.buts.can
6416 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6417 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6418 grid $top.buts - -pady 10 -sticky ew
6419 focus $top.fname
6422 proc wrcomgo {} {
6423 global wrcomtop
6425 set id [$wrcomtop.sha1 get]
6426 set cmd "echo $id | [$wrcomtop.cmd get]"
6427 set fname [$wrcomtop.fname get]
6428 if {[catch {exec sh -c $cmd >$fname &} err]} {
6429 error_popup "[mc "Error writing commit:"] $err"
6431 catch {destroy $wrcomtop}
6432 unset wrcomtop
6435 proc wrcomcan {} {
6436 global wrcomtop
6438 catch {destroy $wrcomtop}
6439 unset wrcomtop
6442 proc mkbranch {} {
6443 global rowmenuid mkbrtop
6445 set top .makebranch
6446 catch {destroy $top}
6447 toplevel $top
6448 label $top.title -text [mc "Create new branch"]
6449 grid $top.title - -pady 10
6450 label $top.id -text [mc "ID:"]
6451 entry $top.sha1 -width 40 -relief flat
6452 $top.sha1 insert 0 $rowmenuid
6453 $top.sha1 conf -state readonly
6454 grid $top.id $top.sha1 -sticky w
6455 label $top.nlab -text [mc "Name:"]
6456 entry $top.name -width 40
6457 grid $top.nlab $top.name -sticky w
6458 frame $top.buts
6459 button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
6460 button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
6461 grid $top.buts.go $top.buts.can
6462 grid columnconfigure $top.buts 0 -weight 1 -uniform a
6463 grid columnconfigure $top.buts 1 -weight 1 -uniform a
6464 grid $top.buts - -pady 10 -sticky ew
6465 focus $top.name
6468 proc mkbrgo {top} {
6469 global headids idheads
6471 set name [$top.name get]
6472 set id [$top.sha1 get]
6473 if {$name eq {}} {
6474 error_popup [mc "Please specify a name for the new branch"]
6475 return
6477 catch {destroy $top}
6478 nowbusy newbranch
6479 update
6480 if {[catch {
6481 exec git branch $name $id
6482 } err]} {
6483 notbusy newbranch
6484 error_popup $err
6485 } else {
6486 set headids($name) $id
6487 lappend idheads($id) $name
6488 addedhead $id $name
6489 notbusy newbranch
6490 redrawtags $id
6491 dispneartags 0
6492 run refill_reflist
6496 proc cherrypick {} {
6497 global rowmenuid curview commitrow
6498 global mainhead
6500 set oldhead [exec git rev-parse HEAD]
6501 set dheads [descheads $rowmenuid]
6502 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
6503 set ok [confirm_popup [mc "Commit %s is already\
6504 included in branch %s -- really re-apply it?" \
6505 [string range $rowmenuid 0 7] $mainhead]]
6506 if {!$ok} return
6508 nowbusy cherrypick [mc "Cherry-picking"]
6509 update
6510 # Unfortunately git-cherry-pick writes stuff to stderr even when
6511 # no error occurs, and exec takes that as an indication of error...
6512 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
6513 notbusy cherrypick
6514 error_popup $err
6515 return
6517 set newhead [exec git rev-parse HEAD]
6518 if {$newhead eq $oldhead} {
6519 notbusy cherrypick
6520 error_popup [mc "No changes committed"]
6521 return
6523 addnewchild $newhead $oldhead
6524 if {[info exists commitrow($curview,$oldhead)]} {
6525 insertrow $commitrow($curview,$oldhead) $newhead
6526 if {$mainhead ne {}} {
6527 movehead $newhead $mainhead
6528 movedhead $newhead $mainhead
6530 redrawtags $oldhead
6531 redrawtags $newhead
6533 notbusy cherrypick
6536 proc resethead {} {
6537 global mainheadid mainhead rowmenuid confirm_ok resettype
6539 set confirm_ok 0
6540 set w ".confirmreset"
6541 toplevel $w
6542 wm transient $w .
6543 wm title $w [mc "Confirm reset"]
6544 message $w.m -text \
6545 [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
6546 -justify center -aspect 1000
6547 pack $w.m -side top -fill x -padx 20 -pady 20
6548 frame $w.f -relief sunken -border 2
6549 message $w.f.rt -text [mc "Reset type:"] -aspect 1000
6550 grid $w.f.rt -sticky w
6551 set resettype mixed
6552 radiobutton $w.f.soft -value soft -variable resettype -justify left \
6553 -text [mc "Soft: Leave working tree and index untouched"]
6554 grid $w.f.soft -sticky w
6555 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6556 -text [mc "Mixed: Leave working tree untouched, reset index"]
6557 grid $w.f.mixed -sticky w
6558 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6559 -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
6560 grid $w.f.hard -sticky w
6561 pack $w.f -side top -fill x
6562 button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
6563 pack $w.ok -side left -fill x -padx 20 -pady 20
6564 button $w.cancel -text [mc Cancel] -command "destroy $w"
6565 pack $w.cancel -side right -fill x -padx 20 -pady 20
6566 bind $w <Visibility> "grab $w; focus $w"
6567 tkwait window $w
6568 if {!$confirm_ok} return
6569 if {[catch {set fd [open \
6570 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6571 error_popup $err
6572 } else {
6573 dohidelocalchanges
6574 filerun $fd [list readresetstat $fd]
6575 nowbusy reset [mc "Resetting"]
6579 proc readresetstat {fd} {
6580 global mainhead mainheadid showlocalchanges rprogcoord
6582 if {[gets $fd line] >= 0} {
6583 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6584 set rprogcoord [expr {1.0 * $m / $n}]
6585 adjustprogress
6587 return 1
6589 set rprogcoord 0
6590 adjustprogress
6591 notbusy reset
6592 if {[catch {close $fd} err]} {
6593 error_popup $err
6595 set oldhead $mainheadid
6596 set newhead [exec git rev-parse HEAD]
6597 if {$newhead ne $oldhead} {
6598 movehead $newhead $mainhead
6599 movedhead $newhead $mainhead
6600 set mainheadid $newhead
6601 redrawtags $oldhead
6602 redrawtags $newhead
6604 if {$showlocalchanges} {
6605 doshowlocalchanges
6607 return 0
6610 # context menu for a head
6611 proc headmenu {x y id head} {
6612 global headmenuid headmenuhead headctxmenu mainhead
6614 stopfinding
6615 set headmenuid $id
6616 set headmenuhead $head
6617 set state normal
6618 if {$head eq $mainhead} {
6619 set state disabled
6621 $headctxmenu entryconfigure 0 -state $state
6622 $headctxmenu entryconfigure 1 -state $state
6623 tk_popup $headctxmenu $x $y
6626 proc cobranch {} {
6627 global headmenuid headmenuhead mainhead headids
6628 global showlocalchanges mainheadid
6630 # check the tree is clean first??
6631 set oldmainhead $mainhead
6632 nowbusy checkout [mc "Checking out"]
6633 update
6634 dohidelocalchanges
6635 if {[catch {
6636 exec git checkout -q $headmenuhead
6637 } err]} {
6638 notbusy checkout
6639 error_popup $err
6640 } else {
6641 notbusy checkout
6642 set mainhead $headmenuhead
6643 set mainheadid $headmenuid
6644 if {[info exists headids($oldmainhead)]} {
6645 redrawtags $headids($oldmainhead)
6647 redrawtags $headmenuid
6649 if {$showlocalchanges} {
6650 dodiffindex
6654 proc rmbranch {} {
6655 global headmenuid headmenuhead mainhead
6656 global idheads
6658 set head $headmenuhead
6659 set id $headmenuid
6660 # this check shouldn't be needed any more...
6661 if {$head eq $mainhead} {
6662 error_popup [mc "Cannot delete the currently checked-out branch"]
6663 return
6665 set dheads [descheads $id]
6666 if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
6667 # the stuff on this branch isn't on any other branch
6668 if {![confirm_popup [mc "The commits on branch %s aren't on any other\
6669 branch.\nReally delete branch %s?" $head $head]]} return
6671 nowbusy rmbranch
6672 update
6673 if {[catch {exec git branch -D $head} err]} {
6674 notbusy rmbranch
6675 error_popup $err
6676 return
6678 removehead $id $head
6679 removedhead $id $head
6680 redrawtags $id
6681 notbusy rmbranch
6682 dispneartags 0
6683 run refill_reflist
6686 # Display a list of tags and heads
6687 proc showrefs {} {
6688 global showrefstop bgcolor fgcolor selectbgcolor
6689 global bglist fglist reflistfilter reflist maincursor
6691 set top .showrefs
6692 set showrefstop $top
6693 if {[winfo exists $top]} {
6694 raise $top
6695 refill_reflist
6696 return
6698 toplevel $top
6699 wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
6700 text $top.list -background $bgcolor -foreground $fgcolor \
6701 -selectbackground $selectbgcolor -font mainfont \
6702 -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
6703 -width 30 -height 20 -cursor $maincursor \
6704 -spacing1 1 -spacing3 1 -state disabled
6705 $top.list tag configure highlight -background $selectbgcolor
6706 lappend bglist $top.list
6707 lappend fglist $top.list
6708 scrollbar $top.ysb -command "$top.list yview" -orient vertical
6709 scrollbar $top.xsb -command "$top.list xview" -orient horizontal
6710 grid $top.list $top.ysb -sticky nsew
6711 grid $top.xsb x -sticky ew
6712 frame $top.f
6713 label $top.f.l -text "[mc "Filter"]: "
6714 entry $top.f.e -width 20 -textvariable reflistfilter
6715 set reflistfilter "*"
6716 trace add variable reflistfilter write reflistfilter_change
6717 pack $top.f.e -side right -fill x -expand 1
6718 pack $top.f.l -side left
6719 grid $top.f - -sticky ew -pady 2
6720 button $top.close -command [list destroy $top] -text [mc "Close"]
6721 grid $top.close -
6722 grid columnconfigure $top 0 -weight 1
6723 grid rowconfigure $top 0 -weight 1
6724 bind $top.list <1> {break}
6725 bind $top.list <B1-Motion> {break}
6726 bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
6727 set reflist {}
6728 refill_reflist
6731 proc sel_reflist {w x y} {
6732 global showrefstop reflist headids tagids otherrefids
6734 if {![winfo exists $showrefstop]} return
6735 set l [lindex [split [$w index "@$x,$y"] "."] 0]
6736 set ref [lindex $reflist [expr {$l-1}]]
6737 set n [lindex $ref 0]
6738 switch -- [lindex $ref 1] {
6739 "H" {selbyid $headids($n)}
6740 "T" {selbyid $tagids($n)}
6741 "o" {selbyid $otherrefids($n)}
6743 $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
6746 proc unsel_reflist {} {
6747 global showrefstop
6749 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
6750 $showrefstop.list tag remove highlight 0.0 end
6753 proc reflistfilter_change {n1 n2 op} {
6754 global reflistfilter
6756 after cancel refill_reflist
6757 after 200 refill_reflist
6760 proc refill_reflist {} {
6761 global reflist reflistfilter showrefstop headids tagids otherrefids
6762 global commitrow curview commitinterest
6764 if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
6765 set refs {}
6766 foreach n [array names headids] {
6767 if {[string match $reflistfilter $n]} {
6768 if {[info exists commitrow($curview,$headids($n))]} {
6769 lappend refs [list $n H]
6770 } else {
6771 set commitinterest($headids($n)) {run refill_reflist}
6775 foreach n [array names tagids] {
6776 if {[string match $reflistfilter $n]} {
6777 if {[info exists commitrow($curview,$tagids($n))]} {
6778 lappend refs [list $n T]
6779 } else {
6780 set commitinterest($tagids($n)) {run refill_reflist}
6784 foreach n [array names otherrefids] {
6785 if {[string match $reflistfilter $n]} {
6786 if {[info exists commitrow($curview,$otherrefids($n))]} {
6787 lappend refs [list $n o]
6788 } else {
6789 set commitinterest($otherrefids($n)) {run refill_reflist}
6793 set refs [lsort -index 0 $refs]
6794 if {$refs eq $reflist} return
6796 # Update the contents of $showrefstop.list according to the
6797 # differences between $reflist (old) and $refs (new)
6798 $showrefstop.list conf -state normal
6799 $showrefstop.list insert end "\n"
6800 set i 0
6801 set j 0
6802 while {$i < [llength $reflist] || $j < [llength $refs]} {
6803 if {$i < [llength $reflist]} {
6804 if {$j < [llength $refs]} {
6805 set cmp [string compare [lindex $reflist $i 0] \
6806 [lindex $refs $j 0]]
6807 if {$cmp == 0} {
6808 set cmp [string compare [lindex $reflist $i 1] \
6809 [lindex $refs $j 1]]
6811 } else {
6812 set cmp -1
6814 } else {
6815 set cmp 1
6817 switch -- $cmp {
6818 -1 {
6819 $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
6820 incr i
6823 incr i
6824 incr j
6827 set l [expr {$j + 1}]
6828 $showrefstop.list image create $l.0 -align baseline \
6829 -image reficon-[lindex $refs $j 1] -padx 2
6830 $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
6831 incr j
6835 set reflist $refs
6836 # delete last newline
6837 $showrefstop.list delete end-2c end-1c
6838 $showrefstop.list conf -state disabled
6841 # Stuff for finding nearby tags
6842 proc getallcommits {} {
6843 global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
6844 global idheads idtags idotherrefs allparents tagobjid
6846 if {![info exists allcommits]} {
6847 set nextarc 0
6848 set allcommits 0
6849 set seeds {}
6850 set allcwait 0
6851 set cachedarcs 0
6852 set allccache [file join [gitdir] "gitk.cache"]
6853 if {![catch {
6854 set f [open $allccache r]
6855 set allcwait 1
6856 getcache $f
6857 }]} return
6860 if {$allcwait} {
6861 return
6863 set cmd [list | git rev-list --parents]
6864 set allcupdate [expr {$seeds ne {}}]
6865 if {!$allcupdate} {
6866 set ids "--all"
6867 } else {
6868 set refs [concat [array names idheads] [array names idtags] \
6869 [array names idotherrefs]]
6870 set ids {}
6871 set tagobjs {}
6872 foreach name [array names tagobjid] {
6873 lappend tagobjs $tagobjid($name)
6875 foreach id [lsort -unique $refs] {
6876 if {![info exists allparents($id)] &&
6877 [lsearch -exact $tagobjs $id] < 0} {
6878 lappend ids $id
6881 if {$ids ne {}} {
6882 foreach id $seeds {
6883 lappend ids "^$id"
6887 if {$ids ne {}} {
6888 set fd [open [concat $cmd $ids] r]
6889 fconfigure $fd -blocking 0
6890 incr allcommits
6891 nowbusy allcommits
6892 filerun $fd [list getallclines $fd]
6893 } else {
6894 dispneartags 0
6898 # Since most commits have 1 parent and 1 child, we group strings of
6899 # such commits into "arcs" joining branch/merge points (BMPs), which
6900 # are commits that either don't have 1 parent or don't have 1 child.
6902 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6903 # arcout(id) - outgoing arcs for BMP
6904 # arcids(a) - list of IDs on arc including end but not start
6905 # arcstart(a) - BMP ID at start of arc
6906 # arcend(a) - BMP ID at end of arc
6907 # growing(a) - arc a is still growing
6908 # arctags(a) - IDs out of arcids (excluding end) that have tags
6909 # archeads(a) - IDs out of arcids (excluding end) that have heads
6910 # The start of an arc is at the descendent end, so "incoming" means
6911 # coming from descendents, and "outgoing" means going towards ancestors.
6913 proc getallclines {fd} {
6914 global allparents allchildren idtags idheads nextarc
6915 global arcnos arcids arctags arcout arcend arcstart archeads growing
6916 global seeds allcommits cachedarcs allcupdate
6918 set nid 0
6919 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6920 set id [lindex $line 0]
6921 if {[info exists allparents($id)]} {
6922 # seen it already
6923 continue
6925 set cachedarcs 0
6926 set olds [lrange $line 1 end]
6927 set allparents($id) $olds
6928 if {![info exists allchildren($id)]} {
6929 set allchildren($id) {}
6930 set arcnos($id) {}
6931 lappend seeds $id
6932 } else {
6933 set a $arcnos($id)
6934 if {[llength $olds] == 1 && [llength $a] == 1} {
6935 lappend arcids($a) $id
6936 if {[info exists idtags($id)]} {
6937 lappend arctags($a) $id
6939 if {[info exists idheads($id)]} {
6940 lappend archeads($a) $id
6942 if {[info exists allparents($olds)]} {
6943 # seen parent already
6944 if {![info exists arcout($olds)]} {
6945 splitarc $olds
6947 lappend arcids($a) $olds
6948 set arcend($a) $olds
6949 unset growing($a)
6951 lappend allchildren($olds) $id
6952 lappend arcnos($olds) $a
6953 continue
6956 foreach a $arcnos($id) {
6957 lappend arcids($a) $id
6958 set arcend($a) $id
6959 unset growing($a)
6962 set ao {}
6963 foreach p $olds {
6964 lappend allchildren($p) $id
6965 set a [incr nextarc]
6966 set arcstart($a) $id
6967 set archeads($a) {}
6968 set arctags($a) {}
6969 set archeads($a) {}
6970 set arcids($a) {}
6971 lappend ao $a
6972 set growing($a) 1
6973 if {[info exists allparents($p)]} {
6974 # seen it already, may need to make a new branch
6975 if {![info exists arcout($p)]} {
6976 splitarc $p
6978 lappend arcids($a) $p
6979 set arcend($a) $p
6980 unset growing($a)
6982 lappend arcnos($p) $a
6984 set arcout($id) $ao
6986 if {$nid > 0} {
6987 global cached_dheads cached_dtags cached_atags
6988 catch {unset cached_dheads}
6989 catch {unset cached_dtags}
6990 catch {unset cached_atags}
6992 if {![eof $fd]} {
6993 return [expr {$nid >= 1000? 2: 1}]
6995 set cacheok 1
6996 if {[catch {
6997 fconfigure $fd -blocking 1
6998 close $fd
6999 } err]} {
7000 # got an error reading the list of commits
7001 # if we were updating, try rereading the whole thing again
7002 if {$allcupdate} {
7003 incr allcommits -1
7004 dropcache $err
7005 return
7007 error_popup "[mc "Error reading commit topology information;\
7008 branch and preceding/following tag information\
7009 will be incomplete."]\n($err)"
7010 set cacheok 0
7012 if {[incr allcommits -1] == 0} {
7013 notbusy allcommits
7014 if {$cacheok} {
7015 run savecache
7018 dispneartags 0
7019 return 0
7022 proc recalcarc {a} {
7023 global arctags archeads arcids idtags idheads
7025 set at {}
7026 set ah {}
7027 foreach id [lrange $arcids($a) 0 end-1] {
7028 if {[info exists idtags($id)]} {
7029 lappend at $id
7031 if {[info exists idheads($id)]} {
7032 lappend ah $id
7035 set arctags($a) $at
7036 set archeads($a) $ah
7039 proc splitarc {p} {
7040 global arcnos arcids nextarc arctags archeads idtags idheads
7041 global arcstart arcend arcout allparents growing
7043 set a $arcnos($p)
7044 if {[llength $a] != 1} {
7045 puts "oops splitarc called but [llength $a] arcs already"
7046 return
7048 set a [lindex $a 0]
7049 set i [lsearch -exact $arcids($a) $p]
7050 if {$i < 0} {
7051 puts "oops splitarc $p not in arc $a"
7052 return
7054 set na [incr nextarc]
7055 if {[info exists arcend($a)]} {
7056 set arcend($na) $arcend($a)
7057 } else {
7058 set l [lindex $allparents([lindex $arcids($a) end]) 0]
7059 set j [lsearch -exact $arcnos($l) $a]
7060 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
7062 set tail [lrange $arcids($a) [expr {$i+1}] end]
7063 set arcids($a) [lrange $arcids($a) 0 $i]
7064 set arcend($a) $p
7065 set arcstart($na) $p
7066 set arcout($p) $na
7067 set arcids($na) $tail
7068 if {[info exists growing($a)]} {
7069 set growing($na) 1
7070 unset growing($a)
7073 foreach id $tail {
7074 if {[llength $arcnos($id)] == 1} {
7075 set arcnos($id) $na
7076 } else {
7077 set j [lsearch -exact $arcnos($id) $a]
7078 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
7082 # reconstruct tags and heads lists
7083 if {$arctags($a) ne {} || $archeads($a) ne {}} {
7084 recalcarc $a
7085 recalcarc $na
7086 } else {
7087 set arctags($na) {}
7088 set archeads($na) {}
7092 # Update things for a new commit added that is a child of one
7093 # existing commit. Used when cherry-picking.
7094 proc addnewchild {id p} {
7095 global allparents allchildren idtags nextarc
7096 global arcnos arcids arctags arcout arcend arcstart archeads growing
7097 global seeds allcommits
7099 if {![info exists allcommits] || ![info exists arcnos($p)]} return
7100 set allparents($id) [list $p]
7101 set allchildren($id) {}
7102 set arcnos($id) {}
7103 lappend seeds $id
7104 lappend allchildren($p) $id
7105 set a [incr nextarc]
7106 set arcstart($a) $id
7107 set archeads($a) {}
7108 set arctags($a) {}
7109 set arcids($a) [list $p]
7110 set arcend($a) $p
7111 if {![info exists arcout($p)]} {
7112 splitarc $p
7114 lappend arcnos($p) $a
7115 set arcout($id) [list $a]
7118 # This implements a cache for the topology information.
7119 # The cache saves, for each arc, the start and end of the arc,
7120 # the ids on the arc, and the outgoing arcs from the end.
7121 proc readcache {f} {
7122 global arcnos arcids arcout arcstart arcend arctags archeads nextarc
7123 global idtags idheads allparents cachedarcs possible_seeds seeds growing
7124 global allcwait
7126 set a $nextarc
7127 set lim $cachedarcs
7128 if {$lim - $a > 500} {
7129 set lim [expr {$a + 500}]
7131 if {[catch {
7132 if {$a == $lim} {
7133 # finish reading the cache and setting up arctags, etc.
7134 set line [gets $f]
7135 if {$line ne "1"} {error "bad final version"}
7136 close $f
7137 foreach id [array names idtags] {
7138 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7139 [llength $allparents($id)] == 1} {
7140 set a [lindex $arcnos($id) 0]
7141 if {$arctags($a) eq {}} {
7142 recalcarc $a
7146 foreach id [array names idheads] {
7147 if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
7148 [llength $allparents($id)] == 1} {
7149 set a [lindex $arcnos($id) 0]
7150 if {$archeads($a) eq {}} {
7151 recalcarc $a
7155 foreach id [lsort -unique $possible_seeds] {
7156 if {$arcnos($id) eq {}} {
7157 lappend seeds $id
7160 set allcwait 0
7161 } else {
7162 while {[incr a] <= $lim} {
7163 set line [gets $f]
7164 if {[llength $line] != 3} {error "bad line"}
7165 set s [lindex $line 0]
7166 set arcstart($a) $s
7167 lappend arcout($s) $a
7168 if {![info exists arcnos($s)]} {
7169 lappend possible_seeds $s
7170 set arcnos($s) {}
7172 set e [lindex $line 1]
7173 if {$e eq {}} {
7174 set growing($a) 1
7175 } else {
7176 set arcend($a) $e
7177 if {![info exists arcout($e)]} {
7178 set arcout($e) {}
7181 set arcids($a) [lindex $line 2]
7182 foreach id $arcids($a) {
7183 lappend allparents($s) $id
7184 set s $id
7185 lappend arcnos($id) $a
7187 if {![info exists allparents($s)]} {
7188 set allparents($s) {}
7190 set arctags($a) {}
7191 set archeads($a) {}
7193 set nextarc [expr {$a - 1}]
7195 } err]} {
7196 dropcache $err
7197 return 0
7199 if {!$allcwait} {
7200 getallcommits
7202 return $allcwait
7205 proc getcache {f} {
7206 global nextarc cachedarcs possible_seeds
7208 if {[catch {
7209 set line [gets $f]
7210 if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
7211 # make sure it's an integer
7212 set cachedarcs [expr {int([lindex $line 1])}]
7213 if {$cachedarcs < 0} {error "bad number of arcs"}
7214 set nextarc 0
7215 set possible_seeds {}
7216 run readcache $f
7217 } err]} {
7218 dropcache $err
7220 return 0
7223 proc dropcache {err} {
7224 global allcwait nextarc cachedarcs seeds
7226 #puts "dropping cache ($err)"
7227 foreach v {arcnos arcout arcids arcstart arcend growing \
7228 arctags archeads allparents allchildren} {
7229 global $v
7230 catch {unset $v}
7232 set allcwait 0
7233 set nextarc 0
7234 set cachedarcs 0
7235 set seeds {}
7236 getallcommits
7239 proc writecache {f} {
7240 global cachearc cachedarcs allccache
7241 global arcstart arcend arcnos arcids arcout
7243 set a $cachearc
7244 set lim $cachedarcs
7245 if {$lim - $a > 1000} {
7246 set lim [expr {$a + 1000}]
7248 if {[catch {
7249 while {[incr a] <= $lim} {
7250 if {[info exists arcend($a)]} {
7251 puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
7252 } else {
7253 puts $f [list $arcstart($a) {} $arcids($a)]
7256 } err]} {
7257 catch {close $f}
7258 catch {file delete $allccache}
7259 #puts "writing cache failed ($err)"
7260 return 0
7262 set cachearc [expr {$a - 1}]
7263 if {$a > $cachedarcs} {
7264 puts $f "1"
7265 close $f
7266 return 0
7268 return 1
7271 proc savecache {} {
7272 global nextarc cachedarcs cachearc allccache
7274 if {$nextarc == $cachedarcs} return
7275 set cachearc 0
7276 set cachedarcs $nextarc
7277 catch {
7278 set f [open $allccache w]
7279 puts $f [list 1 $cachedarcs]
7280 run writecache $f
7284 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
7285 # or 0 if neither is true.
7286 proc anc_or_desc {a b} {
7287 global arcout arcstart arcend arcnos cached_isanc
7289 if {$arcnos($a) eq $arcnos($b)} {
7290 # Both are on the same arc(s); either both are the same BMP,
7291 # or if one is not a BMP, the other is also not a BMP or is
7292 # the BMP at end of the arc (and it only has 1 incoming arc).
7293 # Or both can be BMPs with no incoming arcs.
7294 if {$a eq $b || $arcnos($a) eq {}} {
7295 return 0
7297 # assert {[llength $arcnos($a)] == 1}
7298 set arc [lindex $arcnos($a) 0]
7299 set i [lsearch -exact $arcids($arc) $a]
7300 set j [lsearch -exact $arcids($arc) $b]
7301 if {$i < 0 || $i > $j} {
7302 return 1
7303 } else {
7304 return -1
7308 if {![info exists arcout($a)]} {
7309 set arc [lindex $arcnos($a) 0]
7310 if {[info exists arcend($arc)]} {
7311 set aend $arcend($arc)
7312 } else {
7313 set aend {}
7315 set a $arcstart($arc)
7316 } else {
7317 set aend $a
7319 if {![info exists arcout($b)]} {
7320 set arc [lindex $arcnos($b) 0]
7321 if {[info exists arcend($arc)]} {
7322 set bend $arcend($arc)
7323 } else {
7324 set bend {}
7326 set b $arcstart($arc)
7327 } else {
7328 set bend $b
7330 if {$a eq $bend} {
7331 return 1
7333 if {$b eq $aend} {
7334 return -1
7336 if {[info exists cached_isanc($a,$bend)]} {
7337 if {$cached_isanc($a,$bend)} {
7338 return 1
7341 if {[info exists cached_isanc($b,$aend)]} {
7342 if {$cached_isanc($b,$aend)} {
7343 return -1
7345 if {[info exists cached_isanc($a,$bend)]} {
7346 return 0
7350 set todo [list $a $b]
7351 set anc($a) a
7352 set anc($b) b
7353 for {set i 0} {$i < [llength $todo]} {incr i} {
7354 set x [lindex $todo $i]
7355 if {$anc($x) eq {}} {
7356 continue
7358 foreach arc $arcnos($x) {
7359 set xd $arcstart($arc)
7360 if {$xd eq $bend} {
7361 set cached_isanc($a,$bend) 1
7362 set cached_isanc($b,$aend) 0
7363 return 1
7364 } elseif {$xd eq $aend} {
7365 set cached_isanc($b,$aend) 1
7366 set cached_isanc($a,$bend) 0
7367 return -1
7369 if {![info exists anc($xd)]} {
7370 set anc($xd) $anc($x)
7371 lappend todo $xd
7372 } elseif {$anc($xd) ne $anc($x)} {
7373 set anc($xd) {}
7377 set cached_isanc($a,$bend) 0
7378 set cached_isanc($b,$aend) 0
7379 return 0
7382 # This identifies whether $desc has an ancestor that is
7383 # a growing tip of the graph and which is not an ancestor of $anc
7384 # and returns 0 if so and 1 if not.
7385 # If we subsequently discover a tag on such a growing tip, and that
7386 # turns out to be a descendent of $anc (which it could, since we
7387 # don't necessarily see children before parents), then $desc
7388 # isn't a good choice to display as a descendent tag of
7389 # $anc (since it is the descendent of another tag which is
7390 # a descendent of $anc). Similarly, $anc isn't a good choice to
7391 # display as a ancestor tag of $desc.
7393 proc is_certain {desc anc} {
7394 global arcnos arcout arcstart arcend growing problems
7396 set certain {}
7397 if {[llength $arcnos($anc)] == 1} {
7398 # tags on the same arc are certain
7399 if {$arcnos($desc) eq $arcnos($anc)} {
7400 return 1
7402 if {![info exists arcout($anc)]} {
7403 # if $anc is partway along an arc, use the start of the arc instead
7404 set a [lindex $arcnos($anc) 0]
7405 set anc $arcstart($a)
7408 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
7409 set x $desc
7410 } else {
7411 set a [lindex $arcnos($desc) 0]
7412 set x $arcend($a)
7414 if {$x == $anc} {
7415 return 1
7417 set anclist [list $x]
7418 set dl($x) 1
7419 set nnh 1
7420 set ngrowanc 0
7421 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
7422 set x [lindex $anclist $i]
7423 if {$dl($x)} {
7424 incr nnh -1
7426 set done($x) 1
7427 foreach a $arcout($x) {
7428 if {[info exists growing($a)]} {
7429 if {![info exists growanc($x)] && $dl($x)} {
7430 set growanc($x) 1
7431 incr ngrowanc
7433 } else {
7434 set y $arcend($a)
7435 if {[info exists dl($y)]} {
7436 if {$dl($y)} {
7437 if {!$dl($x)} {
7438 set dl($y) 0
7439 if {![info exists done($y)]} {
7440 incr nnh -1
7442 if {[info exists growanc($x)]} {
7443 incr ngrowanc -1
7445 set xl [list $y]
7446 for {set k 0} {$k < [llength $xl]} {incr k} {
7447 set z [lindex $xl $k]
7448 foreach c $arcout($z) {
7449 if {[info exists arcend($c)]} {
7450 set v $arcend($c)
7451 if {[info exists dl($v)] && $dl($v)} {
7452 set dl($v) 0
7453 if {![info exists done($v)]} {
7454 incr nnh -1
7456 if {[info exists growanc($v)]} {
7457 incr ngrowanc -1
7459 lappend xl $v
7466 } elseif {$y eq $anc || !$dl($x)} {
7467 set dl($y) 0
7468 lappend anclist $y
7469 } else {
7470 set dl($y) 1
7471 lappend anclist $y
7472 incr nnh
7477 foreach x [array names growanc] {
7478 if {$dl($x)} {
7479 return 0
7481 return 0
7483 return 1
7486 proc validate_arctags {a} {
7487 global arctags idtags
7489 set i -1
7490 set na $arctags($a)
7491 foreach id $arctags($a) {
7492 incr i
7493 if {![info exists idtags($id)]} {
7494 set na [lreplace $na $i $i]
7495 incr i -1
7498 set arctags($a) $na
7501 proc validate_archeads {a} {
7502 global archeads idheads
7504 set i -1
7505 set na $archeads($a)
7506 foreach id $archeads($a) {
7507 incr i
7508 if {![info exists idheads($id)]} {
7509 set na [lreplace $na $i $i]
7510 incr i -1
7513 set archeads($a) $na
7516 # Return the list of IDs that have tags that are descendents of id,
7517 # ignoring IDs that are descendents of IDs already reported.
7518 proc desctags {id} {
7519 global arcnos arcstart arcids arctags idtags allparents
7520 global growing cached_dtags
7522 if {![info exists allparents($id)]} {
7523 return {}
7525 set t1 [clock clicks -milliseconds]
7526 set argid $id
7527 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7528 # part-way along an arc; check that arc first
7529 set a [lindex $arcnos($id) 0]
7530 if {$arctags($a) ne {}} {
7531 validate_arctags $a
7532 set i [lsearch -exact $arcids($a) $id]
7533 set tid {}
7534 foreach t $arctags($a) {
7535 set j [lsearch -exact $arcids($a) $t]
7536 if {$j >= $i} break
7537 set tid $t
7539 if {$tid ne {}} {
7540 return $tid
7543 set id $arcstart($a)
7544 if {[info exists idtags($id)]} {
7545 return $id
7548 if {[info exists cached_dtags($id)]} {
7549 return $cached_dtags($id)
7552 set origid $id
7553 set todo [list $id]
7554 set queued($id) 1
7555 set nc 1
7556 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7557 set id [lindex $todo $i]
7558 set done($id) 1
7559 set ta [info exists hastaggedancestor($id)]
7560 if {!$ta} {
7561 incr nc -1
7563 # ignore tags on starting node
7564 if {!$ta && $i > 0} {
7565 if {[info exists idtags($id)]} {
7566 set tagloc($id) $id
7567 set ta 1
7568 } elseif {[info exists cached_dtags($id)]} {
7569 set tagloc($id) $cached_dtags($id)
7570 set ta 1
7573 foreach a $arcnos($id) {
7574 set d $arcstart($a)
7575 if {!$ta && $arctags($a) ne {}} {
7576 validate_arctags $a
7577 if {$arctags($a) ne {}} {
7578 lappend tagloc($id) [lindex $arctags($a) end]
7581 if {$ta || $arctags($a) ne {}} {
7582 set tomark [list $d]
7583 for {set j 0} {$j < [llength $tomark]} {incr j} {
7584 set dd [lindex $tomark $j]
7585 if {![info exists hastaggedancestor($dd)]} {
7586 if {[info exists done($dd)]} {
7587 foreach b $arcnos($dd) {
7588 lappend tomark $arcstart($b)
7590 if {[info exists tagloc($dd)]} {
7591 unset tagloc($dd)
7593 } elseif {[info exists queued($dd)]} {
7594 incr nc -1
7596 set hastaggedancestor($dd) 1
7600 if {![info exists queued($d)]} {
7601 lappend todo $d
7602 set queued($d) 1
7603 if {![info exists hastaggedancestor($d)]} {
7604 incr nc
7609 set tags {}
7610 foreach id [array names tagloc] {
7611 if {![info exists hastaggedancestor($id)]} {
7612 foreach t $tagloc($id) {
7613 if {[lsearch -exact $tags $t] < 0} {
7614 lappend tags $t
7619 set t2 [clock clicks -milliseconds]
7620 set loopix $i
7622 # remove tags that are descendents of other tags
7623 for {set i 0} {$i < [llength $tags]} {incr i} {
7624 set a [lindex $tags $i]
7625 for {set j 0} {$j < $i} {incr j} {
7626 set b [lindex $tags $j]
7627 set r [anc_or_desc $a $b]
7628 if {$r == 1} {
7629 set tags [lreplace $tags $j $j]
7630 incr j -1
7631 incr i -1
7632 } elseif {$r == -1} {
7633 set tags [lreplace $tags $i $i]
7634 incr i -1
7635 break
7640 if {[array names growing] ne {}} {
7641 # graph isn't finished, need to check if any tag could get
7642 # eclipsed by another tag coming later. Simply ignore any
7643 # tags that could later get eclipsed.
7644 set ctags {}
7645 foreach t $tags {
7646 if {[is_certain $t $origid]} {
7647 lappend ctags $t
7650 if {$tags eq $ctags} {
7651 set cached_dtags($origid) $tags
7652 } else {
7653 set tags $ctags
7655 } else {
7656 set cached_dtags($origid) $tags
7658 set t3 [clock clicks -milliseconds]
7659 if {0 && $t3 - $t1 >= 100} {
7660 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
7661 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
7663 return $tags
7666 proc anctags {id} {
7667 global arcnos arcids arcout arcend arctags idtags allparents
7668 global growing cached_atags
7670 if {![info exists allparents($id)]} {
7671 return {}
7673 set t1 [clock clicks -milliseconds]
7674 set argid $id
7675 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7676 # part-way along an arc; check that arc first
7677 set a [lindex $arcnos($id) 0]
7678 if {$arctags($a) ne {}} {
7679 validate_arctags $a
7680 set i [lsearch -exact $arcids($a) $id]
7681 foreach t $arctags($a) {
7682 set j [lsearch -exact $arcids($a) $t]
7683 if {$j > $i} {
7684 return $t
7688 if {![info exists arcend($a)]} {
7689 return {}
7691 set id $arcend($a)
7692 if {[info exists idtags($id)]} {
7693 return $id
7696 if {[info exists cached_atags($id)]} {
7697 return $cached_atags($id)
7700 set origid $id
7701 set todo [list $id]
7702 set queued($id) 1
7703 set taglist {}
7704 set nc 1
7705 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
7706 set id [lindex $todo $i]
7707 set done($id) 1
7708 set td [info exists hastaggeddescendent($id)]
7709 if {!$td} {
7710 incr nc -1
7712 # ignore tags on starting node
7713 if {!$td && $i > 0} {
7714 if {[info exists idtags($id)]} {
7715 set tagloc($id) $id
7716 set td 1
7717 } elseif {[info exists cached_atags($id)]} {
7718 set tagloc($id) $cached_atags($id)
7719 set td 1
7722 foreach a $arcout($id) {
7723 if {!$td && $arctags($a) ne {}} {
7724 validate_arctags $a
7725 if {$arctags($a) ne {}} {
7726 lappend tagloc($id) [lindex $arctags($a) 0]
7729 if {![info exists arcend($a)]} continue
7730 set d $arcend($a)
7731 if {$td || $arctags($a) ne {}} {
7732 set tomark [list $d]
7733 for {set j 0} {$j < [llength $tomark]} {incr j} {
7734 set dd [lindex $tomark $j]
7735 if {![info exists hastaggeddescendent($dd)]} {
7736 if {[info exists done($dd)]} {
7737 foreach b $arcout($dd) {
7738 if {[info exists arcend($b)]} {
7739 lappend tomark $arcend($b)
7742 if {[info exists tagloc($dd)]} {
7743 unset tagloc($dd)
7745 } elseif {[info exists queued($dd)]} {
7746 incr nc -1
7748 set hastaggeddescendent($dd) 1
7752 if {![info exists queued($d)]} {
7753 lappend todo $d
7754 set queued($d) 1
7755 if {![info exists hastaggeddescendent($d)]} {
7756 incr nc
7761 set t2 [clock clicks -milliseconds]
7762 set loopix $i
7763 set tags {}
7764 foreach id [array names tagloc] {
7765 if {![info exists hastaggeddescendent($id)]} {
7766 foreach t $tagloc($id) {
7767 if {[lsearch -exact $tags $t] < 0} {
7768 lappend tags $t
7774 # remove tags that are ancestors of other tags
7775 for {set i 0} {$i < [llength $tags]} {incr i} {
7776 set a [lindex $tags $i]
7777 for {set j 0} {$j < $i} {incr j} {
7778 set b [lindex $tags $j]
7779 set r [anc_or_desc $a $b]
7780 if {$r == -1} {
7781 set tags [lreplace $tags $j $j]
7782 incr j -1
7783 incr i -1
7784 } elseif {$r == 1} {
7785 set tags [lreplace $tags $i $i]
7786 incr i -1
7787 break
7792 if {[array names growing] ne {}} {
7793 # graph isn't finished, need to check if any tag could get
7794 # eclipsed by another tag coming later. Simply ignore any
7795 # tags that could later get eclipsed.
7796 set ctags {}
7797 foreach t $tags {
7798 if {[is_certain $origid $t]} {
7799 lappend ctags $t
7802 if {$tags eq $ctags} {
7803 set cached_atags($origid) $tags
7804 } else {
7805 set tags $ctags
7807 } else {
7808 set cached_atags($origid) $tags
7810 set t3 [clock clicks -milliseconds]
7811 if {0 && $t3 - $t1 >= 100} {
7812 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
7813 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
7815 return $tags
7818 # Return the list of IDs that have heads that are descendents of id,
7819 # including id itself if it has a head.
7820 proc descheads {id} {
7821 global arcnos arcstart arcids archeads idheads cached_dheads
7822 global allparents
7824 if {![info exists allparents($id)]} {
7825 return {}
7827 set aret {}
7828 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
7829 # part-way along an arc; check it first
7830 set a [lindex $arcnos($id) 0]
7831 if {$archeads($a) ne {}} {
7832 validate_archeads $a
7833 set i [lsearch -exact $arcids($a) $id]
7834 foreach t $archeads($a) {
7835 set j [lsearch -exact $arcids($a) $t]
7836 if {$j > $i} break
7837 lappend aret $t
7840 set id $arcstart($a)
7842 set origid $id
7843 set todo [list $id]
7844 set seen($id) 1
7845 set ret {}
7846 for {set i 0} {$i < [llength $todo]} {incr i} {
7847 set id [lindex $todo $i]
7848 if {[info exists cached_dheads($id)]} {
7849 set ret [concat $ret $cached_dheads($id)]
7850 } else {
7851 if {[info exists idheads($id)]} {
7852 lappend ret $id
7854 foreach a $arcnos($id) {
7855 if {$archeads($a) ne {}} {
7856 validate_archeads $a
7857 if {$archeads($a) ne {}} {
7858 set ret [concat $ret $archeads($a)]
7861 set d $arcstart($a)
7862 if {![info exists seen($d)]} {
7863 lappend todo $d
7864 set seen($d) 1
7869 set ret [lsort -unique $ret]
7870 set cached_dheads($origid) $ret
7871 return [concat $ret $aret]
7874 proc addedtag {id} {
7875 global arcnos arcout cached_dtags cached_atags
7877 if {![info exists arcnos($id)]} return
7878 if {![info exists arcout($id)]} {
7879 recalcarc [lindex $arcnos($id) 0]
7881 catch {unset cached_dtags}
7882 catch {unset cached_atags}
7885 proc addedhead {hid head} {
7886 global arcnos arcout cached_dheads
7888 if {![info exists arcnos($hid)]} return
7889 if {![info exists arcout($hid)]} {
7890 recalcarc [lindex $arcnos($hid) 0]
7892 catch {unset cached_dheads}
7895 proc removedhead {hid head} {
7896 global cached_dheads
7898 catch {unset cached_dheads}
7901 proc movedhead {hid head} {
7902 global arcnos arcout cached_dheads
7904 if {![info exists arcnos($hid)]} return
7905 if {![info exists arcout($hid)]} {
7906 recalcarc [lindex $arcnos($hid) 0]
7908 catch {unset cached_dheads}
7911 proc changedrefs {} {
7912 global cached_dheads cached_dtags cached_atags
7913 global arctags archeads arcnos arcout idheads idtags
7915 foreach id [concat [array names idheads] [array names idtags]] {
7916 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
7917 set a [lindex $arcnos($id) 0]
7918 if {![info exists donearc($a)]} {
7919 recalcarc $a
7920 set donearc($a) 1
7924 catch {unset cached_dtags}
7925 catch {unset cached_atags}
7926 catch {unset cached_dheads}
7929 proc rereadrefs {} {
7930 global idtags idheads idotherrefs mainhead
7932 set refids [concat [array names idtags] \
7933 [array names idheads] [array names idotherrefs]]
7934 foreach id $refids {
7935 if {![info exists ref($id)]} {
7936 set ref($id) [listrefs $id]
7939 set oldmainhead $mainhead
7940 readrefs
7941 changedrefs
7942 set refids [lsort -unique [concat $refids [array names idtags] \
7943 [array names idheads] [array names idotherrefs]]]
7944 foreach id $refids {
7945 set v [listrefs $id]
7946 if {![info exists ref($id)] || $ref($id) != $v ||
7947 ($id eq $oldmainhead && $id ne $mainhead) ||
7948 ($id eq $mainhead && $id ne $oldmainhead)} {
7949 redrawtags $id
7952 run refill_reflist
7955 proc listrefs {id} {
7956 global idtags idheads idotherrefs
7958 set x {}
7959 if {[info exists idtags($id)]} {
7960 set x $idtags($id)
7962 set y {}
7963 if {[info exists idheads($id)]} {
7964 set y $idheads($id)
7966 set z {}
7967 if {[info exists idotherrefs($id)]} {
7968 set z $idotherrefs($id)
7970 return [list $x $y $z]
7973 proc showtag {tag isnew} {
7974 global ctext tagcontents tagids linknum tagobjid
7976 if {$isnew} {
7977 addtohistory [list showtag $tag 0]
7979 $ctext conf -state normal
7980 clear_ctext
7981 settabs 0
7982 set linknum 0
7983 if {![info exists tagcontents($tag)]} {
7984 catch {
7985 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7988 if {[info exists tagcontents($tag)]} {
7989 set text $tagcontents($tag)
7990 } else {
7991 set text "[mc "Tag"]: $tag\n[mc "Id"]: $tagids($tag)"
7993 appendwithlinks $text {}
7994 $ctext conf -state disabled
7995 init_flist {}
7998 proc doquit {} {
7999 global stopped
8000 global gitktmpdir
8002 set stopped 100
8003 savestuff .
8004 destroy .
8006 if {[info exists gitktmpdir]} {
8007 catch {file delete -force $gitktmpdir}
8011 proc mkfontdisp {font top which} {
8012 global fontattr fontpref $font
8014 set fontpref($font) [set $font]
8015 button $top.${font}but -text $which -font optionfont \
8016 -command [list choosefont $font $which]
8017 label $top.$font -relief flat -font $font \
8018 -text $fontattr($font,family) -justify left
8019 grid x $top.${font}but $top.$font -sticky w
8022 proc choosefont {font which} {
8023 global fontparam fontlist fonttop fontattr
8025 set fontparam(which) $which
8026 set fontparam(font) $font
8027 set fontparam(family) [font actual $font -family]
8028 set fontparam(size) $fontattr($font,size)
8029 set fontparam(weight) $fontattr($font,weight)
8030 set fontparam(slant) $fontattr($font,slant)
8031 set top .gitkfont
8032 set fonttop $top
8033 if {![winfo exists $top]} {
8034 font create sample
8035 eval font config sample [font actual $font]
8036 toplevel $top
8037 wm title $top [mc "Gitk font chooser"]
8038 label $top.l -textvariable fontparam(which)
8039 pack $top.l -side top
8040 set fontlist [lsort [font families]]
8041 frame $top.f
8042 listbox $top.f.fam -listvariable fontlist \
8043 -yscrollcommand [list $top.f.sb set]
8044 bind $top.f.fam <<ListboxSelect>> selfontfam
8045 scrollbar $top.f.sb -command [list $top.f.fam yview]
8046 pack $top.f.sb -side right -fill y
8047 pack $top.f.fam -side left -fill both -expand 1
8048 pack $top.f -side top -fill both -expand 1
8049 frame $top.g
8050 spinbox $top.g.size -from 4 -to 40 -width 4 \
8051 -textvariable fontparam(size) \
8052 -validatecommand {string is integer -strict %s}
8053 checkbutton $top.g.bold -padx 5 \
8054 -font {{Times New Roman} 12 bold} -text [mc "B"] -indicatoron 0 \
8055 -variable fontparam(weight) -onvalue bold -offvalue normal
8056 checkbutton $top.g.ital -padx 5 \
8057 -font {{Times New Roman} 12 italic} -text [mc "I"] -indicatoron 0 \
8058 -variable fontparam(slant) -onvalue italic -offvalue roman
8059 pack $top.g.size $top.g.bold $top.g.ital -side left
8060 pack $top.g -side top
8061 canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
8062 -background white
8063 $top.c create text 100 25 -anchor center -text $which -font sample \
8064 -fill black -tags text
8065 bind $top.c <Configure> [list centertext $top.c]
8066 pack $top.c -side top -fill x
8067 frame $top.buts
8068 button $top.buts.ok -text [mc "OK"] -command fontok -default active
8069 button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
8070 grid $top.buts.ok $top.buts.can
8071 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8072 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8073 pack $top.buts -side bottom -fill x
8074 trace add variable fontparam write chg_fontparam
8075 } else {
8076 raise $top
8077 $top.c itemconf text -text $which
8079 set i [lsearch -exact $fontlist $fontparam(family)]
8080 if {$i >= 0} {
8081 $top.f.fam selection set $i
8082 $top.f.fam see $i
8086 proc centertext {w} {
8087 $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
8090 proc fontok {} {
8091 global fontparam fontpref prefstop
8093 set f $fontparam(font)
8094 set fontpref($f) [list $fontparam(family) $fontparam(size)]
8095 if {$fontparam(weight) eq "bold"} {
8096 lappend fontpref($f) "bold"
8098 if {$fontparam(slant) eq "italic"} {
8099 lappend fontpref($f) "italic"
8101 set w $prefstop.$f
8102 $w conf -text $fontparam(family) -font $fontpref($f)
8104 fontcan
8107 proc fontcan {} {
8108 global fonttop fontparam
8110 if {[info exists fonttop]} {
8111 catch {destroy $fonttop}
8112 catch {font delete sample}
8113 unset fonttop
8114 unset fontparam
8118 proc selfontfam {} {
8119 global fonttop fontparam
8121 set i [$fonttop.f.fam curselection]
8122 if {$i ne {}} {
8123 set fontparam(family) [$fonttop.f.fam get $i]
8127 proc chg_fontparam {v sub op} {
8128 global fontparam
8130 font config sample -$sub $fontparam($sub)
8133 proc doprefs {} {
8134 global maxwidth maxgraphpct
8135 global oldprefs prefstop showneartags showlocalchanges
8136 global bgcolor fgcolor ctext diffcolors selectbgcolor
8137 global tabstop limitdiffs autoselect extdifftool
8139 set top .gitkprefs
8140 set prefstop $top
8141 if {[winfo exists $top]} {
8142 raise $top
8143 return
8145 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8146 limitdiffs tabstop} {
8147 set oldprefs($v) [set $v]
8149 toplevel $top
8150 wm title $top [mc "Gitk preferences"]
8151 label $top.ldisp -text [mc "Commit list display options"]
8152 grid $top.ldisp - -sticky w -pady 10
8153 label $top.spacer -text " "
8154 label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
8155 -font optionfont
8156 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
8157 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
8158 label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
8159 -font optionfont
8160 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
8161 grid x $top.maxpctl $top.maxpct -sticky w
8162 frame $top.showlocal
8163 label $top.showlocal.l -text [mc "Show local changes"] -font optionfont
8164 checkbutton $top.showlocal.b -variable showlocalchanges
8165 pack $top.showlocal.b $top.showlocal.l -side left
8166 grid x $top.showlocal -sticky w
8167 frame $top.autoselect
8168 label $top.autoselect.l -text [mc "Auto-select SHA1"] -font optionfont
8169 checkbutton $top.autoselect.b -variable autoselect
8170 pack $top.autoselect.b $top.autoselect.l -side left
8171 grid x $top.autoselect -sticky w
8173 label $top.ddisp -text [mc "Diff display options"]
8174 grid $top.ddisp - -sticky w -pady 10
8175 label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
8176 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
8177 grid x $top.tabstopl $top.tabstop -sticky w
8178 frame $top.ntag
8179 label $top.ntag.l -text [mc "Display nearby tags"] -font optionfont
8180 checkbutton $top.ntag.b -variable showneartags
8181 pack $top.ntag.b $top.ntag.l -side left
8182 grid x $top.ntag -sticky w
8183 frame $top.ldiff
8184 label $top.ldiff.l -text [mc "Limit diffs to listed paths"] -font optionfont
8185 checkbutton $top.ldiff.b -variable limitdiffs
8186 pack $top.ldiff.b $top.ldiff.l -side left
8187 grid x $top.ldiff -sticky w
8189 entry $top.extdifft -textvariable extdifftool
8190 frame $top.extdifff
8191 label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
8192 -padx 10
8193 button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
8194 -command choose_extdiff
8195 pack $top.extdifff.l $top.extdifff.b -side left
8196 grid x $top.extdifff $top.extdifft -sticky w
8198 label $top.cdisp -text [mc "Colors: press to choose"]
8199 grid $top.cdisp - -sticky w -pady 10
8200 label $top.bg -padx 40 -relief sunk -background $bgcolor
8201 button $top.bgbut -text [mc "Background"] -font optionfont \
8202 -command [list choosecolor bgcolor {} $top.bg background setbg]
8203 grid x $top.bgbut $top.bg -sticky w
8204 label $top.fg -padx 40 -relief sunk -background $fgcolor
8205 button $top.fgbut -text [mc "Foreground"] -font optionfont \
8206 -command [list choosecolor fgcolor {} $top.fg foreground setfg]
8207 grid x $top.fgbut $top.fg -sticky w
8208 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
8209 button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
8210 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
8211 [list $ctext tag conf d0 -foreground]]
8212 grid x $top.diffoldbut $top.diffold -sticky w
8213 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
8214 button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
8215 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
8216 [list $ctext tag conf d1 -foreground]]
8217 grid x $top.diffnewbut $top.diffnew -sticky w
8218 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
8219 button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
8220 -command [list choosecolor diffcolors 2 $top.hunksep \
8221 "diff hunk header" \
8222 [list $ctext tag conf hunksep -foreground]]
8223 grid x $top.hunksepbut $top.hunksep -sticky w
8224 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
8225 button $top.selbgbut -text [mc "Select bg"] -font optionfont \
8226 -command [list choosecolor selectbgcolor {} $top.selbgsep background setselbg]
8227 grid x $top.selbgbut $top.selbgsep -sticky w
8229 label $top.cfont -text [mc "Fonts: press to choose"]
8230 grid $top.cfont - -sticky w -pady 10
8231 mkfontdisp mainfont $top [mc "Main font"]
8232 mkfontdisp textfont $top [mc "Diff display font"]
8233 mkfontdisp uifont $top [mc "User interface font"]
8235 frame $top.buts
8236 button $top.buts.ok -text [mc "OK"] -command prefsok -default active
8237 button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
8238 grid $top.buts.ok $top.buts.can
8239 grid columnconfigure $top.buts 0 -weight 1 -uniform a
8240 grid columnconfigure $top.buts 1 -weight 1 -uniform a
8241 grid $top.buts - - -pady 10 -sticky ew
8242 bind $top <Visibility> "focus $top.buts.ok"
8245 proc choose_extdiff {} {
8246 global extdifftool
8248 set prog [tk_getOpenFile -title "External diff tool" -multiple false]
8249 if {$prog ne {}} {
8250 set extdifftool $prog
8254 proc choosecolor {v vi w x cmd} {
8255 global $v
8257 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
8258 -title [mc "Gitk: choose color for %s" $x]]
8259 if {$c eq {}} return
8260 $w conf -background $c
8261 lset $v $vi $c
8262 eval $cmd $c
8265 proc setselbg {c} {
8266 global bglist cflist
8267 foreach w $bglist {
8268 $w configure -selectbackground $c
8270 $cflist tag configure highlight \
8271 -background [$cflist cget -selectbackground]
8272 allcanvs itemconf secsel -fill $c
8275 proc setbg {c} {
8276 global bglist
8278 foreach w $bglist {
8279 $w conf -background $c
8283 proc setfg {c} {
8284 global fglist canv
8286 foreach w $fglist {
8287 $w conf -foreground $c
8289 allcanvs itemconf text -fill $c
8290 $canv itemconf circle -outline $c
8293 proc prefscan {} {
8294 global oldprefs prefstop
8296 foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
8297 limitdiffs tabstop} {
8298 global $v
8299 set $v $oldprefs($v)
8301 catch {destroy $prefstop}
8302 unset prefstop
8303 fontcan
8306 proc prefsok {} {
8307 global maxwidth maxgraphpct
8308 global oldprefs prefstop showneartags showlocalchanges
8309 global fontpref mainfont textfont uifont
8310 global limitdiffs treediffs
8312 catch {destroy $prefstop}
8313 unset prefstop
8314 fontcan
8315 set fontchanged 0
8316 if {$mainfont ne $fontpref(mainfont)} {
8317 set mainfont $fontpref(mainfont)
8318 parsefont mainfont $mainfont
8319 eval font configure mainfont [fontflags mainfont]
8320 eval font configure mainfontbold [fontflags mainfont 1]
8321 setcoords
8322 set fontchanged 1
8324 if {$textfont ne $fontpref(textfont)} {
8325 set textfont $fontpref(textfont)
8326 parsefont textfont $textfont
8327 eval font configure textfont [fontflags textfont]
8328 eval font configure textfontbold [fontflags textfont 1]
8330 if {$uifont ne $fontpref(uifont)} {
8331 set uifont $fontpref(uifont)
8332 parsefont uifont $uifont
8333 eval font configure uifont [fontflags uifont]
8335 settabs
8336 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
8337 if {$showlocalchanges} {
8338 doshowlocalchanges
8339 } else {
8340 dohidelocalchanges
8343 if {$limitdiffs != $oldprefs(limitdiffs)} {
8344 # treediffs elements are limited by path
8345 catch {unset treediffs}
8347 if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
8348 || $maxgraphpct != $oldprefs(maxgraphpct)} {
8349 redisplay
8350 } elseif {$showneartags != $oldprefs(showneartags) ||
8351 $limitdiffs != $oldprefs(limitdiffs)} {
8352 reselectline
8356 proc formatdate {d} {
8357 global datetimeformat
8358 if {$d ne {}} {
8359 set d [clock format $d -format $datetimeformat]
8361 return $d
8364 # This list of encoding names and aliases is distilled from
8365 # http://www.iana.org/assignments/character-sets.
8366 # Not all of them are supported by Tcl.
8367 set encoding_aliases {
8368 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
8369 ISO646-US US-ASCII us IBM367 cp367 csASCII }
8370 { ISO-10646-UTF-1 csISO10646UTF1 }
8371 { ISO_646.basic:1983 ref csISO646basic1983 }
8372 { INVARIANT csINVARIANT }
8373 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
8374 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
8375 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
8376 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
8377 { NATS-DANO iso-ir-9-1 csNATSDANO }
8378 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
8379 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
8380 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
8381 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
8382 { ISO-2022-KR csISO2022KR }
8383 { EUC-KR csEUCKR }
8384 { ISO-2022-JP csISO2022JP }
8385 { ISO-2022-JP-2 csISO2022JP2 }
8386 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
8387 csISO13JISC6220jp }
8388 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
8389 { IT iso-ir-15 ISO646-IT csISO15Italian }
8390 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
8391 { ES iso-ir-17 ISO646-ES csISO17Spanish }
8392 { greek7-old iso-ir-18 csISO18Greek7Old }
8393 { latin-greek iso-ir-19 csISO19LatinGreek }
8394 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
8395 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
8396 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
8397 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
8398 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
8399 { BS_viewdata iso-ir-47 csISO47BSViewdata }
8400 { INIS iso-ir-49 csISO49INIS }
8401 { INIS-8 iso-ir-50 csISO50INIS8 }
8402 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
8403 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
8404 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
8405 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
8406 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
8407 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
8408 csISO60Norwegian1 }
8409 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
8410 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
8411 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
8412 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
8413 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
8414 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
8415 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
8416 { greek7 iso-ir-88 csISO88Greek7 }
8417 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
8418 { iso-ir-90 csISO90 }
8419 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
8420 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
8421 csISO92JISC62991984b }
8422 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
8423 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
8424 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
8425 csISO95JIS62291984handadd }
8426 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
8427 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
8428 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
8429 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
8430 CP819 csISOLatin1 }
8431 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
8432 { T.61-7bit iso-ir-102 csISO102T617bit }
8433 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
8434 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
8435 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
8436 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
8437 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
8438 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
8439 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
8440 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
8441 arabic csISOLatinArabic }
8442 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
8443 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
8444 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
8445 greek greek8 csISOLatinGreek }
8446 { T.101-G2 iso-ir-128 csISO128T101G2 }
8447 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
8448 csISOLatinHebrew }
8449 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
8450 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
8451 { CSN_369103 iso-ir-139 csISO139CSN369103 }
8452 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
8453 { ISO_6937-2-add iso-ir-142 csISOTextComm }
8454 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
8455 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
8456 csISOLatinCyrillic }
8457 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
8458 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
8459 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
8460 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
8461 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
8462 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
8463 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
8464 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
8465 { ISO_10367-box iso-ir-155 csISO10367Box }
8466 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
8467 { latin-lap lap iso-ir-158 csISO158Lap }
8468 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
8469 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
8470 { us-dk csUSDK }
8471 { dk-us csDKUS }
8472 { JIS_X0201 X0201 csHalfWidthKatakana }
8473 { KSC5636 ISO646-KR csKSC5636 }
8474 { ISO-10646-UCS-2 csUnicode }
8475 { ISO-10646-UCS-4 csUCS4 }
8476 { DEC-MCS dec csDECMCS }
8477 { hp-roman8 roman8 r8 csHPRoman8 }
8478 { macintosh mac csMacintosh }
8479 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
8480 csIBM037 }
8481 { IBM038 EBCDIC-INT cp038 csIBM038 }
8482 { IBM273 CP273 csIBM273 }
8483 { IBM274 EBCDIC-BE CP274 csIBM274 }
8484 { IBM275 EBCDIC-BR cp275 csIBM275 }
8485 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
8486 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
8487 { IBM280 CP280 ebcdic-cp-it csIBM280 }
8488 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
8489 { IBM284 CP284 ebcdic-cp-es csIBM284 }
8490 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
8491 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
8492 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
8493 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
8494 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
8495 { IBM424 cp424 ebcdic-cp-he csIBM424 }
8496 { IBM437 cp437 437 csPC8CodePage437 }
8497 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
8498 { IBM775 cp775 csPC775Baltic }
8499 { IBM850 cp850 850 csPC850Multilingual }
8500 { IBM851 cp851 851 csIBM851 }
8501 { IBM852 cp852 852 csPCp852 }
8502 { IBM855 cp855 855 csIBM855 }
8503 { IBM857 cp857 857 csIBM857 }
8504 { IBM860 cp860 860 csIBM860 }
8505 { IBM861 cp861 861 cp-is csIBM861 }
8506 { IBM862 cp862 862 csPC862LatinHebrew }
8507 { IBM863 cp863 863 csIBM863 }
8508 { IBM864 cp864 csIBM864 }
8509 { IBM865 cp865 865 csIBM865 }
8510 { IBM866 cp866 866 csIBM866 }
8511 { IBM868 CP868 cp-ar csIBM868 }
8512 { IBM869 cp869 869 cp-gr csIBM869 }
8513 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
8514 { IBM871 CP871 ebcdic-cp-is csIBM871 }
8515 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
8516 { IBM891 cp891 csIBM891 }
8517 { IBM903 cp903 csIBM903 }
8518 { IBM904 cp904 904 csIBBM904 }
8519 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
8520 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
8521 { IBM1026 CP1026 csIBM1026 }
8522 { EBCDIC-AT-DE csIBMEBCDICATDE }
8523 { EBCDIC-AT-DE-A csEBCDICATDEA }
8524 { EBCDIC-CA-FR csEBCDICCAFR }
8525 { EBCDIC-DK-NO csEBCDICDKNO }
8526 { EBCDIC-DK-NO-A csEBCDICDKNOA }
8527 { EBCDIC-FI-SE csEBCDICFISE }
8528 { EBCDIC-FI-SE-A csEBCDICFISEA }
8529 { EBCDIC-FR csEBCDICFR }
8530 { EBCDIC-IT csEBCDICIT }
8531 { EBCDIC-PT csEBCDICPT }
8532 { EBCDIC-ES csEBCDICES }
8533 { EBCDIC-ES-A csEBCDICESA }
8534 { EBCDIC-ES-S csEBCDICESS }
8535 { EBCDIC-UK csEBCDICUK }
8536 { EBCDIC-US csEBCDICUS }
8537 { UNKNOWN-8BIT csUnknown8BiT }
8538 { MNEMONIC csMnemonic }
8539 { MNEM csMnem }
8540 { VISCII csVISCII }
8541 { VIQR csVIQR }
8542 { KOI8-R csKOI8R }
8543 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
8544 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
8545 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
8546 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
8547 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
8548 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
8549 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
8550 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
8551 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
8552 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
8553 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
8554 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
8555 { IBM1047 IBM-1047 }
8556 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
8557 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
8558 { UNICODE-1-1 csUnicode11 }
8559 { CESU-8 csCESU-8 }
8560 { BOCU-1 csBOCU-1 }
8561 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
8562 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
8563 l8 }
8564 { ISO-8859-15 ISO_8859-15 Latin-9 }
8565 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
8566 { GBK CP936 MS936 windows-936 }
8567 { JIS_Encoding csJISEncoding }
8568 { Shift_JIS MS_Kanji csShiftJIS }
8569 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
8570 EUC-JP }
8571 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
8572 { ISO-10646-UCS-Basic csUnicodeASCII }
8573 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
8574 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
8575 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
8576 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
8577 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
8578 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
8579 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
8580 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
8581 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
8582 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
8583 { Adobe-Standard-Encoding csAdobeStandardEncoding }
8584 { Ventura-US csVenturaUS }
8585 { Ventura-International csVenturaInternational }
8586 { PC8-Danish-Norwegian csPC8DanishNorwegian }
8587 { PC8-Turkish csPC8Turkish }
8588 { IBM-Symbols csIBMSymbols }
8589 { IBM-Thai csIBMThai }
8590 { HP-Legal csHPLegal }
8591 { HP-Pi-font csHPPiFont }
8592 { HP-Math8 csHPMath8 }
8593 { Adobe-Symbol-Encoding csHPPSMath }
8594 { HP-DeskTop csHPDesktop }
8595 { Ventura-Math csVenturaMath }
8596 { Microsoft-Publishing csMicrosoftPublishing }
8597 { Windows-31J csWindows31J }
8598 { GB2312 csGB2312 }
8599 { Big5 csBig5 }
8602 proc tcl_encoding {enc} {
8603 global encoding_aliases
8604 set names [encoding names]
8605 set lcnames [string tolower $names]
8606 set enc [string tolower $enc]
8607 set i [lsearch -exact $lcnames $enc]
8608 if {$i < 0} {
8609 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
8610 if {[regsub {^iso[-_]} $enc iso encx]} {
8611 set i [lsearch -exact $lcnames $encx]
8614 if {$i < 0} {
8615 foreach l $encoding_aliases {
8616 set ll [string tolower $l]
8617 if {[lsearch -exact $ll $enc] < 0} continue
8618 # look through the aliases for one that tcl knows about
8619 foreach e $ll {
8620 set i [lsearch -exact $lcnames $e]
8621 if {$i < 0} {
8622 if {[regsub {^iso[-_]} $e iso ex]} {
8623 set i [lsearch -exact $lcnames $ex]
8626 if {$i >= 0} break
8628 break
8631 if {$i >= 0} {
8632 return [lindex $names $i]
8634 return {}
8637 # First check that Tcl/Tk is recent enough
8638 if {[catch {package require Tk 8.4} err]} {
8639 show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
8640 Gitk requires at least Tcl/Tk 8.4."]
8641 exit 1
8644 # defaults...
8645 set datemode 0
8646 set wrcomcmd "git diff-tree --stdin -p --pretty"
8648 set gitencoding {}
8649 catch {
8650 set gitencoding [exec git config --get i18n.commitencoding]
8652 if {$gitencoding == ""} {
8653 set gitencoding "utf-8"
8655 set tclencoding [tcl_encoding $gitencoding]
8656 if {$tclencoding == {}} {
8657 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
8660 set mainfont {Helvetica 9}
8661 set textfont {Courier 9}
8662 set uifont {Helvetica 9 bold}
8663 set tabstop 8
8664 set findmergefiles 0
8665 set maxgraphpct 50
8666 set maxwidth 16
8667 set revlistorder 0
8668 set fastdate 0
8669 set uparrowlen 5
8670 set downarrowlen 5
8671 set mingaplen 100
8672 set cmitmode "patch"
8673 set wrapcomment "none"
8674 set showneartags 1
8675 set maxrefs 20
8676 set maxlinelen 200
8677 set showlocalchanges 1
8678 set limitdiffs 1
8679 set datetimeformat "%Y-%m-%d %H:%M:%S"
8680 set autoselect 1
8682 set extdifftool "meld"
8684 set colors {green red blue magenta darkgrey brown orange}
8685 set bgcolor white
8686 set fgcolor black
8687 set diffcolors {red "#00a000" blue}
8688 set diffcontext 3
8689 set ignorespace 0
8690 set selectbgcolor gray85
8692 ## For msgcat loading, first locate the installation location.
8693 if { [info exists ::env(GITK_MSGSDIR)] } {
8694 ## Msgsdir was manually set in the environment.
8695 set gitk_msgsdir $::env(GITK_MSGSDIR)
8696 } else {
8697 ## Let's guess the prefix from argv0.
8698 set gitk_prefix [file dirname [file dirname [file normalize $argv0]]]
8699 set gitk_libdir [file join $gitk_prefix share gitk lib]
8700 set gitk_msgsdir [file join $gitk_libdir msgs]
8701 unset gitk_prefix
8704 ## Internationalization (i18n) through msgcat and gettext. See
8705 ## http://www.gnu.org/software/gettext/manual/html_node/Tcl.html
8706 package require msgcat
8707 namespace import ::msgcat::mc
8708 ## And eventually load the actual message catalog
8709 ::msgcat::mcload $gitk_msgsdir
8711 catch {source ~/.gitk}
8713 font create optionfont -family sans-serif -size -12
8715 parsefont mainfont $mainfont
8716 eval font create mainfont [fontflags mainfont]
8717 eval font create mainfontbold [fontflags mainfont 1]
8719 parsefont textfont $textfont
8720 eval font create textfont [fontflags textfont]
8721 eval font create textfontbold [fontflags textfont 1]
8723 parsefont uifont $uifont
8724 eval font create uifont [fontflags uifont]
8726 setoptions
8728 # check that we can find a .git directory somewhere...
8729 if {[catch {set gitdir [gitdir]}]} {
8730 show_error {} . [mc "Cannot find a git repository here."]
8731 exit 1
8733 if {![file isdirectory $gitdir]} {
8734 show_error {} . [mc "Cannot find the git directory \"%s\"." $gitdir]
8735 exit 1
8738 set mergeonly 0
8739 set revtreeargs {}
8740 set cmdline_files {}
8741 set i 0
8742 set revtreeargscmd {}
8743 foreach arg $argv {
8744 switch -glob -- $arg {
8745 "" { }
8746 "-d" { set datemode 1 }
8747 "--merge" {
8748 set mergeonly 1
8749 lappend revtreeargs $arg
8751 "--" {
8752 set cmdline_files [lrange $argv [expr {$i + 1}] end]
8753 break
8755 "--argscmd=*" {
8756 set revtreeargscmd [string range $arg 10 end]
8758 default {
8759 lappend revtreeargs $arg
8762 incr i
8765 if {$i >= [llength $argv] && $revtreeargs ne {}} {
8766 # no -- on command line, but some arguments (other than -d)
8767 if {[catch {
8768 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
8769 set cmdline_files [split $f "\n"]
8770 set n [llength $cmdline_files]
8771 set revtreeargs [lrange $revtreeargs 0 end-$n]
8772 # Unfortunately git rev-parse doesn't produce an error when
8773 # something is both a revision and a filename. To be consistent
8774 # with git log and git rev-list, check revtreeargs for filenames.
8775 foreach arg $revtreeargs {
8776 if {[file exists $arg]} {
8777 show_error {} . [mc "Ambiguous argument '%s': both revision\
8778 and filename" $arg]
8779 exit 1
8782 } err]} {
8783 # unfortunately we get both stdout and stderr in $err,
8784 # so look for "fatal:".
8785 set i [string first "fatal:" $err]
8786 if {$i > 0} {
8787 set err [string range $err [expr {$i + 6}] end]
8789 show_error {} . "[mc "Bad arguments to gitk:"]\n$err"
8790 exit 1
8794 if {$mergeonly} {
8795 # find the list of unmerged files
8796 set mlist {}
8797 set nr_unmerged 0
8798 if {[catch {
8799 set fd [open "| git ls-files -u" r]
8800 } err]} {
8801 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
8802 exit 1
8804 while {[gets $fd line] >= 0} {
8805 set i [string first "\t" $line]
8806 if {$i < 0} continue
8807 set fname [string range $line [expr {$i+1}] end]
8808 if {[lsearch -exact $mlist $fname] >= 0} continue
8809 incr nr_unmerged
8810 if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
8811 lappend mlist $fname
8814 catch {close $fd}
8815 if {$mlist eq {}} {
8816 if {$nr_unmerged == 0} {
8817 show_error {} . [mc "No files selected: --merge specified but\
8818 no files are unmerged."]
8819 } else {
8820 show_error {} . [mc "No files selected: --merge specified but\
8821 no unmerged files are within file limit."]
8823 exit 1
8825 set cmdline_files $mlist
8828 set nullid "0000000000000000000000000000000000000000"
8829 set nullid2 "0000000000000000000000000000000000000001"
8830 set nullfile "/dev/null"
8832 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
8834 set runq {}
8835 set history {}
8836 set historyindex 0
8837 set fh_serial 0
8838 set nhl_names {}
8839 set highlight_paths {}
8840 set findpattern {}
8841 set searchdirn -forwards
8842 set boldrows {}
8843 set boldnamerows {}
8844 set diffelide {0 0}
8845 set markingmatches 0
8846 set linkentercount 0
8847 set need_redisplay 0
8848 set nrows_drawn 0
8849 set firsttabstop 0
8851 set nextviewnum 1
8852 set curview 0
8853 set selectedview 0
8854 set selectedhlview [mc "None"]
8855 set highlight_related [mc "None"]
8856 set highlight_files {}
8857 set viewfiles(0) {}
8858 set viewperm(0) 0
8859 set viewargs(0) {}
8860 set viewargscmd(0) {}
8862 set cmdlineok 0
8863 set stopped 0
8864 set stuffsaved 0
8865 set patchnum 0
8866 set localirow -1
8867 set localfrow -1
8868 set lserial 0
8869 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
8870 setcoords
8871 makewindow
8872 # wait for the window to become visible
8873 tkwait visibility .
8874 wm title . "[file tail $argv0]: [file tail [pwd]]"
8875 readrefs
8877 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
8878 # create a view for the files/dirs specified on the command line
8879 set curview 1
8880 set selectedview 1
8881 set nextviewnum 2
8882 set viewname(1) [mc "Command line"]
8883 set viewfiles(1) $cmdline_files
8884 set viewargs(1) $revtreeargs
8885 set viewargscmd(1) $revtreeargscmd
8886 set viewperm(1) 0
8887 addviewmenu 1
8888 .bar.view entryconf [mc "Edit view..."] -state normal
8889 .bar.view entryconf [mc "Delete view"] -state normal
8892 if {[info exists permviews]} {
8893 foreach v $permviews {
8894 set n $nextviewnum
8895 incr nextviewnum
8896 set viewname($n) [lindex $v 0]
8897 set viewfiles($n) [lindex $v 1]
8898 set viewargs($n) [lindex $v 2]
8899 set viewargscmd($n) [lindex $v 3]
8900 set viewperm($n) 1
8901 addviewmenu $n
8904 getcommits