2 # Tcl ignores the next line -*- tcl -*- \
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.
12 if {[info exists env(GIT_DIR)]} {
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.
28 if {[info exists isonrunq($script)]} return
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} {
43 fileevent $fd readable {}
47 lappend runq [list $fd $script]
53 set tstart [clock clicks -milliseconds]
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]
68 fileevent $fd readable [list filereadable $fd $script]
70 } elseif {$fd eq {}} {
71 unset isonrunq($script)
74 if {$t1 - $tstart >= 80} break
81 # Start off a git rev-list process and arrange to read its output
82 proc start_rev_list {view} {
84 global commfd leftover tclencoding datemode
85 global viewargs viewfiles commitidx
86 global lookingforhead showlocalchanges
88 set startmsecs [clock clicks -milliseconds]
89 set commitidx($view) 0
90 set order "--topo-order"
92 set order "--date-order"
95 set fd [open [concat | git log -z --pretty=raw $order --parents \
96 --boundary $viewargs($view) "--" $viewfiles($view)] r]
98 error_popup "Error executing git rev-list: $err"
101 set commfd($view) $fd
102 set leftover($view) {}
103 set lookingforhead $showlocalchanges
104 fconfigure $fd -blocking 0 -translation lf -eofchar {}
105 if {$tclencoding != {}} {
106 fconfigure $fd -encoding $tclencoding
108 filerun $fd [list getcommitlines $fd $view]
112 proc stop_rev_list {} {
113 global commfd curview
115 if {![info exists commfd($curview)]} return
116 set fd $commfd($curview)
122 unset commfd($curview)
126 global phase canv mainfont curview
130 start_rev_list $curview
131 show_status "Reading commits..."
134 proc getcommitlines {fd view} {
136 global leftover commfd
137 global displayorder commitidx commitrow commitdata
138 global parentlist children curview hlview
139 global vparentlist vdisporder vcmitlisted
141 set stuff [read $fd 500000]
142 # git log doesn't terminate the last commit with a null...
143 if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
153 # set it blocking so we wait for the process to terminate
154 fconfigure $fd -blocking 1
155 if {[catch {close $fd} err]} {
157 if {$view != $curview} {
158 set fv " for the \"$viewname($view)\" view"
160 if {[string range $err 0 4] == "usage"} {
161 set err "Gitk: error reading commits$fv:\
162 bad arguments to git rev-list."
163 if {$viewname($view) eq "Command line"} {
165 " (Note: arguments to gitk are passed to git rev-list\
166 to allow selection of commits to be displayed.)"
169 set err "Error reading commits$fv: $err"
173 if {$view == $curview} {
174 run chewcommits $view
181 set i [string first "\0" $stuff $start]
183 append leftover($view) [string range $stuff $start end]
187 set cmit $leftover($view)
188 append cmit [string range $stuff 0 [expr {$i - 1}]]
189 set leftover($view) {}
191 set cmit [string range $stuff $start [expr {$i - 1}]]
193 set start [expr {$i + 1}]
194 set j [string first "\n" $cmit]
197 if {$j >= 0 && [string match "commit *" $cmit]} {
198 set ids [string range $cmit 7 [expr {$j - 1}]]
199 if {[string match {[-<>]*} $ids]} {
200 switch -- [string index $ids 0] {
205 set ids [string range $ids 1 end]
209 if {[string length $id] != 40} {
217 if {[string length $shortcmit] > 80} {
218 set shortcmit "[string range $shortcmit 0 80]..."
220 error_popup "Can't parse git log output: {$shortcmit}"
223 set id [lindex $ids 0]
225 set olds [lrange $ids 1 end]
228 if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
229 lappend children($view,$p) $id
236 if {![info exists children($view,$id)]} {
237 set children($view,$id) {}
239 set commitdata($id) [string range $cmit [expr {$j + 1}] end]
240 set commitrow($view,$id) $commitidx($view)
241 incr commitidx($view)
242 if {$view == $curview} {
243 lappend parentlist $olds
244 lappend displayorder $id
245 lappend commitlisted $listed
247 lappend vparentlist($view) $olds
248 lappend vdisporder($view) $id
249 lappend vcmitlisted($view) $listed
254 run chewcommits $view
259 proc chewcommits {view} {
260 global curview hlview commfd
261 global selectedline pending_select
264 if {$view == $curview} {
265 set allread [expr {![info exists commfd($view)]}]
266 set tlimit [expr {[clock clicks -milliseconds] + 50}]
267 set more [layoutmore $tlimit $allread]
268 if {$allread && !$more} {
269 global displayorder commitidx phase
270 global numcommits startmsecs
272 if {[info exists pending_select]} {
273 set row [first_real_row]
276 if {$commitidx($curview) > 0} {
277 #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
278 #puts "overall $ms ms for $numcommits commits"
280 show_status "No commits selected"
286 if {[info exists hlview] && $view == $hlview} {
292 proc readcommit {id} {
293 if {[catch {set contents [exec git cat-file commit $id]}]} return
294 parsecommit $id $contents 0
297 proc updatecommits {} {
298 global viewdata curview phase displayorder
299 global children commitrow selectedline thickerline
306 foreach id $displayorder {
307 catch {unset children($n,$id)}
308 catch {unset commitrow($n,$id)}
311 catch {unset selectedline}
312 catch {unset thickerline}
313 catch {unset viewdata($n)}
320 proc parsecommit {id contents listed} {
321 global commitinfo cdate
330 set hdrend [string first "\n\n" $contents]
332 # should never happen...
333 set hdrend [string length $contents]
335 set header [string range $contents 0 [expr {$hdrend - 1}]]
336 set comment [string range $contents [expr {$hdrend + 2}] end]
337 foreach line [split $header "\n"] {
338 set tag [lindex $line 0]
339 if {$tag == "author"} {
340 set audate [lindex $line end-1]
341 set auname [lrange $line 1 end-2]
342 } elseif {$tag == "committer"} {
343 set comdate [lindex $line end-1]
344 set comname [lrange $line 1 end-2]
348 # take the first non-blank line of the comment as the headline
349 set headline [string trimleft $comment]
350 set i [string first "\n" $headline]
352 set headline [string range $headline 0 $i]
354 set headline [string trimright $headline]
355 set i [string first "\r" $headline]
357 set headline [string trimright [string range $headline 0 $i]]
360 # git rev-list indents the comment by 4 spaces;
361 # if we got this via git cat-file, add the indentation
363 foreach line [split $comment "\n"] {
364 append newcomment " "
365 append newcomment $line
366 append newcomment "\n"
368 set comment $newcomment
370 if {$comdate != {}} {
371 set cdate($id) $comdate
373 set commitinfo($id) [list $headline $auname $audate \
374 $comname $comdate $comment]
377 proc getcommit {id} {
378 global commitdata commitinfo
380 if {[info exists commitdata($id)]} {
381 parsecommit $id $commitdata($id) 1
384 if {![info exists commitinfo($id)]} {
385 set commitinfo($id) {"No commit information available"}
392 global tagids idtags headids idheads tagobjid
393 global otherrefids idotherrefs mainhead mainheadid
395 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
398 set refd [open [list | git show-ref -d] r]
399 while {[gets $refd line] >= 0} {
400 if {[string index $line 40] ne " "} continue
401 set id [string range $line 0 39]
402 set ref [string range $line 41 end]
403 if {![string match "refs/*" $ref]} continue
404 set name [string range $ref 5 end]
405 if {[string match "remotes/*" $name]} {
406 if {![string match "*/HEAD" $name]} {
407 set headids($name) $id
408 lappend idheads($id) $name
410 } elseif {[string match "heads/*" $name]} {
411 set name [string range $name 6 end]
412 set headids($name) $id
413 lappend idheads($id) $name
414 } elseif {[string match "tags/*" $name]} {
415 # this lets refs/tags/foo^{} overwrite refs/tags/foo,
416 # which is what we want since the former is the commit ID
417 set name [string range $name 5 end]
418 if {[string match "*^{}" $name]} {
419 set name [string range $name 0 end-3]
421 set tagobjid($name) $id
423 set tagids($name) $id
424 lappend idtags($id) $name
426 set otherrefids($name) $id
427 lappend idotherrefs($id) $name
434 set thehead [exec git symbolic-ref HEAD]
435 if {[string match "refs/heads/*" $thehead]} {
436 set mainhead [string range $thehead 11 end]
437 if {[info exists headids($mainhead)]} {
438 set mainheadid $headids($mainhead)
444 # skip over fake commits
445 proc first_real_row {} {
446 global nullid nullid2 displayorder numcommits
448 for {set row 0} {$row < $numcommits} {incr row} {
449 set id [lindex $displayorder $row]
450 if {$id ne $nullid && $id ne $nullid2} {
457 # update things for a head moved to a child of its previous location
458 proc movehead {id name} {
459 global headids idheads
461 removehead $headids($name) $name
462 set headids($name) $id
463 lappend idheads($id) $name
466 # update things when a head has been removed
467 proc removehead {id name} {
468 global headids idheads
470 if {$idheads($id) eq $name} {
473 set i [lsearch -exact $idheads($id) $name]
475 set idheads($id) [lreplace $idheads($id) $i $i]
481 proc show_error {w top msg} {
482 message $w.m -text $msg -justify center -aspect 400
483 pack $w.m -side top -fill x -padx 20 -pady 20
484 button $w.ok -text OK -command "destroy $top"
485 pack $w.ok -side bottom -fill x
486 bind $top <Visibility> "grab $top; focus $top"
487 bind $top <Key-Return> "destroy $top"
491 proc error_popup msg {
495 show_error $w $w $msg
498 proc confirm_popup msg {
504 message $w.m -text $msg -justify center -aspect 400
505 pack $w.m -side top -fill x -padx 20 -pady 20
506 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
507 pack $w.ok -side left -fill x
508 button $w.cancel -text Cancel -command "destroy $w"
509 pack $w.cancel -side right -fill x
510 bind $w <Visibility> "grab $w; focus $w"
516 global canv canv2 canv3 linespc charspc ctext cflist
517 global textfont mainfont uifont tabstop
518 global findtype findtypemenu findloc findstring fstring geometry
519 global entries sha1entry sha1string sha1but
520 global maincursor textcursor curtextcursor
521 global rowctxmenu fakerowmenu mergemax wrapcomment
522 global highlight_files gdttype
523 global searchstring sstring
524 global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
528 .bar add cascade -label "File" -menu .bar.file
529 .bar configure -font $uifont
531 .bar.file add command -label "Update" -command updatecommits
532 .bar.file add command -label "Reread references" -command rereadrefs
533 .bar.file add command -label "Quit" -command doquit
534 .bar.file configure -font $uifont
536 .bar add cascade -label "Edit" -menu .bar.edit
537 .bar.edit add command -label "Preferences" -command doprefs
538 .bar.edit configure -font $uifont
540 menu .bar.view -font $uifont
541 .bar add cascade -label "View" -menu .bar.view
542 .bar.view add command -label "New view..." -command {newview 0}
543 .bar.view add command -label "Edit view..." -command editview \
545 .bar.view add command -label "Delete view" -command delview -state disabled
546 .bar.view add separator
547 .bar.view add radiobutton -label "All files" -command {showview 0} \
548 -variable selectedview -value 0
551 .bar add cascade -label "Help" -menu .bar.help
552 .bar.help add command -label "About gitk" -command about
553 .bar.help add command -label "Key bindings" -command keys
554 .bar.help configure -font $uifont
555 . configure -menu .bar
557 # the gui has upper and lower half, parts of a paned window.
558 panedwindow .ctop -orient vertical
560 # possibly use assumed geometry
561 if {![info exists geometry(pwsash0)]} {
562 set geometry(topheight) [expr {15 * $linespc}]
563 set geometry(topwidth) [expr {80 * $charspc}]
564 set geometry(botheight) [expr {15 * $linespc}]
565 set geometry(botwidth) [expr {50 * $charspc}]
566 set geometry(pwsash0) "[expr {40 * $charspc}] 2"
567 set geometry(pwsash1) "[expr {60 * $charspc}] 2"
570 # the upper half will have a paned window, a scroll bar to the right, and some stuff below
571 frame .tf -height $geometry(topheight) -width $geometry(topwidth)
573 panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
575 # create three canvases
576 set cscroll .tf.histframe.csb
577 set canv .tf.histframe.pwclist.canv
579 -selectbackground $selectbgcolor \
580 -background $bgcolor -bd 0 \
581 -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
582 .tf.histframe.pwclist add $canv
583 set canv2 .tf.histframe.pwclist.canv2
585 -selectbackground $selectbgcolor \
586 -background $bgcolor -bd 0 -yscrollincr $linespc
587 .tf.histframe.pwclist add $canv2
588 set canv3 .tf.histframe.pwclist.canv3
590 -selectbackground $selectbgcolor \
591 -background $bgcolor -bd 0 -yscrollincr $linespc
592 .tf.histframe.pwclist add $canv3
593 eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
594 eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
596 # a scroll bar to rule them
597 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
598 pack $cscroll -side right -fill y
599 bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
600 lappend bglist $canv $canv2 $canv3
601 pack .tf.histframe.pwclist -fill both -expand 1 -side left
603 # we have two button bars at bottom of top frame. Bar 1
605 frame .tf.lbar -height 15
607 set sha1entry .tf.bar.sha1
608 set entries $sha1entry
609 set sha1but .tf.bar.sha1label
610 button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
611 -command gotocommit -width 8 -font $uifont
612 $sha1but conf -disabledforeground [$sha1but cget -foreground]
613 pack .tf.bar.sha1label -side left
614 entry $sha1entry -width 40 -font $textfont -textvariable sha1string
615 trace add variable sha1string write sha1change
616 pack $sha1entry -side left -pady 2
618 image create bitmap bm-left -data {
619 #define left_width 16
620 #define left_height 16
621 static unsigned char left_bits[] = {
622 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
623 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
624 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
626 image create bitmap bm-right -data {
627 #define right_width 16
628 #define right_height 16
629 static unsigned char right_bits[] = {
630 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
631 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
632 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
634 button .tf.bar.leftbut -image bm-left -command goback \
635 -state disabled -width 26
636 pack .tf.bar.leftbut -side left -fill y
637 button .tf.bar.rightbut -image bm-right -command goforw \
638 -state disabled -width 26
639 pack .tf.bar.rightbut -side left -fill y
641 button .tf.bar.findbut -text "Find" -command dofind -font $uifont
642 pack .tf.bar.findbut -side left
644 set fstring .tf.bar.findstring
645 lappend entries $fstring
646 entry $fstring -width 30 -font $textfont -textvariable findstring
647 trace add variable findstring write find_change
648 pack $fstring -side left -expand 1 -fill x -in .tf.bar
650 set findtypemenu [tk_optionMenu .tf.bar.findtype \
651 findtype Exact IgnCase Regexp]
652 trace add variable findtype write find_change
653 .tf.bar.findtype configure -font $uifont
654 .tf.bar.findtype.menu configure -font $uifont
655 set findloc "All fields"
656 tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
657 Comments Author Committer
658 trace add variable findloc write find_change
659 .tf.bar.findloc configure -font $uifont
660 .tf.bar.findloc.menu configure -font $uifont
661 pack .tf.bar.findloc -side right
662 pack .tf.bar.findtype -side right
664 # build up the bottom bar of upper window
665 label .tf.lbar.flabel -text "Highlight: Commits " \
667 pack .tf.lbar.flabel -side left -fill y
668 set gdttype "touching paths:"
669 set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
670 "adding/removing string:"]
671 trace add variable gdttype write hfiles_change
672 $gm conf -font $uifont
673 .tf.lbar.gdttype conf -font $uifont
674 pack .tf.lbar.gdttype -side left -fill y
675 entry .tf.lbar.fent -width 25 -font $textfont \
676 -textvariable highlight_files
677 trace add variable highlight_files write hfiles_change
678 lappend entries .tf.lbar.fent
679 pack .tf.lbar.fent -side left -fill x -expand 1
680 label .tf.lbar.vlabel -text " OR in view" -font $uifont
681 pack .tf.lbar.vlabel -side left -fill y
682 global viewhlmenu selectedhlview
683 set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
684 $viewhlmenu entryconf None -command delvhighlight
685 $viewhlmenu conf -font $uifont
686 .tf.lbar.vhl conf -font $uifont
687 pack .tf.lbar.vhl -side left -fill y
688 label .tf.lbar.rlabel -text " OR " -font $uifont
689 pack .tf.lbar.rlabel -side left -fill y
690 global highlight_related
691 set m [tk_optionMenu .tf.lbar.relm highlight_related None \
692 "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
693 $m conf -font $uifont
694 .tf.lbar.relm conf -font $uifont
695 trace add variable highlight_related write vrel_change
696 pack .tf.lbar.relm -side left -fill y
698 # Finish putting the upper half of the viewer together
699 pack .tf.lbar -in .tf -side bottom -fill x
700 pack .tf.bar -in .tf -side bottom -fill x
701 pack .tf.histframe -fill both -side top -expand 1
703 .ctop paneconfigure .tf -height $geometry(topheight)
704 .ctop paneconfigure .tf -width $geometry(topwidth)
706 # now build up the bottom
707 panedwindow .pwbottom -orient horizontal
709 # lower left, a text box over search bar, scroll bar to the right
710 # if we know window height, then that will set the lower text height, otherwise
711 # we set lower text height which will drive window height
712 if {[info exists geometry(main)]} {
713 frame .bleft -width $geometry(botwidth)
715 frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
720 button .bleft.top.search -text "Search" -command dosearch \
722 pack .bleft.top.search -side left -padx 5
723 set sstring .bleft.top.sstring
724 entry $sstring -width 20 -font $textfont -textvariable searchstring
725 lappend entries $sstring
726 trace add variable searchstring write incrsearch
727 pack $sstring -side left -expand 1 -fill x
728 radiobutton .bleft.mid.diff -text "Diff" \
729 -command changediffdisp -variable diffelide -value {0 0}
730 radiobutton .bleft.mid.old -text "Old version" \
731 -command changediffdisp -variable diffelide -value {0 1}
732 radiobutton .bleft.mid.new -text "New version" \
733 -command changediffdisp -variable diffelide -value {1 0}
734 pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
735 set ctext .bleft.ctext
736 text $ctext -background $bgcolor -foreground $fgcolor \
737 -tabs "[expr {$tabstop * $charspc}]" \
738 -state disabled -font $textfont \
739 -yscrollcommand scrolltext -wrap none
740 scrollbar .bleft.sb -command "$ctext yview"
741 pack .bleft.top -side top -fill x
742 pack .bleft.mid -side top -fill x
743 pack .bleft.sb -side right -fill y
744 pack $ctext -side left -fill both -expand 1
745 lappend bglist $ctext
746 lappend fglist $ctext
748 $ctext tag conf comment -wrap $wrapcomment
749 $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
750 $ctext tag conf hunksep -fore [lindex $diffcolors 2]
751 $ctext tag conf d0 -fore [lindex $diffcolors 0]
752 $ctext tag conf d1 -fore [lindex $diffcolors 1]
753 $ctext tag conf m0 -fore red
754 $ctext tag conf m1 -fore blue
755 $ctext tag conf m2 -fore green
756 $ctext tag conf m3 -fore purple
757 $ctext tag conf m4 -fore brown
758 $ctext tag conf m5 -fore "#009090"
759 $ctext tag conf m6 -fore magenta
760 $ctext tag conf m7 -fore "#808000"
761 $ctext tag conf m8 -fore "#009000"
762 $ctext tag conf m9 -fore "#ff0080"
763 $ctext tag conf m10 -fore cyan
764 $ctext tag conf m11 -fore "#b07070"
765 $ctext tag conf m12 -fore "#70b0f0"
766 $ctext tag conf m13 -fore "#70f0b0"
767 $ctext tag conf m14 -fore "#f0b070"
768 $ctext tag conf m15 -fore "#ff70b0"
769 $ctext tag conf mmax -fore darkgrey
771 $ctext tag conf mresult -font [concat $textfont bold]
772 $ctext tag conf msep -font [concat $textfont bold]
773 $ctext tag conf found -back yellow
776 .pwbottom paneconfigure .bleft -width $geometry(botwidth)
781 radiobutton .bright.mode.patch -text "Patch" \
782 -command reselectline -variable cmitmode -value "patch"
783 .bright.mode.patch configure -font $uifont
784 radiobutton .bright.mode.tree -text "Tree" \
785 -command reselectline -variable cmitmode -value "tree"
786 .bright.mode.tree configure -font $uifont
787 grid .bright.mode.patch .bright.mode.tree -sticky ew
788 pack .bright.mode -side top -fill x
789 set cflist .bright.cfiles
790 set indent [font measure $mainfont "nn"]
792 -selectbackground $selectbgcolor \
793 -background $bgcolor -foreground $fgcolor \
795 -tabs [list $indent [expr {2 * $indent}]] \
796 -yscrollcommand ".bright.sb set" \
797 -cursor [. cget -cursor] \
798 -spacing1 1 -spacing3 1
799 lappend bglist $cflist
800 lappend fglist $cflist
801 scrollbar .bright.sb -command "$cflist yview"
802 pack .bright.sb -side right -fill y
803 pack $cflist -side left -fill both -expand 1
804 $cflist tag configure highlight \
805 -background [$cflist cget -selectbackground]
806 $cflist tag configure bold -font [concat $mainfont bold]
808 .pwbottom add .bright
811 # restore window position if known
812 if {[info exists geometry(main)]} {
813 wm geometry . "$geometry(main)"
816 if {[tk windowingsystem] eq {aqua}} {
822 bind .pwbottom <Configure> {resizecdetpanes %W %w}
823 pack .ctop -fill both -expand 1
824 bindall <1> {selcanvline %W %x %y}
825 #bindall <B1-Motion> {selcanvline %W %x %y}
826 bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
827 bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
828 bindall <2> "canvscan mark %W %x %y"
829 bindall <B2-Motion> "canvscan dragto %W %x %y"
830 bindkey <Home> selfirstline
831 bindkey <End> sellastline
832 bind . <Key-Up> "selnextline -1"
833 bind . <Key-Down> "selnextline 1"
834 bind . <Shift-Key-Up> "next_highlight -1"
835 bind . <Shift-Key-Down> "next_highlight 1"
836 bindkey <Key-Right> "goforw"
837 bindkey <Key-Left> "goback"
838 bind . <Key-Prior> "selnextpage -1"
839 bind . <Key-Next> "selnextpage 1"
840 bind . <$M1B-Home> "allcanvs yview moveto 0.0"
841 bind . <$M1B-End> "allcanvs yview moveto 1.0"
842 bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
843 bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
844 bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
845 bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
846 bindkey <Key-Delete> "$ctext yview scroll -1 pages"
847 bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
848 bindkey <Key-space> "$ctext yview scroll 1 pages"
849 bindkey p "selnextline -1"
850 bindkey n "selnextline 1"
853 bindkey i "selnextline -1"
854 bindkey k "selnextline 1"
857 bindkey b "$ctext yview scroll -1 pages"
858 bindkey d "$ctext yview scroll 18 units"
859 bindkey u "$ctext yview scroll -18 units"
860 bindkey / {findnext 1}
861 bindkey <Key-Return> {findnext 0}
864 bindkey <F5> updatecommits
865 bind . <$M1B-q> doquit
866 bind . <$M1B-f> dofind
867 bind . <$M1B-g> {findnext 0}
868 bind . <$M1B-r> dosearchback
869 bind . <$M1B-s> dosearch
870 bind . <$M1B-equal> {incrfont 1}
871 bind . <$M1B-KP_Add> {incrfont 1}
872 bind . <$M1B-minus> {incrfont -1}
873 bind . <$M1B-KP_Subtract> {incrfont -1}
874 wm protocol . WM_DELETE_WINDOW doquit
875 bind . <Button-1> "click %W"
876 bind $fstring <Key-Return> dofind
877 bind $sha1entry <Key-Return> gotocommit
878 bind $sha1entry <<PasteSelection>> clearsha1
879 bind $cflist <1> {sel_flist %W %x %y; break}
880 bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
881 bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
882 bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
884 set maincursor [. cget -cursor]
885 set textcursor [$ctext cget -cursor]
886 set curtextcursor $textcursor
888 set rowctxmenu .rowctxmenu
889 menu $rowctxmenu -tearoff 0
890 $rowctxmenu add command -label "Diff this -> selected" \
891 -command {diffvssel 0}
892 $rowctxmenu add command -label "Diff selected -> this" \
893 -command {diffvssel 1}
894 $rowctxmenu add command -label "Make patch" -command mkpatch
895 $rowctxmenu add command -label "Create tag" -command mktag
896 $rowctxmenu add command -label "Write commit to file" -command writecommit
897 $rowctxmenu add command -label "Create new branch" -command mkbranch
898 $rowctxmenu add command -label "Cherry-pick this commit" \
900 $rowctxmenu add command -label "Reset HEAD branch to here" \
903 set fakerowmenu .fakerowmenu
904 menu $fakerowmenu -tearoff 0
905 $fakerowmenu add command -label "Diff this -> selected" \
906 -command {diffvssel 0}
907 $fakerowmenu add command -label "Diff selected -> this" \
908 -command {diffvssel 1}
909 $fakerowmenu add command -label "Make patch" -command mkpatch
910 # $fakerowmenu add command -label "Commit" -command {mkcommit 0}
911 # $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
912 # $fakerowmenu add command -label "Revert local changes" -command revertlocal
914 set headctxmenu .headctxmenu
915 menu $headctxmenu -tearoff 0
916 $headctxmenu add command -label "Check out this branch" \
918 $headctxmenu add command -label "Remove this branch" \
922 set flist_menu .flistctxmenu
923 menu $flist_menu -tearoff 0
924 $flist_menu add command -label "Highlight this too" \
925 -command {flist_hl 0}
926 $flist_menu add command -label "Highlight this only" \
927 -command {flist_hl 1}
930 # mouse-2 makes all windows scan vertically, but only the one
931 # the cursor is in scans horizontally
932 proc canvscan {op w x y} {
933 global canv canv2 canv3
934 foreach c [list $canv $canv2 $canv3] {
943 proc scrollcanv {cscroll f0 f1} {
949 # when we make a key binding for the toplevel, make sure
950 # it doesn't get triggered when that key is pressed in the
951 # find string entry widget.
952 proc bindkey {ev script} {
955 set escript [bind Entry $ev]
956 if {$escript == {}} {
957 set escript [bind Entry <Key>]
960 bind $e $ev "$escript; break"
964 # set the focus back to the toplevel for any click outside
975 global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
976 global stuffsaved findmergefiles maxgraphpct
977 global maxwidth showneartags showlocalchanges
978 global viewname viewfiles viewargs viewperm nextviewnum
979 global cmitmode wrapcomment
980 global colors bgcolor fgcolor diffcolors selectbgcolor
982 if {$stuffsaved} return
983 if {![winfo viewable .]} return
985 set f [open "~/.gitk-new" w]
986 puts $f [list set mainfont $mainfont]
987 puts $f [list set textfont $textfont]
988 puts $f [list set uifont $uifont]
989 puts $f [list set tabstop $tabstop]
990 puts $f [list set findmergefiles $findmergefiles]
991 puts $f [list set maxgraphpct $maxgraphpct]
992 puts $f [list set maxwidth $maxwidth]
993 puts $f [list set cmitmode $cmitmode]
994 puts $f [list set wrapcomment $wrapcomment]
995 puts $f [list set showneartags $showneartags]
996 puts $f [list set showlocalchanges $showlocalchanges]
997 puts $f [list set bgcolor $bgcolor]
998 puts $f [list set fgcolor $fgcolor]
999 puts $f [list set colors $colors]
1000 puts $f [list set diffcolors $diffcolors]
1001 puts $f [list set selectbgcolor $selectbgcolor]
1003 puts $f "set geometry(main) [wm geometry .]"
1004 puts $f "set geometry(topwidth) [winfo width .tf]"
1005 puts $f "set geometry(topheight) [winfo height .tf]"
1006 puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
1007 puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
1008 puts $f "set geometry(botwidth) [winfo width .bleft]"
1009 puts $f "set geometry(botheight) [winfo height .bleft]"
1011 puts -nonewline $f "set permviews {"
1012 for {set v 0} {$v < $nextviewnum} {incr v} {
1013 if {$viewperm($v)} {
1014 puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
1019 file rename -force "~/.gitk-new" "~/.gitk"
1024 proc resizeclistpanes {win w} {
1026 if {[info exists oldwidth($win)]} {
1027 set s0 [$win sash coord 0]
1028 set s1 [$win sash coord 1]
1030 set sash0 [expr {int($w/2 - 2)}]
1031 set sash1 [expr {int($w*5/6 - 2)}]
1033 set factor [expr {1.0 * $w / $oldwidth($win)}]
1034 set sash0 [expr {int($factor * [lindex $s0 0])}]
1035 set sash1 [expr {int($factor * [lindex $s1 0])}]
1039 if {$sash1 < $sash0 + 20} {
1040 set sash1 [expr {$sash0 + 20}]
1042 if {$sash1 > $w - 10} {
1043 set sash1 [expr {$w - 10}]
1044 if {$sash0 > $sash1 - 20} {
1045 set sash0 [expr {$sash1 - 20}]
1049 $win sash place 0 $sash0 [lindex $s0 1]
1050 $win sash place 1 $sash1 [lindex $s1 1]
1052 set oldwidth($win) $w
1055 proc resizecdetpanes {win w} {
1057 if {[info exists oldwidth($win)]} {
1058 set s0 [$win sash coord 0]
1060 set sash0 [expr {int($w*3/4 - 2)}]
1062 set factor [expr {1.0 * $w / $oldwidth($win)}]
1063 set sash0 [expr {int($factor * [lindex $s0 0])}]
1067 if {$sash0 > $w - 15} {
1068 set sash0 [expr {$w - 15}]
1071 $win sash place 0 $sash0 [lindex $s0 1]
1073 set oldwidth($win) $w
1076 proc allcanvs args {
1077 global canv canv2 canv3
1083 proc bindall {event action} {
1084 global canv canv2 canv3
1085 bind $canv $event $action
1086 bind $canv2 $event $action
1087 bind $canv3 $event $action
1093 if {[winfo exists $w]} {
1098 wm title $w "About gitk"
1099 message $w.m -text {
1100 Gitk - a commit viewer for git
1102 Copyright © 2005-2006 Paul Mackerras
1104 Use and redistribute under the terms of the GNU General Public License} \
1105 -justify center -aspect 400 -border 2 -bg white -relief groove
1106 pack $w.m -side top -fill x -padx 2 -pady 2
1107 $w.m configure -font $uifont
1108 button $w.ok -text Close -command "destroy $w" -default active
1109 pack $w.ok -side bottom
1110 $w.ok configure -font $uifont
1111 bind $w <Visibility> "focus $w.ok"
1112 bind $w <Key-Escape> "destroy $w"
1113 bind $w <Key-Return> "destroy $w"
1119 if {[winfo exists $w]} {
1123 if {[tk windowingsystem] eq {aqua}} {
1129 wm title $w "Gitk key bindings"
1130 message $w.m -text "
1134 <Home> Move to first commit
1135 <End> Move to last commit
1136 <Up>, p, i Move up one commit
1137 <Down>, n, k Move down one commit
1138 <Left>, z, j Go back in history list
1139 <Right>, x, l Go forward in history list
1140 <PageUp> Move up one page in commit list
1141 <PageDown> Move down one page in commit list
1142 <$M1T-Home> Scroll to top of commit list
1143 <$M1T-End> Scroll to bottom of commit list
1144 <$M1T-Up> Scroll commit list up one line
1145 <$M1T-Down> Scroll commit list down one line
1146 <$M1T-PageUp> Scroll commit list up one page
1147 <$M1T-PageDown> Scroll commit list down one page
1148 <Shift-Up> Move to previous highlighted line
1149 <Shift-Down> Move to next highlighted line
1150 <Delete>, b Scroll diff view up one page
1151 <Backspace> Scroll diff view up one page
1152 <Space> Scroll diff view down one page
1153 u Scroll diff view up 18 lines
1154 d Scroll diff view down 18 lines
1156 <$M1T-G> Move to next find hit
1157 <Return> Move to next find hit
1158 / Move to next find hit, or redo find
1159 ? Move to previous find hit
1160 f Scroll diff view to next file
1161 <$M1T-S> Search for next hit in diff view
1162 <$M1T-R> Search for previous hit in diff view
1163 <$M1T-KP+> Increase font size
1164 <$M1T-plus> Increase font size
1165 <$M1T-KP-> Decrease font size
1166 <$M1T-minus> Decrease font size
1169 -justify left -bg white -border 2 -relief groove
1170 pack $w.m -side top -fill both -padx 2 -pady 2
1171 $w.m configure -font $uifont
1172 button $w.ok -text Close -command "destroy $w" -default active
1173 pack $w.ok -side bottom
1174 $w.ok configure -font $uifont
1175 bind $w <Visibility> "focus $w.ok"
1176 bind $w <Key-Escape> "destroy $w"
1177 bind $w <Key-Return> "destroy $w"
1180 # Procedures for manipulating the file list window at the
1181 # bottom right of the overall window.
1183 proc treeview {w l openlevs} {
1184 global treecontents treediropen treeheight treeparent treeindex
1194 set treecontents() {}
1195 $w conf -state normal
1197 while {[string range $f 0 $prefixend] ne $prefix} {
1198 if {$lev <= $openlevs} {
1199 $w mark set e:$treeindex($prefix) "end -1c"
1200 $w mark gravity e:$treeindex($prefix) left
1202 set treeheight($prefix) $ht
1203 incr ht [lindex $htstack end]
1204 set htstack [lreplace $htstack end end]
1205 set prefixend [lindex $prefendstack end]
1206 set prefendstack [lreplace $prefendstack end end]
1207 set prefix [string range $prefix 0 $prefixend]
1210 set tail [string range $f [expr {$prefixend+1}] end]
1211 while {[set slash [string first "/" $tail]] >= 0} {
1214 lappend prefendstack $prefixend
1215 incr prefixend [expr {$slash + 1}]
1216 set d [string range $tail 0 $slash]
1217 lappend treecontents($prefix) $d
1218 set oldprefix $prefix
1220 set treecontents($prefix) {}
1221 set treeindex($prefix) [incr ix]
1222 set treeparent($prefix) $oldprefix
1223 set tail [string range $tail [expr {$slash+1}] end]
1224 if {$lev <= $openlevs} {
1226 set treediropen($prefix) [expr {$lev < $openlevs}]
1227 set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
1228 $w mark set d:$ix "end -1c"
1229 $w mark gravity d:$ix left
1231 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1233 $w image create end -align center -image $bm -padx 1 \
1235 $w insert end $d [highlight_tag $prefix]
1236 $w mark set s:$ix "end -1c"
1237 $w mark gravity s:$ix left
1242 if {$lev <= $openlevs} {
1245 for {set i 0} {$i < $lev} {incr i} {append str "\t"}
1247 $w insert end $tail [highlight_tag $f]
1249 lappend treecontents($prefix) $tail
1252 while {$htstack ne {}} {
1253 set treeheight($prefix) $ht
1254 incr ht [lindex $htstack end]
1255 set htstack [lreplace $htstack end end]
1256 set prefixend [lindex $prefendstack end]
1257 set prefendstack [lreplace $prefendstack end end]
1258 set prefix [string range $prefix 0 $prefixend]
1260 $w conf -state disabled
1263 proc linetoelt {l} {
1264 global treeheight treecontents
1269 foreach e $treecontents($prefix) {
1274 if {[string index $e end] eq "/"} {
1275 set n $treeheight($prefix$e)
1287 proc highlight_tree {y prefix} {
1288 global treeheight treecontents cflist
1290 foreach e $treecontents($prefix) {
1292 if {[highlight_tag $path] ne {}} {
1293 $cflist tag add bold $y.0 "$y.0 lineend"
1296 if {[string index $e end] eq "/" && $treeheight($path) > 1} {
1297 set y [highlight_tree $y $path]
1303 proc treeclosedir {w dir} {
1304 global treediropen treeheight treeparent treeindex
1306 set ix $treeindex($dir)
1307 $w conf -state normal
1308 $w delete s:$ix e:$ix
1309 set treediropen($dir) 0
1310 $w image configure a:$ix -image tri-rt
1311 $w conf -state disabled
1312 set n [expr {1 - $treeheight($dir)}]
1313 while {$dir ne {}} {
1314 incr treeheight($dir) $n
1315 set dir $treeparent($dir)
1319 proc treeopendir {w dir} {
1320 global treediropen treeheight treeparent treecontents treeindex
1322 set ix $treeindex($dir)
1323 $w conf -state normal
1324 $w image configure a:$ix -image tri-dn
1325 $w mark set e:$ix s:$ix
1326 $w mark gravity e:$ix right
1329 set n [llength $treecontents($dir)]
1330 for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
1333 incr treeheight($x) $n
1335 foreach e $treecontents($dir) {
1337 if {[string index $e end] eq "/"} {
1338 set iy $treeindex($de)
1339 $w mark set d:$iy e:$ix
1340 $w mark gravity d:$iy left
1341 $w insert e:$ix $str
1342 set treediropen($de) 0
1343 $w image create e:$ix -align center -image tri-rt -padx 1 \
1345 $w insert e:$ix $e [highlight_tag $de]
1346 $w mark set s:$iy e:$ix
1347 $w mark gravity s:$iy left
1348 set treeheight($de) 1
1350 $w insert e:$ix $str
1351 $w insert e:$ix $e [highlight_tag $de]
1354 $w mark gravity e:$ix left
1355 $w conf -state disabled
1356 set treediropen($dir) 1
1357 set top [lindex [split [$w index @0,0] .] 0]
1358 set ht [$w cget -height]
1359 set l [lindex [split [$w index s:$ix] .] 0]
1362 } elseif {$l + $n + 1 > $top + $ht} {
1363 set top [expr {$l + $n + 2 - $ht}]
1371 proc treeclick {w x y} {
1372 global treediropen cmitmode ctext cflist cflist_top
1374 if {$cmitmode ne "tree"} return
1375 if {![info exists cflist_top]} return
1376 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1377 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1378 $cflist tag add highlight $l.0 "$l.0 lineend"
1384 set e [linetoelt $l]
1385 if {[string index $e end] ne "/"} {
1387 } elseif {$treediropen($e)} {
1394 proc setfilelist {id} {
1395 global treefilelist cflist
1397 treeview $cflist $treefilelist($id) 0
1400 image create bitmap tri-rt -background black -foreground blue -data {
1401 #define tri-rt_width 13
1402 #define tri-rt_height 13
1403 static unsigned char tri-rt_bits[] = {
1404 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
1405 0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
1408 #define tri-rt-mask_width 13
1409 #define tri-rt-mask_height 13
1410 static unsigned char tri-rt-mask_bits[] = {
1411 0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
1412 0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
1415 image create bitmap tri-dn -background black -foreground blue -data {
1416 #define tri-dn_width 13
1417 #define tri-dn_height 13
1418 static unsigned char tri-dn_bits[] = {
1419 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
1420 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1423 #define tri-dn-mask_width 13
1424 #define tri-dn-mask_height 13
1425 static unsigned char tri-dn-mask_bits[] = {
1426 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
1427 0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
1431 proc init_flist {first} {
1432 global cflist cflist_top selectedline difffilestart
1434 $cflist conf -state normal
1435 $cflist delete 0.0 end
1437 $cflist insert end $first
1439 $cflist tag add highlight 1.0 "1.0 lineend"
1441 catch {unset cflist_top}
1443 $cflist conf -state disabled
1444 set difffilestart {}
1447 proc highlight_tag {f} {
1448 global highlight_paths
1450 foreach p $highlight_paths {
1451 if {[string match $p $f]} {
1458 proc highlight_filelist {} {
1459 global cmitmode cflist
1461 $cflist conf -state normal
1462 if {$cmitmode ne "tree"} {
1463 set end [lindex [split [$cflist index end] .] 0]
1464 for {set l 2} {$l < $end} {incr l} {
1465 set line [$cflist get $l.0 "$l.0 lineend"]
1466 if {[highlight_tag $line] ne {}} {
1467 $cflist tag add bold $l.0 "$l.0 lineend"
1473 $cflist conf -state disabled
1476 proc unhighlight_filelist {} {
1479 $cflist conf -state normal
1480 $cflist tag remove bold 1.0 end
1481 $cflist conf -state disabled
1484 proc add_flist {fl} {
1487 $cflist conf -state normal
1489 $cflist insert end "\n"
1490 $cflist insert end $f [highlight_tag $f]
1492 $cflist conf -state disabled
1495 proc sel_flist {w x y} {
1496 global ctext difffilestart cflist cflist_top cmitmode
1498 if {$cmitmode eq "tree"} return
1499 if {![info exists cflist_top]} return
1500 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1501 $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
1502 $cflist tag add highlight $l.0 "$l.0 lineend"
1507 catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
1511 proc pop_flist_menu {w X Y x y} {
1512 global ctext cflist cmitmode flist_menu flist_menu_file
1513 global treediffs diffids
1515 set l [lindex [split [$w index "@$x,$y"] "."] 0]
1517 if {$cmitmode eq "tree"} {
1518 set e [linetoelt $l]
1519 if {[string index $e end] eq "/"} return
1521 set e [lindex $treediffs($diffids) [expr {$l-2}]]
1523 set flist_menu_file $e
1524 tk_popup $flist_menu $X $Y
1527 proc flist_hl {only} {
1528 global flist_menu_file highlight_files
1530 set x [shellquote $flist_menu_file]
1531 if {$only || $highlight_files eq {}} {
1532 set highlight_files $x
1534 append highlight_files " " $x
1538 # Functions for adding and removing shell-type quoting
1540 proc shellquote {str} {
1541 if {![string match "*\['\"\\ \t]*" $str]} {
1544 if {![string match "*\['\"\\]*" $str]} {
1547 if {![string match "*'*" $str]} {
1550 return "\"[string map {\" \\\" \\ \\\\} $str]\""
1553 proc shellarglist {l} {
1559 append str [shellquote $a]
1564 proc shelldequote {str} {
1569 if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
1570 append ret [string range $str $used end]
1571 set used [string length $str]
1574 set first [lindex $first 0]
1575 set ch [string index $str $first]
1576 if {$first > $used} {
1577 append ret [string range $str $used [expr {$first - 1}]]
1580 if {$ch eq " " || $ch eq "\t"} break
1583 set first [string first "'" $str $used]
1585 error "unmatched single-quote"
1587 append ret [string range $str $used [expr {$first - 1}]]
1592 if {$used >= [string length $str]} {
1593 error "trailing backslash"
1595 append ret [string index $str $used]
1600 if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
1601 error "unmatched double-quote"
1603 set first [lindex $first 0]
1604 set ch [string index $str $first]
1605 if {$first > $used} {
1606 append ret [string range $str $used [expr {$first - 1}]]
1609 if {$ch eq "\""} break
1611 append ret [string index $str $used]
1615 return [list $used $ret]
1618 proc shellsplit {str} {
1621 set str [string trimleft $str]
1622 if {$str eq {}} break
1623 set dq [shelldequote $str]
1624 set n [lindex $dq 0]
1625 set word [lindex $dq 1]
1626 set str [string range $str $n end]
1632 # Code to implement multiple views
1634 proc newview {ishighlight} {
1635 global nextviewnum newviewname newviewperm uifont newishighlight
1636 global newviewargs revtreeargs
1638 set newishighlight $ishighlight
1640 if {[winfo exists $top]} {
1644 set newviewname($nextviewnum) "View $nextviewnum"
1645 set newviewperm($nextviewnum) 0
1646 set newviewargs($nextviewnum) [shellarglist $revtreeargs]
1647 vieweditor $top $nextviewnum "Gitk view definition"
1652 global viewname viewperm newviewname newviewperm
1653 global viewargs newviewargs
1655 set top .gitkvedit-$curview
1656 if {[winfo exists $top]} {
1660 set newviewname($curview) $viewname($curview)
1661 set newviewperm($curview) $viewperm($curview)
1662 set newviewargs($curview) [shellarglist $viewargs($curview)]
1663 vieweditor $top $curview "Gitk: edit view $viewname($curview)"
1666 proc vieweditor {top n title} {
1667 global newviewname newviewperm viewfiles
1671 wm title $top $title
1672 label $top.nl -text "Name" -font $uifont
1673 entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
1674 grid $top.nl $top.name -sticky w -pady 5
1675 checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
1677 grid $top.perm - -pady 5 -sticky w
1678 message $top.al -aspect 1000 -font $uifont \
1679 -text "Commits to include (arguments to git rev-list):"
1680 grid $top.al - -sticky w -pady 5
1681 entry $top.args -width 50 -textvariable newviewargs($n) \
1682 -background white -font $uifont
1683 grid $top.args - -sticky ew -padx 5
1684 message $top.l -aspect 1000 -font $uifont \
1685 -text "Enter files and directories to include, one per line:"
1686 grid $top.l - -sticky w
1687 text $top.t -width 40 -height 10 -background white -font $uifont
1688 if {[info exists viewfiles($n)]} {
1689 foreach f $viewfiles($n) {
1690 $top.t insert end $f
1691 $top.t insert end "\n"
1693 $top.t delete {end - 1c} end
1694 $top.t mark set insert 0.0
1696 grid $top.t - -sticky ew -padx 5
1698 button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
1700 button $top.buts.can -text "Cancel" -command [list destroy $top] \
1702 grid $top.buts.ok $top.buts.can
1703 grid columnconfigure $top.buts 0 -weight 1 -uniform a
1704 grid columnconfigure $top.buts 1 -weight 1 -uniform a
1705 grid $top.buts - -pady 10 -sticky ew
1709 proc doviewmenu {m first cmd op argv} {
1710 set nmenu [$m index end]
1711 for {set i $first} {$i <= $nmenu} {incr i} {
1712 if {[$m entrycget $i -command] eq $cmd} {
1713 eval $m $op $i $argv
1719 proc allviewmenus {n op args} {
1722 doviewmenu .bar.view 5 [list showview $n] $op $args
1723 doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
1726 proc newviewok {top n} {
1727 global nextviewnum newviewperm newviewname newishighlight
1728 global viewname viewfiles viewperm selectedview curview
1729 global viewargs newviewargs viewhlmenu
1732 set newargs [shellsplit $newviewargs($n)]
1734 error_popup "Error in commit selection arguments: $err"
1740 foreach f [split [$top.t get 0.0 end] "\n"] {
1741 set ft [string trim $f]
1746 if {![info exists viewfiles($n)]} {
1747 # creating a new view
1749 set viewname($n) $newviewname($n)
1750 set viewperm($n) $newviewperm($n)
1751 set viewfiles($n) $files
1752 set viewargs($n) $newargs
1754 if {!$newishighlight} {
1757 run addvhighlight $n
1760 # editing an existing view
1761 set viewperm($n) $newviewperm($n)
1762 if {$newviewname($n) ne $viewname($n)} {
1763 set viewname($n) $newviewname($n)
1764 doviewmenu .bar.view 5 [list showview $n] \
1765 entryconf [list -label $viewname($n)]
1766 doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
1767 entryconf [list -label $viewname($n) -value $viewname($n)]
1769 if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
1770 set viewfiles($n) $files
1771 set viewargs($n) $newargs
1772 if {$curview == $n} {
1777 catch {destroy $top}
1781 global curview viewdata viewperm hlview selectedhlview
1783 if {$curview == 0} return
1784 if {[info exists hlview] && $hlview == $curview} {
1785 set selectedhlview None
1788 allviewmenus $curview delete
1789 set viewdata($curview) {}
1790 set viewperm($curview) 0
1794 proc addviewmenu {n} {
1795 global viewname viewhlmenu
1797 .bar.view add radiobutton -label $viewname($n) \
1798 -command [list showview $n] -variable selectedview -value $n
1799 $viewhlmenu add radiobutton -label $viewname($n) \
1800 -command [list addvhighlight $n] -variable selectedhlview
1803 proc flatten {var} {
1807 foreach i [array names $var] {
1808 lappend ret $i [set $var\($i\)]
1813 proc unflatten {var l} {
1823 global curview viewdata viewfiles
1824 global displayorder parentlist rowidlist rowoffsets
1825 global colormap rowtextx commitrow nextcolor canvxmax
1826 global numcommits rowrangelist commitlisted idrowranges rowchk
1827 global selectedline currentid canv canvy0
1829 global pending_select phase
1830 global commitidx rowlaidout rowoptim
1832 global selectedview selectfirst
1833 global vparentlist vdisporder vcmitlisted
1834 global hlview selectedhlview
1836 if {$n == $curview} return
1838 if {[info exists selectedline]} {
1839 set selid $currentid
1840 set y [yc $selectedline]
1841 set ymax [lindex [$canv cget -scrollregion] 3]
1842 set span [$canv yview]
1843 set ytop [expr {[lindex $span 0] * $ymax}]
1844 set ybot [expr {[lindex $span 1] * $ymax}]
1845 if {$ytop < $y && $y < $ybot} {
1846 set yscreen [expr {$y - $ytop}]
1848 set yscreen [expr {($ybot - $ytop) / 2}]
1850 } elseif {[info exists pending_select]} {
1851 set selid $pending_select
1852 unset pending_select
1856 if {$curview >= 0} {
1857 set vparentlist($curview) $parentlist
1858 set vdisporder($curview) $displayorder
1859 set vcmitlisted($curview) $commitlisted
1861 set viewdata($curview) \
1862 [list $phase $rowidlist $rowoffsets $rowrangelist \
1863 [flatten idrowranges] [flatten idinlist] \
1864 $rowlaidout $rowoptim $numcommits]
1865 } elseif {![info exists viewdata($curview)]
1866 || [lindex $viewdata($curview) 0] ne {}} {
1867 set viewdata($curview) \
1868 [list {} $rowidlist $rowoffsets $rowrangelist]
1871 catch {unset treediffs}
1873 if {[info exists hlview] && $hlview == $n} {
1875 set selectedhlview None
1880 .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
1881 .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
1883 if {![info exists viewdata($n)]} {
1885 set pending_select $selid
1892 set phase [lindex $v 0]
1893 set displayorder $vdisporder($n)
1894 set parentlist $vparentlist($n)
1895 set commitlisted $vcmitlisted($n)
1896 set rowidlist [lindex $v 1]
1897 set rowoffsets [lindex $v 2]
1898 set rowrangelist [lindex $v 3]
1900 set numcommits [llength $displayorder]
1901 catch {unset idrowranges}
1903 unflatten idrowranges [lindex $v 4]
1904 unflatten idinlist [lindex $v 5]
1905 set rowlaidout [lindex $v 6]
1906 set rowoptim [lindex $v 7]
1907 set numcommits [lindex $v 8]
1908 catch {unset rowchk}
1911 catch {unset colormap}
1912 catch {unset rowtextx}
1914 set canvxmax [$canv cget -width]
1921 if {$selid ne {} && [info exists commitrow($n,$selid)]} {
1922 set row $commitrow($n,$selid)
1923 # try to get the selected row in the same position on the screen
1924 set ymax [lindex [$canv cget -scrollregion] 3]
1925 set ytop [expr {[yc $row] - $yscreen}]
1929 set yf [expr {$ytop * 1.0 / $ymax}]
1931 allcanvs yview moveto $yf
1935 } elseif {$selid ne {}} {
1936 set pending_select $selid
1938 set row [first_real_row]
1939 if {$row < $numcommits} {
1946 if {$phase eq "getcommits"} {
1947 show_status "Reading commits..."
1950 } elseif {$numcommits == 0} {
1951 show_status "No commits selected"
1955 # Stuff relating to the highlighting facility
1957 proc ishighlighted {row} {
1958 global vhighlights fhighlights nhighlights rhighlights
1960 if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
1961 return $nhighlights($row)
1963 if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
1964 return $vhighlights($row)
1966 if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
1967 return $fhighlights($row)
1969 if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
1970 return $rhighlights($row)
1975 proc bolden {row font} {
1976 global canv linehtag selectedline boldrows
1978 lappend boldrows $row
1979 $canv itemconf $linehtag($row) -font $font
1980 if {[info exists selectedline] && $row == $selectedline} {
1982 set t [eval $canv create rect [$canv bbox $linehtag($row)] \
1983 -outline {{}} -tags secsel \
1984 -fill [$canv cget -selectbackground]]
1989 proc bolden_name {row font} {
1990 global canv2 linentag selectedline boldnamerows
1992 lappend boldnamerows $row
1993 $canv2 itemconf $linentag($row) -font $font
1994 if {[info exists selectedline] && $row == $selectedline} {
1995 $canv2 delete secsel
1996 set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
1997 -outline {{}} -tags secsel \
1998 -fill [$canv2 cget -selectbackground]]
2004 global mainfont boldrows
2007 foreach row $boldrows {
2008 if {![ishighlighted $row]} {
2009 bolden $row $mainfont
2011 lappend stillbold $row
2014 set boldrows $stillbold
2017 proc addvhighlight {n} {
2018 global hlview curview viewdata vhl_done vhighlights commitidx
2020 if {[info exists hlview]} {
2024 if {$n != $curview && ![info exists viewdata($n)]} {
2025 set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
2026 set vparentlist($n) {}
2027 set vdisporder($n) {}
2028 set vcmitlisted($n) {}
2031 set vhl_done $commitidx($hlview)
2032 if {$vhl_done > 0} {
2037 proc delvhighlight {} {
2038 global hlview vhighlights
2040 if {![info exists hlview]} return
2042 catch {unset vhighlights}
2046 proc vhighlightmore {} {
2047 global hlview vhl_done commitidx vhighlights
2048 global displayorder vdisporder curview mainfont
2050 set font [concat $mainfont bold]
2051 set max $commitidx($hlview)
2052 if {$hlview == $curview} {
2053 set disp $displayorder
2055 set disp $vdisporder($hlview)
2057 set vr [visiblerows]
2058 set r0 [lindex $vr 0]
2059 set r1 [lindex $vr 1]
2060 for {set i $vhl_done} {$i < $max} {incr i} {
2061 set id [lindex $disp $i]
2062 if {[info exists commitrow($curview,$id)]} {
2063 set row $commitrow($curview,$id)
2064 if {$r0 <= $row && $row <= $r1} {
2065 if {![highlighted $row]} {
2068 set vhighlights($row) 1
2075 proc askvhighlight {row id} {
2076 global hlview vhighlights commitrow iddrawn mainfont
2078 if {[info exists commitrow($hlview,$id)]} {
2079 if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
2080 bolden $row [concat $mainfont bold]
2082 set vhighlights($row) 1
2084 set vhighlights($row) 0
2088 proc hfiles_change {name ix op} {
2089 global highlight_files filehighlight fhighlights fh_serial
2090 global mainfont highlight_paths
2092 if {[info exists filehighlight]} {
2093 # delete previous highlights
2094 catch {close $filehighlight}
2096 catch {unset fhighlights}
2098 unhighlight_filelist
2100 set highlight_paths {}
2101 after cancel do_file_hl $fh_serial
2103 if {$highlight_files ne {}} {
2104 after 300 do_file_hl $fh_serial
2108 proc makepatterns {l} {
2111 set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
2112 if {[string index $ee end] eq "/"} {
2122 proc do_file_hl {serial} {
2123 global highlight_files filehighlight highlight_paths gdttype fhl_list
2125 if {$gdttype eq "touching paths:"} {
2126 if {[catch {set paths [shellsplit $highlight_files]}]} return
2127 set highlight_paths [makepatterns $paths]
2129 set gdtargs [concat -- $paths]
2131 set gdtargs [list "-S$highlight_files"]
2133 set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2134 set filehighlight [open $cmd r+]
2135 fconfigure $filehighlight -blocking 0
2136 filerun $filehighlight readfhighlight
2142 proc flushhighlights {} {
2143 global filehighlight fhl_list
2145 if {[info exists filehighlight]} {
2147 puts $filehighlight ""
2148 flush $filehighlight
2152 proc askfilehighlight {row id} {
2153 global filehighlight fhighlights fhl_list
2155 lappend fhl_list $id
2156 set fhighlights($row) -1
2157 puts $filehighlight $id
2160 proc readfhighlight {} {
2161 global filehighlight fhighlights commitrow curview mainfont iddrawn
2164 if {![info exists filehighlight]} {
2168 while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
2169 set line [string trim $line]
2170 set i [lsearch -exact $fhl_list $line]
2171 if {$i < 0} continue
2172 for {set j 0} {$j < $i} {incr j} {
2173 set id [lindex $fhl_list $j]
2174 if {[info exists commitrow($curview,$id)]} {
2175 set fhighlights($commitrow($curview,$id)) 0
2178 set fhl_list [lrange $fhl_list [expr {$i+1}] end]
2179 if {$line eq {}} continue
2180 if {![info exists commitrow($curview,$line)]} continue
2181 set row $commitrow($curview,$line)
2182 if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
2183 bolden $row [concat $mainfont bold]
2185 set fhighlights($row) 1
2187 if {[eof $filehighlight]} {
2189 puts "oops, git diff-tree died"
2190 catch {close $filehighlight}
2198 proc find_change {name ix op} {
2199 global nhighlights mainfont boldnamerows
2200 global findstring findpattern findtype
2202 # delete previous highlights, if any
2203 foreach row $boldnamerows {
2204 bolden_name $row $mainfont
2207 catch {unset nhighlights}
2210 if {$findtype ne "Regexp"} {
2211 set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
2213 set findpattern "*$e*"
2218 proc doesmatch {f} {
2219 global findtype findstring findpattern
2221 if {$findtype eq "Regexp"} {
2222 return [regexp $findstring $f]
2223 } elseif {$findtype eq "IgnCase"} {
2224 return [string match -nocase $findpattern $f]
2226 return [string match $findpattern $f]
2230 proc askfindhighlight {row id} {
2231 global nhighlights commitinfo iddrawn mainfont
2233 global markingmatches
2235 if {![info exists commitinfo($id)]} {
2238 set info $commitinfo($id)
2240 set fldtypes {Headline Author Date Committer CDate Comments}
2241 foreach f $info ty $fldtypes {
2242 if {($findloc eq "All fields" || $findloc eq $ty) &&
2244 if {$ty eq "Author"} {
2251 if {$isbold && [info exists iddrawn($id)]} {
2252 set f [concat $mainfont bold]
2253 if {![ishighlighted $row]} {
2259 if {$markingmatches} {
2260 markrowmatches $row $id
2263 set nhighlights($row) $isbold
2266 proc markrowmatches {row id} {
2267 global canv canv2 linehtag linentag commitinfo findloc
2269 set headline [lindex $commitinfo($id) 0]
2270 set author [lindex $commitinfo($id) 1]
2271 $canv delete match$row
2272 $canv2 delete match$row
2273 if {$findloc eq "All fields" || $findloc eq "Headline"} {
2274 set m [findmatches $headline]
2276 markmatches $canv $row $headline $linehtag($row) $m \
2277 [$canv itemcget $linehtag($row) -font] $row
2280 if {$findloc eq "All fields" || $findloc eq "Author"} {
2281 set m [findmatches $author]
2283 markmatches $canv2 $row $author $linentag($row) $m \
2284 [$canv2 itemcget $linentag($row) -font] $row
2289 proc vrel_change {name ix op} {
2290 global highlight_related
2293 if {$highlight_related ne "None"} {
2298 # prepare for testing whether commits are descendents or ancestors of a
2299 proc rhighlight_sel {a} {
2300 global descendent desc_todo ancestor anc_todo
2301 global highlight_related rhighlights
2303 catch {unset descendent}
2304 set desc_todo [list $a]
2305 catch {unset ancestor}
2306 set anc_todo [list $a]
2307 if {$highlight_related ne "None"} {
2313 proc rhighlight_none {} {
2316 catch {unset rhighlights}
2320 proc is_descendent {a} {
2321 global curview children commitrow descendent desc_todo
2324 set la $commitrow($v,$a)
2328 for {set i 0} {$i < [llength $todo]} {incr i} {
2329 set do [lindex $todo $i]
2330 if {$commitrow($v,$do) < $la} {
2331 lappend leftover $do
2334 foreach nk $children($v,$do) {
2335 if {![info exists descendent($nk)]} {
2336 set descendent($nk) 1
2344 set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2348 set descendent($a) 0
2349 set desc_todo $leftover
2352 proc is_ancestor {a} {
2353 global curview parentlist commitrow ancestor anc_todo
2356 set la $commitrow($v,$a)
2360 for {set i 0} {$i < [llength $todo]} {incr i} {
2361 set do [lindex $todo $i]
2362 if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
2363 lappend leftover $do
2366 foreach np [lindex $parentlist $commitrow($v,$do)] {
2367 if {![info exists ancestor($np)]} {
2376 set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
2381 set anc_todo $leftover
2384 proc askrelhighlight {row id} {
2385 global descendent highlight_related iddrawn mainfont rhighlights
2386 global selectedline ancestor
2388 if {![info exists selectedline]} return
2390 if {$highlight_related eq "Descendent" ||
2391 $highlight_related eq "Not descendent"} {
2392 if {![info exists descendent($id)]} {
2395 if {$descendent($id) == ($highlight_related eq "Descendent")} {
2398 } elseif {$highlight_related eq "Ancestor" ||
2399 $highlight_related eq "Not ancestor"} {
2400 if {![info exists ancestor($id)]} {
2403 if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
2407 if {[info exists iddrawn($id)]} {
2408 if {$isbold && ![ishighlighted $row]} {
2409 bolden $row [concat $mainfont bold]
2412 set rhighlights($row) $isbold
2415 proc next_hlcont {} {
2416 global fhl_row fhl_dirn displayorder numcommits
2417 global vhighlights fhighlights nhighlights rhighlights
2418 global hlview filehighlight findstring highlight_related
2420 if {![info exists fhl_dirn] || $fhl_dirn == 0} return
2423 if {$row < 0 || $row >= $numcommits} {
2428 set id [lindex $displayorder $row]
2429 if {[info exists hlview]} {
2430 if {![info exists vhighlights($row)]} {
2431 askvhighlight $row $id
2433 if {$vhighlights($row) > 0} break
2435 if {$findstring ne {}} {
2436 if {![info exists nhighlights($row)]} {
2437 askfindhighlight $row $id
2439 if {$nhighlights($row) > 0} break
2441 if {$highlight_related ne "None"} {
2442 if {![info exists rhighlights($row)]} {
2443 askrelhighlight $row $id
2445 if {$rhighlights($row) > 0} break
2447 if {[info exists filehighlight]} {
2448 if {![info exists fhighlights($row)]} {
2449 # ask for a few more while we're at it...
2451 for {set n 0} {$n < 100} {incr n} {
2452 if {![info exists fhighlights($r)]} {
2453 askfilehighlight $r [lindex $displayorder $r]
2456 if {$r < 0 || $r >= $numcommits} break
2460 if {$fhighlights($row) < 0} {
2464 if {$fhighlights($row) > 0} break
2472 proc next_highlight {dirn} {
2473 global selectedline fhl_row fhl_dirn
2474 global hlview filehighlight findstring highlight_related
2476 if {![info exists selectedline]} return
2477 if {!([info exists hlview] || $findstring ne {} ||
2478 $highlight_related ne "None" || [info exists filehighlight])} return
2479 set fhl_row [expr {$selectedline + $dirn}]
2484 proc cancel_next_highlight {} {
2490 # Graph layout functions
2492 proc shortids {ids} {
2495 if {[llength $id] > 1} {
2496 lappend res [shortids $id]
2497 } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
2498 lappend res [string range $id 0 7]
2506 proc incrange {l x o} {
2509 set e [lindex $l $x]
2511 lset l $x [expr {$e + $o}]
2520 for {} {$n > 0} {incr n -1} {
2526 proc usedinrange {id l1 l2} {
2527 global children commitrow curview
2529 if {[info exists commitrow($curview,$id)]} {
2530 set r $commitrow($curview,$id)
2531 if {$l1 <= $r && $r <= $l2} {
2532 return [expr {$r - $l1 + 1}]
2535 set kids $children($curview,$id)
2537 set r $commitrow($curview,$c)
2538 if {$l1 <= $r && $r <= $l2} {
2539 return [expr {$r - $l1 + 1}]
2545 proc sanity {row {full 0}} {
2546 global rowidlist rowoffsets
2549 set ids [lindex $rowidlist $row]
2552 if {$id eq {}} continue
2553 if {$col < [llength $ids] - 1 &&
2554 [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
2555 puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
2557 set o [lindex $rowoffsets $row $col]
2563 if {[lindex $rowidlist $y $x] != $id} {
2564 puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
2565 puts " id=[shortids $id] check started at row $row"
2566 for {set i $row} {$i >= $y} {incr i -1} {
2567 puts " row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
2572 set o [lindex $rowoffsets $y $x]
2577 proc makeuparrow {oid x y z} {
2578 global rowidlist rowoffsets uparrowlen idrowranges displayorder
2580 for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
2583 set off0 [lindex $rowoffsets $y]
2584 for {set x0 $x} {1} {incr x0} {
2585 if {$x0 >= [llength $off0]} {
2586 set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
2589 set z [lindex $off0 $x0]
2595 set z [expr {$x0 - $x}]
2596 lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
2597 lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
2599 set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
2600 lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
2601 lappend idrowranges($oid) [lindex $displayorder $y]
2604 proc initlayout {} {
2605 global rowidlist rowoffsets displayorder commitlisted
2606 global rowlaidout rowoptim
2607 global idinlist rowchk rowrangelist idrowranges
2608 global numcommits canvxmax canv
2611 global colormap rowtextx
2622 catch {unset idinlist}
2623 catch {unset rowchk}
2626 set canvxmax [$canv cget -width]
2627 catch {unset colormap}
2628 catch {unset rowtextx}
2629 catch {unset idrowranges}
2633 proc setcanvscroll {} {
2634 global canv canv2 canv3 numcommits linespc canvxmax canvy0
2636 set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
2637 $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
2638 $canv2 conf -scrollregion [list 0 0 0 $ymax]
2639 $canv3 conf -scrollregion [list 0 0 0 $ymax]
2642 proc visiblerows {} {
2643 global canv numcommits linespc
2645 set ymax [lindex [$canv cget -scrollregion] 3]
2646 if {$ymax eq {} || $ymax == 0} return
2648 set y0 [expr {int([lindex $f 0] * $ymax)}]
2649 set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
2653 set y1 [expr {int([lindex $f 1] * $ymax)}]
2654 set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
2655 if {$r1 >= $numcommits} {
2656 set r1 [expr {$numcommits - 1}]
2658 return [list $r0 $r1]
2661 proc layoutmore {tmax allread} {
2662 global rowlaidout rowoptim commitidx numcommits optim_delay
2663 global uparrowlen curview rowidlist idinlist
2666 set showdelay $optim_delay
2667 set optdelay [expr {$uparrowlen + 1}]
2669 if {$rowoptim - $showdelay > $numcommits} {
2670 showstuff [expr {$rowoptim - $showdelay}] $showlast
2671 } elseif {$rowlaidout - $optdelay > $rowoptim} {
2672 set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
2676 optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
2678 } elseif {$commitidx($curview) > $rowlaidout} {
2679 set nr [expr {$commitidx($curview) - $rowlaidout}]
2680 # may need to increase this threshold if uparrowlen or
2681 # mingaplen are increased...
2686 set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
2687 if {$rowlaidout == $row} {
2690 } elseif {$allread} {
2692 set nrows $commitidx($curview)
2693 if {[lindex $rowidlist $nrows] ne {} ||
2694 [array names idinlist] ne {}} {
2696 set rowlaidout $commitidx($curview)
2697 } elseif {$rowoptim == $nrows} {
2700 if {$numcommits == $nrows} {
2707 if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
2713 proc showstuff {canshow last} {
2714 global numcommits commitrow pending_select selectedline curview
2715 global lookingforhead mainheadid displayorder selectfirst
2716 global lastscrollset
2718 if {$numcommits == 0} {
2720 set phase "incrdraw"
2724 set prev $numcommits
2725 set numcommits $canshow
2726 set t [clock clicks -milliseconds]
2727 if {$prev < 100 || $last || $t - $lastscrollset > 500} {
2728 set lastscrollset $t
2731 set rows [visiblerows]
2732 set r1 [lindex $rows 1]
2733 if {$r1 >= $canshow} {
2734 set r1 [expr {$canshow - 1}]
2739 if {[info exists pending_select] &&
2740 [info exists commitrow($curview,$pending_select)] &&
2741 $commitrow($curview,$pending_select) < $numcommits} {
2742 selectline $commitrow($curview,$pending_select) 1
2745 if {[info exists selectedline] || [info exists pending_select]} {
2748 set l [first_real_row]
2753 if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
2754 && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2755 set lookingforhead 0
2760 proc doshowlocalchanges {} {
2761 global lookingforhead curview mainheadid phase commitrow
2763 if {[info exists commitrow($curview,$mainheadid)] &&
2764 ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
2766 } elseif {$phase ne {}} {
2767 set lookingforhead 1
2771 proc dohidelocalchanges {} {
2772 global lookingforhead localfrow localirow lserial
2774 set lookingforhead 0
2775 if {$localfrow >= 0} {
2776 removerow $localfrow
2778 if {$localirow > 0} {
2782 if {$localirow >= 0} {
2783 removerow $localirow
2789 # spawn off a process to do git diff-index --cached HEAD
2790 proc dodiffindex {} {
2791 global localirow localfrow lserial
2796 set fd [open "|git diff-index --cached HEAD" r]
2797 fconfigure $fd -blocking 0
2798 filerun $fd [list readdiffindex $fd $lserial]
2801 proc readdiffindex {fd serial} {
2802 global localirow commitrow mainheadid nullid2 curview
2803 global commitinfo commitdata lserial
2806 if {[gets $fd line] < 0} {
2812 # we only need to see one line and we don't really care what it says...
2815 # now see if there are any local changes not checked in to the index
2816 if {$serial == $lserial} {
2817 set fd [open "|git diff-files" r]
2818 fconfigure $fd -blocking 0
2819 filerun $fd [list readdifffiles $fd $serial]
2822 if {$isdiff && $serial == $lserial && $localirow == -1} {
2823 # add the line for the changes in the index to the graph
2824 set localirow $commitrow($curview,$mainheadid)
2825 set hl "Local changes checked in to index but not committed"
2826 set commitinfo($nullid2) [list $hl {} {} {} {} " $hl\n"]
2827 set commitdata($nullid2) "\n $hl\n"
2828 insertrow $localirow $nullid2
2833 proc readdifffiles {fd serial} {
2834 global localirow localfrow commitrow mainheadid nullid curview
2835 global commitinfo commitdata lserial
2838 if {[gets $fd line] < 0} {
2844 # we only need to see one line and we don't really care what it says...
2847 if {$isdiff && $serial == $lserial && $localfrow == -1} {
2848 # add the line for the local diff to the graph
2849 if {$localirow >= 0} {
2850 set localfrow $localirow
2853 set localfrow $commitrow($curview,$mainheadid)
2855 set hl "Local uncommitted changes, not checked in to index"
2856 set commitinfo($nullid) [list $hl {} {} {} {} " $hl\n"]
2857 set commitdata($nullid) "\n $hl\n"
2858 insertrow $localfrow $nullid
2863 proc layoutrows {row endrow last} {
2864 global rowidlist rowoffsets displayorder
2865 global uparrowlen downarrowlen maxwidth mingaplen
2866 global children parentlist
2868 global commitidx curview
2869 global idinlist rowchk rowrangelist
2871 set idlist [lindex $rowidlist $row]
2872 set offs [lindex $rowoffsets $row]
2873 while {$row < $endrow} {
2874 set id [lindex $displayorder $row]
2877 foreach p [lindex $parentlist $row] {
2878 if {![info exists idinlist($p)]} {
2880 } elseif {!$idinlist($p)} {
2884 set nev [expr {[llength $idlist] + [llength $newolds]
2885 + [llength $oldolds] - $maxwidth + 1}]
2888 $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
2889 for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
2890 set i [lindex $idlist $x]
2891 if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
2892 set r [usedinrange $i [expr {$row - $downarrowlen}] \
2893 [expr {$row + $uparrowlen + $mingaplen}]]
2895 set idlist [lreplace $idlist $x $x]
2896 set offs [lreplace $offs $x $x]
2897 set offs [incrange $offs $x 1]
2899 set rm1 [expr {$row - 1}]
2900 lappend idrowranges($i) [lindex $displayorder $rm1]
2901 if {[incr nev -1] <= 0} break
2904 set rowchk($id) [expr {$row + $r}]
2907 lset rowidlist $row $idlist
2908 lset rowoffsets $row $offs
2910 set col [lsearch -exact $idlist $id]
2912 set col [llength $idlist]
2914 lset rowidlist $row $idlist
2916 if {$children($curview,$id) ne {}} {
2917 set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
2921 lset rowoffsets $row $offs
2923 makeuparrow $id $col $row $z
2929 if {[info exists idrowranges($id)]} {
2930 set ranges $idrowranges($id)
2932 unset idrowranges($id)
2934 lappend rowrangelist $ranges
2936 set offs [ntimes [llength $idlist] 0]
2937 set l [llength $newolds]
2938 set idlist [eval lreplace \$idlist $col $col $newolds]
2941 set offs [lrange $offs 0 [expr {$col - 1}]]
2942 foreach x $newolds {
2947 set tmp [expr {[llength $idlist] - [llength $offs]}]
2949 set offs [concat $offs [ntimes $tmp $o]]
2954 foreach i $newolds {
2956 set idrowranges($i) $id
2959 foreach oid $oldolds {
2960 set idinlist($oid) 1
2961 set idlist [linsert $idlist $col $oid]
2962 set offs [linsert $offs $col $o]
2963 makeuparrow $oid $col $row $o
2966 lappend rowidlist $idlist
2967 lappend rowoffsets $offs
2972 proc addextraid {id row} {
2973 global displayorder commitrow commitinfo
2974 global commitidx commitlisted
2975 global parentlist children curview
2977 incr commitidx($curview)
2978 lappend displayorder $id
2979 lappend commitlisted 0
2980 lappend parentlist {}
2981 set commitrow($curview,$id) $row
2983 if {![info exists commitinfo($id)]} {
2984 set commitinfo($id) {"No commit information available"}
2986 if {![info exists children($curview,$id)]} {
2987 set children($curview,$id) {}
2991 proc layouttail {} {
2992 global rowidlist rowoffsets idinlist commitidx curview
2993 global idrowranges rowrangelist
2995 set row $commitidx($curview)
2996 set idlist [lindex $rowidlist $row]
2997 while {$idlist ne {}} {
2998 set col [expr {[llength $idlist] - 1}]
2999 set id [lindex $idlist $col]
3002 lappend idrowranges($id) $id
3003 lappend rowrangelist $idrowranges($id)
3004 unset idrowranges($id)
3006 set offs [ntimes $col 0]
3007 set idlist [lreplace $idlist $col $col]
3008 lappend rowidlist $idlist
3009 lappend rowoffsets $offs
3012 foreach id [array names idinlist] {
3015 lset rowidlist $row [list $id]
3016 lset rowoffsets $row 0
3017 makeuparrow $id 0 $row 0
3018 lappend idrowranges($id) $id
3019 lappend rowrangelist $idrowranges($id)
3020 unset idrowranges($id)
3022 lappend rowidlist {}
3023 lappend rowoffsets {}
3027 proc insert_pad {row col npad} {
3028 global rowidlist rowoffsets
3030 set pad [ntimes $npad {}]
3031 lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
3032 set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
3033 lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
3036 proc optimize_rows {row col endrow} {
3037 global rowidlist rowoffsets displayorder
3039 for {} {$row < $endrow} {incr row} {
3040 set idlist [lindex $rowidlist $row]
3041 set offs [lindex $rowoffsets $row]
3043 for {} {$col < [llength $offs]} {incr col} {
3044 if {[lindex $idlist $col] eq {}} {
3048 set z [lindex $offs $col]
3049 if {$z eq {}} continue
3051 set x0 [expr {$col + $z}]
3052 set y0 [expr {$row - 1}]
3053 set z0 [lindex $rowoffsets $y0 $x0]
3055 set id [lindex $idlist $col]
3056 set ranges [rowranges $id]
3057 if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
3061 # Looking at lines from this row to the previous row,
3062 # make them go straight up if they end in an arrow on
3063 # the previous row; otherwise make them go straight up
3065 if {$z < -1 || ($z < 0 && $isarrow)} {
3066 # Line currently goes left too much;
3067 # insert pads in the previous row, then optimize it
3068 set npad [expr {-1 - $z + $isarrow}]
3069 set offs [incrange $offs $col $npad]
3070 insert_pad $y0 $x0 $npad
3072 optimize_rows $y0 $x0 $row
3074 set z [lindex $offs $col]
3075 set x0 [expr {$col + $z}]
3076 set z0 [lindex $rowoffsets $y0 $x0]
3077 } elseif {$z > 1 || ($z > 0 && $isarrow)} {
3078 # Line currently goes right too much;
3079 # insert pads in this line and adjust the next's rowoffsets
3080 set npad [expr {$z - 1 + $isarrow}]
3081 set y1 [expr {$row + 1}]
3082 set offs2 [lindex $rowoffsets $y1]
3086 if {$z eq {} || $x1 + $z < $col} continue
3087 if {$x1 + $z > $col} {
3090 lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
3093 set pad [ntimes $npad {}]
3094 set idlist [eval linsert \$idlist $col $pad]
3095 set tmp [eval linsert \$offs $col $pad]
3097 set offs [incrange $tmp $col [expr {-$npad}]]
3098 set z [lindex $offs $col]
3101 if {$z0 eq {} && !$isarrow} {
3102 # this line links to its first child on row $row-2
3103 set rm2 [expr {$row - 2}]
3104 set id [lindex $displayorder $rm2]
3105 set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
3107 set z0 [expr {$xc - $x0}]
3110 # avoid lines jigging left then immediately right
3111 if {$z0 ne {} && $z < 0 && $z0 > 0} {
3112 insert_pad $y0 $x0 1
3113 set offs [incrange $offs $col 1]
3114 optimize_rows $y0 [expr {$x0 + 1}] $row
3119 # Find the first column that doesn't have a line going right
3120 for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
3121 set o [lindex $offs $col]
3123 # check if this is the link to the first child
3124 set id [lindex $idlist $col]
3125 set ranges [rowranges $id]
3126 if {$ranges ne {} && $row == [lindex $ranges 0]} {
3127 # it is, work out offset to child
3128 set y0 [expr {$row - 1}]
3129 set id [lindex $displayorder $y0]
3130 set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
3132 set o [expr {$x0 - $col}]
3136 if {$o eq {} || $o <= 0} break
3138 # Insert a pad at that column as long as it has a line and
3139 # isn't the last column, and adjust the next row' offsets
3140 if {$o ne {} && [incr col] < [llength $idlist]} {
3141 set y1 [expr {$row + 1}]
3142 set offs2 [lindex $rowoffsets $y1]
3146 if {$z eq {} || $x1 + $z < $col} continue
3147 lset rowoffsets $y1 [incrange $offs2 $x1 1]
3150 set idlist [linsert $idlist $col {}]
3151 set tmp [linsert $offs $col {}]
3153 set offs [incrange $tmp $col -1]
3156 lset rowidlist $row $idlist
3157 lset rowoffsets $row $offs
3163 global canvx0 linespc
3164 return [expr {$canvx0 + $col * $linespc}]
3168 global canvy0 linespc
3169 return [expr {$canvy0 + $row * $linespc}]
3172 proc linewidth {id} {
3173 global thickerline lthickness
3176 if {[info exists thickerline] && $id eq $thickerline} {
3177 set wid [expr {2 * $lthickness}]
3182 proc rowranges {id} {
3183 global phase idrowranges commitrow rowlaidout rowrangelist curview
3187 ([info exists commitrow($curview,$id)]
3188 && $commitrow($curview,$id) < $rowlaidout)} {
3189 set ranges [lindex $rowrangelist $commitrow($curview,$id)]
3190 } elseif {[info exists idrowranges($id)]} {
3191 set ranges $idrowranges($id)
3194 foreach rid $ranges {
3195 lappend linenos $commitrow($curview,$rid)
3197 if {$linenos ne {}} {
3198 lset linenos 0 [expr {[lindex $linenos 0] + 1}]
3203 # work around tk8.4 refusal to draw arrows on diagonal segments
3204 proc adjarrowhigh {coords} {
3207 set x0 [lindex $coords 0]
3208 set x1 [lindex $coords 2]
3210 set y0 [lindex $coords 1]
3211 set y1 [lindex $coords 3]
3212 if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
3213 # we have a nearby vertical segment, just trim off the diag bit
3214 set coords [lrange $coords 2 end]
3216 set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
3217 set xi [expr {$x0 - $slope * $linespc / 2}]
3218 set yi [expr {$y0 - $linespc / 2}]
3219 set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
3225 proc drawlineseg {id row endrow arrowlow} {
3226 global rowidlist displayorder iddrawn linesegs
3227 global canv colormap linespc curview maxlinelen
3229 set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
3230 set le [expr {$row + 1}]
3233 set c [lsearch -exact [lindex $rowidlist $le] $id]
3239 set x [lindex $displayorder $le]
3244 if {[info exists iddrawn($x)] || $le == $endrow} {
3245 set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
3261 if {[info exists linesegs($id)]} {
3262 set lines $linesegs($id)
3264 set r0 [lindex $li 0]
3266 if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
3276 set li [lindex $lines [expr {$i-1}]]
3277 set r1 [lindex $li 1]
3278 if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
3283 set x [lindex $cols [expr {$le - $row}]]
3284 set xp [lindex $cols [expr {$le - 1 - $row}]]
3285 set dir [expr {$xp - $x}]
3287 set ith [lindex $lines $i 2]
3288 set coords [$canv coords $ith]
3289 set ah [$canv itemcget $ith -arrow]
3290 set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
3291 set x2 [lindex $cols [expr {$le + 1 - $row}]]
3292 if {$x2 ne {} && $x - $x2 == $dir} {
3293 set coords [lrange $coords 0 end-2]
3296 set coords [list [xc $le $x] [yc $le]]
3299 set itl [lindex $lines [expr {$i-1}] 2]
3300 set al [$canv itemcget $itl -arrow]
3301 set arrowlow [expr {$al eq "last" || $al eq "both"}]
3302 } elseif {$arrowlow &&
3303 [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
3306 set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
3307 for {set y $le} {[incr y -1] > $row} {} {
3309 set xp [lindex $cols [expr {$y - 1 - $row}]]
3310 set ndir [expr {$xp - $x}]
3311 if {$dir != $ndir || $xp < 0} {
3312 lappend coords [xc $y $x] [yc $y]
3318 # join parent line to first child
3319 set ch [lindex $displayorder $row]
3320 set xc [lsearch -exact [lindex $rowidlist $row] $ch]
3322 puts "oops: drawlineseg: child $ch not on row $row"
3325 lappend coords [xc $row [expr {$x-1}]] [yc $row]
3326 } elseif {$xc > $x + 1} {
3327 lappend coords [xc $row [expr {$x+1}]] [yc $row]
3331 lappend coords [xc $row $x] [yc $row]
3333 set xn [xc $row $xp]
3335 # work around tk8.4 refusal to draw arrows on diagonal segments
3336 if {$arrowlow && $xn != [lindex $coords end-1]} {
3337 if {[llength $coords] < 4 ||
3338 [lindex $coords end-3] != [lindex $coords end-1] ||
3339 [lindex $coords end] - $yn > 2 * $linespc} {
3340 set xn [xc $row [expr {$xp - 0.5 * $dir}]]
3341 set yo [yc [expr {$row + 0.5}]]
3342 lappend coords $xn $yo $xn $yn
3345 lappend coords $xn $yn
3350 set coords [adjarrowhigh $coords]
3353 set t [$canv create line $coords -width [linewidth $id] \
3354 -fill $colormap($id) -tags lines.$id -arrow $arrow]
3357 set lines [linsert $lines $i [list $row $le $t]]
3359 $canv coords $ith $coords
3360 if {$arrow ne $ah} {
3361 $canv itemconf $ith -arrow $arrow
3363 lset lines $i 0 $row
3366 set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
3367 set ndir [expr {$xo - $xp}]
3368 set clow [$canv coords $itl]
3369 if {$dir == $ndir} {
3370 set clow [lrange $clow 2 end]
3372 set coords [concat $coords $clow]
3374 lset lines [expr {$i-1}] 1 $le
3376 set coords [adjarrowhigh $coords]
3379 # coalesce two pieces
3381 set b [lindex $lines [expr {$i-1}] 0]
3382 set e [lindex $lines $i 1]
3383 set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
3385 $canv coords $itl $coords
3386 if {$arrow ne $al} {
3387 $canv itemconf $itl -arrow $arrow
3391 set linesegs($id) $lines
3395 proc drawparentlinks {id row} {
3396 global rowidlist canv colormap curview parentlist
3399 set rowids [lindex $rowidlist $row]
3400 set col [lsearch -exact $rowids $id]
3401 if {$col < 0} return
3402 set olds [lindex $parentlist $row]
3403 set row2 [expr {$row + 1}]
3404 set x [xc $row $col]
3407 set ids [lindex $rowidlist $row2]
3408 # rmx = right-most X coord used
3411 set i [lsearch -exact $ids $p]
3413 puts "oops, parent $p of $id not in list"
3416 set x2 [xc $row2 $i]
3420 if {[lsearch -exact $rowids $p] < 0} {
3421 # drawlineseg will do this one for us
3425 # should handle duplicated parents here...
3426 set coords [list $x $y]
3427 if {$i < $col - 1} {
3428 lappend coords [xc $row [expr {$i + 1}]] $y
3429 } elseif {$i > $col + 1} {
3430 lappend coords [xc $row [expr {$i - 1}]] $y
3432 lappend coords $x2 $y2
3433 set t [$canv create line $coords -width [linewidth $p] \
3434 -fill $colormap($p) -tags lines.$p]
3438 if {$rmx > [lindex $idpos($id) 1]} {
3439 lset idpos($id) 1 $rmx
3444 proc drawlines {id} {
3447 $canv itemconf lines.$id -width [linewidth $id]
3450 proc drawcmittext {id row col} {
3451 global linespc canv canv2 canv3 canvy0 fgcolor curview
3452 global commitlisted commitinfo rowidlist parentlist
3453 global rowtextx idpos idtags idheads idotherrefs
3454 global linehtag linentag linedtag
3455 global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
3457 # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
3458 set listed [lindex $commitlisted $row]
3459 if {$id eq $nullid} {
3461 } elseif {$id eq $nullid2} {
3464 set ofill [expr {$listed != 0? "blue": "white"}]
3466 set x [xc $row $col]
3468 set orad [expr {$linespc / 3}]
3470 set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
3471 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3472 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3473 } elseif {$listed == 2} {
3474 # triangle pointing left for left-side commits
3475 set t [$canv create polygon \
3476 [expr {$x - $orad}] $y \
3477 [expr {$x + $orad - 1}] [expr {$y - $orad}] \
3478 [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
3479 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3481 # triangle pointing right for right-side commits
3482 set t [$canv create polygon \
3483 [expr {$x + $orad - 1}] $y \
3484 [expr {$x - $orad}] [expr {$y - $orad}] \
3485 [expr {$x - $orad}] [expr {$y + $orad - 1}] \
3486 -fill $ofill -outline $fgcolor -width 1 -tags circle]
3489 $canv bind $t <1> {selcanvline {} %x %y}
3490 set rmx [llength [lindex $rowidlist $row]]
3491 set olds [lindex $parentlist $row]
3493 set nextids [lindex $rowidlist [expr {$row + 1}]]
3495 set i [lsearch -exact $nextids $p]
3501 set xt [xc $row $rmx]
3502 set rowtextx($row) $xt
3503 set idpos($id) [list $x $xt $y]
3504 if {[info exists idtags($id)] || [info exists idheads($id)]
3505 || [info exists idotherrefs($id)]} {
3506 set xt [drawtags $id $x $xt $y]
3508 set headline [lindex $commitinfo($id) 0]
3509 set name [lindex $commitinfo($id) 1]
3510 set date [lindex $commitinfo($id) 2]
3511 set date [formatdate $date]
3514 set isbold [ishighlighted $row]
3516 lappend boldrows $row
3519 lappend boldnamerows $row
3523 set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
3524 -text $headline -font $font -tags text]
3525 $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
3526 set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
3527 -text $name -font $nfont -tags text]
3528 set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
3529 -text $date -font $mainfont -tags text]
3530 set xr [expr {$xt + [font measure $mainfont $headline]}]
3531 if {$xr > $canvxmax} {
3537 proc drawcmitrow {row} {
3538 global displayorder rowidlist
3539 global iddrawn markingmatches
3540 global commitinfo parentlist numcommits
3541 global filehighlight fhighlights findstring nhighlights
3542 global hlview vhighlights
3543 global highlight_related rhighlights
3545 if {$row >= $numcommits} return
3547 set id [lindex $displayorder $row]
3548 if {[info exists hlview] && ![info exists vhighlights($row)]} {
3549 askvhighlight $row $id
3551 if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
3552 askfilehighlight $row $id
3554 if {$findstring ne {} && ![info exists nhighlights($row)]} {
3555 askfindhighlight $row $id
3557 if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
3558 askrelhighlight $row $id
3560 if {![info exists iddrawn($id)]} {
3561 set col [lsearch -exact [lindex $rowidlist $row] $id]
3563 puts "oops, row $row id $id not in list"
3566 if {![info exists commitinfo($id)]} {
3570 drawcmittext $id $row $col
3573 if {$markingmatches} {
3574 markrowmatches $row $id
3578 proc drawcommits {row {endrow {}}} {
3579 global numcommits iddrawn displayorder curview
3580 global parentlist rowidlist
3585 if {$endrow eq {}} {
3588 if {$endrow >= $numcommits} {
3589 set endrow [expr {$numcommits - 1}]
3592 # make the lines join to already-drawn rows either side
3593 set r [expr {$row - 1}]
3594 if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
3597 set er [expr {$endrow + 1}]
3598 if {$er >= $numcommits ||
3599 ![info exists iddrawn([lindex $displayorder $er])]} {
3602 for {} {$r <= $er} {incr r} {
3603 set id [lindex $displayorder $r]
3604 set wasdrawn [info exists iddrawn($id)]
3606 if {$r == $er} break
3607 set nextid [lindex $displayorder [expr {$r + 1}]]
3608 if {$wasdrawn && [info exists iddrawn($nextid)]} {
3609 catch {unset prevlines}
3612 drawparentlinks $id $r
3614 if {[info exists lineends($r)]} {
3615 foreach lid $lineends($r) {
3616 unset prevlines($lid)
3619 set rowids [lindex $rowidlist $r]
3620 foreach lid $rowids {
3621 if {$lid eq {}} continue
3623 # see if this is the first child of any of its parents
3624 foreach p [lindex $parentlist $r] {
3625 if {[lsearch -exact $rowids $p] < 0} {
3626 # make this line extend up to the child
3627 set le [drawlineseg $p $r $er 0]
3628 lappend lineends($le) $p
3632 } elseif {![info exists prevlines($lid)]} {
3633 set le [drawlineseg $lid $r $er 1]
3634 lappend lineends($le) $lid
3635 set prevlines($lid) 1
3641 proc drawfrac {f0 f1} {
3644 set ymax [lindex [$canv cget -scrollregion] 3]
3645 if {$ymax eq {} || $ymax == 0} return
3646 set y0 [expr {int($f0 * $ymax)}]
3647 set row [expr {int(($y0 - 3) / $linespc) - 1}]
3648 set y1 [expr {int($f1 * $ymax)}]
3649 set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
3650 drawcommits $row $endrow
3653 proc drawvisible {} {
3655 eval drawfrac [$canv yview]
3658 proc clear_display {} {
3659 global iddrawn linesegs
3660 global vhighlights fhighlights nhighlights rhighlights
3663 catch {unset iddrawn}
3664 catch {unset linesegs}
3665 catch {unset vhighlights}
3666 catch {unset fhighlights}
3667 catch {unset nhighlights}
3668 catch {unset rhighlights}
3671 proc findcrossings {id} {
3672 global rowidlist parentlist numcommits rowoffsets displayorder
3676 foreach {s e} [rowranges $id] {
3677 if {$e >= $numcommits} {
3678 set e [expr {$numcommits - 1}]
3680 if {$e <= $s} continue
3681 set x [lsearch -exact [lindex $rowidlist $e] $id]
3683 puts "findcrossings: oops, no [shortids $id] in row $e"
3686 for {set row $e} {[incr row -1] >= $s} {} {
3687 set olds [lindex $parentlist $row]
3688 set kid [lindex $displayorder $row]
3689 set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
3690 if {$kidx < 0} continue
3691 set nextrow [lindex $rowidlist [expr {$row + 1}]]
3693 set px [lsearch -exact $nextrow $p]
3694 if {$px < 0} continue
3695 if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
3696 if {[lsearch -exact $ccross $p] >= 0} continue
3697 if {$x == $px + ($kidx < $px? -1: 1)} {
3699 } elseif {[lsearch -exact $cross $p] < 0} {
3704 set inc [lindex $rowoffsets $row $x]
3705 if {$inc eq {}} break
3709 return [concat $ccross {{}} $cross]
3712 proc assigncolor {id} {
3713 global colormap colors nextcolor
3714 global commitrow parentlist children children curview
3716 if {[info exists colormap($id)]} return
3717 set ncolors [llength $colors]
3718 if {[info exists children($curview,$id)]} {
3719 set kids $children($curview,$id)
3723 if {[llength $kids] == 1} {
3724 set child [lindex $kids 0]
3725 if {[info exists colormap($child)]
3726 && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
3727 set colormap($id) $colormap($child)
3733 foreach x [findcrossings $id] {
3735 # delimiter between corner crossings and other crossings
3736 if {[llength $badcolors] >= $ncolors - 1} break
3737 set origbad $badcolors
3739 if {[info exists colormap($x)]
3740 && [lsearch -exact $badcolors $colormap($x)] < 0} {
3741 lappend badcolors $colormap($x)
3744 if {[llength $badcolors] >= $ncolors} {
3745 set badcolors $origbad
3747 set origbad $badcolors
3748 if {[llength $badcolors] < $ncolors - 1} {
3749 foreach child $kids {
3750 if {[info exists colormap($child)]
3751 && [lsearch -exact $badcolors $colormap($child)] < 0} {
3752 lappend badcolors $colormap($child)
3754 foreach p [lindex $parentlist $commitrow($curview,$child)] {
3755 if {[info exists colormap($p)]
3756 && [lsearch -exact $badcolors $colormap($p)] < 0} {
3757 lappend badcolors $colormap($p)
3761 if {[llength $badcolors] >= $ncolors} {
3762 set badcolors $origbad
3765 for {set i 0} {$i <= $ncolors} {incr i} {
3766 set c [lindex $colors $nextcolor]
3767 if {[incr nextcolor] >= $ncolors} {
3770 if {[lsearch -exact $badcolors $c]} break
3772 set colormap($id) $c
3775 proc bindline {t id} {
3778 $canv bind $t <Enter> "lineenter %x %y $id"
3779 $canv bind $t <Motion> "linemotion %x %y $id"
3780 $canv bind $t <Leave> "lineleave $id"
3781 $canv bind $t <Button-1> "lineclick %x %y $id 1"
3784 proc drawtags {id x xt y1} {
3785 global idtags idheads idotherrefs mainhead
3786 global linespc lthickness
3787 global canv mainfont commitrow rowtextx curview fgcolor bgcolor
3792 if {[info exists idtags($id)]} {
3793 set marks $idtags($id)
3794 set ntags [llength $marks]
3796 if {[info exists idheads($id)]} {
3797 set marks [concat $marks $idheads($id)]
3798 set nheads [llength $idheads($id)]
3800 if {[info exists idotherrefs($id)]} {
3801 set marks [concat $marks $idotherrefs($id)]
3807 set delta [expr {int(0.5 * ($linespc - $lthickness))}]
3808 set yt [expr {$y1 - 0.5 * $linespc}]
3809 set yb [expr {$yt + $linespc - 1}]
3813 foreach tag $marks {
3815 if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
3816 set wid [font measure [concat $mainfont bold] $tag]
3818 set wid [font measure $mainfont $tag]
3822 set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
3824 set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
3825 -width $lthickness -fill black -tags tag.$id]
3827 foreach tag $marks x $xvals wid $wvals {
3828 set xl [expr {$x + $delta}]
3829 set xr [expr {$x + $delta + $wid + $lthickness}]
3831 if {[incr ntags -1] >= 0} {
3833 set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
3834 $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
3835 -width 1 -outline black -fill yellow -tags tag.$id]
3836 $canv bind $t <1> [list showtag $tag 1]
3837 set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
3839 # draw a head or other ref
3840 if {[incr nheads -1] >= 0} {
3842 if {$tag eq $mainhead} {
3848 set xl [expr {$xl - $delta/2}]
3849 $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
3850 -width 1 -outline black -fill $col -tags tag.$id
3851 if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
3852 set rwid [font measure $mainfont $remoteprefix]
3853 set xi [expr {$x + 1}]
3854 set yti [expr {$yt + 1}]
3855 set xri [expr {$x + $rwid}]
3856 $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
3857 -width 0 -fill "#ffddaa" -tags tag.$id
3860 set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
3861 -font $font -tags [list tag.$id text]]
3863 $canv bind $t <1> [list showtag $tag 1]
3864 } elseif {$nheads >= 0} {
3865 $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
3871 proc xcoord {i level ln} {
3872 global canvx0 xspc1 xspc2
3874 set x [expr {$canvx0 + $i * $xspc1($ln)}]
3875 if {$i > 0 && $i == $level} {
3876 set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
3877 } elseif {$i > $level} {
3878 set x [expr {$x + $xspc2 - $xspc1($ln)}]
3883 proc show_status {msg} {
3884 global canv mainfont fgcolor
3887 $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
3888 -tags text -fill $fgcolor
3891 # Insert a new commit as the child of the commit on row $row.
3892 # The new commit will be displayed on row $row and the commits
3893 # on that row and below will move down one row.
3894 proc insertrow {row newcmit} {
3895 global displayorder parentlist commitlisted children
3896 global commitrow curview rowidlist rowoffsets numcommits
3897 global rowrangelist rowlaidout rowoptim numcommits
3898 global selectedline rowchk commitidx
3900 if {$row >= $numcommits} {
3901 puts "oops, inserting new row $row but only have $numcommits rows"
3904 set p [lindex $displayorder $row]
3905 set displayorder [linsert $displayorder $row $newcmit]
3906 set parentlist [linsert $parentlist $row $p]
3907 set kids $children($curview,$p)
3908 lappend kids $newcmit
3909 set children($curview,$p) $kids
3910 set children($curview,$newcmit) {}
3911 set commitlisted [linsert $commitlisted $row 1]
3912 set l [llength $displayorder]
3913 for {set r $row} {$r < $l} {incr r} {
3914 set id [lindex $displayorder $r]
3915 set commitrow($curview,$id) $r
3917 incr commitidx($curview)
3919 set idlist [lindex $rowidlist $row]
3920 set offs [lindex $rowoffsets $row]
3923 if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
3929 if {[llength $kids] == 1} {
3930 set col [lsearch -exact $idlist $p]
3931 lset idlist $col $newcmit
3933 set col [llength $idlist]
3934 lappend idlist $newcmit
3936 lset rowoffsets $row $offs
3938 set rowidlist [linsert $rowidlist $row $idlist]
3939 set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
3941 set rowrangelist [linsert $rowrangelist $row {}]
3942 if {[llength $kids] > 1} {
3943 set rp1 [expr {$row + 1}]
3944 set ranges [lindex $rowrangelist $rp1]
3945 if {$ranges eq {}} {
3946 set ranges [list $newcmit $p]
3947 } elseif {[lindex $ranges end-1] eq $p} {
3948 lset ranges end-1 $newcmit
3950 lset rowrangelist $rp1 $ranges
3953 catch {unset rowchk}
3959 if {[info exists selectedline] && $selectedline >= $row} {
3965 # Remove a commit that was inserted with insertrow on row $row.
3966 proc removerow {row} {
3967 global displayorder parentlist commitlisted children
3968 global commitrow curview rowidlist rowoffsets numcommits
3969 global rowrangelist idrowranges rowlaidout rowoptim numcommits
3970 global linesegends selectedline rowchk commitidx
3972 if {$row >= $numcommits} {
3973 puts "oops, removing row $row but only have $numcommits rows"
3976 set rp1 [expr {$row + 1}]
3977 set id [lindex $displayorder $row]
3978 set p [lindex $parentlist $row]
3979 set displayorder [lreplace $displayorder $row $row]
3980 set parentlist [lreplace $parentlist $row $row]
3981 set commitlisted [lreplace $commitlisted $row $row]
3982 set kids $children($curview,$p)
3983 set i [lsearch -exact $kids $id]
3985 set kids [lreplace $kids $i $i]
3986 set children($curview,$p) $kids
3988 set l [llength $displayorder]
3989 for {set r $row} {$r < $l} {incr r} {
3990 set id [lindex $displayorder $r]
3991 set commitrow($curview,$id) $r
3993 incr commitidx($curview) -1
3995 set rowidlist [lreplace $rowidlist $row $row]
3996 set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
3998 set offs [lindex $rowoffsets $row]
3999 set offs [lreplace $offs end end]
4000 lset rowoffsets $row $offs
4003 set rowrangelist [lreplace $rowrangelist $row $row]
4004 if {[llength $kids] > 0} {
4005 set ranges [lindex $rowrangelist $row]
4006 if {[lindex $ranges end-1] eq $id} {
4007 set ranges [lreplace $ranges end-1 end]
4008 lset rowrangelist $row $ranges
4012 catch {unset rowchk}
4018 if {[info exists selectedline] && $selectedline > $row} {
4019 incr selectedline -1
4024 # Don't change the text pane cursor if it is currently the hand cursor,
4025 # showing that we are over a sha1 ID link.
4026 proc settextcursor {c} {
4027 global ctext curtextcursor
4029 if {[$ctext cget -cursor] == $curtextcursor} {
4030 $ctext config -cursor $c
4032 set curtextcursor $c
4035 proc nowbusy {what} {
4038 if {[array names isbusy] eq {}} {
4039 . config -cursor watch
4045 proc notbusy {what} {
4046 global isbusy maincursor textcursor
4048 catch {unset isbusy($what)}
4049 if {[array names isbusy] eq {}} {
4050 . config -cursor $maincursor
4051 settextcursor $textcursor
4055 proc findmatches {f} {
4056 global findtype findstring
4057 if {$findtype == "Regexp"} {
4058 set matches [regexp -indices -all -inline $findstring $f]
4061 if {$findtype == "IgnCase"} {
4062 set f [string tolower $f]
4063 set fs [string tolower $fs]
4067 set l [string length $fs]
4068 while {[set j [string first $fs $f $i]] >= 0} {
4069 lappend matches [list $j [expr {$j+$l-1}]]
4070 set i [expr {$j + $l}]
4076 proc dofind {{rev 0}} {
4077 global findstring findstartline findcurline selectedline numcommits
4080 cancel_next_highlight
4082 if {$findstring eq {} || $numcommits == 0} return
4083 if {![info exists selectedline]} {
4084 set findstartline [lindex [visiblerows] $rev]
4086 set findstartline $selectedline
4088 set findcurline $findstartline
4093 if {$findcurline == 0} {
4094 set findcurline $numcommits
4101 proc findnext {restart} {
4103 if {![info exists findcurline]} {
4117 if {![info exists findcurline]} {
4126 global commitdata commitinfo numcommits findstring findpattern findloc
4127 global findstartline findcurline displayorder
4129 set fldtypes {Headline Author Date Committer CDate Comments}
4130 set l [expr {$findcurline + 1}]
4131 if {$l >= $numcommits} {
4134 if {$l <= $findstartline} {
4135 set lim [expr {$findstartline + 1}]
4139 if {$lim - $l > 500} {
4140 set lim [expr {$l + 500}]
4143 for {} {$l < $lim} {incr l} {
4144 set id [lindex $displayorder $l]
4145 # shouldn't happen unless git log doesn't give all the commits...
4146 if {![info exists commitdata($id)]} continue
4147 if {![doesmatch $commitdata($id)]} continue
4148 if {![info exists commitinfo($id)]} {
4151 set info $commitinfo($id)
4152 foreach f $info ty $fldtypes {
4153 if {($findloc eq "All fields" || $findloc eq $ty) &&
4161 if {$l == $findstartline + 1} {
4167 set findcurline [expr {$l - 1}]
4171 proc findmorerev {} {
4172 global commitdata commitinfo numcommits findstring findpattern findloc
4173 global findstartline findcurline displayorder
4175 set fldtypes {Headline Author Date Committer CDate Comments}
4181 if {$l >= $findstartline} {
4182 set lim [expr {$findstartline - 1}]
4186 if {$l - $lim > 500} {
4187 set lim [expr {$l - 500}]
4190 for {} {$l > $lim} {incr l -1} {
4191 set id [lindex $displayorder $l]
4192 if {![doesmatch $commitdata($id)]} continue
4193 if {![info exists commitinfo($id)]} {
4196 set info $commitinfo($id)
4197 foreach f $info ty $fldtypes {
4198 if {($findloc eq "All fields" || $findloc eq $ty) &&
4212 set findcurline [expr {$l + 1}]
4216 proc findselectline {l} {
4217 global findloc commentend ctext findcurline markingmatches
4219 set markingmatches 1
4222 if {$findloc == "All fields" || $findloc == "Comments"} {
4223 # highlight the matches in the comments
4224 set f [$ctext get 1.0 $commentend]
4225 set matches [findmatches $f]
4226 foreach match $matches {
4227 set start [lindex $match 0]
4228 set end [expr {[lindex $match 1] + 1}]
4229 $ctext tag add found "1.0 + $start c" "1.0 + $end c"
4235 # mark the bits of a headline or author that match a find string
4236 proc markmatches {canv l str tag matches font row} {
4239 set bbox [$canv bbox $tag]
4240 set x0 [lindex $bbox 0]
4241 set y0 [lindex $bbox 1]
4242 set y1 [lindex $bbox 3]
4243 foreach match $matches {
4244 set start [lindex $match 0]
4245 set end [lindex $match 1]
4246 if {$start > $end} continue
4247 set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
4248 set xlen [font measure $font [string range $str 0 [expr {$end}]]]
4249 set t [$canv create rect [expr {$x0+$xoff}] $y0 \
4250 [expr {$x0+$xlen+2}] $y1 \
4251 -outline {} -tags [list match$l matches] -fill yellow]
4253 if {[info exists selectedline] && $row == $selectedline} {
4254 $canv raise $t secsel
4259 proc unmarkmatches {} {
4260 global findids markingmatches findcurline
4262 allcanvs delete matches
4263 catch {unset findids}
4264 set markingmatches 0
4265 catch {unset findcurline}
4268 proc selcanvline {w x y} {
4269 global canv canvy0 ctext linespc
4271 set ymax [lindex [$canv cget -scrollregion] 3]
4272 if {$ymax == {}} return
4273 set yfrac [lindex [$canv yview] 0]
4274 set y [expr {$y + $yfrac * $ymax}]
4275 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
4280 if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
4286 proc commit_descriptor {p} {
4288 if {![info exists commitinfo($p)]} {
4292 if {[llength $commitinfo($p)] > 1} {
4293 set l [lindex $commitinfo($p) 0]
4298 # append some text to the ctext widget, and make any SHA1 ID
4299 # that we know about be a clickable link.
4300 proc appendwithlinks {text tags} {
4301 global ctext commitrow linknum curview
4303 set start [$ctext index "end - 1c"]
4304 $ctext insert end $text $tags
4305 set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
4309 set linkid [string range $text $s $e]
4310 if {![info exists commitrow($curview,$linkid)]} continue
4312 $ctext tag add link "$start + $s c" "$start + $e c"
4313 $ctext tag add link$linknum "$start + $s c" "$start + $e c"
4314 $ctext tag bind link$linknum <1> \
4315 [list selectline $commitrow($curview,$linkid) 1]
4318 $ctext tag conf link -foreground blue -underline 1
4319 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
4320 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
4323 proc viewnextline {dir} {
4327 set ymax [lindex [$canv cget -scrollregion] 3]
4328 set wnow [$canv yview]
4329 set wtop [expr {[lindex $wnow 0] * $ymax}]
4330 set newtop [expr {$wtop + $dir * $linespc}]
4333 } elseif {$newtop > $ymax} {
4336 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4339 # add a list of tag or branch names at position pos
4340 # returns the number of names inserted
4341 proc appendrefs {pos ids var} {
4342 global ctext commitrow linknum curview $var maxrefs
4344 if {[catch {$ctext index $pos}]} {
4347 $ctext conf -state normal
4348 $ctext delete $pos "$pos lineend"
4351 foreach tag [set $var\($id\)] {
4352 lappend tags [list $tag $id]
4355 if {[llength $tags] > $maxrefs} {
4356 $ctext insert $pos "many ([llength $tags])"
4358 set tags [lsort -index 0 -decreasing $tags]
4361 set id [lindex $ti 1]
4364 $ctext tag delete $lk
4365 $ctext insert $pos $sep
4366 $ctext insert $pos [lindex $ti 0] $lk
4367 if {[info exists commitrow($curview,$id)]} {
4368 $ctext tag conf $lk -foreground blue
4369 $ctext tag bind $lk <1> \
4370 [list selectline $commitrow($curview,$id) 1]
4371 $ctext tag conf $lk -underline 1
4372 $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
4373 $ctext tag bind $lk <Leave> \
4374 { %W configure -cursor $curtextcursor }
4379 $ctext conf -state disabled
4380 return [llength $tags]
4383 # called when we have finished computing the nearby tags
4384 proc dispneartags {delay} {
4385 global selectedline currentid showneartags tagphase
4387 if {![info exists selectedline] || !$showneartags} return
4388 after cancel dispnexttag
4390 after 200 dispnexttag
4393 after idle dispnexttag
4398 proc dispnexttag {} {
4399 global selectedline currentid showneartags tagphase ctext
4401 if {![info exists selectedline] || !$showneartags} return
4402 switch -- $tagphase {
4404 set dtags [desctags $currentid]
4406 appendrefs precedes $dtags idtags
4410 set atags [anctags $currentid]
4412 appendrefs follows $atags idtags
4416 set dheads [descheads $currentid]
4417 if {$dheads ne {}} {
4418 if {[appendrefs branch $dheads idheads] > 1
4419 && [$ctext get "branch -3c"] eq "h"} {
4420 # turn "Branch" into "Branches"
4421 $ctext conf -state normal
4422 $ctext insert "branch -2c" "es"
4423 $ctext conf -state disabled
4428 if {[incr tagphase] <= 2} {
4429 after idle dispnexttag
4433 proc selectline {l isnew} {
4434 global canv canv2 canv3 ctext commitinfo selectedline
4435 global displayorder linehtag linentag linedtag
4436 global canvy0 linespc parentlist children curview
4437 global currentid sha1entry
4438 global commentend idtags linknum
4439 global mergemax numcommits pending_select
4440 global cmitmode showneartags allcommits
4442 catch {unset pending_select}
4445 cancel_next_highlight
4446 if {$l < 0 || $l >= $numcommits} return
4447 set y [expr {$canvy0 + $l * $linespc}]
4448 set ymax [lindex [$canv cget -scrollregion] 3]
4449 set ytop [expr {$y - $linespc - 1}]
4450 set ybot [expr {$y + $linespc + 1}]
4451 set wnow [$canv yview]
4452 set wtop [expr {[lindex $wnow 0] * $ymax}]
4453 set wbot [expr {[lindex $wnow 1] * $ymax}]
4454 set wh [expr {$wbot - $wtop}]
4456 if {$ytop < $wtop} {
4457 if {$ybot < $wtop} {
4458 set newtop [expr {$y - $wh / 2.0}]
4461 if {$newtop > $wtop - $linespc} {
4462 set newtop [expr {$wtop - $linespc}]
4465 } elseif {$ybot > $wbot} {
4466 if {$ytop > $wbot} {
4467 set newtop [expr {$y - $wh / 2.0}]
4469 set newtop [expr {$ybot - $wh}]
4470 if {$newtop < $wtop + $linespc} {
4471 set newtop [expr {$wtop + $linespc}]
4475 if {$newtop != $wtop} {
4479 allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
4483 if {![info exists linehtag($l)]} return
4485 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
4486 -tags secsel -fill [$canv cget -selectbackground]]
4488 $canv2 delete secsel
4489 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
4490 -tags secsel -fill [$canv2 cget -selectbackground]]
4492 $canv3 delete secsel
4493 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
4494 -tags secsel -fill [$canv3 cget -selectbackground]]
4498 addtohistory [list selectline $l 0]
4503 set id [lindex $displayorder $l]
4505 $sha1entry delete 0 end
4506 $sha1entry insert 0 $id
4507 $sha1entry selection from 0
4508 $sha1entry selection to end
4511 $ctext conf -state normal
4514 set info $commitinfo($id)
4515 set date [formatdate [lindex $info 2]]
4516 $ctext insert end "Author: [lindex $info 1] $date\n"
4517 set date [formatdate [lindex $info 4]]
4518 $ctext insert end "Committer: [lindex $info 3] $date\n"
4519 if {[info exists idtags($id)]} {
4520 $ctext insert end "Tags:"
4521 foreach tag $idtags($id) {
4522 $ctext insert end " $tag"
4524 $ctext insert end "\n"
4528 set olds [lindex $parentlist $l]
4529 if {[llength $olds] > 1} {
4532 if {$np >= $mergemax} {
4537 $ctext insert end "Parent: " $tag
4538 appendwithlinks [commit_descriptor $p] {}
4543 append headers "Parent: [commit_descriptor $p]"
4547 foreach c $children($curview,$id) {
4548 append headers "Child: [commit_descriptor $c]"
4551 # make anything that looks like a SHA1 ID be a clickable link
4552 appendwithlinks $headers {}
4553 if {$showneartags} {
4554 if {![info exists allcommits]} {
4557 $ctext insert end "Branch: "
4558 $ctext mark set branch "end -1c"
4559 $ctext mark gravity branch left
4560 $ctext insert end "\nFollows: "
4561 $ctext mark set follows "end -1c"
4562 $ctext mark gravity follows left
4563 $ctext insert end "\nPrecedes: "
4564 $ctext mark set precedes "end -1c"
4565 $ctext mark gravity precedes left
4566 $ctext insert end "\n"
4569 $ctext insert end "\n"
4570 set comment [lindex $info 5]
4571 if {[string first "\r" $comment] >= 0} {
4572 set comment [string map {"\r" "\n "} $comment]
4574 appendwithlinks $comment {comment}
4576 $ctext tag remove found 1.0 end
4577 $ctext conf -state disabled
4578 set commentend [$ctext index "end - 1c"]
4580 init_flist "Comments"
4581 if {$cmitmode eq "tree"} {
4583 } elseif {[llength $olds] <= 1} {
4590 proc selfirstline {} {
4595 proc sellastline {} {
4598 set l [expr {$numcommits - 1}]
4602 proc selnextline {dir} {
4604 if {![info exists selectedline]} return
4605 set l [expr {$selectedline + $dir}]
4610 proc selnextpage {dir} {
4611 global canv linespc selectedline numcommits
4613 set lpp [expr {([winfo height $canv] - 2) / $linespc}]
4617 allcanvs yview scroll [expr {$dir * $lpp}] units
4619 if {![info exists selectedline]} return
4620 set l [expr {$selectedline + $dir * $lpp}]
4623 } elseif {$l >= $numcommits} {
4624 set l [expr $numcommits - 1]
4630 proc unselectline {} {
4631 global selectedline currentid
4633 catch {unset selectedline}
4634 catch {unset currentid}
4635 allcanvs delete secsel
4637 cancel_next_highlight
4640 proc reselectline {} {
4643 if {[info exists selectedline]} {
4644 selectline $selectedline 0
4648 proc addtohistory {cmd} {
4649 global history historyindex curview
4651 set elt [list $curview $cmd]
4652 if {$historyindex > 0
4653 && [lindex $history [expr {$historyindex - 1}]] == $elt} {
4657 if {$historyindex < [llength $history]} {
4658 set history [lreplace $history $historyindex end $elt]
4660 lappend history $elt
4663 if {$historyindex > 1} {
4664 .tf.bar.leftbut conf -state normal
4666 .tf.bar.leftbut conf -state disabled
4668 .tf.bar.rightbut conf -state disabled
4674 set view [lindex $elt 0]
4675 set cmd [lindex $elt 1]
4676 if {$curview != $view} {
4683 global history historyindex
4685 if {$historyindex > 1} {
4686 incr historyindex -1
4687 godo [lindex $history [expr {$historyindex - 1}]]
4688 .tf.bar.rightbut conf -state normal
4690 if {$historyindex <= 1} {
4691 .tf.bar.leftbut conf -state disabled
4696 global history historyindex
4698 if {$historyindex < [llength $history]} {
4699 set cmd [lindex $history $historyindex]
4702 .tf.bar.leftbut conf -state normal
4704 if {$historyindex >= [llength $history]} {
4705 .tf.bar.rightbut conf -state disabled
4710 global treefilelist treeidlist diffids diffmergeid treepending
4711 global nullid nullid2
4714 catch {unset diffmergeid}
4715 if {![info exists treefilelist($id)]} {
4716 if {![info exists treepending]} {
4717 if {$id eq $nullid} {
4718 set cmd [list | git ls-files]
4719 } elseif {$id eq $nullid2} {
4720 set cmd [list | git ls-files --stage -t]
4722 set cmd [list | git ls-tree -r $id]
4724 if {[catch {set gtf [open $cmd r]}]} {
4728 set treefilelist($id) {}
4729 set treeidlist($id) {}
4730 fconfigure $gtf -blocking 0
4731 filerun $gtf [list gettreeline $gtf $id]
4738 proc gettreeline {gtf id} {
4739 global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
4742 while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
4743 if {$diffids eq $nullid} {
4746 if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
4747 set i [string first "\t" $line]
4748 if {$i < 0} continue
4749 set sha1 [lindex $line 2]
4750 set fname [string range $line [expr {$i+1}] end]
4751 if {[string index $fname 0] eq "\""} {
4752 set fname [lindex $fname 0]
4754 lappend treeidlist($id) $sha1
4756 lappend treefilelist($id) $fname
4759 return [expr {$nl >= 1000? 2: 1}]
4763 if {$cmitmode ne "tree"} {
4764 if {![info exists diffmergeid]} {
4765 gettreediffs $diffids
4767 } elseif {$id ne $diffids} {
4776 global treefilelist treeidlist diffids nullid nullid2
4777 global ctext commentend
4779 set i [lsearch -exact $treefilelist($diffids) $f]
4781 puts "oops, $f not in list for id $diffids"
4784 if {$diffids eq $nullid} {
4785 if {[catch {set bf [open $f r]} err]} {
4786 puts "oops, can't read $f: $err"
4790 set blob [lindex $treeidlist($diffids) $i]
4791 if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
4792 puts "oops, error reading blob $blob: $err"
4796 fconfigure $bf -blocking 0
4797 filerun $bf [list getblobline $bf $diffids]
4798 $ctext config -state normal
4799 clear_ctext $commentend
4800 $ctext insert end "\n"
4801 $ctext insert end "$f\n" filesep
4802 $ctext config -state disabled
4803 $ctext yview $commentend
4806 proc getblobline {bf id} {
4807 global diffids cmitmode ctext
4809 if {$id ne $diffids || $cmitmode ne "tree"} {
4813 $ctext config -state normal
4815 while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
4816 $ctext insert end "$line\n"
4819 # delete last newline
4820 $ctext delete "end - 2c" "end - 1c"
4824 $ctext config -state disabled
4825 return [expr {$nl >= 1000? 2: 1}]
4828 proc mergediff {id l} {
4829 global diffmergeid diffopts mdifffd
4835 # this doesn't seem to actually affect anything...
4836 set env(GIT_DIFF_OPTS) $diffopts
4837 set cmd [concat | git diff-tree --no-commit-id --cc $id]
4838 if {[catch {set mdf [open $cmd r]} err]} {
4839 error_popup "Error getting merge diffs: $err"
4842 fconfigure $mdf -blocking 0
4843 set mdifffd($id) $mdf
4844 set np [llength [lindex $parentlist $l]]
4845 filerun $mdf [list getmergediffline $mdf $id $np]
4848 proc getmergediffline {mdf id np} {
4849 global diffmergeid ctext cflist mergemax
4850 global difffilestart mdifffd
4852 $ctext conf -state normal
4854 while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
4855 if {![info exists diffmergeid] || $id != $diffmergeid
4856 || $mdf != $mdifffd($id)} {
4860 if {[regexp {^diff --cc (.*)} $line match fname]} {
4861 # start of a new file
4862 $ctext insert end "\n"
4863 set here [$ctext index "end - 1c"]
4864 lappend difffilestart $here
4865 add_flist [list $fname]
4866 set l [expr {(78 - [string length $fname]) / 2}]
4867 set pad [string range "----------------------------------------" 1 $l]
4868 $ctext insert end "$pad $fname $pad\n" filesep
4869 } elseif {[regexp {^@@} $line]} {
4870 $ctext insert end "$line\n" hunksep
4871 } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
4874 # parse the prefix - one ' ', '-' or '+' for each parent
4879 for {set j 0} {$j < $np} {incr j} {
4880 set c [string range $line $j $j]
4883 } elseif {$c == "-"} {
4885 } elseif {$c == "+"} {
4894 if {!$isbad && $minuses ne {} && $pluses eq {}} {
4895 # line doesn't appear in result, parents in $minuses have the line
4896 set num [lindex $minuses 0]
4897 } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
4898 # line appears in result, parents in $pluses don't have the line
4899 lappend tags mresult
4900 set num [lindex $spaces 0]
4903 if {$num >= $mergemax} {
4908 $ctext insert end "$line\n" $tags
4911 $ctext conf -state disabled
4916 return [expr {$nr >= 1000? 2: 1}]
4919 proc startdiff {ids} {
4920 global treediffs diffids treepending diffmergeid nullid nullid2
4923 catch {unset diffmergeid}
4924 if {![info exists treediffs($ids)] ||
4925 [lsearch -exact $ids $nullid] >= 0 ||
4926 [lsearch -exact $ids $nullid2] >= 0} {
4927 if {![info exists treepending]} {
4935 proc addtocflist {ids} {
4936 global treediffs cflist
4937 add_flist $treediffs($ids)
4941 proc diffcmd {ids flags} {
4942 global nullid nullid2
4944 set i [lsearch -exact $ids $nullid]
4945 set j [lsearch -exact $ids $nullid2]
4947 if {[llength $ids] > 1 && $j < 0} {
4948 # comparing working directory with some specific revision
4949 set cmd [concat | git diff-index $flags]
4951 lappend cmd -R [lindex $ids 1]
4953 lappend cmd [lindex $ids 0]
4956 # comparing working directory with index
4957 set cmd [concat | git diff-files $flags]
4962 } elseif {$j >= 0} {
4963 set cmd [concat | git diff-index --cached $flags]
4964 if {[llength $ids] > 1} {
4965 # comparing index with specific revision
4967 lappend cmd -R [lindex $ids 1]
4969 lappend cmd [lindex $ids 0]
4972 # comparing index with HEAD
4976 set cmd [concat | git diff-tree -r $flags $ids]
4981 proc gettreediffs {ids} {
4982 global treediff treepending
4984 set treepending $ids
4986 if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
4987 fconfigure $gdtf -blocking 0
4988 filerun $gdtf [list gettreediffline $gdtf $ids]
4991 proc gettreediffline {gdtf ids} {
4992 global treediff treediffs treepending diffids diffmergeid
4996 while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
4997 set i [string first "\t" $line]
4999 set file [string range $line [expr {$i+1}] end]
5000 if {[string index $file 0] eq "\""} {
5001 set file [lindex $file 0]
5003 lappend treediff $file
5007 return [expr {$nr >= 1000? 2: 1}]
5010 set treediffs($ids) $treediff
5012 if {$cmitmode eq "tree"} {
5014 } elseif {$ids != $diffids} {
5015 if {![info exists diffmergeid]} {
5016 gettreediffs $diffids
5024 proc getblobdiffs {ids} {
5025 global diffopts blobdifffd diffids env
5026 global diffinhdr treediffs
5028 set env(GIT_DIFF_OPTS) $diffopts
5029 if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
5030 puts "error getting diffs: $err"
5034 fconfigure $bdf -blocking 0
5035 set blobdifffd($ids) $bdf
5036 filerun $bdf [list getblobdiffline $bdf $diffids]
5039 proc setinlist {var i val} {
5042 while {[llength [set $var]] < $i} {
5045 if {[llength [set $var]] == $i} {
5052 proc makediffhdr {fname ids} {
5053 global ctext curdiffstart treediffs
5055 set i [lsearch -exact $treediffs($ids) $fname]
5057 setinlist difffilestart $i $curdiffstart
5059 set l [expr {(78 - [string length $fname]) / 2}]
5060 set pad [string range "----------------------------------------" 1 $l]
5061 $ctext insert $curdiffstart "$pad $fname $pad" filesep
5064 proc getblobdiffline {bdf ids} {
5065 global diffids blobdifffd ctext curdiffstart
5066 global diffnexthead diffnextnote difffilestart
5067 global diffinhdr treediffs
5070 $ctext conf -state normal
5071 while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
5072 if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
5076 if {![string compare -length 11 "diff --git " $line]} {
5077 # trim off "diff --git "
5078 set line [string range $line 11 end]
5080 # start of a new file
5081 $ctext insert end "\n"
5082 set curdiffstart [$ctext index "end - 1c"]
5083 $ctext insert end "\n" filesep
5084 # If the name hasn't changed the length will be odd,
5085 # the middle char will be a space, and the two bits either
5086 # side will be a/name and b/name, or "a/name" and "b/name".
5087 # If the name has changed we'll get "rename from" and
5088 # "rename to" lines following this, and we'll use them
5089 # to get the filenames.
5090 # This complexity is necessary because spaces in the filename(s)
5091 # don't get escaped.
5092 set l [string length $line]
5093 set i [expr {$l / 2}]
5094 if {!(($l & 1) && [string index $line $i] eq " " &&
5095 [string range $line 2 [expr {$i - 1}]] eq \
5096 [string range $line [expr {$i + 3}] end])} {
5099 # unescape if quoted and chop off the a/ from the front
5100 if {[string index $line 0] eq "\""} {
5101 set fname [string range [lindex $line 0] 2 end]
5103 set fname [string range $line 2 [expr {$i - 1}]]
5105 makediffhdr $fname $ids
5107 } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
5108 $line match f1l f1c f2l f2c rest]} {
5109 $ctext insert end "$line\n" hunksep
5112 } elseif {$diffinhdr} {
5113 if {![string compare -length 12 "rename from " $line]} {
5114 set fname [string range $line 12 end]
5115 if {[string index $fname 0] eq "\""} {
5116 set fname [lindex $fname 0]
5118 set i [lsearch -exact $treediffs($ids) $fname]
5120 setinlist difffilestart $i $curdiffstart
5122 } elseif {![string compare -length 10 $line "rename to "]} {
5123 set fname [string range $line 10 end]
5124 if {[string index $fname 0] eq "\""} {
5125 set fname [lindex $fname 0]
5127 makediffhdr $fname $ids
5128 } elseif {[string compare -length 3 $line "---"] == 0} {
5131 } elseif {[string compare -length 3 $line "+++"] == 0} {
5135 $ctext insert end "$line\n" filesep
5138 set x [string range $line 0 0]
5139 if {$x == "-" || $x == "+"} {
5140 set tag [expr {$x == "+"}]
5141 $ctext insert end "$line\n" d$tag
5142 } elseif {$x == " "} {
5143 $ctext insert end "$line\n"
5145 # "\ No newline at end of file",
5146 # or something else we don't recognize
5147 $ctext insert end "$line\n" hunksep
5151 $ctext conf -state disabled
5156 return [expr {$nr >= 1000? 2: 1}]
5159 proc changediffdisp {} {
5160 global ctext diffelide
5162 $ctext tag conf d0 -elide [lindex $diffelide 0]
5163 $ctext tag conf d1 -elide [lindex $diffelide 1]
5167 global difffilestart ctext
5168 set prev [lindex $difffilestart 0]
5169 set here [$ctext index @0,0]
5170 foreach loc $difffilestart {
5171 if {[$ctext compare $loc >= $here]} {
5181 global difffilestart ctext
5182 set here [$ctext index @0,0]
5183 foreach loc $difffilestart {
5184 if {[$ctext compare $loc > $here]} {
5191 proc clear_ctext {{first 1.0}} {
5192 global ctext smarktop smarkbot
5194 set l [lindex [split $first .] 0]
5195 if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
5198 if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} {
5201 $ctext delete $first end
5204 proc incrsearch {name ix op} {
5205 global ctext searchstring searchdirn
5207 $ctext tag remove found 1.0 end
5208 if {[catch {$ctext index anchor}]} {
5209 # no anchor set, use start of selection, or of visible area
5210 set sel [$ctext tag ranges sel]
5212 $ctext mark set anchor [lindex $sel 0]
5213 } elseif {$searchdirn eq "-forwards"} {
5214 $ctext mark set anchor @0,0
5216 $ctext mark set anchor @0,[winfo height $ctext]
5219 if {$searchstring ne {}} {
5220 set here [$ctext search $searchdirn -- $searchstring anchor]
5229 global sstring ctext searchstring searchdirn
5232 $sstring icursor end
5233 set searchdirn -forwards
5234 if {$searchstring ne {}} {
5235 set sel [$ctext tag ranges sel]
5237 set start "[lindex $sel 0] + 1c"
5238 } elseif {[catch {set start [$ctext index anchor]}]} {
5241 set match [$ctext search -count mlen -- $searchstring $start]
5242 $ctext tag remove sel 1.0 end
5248 set mend "$match + $mlen c"
5249 $ctext tag add sel $match $mend
5250 $ctext mark unset anchor
5254 proc dosearchback {} {
5255 global sstring ctext searchstring searchdirn
5258 $sstring icursor end
5259 set searchdirn -backwards
5260 if {$searchstring ne {}} {
5261 set sel [$ctext tag ranges sel]
5263 set start [lindex $sel 0]
5264 } elseif {[catch {set start [$ctext index anchor]}]} {
5265 set start @0,[winfo height $ctext]
5267 set match [$ctext search -backwards -count ml -- $searchstring $start]
5268 $ctext tag remove sel 1.0 end
5274 set mend "$match + $ml c"
5275 $ctext tag add sel $match $mend
5276 $ctext mark unset anchor
5280 proc searchmark {first last} {
5281 global ctext searchstring
5285 set match [$ctext search -count mlen -- $searchstring $mend $last.end]
5286 if {$match eq {}} break
5287 set mend "$match + $mlen c"
5288 $ctext tag add found $match $mend
5292 proc searchmarkvisible {doall} {
5293 global ctext smarktop smarkbot
5295 set topline [lindex [split [$ctext index @0,0] .] 0]
5296 set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
5297 if {$doall || $botline < $smarktop || $topline > $smarkbot} {
5298 # no overlap with previous
5299 searchmark $topline $botline
5300 set smarktop $topline
5301 set smarkbot $botline
5303 if {$topline < $smarktop} {
5304 searchmark $topline [expr {$smarktop-1}]
5305 set smarktop $topline
5307 if {$botline > $smarkbot} {
5308 searchmark [expr {$smarkbot+1}] $botline
5309 set smarkbot $botline
5314 proc scrolltext {f0 f1} {
5317 .bleft.sb set $f0 $f1
5318 if {$searchstring ne {}} {
5324 global linespc charspc canvx0 canvy0 mainfont
5325 global xspc1 xspc2 lthickness
5327 set linespc [font metrics $mainfont -linespace]
5328 set charspc [font measure $mainfont "m"]
5329 set canvy0 [expr {int(3 + 0.5 * $linespc)}]
5330 set canvx0 [expr {int(3 + 0.5 * $linespc)}]
5331 set lthickness [expr {int($linespc / 9) + 1}]
5332 set xspc1(0) $linespc
5340 set ymax [lindex [$canv cget -scrollregion] 3]
5341 if {$ymax eq {} || $ymax == 0} return
5342 set span [$canv yview]
5345 allcanvs yview moveto [lindex $span 0]
5347 if {[info exists selectedline]} {
5348 selectline $selectedline 0
5349 allcanvs yview moveto [lindex $span 0]
5353 proc incrfont {inc} {
5354 global mainfont textfont ctext canv phase cflist
5355 global charspc tabstop
5356 global stopped entries
5358 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
5359 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
5361 $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
5362 $cflist conf -font $textfont
5363 $ctext tag conf filesep -font [concat $textfont bold]
5364 foreach e $entries {
5365 $e conf -font $mainfont
5367 if {$phase eq "getcommits"} {
5368 $canv itemconf textitems -font $mainfont
5374 global sha1entry sha1string
5375 if {[string length $sha1string] == 40} {
5376 $sha1entry delete 0 end
5380 proc sha1change {n1 n2 op} {
5381 global sha1string currentid sha1but
5382 if {$sha1string == {}
5383 || ([info exists currentid] && $sha1string == $currentid)} {
5388 if {[$sha1but cget -state] == $state} return
5389 if {$state == "normal"} {
5390 $sha1but conf -state normal -relief raised -text "Goto: "
5392 $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
5396 proc gotocommit {} {
5397 global sha1string currentid commitrow tagids headids
5398 global displayorder numcommits curview
5400 if {$sha1string == {}
5401 || ([info exists currentid] && $sha1string == $currentid)} return
5402 if {[info exists tagids($sha1string)]} {
5403 set id $tagids($sha1string)
5404 } elseif {[info exists headids($sha1string)]} {
5405 set id $headids($sha1string)
5407 set id [string tolower $sha1string]
5408 if {[regexp {^[0-9a-f]{4,39}$} $id]} {
5410 foreach i $displayorder {
5411 if {[string match $id* $i]} {
5415 if {$matches ne {}} {
5416 if {[llength $matches] > 1} {
5417 error_popup "Short SHA1 id $id is ambiguous"
5420 set id [lindex $matches 0]
5424 if {[info exists commitrow($curview,$id)]} {
5425 selectline $commitrow($curview,$id) 1
5428 if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
5433 error_popup "$type $sha1string is not known"
5436 proc lineenter {x y id} {
5437 global hoverx hovery hoverid hovertimer
5438 global commitinfo canv
5440 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5444 if {[info exists hovertimer]} {
5445 after cancel $hovertimer
5447 set hovertimer [after 500 linehover]
5451 proc linemotion {x y id} {
5452 global hoverx hovery hoverid hovertimer
5454 if {[info exists hoverid] && $id == $hoverid} {
5457 if {[info exists hovertimer]} {
5458 after cancel $hovertimer
5460 set hovertimer [after 500 linehover]
5464 proc lineleave {id} {
5465 global hoverid hovertimer canv
5467 if {[info exists hoverid] && $id == $hoverid} {
5469 if {[info exists hovertimer]} {
5470 after cancel $hovertimer
5478 global hoverx hovery hoverid hovertimer
5479 global canv linespc lthickness
5480 global commitinfo mainfont
5482 set text [lindex $commitinfo($hoverid) 0]
5483 set ymax [lindex [$canv cget -scrollregion] 3]
5484 if {$ymax == {}} return
5485 set yfrac [lindex [$canv yview] 0]
5486 set x [expr {$hoverx + 2 * $linespc}]
5487 set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
5488 set x0 [expr {$x - 2 * $lthickness}]
5489 set y0 [expr {$y - 2 * $lthickness}]
5490 set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
5491 set y1 [expr {$y + $linespc + 2 * $lthickness}]
5492 set t [$canv create rectangle $x0 $y0 $x1 $y1 \
5493 -fill \#ffff80 -outline black -width 1 -tags hover]
5495 set t [$canv create text $x $y -anchor nw -text $text -tags hover \
5500 proc clickisonarrow {id y} {
5503 set ranges [rowranges $id]
5504 set thresh [expr {2 * $lthickness + 6}]
5505 set n [expr {[llength $ranges] - 1}]
5506 for {set i 1} {$i < $n} {incr i} {
5507 set row [lindex $ranges $i]
5508 if {abs([yc $row] - $y) < $thresh} {
5515 proc arrowjump {id n y} {
5518 # 1 <-> 2, 3 <-> 4, etc...
5519 set n [expr {(($n - 1) ^ 1) + 1}]
5520 set row [lindex [rowranges $id] $n]
5522 set ymax [lindex [$canv cget -scrollregion] 3]
5523 if {$ymax eq {} || $ymax <= 0} return
5524 set view [$canv yview]
5525 set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
5526 set yfrac [expr {$yt / $ymax - $yspan / 2}]
5530 allcanvs yview moveto $yfrac
5533 proc lineclick {x y id isnew} {
5534 global ctext commitinfo children canv thickerline curview
5536 if {![info exists commitinfo($id)] && ![getcommit $id]} return
5541 # draw this line thicker than normal
5545 set ymax [lindex [$canv cget -scrollregion] 3]
5546 if {$ymax eq {}} return
5547 set yfrac [lindex [$canv yview] 0]
5548 set y [expr {$y + $yfrac * $ymax}]
5550 set dirn [clickisonarrow $id $y]
5552 arrowjump $id $dirn $y
5557 addtohistory [list lineclick $x $y $id 0]
5559 # fill the details pane with info about this line
5560 $ctext conf -state normal
5562 $ctext tag conf link -foreground blue -underline 1
5563 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5564 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5565 $ctext insert end "Parent:\t"
5566 $ctext insert end $id [list link link0]
5567 $ctext tag bind link0 <1> [list selbyid $id]
5568 set info $commitinfo($id)
5569 $ctext insert end "\n\t[lindex $info 0]\n"
5570 $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
5571 set date [formatdate [lindex $info 2]]
5572 $ctext insert end "\tDate:\t$date\n"
5573 set kids $children($curview,$id)
5575 $ctext insert end "\nChildren:"
5577 foreach child $kids {
5579 if {![info exists commitinfo($child)] && ![getcommit $child]} continue
5580 set info $commitinfo($child)
5581 $ctext insert end "\n\t"
5582 $ctext insert end $child [list link link$i]
5583 $ctext tag bind link$i <1> [list selbyid $child]
5584 $ctext insert end "\n\t[lindex $info 0]"
5585 $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
5586 set date [formatdate [lindex $info 2]]
5587 $ctext insert end "\n\tDate:\t$date\n"
5590 $ctext conf -state disabled
5594 proc normalline {} {
5596 if {[info exists thickerline]} {
5604 global commitrow curview
5605 if {[info exists commitrow($curview,$id)]} {
5606 selectline $commitrow($curview,$id) 1
5612 if {![info exists startmstime]} {
5613 set startmstime [clock clicks -milliseconds]
5615 return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
5618 proc rowmenu {x y id} {
5619 global rowctxmenu commitrow selectedline rowmenuid curview
5620 global nullid nullid2 fakerowmenu mainhead
5623 if {![info exists selectedline]
5624 || $commitrow($curview,$id) eq $selectedline} {
5629 if {$id ne $nullid && $id ne $nullid2} {
5630 set menu $rowctxmenu
5631 $menu entryconfigure 7 -label "Reset $mainhead branch to here"
5633 set menu $fakerowmenu
5635 $menu entryconfigure "Diff this*" -state $state
5636 $menu entryconfigure "Diff selected*" -state $state
5637 $menu entryconfigure "Make patch" -state $state
5638 tk_popup $menu $x $y
5641 proc diffvssel {dirn} {
5642 global rowmenuid selectedline displayorder
5644 if {![info exists selectedline]} return
5646 set oldid [lindex $displayorder $selectedline]
5647 set newid $rowmenuid
5649 set oldid $rowmenuid
5650 set newid [lindex $displayorder $selectedline]
5652 addtohistory [list doseldiff $oldid $newid]
5653 doseldiff $oldid $newid
5656 proc doseldiff {oldid newid} {
5660 $ctext conf -state normal
5663 $ctext insert end "From "
5664 $ctext tag conf link -foreground blue -underline 1
5665 $ctext tag bind link <Enter> { %W configure -cursor hand2 }
5666 $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
5667 $ctext tag bind link0 <1> [list selbyid $oldid]
5668 $ctext insert end $oldid [list link link0]
5669 $ctext insert end "\n "
5670 $ctext insert end [lindex $commitinfo($oldid) 0]
5671 $ctext insert end "\n\nTo "
5672 $ctext tag bind link1 <1> [list selbyid $newid]
5673 $ctext insert end $newid [list link link1]
5674 $ctext insert end "\n "
5675 $ctext insert end [lindex $commitinfo($newid) 0]
5676 $ctext insert end "\n"
5677 $ctext conf -state disabled
5678 $ctext tag remove found 1.0 end
5679 startdiff [list $oldid $newid]
5683 global rowmenuid currentid commitinfo patchtop patchnum
5685 if {![info exists currentid]} return
5686 set oldid $currentid
5687 set oldhead [lindex $commitinfo($oldid) 0]
5688 set newid $rowmenuid
5689 set newhead [lindex $commitinfo($newid) 0]
5692 catch {destroy $top}
5694 label $top.title -text "Generate patch"
5695 grid $top.title - -pady 10
5696 label $top.from -text "From:"
5697 entry $top.fromsha1 -width 40 -relief flat
5698 $top.fromsha1 insert 0 $oldid
5699 $top.fromsha1 conf -state readonly
5700 grid $top.from $top.fromsha1 -sticky w
5701 entry $top.fromhead -width 60 -relief flat
5702 $top.fromhead insert 0 $oldhead
5703 $top.fromhead conf -state readonly
5704 grid x $top.fromhead -sticky w
5705 label $top.to -text "To:"
5706 entry $top.tosha1 -width 40 -relief flat
5707 $top.tosha1 insert 0 $newid
5708 $top.tosha1 conf -state readonly
5709 grid $top.to $top.tosha1 -sticky w
5710 entry $top.tohead -width 60 -relief flat
5711 $top.tohead insert 0 $newhead
5712 $top.tohead conf -state readonly
5713 grid x $top.tohead -sticky w
5714 button $top.rev -text "Reverse" -command mkpatchrev -padx 5
5715 grid $top.rev x -pady 10
5716 label $top.flab -text "Output file:"
5717 entry $top.fname -width 60
5718 $top.fname insert 0 [file normalize "patch$patchnum.patch"]
5720 grid $top.flab $top.fname -sticky w
5722 button $top.buts.gen -text "Generate" -command mkpatchgo
5723 button $top.buts.can -text "Cancel" -command mkpatchcan
5724 grid $top.buts.gen $top.buts.can
5725 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5726 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5727 grid $top.buts - -pady 10 -sticky ew
5731 proc mkpatchrev {} {
5734 set oldid [$patchtop.fromsha1 get]
5735 set oldhead [$patchtop.fromhead get]
5736 set newid [$patchtop.tosha1 get]
5737 set newhead [$patchtop.tohead get]
5738 foreach e [list fromsha1 fromhead tosha1 tohead] \
5739 v [list $newid $newhead $oldid $oldhead] {
5740 $patchtop.$e conf -state normal
5741 $patchtop.$e delete 0 end
5742 $patchtop.$e insert 0 $v
5743 $patchtop.$e conf -state readonly
5748 global patchtop nullid nullid2
5750 set oldid [$patchtop.fromsha1 get]
5751 set newid [$patchtop.tosha1 get]
5752 set fname [$patchtop.fname get]
5753 set cmd [diffcmd [list $oldid $newid] -p]
5754 lappend cmd >$fname &
5755 if {[catch {eval exec $cmd} err]} {
5756 error_popup "Error creating patch: $err"
5758 catch {destroy $patchtop}
5762 proc mkpatchcan {} {
5765 catch {destroy $patchtop}
5770 global rowmenuid mktagtop commitinfo
5774 catch {destroy $top}
5776 label $top.title -text "Create tag"
5777 grid $top.title - -pady 10
5778 label $top.id -text "ID:"
5779 entry $top.sha1 -width 40 -relief flat
5780 $top.sha1 insert 0 $rowmenuid
5781 $top.sha1 conf -state readonly
5782 grid $top.id $top.sha1 -sticky w
5783 entry $top.head -width 60 -relief flat
5784 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5785 $top.head conf -state readonly
5786 grid x $top.head -sticky w
5787 label $top.tlab -text "Tag name:"
5788 entry $top.tag -width 60
5789 grid $top.tlab $top.tag -sticky w
5791 button $top.buts.gen -text "Create" -command mktaggo
5792 button $top.buts.can -text "Cancel" -command mktagcan
5793 grid $top.buts.gen $top.buts.can
5794 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5795 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5796 grid $top.buts - -pady 10 -sticky ew
5801 global mktagtop env tagids idtags
5803 set id [$mktagtop.sha1 get]
5804 set tag [$mktagtop.tag get]
5806 error_popup "No tag name specified"
5809 if {[info exists tagids($tag)]} {
5810 error_popup "Tag \"$tag\" already exists"
5815 set fname [file join $dir "refs/tags" $tag]
5816 set f [open $fname w]
5820 error_popup "Error creating tag: $err"
5824 set tagids($tag) $id
5825 lappend idtags($id) $tag
5830 proc redrawtags {id} {
5831 global canv linehtag commitrow idpos selectedline curview
5832 global mainfont canvxmax iddrawn
5834 if {![info exists commitrow($curview,$id)]} return
5835 if {![info exists iddrawn($id)]} return
5836 drawcommits $commitrow($curview,$id)
5837 $canv delete tag.$id
5838 set xt [eval drawtags $id $idpos($id)]
5839 $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
5840 set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
5841 set xr [expr {$xt + [font measure $mainfont $text]}]
5842 if {$xr > $canvxmax} {
5846 if {[info exists selectedline]
5847 && $selectedline == $commitrow($curview,$id)} {
5848 selectline $selectedline 0
5855 catch {destroy $mktagtop}
5864 proc writecommit {} {
5865 global rowmenuid wrcomtop commitinfo wrcomcmd
5867 set top .writecommit
5869 catch {destroy $top}
5871 label $top.title -text "Write commit to file"
5872 grid $top.title - -pady 10
5873 label $top.id -text "ID:"
5874 entry $top.sha1 -width 40 -relief flat
5875 $top.sha1 insert 0 $rowmenuid
5876 $top.sha1 conf -state readonly
5877 grid $top.id $top.sha1 -sticky w
5878 entry $top.head -width 60 -relief flat
5879 $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
5880 $top.head conf -state readonly
5881 grid x $top.head -sticky w
5882 label $top.clab -text "Command:"
5883 entry $top.cmd -width 60 -textvariable wrcomcmd
5884 grid $top.clab $top.cmd -sticky w -pady 10
5885 label $top.flab -text "Output file:"
5886 entry $top.fname -width 60
5887 $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
5888 grid $top.flab $top.fname -sticky w
5890 button $top.buts.gen -text "Write" -command wrcomgo
5891 button $top.buts.can -text "Cancel" -command wrcomcan
5892 grid $top.buts.gen $top.buts.can
5893 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5894 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5895 grid $top.buts - -pady 10 -sticky ew
5902 set id [$wrcomtop.sha1 get]
5903 set cmd "echo $id | [$wrcomtop.cmd get]"
5904 set fname [$wrcomtop.fname get]
5905 if {[catch {exec sh -c $cmd >$fname &} err]} {
5906 error_popup "Error writing commit: $err"
5908 catch {destroy $wrcomtop}
5915 catch {destroy $wrcomtop}
5920 global rowmenuid mkbrtop
5923 catch {destroy $top}
5925 label $top.title -text "Create new branch"
5926 grid $top.title - -pady 10
5927 label $top.id -text "ID:"
5928 entry $top.sha1 -width 40 -relief flat
5929 $top.sha1 insert 0 $rowmenuid
5930 $top.sha1 conf -state readonly
5931 grid $top.id $top.sha1 -sticky w
5932 label $top.nlab -text "Name:"
5933 entry $top.name -width 40
5934 grid $top.nlab $top.name -sticky w
5936 button $top.buts.go -text "Create" -command [list mkbrgo $top]
5937 button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
5938 grid $top.buts.go $top.buts.can
5939 grid columnconfigure $top.buts 0 -weight 1 -uniform a
5940 grid columnconfigure $top.buts 1 -weight 1 -uniform a
5941 grid $top.buts - -pady 10 -sticky ew
5946 global headids idheads
5948 set name [$top.name get]
5949 set id [$top.sha1 get]
5951 error_popup "Please specify a name for the new branch"
5954 catch {destroy $top}
5958 exec git branch $name $id
5963 set headids($name) $id
5964 lappend idheads($id) $name
5972 proc cherrypick {} {
5973 global rowmenuid curview commitrow
5976 set oldhead [exec git rev-parse HEAD]
5977 set dheads [descheads $rowmenuid]
5978 if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
5979 set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
5980 included in branch $mainhead -- really re-apply it?"]
5985 # Unfortunately git-cherry-pick writes stuff to stderr even when
5986 # no error occurs, and exec takes that as an indication of error...
5987 if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
5992 set newhead [exec git rev-parse HEAD]
5993 if {$newhead eq $oldhead} {
5995 error_popup "No changes committed"
5998 addnewchild $newhead $oldhead
5999 if {[info exists commitrow($curview,$oldhead)]} {
6000 insertrow $commitrow($curview,$oldhead) $newhead
6001 if {$mainhead ne {}} {
6002 movehead $newhead $mainhead
6003 movedhead $newhead $mainhead
6012 global mainheadid mainhead rowmenuid confirm_ok resettype
6013 global showlocalchanges
6016 set w ".confirmreset"
6019 wm title $w "Confirm reset"
6020 message $w.m -text \
6021 "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
6022 -justify center -aspect 1000
6023 pack $w.m -side top -fill x -padx 20 -pady 20
6024 frame $w.f -relief sunken -border 2
6025 message $w.f.rt -text "Reset type:" -aspect 1000
6026 grid $w.f.rt -sticky w
6028 radiobutton $w.f.soft -value soft -variable resettype -justify left \
6029 -text "Soft: Leave working tree and index untouched"
6030 grid $w.f.soft -sticky w
6031 radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
6032 -text "Mixed: Leave working tree untouched, reset index"
6033 grid $w.f.mixed -sticky w
6034 radiobutton $w.f.hard -value hard -variable resettype -justify left \
6035 -text "Hard: Reset working tree and index\n(discard ALL local changes)"
6036 grid $w.f.hard -sticky w
6037 pack $w.f -side top -fill x
6038 button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
6039 pack $w.ok -side left -fill x -padx 20 -pady 20
6040 button $w.cancel -text Cancel -command "destroy $w"
6041 pack $w.cancel -side right -fill x -padx 20 -pady 20
6042 bind $w <Visibility> "grab $w; focus $w"
6044 if {!$confirm_ok} return
6045 if {[catch {set fd [open \
6046 [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
6050 set w ".resetprogress"
6051 filerun $fd [list readresetstat $fd $w]
6054 wm title $w "Reset progress"
6055 message $w.m -text "Reset in progress, please wait..." \
6056 -justify center -aspect 1000
6057 pack $w.m -side top -fill x -padx 20 -pady 5
6058 canvas $w.c -width 150 -height 20 -bg white
6059 $w.c create rect 0 0 0 20 -fill green -tags rect
6060 pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
6065 proc readresetstat {fd w} {
6066 global mainhead mainheadid showlocalchanges
6068 if {[gets $fd line] >= 0} {
6069 if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
6070 set x [expr {($m * 150) / $n}]
6071 $w.c coords rect 0 0 $x 20
6077 if {[catch {close $fd} err]} {
6080 set oldhead $mainheadid
6081 set newhead [exec git rev-parse HEAD]
6082 if {$newhead ne $oldhead} {
6083 movehead $newhead $mainhead
6084 movedhead $newhead $mainhead
6085 set mainheadid $newhead
6089 if {$showlocalchanges} {
6095 # context menu for a head
6096 proc headmenu {x y id head} {
6097 global headmenuid headmenuhead headctxmenu mainhead
6100 set headmenuhead $head
6102 if {$head eq $mainhead} {
6105 $headctxmenu entryconfigure 0 -state $state
6106 $headctxmenu entryconfigure 1 -state $state
6107 tk_popup $headctxmenu $x $y
6111 global headmenuid headmenuhead mainhead headids
6112 global showlocalchanges mainheadid
6114 # check the tree is clean first??
6115 set oldmainhead $mainhead
6120 exec git checkout -q $headmenuhead
6126 set mainhead $headmenuhead
6127 set mainheadid $headmenuid
6128 if {[info exists headids($oldmainhead)]} {
6129 redrawtags $headids($oldmainhead)
6131 redrawtags $headmenuid
6133 if {$showlocalchanges} {
6139 global headmenuid headmenuhead mainhead
6140 global headids idheads
6142 set head $headmenuhead
6144 # this check shouldn't be needed any more...
6145 if {$head eq $mainhead} {
6146 error_popup "Cannot delete the currently checked-out branch"
6149 set dheads [descheads $id]
6150 if {$dheads eq $headids($head)} {
6151 # the stuff on this branch isn't on any other branch
6152 if {![confirm_popup "The commits on branch $head aren't on any other\
6153 branch.\nReally delete branch $head?"]} return
6157 if {[catch {exec git branch -D $head} err]} {
6162 removehead $id $head
6163 removedhead $id $head
6169 # Stuff for finding nearby tags
6170 proc getallcommits {} {
6171 global allcommits allids nbmp nextarc seeds
6181 # Called when the graph might have changed
6182 proc regetallcommits {} {
6183 global allcommits seeds
6185 set cmd [concat | git rev-list --all --parents]
6189 set fd [open $cmd r]
6190 fconfigure $fd -blocking 0
6193 filerun $fd [list getallclines $fd]
6196 # Since most commits have 1 parent and 1 child, we group strings of
6197 # such commits into "arcs" joining branch/merge points (BMPs), which
6198 # are commits that either don't have 1 parent or don't have 1 child.
6200 # arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
6201 # arcout(id) - outgoing arcs for BMP
6202 # arcids(a) - list of IDs on arc including end but not start
6203 # arcstart(a) - BMP ID at start of arc
6204 # arcend(a) - BMP ID at end of arc
6205 # growing(a) - arc a is still growing
6206 # arctags(a) - IDs out of arcids (excluding end) that have tags
6207 # archeads(a) - IDs out of arcids (excluding end) that have heads
6208 # The start of an arc is at the descendent end, so "incoming" means
6209 # coming from descendents, and "outgoing" means going towards ancestors.
6211 proc getallclines {fd} {
6212 global allids allparents allchildren idtags idheads nextarc nbmp
6213 global arcnos arcids arctags arcout arcend arcstart archeads growing
6214 global seeds allcommits
6217 while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
6218 set id [lindex $line 0]
6219 if {[info exists allparents($id)]} {
6224 set olds [lrange $line 1 end]
6225 set allparents($id) $olds
6226 if {![info exists allchildren($id)]} {
6227 set allchildren($id) {}
6232 if {[llength $olds] == 1 && [llength $a] == 1} {
6233 lappend arcids($a) $id
6234 if {[info exists idtags($id)]} {
6235 lappend arctags($a) $id
6237 if {[info exists idheads($id)]} {
6238 lappend archeads($a) $id
6240 if {[info exists allparents($olds)]} {
6241 # seen parent already
6242 if {![info exists arcout($olds)]} {
6245 lappend arcids($a) $olds
6246 set arcend($a) $olds
6249 lappend allchildren($olds) $id
6250 lappend arcnos($olds) $a
6255 foreach a $arcnos($id) {
6256 lappend arcids($a) $id
6263 lappend allchildren($p) $id
6264 set a [incr nextarc]
6265 set arcstart($a) $id
6272 if {[info exists allparents($p)]} {
6273 # seen it already, may need to make a new branch
6274 if {![info exists arcout($p)]} {
6277 lappend arcids($a) $p
6281 lappend arcnos($p) $a
6286 global cached_dheads cached_dtags cached_atags
6287 catch {unset cached_dheads}
6288 catch {unset cached_dtags}
6289 catch {unset cached_atags}
6292 return [expr {$nid >= 1000? 2: 1}]
6295 if {[incr allcommits -1] == 0} {
6302 proc recalcarc {a} {
6303 global arctags archeads arcids idtags idheads
6307 foreach id [lrange $arcids($a) 0 end-1] {
6308 if {[info exists idtags($id)]} {
6311 if {[info exists idheads($id)]} {
6316 set archeads($a) $ah
6320 global arcnos arcids nextarc nbmp arctags archeads idtags idheads
6321 global arcstart arcend arcout allparents growing
6324 if {[llength $a] != 1} {
6325 puts "oops splitarc called but [llength $a] arcs already"
6329 set i [lsearch -exact $arcids($a) $p]
6331 puts "oops splitarc $p not in arc $a"
6334 set na [incr nextarc]
6335 if {[info exists arcend($a)]} {
6336 set arcend($na) $arcend($a)
6338 set l [lindex $allparents([lindex $arcids($a) end]) 0]
6339 set j [lsearch -exact $arcnos($l) $a]
6340 set arcnos($l) [lreplace $arcnos($l) $j $j $na]
6342 set tail [lrange $arcids($a) [expr {$i+1}] end]
6343 set arcids($a) [lrange $arcids($a) 0 $i]
6345 set arcstart($na) $p
6347 set arcids($na) $tail
6348 if {[info exists growing($a)]} {
6355 if {[llength $arcnos($id)] == 1} {
6358 set j [lsearch -exact $arcnos($id) $a]
6359 set arcnos($id) [lreplace $arcnos($id) $j $j $na]
6363 # reconstruct tags and heads lists
6364 if {$arctags($a) ne {} || $archeads($a) ne {}} {
6369 set archeads($na) {}
6373 # Update things for a new commit added that is a child of one
6374 # existing commit. Used when cherry-picking.
6375 proc addnewchild {id p} {
6376 global allids allparents allchildren idtags nextarc nbmp
6377 global arcnos arcids arctags arcout arcend arcstart archeads growing
6381 set allparents($id) [list $p]
6382 set allchildren($id) {}
6386 lappend allchildren($p) $id
6387 set a [incr nextarc]
6388 set arcstart($a) $id
6391 set arcids($a) [list $p]
6393 if {![info exists arcout($p)]} {
6396 lappend arcnos($p) $a
6397 set arcout($id) [list $a]
6400 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
6401 # or 0 if neither is true.
6402 proc anc_or_desc {a b} {
6403 global arcout arcstart arcend arcnos cached_isanc
6405 if {$arcnos($a) eq $arcnos($b)} {
6406 # Both are on the same arc(s); either both are the same BMP,
6407 # or if one is not a BMP, the other is also not a BMP or is
6408 # the BMP at end of the arc (and it only has 1 incoming arc).
6409 # Or both can be BMPs with no incoming arcs.
6410 if {$a eq $b || $arcnos($a) eq {}} {
6413 # assert {[llength $arcnos($a)] == 1}
6414 set arc [lindex $arcnos($a) 0]
6415 set i [lsearch -exact $arcids($arc) $a]
6416 set j [lsearch -exact $arcids($arc) $b]
6417 if {$i < 0 || $i > $j} {
6424 if {![info exists arcout($a)]} {
6425 set arc [lindex $arcnos($a) 0]
6426 if {[info exists arcend($arc)]} {
6427 set aend $arcend($arc)
6431 set a $arcstart($arc)
6435 if {![info exists arcout($b)]} {
6436 set arc [lindex $arcnos($b) 0]
6437 if {[info exists arcend($arc)]} {
6438 set bend $arcend($arc)
6442 set b $arcstart($arc)
6452 if {[info exists cached_isanc($a,$bend)]} {
6453 if {$cached_isanc($a,$bend)} {
6457 if {[info exists cached_isanc($b,$aend)]} {
6458 if {$cached_isanc($b,$aend)} {
6461 if {[info exists cached_isanc($a,$bend)]} {
6466 set todo [list $a $b]
6469 for {set i 0} {$i < [llength $todo]} {incr i} {
6470 set x [lindex $todo $i]
6471 if {$anc($x) eq {}} {
6474 foreach arc $arcnos($x) {
6475 set xd $arcstart($arc)
6477 set cached_isanc($a,$bend) 1
6478 set cached_isanc($b,$aend) 0
6480 } elseif {$xd eq $aend} {
6481 set cached_isanc($b,$aend) 1
6482 set cached_isanc($a,$bend) 0
6485 if {![info exists anc($xd)]} {
6486 set anc($xd) $anc($x)
6488 } elseif {$anc($xd) ne $anc($x)} {
6493 set cached_isanc($a,$bend) 0
6494 set cached_isanc($b,$aend) 0
6498 # This identifies whether $desc has an ancestor that is
6499 # a growing tip of the graph and which is not an ancestor of $anc
6500 # and returns 0 if so and 1 if not.
6501 # If we subsequently discover a tag on such a growing tip, and that
6502 # turns out to be a descendent of $anc (which it could, since we
6503 # don't necessarily see children before parents), then $desc
6504 # isn't a good choice to display as a descendent tag of
6505 # $anc (since it is the descendent of another tag which is
6506 # a descendent of $anc). Similarly, $anc isn't a good choice to
6507 # display as a ancestor tag of $desc.
6509 proc is_certain {desc anc} {
6510 global arcnos arcout arcstart arcend growing problems
6513 if {[llength $arcnos($anc)] == 1} {
6514 # tags on the same arc are certain
6515 if {$arcnos($desc) eq $arcnos($anc)} {
6518 if {![info exists arcout($anc)]} {
6519 # if $anc is partway along an arc, use the start of the arc instead
6520 set a [lindex $arcnos($anc) 0]
6521 set anc $arcstart($a)
6524 if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
6527 set a [lindex $arcnos($desc) 0]
6533 set anclist [list $x]
6537 for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
6538 set x [lindex $anclist $i]
6543 foreach a $arcout($x) {
6544 if {[info exists growing($a)]} {
6545 if {![info exists growanc($x)] && $dl($x)} {
6551 if {[info exists dl($y)]} {
6555 if {![info exists done($y)]} {
6558 if {[info exists growanc($x)]} {
6562 for {set k 0} {$k < [llength $xl]} {incr k} {
6563 set z [lindex $xl $k]
6564 foreach c $arcout($z) {
6565 if {[info exists arcend($c)]} {
6567 if {[info exists dl($v)] && $dl($v)} {
6569 if {![info exists done($v)]} {
6572 if {[info exists growanc($v)]} {
6582 } elseif {$y eq $anc || !$dl($x)} {
6593 foreach x [array names growanc] {
6602 proc validate_arctags {a} {
6603 global arctags idtags
6607 foreach id $arctags($a) {
6609 if {![info exists idtags($id)]} {
6610 set na [lreplace $na $i $i]
6617 proc validate_archeads {a} {
6618 global archeads idheads
6621 set na $archeads($a)
6622 foreach id $archeads($a) {
6624 if {![info exists idheads($id)]} {
6625 set na [lreplace $na $i $i]
6629 set archeads($a) $na
6632 # Return the list of IDs that have tags that are descendents of id,
6633 # ignoring IDs that are descendents of IDs already reported.
6634 proc desctags {id} {
6635 global arcnos arcstart arcids arctags idtags allparents
6636 global growing cached_dtags
6638 if {![info exists allparents($id)]} {
6641 set t1 [clock clicks -milliseconds]
6643 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6644 # part-way along an arc; check that arc first
6645 set a [lindex $arcnos($id) 0]
6646 if {$arctags($a) ne {}} {
6648 set i [lsearch -exact $arcids($a) $id]
6650 foreach t $arctags($a) {
6651 set j [lsearch -exact $arcids($a) $t]
6659 set id $arcstart($a)
6660 if {[info exists idtags($id)]} {
6664 if {[info exists cached_dtags($id)]} {
6665 return $cached_dtags($id)
6672 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6673 set id [lindex $todo $i]
6675 set ta [info exists hastaggedancestor($id)]
6679 # ignore tags on starting node
6680 if {!$ta && $i > 0} {
6681 if {[info exists idtags($id)]} {
6684 } elseif {[info exists cached_dtags($id)]} {
6685 set tagloc($id) $cached_dtags($id)
6689 foreach a $arcnos($id) {
6691 if {!$ta && $arctags($a) ne {}} {
6693 if {$arctags($a) ne {}} {
6694 lappend tagloc($id) [lindex $arctags($a) end]
6697 if {$ta || $arctags($a) ne {}} {
6698 set tomark [list $d]
6699 for {set j 0} {$j < [llength $tomark]} {incr j} {
6700 set dd [lindex $tomark $j]
6701 if {![info exists hastaggedancestor($dd)]} {
6702 if {[info exists done($dd)]} {
6703 foreach b $arcnos($dd) {
6704 lappend tomark $arcstart($b)
6706 if {[info exists tagloc($dd)]} {
6709 } elseif {[info exists queued($dd)]} {
6712 set hastaggedancestor($dd) 1
6716 if {![info exists queued($d)]} {
6719 if {![info exists hastaggedancestor($d)]} {
6726 foreach id [array names tagloc] {
6727 if {![info exists hastaggedancestor($id)]} {
6728 foreach t $tagloc($id) {
6729 if {[lsearch -exact $tags $t] < 0} {
6735 set t2 [clock clicks -milliseconds]
6738 # remove tags that are descendents of other tags
6739 for {set i 0} {$i < [llength $tags]} {incr i} {
6740 set a [lindex $tags $i]
6741 for {set j 0} {$j < $i} {incr j} {
6742 set b [lindex $tags $j]
6743 set r [anc_or_desc $a $b]
6745 set tags [lreplace $tags $j $j]
6748 } elseif {$r == -1} {
6749 set tags [lreplace $tags $i $i]
6756 if {[array names growing] ne {}} {
6757 # graph isn't finished, need to check if any tag could get
6758 # eclipsed by another tag coming later. Simply ignore any
6759 # tags that could later get eclipsed.
6762 if {[is_certain $t $origid]} {
6766 if {$tags eq $ctags} {
6767 set cached_dtags($origid) $tags
6772 set cached_dtags($origid) $tags
6774 set t3 [clock clicks -milliseconds]
6775 if {0 && $t3 - $t1 >= 100} {
6776 puts "iterating descendents ($loopix/[llength $todo] nodes) took\
6777 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6783 global arcnos arcids arcout arcend arctags idtags allparents
6784 global growing cached_atags
6786 if {![info exists allparents($id)]} {
6789 set t1 [clock clicks -milliseconds]
6791 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6792 # part-way along an arc; check that arc first
6793 set a [lindex $arcnos($id) 0]
6794 if {$arctags($a) ne {}} {
6796 set i [lsearch -exact $arcids($a) $id]
6797 foreach t $arctags($a) {
6798 set j [lsearch -exact $arcids($a) $t]
6804 if {![info exists arcend($a)]} {
6808 if {[info exists idtags($id)]} {
6812 if {[info exists cached_atags($id)]} {
6813 return $cached_atags($id)
6821 for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
6822 set id [lindex $todo $i]
6824 set td [info exists hastaggeddescendent($id)]
6828 # ignore tags on starting node
6829 if {!$td && $i > 0} {
6830 if {[info exists idtags($id)]} {
6833 } elseif {[info exists cached_atags($id)]} {
6834 set tagloc($id) $cached_atags($id)
6838 foreach a $arcout($id) {
6839 if {!$td && $arctags($a) ne {}} {
6841 if {$arctags($a) ne {}} {
6842 lappend tagloc($id) [lindex $arctags($a) 0]
6845 if {![info exists arcend($a)]} continue
6847 if {$td || $arctags($a) ne {}} {
6848 set tomark [list $d]
6849 for {set j 0} {$j < [llength $tomark]} {incr j} {
6850 set dd [lindex $tomark $j]
6851 if {![info exists hastaggeddescendent($dd)]} {
6852 if {[info exists done($dd)]} {
6853 foreach b $arcout($dd) {
6854 if {[info exists arcend($b)]} {
6855 lappend tomark $arcend($b)
6858 if {[info exists tagloc($dd)]} {
6861 } elseif {[info exists queued($dd)]} {
6864 set hastaggeddescendent($dd) 1
6868 if {![info exists queued($d)]} {
6871 if {![info exists hastaggeddescendent($d)]} {
6877 set t2 [clock clicks -milliseconds]
6880 foreach id [array names tagloc] {
6881 if {![info exists hastaggeddescendent($id)]} {
6882 foreach t $tagloc($id) {
6883 if {[lsearch -exact $tags $t] < 0} {
6890 # remove tags that are ancestors of other tags
6891 for {set i 0} {$i < [llength $tags]} {incr i} {
6892 set a [lindex $tags $i]
6893 for {set j 0} {$j < $i} {incr j} {
6894 set b [lindex $tags $j]
6895 set r [anc_or_desc $a $b]
6897 set tags [lreplace $tags $j $j]
6900 } elseif {$r == 1} {
6901 set tags [lreplace $tags $i $i]
6908 if {[array names growing] ne {}} {
6909 # graph isn't finished, need to check if any tag could get
6910 # eclipsed by another tag coming later. Simply ignore any
6911 # tags that could later get eclipsed.
6914 if {[is_certain $origid $t]} {
6918 if {$tags eq $ctags} {
6919 set cached_atags($origid) $tags
6924 set cached_atags($origid) $tags
6926 set t3 [clock clicks -milliseconds]
6927 if {0 && $t3 - $t1 >= 100} {
6928 puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
6929 [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
6934 # Return the list of IDs that have heads that are descendents of id,
6935 # including id itself if it has a head.
6936 proc descheads {id} {
6937 global arcnos arcstart arcids archeads idheads cached_dheads
6940 if {![info exists allparents($id)]} {
6944 if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
6945 # part-way along an arc; check it first
6946 set a [lindex $arcnos($id) 0]
6947 if {$archeads($a) ne {}} {
6948 validate_archeads $a
6949 set i [lsearch -exact $arcids($a) $id]
6950 foreach t $archeads($a) {
6951 set j [lsearch -exact $arcids($a) $t]
6956 set id $arcstart($a)
6962 for {set i 0} {$i < [llength $todo]} {incr i} {
6963 set id [lindex $todo $i]
6964 if {[info exists cached_dheads($id)]} {
6965 set ret [concat $ret $cached_dheads($id)]
6967 if {[info exists idheads($id)]} {
6970 foreach a $arcnos($id) {
6971 if {$archeads($a) ne {}} {
6972 validate_archeads $a
6973 if {$archeads($a) ne {}} {
6974 set ret [concat $ret $archeads($a)]
6978 if {![info exists seen($d)]} {
6985 set ret [lsort -unique $ret]
6986 set cached_dheads($origid) $ret
6987 return [concat $ret $aret]
6990 proc addedtag {id} {
6991 global arcnos arcout cached_dtags cached_atags
6993 if {![info exists arcnos($id)]} return
6994 if {![info exists arcout($id)]} {
6995 recalcarc [lindex $arcnos($id) 0]
6997 catch {unset cached_dtags}
6998 catch {unset cached_atags}
7001 proc addedhead {hid head} {
7002 global arcnos arcout cached_dheads
7004 if {![info exists arcnos($hid)]} return
7005 if {![info exists arcout($hid)]} {
7006 recalcarc [lindex $arcnos($hid) 0]
7008 catch {unset cached_dheads}
7011 proc removedhead {hid head} {
7012 global cached_dheads
7014 catch {unset cached_dheads}
7017 proc movedhead {hid head} {
7018 global arcnos arcout cached_dheads
7020 if {![info exists arcnos($hid)]} return
7021 if {![info exists arcout($hid)]} {
7022 recalcarc [lindex $arcnos($hid) 0]
7024 catch {unset cached_dheads}
7027 proc changedrefs {} {
7028 global cached_dheads cached_dtags cached_atags
7029 global arctags archeads arcnos arcout idheads idtags
7031 foreach id [concat [array names idheads] [array names idtags]] {
7032 if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
7033 set a [lindex $arcnos($id) 0]
7034 if {![info exists donearc($a)]} {
7040 catch {unset cached_dtags}
7041 catch {unset cached_atags}
7042 catch {unset cached_dheads}
7045 proc rereadrefs {} {
7046 global idtags idheads idotherrefs mainhead
7048 set refids [concat [array names idtags] \
7049 [array names idheads] [array names idotherrefs]]
7050 foreach id $refids {
7051 if {![info exists ref($id)]} {
7052 set ref($id) [listrefs $id]
7055 set oldmainhead $mainhead
7058 set refids [lsort -unique [concat $refids [array names idtags] \
7059 [array names idheads] [array names idotherrefs]]]
7060 foreach id $refids {
7061 set v [listrefs $id]
7062 if {![info exists ref($id)] || $ref($id) != $v ||
7063 ($id eq $oldmainhead && $id ne $mainhead) ||
7064 ($id eq $mainhead && $id ne $oldmainhead)} {
7070 proc listrefs {id} {
7071 global idtags idheads idotherrefs
7074 if {[info exists idtags($id)]} {
7078 if {[info exists idheads($id)]} {
7082 if {[info exists idotherrefs($id)]} {
7083 set z $idotherrefs($id)
7085 return [list $x $y $z]
7088 proc showtag {tag isnew} {
7089 global ctext tagcontents tagids linknum tagobjid
7092 addtohistory [list showtag $tag 0]
7094 $ctext conf -state normal
7097 if {![info exists tagcontents($tag)]} {
7099 set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
7102 if {[info exists tagcontents($tag)]} {
7103 set text $tagcontents($tag)
7105 set text "Tag: $tag\nId: $tagids($tag)"
7107 appendwithlinks $text {}
7108 $ctext conf -state disabled
7120 global maxwidth maxgraphpct diffopts
7121 global oldprefs prefstop showneartags showlocalchanges
7122 global bgcolor fgcolor ctext diffcolors selectbgcolor
7123 global uifont tabstop
7127 if {[winfo exists $top]} {
7131 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7132 set oldprefs($v) [set $v]
7135 wm title $top "Gitk preferences"
7136 label $top.ldisp -text "Commit list display options"
7137 $top.ldisp configure -font $uifont
7138 grid $top.ldisp - -sticky w -pady 10
7139 label $top.spacer -text " "
7140 label $top.maxwidthl -text "Maximum graph width (lines)" \
7142 spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
7143 grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
7144 label $top.maxpctl -text "Maximum graph width (% of pane)" \
7146 spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
7147 grid x $top.maxpctl $top.maxpct -sticky w
7148 frame $top.showlocal
7149 label $top.showlocal.l -text "Show local changes" -font optionfont
7150 checkbutton $top.showlocal.b -variable showlocalchanges
7151 pack $top.showlocal.b $top.showlocal.l -side left
7152 grid x $top.showlocal -sticky w
7154 label $top.ddisp -text "Diff display options"
7155 $top.ddisp configure -font $uifont
7156 grid $top.ddisp - -sticky w -pady 10
7157 label $top.diffoptl -text "Options for diff program" \
7159 entry $top.diffopt -width 20 -textvariable diffopts
7160 grid x $top.diffoptl $top.diffopt -sticky w
7162 label $top.ntag.l -text "Display nearby tags" -font optionfont
7163 checkbutton $top.ntag.b -variable showneartags
7164 pack $top.ntag.b $top.ntag.l -side left
7165 grid x $top.ntag -sticky w
7166 label $top.tabstopl -text "tabstop" -font optionfont
7167 spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
7168 grid x $top.tabstopl $top.tabstop -sticky w
7170 label $top.cdisp -text "Colors: press to choose"
7171 $top.cdisp configure -font $uifont
7172 grid $top.cdisp - -sticky w -pady 10
7173 label $top.bg -padx 40 -relief sunk -background $bgcolor
7174 button $top.bgbut -text "Background" -font optionfont \
7175 -command [list choosecolor bgcolor 0 $top.bg background setbg]
7176 grid x $top.bgbut $top.bg -sticky w
7177 label $top.fg -padx 40 -relief sunk -background $fgcolor
7178 button $top.fgbut -text "Foreground" -font optionfont \
7179 -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
7180 grid x $top.fgbut $top.fg -sticky w
7181 label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
7182 button $top.diffoldbut -text "Diff: old lines" -font optionfont \
7183 -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
7184 [list $ctext tag conf d0 -foreground]]
7185 grid x $top.diffoldbut $top.diffold -sticky w
7186 label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
7187 button $top.diffnewbut -text "Diff: new lines" -font optionfont \
7188 -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
7189 [list $ctext tag conf d1 -foreground]]
7190 grid x $top.diffnewbut $top.diffnew -sticky w
7191 label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
7192 button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
7193 -command [list choosecolor diffcolors 2 $top.hunksep \
7194 "diff hunk header" \
7195 [list $ctext tag conf hunksep -foreground]]
7196 grid x $top.hunksepbut $top.hunksep -sticky w
7197 label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
7198 button $top.selbgbut -text "Select bg" -font optionfont \
7199 -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
7200 grid x $top.selbgbut $top.selbgsep -sticky w
7203 button $top.buts.ok -text "OK" -command prefsok -default active
7204 $top.buts.ok configure -font $uifont
7205 button $top.buts.can -text "Cancel" -command prefscan -default normal
7206 $top.buts.can configure -font $uifont
7207 grid $top.buts.ok $top.buts.can
7208 grid columnconfigure $top.buts 0 -weight 1 -uniform a
7209 grid columnconfigure $top.buts 1 -weight 1 -uniform a
7210 grid $top.buts - - -pady 10 -sticky ew
7211 bind $top <Visibility> "focus $top.buts.ok"
7214 proc choosecolor {v vi w x cmd} {
7217 set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
7218 -title "Gitk: choose color for $x"]
7219 if {$c eq {}} return
7220 $w conf -background $c
7226 global bglist cflist
7228 $w configure -selectbackground $c
7230 $cflist tag configure highlight \
7231 -background [$cflist cget -selectbackground]
7232 allcanvs itemconf secsel -fill $c
7239 $w conf -background $c
7247 $w conf -foreground $c
7249 allcanvs itemconf text -fill $c
7250 $canv itemconf circle -outline $c
7254 global maxwidth maxgraphpct diffopts
7255 global oldprefs prefstop showneartags showlocalchanges
7257 foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
7258 set $v $oldprefs($v)
7260 catch {destroy $prefstop}
7265 global maxwidth maxgraphpct
7266 global oldprefs prefstop showneartags showlocalchanges
7267 global charspc ctext tabstop
7269 catch {destroy $prefstop}
7271 $ctext configure -tabs "[expr {$tabstop * $charspc}]"
7272 if {$showlocalchanges != $oldprefs(showlocalchanges)} {
7273 if {$showlocalchanges} {
7279 if {$maxwidth != $oldprefs(maxwidth)
7280 || $maxgraphpct != $oldprefs(maxgraphpct)} {
7282 } elseif {$showneartags != $oldprefs(showneartags)} {
7287 proc formatdate {d} {
7289 set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
7294 # This list of encoding names and aliases is distilled from
7295 # http://www.iana.org/assignments/character-sets.
7296 # Not all of them are supported by Tcl.
7297 set encoding_aliases {
7298 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
7299 ISO646-US US-ASCII us IBM367 cp367 csASCII }
7300 { ISO-10646-UTF-1 csISO10646UTF1 }
7301 { ISO_646.basic:1983 ref csISO646basic1983 }
7302 { INVARIANT csINVARIANT }
7303 { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
7304 { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
7305 { NATS-SEFI iso-ir-8-1 csNATSSEFI }
7306 { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
7307 { NATS-DANO iso-ir-9-1 csNATSDANO }
7308 { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
7309 { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
7310 { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
7311 { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
7312 { ISO-2022-KR csISO2022KR }
7314 { ISO-2022-JP csISO2022JP }
7315 { ISO-2022-JP-2 csISO2022JP2 }
7316 { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
7318 { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
7319 { IT iso-ir-15 ISO646-IT csISO15Italian }
7320 { PT iso-ir-16 ISO646-PT csISO16Portuguese }
7321 { ES iso-ir-17 ISO646-ES csISO17Spanish }
7322 { greek7-old iso-ir-18 csISO18Greek7Old }
7323 { latin-greek iso-ir-19 csISO19LatinGreek }
7324 { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
7325 { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
7326 { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
7327 { ISO_5427 iso-ir-37 csISO5427Cyrillic }
7328 { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
7329 { BS_viewdata iso-ir-47 csISO47BSViewdata }
7330 { INIS iso-ir-49 csISO49INIS }
7331 { INIS-8 iso-ir-50 csISO50INIS8 }
7332 { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
7333 { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
7334 { ISO_5428:1980 iso-ir-55 csISO5428Greek }
7335 { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
7336 { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
7337 { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
7339 { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
7340 { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
7341 { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
7342 { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
7343 { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
7344 { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
7345 { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
7346 { greek7 iso-ir-88 csISO88Greek7 }
7347 { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
7348 { iso-ir-90 csISO90 }
7349 { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
7350 { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
7351 csISO92JISC62991984b }
7352 { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
7353 { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
7354 { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
7355 csISO95JIS62291984handadd }
7356 { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
7357 { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
7358 { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
7359 { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
7361 { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
7362 { T.61-7bit iso-ir-102 csISO102T617bit }
7363 { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
7364 { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
7365 { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
7366 { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
7367 { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
7368 { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
7369 { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
7370 { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
7371 arabic csISOLatinArabic }
7372 { ISO_8859-6-E csISO88596E ISO-8859-6-E }
7373 { ISO_8859-6-I csISO88596I ISO-8859-6-I }
7374 { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
7375 greek greek8 csISOLatinGreek }
7376 { T.101-G2 iso-ir-128 csISO128T101G2 }
7377 { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
7379 { ISO_8859-8-E csISO88598E ISO-8859-8-E }
7380 { ISO_8859-8-I csISO88598I ISO-8859-8-I }
7381 { CSN_369103 iso-ir-139 csISO139CSN369103 }
7382 { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
7383 { ISO_6937-2-add iso-ir-142 csISOTextComm }
7384 { IEC_P27-1 iso-ir-143 csISO143IECP271 }
7385 { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
7386 csISOLatinCyrillic }
7387 { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
7388 { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
7389 { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
7390 { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
7391 { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
7392 { ISO_6937-2-25 iso-ir-152 csISO6937Add }
7393 { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
7394 { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
7395 { ISO_10367-box iso-ir-155 csISO10367Box }
7396 { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
7397 { latin-lap lap iso-ir-158 csISO158Lap }
7398 { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
7399 { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
7402 { JIS_X0201 X0201 csHalfWidthKatakana }
7403 { KSC5636 ISO646-KR csKSC5636 }
7404 { ISO-10646-UCS-2 csUnicode }
7405 { ISO-10646-UCS-4 csUCS4 }
7406 { DEC-MCS dec csDECMCS }
7407 { hp-roman8 roman8 r8 csHPRoman8 }
7408 { macintosh mac csMacintosh }
7409 { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
7411 { IBM038 EBCDIC-INT cp038 csIBM038 }
7412 { IBM273 CP273 csIBM273 }
7413 { IBM274 EBCDIC-BE CP274 csIBM274 }
7414 { IBM275 EBCDIC-BR cp275 csIBM275 }
7415 { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
7416 { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
7417 { IBM280 CP280 ebcdic-cp-it csIBM280 }
7418 { IBM281 EBCDIC-JP-E cp281 csIBM281 }
7419 { IBM284 CP284 ebcdic-cp-es csIBM284 }
7420 { IBM285 CP285 ebcdic-cp-gb csIBM285 }
7421 { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
7422 { IBM297 cp297 ebcdic-cp-fr csIBM297 }
7423 { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
7424 { IBM423 cp423 ebcdic-cp-gr csIBM423 }
7425 { IBM424 cp424 ebcdic-cp-he csIBM424 }
7426 { IBM437 cp437 437 csPC8CodePage437 }
7427 { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
7428 { IBM775 cp775 csPC775Baltic }
7429 { IBM850 cp850 850 csPC850Multilingual }
7430 { IBM851 cp851 851 csIBM851 }
7431 { IBM852 cp852 852 csPCp852 }
7432 { IBM855 cp855 855 csIBM855 }
7433 { IBM857 cp857 857 csIBM857 }
7434 { IBM860 cp860 860 csIBM860 }
7435 { IBM861 cp861 861 cp-is csIBM861 }
7436 { IBM862 cp862 862 csPC862LatinHebrew }
7437 { IBM863 cp863 863 csIBM863 }
7438 { IBM864 cp864 csIBM864 }
7439 { IBM865 cp865 865 csIBM865 }
7440 { IBM866 cp866 866 csIBM866 }
7441 { IBM868 CP868 cp-ar csIBM868 }
7442 { IBM869 cp869 869 cp-gr csIBM869 }
7443 { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
7444 { IBM871 CP871 ebcdic-cp-is csIBM871 }
7445 { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
7446 { IBM891 cp891 csIBM891 }
7447 { IBM903 cp903 csIBM903 }
7448 { IBM904 cp904 904 csIBBM904 }
7449 { IBM905 CP905 ebcdic-cp-tr csIBM905 }
7450 { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
7451 { IBM1026 CP1026 csIBM1026 }
7452 { EBCDIC-AT-DE csIBMEBCDICATDE }
7453 { EBCDIC-AT-DE-A csEBCDICATDEA }
7454 { EBCDIC-CA-FR csEBCDICCAFR }
7455 { EBCDIC-DK-NO csEBCDICDKNO }
7456 { EBCDIC-DK-NO-A csEBCDICDKNOA }
7457 { EBCDIC-FI-SE csEBCDICFISE }
7458 { EBCDIC-FI-SE-A csEBCDICFISEA }
7459 { EBCDIC-FR csEBCDICFR }
7460 { EBCDIC-IT csEBCDICIT }
7461 { EBCDIC-PT csEBCDICPT }
7462 { EBCDIC-ES csEBCDICES }
7463 { EBCDIC-ES-A csEBCDICESA }
7464 { EBCDIC-ES-S csEBCDICESS }
7465 { EBCDIC-UK csEBCDICUK }
7466 { EBCDIC-US csEBCDICUS }
7467 { UNKNOWN-8BIT csUnknown8BiT }
7468 { MNEMONIC csMnemonic }
7473 { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
7474 { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
7475 { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
7476 { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
7477 { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
7478 { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
7479 { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
7480 { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
7481 { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
7482 { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
7483 { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
7484 { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
7485 { IBM1047 IBM-1047 }
7486 { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
7487 { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
7488 { UNICODE-1-1 csUnicode11 }
7491 { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
7492 { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
7494 { ISO-8859-15 ISO_8859-15 Latin-9 }
7495 { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
7496 { GBK CP936 MS936 windows-936 }
7497 { JIS_Encoding csJISEncoding }
7498 { Shift_JIS MS_Kanji csShiftJIS }
7499 { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
7501 { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
7502 { ISO-10646-UCS-Basic csUnicodeASCII }
7503 { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
7504 { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
7505 { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
7506 { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
7507 { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
7508 { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
7509 { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
7510 { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
7511 { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
7512 { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
7513 { Adobe-Standard-Encoding csAdobeStandardEncoding }
7514 { Ventura-US csVenturaUS }
7515 { Ventura-International csVenturaInternational }
7516 { PC8-Danish-Norwegian csPC8DanishNorwegian }
7517 { PC8-Turkish csPC8Turkish }
7518 { IBM-Symbols csIBMSymbols }
7519 { IBM-Thai csIBMThai }
7520 { HP-Legal csHPLegal }
7521 { HP-Pi-font csHPPiFont }
7522 { HP-Math8 csHPMath8 }
7523 { Adobe-Symbol-Encoding csHPPSMath }
7524 { HP-DeskTop csHPDesktop }
7525 { Ventura-Math csVenturaMath }
7526 { Microsoft-Publishing csMicrosoftPublishing }
7527 { Windows-31J csWindows31J }
7532 proc tcl_encoding {enc} {
7533 global encoding_aliases
7534 set names [encoding names]
7535 set lcnames [string tolower $names]
7536 set enc [string tolower $enc]
7537 set i [lsearch -exact $lcnames $enc]
7539 # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
7540 if {[regsub {^iso[-_]} $enc iso encx]} {
7541 set i [lsearch -exact $lcnames $encx]
7545 foreach l $encoding_aliases {
7546 set ll [string tolower $l]
7547 if {[lsearch -exact $ll $enc] < 0} continue
7548 # look through the aliases for one that tcl knows about
7550 set i [lsearch -exact $lcnames $e]
7552 if {[regsub {^iso[-_]} $e iso ex]} {
7553 set i [lsearch -exact $lcnames $ex]
7562 return [lindex $names $i]
7569 set diffopts "-U 5 -p"
7570 set wrcomcmd "git diff-tree --stdin -p --pretty"
7574 set gitencoding [exec git config --get i18n.commitencoding]
7576 if {$gitencoding == ""} {
7577 set gitencoding "utf-8"
7579 set tclencoding [tcl_encoding $gitencoding]
7580 if {$tclencoding == {}} {
7581 puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
7584 set mainfont {Helvetica 9}
7585 set textfont {Courier 9}
7586 set uifont {Helvetica 9 bold}
7588 set findmergefiles 0
7596 set cmitmode "patch"
7597 set wrapcomment "none"
7601 set showlocalchanges 1
7603 set colors {green red blue magenta darkgrey brown orange}
7606 set diffcolors {red "#00a000" blue}
7607 set selectbgcolor gray85
7609 catch {source ~/.gitk}
7611 font create optionfont -family sans-serif -size -12
7613 # check that we can find a .git directory somewhere...
7615 if {![file isdirectory $gitdir]} {
7616 show_error {} . "Cannot find the git directory \"$gitdir\"."
7621 set cmdline_files {}
7626 "-d" { set datemode 1 }
7628 set cmdline_files [lrange $argv [expr {$i + 1}] end]
7632 lappend revtreeargs $arg
7638 if {$i >= [llength $argv] && $revtreeargs ne {}} {
7639 # no -- on command line, but some arguments (other than -d)
7641 set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
7642 set cmdline_files [split $f "\n"]
7643 set n [llength $cmdline_files]
7644 set revtreeargs [lrange $revtreeargs 0 end-$n]
7645 # Unfortunately git rev-parse doesn't produce an error when
7646 # something is both a revision and a filename. To be consistent
7647 # with git log and git rev-list, check revtreeargs for filenames.
7648 foreach arg $revtreeargs {
7649 if {[file exists $arg]} {
7650 show_error {} . "Ambiguous argument '$arg': both revision\
7656 # unfortunately we get both stdout and stderr in $err,
7657 # so look for "fatal:".
7658 set i [string first "fatal:" $err]
7660 set err [string range $err [expr {$i + 6}] end]
7662 show_error {} . "Bad arguments to gitk:\n$err"
7667 set nullid "0000000000000000000000000000000000000000"
7668 set nullid2 "0000000000000000000000000000000000000001"
7676 set highlight_paths {}
7677 set searchdirn -forwards
7681 set markingmatches 0
7688 set selectedhlview None
7697 set lookingforhead 0
7703 # wait for the window to become visible
7705 wm title . "[file tail $argv0]: [file tail [pwd]]"
7708 if {$cmdline_files ne {} || $revtreeargs ne {}} {
7709 # create a view for the files/dirs specified on the command line
7713 set viewname(1) "Command line"
7714 set viewfiles(1) $cmdline_files
7715 set viewargs(1) $revtreeargs
7718 .bar.view entryconf Edit* -state normal
7719 .bar.view entryconf Delete* -state normal
7722 if {[info exists permviews]} {
7723 foreach v $permviews {
7726 set viewname($n) [lindex $v 0]
7727 set viewfiles($n) [lindex $v 1]
7728 set viewargs($n) [lindex $v 2]